a little cleanup

This commit is contained in:
Stepan Usatiuk
2023-12-29 18:22:43 +01:00
parent 5a70e2555d
commit bf8a2a3123
42 changed files with 538 additions and 409 deletions

View File

@@ -38,6 +38,17 @@
cursor: pointer;
color: inherit;
font-size: 0.8rem;
font-family: inherit;
}
span {
background: none;
border: none;
padding: 0;
text-decoration: none;
color: inherit;
font-size: 0.8rem;
font-family: inherit;
}
}
}

View File

@@ -9,6 +9,7 @@ export function ProfileCard({
actions,
alreadyFollowing,
isAdmin,
isFollower,
}: {
username: string;
fullName: string;
@@ -16,6 +17,7 @@ export function ProfileCard({
actions: boolean;
alreadyFollowing: boolean;
isAdmin: boolean;
isFollower: boolean;
}) {
const homeContext = useHomeContext();
@@ -85,6 +87,7 @@ export function ProfileCard({
</button>
</Form>
))}
{isFollower && <span>follows you</span>}
</div>
</div>
);

View File

@@ -14,8 +14,8 @@ export function UserList() {
return <div>Error</div>;
}
const { people, following } = loaderData;
if (isError(following) || isError(people)) {
const { people, following, followers } = loaderData;
if (isError(following) || isError(people) || isError(followers)) {
return <div>Error</div>;
}
return (
@@ -32,6 +32,7 @@ export function UserList() {
alreadyFollowing={following.some(
(f) => f.uuid == u.uuid,
)}
isFollower={followers.some((f) => f.uuid == u.uuid)}
/>
);
})}

View File

@@ -56,6 +56,10 @@ export async function getFollowing(): Promise<TPersonToArrResp> {
return fetchJSONAuth("/person/following", "GET", PersonToArrResp);
}
export async function getFollowers(): Promise<TPersonToArrResp> {
return fetchJSONAuth("/person/followers", "GET", PersonToArrResp);
}
export async function getPersonByUsername(
username: string,
): Promise<TPersonToResp> {

View File

@@ -31,7 +31,7 @@ export async function getPostsByAuthorUuid(
author: string,
): Promise<TPostToArrResp> {
return fetchJSONAuth(
`/post/by-author-uuid?author=${author}`,
`/post/by-author-uuid/${author}`,
"GET",
PostToArrResp,
);
@@ -45,7 +45,7 @@ export async function getPostsByAuthorUsername(
author: string,
): Promise<TPostToArrResp> {
return fetchJSONAuth(
`/post/by-author-username?author=${author}`,
`/post/by-author-username/${author}`,
"GET",
PostToArrResp,
);

View File

@@ -1,5 +1,6 @@
import {
getAllPerson,
getFollowers,
getFollowing,
getPersonByUsername,
getSelf,
@@ -36,7 +37,11 @@ export async function homeLoader() {
}
export async function userListLoader() {
return { people: await getAllPerson(), following: await getFollowing() };
return {
people: await getAllPerson(),
following: await getFollowing(),
followers: await getFollowers(),
};
}
export async function profileLoader({

View File

@@ -3,22 +3,15 @@ package com.usatiuk.tjv.y.server.controller;
import com.usatiuk.tjv.y.server.dto.MessageCreateTo;
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;
import java.util.Collection;
@RestController
@RequestMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE)
@@ -36,54 +29,29 @@ public class MessageController {
}
@GetMapping(path = "/by-chat/{chatTd}")
public Stream<MessageTo> 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, authentication.getName());
if (!chat.getMembers().contains(userRef))
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat");
return chat.getMessages().stream().map(messageMapper::makeDto);
public Collection<MessageTo> get(Authentication authentication, @PathVariable Long chatTd) {
return messageService.getByChat(authentication, chatTd);
}
@PostMapping(path = "/by-chat/{chatId}")
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, authentication.getName());
if (!chat.getMembers().contains(userRef))
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat");
Message message = new Message().setChat(chat).setAuthor(userRef).setContents(messageCreateTo.contents());
messageService.create(message);
return messageMapper.makeDto(message);
return messageService.addToChat(authentication, chatId, messageCreateTo);
}
@PatchMapping(path = "/by-id/{id}")
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(), authentication.getName()))
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
message.setContents(messageCreateTo.contents());
messageService.update(message);
return messageMapper.makeDto(message);
return messageService.update(authentication, id, messageCreateTo);
}
@DeleteMapping(path = "/by-id/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(Authentication authentication, @PathVariable long id) {
var read = messageService.readById(id);
if (read.isEmpty()) return;
if (!Objects.equals(read.get().getAuthor().getId(), authentication.getName())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
messageService.deleteById(id);
messageService.delete(authentication, id);
}
@GetMapping
public Stream<MessageTo> 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);
public Collection<MessageTo> getAll(Authentication authentication) {
return messageService.readAll(authentication);
}

View File

@@ -3,9 +3,6 @@ package com.usatiuk.tjv.y.server.controller;
import com.usatiuk.tjv.y.server.dto.PersonCreateTo;
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;
@@ -13,14 +10,10 @@ 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;
import java.util.Optional;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.Collection;
@RestController
@RequestMapping(value = "/person", produces = MediaType.APPLICATION_JSON_VALUE)
@@ -39,103 +32,60 @@ public class PersonController {
@PostMapping
public PersonTo signup(@RequestBody PersonCreateTo signupRequest) throws UserAlreadyExistsException {
Person toCreate = new Person();
toCreate.setUsername(signupRequest.username())
.setPassword(signupRequest.password())
.setFullName(signupRequest.fullName());
Person created = personService.signup(toCreate);
return personMapper.makeDto(created);
return personService.signup(signupRequest);
}
@GetMapping(path = "/by-username/{username}")
public PersonTo getByUsername(@PathVariable String username) throws UserNotFoundException {
Optional<Person> found = personService.readByUsername(username);
if (found.isEmpty()) throw new UserNotFoundException();
return personMapper.makeDto(found.get());
return personService.readByUsername(username);
}
@GetMapping(path = "/by-uuid/{uuid}")
public PersonTo getByUuid(@PathVariable String uuid) throws UserNotFoundException {
Optional<Person> found = personService.readById(uuid);
if (found.isEmpty()) throw new UserNotFoundException();
return personMapper.makeDto(found.get());
return personService.readByUuid(uuid);
}
@GetMapping(path = "/self")
public PersonTo getSelf(Authentication authentication) throws UserNotFoundException {
Optional<Person> found = personService.readById(authentication.getName());
if (found.isEmpty()) throw new UserNotFoundException();
return personMapper.makeDto(found.get());
return personService.readSelf(authentication);
}
@PatchMapping(path = "/self")
public PersonTo update(Authentication authentication, @RequestBody PersonCreateTo personCreateTo) {
var person = personService.readById(authentication.getName()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
person.setUsername(personCreateTo.username())
.setFullName(personCreateTo.fullName());
if (!personCreateTo.password().isEmpty()) person.setPassword(passwordEncoder.encode(personCreateTo.password()));
personService.update(person);
return personMapper.makeDto(person);
return personService.update(authentication, personCreateTo);
}
@DeleteMapping(path = "/self")
@ResponseStatus(HttpStatus.NO_CONTENT)
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(authentication.getName());
personService.deleteSelf(authentication);
}
@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());
personService.deleteByUuid(authentication, uuid);
}
@GetMapping
public Stream<PersonTo> getAll() throws UserNotFoundException {
return StreamSupport.stream(personService.readAll().spliterator(), false).map(personMapper::makeDto);
public Collection<PersonTo> getAll() throws UserNotFoundException {
return personService.readAll();
}
@GetMapping(path = "/followers")
public Stream<PersonTo> getFollowers(Authentication authentication) throws UserNotFoundException {
return personService.getFollowers(authentication.getName()).stream().map(personMapper::makeDto);
public Collection<PersonTo> getFollowers(Authentication authentication) throws UserNotFoundException {
return personService.getFollowers(authentication);
}
@GetMapping(path = "/following")
public Stream<PersonTo> getFollowing(Authentication authentication) throws UserNotFoundException {
return personService.getFollowing(authentication.getName()).stream().map(personMapper::makeDto);
public Collection<PersonTo> getFollowing(Authentication authentication) throws UserNotFoundException {
return personService.getFollowing(authentication);
}
@GetMapping(path = "/admins")
public Stream<PersonTo> getAdmins() {
return personService.getAdmins().stream().map(personMapper::makeDto);
public Collection<PersonTo> getAdmins() {
return personService.getAdmins();
}
@PutMapping(path = "/admins/{uuid}")
@@ -150,17 +100,16 @@ public class PersonController {
personService.removeAdmin(authentication, uuid);
}
@PutMapping(path = "/following/{uuid}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void addFollowing(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException {
personService.addFollower(authentication.getName(), uuid);
personService.addFollower(authentication, uuid);
}
@DeleteMapping(path = "/following/{uuid}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteFollowing(Authentication authentication, @PathVariable String uuid) throws UserNotFoundException {
personService.removeFollower(authentication.getName(), uuid);
personService.removeFollower(authentication, uuid);
}
}

View File

@@ -3,99 +3,63 @@ package com.usatiuk.tjv.y.server.controller;
import com.usatiuk.tjv.y.server.dto.PostCreateTo;
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;
import java.util.Collection;
@RestController
@RequestMapping(value = "/post", produces = MediaType.APPLICATION_JSON_VALUE)
public class PostController {
private final PostService postService;
private final PostMapper postMapper;
private final EntityManager entityManager;
public PostController(PostService postService, PostMapper postMapper, EntityManager entityManager) {
this.postService = postService;
this.postMapper = postMapper;
this.entityManager = entityManager;
}
@PostMapping
public PostTo createPost(Authentication authentication, @RequestBody PostCreateTo postCreateTo) {
Post post = new Post();
post.setAuthor(entityManager.getReference(Person.class, authentication.getName()));
post.setText(postCreateTo.text());
return postMapper.makeDto(postService.create(post));
return postService.createPost(authentication, postCreateTo);
}
@GetMapping(path = "/by-author-uuid")
public Stream<PostTo> readAllByAuthorUuid(@RequestParam Optional<String> author) {
if (author.isPresent())
return postService.readByAuthorId(author.get()).stream().map(postMapper::makeDto);
else
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
@GetMapping(path = "/by-author-uuid/{uuid}")
public Collection<PostTo> readAllByAuthorUuid(@PathVariable String uuid) {
return postService.readByAuthorId(uuid);
}
@GetMapping(path = "/by-author-username")
public Stream<PostTo> readAllByAuthorUsername(@RequestParam Optional<String> author) {
if (author.isPresent())
return postService.readByAuthorUsername(author.get()).stream().map(postMapper::makeDto);
else
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
@GetMapping(path = "/by-author-username/{username}")
public Collection<PostTo> readAllByAuthorUsername(@PathVariable String username) {
return postService.readByAuthorUsername(username);
}
@GetMapping(path = "/by-following")
public Stream<PostTo> readAllByFollowees(Authentication authentication) {
return postService.readByPersonFollowees(authentication.getName()).stream().map(postMapper::makeDto);
public Collection<PostTo> readAllByFollowees(Authentication authentication) {
return postService.readByPersonFollowees(authentication);
}
@GetMapping(path = "/{id}")
public PostTo get(@PathVariable long id) {
var post = postService.readById(id);
if (post.isEmpty()) throw new ResponseStatusException(HttpStatus.NOT_FOUND);
return postMapper.makeDto(post.get());
return postService.readById(id);
}
@PatchMapping(path = "/{id}")
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(), authentication.getName()))
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
post.setText(postCreateTo.text());
postService.update(post);
return postMapper.makeDto(post);
return postService.updatePost(authentication, id, postCreateTo);
}
@DeleteMapping(path = "/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(Authentication authentication, @PathVariable long id) {
var read = postService.readById(id);
if (read.isEmpty()) return;
if (!Objects.equals(read.get().getAuthor().getId(), authentication.getName())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
postService.deleteById(id);
postService.deletePost(authentication, id);
}
@GetMapping
public Stream<PostTo> 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);
public Collection<PostTo> getAll(Authentication authentication) {
return postService.readAll(authentication);
}
}

View File

@@ -2,35 +2,28 @@ package com.usatiuk.tjv.y.server.controller;
import com.usatiuk.tjv.y.server.dto.TokenRequestTo;
import com.usatiuk.tjv.y.server.dto.TokenResponseTo;
import com.usatiuk.tjv.y.server.entity.Person;
import com.usatiuk.tjv.y.server.service.LoginTokenService;
import com.usatiuk.tjv.y.server.service.PersonService;
import com.usatiuk.tjv.y.server.service.TokenService;
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.*;
import org.springframework.web.server.ResponseStatusException;
import java.util.Optional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/token", produces = MediaType.APPLICATION_JSON_VALUE)
public class TokenController {
private final PersonService personService;
private final TokenService tokenService;
private final LoginTokenService loginTokenService;
public TokenController(PersonService personService, TokenService tokenService) {
public TokenController(PersonService personService, LoginTokenService loginTokenService) {
this.personService = personService;
this.tokenService = tokenService;
this.loginTokenService = loginTokenService;
}
@PostMapping
public TokenResponseTo request(@RequestBody TokenRequestTo tokenRequestTo) throws UserNotFoundException {
Optional<Person> found = personService.login(tokenRequestTo.username(), tokenRequestTo.password());
if (found.isEmpty()) throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Couldn't find user");
return new TokenResponseTo(tokenService.generateToken(found.get().getId()));
public TokenResponseTo request(@RequestBody TokenRequestTo tokenRequestTo) {
return loginTokenService.login(tokenRequestTo);
}
}

View File

@@ -3,5 +3,8 @@ package com.usatiuk.tjv.y.server.repository;
import com.usatiuk.tjv.y.server.entity.Message;
import org.springframework.data.repository.CrudRepository;
import java.util.Collection;
public interface MessageRepository extends CrudRepository<Message, Long> {
Collection<Message> findByChat_Id(Long chatId);
}

View File

@@ -1,6 +1,7 @@
package com.usatiuk.tjv.y.server.repository;
import com.usatiuk.tjv.y.server.entity.Person;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import java.util.Collection;
@@ -9,6 +10,9 @@ import java.util.Optional;
public interface PersonRepository extends CrudRepository<Person, String> {
Optional<Person> findByUsername(String username);
@Query(value = "SELECT p from Person p where p.uuid = :usernameOrId or p.username = :usernameOrId")
Optional<Person> findByUsernameOrId(String usernameOrId);
boolean existsByUsername(String username);
Collection<Person> findByAdminIsTrue();

View File

@@ -0,0 +1,20 @@
package com.usatiuk.tjv.y.server.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@Configuration
public class AuthenticationManagerConfig {
@Bean
public AuthenticationManager authManager(HttpSecurity http, DaoAuthenticationProvider daoAuthenticationProvider) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.authenticationProvider(daoAuthenticationProvider);
return authenticationManagerBuilder.build();
}
}

View File

@@ -0,0 +1,20 @@
package com.usatiuk.tjv.y.server.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class DaoAuthenticationProviderConf {
@Bean
public DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
}

View File

@@ -1,6 +1,5 @@
package com.usatiuk.tjv.y.server.security;
import com.usatiuk.tjv.y.server.service.TokenService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -18,12 +17,12 @@ import java.util.Optional;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final TokenService tokenService;
private final JwtUserDetailsService jwtUserDetailsService;
private final JwtTokenService jwtTokenService;
private final UserDetailsService userDetailsService;
public JwtRequestFilter(TokenService tokenService, JwtUserDetailsService jwtUserDetailsService) {
this.tokenService = tokenService;
this.jwtUserDetailsService = jwtUserDetailsService;
public JwtRequestFilter(JwtTokenService jwtTokenService, UserDetailsService userDetailsService) {
this.jwtTokenService = jwtTokenService;
this.userDetailsService = userDetailsService;
}
@Override
@@ -36,13 +35,13 @@ public class JwtRequestFilter extends OncePerRequestFilter {
}
String token = header.substring(7);
Optional<String> userUuid = tokenService.parseToken(token);
Optional<String> userUuid = jwtTokenService.getPersonUuidFromToken(token);
if (userUuid.isEmpty()) {
filterChain.doFilter(request, response);
return;
}
UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(userUuid.get());
UserDetails userDetails = userDetailsService.loadUserByUsername(userUuid.get());
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());

View File

@@ -1,10 +1,8 @@
package com.usatiuk.tjv.y.server.service;
package com.usatiuk.tjv.y.server.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -16,28 +14,25 @@ import java.util.Date;
import java.util.Optional;
@Service
public class TokenServiceImpl implements TokenService {
private static final Duration JWT_EXPIRY = Duration.ofMinutes(20);
public class JwtTokenService {
private final SecretKey key;
private final Duration jwtExpiry;
public TokenServiceImpl(@Value("${jwt.secret}") String secret) {
// FIXME:
public JwtTokenService(@Value("${jwt.secret}") String secret, @Value("${jwt.expiryMinutes}") Long expiry) {
this.key = Keys.hmacShaKeyFor(secret.getBytes());
this.jwtExpiry = Duration.ofMinutes(expiry);
}
@Override
public String generateToken(String personUuid) {
Instant now = Instant.now();
return Jwts.builder()
.subject(personUuid)
.expiration(Date.from(now.plus(JWT_EXPIRY)))
.expiration(Date.from(now.plus(jwtExpiry)))
.signWith(key, Jwts.SIG.HS512)
.compact();
}
@Override
public Optional<String> parseToken(String token) {
public Optional<String> getPersonUuidFromToken(String token) {
try {
Claims claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload();
if (claims.getExpiration().before(new Date())) return Optional.empty();
@@ -46,4 +41,5 @@ public class TokenServiceImpl implements TokenService {
return Optional.empty();
}
}
}

View File

@@ -1,14 +0,0 @@
package com.usatiuk.tjv.y.server.security;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
public class JwtUser extends User {
public JwtUser(String uuid, String hash,
Collection<? extends GrantedAuthority> authorities) {
super(uuid, hash, authorities);
}
}

View File

@@ -1,33 +0,0 @@
package com.usatiuk.tjv.y.server.security;
import com.usatiuk.tjv.y.server.entity.Person;
import com.usatiuk.tjv.y.server.service.PersonService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
public class JwtUserDetailsService implements UserDetailsService {
private final PersonService personService;
public JwtUserDetailsService(PersonService personService) {
this.personService = personService;
}
@Override
public UserDetails loadUserByUsername(String uuid) {
Optional<Person> person = personService.readById(uuid);
if (!person.isPresent()) throw new UsernameNotFoundException("User with UUID " + uuid + " not found");
ArrayList<SimpleGrantedAuthority> 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);
}
}

View File

@@ -0,0 +1,13 @@
package com.usatiuk.tjv.y.server.security;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
public class User extends org.springframework.security.core.userdetails.User {
public User(String uuid, String hash,
Collection<? extends GrantedAuthority> authorities) {
super(uuid, hash, authorities);
}
}

View File

@@ -0,0 +1,30 @@
package com.usatiuk.tjv.y.server.security;
import com.usatiuk.tjv.y.server.entity.Person;
import com.usatiuk.tjv.y.server.repository.PersonRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
private final PersonRepository personRepository;
public UserDetailsService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
@Override
public UserDetails loadUserByUsername(String usernameOrUuid) {
Person person = personRepository.findByUsernameOrId(usernameOrUuid).orElseThrow(() -> new UsernameNotFoundException("Username not found"));
ArrayList<SimpleGrantedAuthority> roles =
new ArrayList<>(List.of(new SimpleGrantedAuthority(UserRoles.ROLE_USER.name())));
if (person.isAdmin()) roles.add(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name()));
return new User(person.getUuid(), person.getPassword(), roles);
}
}

View File

@@ -60,7 +60,7 @@ public class WebSecurityConfig {
return http.cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(mvc.pattern(HttpMethod.GET, "/post/*")).permitAll()
.requestMatchers(mvc.pattern(HttpMethod.GET, "/post/**")).permitAll()
.requestMatchers(mvc.pattern(HttpMethod.GET, "/post*")).permitAll()
.requestMatchers(mvc.pattern(HttpMethod.POST, "/person")).permitAll()
.requestMatchers(mvc.pattern(HttpMethod.GET, "/person")).permitAll()
@@ -75,7 +75,6 @@ public class WebSecurityConfig {
.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

View File

@@ -3,12 +3,11 @@ package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.dto.ChatCreateTo;
import com.usatiuk.tjv.y.server.dto.ChatTo;
import com.usatiuk.tjv.y.server.dto.PersonTo;
import com.usatiuk.tjv.y.server.entity.Chat;
import org.springframework.security.core.Authentication;
import java.util.Collection;
public interface ChatService extends CrudService<Chat, Long> {
public interface ChatService {
ChatTo create(Authentication authentication, ChatCreateTo chatCreateTo);
ChatTo update(Authentication authentication, Long id, ChatCreateTo chatCreateTo);
@@ -18,4 +17,6 @@ public interface ChatService extends CrudService<Chat, Long> {
void deleteById(Authentication authentication, Long id);
Collection<PersonTo> getMembers(Authentication authentication, Long id);
boolean isMemberOf(String personUuid, Long chatId);
}

View File

@@ -9,7 +9,6 @@ import com.usatiuk.tjv.y.server.entity.Chat;
import com.usatiuk.tjv.y.server.entity.Person;
import com.usatiuk.tjv.y.server.repository.ChatRepository;
import jakarta.persistence.EntityManager;
import org.springframework.data.repository.CrudRepository;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
@@ -21,7 +20,7 @@ import java.util.Collection;
import java.util.Objects;
@Service
public class ChatServiceImpl extends CrudServiceImpl<Chat, Long> implements ChatService {
public class ChatServiceImpl implements ChatService {
private final ChatRepository chatRepository;
private final ChatMapper chatMapper;
@@ -35,12 +34,6 @@ public class ChatServiceImpl extends CrudServiceImpl<Chat, Long> implements Chat
this.entityManager = entityManager;
}
@Override
protected CrudRepository<Chat, Long> getRepository() {
return chatRepository;
}
@Override
public ChatTo create(Authentication authentication, ChatCreateTo chatCreateTo) {
var chat = new Chat();
@@ -63,7 +56,7 @@ public class ChatServiceImpl extends CrudServiceImpl<Chat, Long> implements Chat
@Override
public ChatTo update(Authentication authentication, Long id, ChatCreateTo chatCreateTo) {
var chat = readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
var chat = chatRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
if (!Objects.equals(chat.getCreator().getUuid(), authentication.getName()))
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't creator of the chat");
@@ -89,7 +82,7 @@ public class ChatServiceImpl extends CrudServiceImpl<Chat, Long> implements Chat
@Override
public ChatTo getById(Authentication authentication, Long id) {
var chat = readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
var chat = chatRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
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");
@@ -99,18 +92,25 @@ public class ChatServiceImpl extends CrudServiceImpl<Chat, Long> implements Chat
@Override
public void deleteById(Authentication authentication, Long id) {
var chat = readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
var chat = chatRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
if (!Objects.equals(chat.getCreator().getUuid(), authentication.getName()))
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't creator of the chat");
deleteById(id);
chatRepository.delete(chat);
}
@Override
public Collection<PersonTo> getMembers(Authentication authentication, Long id) {
var chat = readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
var chat = chatRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
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).toList();
}
@Override
public boolean isMemberOf(String personUuid, Long chatId) {
var chat = chatRepository.findById(chatId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
var userRef = entityManager.getReference(Person.class, personUuid);
return chat.getMembers().contains(userRef);
}
}

View File

@@ -1,18 +0,0 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.entity.EntityWithId;
import java.io.Serializable;
import java.util.Optional;
public interface CrudService<T extends EntityWithId<ID>, ID extends Serializable> {
T create(T e);
Optional<T> readById(ID id);
Iterable<T> readAll();
void update(T e);
void deleteById(ID id);
}

View File

@@ -1,40 +0,0 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.entity.EntityWithId;
import org.springframework.data.repository.CrudRepository;
import java.io.Serializable;
import java.util.Optional;
public abstract class CrudServiceImpl<T extends EntityWithId<ID>, ID extends Serializable>
implements CrudService<T, ID> {
@Override
public T create(T e) {
if (e == null || (e.getId() != null && getRepository().existsById(e.getId())))
throw new IllegalArgumentException();
return getRepository().save(e);
}
@Override
public Optional<T> readById(ID id) {
return getRepository().findById(id);
}
@Override
public Iterable<T> readAll() {
return getRepository().findAll();
}
@Override
public void update(T e) {
getRepository().save(e);
}
@Override
public void deleteById(ID id) {
getRepository().deleteById(id);
}
protected abstract CrudRepository<T, ID> getRepository();
}

View File

@@ -0,0 +1,8 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.dto.TokenRequestTo;
import com.usatiuk.tjv.y.server.dto.TokenResponseTo;
public interface LoginTokenService {
TokenResponseTo login(TokenRequestTo tokenRequestTo);
}

View File

@@ -0,0 +1,29 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.dto.TokenRequestTo;
import com.usatiuk.tjv.y.server.dto.TokenResponseTo;
import com.usatiuk.tjv.y.server.security.JwtTokenService;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
@Service
public class LoginTokenServiceImpl implements LoginTokenService {
private final AuthenticationManager authenticationManager;
private final JwtTokenService jwtTokenService;
public LoginTokenServiceImpl(AuthenticationManager authenticationManager, JwtTokenService jwtTokenService) {
this.authenticationManager = authenticationManager;
this.jwtTokenService = jwtTokenService;
}
@Override
public TokenResponseTo login(TokenRequestTo tokenRequestTo) {
Authentication authentication =
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(tokenRequestTo.username(), tokenRequestTo.password()));
String uuid = authentication.getName();
String token = jwtTokenService.generateToken(uuid);
return new TokenResponseTo(token);
}
}

View File

@@ -1,6 +1,17 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.entity.Message;
import com.usatiuk.tjv.y.server.dto.MessageCreateTo;
import com.usatiuk.tjv.y.server.dto.MessageTo;
import org.springframework.security.core.Authentication;
public interface MessageService extends CrudService<Message, Long> {
import java.util.Collection;
public interface MessageService {
Collection<MessageTo> getByChat(Authentication authentication, Long chatId);
MessageTo addToChat(Authentication authentication, Long chatId, MessageCreateTo messageCreateTo);
MessageTo update(Authentication authentication, Long id, MessageCreateTo msg);
void delete(Authentication authentication, Long id);
Collection<MessageTo> readAll(Authentication authentication);
}

View File

@@ -1,21 +1,85 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.dto.MessageCreateTo;
import com.usatiuk.tjv.y.server.dto.MessageTo;
import com.usatiuk.tjv.y.server.dto.converters.MessageMapper;
import com.usatiuk.tjv.y.server.entity.Chat;
import com.usatiuk.tjv.y.server.entity.Message;
import com.usatiuk.tjv.y.server.entity.Person;
import com.usatiuk.tjv.y.server.repository.MessageRepository;
import org.springframework.data.repository.CrudRepository;
import com.usatiuk.tjv.y.server.security.UserRoles;
import jakarta.persistence.EntityManager;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.StreamSupport;
@Service
public class MessageServiceImpl extends CrudServiceImpl<Message, Long> implements MessageService {
public class MessageServiceImpl implements MessageService {
private final MessageRepository messageRepository;
private final MessageMapper messageMapper;
private final ChatService chatService;
private final EntityManager entityManager;
public MessageServiceImpl(MessageRepository messageRepository) {
public MessageServiceImpl(MessageRepository messageRepository, MessageMapper messageMapper, ChatService chatService, EntityManager entityManager) {
this.messageRepository = messageRepository;
this.messageMapper = messageMapper;
this.chatService = chatService;
this.entityManager = entityManager;
}
@Override
protected CrudRepository<Message, Long> getRepository() {
return messageRepository;
public Collection<MessageTo> getByChat(Authentication authentication, Long chatId) {
if (!chatService.isMemberOf(authentication.getName(), chatId))
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat");
return messageRepository.findByChat_Id(chatId).stream().map(messageMapper::makeDto).toList();
}
@Override
public MessageTo addToChat(Authentication authentication, Long chatId, MessageCreateTo messageCreateTo) {
if (!chatService.isMemberOf(authentication.getName(), chatId))
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat");
Message message = new Message().setChat(entityManager.getReference(Chat.class, chatId))
.setAuthor(entityManager.getReference(Person.class, authentication.getName()))
.setContents(messageCreateTo.contents());
messageRepository.save(message);
return messageMapper.makeDto(message);
}
@Override
public MessageTo update(Authentication authentication, Long id, MessageCreateTo msg) {
var message = messageRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (!Objects.equals(message.getAuthor().getUuid(), authentication.getName()))
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
message.setContents(msg.contents());
messageRepository.save(message);
return messageMapper.makeDto(message);
}
@Override
public void delete(Authentication authentication, Long id) {
var read = messageRepository.findById(id);
if (read.isEmpty()) return;
if (!Objects.equals(read.get().getAuthor().getId(), authentication.getName())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
messageRepository.delete(read.get());
}
@Override
public Collection<MessageTo> readAll(Authentication authentication) {
if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name())))
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
return StreamSupport.stream(messageRepository.findAll().spliterator(), false).map(messageMapper::makeDto).toList();
}
}

View File

@@ -1,28 +1,37 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.dto.PersonCreateTo;
import com.usatiuk.tjv.y.server.dto.PersonTo;
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;
public interface PersonService extends CrudService<Person, String> {
Person signup(Person person) throws UserAlreadyExistsException;
public interface PersonService {
PersonTo signup(PersonCreateTo person);
Optional<Person> login(String username, String password);
Optional<Person> readByUsername(String username);
PersonTo readByUsername(String username);
PersonTo readByUuid(String uuid);
PersonTo readSelf(Authentication authentication);
Collection<Person> getFollowers(String uuid) throws UserNotFoundException;
Collection<Person> getFollowing(String uuid) throws UserNotFoundException;
PersonTo update(Authentication authentication, PersonCreateTo person);
void addFollower(String follower, String followee) throws UserNotFoundException;
void removeFollower(String follower, String followee) throws UserNotFoundException;
void deleteSelf(Authentication authentication);
void deleteByUuid(Authentication authentication, String uuid);
Collection<Person> getAdmins();
void addAdmin(Authentication caller, String uuid) throws UserNotFoundException;
void removeAdmin(Authentication caller, String uuid) throws UserNotFoundException;
Collection<PersonTo> readAll();
Collection<PersonTo> getFollowers(Authentication authentication);
Collection<PersonTo> getFollowing(Authentication authentication);
void addFollower(Authentication authentication, String followee);
void removeFollower(Authentication authentication, String followee);
Collection<PersonTo> getAdmins();
void addAdmin(Authentication caller, String uuid);
void removeAdmin(Authentication caller, String uuid);
}

View File

@@ -1,12 +1,15 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.dto.PersonCreateTo;
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.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;
@@ -15,36 +18,41 @@ import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.StreamSupport;
@Service
public class PersonServiceImpl extends CrudServiceImpl<Person, String> implements PersonService {
public class PersonServiceImpl implements PersonService {
private final PersonRepository personRepository;
private final PasswordEncoder passwordEncoder;
private final EntityManager entityManager;
private final PersonMapper personMapper;
public PersonServiceImpl(PersonRepository personRepository,
PasswordEncoder passwordEncoder, EntityManager entityManager) {
PasswordEncoder passwordEncoder, EntityManager entityManager, PersonMapper personMapper) {
this.personRepository = personRepository;
this.passwordEncoder = passwordEncoder;
this.entityManager = entityManager;
this.personMapper = personMapper;
}
@Override
protected CrudRepository<Person, String> getRepository() {
return personRepository;
}
@Override
public Person signup(Person person) throws UserAlreadyExistsException {
if (personRepository.existsByUsername(person.getUsername()))
public PersonTo signup(PersonCreateTo signupRequest) {
if (personRepository.existsByUsername(signupRequest.username()))
throw new UserAlreadyExistsException();
person.setPassword(passwordEncoder.encode(person.getPassword()));
Person toCreate = new Person();
if (personRepository.findByAdminIsTrue().isEmpty()) person.setAdmin(true);
toCreate.setUsername(signupRequest.username())
.setPassword(signupRequest.password())
.setFullName(signupRequest.fullName());
return create(person);
toCreate.setPassword(passwordEncoder.encode(signupRequest.password()));
if (personRepository.findByAdminIsTrue().isEmpty()) toCreate.setAdmin(true);
return personMapper.makeDto(personRepository.save(toCreate));
}
@Override
@@ -57,36 +65,90 @@ public class PersonServiceImpl extends CrudServiceImpl<Person, String> implement
}
@Override
public Optional<Person> readByUsername(String username) {
return personRepository.findByUsername(username);
public PersonTo readByUsername(String username) {
return personMapper.makeDto(personRepository.findByUsername(username).orElseThrow(UserNotFoundException::new));
}
@Override
public Collection<Person> getFollowers(String uuid) throws UserNotFoundException {
return personRepository.findById(uuid).orElseThrow(UserNotFoundException::new).getFollowers();
public PersonTo readByUuid(String uuid) {
return personMapper.makeDto(personRepository.findById(uuid).orElseThrow(UserNotFoundException::new));
}
@Override
public Collection<Person> getFollowing(String uuid) throws UserNotFoundException {
return personRepository.findById(uuid).orElseThrow(UserNotFoundException::new).getFollowing();
public PersonTo readSelf(Authentication authentication) {
return readByUuid(authentication.getName());
}
@Override
public void addFollower(String follower, String followee) throws UserNotFoundException {
var person = personRepository.findById(follower).orElseThrow(UserNotFoundException::new);
public PersonTo update(Authentication authentication, PersonCreateTo person) {
var found = personRepository.findById(authentication.getName()).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
found.setUsername(person.username())
.setFullName(person.fullName());
if (!person.password().isEmpty()) found.setPassword(passwordEncoder.encode(person.password()));
personRepository.save(found);
return personMapper.makeDto(found);
}
private void deleteByUuid(String uuid) {
var person = personRepository.findById(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
for (Chat c : person.getChats()) {
c.getMembers().remove(person);
}
for (Person p : person.getFollowers()) {
p.getFollowing().remove(person);
personRepository.save(p);
}
personRepository.delete(person);
}
@Override
public void deleteSelf(Authentication authentication) {
deleteByUuid(authentication.getName());
}
@Override
public void deleteByUuid(Authentication authentication, String uuid) {
if ((!Objects.equals(authentication.getName(), uuid)) &&
!authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name())))
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
deleteByUuid(uuid);
}
@Override
public Collection<PersonTo> readAll() {
return StreamSupport.stream(personRepository.findAll().spliterator(), false).map(personMapper::makeDto).toList();
}
@Override
public Collection<PersonTo> getFollowers(Authentication authentication) {
return personRepository.findById(authentication.getName()).orElseThrow(UserNotFoundException::new)
.getFollowers().stream().map(personMapper::makeDto).toList();
}
@Override
public Collection<PersonTo> getFollowing(Authentication authentication) {
return personRepository.findById(authentication.getName()).orElseThrow(UserNotFoundException::new)
.getFollowing().stream().map(personMapper::makeDto).toList();
}
@Override
public void addFollower(Authentication authentication, String followee) {
var person = personRepository.findById(authentication.getName()).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);
public void removeFollower(Authentication authentication, String followee) {
var person = personRepository.findById(authentication.getName()).orElseThrow(UserNotFoundException::new);
person.getFollowing().remove(entityManager.getReference(Person.class, followee));
personRepository.save(person);
}
@Override
public void addAdmin(Authentication caller, String uuid) throws UserNotFoundException {
public void addAdmin(Authentication caller, String uuid) {
if (!caller.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name())))
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
@@ -96,7 +158,7 @@ public class PersonServiceImpl extends CrudServiceImpl<Person, String> implement
}
@Override
public void removeAdmin(Authentication caller, String uuid) throws UserNotFoundException {
public void removeAdmin(Authentication caller, String uuid) {
if (!caller.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name())))
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
@@ -109,7 +171,7 @@ public class PersonServiceImpl extends CrudServiceImpl<Person, String> implement
}
@Override
public Collection<Person> getAdmins() {
return personRepository.findByAdminIsTrue();
public Collection<PersonTo> getAdmins() {
return personRepository.findByAdminIsTrue().stream().map(personMapper::makeDto).toList();
}
}

View File

@@ -1,13 +1,22 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.entity.Post;
import com.usatiuk.tjv.y.server.dto.PostCreateTo;
import com.usatiuk.tjv.y.server.dto.PostTo;
import org.springframework.security.core.Authentication;
import java.util.Collection;
public interface PostService extends CrudService<Post, Long> {
Collection<Post> readByAuthorId(String authorUuid);
public interface PostService {
PostTo createPost(Authentication authentication, PostCreateTo postCreateTo);
PostTo updatePost(Authentication authentication, Long id, PostCreateTo postCreateTo);
void deletePost(Authentication authentication, Long id);
Collection<Post> readByAuthorUsername(String authorUsername);
PostTo readById(Long id);
Collection<Post> readByPersonFollowees(String personUuid);
Collection<PostTo> readByAuthorId(String authorUuid);
Collection<PostTo> readByAuthorUsername(String authorUsername);
Collection<PostTo> readByPersonFollowees(Authentication authentication);
Collection<PostTo> readAll(Authentication authentication);
}

View File

@@ -1,37 +1,89 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.dto.PostCreateTo;
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.repository.PostRepository;
import org.springframework.data.repository.CrudRepository;
import com.usatiuk.tjv.y.server.security.UserRoles;
import jakarta.persistence.EntityManager;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.StreamSupport;
@Service
public class PostServiceImpl extends CrudServiceImpl<Post, Long> implements PostService {
public class PostServiceImpl implements PostService {
private final PostRepository postRepository;
private final PostMapper postMapper;
private final EntityManager entityManager;
public PostServiceImpl(PostRepository postRepository) {
public PostServiceImpl(PostRepository postRepository, PostMapper postMapper, EntityManager entityManager) {
this.postRepository = postRepository;
this.postMapper = postMapper;
this.entityManager = entityManager;
}
@Override
protected CrudRepository<Post, Long> getRepository() {
return postRepository;
public PostTo createPost(Authentication authentication, PostCreateTo postCreateTo) {
Post post = new Post();
post.setAuthor(entityManager.getReference(Person.class, authentication.getName()));
post.setText(postCreateTo.text());
return postMapper.makeDto(postRepository.save(post));
}
@Override
public Collection<Post> readByAuthorId(String authorId) {
return postRepository.findByAuthorUuid(authorId);
public PostTo readById(Long id) {
return postMapper.makeDto(postRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found")));
}
@Override
public Collection<Post> readByAuthorUsername(String authorUsername) {
return postRepository.findByAuthorUsername(authorUsername);
public Collection<PostTo> readByAuthorId(String authorId) {
return postRepository.findByAuthorUuid(authorId).stream().map(postMapper::makeDto).toList();
}
@Override
public Collection<Post> readByPersonFollowees(String personUuid) {
return postRepository.findByPersonFollowees(personUuid);
public Collection<PostTo> readByAuthorUsername(String authorUsername) {
return postRepository.findByAuthorUsername(authorUsername).stream().map(postMapper::makeDto).toList();
}
@Override
public Collection<PostTo> readByPersonFollowees(Authentication authentication) {
return postRepository.findByPersonFollowees(authentication.getName()).stream().map(postMapper::makeDto).toList();
}
@Override
public PostTo updatePost(Authentication authentication, Long id, PostCreateTo postCreateTo) {
var post = postRepository.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
if (!Objects.equals(post.getAuthor().getUuid(), authentication.getName()))
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
post.setText(postCreateTo.text());
postRepository.save(post);
return postMapper.makeDto(post);
}
@Override
public void deletePost(Authentication authentication, Long id) {
var read = postRepository.findById(id);
if (read.isEmpty()) return;
if (!Objects.equals(read.get().getAuthor().getId(), authentication.getName())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
}
postRepository.delete(read.get());
}
@Override
public Collection<PostTo> readAll(Authentication authentication) {
if (!authentication.getAuthorities().contains(new SimpleGrantedAuthority(UserRoles.ROLE_ADMIN.name())))
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
return StreamSupport.stream(postRepository.findAll().spliterator(), false).map(postMapper::makeDto).toList();
}
}

View File

@@ -1,9 +0,0 @@
package com.usatiuk.tjv.y.server.service;
import java.util.Optional;
public interface TokenService {
String generateToken(String personUuid);
Optional<String> parseToken(String token);
}

View File

@@ -4,7 +4,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.CONFLICT)
public class UserAlreadyExistsException extends Exception {
public class UserAlreadyExistsException extends RuntimeException {
public UserAlreadyExistsException() {
super();
}

View File

@@ -4,11 +4,11 @@ import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends Exception {
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException() {
super();
}
public UserNotFoundException(String message) {
super(message);
}

View File

@@ -1,4 +1,5 @@
jwt.secret=JKLASJKLASJKLJHKLDFAHJKFDSHJKFJHKDSHJKFHJKSDFJHKSDJHKFJHKS98346783467899782345jkhgsdoigh938g
jwt.expiryMinutes=20
logging.level.root=DEBUG
logging.level.org.springframework.security=DEBUG
spring.datasource.url=jdbc:h2:file:~/tjvserver.h2

View File

@@ -109,7 +109,7 @@ public class ChatControllerTest extends DemoDataDbTest {
@Test
void shouldNotChatUnauthorized() {
void shouldNotGetChatUnauthorized() {
var response = restTemplate.exchange(addr + "/chat/by-id/" + chat1.getId(), HttpMethod.GET,
new HttpEntity<>(createAuthHeaders(person3Auth)),
ErrorTo.class);

View File

@@ -2,12 +2,14 @@ package com.usatiuk.tjv.y.server.controller;
import com.usatiuk.tjv.y.server.dto.TokenResponseTo;
import com.usatiuk.tjv.y.server.entity.Chat;
import com.usatiuk.tjv.y.server.entity.Message;
import com.usatiuk.tjv.y.server.entity.Person;
import com.usatiuk.tjv.y.server.entity.Post;
import com.usatiuk.tjv.y.server.repository.ChatRepository;
import com.usatiuk.tjv.y.server.repository.MessageRepository;
import com.usatiuk.tjv.y.server.repository.PersonRepository;
import com.usatiuk.tjv.y.server.repository.PostRepository;
import com.usatiuk.tjv.y.server.service.TokenService;
import com.usatiuk.tjv.y.server.security.JwtTokenService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
@@ -35,13 +37,15 @@ public abstract class DemoDataDbTest {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private TokenService tokenService;
private JwtTokenService jwtTokenService;
@Autowired
private PersonRepository personRepository;
@Autowired
private PostRepository postRepository;
@Autowired
private ChatRepository chatRepository;
@Autowired
private MessageRepository messageRepository;
protected static final String person1Password = "p1p";
protected Person person1;
@@ -59,6 +63,11 @@ public abstract class DemoDataDbTest {
protected Chat chat1;
protected Chat chat2;
protected Message message1;
protected Message message2;
protected Message message3;
protected Message message4;
protected HttpHeaders createAuthHeaders(TokenResponseTo personAuth) {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
@@ -74,20 +83,20 @@ public abstract class DemoDataDbTest {
.setUsername("person1")
.setFullName("Person 1")
.setPassword(passwordEncoder.encode(person1Password)));
person1Auth = new TokenResponseTo(tokenService.generateToken(person1.getUuid()));
person1Auth = new TokenResponseTo(jwtTokenService.generateToken(person1.getUuid()));
person2 = personRepository.save(
new Person()
.setUsername("person2")
.setFullName("Person 2")
.setPassword(passwordEncoder.encode(person2Password)).setFollowing(List.of(person1)));
person2Auth = new TokenResponseTo(tokenService.generateToken(person2.getUuid()));
person2Auth = new TokenResponseTo(jwtTokenService.generateToken(person2.getUuid()));
person3 = personRepository.save(
new Person()
.setUsername("person3")
.setFullName("Person 3")
.setPassword(passwordEncoder.encode(person3Password))
.setFollowing(List.of(person2, person1)));
person3Auth = new TokenResponseTo(tokenService.generateToken(person3.getUuid()));
person3Auth = new TokenResponseTo(jwtTokenService.generateToken(person3.getUuid()));
post1 = postRepository.save(new Post().setAuthor(person1).setText("post 1"));
post2 = postRepository.save(new Post().setAuthor(person2).setText("post 2"));
@@ -102,12 +111,17 @@ public abstract class DemoDataDbTest {
.setCreator(person3)
.setMembers(List.of(person2, person3))
.setName("Chat 1"));
message1 = messageRepository.save(new Message().setAuthor(person1).setChat(chat1).setContents("message 1"));
message2 = messageRepository.save(new Message().setAuthor(person2).setChat(chat1).setContents("message2"));
message3 = messageRepository.save(new Message().setAuthor(person2).setChat(chat2).setContents("message 3"));
message4 = messageRepository.save(new Message().setAuthor(person3).setChat(chat2).setContents("message 4"));
}
@AfterEach
void erase() {
assert !TestTransaction.isActive();
JdbcTestUtils.deleteFromTables(jdbcTemplate, "person_follows", "chat_person", "post", "chat", "message", "person");
JdbcTestUtils.deleteFromTables(jdbcTemplate, "person_follows", "chat_person", "post", "message", "chat", "person");
}
}

View File

@@ -63,7 +63,7 @@ public class PostControllerTest extends DemoDataDbTest {
@Test
void shouldGetByAuthor() {
var response = restTemplate.exchange(addr + "/post/by-author-uuid?author=" + person1.getUuid(), HttpMethod.GET,
var response = restTemplate.exchange(addr + "/post/by-author-uuid/" + person1.getUuid(), HttpMethod.GET,
HttpEntity.EMPTY, PostTo[].class);
Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());

View File

@@ -2,7 +2,7 @@ package com.usatiuk.tjv.y.server.controller;
import com.usatiuk.tjv.y.server.dto.TokenRequestTo;
import com.usatiuk.tjv.y.server.dto.TokenResponseTo;
import com.usatiuk.tjv.y.server.service.TokenService;
import com.usatiuk.tjv.y.server.security.JwtTokenService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -13,7 +13,7 @@ import org.springframework.http.HttpStatus;
public class TokenControllerTest extends DemoDataDbTest {
@Autowired
private TokenService tokenService;
private JwtTokenService jwtTokenService;
@Test
void shouldLogin() {
@@ -26,7 +26,7 @@ public class TokenControllerTest extends DemoDataDbTest {
TokenResponseTo parsedResponse = response.getBody();
Assertions.assertNotNull(parsedResponse);
Assertions.assertTrue(tokenService.parseToken(parsedResponse.token()).isPresent());
Assertions.assertTrue(jwtTokenService.getPersonUuidFromToken(parsedResponse.token()).isPresent());
}

View File

@@ -1,3 +1,4 @@
jwt.secret=FLJKDSDKLJFJKLISDAHJKFHOUIJOHUIJFHUOI$UIHGOUIOFG$#UIOYFOUYIG#$UIOHDUGHIOHUGIHJFHJLKDFHJLKDHJLKFSJDHKFHJKLSH
jwt.expiryMinutes=20
logging.level.root=DEBUG
logging.level.org.springframework.security=DEBUG