feat: Implemented chat

This commit is contained in:
2024-12-23 14:42:01 +01:00
parent ce1710cfef
commit 4bb9a31ffa
18 changed files with 394 additions and 8 deletions

View File

@ -9,6 +9,7 @@ import cz.jzitnik.chronos.payload.responses.TakeItemsResponse;
import cz.jzitnik.chronos.payload.responses.UnifiedResponse;
import cz.jzitnik.chronos.repository.CharacterRepository;
import cz.jzitnik.chronos.repository.PlayerRepository;
import cz.jzitnik.chronos.services.ItemService;
import cz.jzitnik.chronos.utils.anotations.CheckUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
@ -28,6 +29,9 @@ public class CharacterController {
@Autowired
private CharacterRepository characterRepository;
@Autowired
private ItemService itemService;
@GetMapping("/interaction")
@CheckUser
public ResponseEntity<UnifiedResponse<Interaction, Error>> getInteractionData(@RequestParam String playerKey, @RequestParam Long characterId) {
@ -99,7 +103,7 @@ public class CharacterController {
for (Item item : character.getInventory()) {
var itemClone = new Item(item.getItemType(), player);
player.addItem(itemClone);
itemService.addItem(player, itemClone);
}
player.getSeenCharacters().add(character);

View File

@ -0,0 +1,58 @@
package cz.jzitnik.chronos.controllers;
import cz.jzitnik.chronos.entities.Message;
import cz.jzitnik.chronos.payload.requests.MessageRequest;
import cz.jzitnik.chronos.payload.responses.UnifiedResponse;
import cz.jzitnik.chronos.repository.MessageRepository;
import cz.jzitnik.chronos.repository.PlayerRepository;
import cz.jzitnik.chronos.utils.anotations.CheckUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/game/chat")
public class ChatController {
@Autowired
private MessageRepository messageRepository;
@Autowired
private PlayerRepository playerRepository;
@GetMapping
@CheckUser
public ResponseEntity<UnifiedResponse<List<Message>, Error>> getMessages(@RequestParam String playerKey) {
var player = playerRepository.findByPlayerKey(playerKey).get();
var gameId = player.getGame().getId();
Pageable pageable = PageRequest.of(0, 20);
var messages = messageRepository.findLastMessagesByGameId(gameId, pageable);
return ResponseEntity.ok(UnifiedResponse.success(messages));
}
@PostMapping
@CheckUser
public ResponseEntity<UnifiedResponse<Object, Error>> sendMessage(@RequestParam String playerKey, @RequestBody MessageRequest messageRequest) {
var player = playerRepository.findByPlayerKey(playerKey).get();
if (messageRequest.getContent().length() > 100 || messageRequest.getContent().isEmpty()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(
UnifiedResponse.failure(new Error("Message cannot be longer than 100 characters and cannot be empty!"))
);
}
var message = new Message(player, messageRequest.getContent());
messageRepository.save(message);
return ResponseEntity.ok(UnifiedResponse.success(null));
}
}

View File

@ -1,5 +1,6 @@
package cz.jzitnik.chronos.controllers;
import cz.jzitnik.chronos.entities.Item;
import cz.jzitnik.chronos.entities.Player;
import cz.jzitnik.chronos.payload.errors.ItemNotUsableException;
import cz.jzitnik.chronos.payload.errors.NotFoundError;
@ -87,4 +88,17 @@ public class PlayerController {
return ResponseEntity.ok(UnifiedResponse.success(null));
}
@GetMapping("/item")
@CheckUser
public ResponseEntity<UnifiedResponse<Item, Error>> getItemInfo(@RequestParam String playerKey, @RequestParam Long itemId) {
var itemOptional = itemRepository.findById(itemId);
if (itemOptional.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(UnifiedResponse.failure(new NotFoundError("Item was not found!")));
}
var item = itemOptional.get();
return ResponseEntity.ok(UnifiedResponse.success(item));
}
}

View File

@ -1,6 +1,9 @@
package cz.jzitnik.chronos.controllers;
import cz.jzitnik.chronos.entities.Message;
import cz.jzitnik.chronos.entities.MessageType;
import cz.jzitnik.chronos.entities.Room;
import cz.jzitnik.chronos.payload.errors.NotFoundError;
import cz.jzitnik.chronos.payload.responses.UnifiedResponse;
import cz.jzitnik.chronos.repository.PlayerRepository;
import cz.jzitnik.chronos.repository.RoomRepository;
@ -35,6 +38,30 @@ public class RoomController {
);
}
@GetMapping("/room")
@CheckUser
public ResponseEntity<UnifiedResponse<Room, Error>> getRoom(@RequestParam("playerKey") String playerKey, @RequestParam("roomId") Long roomId) {
var player = playerRepository.findByPlayerKey(playerKey).get();
var roomOptional = roomRepository.findById(roomId);
if (roomOptional.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
UnifiedResponse.failure(new NotFoundError("Room with id " + roomId + " was not found!"))
);
}
var room = roomOptional.get();
if (!room.getGame().getId().equals(player.getGame().getId())) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(
UnifiedResponse.failure(new Error("This room is from a different game!"))
);
}
return ResponseEntity.ok(UnifiedResponse.success(room));
}
@GetMapping("/current_room")
@CheckUser
public ResponseEntity<UnifiedResponse<Room, Error>> getCurrentRoom(@RequestParam("playerKey") String playerKey) {
@ -64,6 +91,11 @@ public class RoomController {
player.setCurrentRoom(roomOptional.get());
// Send message to chat
var message = new Message(player, String.valueOf(roomId), MessageType.MOVE_TO_ROOM);
player.getMessages().add(message);
playerRepository.save(player);
return ResponseEntity.ok(UnifiedResponse.success(null));

View File

@ -0,0 +1,36 @@
package cz.jzitnik.chronos.entities;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Player author;
private String content;
@Enumerated(EnumType.STRING)
private MessageType messageType;
public Message(Player author, String content) {
this.author = author;
this.content = content;
this.messageType = MessageType.CUSTOM;
}
public Message(Player author, String content, MessageType messageType) {
this.author = author;
this.content = content;
this.messageType = messageType;
}
}

View File

@ -0,0 +1,8 @@
package cz.jzitnik.chronos.entities;
public enum MessageType {
CUSTOM,
MOVE_TO_ROOM,
GOT_ITEM,
JOINED,
}

View File

@ -42,6 +42,10 @@ public class Player {
private boolean luck = false;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
@JsonIgnore
private List<Message> messages = new ArrayList<>();
@ManyToMany
@JoinTable(
name = "character_player",

View File

@ -7,11 +7,11 @@ import cz.jzitnik.chronos.entities.Character;
import cz.jzitnik.chronos.payload.responses.InteractionResponse;
import cz.jzitnik.chronos.repository.CharacterRepository;
import cz.jzitnik.chronos.repository.PlayerRepository;
import cz.jzitnik.chronos.services.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Optional;
import java.util.Random;
@Service
@ -22,6 +22,9 @@ public class InteractionService {
@Autowired
private CharacterRepository characterRepository;
@Autowired
private ItemService itemService;
@FunctionalInterface
public interface Function3<T, U, V, W> {
W apply(T t, U u, V v);
@ -51,8 +54,7 @@ public class InteractionService {
if (playerWins) {
var item = new Item(ItemType.KEY_FRAGMENT, player);
player.addItem(item);
playerRepository.save(player);
itemService.addItem(player, item);
character.setInteractedWith(true);
characterRepository.save(character);
@ -72,8 +74,7 @@ public class InteractionService {
return new InteractionResponse(false, "Remíza. Jestli chceš můžeš si se mnou zahrát ještě jednou.", new ArrayList<>());
} else if ((userChoice == 0 && characterChoice == 2) || (userChoice == 1 && characterChoice == 0) || (userChoice == 2 && characterChoice == 1)) {
var item = new Item(ItemType.KEY_FRAGMENT, player);
player.addItem(item);
playerRepository.save(player);
itemService.addItem(player, item);
character.setInteractedWith(true);
characterRepository.save(character);

View File

@ -0,0 +1,10 @@
package cz.jzitnik.chronos.payload.requests;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Getter
public class MessageRequest {
private String content;
}

View File

@ -0,0 +1,19 @@
package cz.jzitnik.chronos.repository;
import cz.jzitnik.chronos.entities.Message;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface MessageRepository extends JpaRepository<Message, Long> {
@Query("""
SELECT m FROM Message m
WHERE m.author.game.id = :gameId
ORDER BY m.id DESC
""")
List<Message> findLastMessagesByGameId(@Param("gameId") Long gameId, Pageable pageable);
}

View File

@ -0,0 +1,28 @@
package cz.jzitnik.chronos.services;
import cz.jzitnik.chronos.entities.Message;
import cz.jzitnik.chronos.entities.MessageType;
import cz.jzitnik.chronos.entities.Player;
import cz.jzitnik.chronos.entities.Item;
import cz.jzitnik.chronos.repository.ItemRepository;
import cz.jzitnik.chronos.repository.PlayerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ItemService {
@Autowired
private PlayerRepository playerRepository;
@Autowired
private ItemRepository itemRepository;
public void addItem(Player player, Item item) {
var itemSaved = itemRepository.save(item);
var message = new Message(player, String.valueOf(itemSaved.getId()), MessageType.GOT_ITEM);
player.getMessages().add(message);
playerRepository.save(player);
}
}