Allow public GET access to services and categories
This commit is contained in:
@@ -11,6 +11,8 @@ services:
|
|||||||
- "3306:3306"
|
- "3306:3306"
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
|
- ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
|
||||||
|
- ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
|
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
|
|||||||
5
pom.xml
5
pom.xml
@@ -43,6 +43,11 @@
|
|||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-j</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.petshop.backend.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||||
|
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocketMessageBroker
|
||||||
|
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||||
|
config.enableSimpleBroker("/topic", "/queue");
|
||||||
|
config.setApplicationDestinationPrefixes("/app");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||||
|
registry.addEndpoint("/ws/chat")
|
||||||
|
.setAllowedOriginPatterns("*")
|
||||||
|
.withSockJS();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/adoptions")
|
@RequestMapping("/api/v1/adoptions")
|
||||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
|
||||||
public class AdoptionController {
|
public class AdoptionController {
|
||||||
|
|
||||||
private final AdoptionService adoptionService;
|
private final AdoptionService adoptionService;
|
||||||
@@ -24,6 +23,7 @@ public class AdoptionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Page<AdoptionResponse>> getAllAdoptions(
|
public ResponseEntity<Page<AdoptionResponse>> getAllAdoptions(
|
||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
@@ -31,16 +31,19 @@ public class AdoptionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<AdoptionResponse> getAdoptionById(@PathVariable Long id) {
|
public ResponseEntity<AdoptionResponse> getAdoptionById(@PathVariable Long id) {
|
||||||
return ResponseEntity.ok(adoptionService.getAdoptionById(id));
|
return ResponseEntity.ok(adoptionService.getAdoptionById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<AdoptionResponse> createAdoption(@Valid @RequestBody AdoptionRequest request) {
|
public ResponseEntity<AdoptionResponse> createAdoption(@Valid @RequestBody AdoptionRequest request) {
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request));
|
return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<AdoptionResponse> updateAdoption(
|
public ResponseEntity<AdoptionResponse> updateAdoption(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@Valid @RequestBody AdoptionRequest request) {
|
@Valid @RequestBody AdoptionRequest request) {
|
||||||
@@ -48,12 +51,14 @@ public class AdoptionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Void> deleteAdoption(@PathVariable Long id) {
|
public ResponseEntity<Void> deleteAdoption(@PathVariable Long id) {
|
||||||
adoptionService.deleteAdoption(id);
|
adoptionService.deleteAdoption(id);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping
|
@DeleteMapping
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
public ResponseEntity<Void> bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
adoptionService.bulkDeleteAdoptions(request);
|
adoptionService.bulkDeleteAdoptions(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import java.util.List;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/appointments")
|
@RequestMapping("/api/v1/appointments")
|
||||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
|
||||||
public class AppointmentController {
|
public class AppointmentController {
|
||||||
|
|
||||||
private final AppointmentService appointmentService;
|
private final AppointmentService appointmentService;
|
||||||
@@ -27,6 +26,7 @@ public class AppointmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Page<AppointmentResponse>> getAllAppointments(
|
public ResponseEntity<Page<AppointmentResponse>> getAllAppointments(
|
||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
@@ -34,16 +34,19 @@ public class AppointmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<AppointmentResponse> getAppointmentById(@PathVariable Long id) {
|
public ResponseEntity<AppointmentResponse> getAppointmentById(@PathVariable Long id) {
|
||||||
return ResponseEntity.ok(appointmentService.getAppointmentById(id));
|
return ResponseEntity.ok(appointmentService.getAppointmentById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<AppointmentResponse> createAppointment(@Valid @RequestBody AppointmentRequest request) {
|
public ResponseEntity<AppointmentResponse> createAppointment(@Valid @RequestBody AppointmentRequest request) {
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(appointmentService.createAppointment(request));
|
return ResponseEntity.status(HttpStatus.CREATED).body(appointmentService.createAppointment(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<AppointmentResponse> updateAppointment(
|
public ResponseEntity<AppointmentResponse> updateAppointment(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@Valid @RequestBody AppointmentRequest request) {
|
@Valid @RequestBody AppointmentRequest request) {
|
||||||
@@ -51,12 +54,14 @@ public class AppointmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Void> deleteAppointment(@PathVariable Long id) {
|
public ResponseEntity<Void> deleteAppointment(@PathVariable Long id) {
|
||||||
appointmentService.deleteAppointment(id);
|
appointmentService.deleteAppointment(id);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping
|
@DeleteMapping
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
public ResponseEntity<Void> bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
appointmentService.bulkDeleteAppointments(request);
|
appointmentService.bulkDeleteAppointments(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/categories")
|
@RequestMapping("/api/v1/categories")
|
||||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
|
||||||
public class CategoryController {
|
public class CategoryController {
|
||||||
|
|
||||||
private final CategoryService categoryService;
|
private final CategoryService categoryService;
|
||||||
@@ -36,11 +35,13 @@ public class CategoryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<CategoryResponse> createCategory(@Valid @RequestBody CategoryRequest request) {
|
public ResponseEntity<CategoryResponse> createCategory(@Valid @RequestBody CategoryRequest request) {
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(categoryService.createCategory(request));
|
return ResponseEntity.status(HttpStatus.CREATED).body(categoryService.createCategory(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<CategoryResponse> updateCategory(
|
public ResponseEntity<CategoryResponse> updateCategory(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@Valid @RequestBody CategoryRequest request) {
|
@Valid @RequestBody CategoryRequest request) {
|
||||||
@@ -48,12 +49,14 @@ public class CategoryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Void> deleteCategory(@PathVariable Long id) {
|
public ResponseEntity<Void> deleteCategory(@PathVariable Long id) {
|
||||||
categoryService.deleteCategory(id);
|
categoryService.deleteCategory(id);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping
|
@DeleteMapping
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Void> bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
categoryService.bulkDeleteCategories(request);
|
categoryService.bulkDeleteCategories(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.chat.ConversationRequest;
|
||||||
|
import com.petshop.backend.dto.chat.ConversationResponse;
|
||||||
|
import com.petshop.backend.dto.chat.MessageRequest;
|
||||||
|
import com.petshop.backend.dto.chat.MessageResponse;
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import com.petshop.backend.service.ChatService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/chat")
|
||||||
|
public class ChatController {
|
||||||
|
|
||||||
|
private final ChatService chatService;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public ChatController(ChatService chatService, UserRepository userRepository) {
|
||||||
|
this.chatService = chatService;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
private User getCurrentUser() {
|
||||||
|
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
return userRepository.findByUsername(userDetails.getUsername())
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/conversations")
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<ConversationResponse> createConversation(@Valid @RequestBody ConversationRequest request) {
|
||||||
|
User user = getCurrentUser();
|
||||||
|
ConversationResponse response = chatService.createConversation(user.getId(), request);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/conversations")
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<List<ConversationResponse>> getConversations() {
|
||||||
|
User user = getCurrentUser();
|
||||||
|
List<ConversationResponse> conversations = chatService.getConversations(user.getId(), user.getRole());
|
||||||
|
return ResponseEntity.ok(conversations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/conversations/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<ConversationResponse> getConversation(@PathVariable Long id) {
|
||||||
|
User user = getCurrentUser();
|
||||||
|
ConversationResponse conversation = chatService.getConversation(id, user.getId(), user.getRole());
|
||||||
|
return ResponseEntity.ok(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/conversations/{id}/messages")
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<MessageResponse> sendMessage(
|
||||||
|
@PathVariable Long id,
|
||||||
|
@Valid @RequestBody MessageRequest request) {
|
||||||
|
User user = getCurrentUser();
|
||||||
|
MessageResponse message = chatService.sendMessage(id, user.getId(), request);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/conversations/{id}/messages")
|
||||||
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
|
public ResponseEntity<List<MessageResponse>> getMessages(@PathVariable Long id) {
|
||||||
|
User user = getCurrentUser();
|
||||||
|
List<MessageResponse> messages = chatService.getMessages(id, user.getId(), user.getRole());
|
||||||
|
return ResponseEntity.ok(messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/services")
|
@RequestMapping("/api/v1/services")
|
||||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
|
||||||
public class ServiceController {
|
public class ServiceController {
|
||||||
|
|
||||||
private final ServiceService serviceService;
|
private final ServiceService serviceService;
|
||||||
@@ -36,11 +35,13 @@ public class ServiceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<ServiceResponse> createService(@Valid @RequestBody ServiceRequest request) {
|
public ResponseEntity<ServiceResponse> createService(@Valid @RequestBody ServiceRequest request) {
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(serviceService.createService(request));
|
return ResponseEntity.status(HttpStatus.CREATED).body(serviceService.createService(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<ServiceResponse> updateService(
|
public ResponseEntity<ServiceResponse> updateService(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@Valid @RequestBody ServiceRequest request) {
|
@Valid @RequestBody ServiceRequest request) {
|
||||||
@@ -48,12 +49,14 @@ public class ServiceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Void> deleteService(@PathVariable Long id) {
|
public ResponseEntity<Void> deleteService(@PathVariable Long id) {
|
||||||
serviceService.deleteService(id);
|
serviceService.deleteService(id);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping
|
@DeleteMapping
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Void> bulkDeleteServices(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteServices(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
serviceService.bulkDeleteServices(request);
|
serviceService.bulkDeleteServices(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.petshop.backend.dto.chat;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class ConversationRequest {
|
||||||
|
@NotBlank(message = "Initial message is required")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ConversationRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConversationRequest(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.petshop.backend.dto.chat;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.Conversation;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class ConversationResponse {
|
||||||
|
private Long id;
|
||||||
|
private Long customerId;
|
||||||
|
private Long staffId;
|
||||||
|
private String status;
|
||||||
|
private String lastMessage;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public ConversationResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConversationResponse(Long id, Long customerId, Long staffId, String status, String lastMessage, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
|
this.id = id;
|
||||||
|
this.customerId = customerId;
|
||||||
|
this.staffId = staffId;
|
||||||
|
this.status = status;
|
||||||
|
this.lastMessage = lastMessage;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConversationResponse fromEntity(Conversation conversation, String lastMessage) {
|
||||||
|
ConversationResponse response = new ConversationResponse();
|
||||||
|
response.setId(conversation.getId());
|
||||||
|
response.setCustomerId(conversation.getCustomerId());
|
||||||
|
response.setStaffId(conversation.getStaffId());
|
||||||
|
response.setStatus(conversation.getStatus().name());
|
||||||
|
response.setLastMessage(lastMessage);
|
||||||
|
response.setCreatedAt(conversation.getCreatedAt());
|
||||||
|
response.setUpdatedAt(conversation.getUpdatedAt());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCustomerId() {
|
||||||
|
return customerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerId(Long customerId) {
|
||||||
|
this.customerId = customerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getStaffId() {
|
||||||
|
return staffId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStaffId(Long staffId) {
|
||||||
|
this.staffId = staffId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastMessage() {
|
||||||
|
return lastMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastMessage(String lastMessage) {
|
||||||
|
this.lastMessage = lastMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.petshop.backend.dto.chat;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public class MessageRequest {
|
||||||
|
@NotBlank(message = "Message content is required")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
public MessageRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageRequest(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.petshop.backend.dto.chat;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.Message;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class MessageResponse {
|
||||||
|
private Long id;
|
||||||
|
private Long conversationId;
|
||||||
|
private Long senderId;
|
||||||
|
private String content;
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
private Boolean isRead;
|
||||||
|
|
||||||
|
public MessageResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageResponse(Long id, Long conversationId, Long senderId, String content, LocalDateTime timestamp, Boolean isRead) {
|
||||||
|
this.id = id;
|
||||||
|
this.conversationId = conversationId;
|
||||||
|
this.senderId = senderId;
|
||||||
|
this.content = content;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.isRead = isRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageResponse fromEntity(Message message) {
|
||||||
|
MessageResponse response = new MessageResponse();
|
||||||
|
response.setId(message.getId());
|
||||||
|
response.setConversationId(message.getConversationId());
|
||||||
|
response.setSenderId(message.getSenderId());
|
||||||
|
response.setContent(message.getContent());
|
||||||
|
response.setTimestamp(message.getTimestamp());
|
||||||
|
response.setIsRead(message.getIsRead());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getConversationId() {
|
||||||
|
return conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConversationId(Long conversationId) {
|
||||||
|
this.conversationId = conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSenderId() {
|
||||||
|
return senderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderId(Long senderId) {
|
||||||
|
this.senderId = senderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(LocalDateTime timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsRead() {
|
||||||
|
return isRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsRead(Boolean isRead) {
|
||||||
|
this.isRead = isRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/main/java/com/petshop/backend/entity/Conversation.java
Normal file
98
src/main/java/com/petshop/backend/entity/Conversation.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package com.petshop.backend.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
import org.hibernate.annotations.UpdateTimestamp;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "conversation")
|
||||||
|
public class Conversation {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long customerId;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
private Long staffId;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(length = 20, nullable = false)
|
||||||
|
private ConversationStatus status = ConversationStatus.OPEN;
|
||||||
|
|
||||||
|
@CreationTimestamp
|
||||||
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@UpdateTimestamp
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public enum ConversationStatus {
|
||||||
|
OPEN, CLOSED
|
||||||
|
}
|
||||||
|
|
||||||
|
public Conversation() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Conversation(Long id, Long customerId, Long staffId, ConversationStatus status, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
|
this.id = id;
|
||||||
|
this.customerId = customerId;
|
||||||
|
this.staffId = staffId;
|
||||||
|
this.status = status;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCustomerId() {
|
||||||
|
return customerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerId(Long customerId) {
|
||||||
|
this.customerId = customerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getStaffId() {
|
||||||
|
return staffId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStaffId(Long staffId) {
|
||||||
|
this.staffId = staffId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConversationStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(ConversationStatus status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/main/java/com/petshop/backend/entity/Message.java
Normal file
91
src/main/java/com/petshop/backend/entity/Message.java
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package com.petshop.backend.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.CreationTimestamp;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "message")
|
||||||
|
public class Message {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long conversationId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Long senderId;
|
||||||
|
|
||||||
|
@Column(nullable = false, columnDefinition = "TEXT")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@CreationTimestamp
|
||||||
|
@Column(nullable = false, updatable = false)
|
||||||
|
private LocalDateTime timestamp;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Boolean isRead = false;
|
||||||
|
|
||||||
|
public Message() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message(Long id, Long conversationId, Long senderId, String content, LocalDateTime timestamp, Boolean isRead) {
|
||||||
|
this.id = id;
|
||||||
|
this.conversationId = conversationId;
|
||||||
|
this.senderId = senderId;
|
||||||
|
this.content = content;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.isRead = isRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getConversationId() {
|
||||||
|
return conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConversationId(Long conversationId) {
|
||||||
|
this.conversationId = conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getSenderId() {
|
||||||
|
return senderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderId(Long senderId) {
|
||||||
|
this.senderId = senderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(LocalDateTime timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getIsRead() {
|
||||||
|
return isRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsRead(Boolean isRead) {
|
||||||
|
this.isRead = isRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.Conversation;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ConversationRepository extends JpaRepository<Conversation, Long> {
|
||||||
|
List<Conversation> findByCustomerId(Long customerId);
|
||||||
|
List<Conversation> findByStaffId(Long staffId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.Message;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface MessageRepository extends JpaRepository<Message, Long> {
|
||||||
|
List<Message> findByConversationIdOrderByTimestampAsc(Long conversationId);
|
||||||
|
}
|
||||||
126
src/main/java/com/petshop/backend/service/ChatService.java
Normal file
126
src/main/java/com/petshop/backend/service/ChatService.java
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.chat.ConversationRequest;
|
||||||
|
import com.petshop.backend.dto.chat.ConversationResponse;
|
||||||
|
import com.petshop.backend.dto.chat.MessageRequest;
|
||||||
|
import com.petshop.backend.dto.chat.MessageResponse;
|
||||||
|
import com.petshop.backend.entity.Conversation;
|
||||||
|
import com.petshop.backend.entity.Message;
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
|
import com.petshop.backend.repository.ConversationRepository;
|
||||||
|
import com.petshop.backend.repository.MessageRepository;
|
||||||
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ChatService {
|
||||||
|
|
||||||
|
private final ConversationRepository conversationRepository;
|
||||||
|
private final MessageRepository messageRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
public ChatService(ConversationRepository conversationRepository,
|
||||||
|
MessageRepository messageRepository,
|
||||||
|
UserRepository userRepository) {
|
||||||
|
this.conversationRepository = conversationRepository;
|
||||||
|
this.messageRepository = messageRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ConversationResponse createConversation(Long userId, ConversationRequest request) {
|
||||||
|
User user = userRepository.findById(userId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
|
||||||
|
|
||||||
|
Conversation conversation = new Conversation();
|
||||||
|
conversation.setCustomerId(userId);
|
||||||
|
conversation.setStatus(Conversation.ConversationStatus.OPEN);
|
||||||
|
conversation = conversationRepository.save(conversation);
|
||||||
|
|
||||||
|
Message message = new Message();
|
||||||
|
message.setConversationId(conversation.getId());
|
||||||
|
message.setSenderId(userId);
|
||||||
|
message.setContent(request.getMessage());
|
||||||
|
message.setIsRead(false);
|
||||||
|
messageRepository.save(message);
|
||||||
|
|
||||||
|
return ConversationResponse.fromEntity(conversation, request.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ConversationResponse> getConversations(Long userId, User.Role role) {
|
||||||
|
List<Conversation> conversations;
|
||||||
|
|
||||||
|
if (role == User.Role.CUSTOMER) {
|
||||||
|
conversations = conversationRepository.findByCustomerId(userId);
|
||||||
|
} else if (role == User.Role.STAFF) {
|
||||||
|
conversations = conversationRepository.findByStaffId(userId);
|
||||||
|
if (conversations.isEmpty()) {
|
||||||
|
conversations = conversationRepository.findAll();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conversations = conversationRepository.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversations.stream()
|
||||||
|
.map(conv -> {
|
||||||
|
List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conv.getId());
|
||||||
|
String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent();
|
||||||
|
return ConversationResponse.fromEntity(conv, lastMessage);
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConversationResponse getConversation(Long conversationId, Long userId, User.Role role) {
|
||||||
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|
||||||
|
if (role == User.Role.CUSTOMER && !conversation.getCustomerId().equals(userId)) {
|
||||||
|
throw new AccessDeniedException("You can only view your own conversations");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
|
||||||
|
String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent();
|
||||||
|
|
||||||
|
return ConversationResponse.fromEntity(conversation, lastMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public MessageResponse sendMessage(Long conversationId, Long userId, MessageRequest request) {
|
||||||
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|
||||||
|
Message message = new Message();
|
||||||
|
message.setConversationId(conversationId);
|
||||||
|
message.setSenderId(userId);
|
||||||
|
message.setContent(request.getContent());
|
||||||
|
message.setIsRead(false);
|
||||||
|
message = messageRepository.save(message);
|
||||||
|
|
||||||
|
if (conversation.getStaffId() == null && !userId.equals(conversation.getCustomerId())) {
|
||||||
|
conversation.setStaffId(userId);
|
||||||
|
conversationRepository.save(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageResponse.fromEntity(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MessageResponse> getMessages(Long conversationId, Long userId, User.Role role) {
|
||||||
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|
||||||
|
if (role == User.Role.CUSTOMER && !conversation.getCustomerId().equals(userId)) {
|
||||||
|
throw new AccessDeniedException("You can only view messages from your own conversations");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
|
||||||
|
return messages.stream()
|
||||||
|
.map(MessageResponse::fromEntity)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user