Skip to content

Commit 8df60d0

Browse files
committed
chore: add overview controller along with new exception handling for 404 path errors
1 parent 7197c62 commit 8df60d0

File tree

4 files changed

+158
-1
lines changed

4 files changed

+158
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<p align="center">
88
<img src="https://img.shields.io/badge/License-MIT-green.svg"/>
99
<img src="https://img.shields.io/badge/Maven-3.9.11-blue.svg?logo=apachemaven"/>
10-
<img src="https://github.com/rajat069/leetstats-api/actions/workflows/build.yml/badge.svg"/>
10+
<img src="https://github.com/rajat069/leetcode-stats-api/actions/workflows/build.yml/badge.svg"/>
1111
</p>
1212

1313
<p align="center">
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.rajat_singh.leetcode_api.controller;
2+
3+
import org.springframework.http.ResponseEntity;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.RequestMapping;
6+
import org.springframework.web.bind.annotation.RestController;
7+
8+
import java.util.LinkedHashMap;
9+
import java.util.Map;
10+
11+
/**
12+
* This controller provides a root "index" endpoint for the API,
13+
* documenting all available routes for users.
14+
*/
15+
@RestController
16+
@RequestMapping("/api/v1")
17+
public class ApiOverviewController {
18+
19+
@GetMapping("/")
20+
public ResponseEntity<Map<String, Object>> getApiOverview() {
21+
// Using LinkedHashMap to preserve the insertion order for a clean JSON output
22+
Map<String, Object> overview = new LinkedHashMap<>();
23+
24+
overview.put("apiOverview",
25+
"Welcome to leetcode-stats-api! This is a custom API wrapper for LeetCode data, providing stable, well-documented endpoints.");
26+
27+
overview.put("apiDocumentationLink",
28+
"https://github.com/rajat069/leetcode-stats-api/blob/main/README.md");
29+
30+
Map<String, Object> routes = new LinkedHashMap<>();
31+
32+
Map<String, Object> userDetails = new LinkedHashMap<>();
33+
userDetails.put("description", "Endpoints for retrieving detailed user profile information.");
34+
userDetails.put("Method", "GET");
35+
36+
Map<String, String> userPaths = new LinkedHashMap<>();
37+
userPaths.put("/users/{username}/profile", "Get question progress (accepted, failed, untouched).");
38+
userPaths.put("/users/{username}/languageStats", "Get stats on languages used and problems solved per language.");
39+
userPaths.put("/users/{username}/publicInfo", "Get public profile info (avatar, ranking, social links).");
40+
userPaths.put("/users/{username}/badges", "Get a list of badges earned by the user.");
41+
userPaths.put("/users/{username}/userSkillStats", "Get advanced, intermediate, and fundamental skill stats.");
42+
userPaths.put("/users/{username}/recentUserSubmissions/{limit}", "Get the {limit} most recent AC submissions.");
43+
userPaths.put("/users/{username}/userCalendarStats/{year}", "Get submission calendar, streak, and active days for a given {year}.");
44+
45+
userDetails.put("endpoints", userPaths);
46+
routes.put("userDetails", userDetails);
47+
48+
Map<String, Object> userContests = new LinkedHashMap<>();
49+
userContests.put("description", "Endpoints for retrieving user contest history and rankings.");
50+
userContests.put("Method", "GET");
51+
52+
Map<String, String> contestPaths = new LinkedHashMap<>();
53+
contestPaths.put("/users/{username}/contests/", "Get user contest ranking and full contest history.");
54+
contestPaths.put("/users/{username}/contests/ranking", "Get just the user's contest ranking details.");
55+
contestPaths.put("/users/{username}/contests/bestRanking", "Get the user's single best-ranking contest performance.");
56+
contestPaths.put("/users/{username}/contests/rankingHistory", "Get the user's entire contest history.");
57+
contestPaths.put("/users/{username}/contests/contest-name/{contestTitle}", "Find contest history by matching part of a {contestTitle}.");
58+
contestPaths.put("/users/{username}/contests/hasAttended/{attended}", "Filter history by attendance (true or false).");
59+
contestPaths.put("/users/{username}/contests/trendDirection/{direction}", "Filter history by rating trend (UP, DOWN, NONE).");
60+
contestPaths.put("/users/{username}/contests/problemSolvedGTE/{count}", "Filter history for contests where problems solved were >= {count}.");
61+
contestPaths.put("/users/{username}/contests/finishTime/{timeInSeconds}", "Filter history for contests finished in less than {timeInSeconds}.");
62+
contestPaths.put("/users/{username}/contests/biggestJumpInRating", "Get the contest with the user's biggest rating increase.");
63+
64+
userContests.put("endpoints", contestPaths);
65+
routes.put("userContests", userContests);
66+
67+
Map<String, Object> questions = new LinkedHashMap<>();
68+
questions.put("description", "Endpoints for fetching and searching questions from the local database.");
69+
70+
Map<String, String> questionPaths = new LinkedHashMap<>();
71+
questionPaths.put("/questions/ (GET)", "Get a paginated list of all questions. Supports ?page=, &size=, &sort=.");
72+
questionPaths.put("/questions/potd (GET)", "Get the current LeetCode Problem of the Day (POTD).");
73+
questionPaths.put("/questions/search (POST)", "A powerful search endpoint to filter questions. See README for request body.");
74+
75+
questions.put("endpoints", questionPaths);
76+
routes.put("questions", questions);
77+
78+
79+
Map<String, Object> globalContests = new LinkedHashMap<>();
80+
globalContests.put("description", "Endpoints for retrieving global contest data.");
81+
globalContests.put("Method", "GET");
82+
83+
Map<String, String> globalContestPaths = new LinkedHashMap<>();
84+
globalContestPaths.put("/globalContestInfo/fetchContests", "Get a paginated list of all global contests from the local database.");
85+
86+
globalContests.put("endpoints", globalContestPaths);
87+
routes.put("globalContests", globalContests);
88+
89+
overview.put("routes", routes);
90+
91+
return ResponseEntity.ok(overview);
92+
}
93+
}

src/main/java/com/rajat_singh/leetcode_api/exceptions/GlobalExceptionHandler.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,68 @@
44
import jakarta.servlet.http.HttpServletRequest;
55
import org.springframework.http.HttpStatus;
66
import org.springframework.http.ResponseEntity;
7+
import org.springframework.http.converter.HttpMessageNotReadableException;
78
import org.springframework.web.bind.annotation.ExceptionHandler;
89
import org.springframework.web.bind.annotation.RestControllerAdvice;
10+
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
11+
import org.springframework.web.servlet.NoHandlerFoundException;
12+
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
913

1014
import java.time.LocalDateTime;
1115

1216
@RestControllerAdvice
1317
public class GlobalExceptionHandler {
18+
/**
19+
* Handles 404 errors for paths that do not exist.
20+
* This is triggered when spring.mvc.throw-exception-if-no-handler-found=true
21+
*/
22+
23+
@ExceptionHandler(NoHandlerFoundException.class)
24+
public ResponseEntity<ApiErrorResponse> handleNoHandlerFound(
25+
NoHandlerFoundException ex, HttpServletRequest request) {
26+
27+
ApiErrorResponse response = new ApiErrorResponse(
28+
LocalDateTime.now(),
29+
HttpStatus.NOT_FOUND.value(),
30+
"Not Found",
31+
"The requested path " + ex.getRequestURL() + " could not be found.",
32+
request.getRequestURI()
33+
);
34+
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
35+
}
36+
37+
38+
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
39+
public ResponseEntity<ApiErrorResponse> handleTypeMismatch(
40+
MethodArgumentTypeMismatchException ex, HttpServletRequest request) {
41+
42+
String expectedType = (ex.getRequiredType() != null) ? ex.getRequiredType().getSimpleName() : "unknown";
43+
String message = String.format("The parameter '%s' with value '%s' is invalid. Expected type is '%s'.",
44+
ex.getName(), ex.getValue(), expectedType);
45+
46+
ApiErrorResponse response = new ApiErrorResponse(
47+
LocalDateTime.now(),
48+
HttpStatus.BAD_REQUEST.value(),
49+
"Bad Request",
50+
message,
51+
request.getRequestURI()
52+
);
53+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
54+
}
55+
56+
@ExceptionHandler(HttpMessageNotReadableException.class)
57+
public ResponseEntity<ApiErrorResponse> handleUnreadableRequest(
58+
HttpMessageNotReadableException ex, HttpServletRequest request) {
59+
60+
ApiErrorResponse response = new ApiErrorResponse(
61+
LocalDateTime.now(),
62+
HttpStatus.BAD_REQUEST.value(),
63+
"Bad Request",
64+
"The request body is malformed or unreadable. Please check JSON format.",
65+
request.getRequestURI()
66+
);
67+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
68+
}
1469

1570
@ExceptionHandler(UserNotFoundException.class)
1671
public ResponseEntity<ApiErrorResponse> handleUserNotFound(

src/main/resources/application.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ spring:
2121
properties:
2222
hibernate:
2323
format_sql: true
24+
mvc:
25+
# This tells Spring to throw an exception when no handler is found,
26+
# which our new @ExceptionHandler(NoHandlerFoundException.class) can catch.
27+
throw-exception-if-no-handler-found: true
28+
web:
29+
resources:
30+
# This disables Spring's default static resource handling,
31+
# which is necessary for the above property to work reliably for API-only apps.
32+
add-mappings: false
2433

2534
leetcode:
2635
graphql:

0 commit comments

Comments
 (0)