creating chats

This commit is contained in:
Stepan Usatiuk
2023-12-22 11:03:58 +01:00
parent b2e377f8c4
commit fbaed180cf
25 changed files with 300 additions and 28 deletions

View File

@@ -0,0 +1,58 @@
package com.usatiuk.tjv.y.server.controller;
import com.usatiuk.tjv.y.server.dto.ChatCreateTo;
import com.usatiuk.tjv.y.server.dto.ChatTo;
import com.usatiuk.tjv.y.server.dto.converters.ChatMapper;
import com.usatiuk.tjv.y.server.entity.Chat;
import com.usatiuk.tjv.y.server.entity.Person;
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.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.security.Principal;
import java.util.Arrays;
import java.util.Objects;
@RestController
@RequestMapping(value = "/chat", produces = MediaType.APPLICATION_JSON_VALUE)
public class ChatController {
private final EntityManager entityManager;
private final ChatService chatService;
private final ChatMapper chatMapper;
public ChatController(EntityManager entityManager, ChatService chatService, ChatMapper chatMapper) {
this.entityManager = entityManager;
this.chatService = chatService;
this.chatMapper = chatMapper;
}
@PostMapping
public ChatTo create(Principal principal, @RequestBody ChatCreateTo chatCreateTo) {
var chat = new Chat();
if (Arrays.stream(chatCreateTo.memberUuids()).noneMatch(n -> Objects.equals(n, principal.getName())))
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Creator of chat must be its member");
chat.setCreator(entityManager.getReference(Person.class, principal.getName()));
chat.setMembers(Arrays.stream(chatCreateTo.memberUuids()).map(
p -> entityManager.getReference(Person.class, p)
).toList());
chat.setName(chatCreateTo.name());
chatService.create(chat);
return chatMapper.makeDto(chat);
}
@PostMapping(path = "/by-id/:id")
public ChatTo get(Principal principal, @PathVariable Long id) {
var chat = chatService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
if (!chat.getMembers().contains(entityManager.getReference(Person.class, principal.getName())))
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat");
return chatMapper.makeDto(chat);
}
}

View File

@@ -0,0 +1,10 @@
package com.usatiuk.tjv.y.server.controller;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE)
public class MessageController {
}

View File

@@ -20,9 +20,11 @@ import java.util.stream.StreamSupport;
@RequestMapping(value = "/person", produces = MediaType.APPLICATION_JSON_VALUE)
public class PersonController {
private final PersonService personService;
private final PersonMapper personMapper;
public PersonController(PersonService personService) {
public PersonController(PersonService personService, PersonMapper personMapper) {
this.personService = personService;
this.personMapper = personMapper;
}
@PostMapping
@@ -34,7 +36,7 @@ public class PersonController {
Person created = personService.signup(toCreate);
return PersonMapper.makeDto(created);
return personMapper.makeDto(created);
}
@GetMapping(path = "/by-username/{username}")
@@ -43,7 +45,7 @@ public class PersonController {
if (found.isEmpty()) throw new UserNotFoundException();
return PersonMapper.makeDto(found.get());
return personMapper.makeDto(found.get());
}
@GetMapping(path = "/by-uuid/{uuid}")
@@ -52,7 +54,7 @@ public class PersonController {
if (found.isEmpty()) throw new UserNotFoundException();
return PersonMapper.makeDto(found.get());
return personMapper.makeDto(found.get());
}
@@ -62,22 +64,22 @@ public class PersonController {
if (found.isEmpty()) throw new UserNotFoundException();
return PersonMapper.makeDto(found.get());
return personMapper.makeDto(found.get());
}
@GetMapping
public Stream<PersonTo> getAll() throws UserNotFoundException {
return StreamSupport.stream(personService.readAll().spliterator(), false).map(PersonMapper::makeDto);
return StreamSupport.stream(personService.readAll().spliterator(), false).map(personMapper::makeDto);
}
@GetMapping(path = "/followers")
public Stream<PersonTo> getFollowers(Principal principal) throws UserNotFoundException {
return personService.getFollowers(principal.getName()).stream().map(PersonMapper::makeDto);
return personService.getFollowers(principal.getName()).stream().map(personMapper::makeDto);
}
@GetMapping(path = "/following")
public Stream<PersonTo> getFollowing(Principal principal) throws UserNotFoundException {
return personService.getFollowing(principal.getName()).stream().map(PersonMapper::makeDto);
return personService.getFollowing(principal.getName()).stream().map(personMapper::makeDto);
}
@PutMapping(path = "/following/{uuid}")

View File

@@ -21,10 +21,12 @@ import java.util.stream.Stream;
@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, EntityManager entityManager) {
public PostController(PostService postService, PostMapper postMapper, EntityManager entityManager) {
this.postService = postService;
this.postMapper = postMapper;
this.entityManager = entityManager;
}
@@ -33,13 +35,13 @@ public class PostController {
Post post = new Post();
post.setAuthor(entityManager.getReference(Person.class, principal.getName()));
post.setText(postCreateTo.text());
return PostMapper.makeDto(postService.create(post));
return postMapper.makeDto(postService.create(post));
}
@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);
return postService.readByAuthorId(author.get()).stream().map(postMapper::makeDto);
else
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
@@ -47,21 +49,21 @@ public class PostController {
@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);
return postService.readByAuthorUsername(author.get()).stream().map(postMapper::makeDto);
else
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
}
@GetMapping(path = "/by-following")
public Stream<PostTo> readAllByFollowees(Principal principal) {
return postService.readByPersonFollowees(principal.getName()).stream().map(PostMapper::makeDto);
return postService.readByPersonFollowees(principal.getName()).stream().map(postMapper::makeDto);
}
@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 postMapper.makeDto(post.get());
}
@PatchMapping(path = "/{id}")
@@ -71,7 +73,7 @@ public class PostController {
throw new ResponseStatusException(HttpStatus.FORBIDDEN);
post.setText(postCreateTo.text());
postService.update(post);
return PostMapper.makeDto(post);
return postMapper.makeDto(post);
}
@DeleteMapping(path = "/{id}")

View File

@@ -0,0 +1,4 @@
package com.usatiuk.tjv.y.server.dto;
public record ChatCreateTo(String name, String[] memberUuids) {
}

View File

@@ -0,0 +1,5 @@
package com.usatiuk.tjv.y.server.dto;
public record ChatTo(Long id, String name, String creatorUuid, PersonTo[] memberUuids, MessageTo[] messages) {
}

View File

@@ -0,0 +1,4 @@
package com.usatiuk.tjv.y.server.dto;
public record MessageTo(Long id, Long chatId, String authorUuid, String contents) {
}

View File

@@ -0,0 +1,25 @@
package com.usatiuk.tjv.y.server.dto.converters;
import com.usatiuk.tjv.y.server.dto.ChatTo;
import com.usatiuk.tjv.y.server.dto.MessageTo;
import com.usatiuk.tjv.y.server.dto.PersonTo;
import com.usatiuk.tjv.y.server.entity.Chat;
import org.springframework.stereotype.Component;
@Component
public class ChatMapper {
private final PersonMapper personMapper;
private final MessageMapper messageMapper;
public ChatMapper(PersonMapper personMapper, MessageMapper messageMapper) {
this.personMapper = personMapper;
this.messageMapper = messageMapper;
}
public ChatTo makeDto(Chat chat) {
return new ChatTo(chat.getId(), chat.getName(), chat.getCreator().getUuid(),
chat.getMembers().stream().map(personMapper::makeDto).toArray(PersonTo[]::new),
chat.getMessages().stream().map(messageMapper::makeDto).toArray(MessageTo[]::new));
}
}

View File

@@ -0,0 +1,13 @@
package com.usatiuk.tjv.y.server.dto.converters;
import com.usatiuk.tjv.y.server.dto.MessageTo;
import com.usatiuk.tjv.y.server.entity.Message;
import org.springframework.stereotype.Component;
@Component
public class MessageMapper {
public MessageTo makeDto(Message message) {
return new MessageTo(message.getId(), message.getChat().getId(),
message.getAuthor().getUuid(), message.getContents());
}
}

View File

@@ -2,9 +2,11 @@ package com.usatiuk.tjv.y.server.dto.converters;
import com.usatiuk.tjv.y.server.dto.PersonTo;
import com.usatiuk.tjv.y.server.entity.Person;
import org.springframework.stereotype.Component;
@Component
public class PersonMapper {
public static PersonTo makeDto(Person person) {
public PersonTo makeDto(Person person) {
return new PersonTo(person.getUuid(), person.getUsername(), person.getFullName());
}
}

View File

@@ -2,9 +2,11 @@ package com.usatiuk.tjv.y.server.dto.converters;
import com.usatiuk.tjv.y.server.dto.PostTo;
import com.usatiuk.tjv.y.server.entity.Post;
import org.springframework.stereotype.Component;
@Component
public class PostMapper {
public static PostTo makeDto(Post post) {
public PostTo makeDto(Post post) {
return new PostTo(post.getId(), post.getAuthor().getUuid(), post.getAuthor().getUsername(), post.getText(), post.getCreatedAt().getEpochSecond());
}
}

View File

@@ -29,6 +29,9 @@ public class Chat implements EntityWithId<Long> {
private Collection<Message> messages = new ArrayList<>();
@ManyToMany(mappedBy = "chats")
private Collection<Person> users;
private Collection<Person> members = new ArrayList<>();
@ManyToOne
private Person creator;
}

View File

@@ -37,11 +37,14 @@ public class Person implements EntityWithId<String> {
@OneToMany(mappedBy = "author")
private Collection<Post> posts = new ArrayList<>();
@OneToMany(mappedBy = "creator")
private Collection<Chat> createdChats = new ArrayList<>();
@OneToMany(mappedBy = "author")
private Collection<Message> messages = new ArrayList<>();
@ManyToMany
@JoinTable(name = "user_follows",
@JoinTable(name = "person_follows",
joinColumns = @JoinColumn(name = "follower"),
inverseJoinColumns = @JoinColumn(name = "followee"))
private Collection<Person> following;
@@ -50,8 +53,8 @@ public class Person implements EntityWithId<String> {
private Collection<Person> followers;
@ManyToMany
@JoinTable(name = "user_chat",
joinColumns = @JoinColumn(name = "user"),
@JoinTable(name = "person_chat",
joinColumns = @JoinColumn(name = "person"),
inverseJoinColumns = @JoinColumn(name = "chat"))
private Collection<Chat> chats;

View File

@@ -0,0 +1,10 @@
package com.usatiuk.tjv.y.server.repository;
import com.usatiuk.tjv.y.server.entity.Chat;
import org.springframework.data.repository.CrudRepository;
import java.util.Optional;
public interface ChatRepository extends CrudRepository<Chat, Long> {
Optional<Chat> findByName(String name);
}

View File

@@ -0,0 +1,7 @@
package com.usatiuk.tjv.y.server.repository;
import com.usatiuk.tjv.y.server.entity.Message;
import org.springframework.data.repository.CrudRepository;
public interface MessageRepository extends CrudRepository<Message, Long> {
}

View File

@@ -67,6 +67,7 @@ public class WebSecurityConfig {
.requestMatchers(mvc.pattern(HttpMethod.GET, "/person/by-username/*")).permitAll()
.requestMatchers(mvc.pattern(HttpMethod.GET, "/person/by-uuid/*")).permitAll()
.requestMatchers(mvc.pattern(HttpMethod.POST, "/token")).permitAll()
.requestMatchers(mvc.pattern("/error")).permitAll()
.anyRequest().hasAuthority(UserRoles.ROLE_USER.name()))
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)

View File

@@ -0,0 +1,6 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.entity.Chat;
public interface ChatService extends CrudService<Chat, Long> {
}

View File

@@ -0,0 +1,21 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.entity.Chat;
import com.usatiuk.tjv.y.server.repository.ChatRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Service;
@Service
public class ChatServiceImpl extends CrudServiceImpl<Chat, Long> implements ChatService {
private final ChatRepository chatRepository;
public ChatServiceImpl(ChatRepository chatRepository) {
this.chatRepository = chatRepository;
}
@Override
protected CrudRepository<Chat, Long> getRepository() {
return chatRepository;
}
}

View File

@@ -0,0 +1,6 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.entity.Message;
public interface MessageService extends CrudService<Message, Long> {
}

View File

@@ -0,0 +1,21 @@
package com.usatiuk.tjv.y.server.service;
import com.usatiuk.tjv.y.server.entity.Message;
import com.usatiuk.tjv.y.server.repository.MessageRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Service;
@Service
public class MessageServiceImpl extends CrudServiceImpl<Message, Long> implements MessageService {
private final MessageRepository messageRepository;
public MessageServiceImpl(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
@Override
protected CrudRepository<Message, Long> getRepository() {
return messageRepository;
}
}

View File

@@ -0,0 +1,41 @@
package com.usatiuk.tjv.y.server.controller;
import com.usatiuk.tjv.y.server.dto.ChatCreateTo;
import com.usatiuk.tjv.y.server.dto.ChatTo;
import com.usatiuk.tjv.y.server.dto.converters.PersonMapper;
import com.usatiuk.tjv.y.server.repository.ChatRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import java.util.Arrays;
import java.util.stream.Stream;
public class ChatControllerTest extends DemoDataDbTest {
@Autowired
private PersonMapper personMapper;
@Autowired
private ChatRepository chatRepository;
@Test
void shouldCreateChat() {
var response = restTemplate.exchange(addr + "/chat", HttpMethod.POST,
new HttpEntity<>(new ChatCreateTo("chatnew", new String[]{person1.getUuid(), person2.getUuid()}), createAuthHeaders(person1Auth)),
ChatTo.class);
Assertions.assertNotNull(response);
Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
var toResponse = response.getBody();
Assertions.assertNotNull(toResponse);
Assertions.assertEquals("chatnew", toResponse.name());
Assertions.assertEquals(person1.getUuid(), toResponse.creatorUuid());
Assertions.assertIterableEquals(Stream.of(person1, person2).map(personMapper::makeDto).toList(), Arrays.asList(toResponse.memberUuids()));
Assertions.assertTrue(chatRepository.findByName("chatnew").isPresent());
}
}

View File

@@ -1,8 +1,10 @@
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.Person;
import com.usatiuk.tjv.y.server.entity.Post;
import com.usatiuk.tjv.y.server.repository.ChatRepository;
import com.usatiuk.tjv.y.server.repository.PersonRepository;
import com.usatiuk.tjv.y.server.repository.PostRepository;
import com.usatiuk.tjv.y.server.service.TokenService;
@@ -38,6 +40,8 @@ public abstract class DemoDataDbTest {
private PersonRepository personRepository;
@Autowired
private PostRepository postRepository;
@Autowired
private ChatRepository chatRepository;
protected static final String person1Password = "p1p";
protected Person person1;
@@ -52,6 +56,9 @@ public abstract class DemoDataDbTest {
protected Post post1;
protected Post post2;
protected Chat chat1;
protected Chat chat2;
protected HttpHeaders createAuthHeaders(TokenResponseTo personAuth) {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
@@ -84,12 +91,23 @@ public abstract class DemoDataDbTest {
post1 = postRepository.save(new Post().setAuthor(person1).setText("post 1"));
post2 = postRepository.save(new Post().setAuthor(person2).setText("post 2"));
chat1 = chatRepository.save(
new Chat()
.setCreator(person1)
.setMembers(List.of(person1, person2))
.setName("Chat 1"));
chat2 = chatRepository.save(
new Chat()
.setCreator(person3)
.setMembers(List.of(person2, person3))
.setName("Chat 1"));
}
@AfterEach
void erase() {
assert !TestTransaction.isActive();
JdbcTestUtils.deleteFromTables(jdbcTemplate, "user_follows", "post", "person");
JdbcTestUtils.deleteFromTables(jdbcTemplate, "person_follows", "person_chat", "post", "chat", "message", "person");
}
}

View File

@@ -18,6 +18,9 @@ public class PersonControllerTest extends DemoDataDbTest {
@Autowired
private PersonRepository personRepository;
@Autowired
private PersonMapper personMapper;
@Test
void shouldSignUp() {
var response = restTemplate.exchange(addr + "/person", HttpMethod.POST,
@@ -89,7 +92,7 @@ public class PersonControllerTest extends DemoDataDbTest {
Assertions.assertNotNull(personToResponse);
Assertions.assertEquals(2, personToResponse.length);
Assertions.assertIterableEquals(Arrays.asList(personToResponse), List.of(PersonMapper.makeDto(person2), PersonMapper.makeDto(person3)));
Assertions.assertIterableEquals(Arrays.asList(personToResponse), List.of(personMapper.makeDto(person2), personMapper.makeDto(person3)));
}
@Test
@@ -116,7 +119,7 @@ public class PersonControllerTest extends DemoDataDbTest {
Assertions.assertNotNull(personToResponse);
Assertions.assertEquals(2, personToResponse.length);
Assertions.assertIterableEquals(Arrays.asList(personToResponse), List.of(PersonMapper.makeDto(person2), PersonMapper.makeDto(person1)));
Assertions.assertIterableEquals(Arrays.asList(personToResponse), List.of(personMapper.makeDto(person2), personMapper.makeDto(person1)));
}
@Test
@@ -137,7 +140,7 @@ public class PersonControllerTest extends DemoDataDbTest {
Assertions.assertNotNull(personToResponse);
Assertions.assertEquals(1, personToResponse.length);
Assertions.assertIterableEquals(Arrays.asList(personToResponse), List.of(PersonMapper.makeDto(person3)));
Assertions.assertIterableEquals(Arrays.asList(personToResponse), List.of(personMapper.makeDto(person3)));
}
}

View File

@@ -18,6 +18,9 @@ public class PostControllerTest extends DemoDataDbTest {
@Autowired
private PostRepository postRepository;
@Autowired
private PostMapper postMapper;
@Test
void shouldNotCreatePostWithoutAuth() {
Long postsBefore = postRepository.count();
@@ -70,7 +73,7 @@ public class PostControllerTest extends DemoDataDbTest {
Assertions.assertNotNull(parsedResponse);
Assertions.assertTrue(parsedResponse.length > 0);
Assertions.assertEquals(parsedResponse.length, repoResponse.size());
Assertions.assertIterableEquals(Arrays.asList(parsedResponse), repoResponse.stream().map(PostMapper::makeDto).toList());
Assertions.assertIterableEquals(Arrays.asList(parsedResponse), repoResponse.stream().map(postMapper::makeDto).toList());
}
@Test
@@ -83,6 +86,6 @@ public class PostControllerTest extends DemoDataDbTest {
Assertions.assertNotNull(parsedResponse);
Assertions.assertEquals(2, parsedResponse.length);
Assertions.assertIterableEquals(Arrays.asList(parsedResponse), List.of(PostMapper.makeDto(post1), PostMapper.makeDto(post2)));
Assertions.assertIterableEquals(Arrays.asList(parsedResponse), List.of(postMapper.makeDto(post1), postMapper.makeDto(post2)));
}
}

View File

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