Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,10 @@ out/

### VS Code ###
.vscode/

### Credentials ###
*.env

### Can Remove This ###
personalnotes/
logs/
8 changes: 5 additions & 3 deletions RestroHub/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ dependencies {
// Testing
testCompileOnly "org.projectlombok:lombok:${lombokVersion}"
testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}"
// testImplementation 'org.springframework.boot:spring-boot-starter-test';
testImplementation 'org.springframework.boot:spring-boot-starter-test';
testImplementation 'org.springframework.security:spring-security-test';

//OpenAPI Documentation
Expand All @@ -83,11 +83,13 @@ dependencies {
implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5'
}

// tasks.named('test') {
// enabled = false
// }
tasks.named('test') {
enabled = false
useJUnitPlatform()
}


//tasks.withType<Test> {
// useJUnitPlatform()
//}
1 change: 1 addition & 0 deletions RestroHub/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.gradle.java.installations.auto-download=true
5 changes: 4 additions & 1 deletion RestroHub/settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
rootProject.name = 'restroly'
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
rootProject.name = 'restroly'
Original file line number Diff line number Diff line change
Expand Up @@ -32,175 +32,109 @@
@Tag(name = "Authentication", description = "APIs for user authentication and token management")
public class AuthController {

@Autowired
private AuthService authService;

@PostMapping("/login")
@Operation(
summary = "Authenticate user and generate tokens",
description = "Authenticates user credentials and returns JWT access token and refresh token"
)
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "Authentication successful",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = AuthResponse.class),
examples = @ExampleObject(value = """
{
"success": true,
"message": "Login successful",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 86400,
"username": "admin",
"roles": ["ROLE_ADMIN"]
},
"timestamp": "2024-01-15T10:30:00"
}
""")
)
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "400",
description = "Invalid request - validation failed",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)
)
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "401",
description = "Authentication failed - invalid credentials",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class),
examples = @ExampleObject(value = """
{
"status": 401,
"error": "UNAUTHORIZED",
"message": "Invalid username or password",
"path": "/api/v1/auth/login",
"timestamp": "2024-01-15T10:30:00",
"traceId": "abc123"
}
""")
)
)
})
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "Login credentials",
required = true,
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = LoginRequest.class),
examples = @ExampleObject(value = """
{
"username": "admin",
"password": "admin123"
}
""")
)
)
public ResponseEntity<ApiResponse<AuthResponse>> login(
@Valid @RequestBody LoginRequest loginRequest) {

log.info("Login request received for user: {}", loginRequest.getUsername());

AuthResponse authResponse = authService.login(loginRequest);

return ResponseEntity.ok(ApiResponse.success(authResponse, "Login successful"));
}

@PostMapping("/refresh")
@Operation(
summary = "Refresh access token",
description = "Generates new access and refresh tokens using a valid refresh token"
)
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "Token refreshed successfully",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = AuthResponse.class)
)
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "400",
description = "Invalid or expired refresh token",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorResponse.class)
)
)
})
public ResponseEntity<ApiResponse<AuthResponse>> refreshToken(
@Valid @RequestBody RefreshTokenRequest refreshTokenRequest) {

log.debug("Refresh token request received");

AuthResponse authResponse = authService.refreshToken(refreshTokenRequest);

return ResponseEntity.ok(ApiResponse.success(authResponse, "Token refreshed successfully"));
}

@PostMapping("/logout")
@Operation(
summary = "Logout user",
description = "Invalidates the current user session"
)
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "Logout successful"
)
})
public ResponseEntity<ApiResponse<String>> logout(HttpServletRequest request) {
String token = extractToken(request);
authService.logout(token);

log.info("User logged out successfully");

return ResponseEntity.ok(ApiResponse.success("Logout successful"));
}

@GetMapping("/validate")
@Operation(
summary = "Validate token",
description = "Validates if the provided JWT token is valid and not expired"
)
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "Token is valid"
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "401",
description = "Token is invalid or expired"
)
})
public ResponseEntity<ApiResponse<String>> validateToken(HttpServletRequest request) {
String token = extractToken(request);

if (token == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error("No token provided"));
@Autowired
private AuthService authService;

@PostMapping("/login")
@Operation(summary = "Authenticate user and generate tokens", description = "Authenticates user credentials and returns JWT access token and refresh token")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Authentication successful", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AuthResponse.class), examples = @ExampleObject(value = """
{
"success": true,
"message": "Login successful",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 86400,
"username": "admin",
"roles": ["ROLE_ADMIN"]
},
"timestamp": "2024-01-15T10:30:00"
}
"""))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid request - validation failed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Authentication failed - invalid credentials", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(value = """
{
"status": 401,
"error": "UNAUTHORIZED",
"message": "Invalid username or password",
"path": "/api/v1/auth/login",
"timestamp": "2024-01-15T10:30:00",
"traceId": "abc123"
}
""")))
})
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Login credentials", required = true, content = @Content(mediaType = "application/json", schema = @Schema(implementation = LoginRequest.class), examples = @ExampleObject(value = """
{
"username": "admin",
"password": "admin123"
}
""")))
public ResponseEntity<ApiResponse<AuthResponse>> login(
@Valid @RequestBody LoginRequest loginRequest) {

log.info("Login request received for user: {}", loginRequest.getUsername());

AuthResponse authResponse = authService.login(loginRequest);

return ResponseEntity.ok(ApiResponse.success(authResponse, "Login successful"));
}

// Token validation happens in the security filter
// If we reach here, the token is valid
return ResponseEntity.ok(ApiResponse.success("Token is valid"));
}
@PostMapping("/refresh")
@Operation(summary = "Refresh access token", description = "Generates new access and refresh tokens using a valid refresh token")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Token refreshed successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AuthResponse.class))),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid or expired refresh token", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)))
})
public ResponseEntity<ApiResponse<AuthResponse>> refreshToken(
@Valid @RequestBody RefreshTokenRequest refreshTokenRequest) {

private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
log.debug("Refresh token request received");

AuthResponse authResponse = authService.refreshToken(refreshTokenRequest);

return ResponseEntity.ok(ApiResponse.success(authResponse, "Token refreshed successfully"));
}

@PostMapping("/logout")
@Operation(summary = "Logout user", description = "Invalidates the current user session")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Logout successful")
})
public ResponseEntity<ApiResponse<String>> logout(HttpServletRequest request) {
String token = extractToken(request);
authService.logout(token);

log.info("User logged out successfully");

return ResponseEntity.ok(ApiResponse.success("Logout successful"));
}

@GetMapping("/validate")
@Operation(summary = "Validate token", description = "Validates if the provided JWT token is valid and not expired")
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Token is valid"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Token is invalid or expired")
})
public ResponseEntity<ApiResponse<String>> validateToken(HttpServletRequest request) {
String token = extractToken(request);

if (token == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error("No token provided"));
}

// Token validation happens in the security filter
// If we reach here, the token is valid
return ResponseEntity.ok(ApiResponse.success("Token is valid"));
}

private String extractToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public class Branch {
@Builder.Default
private List<Tables> tables = new ArrayList<>();

@Column(name = "branch_upi_id")
private String branchUpiId;

@PrePersist
protected void onCreate() {
createdDate = LocalDateTime.now();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.restroly.qrmenu.order.dto.OrderResponse;
import com.restroly.qrmenu.order.dto.UpdateOrderStatusRequest;
import com.restroly.qrmenu.order.service.OrderService;
import com.restroly.qrmenu.payment.service.PaymentService;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -29,14 +30,21 @@
@RequestMapping(SECURE_API_VERSION+"/orders")
@CrossOrigin(origins = "*")
public class OrderController {

@Autowired
private final PaymentService paymentService = null;
@Autowired
private final OrderService orderService = null;


//private final OrderService orderService;
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@Valid @RequestBody CreateOrderRequest request) {
OrderResponse response = orderService.createOrder(request);
//Order tacking improvement needed
//Response is used to carry the UPI Id out of instead of calling it from database again hence reducing the number of calls to database and improving the performance of the application
String paymentUrl = paymentService.generatePaymentLink(response.getTotalAmount(), response.getOrderId(), response.getPaymentLink());
//Genarated payment link is stored in response object to send it to client and use it for payment
response.setPaymentLink(paymentUrl);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class OrderResponse {
private String customerPhone;
private String specialInstructions;
private BigDecimal totalAmount;
private String paymentLink;
private OrderStatus status;
private LocalDateTime createdAt;
private List<OrderItemResponse> items;
Expand Down Expand Up @@ -101,5 +102,11 @@ public List<OrderItemResponse> getItems() {
public void setItems(List<OrderItemResponse> items) {
this.items = items;
}
public String getPaymentLink() {
return paymentLink;
}
public void setPaymentLink(String paymentLink) {
this.paymentLink = paymentLink;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public class Order {
@Builder.Default
private List<OrderItem> orderItems = new ArrayList<>();

//Not storing in database only for better payment utility
@Transient
private String paymentId;

@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
Expand Down
Loading