Skip to content

Commit

Permalink
Merge pull request #20 from duongminhhieu/develop
Browse files Browse the repository at this point in the history
Develop into Master
  • Loading branch information
duongminhhieu committed Jun 6, 2024
2 parents aa6902c + f53faf9 commit a09a1af
Show file tree
Hide file tree
Showing 32 changed files with 2,848 additions and 624 deletions.
26 changes: 20 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.learning</groupId>
<artifactId>SpringSecurity</artifactId>
<artifactId>YasMiniShop</artifactId>
<version>1.0.0</version>
<name>SpringSecurity</name>
<description>SpringSecurity</description>
<name>YasMini</name>
<description>YasMini CarShop</description>
<properties>
<java.version>21</java.version>
<projectlombok-lombok.version>1.18.30</projectlombok-lombok.version>
Expand All @@ -25,6 +25,7 @@
<jacoco.version>0.8.12</jacoco.version>
<spring-cloud-gcp.version>5.1.2</spring-cloud-gcp.version>
<firebase-admin.version>9.2.0</firebase-admin.version>
<google-cloud-bom.version>26.32.0</google-cloud-bom.version>
</properties>
<dependencyManagement>
<dependencies>
Expand All @@ -35,6 +36,15 @@
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>${google-cloud-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>
</dependencyManagement>
<dependencies>
Expand Down Expand Up @@ -106,6 +116,11 @@
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-vertexai</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down Expand Up @@ -183,16 +198,15 @@
</executions>
<configuration>
<excludes>
<exclude>**/common/**</exclude>
<exclude>**/dto/**</exclude>
<exclude>**/entity/**</exclude>
<exclude>**/mapper/**</exclude>
<exclude>**/configs/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
public interface CartItemRepository extends JpaRepository<CartItem, String>{
Optional<CartItem> findByProductAndUser(Product product, User user);
List<CartItem> findAllByUserOrderByLastModifiedDateDesc(User user);
List<CartItem> findAllByUserAndProductIsAvailable(User user, Boolean isAvailable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.learning.yasminishop.common.configs;

import com.google.cloud.vertexai.VertexAI;
import com.google.cloud.vertexai.generativeai.ChatSession;
import com.google.cloud.vertexai.generativeai.GenerativeModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class GeminiConfig {

private static final String MODEL_NAME = "gemini-1.5-flash";
private static final String LOCATION = "asia-southeast1";
private static final String PROJECT_ID = "yasmini";

@Bean
public VertexAI vertexAI() {
return new VertexAI(PROJECT_ID, LOCATION);
}

@Bean
public GenerativeModel getModel(VertexAI vertexAI) {
return new GenerativeModel(MODEL_NAME, vertexAI);
}

@Bean
public ChatSession chatSession(@Qualifier("getModel") GenerativeModel generativeModel) {
return new ChatSession(generativeModel);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public class SecurityConfiguration {
"/categories/{slug}",
"/products",
"/products/{slug}",
"/rating"
"/rating",
"notifications/**",
};

private static final String[] ALLOWED_METHODS = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.learning.yasminishop.common.entity;

import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.FieldDefaults;

@Entity
@Table(name = "t_notification")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Notification extends AuditEntity<String>{
@Id
@GeneratedValue(strategy = GenerationType.UUID)
String id;

@Column(columnDefinition = "TEXT")
String title;

@Column(columnDefinition = "TEXT")
String content;

String thumbnail;

String link;

Boolean isRead = false;

@ManyToOne
User user;

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,13 @@ public class Product extends AuditEntity<String>{
@ManyToMany
private Set<Category> categories;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(cascade = CascadeType.ALL)
private Set<Storage> images;

@OneToMany(cascade = CascadeType.ALL)
private Set<OrderItem> orderItems;

@OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
private Set<CartItem> cartItems;

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,7 @@ public class User extends AuditEntity<String> {
@ManyToMany(fetch = FetchType.LAZY)
private Set<Role> roles;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Notification> notifications;

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public enum ErrorCode {
PRODUCT_STOCK_NOT_ENOUGH(1021, "Product stock is not enough", HttpStatus.BAD_REQUEST),
CART_ITEM_NOT_FOUND(1022, "Cart item not found", HttpStatus.NOT_FOUND),
ORDER_NOT_FOUND(1023, "Order not found", HttpStatus.NOT_FOUND),
GENERATIVE_AI_ERROR(1024, "Error in generative AI", HttpStatus.INTERNAL_SERVER_ERROR),
PRODUCT_IN_ORDER(1025, "Product is in order", HttpStatus.BAD_REQUEST),
PRODUCT_IN_CART(1026, "Product is in cart", HttpStatus.BAD_REQUEST),


// Constraint violation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.learning.yasminishop.notification;

import com.learning.yasminishop.common.dto.APIResponse;
import com.learning.yasminishop.notification.dto.response.NotificationResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.List;

@RestController
@RequestMapping("/notifications")
@RequiredArgsConstructor
@Slf4j
public class NotificationController {

private final NotificationService notificationService;

@GetMapping(path = "/subscribe/{token}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter subscribe( @PathVariable String token) {
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
notificationService.addEmitter(emitter, token);
return emitter;
}

@GetMapping
public APIResponse<List<NotificationResponse>> getNotifications() {
List<NotificationResponse> notifications = notificationService.getNotifications();
return APIResponse.<List<NotificationResponse>>builder()
.result(notifications)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.learning.yasminishop.notification;

import com.learning.yasminishop.common.entity.Notification;
import com.learning.yasminishop.common.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface NotificationRepository extends JpaRepository<Notification, String>{

List<Notification> findAllByUserOrderByCreatedDateDesc(User user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.learning.yasminishop.notification;

import com.learning.yasminishop.common.configs.security.JwtService;
import com.learning.yasminishop.common.entity.Notification;
import com.learning.yasminishop.common.entity.User;
import com.learning.yasminishop.common.exception.AppException;
import com.learning.yasminishop.common.exception.ErrorCode;
import com.learning.yasminishop.notification.dto.response.NotificationResponse;
import com.learning.yasminishop.notification.mapper.NotificationMapper;
import com.learning.yasminishop.user.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class NotificationService {

private final NotificationRepository notificationRepository;
private final UserRepository userRepository;
private final NotificationMapper notificationMapper;

private final JwtService jwtService;
private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();


public void addEmitter(SseEmitter emitter, String token) {

if(!jwtService.isTokenValid(token)) {
log.error("Token is not valid");
emitter.completeWithError(new AppException(ErrorCode.INVALID_TOKEN));
return;
}

String email = jwtService.extractUserEmail(token);
log.info("User with email {} subscribed to notifications", email);
emitters.put(email, emitter);
}

public void sendNotification(String clientId, NotificationResponse notificationResponse) {
SseEmitter emitter = emitters.get(clientId);
if (emitter != null) {
try {
emitter.send(notificationResponse);
} catch (IOException e) {
emitter.completeWithError(e);
emitters.remove(clientId);
}
}
}

public List<NotificationResponse> getNotifications() {
String email = SecurityContextHolder.getContext().getAuthentication().getName();

User user = userRepository.findByEmail(email)
.orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND));

List<Notification> notifications = notificationRepository.findAllByUserOrderByCreatedDateDesc(user);
return notifications
.stream()
.map(notificationMapper::toNotificationResponse)
.toList();
}

public void createNotification(Notification notification) {
notificationRepository.save(notification);
sendNotification(notification.getUser().getEmail(), notificationMapper.toNotificationResponse(notification));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.learning.yasminishop.notification.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldDefaults;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@FieldDefaults(level = lombok.AccessLevel.PRIVATE)
public class NotificationResponse {

String id;
String title;
String content;
String thumbnail;
String link;
Boolean isRead;

LocalDateTime createdDate;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.learning.yasminishop.notification.mapper;

import com.learning.yasminishop.common.entity.Notification;
import com.learning.yasminishop.notification.dto.response.NotificationResponse;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface NotificationMapper {
NotificationResponse toNotificationResponse(Notification notification);
}
Loading

0 comments on commit a09a1af

Please sign in to comment.