Skip to content

Commit 6857985

Browse files
authored
Merge pull request #4 from noworneverev/feature/add-new-endpoints
Add new endpoints
2 parents d5c8fd8 + b3a8057 commit 6857985

File tree

4 files changed

+177
-159
lines changed

4 files changed

+177
-159
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ curl https://leetcode-api-pied.vercel.app/user/lee215
3434
|---------------------------------|--------|-------------------------------------|---------------------------------------------------------------------------------------------|
3535
| `/problems` | GET | All LeetCode problems | [/problems](https://leetcode-api-pied.vercel.app/problems) |
3636
| `/problem/{id_or_slug}` | GET | Get problem by ID/slug | [/problem/two-sum](https://leetcode-api-pied.vercel.app/problem/two-sum) |
37-
| `/problems/{topic}` | GET | Problems by topic (arrays, DP, etc) | [/problems/array](https://leetcode-api-pied.vercel.app/problems/array) |
37+
| `/search/{query}` | GET | Search for problems | [/search/two%20sum](https://leetcode-api-pied.vercel.app/search/two%20sum) |
38+
| `/random` | GET | Random LeetCode problem | [/random](https://leetcode-api-pied.vercel.app/random) |
3839
| `/user/{username}` | GET | User profile & stats | [/user/lee215](https://leetcode-api-pied.vercel.app/user/lee215) |
3940
| `/user/{username}/contests` | GET | User's recent contests | [/user/lee215/contests](https://leetcode-api-pied.vercel.app/user/lee215/contests) |
4041
| `/user/{username}/submissions` | GET | User's recent submissions | [/user/lee215/submissions](https://leetcode-api-pied.vercel.app/user/lee215/submissions) |

src/api/api.py

Lines changed: 50 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import uvicorn
77
from fastapi import FastAPI, HTTPException
88
from fastapi.responses import HTMLResponse
9+
import random
910

1011
app = FastAPI()
1112
leetcode_url = "https://leetcode.com/graphql"
@@ -146,40 +147,41 @@ async def get_problem(id_or_slug: str):
146147
cache.question_details[question_id] = question_data
147148
return question_data
148149

149-
@app.get("/problems/{topic}", tags=["Problems"])
150-
async def get_problems_by_topic(topic: str):
151-
async with httpx.AsyncClient() as client:
152-
query = """query problemsetQuestionList($categorySlug: String, $filters: QuestionListFilterInput) {
153-
problemsetQuestionList: questionList(
154-
categorySlug: $categorySlug
155-
filters: $filters
156-
) {
157-
questions: data {
158-
questionId
159-
title
160-
titleSlug
161-
difficulty
162-
topicTags { name }
163-
}
164-
}
165-
}"""
166-
167-
payload = {
168-
"query": query,
169-
"variables": {
170-
"categorySlug": "",
171-
"filters": {"tags": [topic]}
172-
}
173-
}
174-
175-
try:
176-
response = await client.post(leetcode_url, json=payload)
177-
if response.status_code == 200:
178-
data = response.json()
179-
return data["data"]["problemsetQuestionList"]["questions"]
180-
raise HTTPException(status_code=response.status_code, detail="Error fetching problems by topic")
181-
except Exception as e:
182-
raise HTTPException(status_code=500, detail=str(e))
150+
@app.get("/search", tags=["Problems"])
151+
async def search_problems(query: str):
152+
"""
153+
Search for problems whose titles contain the given query (case-insensitive).
154+
"""
155+
await cache.initialize()
156+
query_lower = query.lower()
157+
results = []
158+
for q in cache.questions.values():
159+
if query_lower in q["title"].lower():
160+
results.append({
161+
"id": q["questionId"],
162+
"frontend_id": q["questionFrontendId"],
163+
"title": q["title"],
164+
"title_slug": q["titleSlug"],
165+
"url": f"https://leetcode.com/problems/{q['titleSlug']}/"
166+
})
167+
return results
168+
169+
@app.get("/random", tags=["Problems"])
170+
async def get_random_problem():
171+
"""
172+
Return a random problem from the cached questions.
173+
"""
174+
await cache.initialize()
175+
if not cache.questions:
176+
raise HTTPException(status_code=404, detail="No questions available")
177+
q = random.choice(list(cache.questions.values()))
178+
return {
179+
"id": q["questionId"],
180+
"frontend_id": q["questionFrontendId"],
181+
"title": q["title"],
182+
"title_slug": q["titleSlug"],
183+
"url": f"https://leetcode.com/problems/{q['titleSlug']}/"
184+
}
183185

184186
@app.get("/user/{username}", tags=["Users"])
185187
async def get_user_profile(username: str):
@@ -328,132 +330,23 @@ async def get_daily_challenge():
328330
except Exception as e:
329331
raise HTTPException(status_code=500, detail=str(e))
330332

331-
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
332-
async def home():
333-
return """
334-
<!DOCTYPE html>
335-
<html lang="en">
336-
<head>
337-
<meta charset="UTF-8">
338-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
339-
<title>LeetCode API</title>
340-
<script src="https://cdn.tailwindcss.com"></script>
341-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
342-
<style>
343-
/* Dark mode styles */
344-
body.dark-mode {
345-
background-color: #1a202c !important;
346-
color: #e2e8f0;
347-
}
348-
349-
body.dark-mode .bg-white {
350-
background-color: #2d3748 !important;
351-
}
352-
353-
body.dark-mode .text-gray-800 {
354-
color: #e2e8f0 !important;
355-
}
356-
357-
body.dark-mode .text-gray-600 {
358-
color: #cbd5e0 !important;
359-
}
360-
361-
body.dark-mode .text-gray-500 {
362-
color: #a0aec0 !important;
363-
}
333+
@app.get("/health", tags=["Utility"])
334+
async def health_check():
335+
return {"status": "ok", "timestamp": time.time()}
364336

365-
body.dark-mode .bg-blue-500 {
366-
background-color: #2563eb !important;
367-
}
337+
@app.get("/refresh", tags=["Admin"])
338+
async def refresh_cache():
339+
# Force refresh the question cache regardless of the update interval
340+
async with cache.lock:
341+
await cache._fetch_all_questions()
342+
cache.last_updated = time.time()
343+
return {"detail": "Cache refreshed"}
368344

369-
body.dark-mode .bg-green-500 {
370-
background-color: #059669 !important;
371-
}
372-
373-
body.dark-mode .hover\:bg-blue-600:hover {
374-
background-color: #1d4ed8 !important;
375-
}
376-
377-
body.dark-mode .hover\:bg-green-600:hover {
378-
background-color: #047857 !important;
379-
}
380-
</style>
381-
</head>
382-
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
383-
<div class="max-w-2xl mx-4 text-center">
384-
<div class="bg-white rounded-lg shadow-lg p-8 space-y-6">
385-
<div class="flex justify-end">
386-
<button id="theme-toggle" class="text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-100 focus:outline-none">
387-
<i id="theme-icon" class="fas fa-moon"></i>
388-
</button>
389-
</div>
390-
<h1 class="text-4xl font-bold text-gray-800 mb-4">
391-
LeetCode API Gateway
392-
<i class="fas fa-rocket text-blue-500 ml-2"></i>
393-
</h1>
394-
395-
<p class="text-gray-600 text-lg">
396-
Explore LeetCode data through our API endpoints. Get problem details,
397-
user statistics, submissions history, and more!
398-
</p>
399-
400-
<div class="flex flex-col sm:flex-row justify-center gap-4">
401-
<a href="https://leetcode-api-pied.vercel.app/docs"
402-
target="_blank"
403-
class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg
404-
transition-all duration-300 transform hover:scale-105
405-
flex items-center justify-center gap-2">
406-
<i class="fas fa-book-open"></i>
407-
API Documentation
408-
</a>
409-
410-
<a href="https://docs.google.com/spreadsheets/d/1sRWp95wqo3a7lLBbtNd_3KkTyGjx_9sctTOL5JOb6pA/edit?usp=sharing"
411-
target="_blank"
412-
class="bg-green-500 hover:bg-green-600 text-white px-6 py-3 rounded-lg
413-
transition-all duration-300 transform hover:scale-105
414-
flex items-center justify-center gap-2">
415-
<i class="fas fa-table"></i>
416-
Google Sheet (Updated Daily)
417-
</a>
418-
</div>
419-
420-
<p class="text-gray-500 text-sm mt-8 flex items-center justify-center gap-1">
421-
Made with ❤️ by
422-
<a href="https://noworneverev.github.io/" target="_blank"
423-
class="text-blue-500 font-semibold hover:text-blue-600 transition duration-300">
424-
Yan-Ying Liao
425-
</a>
426-
</p>
427-
</div>
428-
</div>
429-
430-
<script>
431-
const themeToggleBtn = document.getElementById('theme-toggle');
432-
const themeIcon = document.getElementById('theme-icon');
433-
const body = document.body;
434-
435-
// Check local storage for theme preference
436-
const currentTheme = localStorage.getItem('theme');
437-
if (currentTheme === 'dark') {
438-
body.classList.add('dark-mode');
439-
themeIcon.classList.replace('fa-moon', 'fa-sun');
440-
}
441-
442-
themeToggleBtn.addEventListener('click', () => {
443-
body.classList.toggle('dark-mode');
444-
if (body.classList.contains('dark-mode')) {
445-
themeIcon.classList.replace('fa-moon', 'fa-sun');
446-
localStorage.setItem('theme', 'dark');
447-
} else {
448-
themeIcon.classList.replace('fa-sun', 'fa-moon');
449-
localStorage.setItem('theme', 'light');
450-
}
451-
});
452-
</script>
453-
</body>
454-
</html>
455-
"""
456345

346+
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
347+
async def home():
348+
from src.api.home import HOME_PAGE_HTML
349+
return HOME_PAGE_HTML
457350

458351
if __name__ == "__main__":
459352
uvicorn.run(app, host="0.0.0.0", port=8000)

src/api/home.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
HOME_PAGE_HTML ="""
2+
<!DOCTYPE html>
3+
<html lang="en">
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>LeetCode API</title>
8+
<link rel="icon" type="image/png" href="https://img.icons8.com/material-rounded/48/000000/code.png">
9+
<script src="https://cdn.tailwindcss.com"></script>
10+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
11+
<style>
12+
/* Dark mode styles */
13+
body.dark-mode {
14+
background-color: #1a202c !important;
15+
color: #e2e8f0;
16+
}
17+
18+
body.dark-mode .bg-white {
19+
background-color: #2d3748 !important;
20+
}
21+
22+
body.dark-mode .text-gray-800 {
23+
color: #e2e8f0 !important;
24+
}
25+
26+
body.dark-mode .text-gray-600 {
27+
color: #cbd5e0 !important;
28+
}
29+
30+
body.dark-mode .text-gray-500 {
31+
color: #a0aec0 !important;
32+
}
33+
34+
body.dark-mode .bg-blue-500 {
35+
background-color: #2563eb !important;
36+
}
37+
38+
body.dark-mode .bg-green-500 {
39+
background-color: #059669 !important;
40+
}
41+
42+
body.dark-mode .hover\:bg-blue-600:hover {
43+
background-color: #1d4ed8 !important;
44+
}
45+
46+
body.dark-mode .hover\:bg-green-600:hover {
47+
background-color: #047857 !important;
48+
}
49+
</style>
50+
</head>
51+
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
52+
<div class="max-w-2xl mx-4 text-center">
53+
<div class="bg-white rounded-lg shadow-lg p-8 space-y-6">
54+
<div class="flex justify-end">
55+
<button id="theme-toggle" class="text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-100 focus:outline-none">
56+
<i id="theme-icon" class="fas fa-moon"></i>
57+
</button>
58+
</div>
59+
<h1 class="text-4xl font-bold text-gray-800 mb-4">
60+
LeetCode API Gateway
61+
<i class="fas fa-rocket text-blue-500 ml-2"></i>
62+
</h1>
63+
64+
<p class="text-gray-600 text-lg">
65+
Explore LeetCode data through our API endpoints. Get problem details,
66+
user statistics, submissions history, and more!
67+
</p>
68+
69+
<div class="flex flex-col sm:flex-row justify-center gap-4">
70+
<a href="https://leetcode-api-pied.vercel.app/docs"
71+
target="_blank"
72+
class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-3 rounded-lg
73+
transition-all duration-300 transform hover:scale-105
74+
flex items-center justify-center gap-2">
75+
<i class="fas fa-book-open"></i>
76+
API Documentation
77+
</a>
78+
79+
<a href="https://docs.google.com/spreadsheets/d/1sRWp95wqo3a7lLBbtNd_3KkTyGjx_9sctTOL5JOb6pA/edit?usp=sharing"
80+
target="_blank"
81+
class="bg-green-500 hover:bg-green-600 text-white px-6 py-3 rounded-lg
82+
transition-all duration-300 transform hover:scale-105
83+
flex items-center justify-center gap-2">
84+
<i class="fas fa-table"></i>
85+
Google Sheet (Updated Daily)
86+
</a>
87+
</div>
88+
89+
<p class="text-gray-500 text-sm mt-8 flex items-center justify-center gap-1">
90+
Made with ❤️ by
91+
<a href="https://noworneverev.github.io/" target="_blank"
92+
class="text-blue-500 font-semibold hover:text-blue-600 transition duration-300">
93+
Yan-Ying Liao
94+
</a>
95+
</p>
96+
</div>
97+
</div>
98+
99+
<script>
100+
const themeToggleBtn = document.getElementById('theme-toggle');
101+
const themeIcon = document.getElementById('theme-icon');
102+
const body = document.body;
103+
104+
// Check local storage for theme preference
105+
const currentTheme = localStorage.getItem('theme');
106+
if (currentTheme === 'dark') {
107+
body.classList.add('dark-mode');
108+
themeIcon.classList.replace('fa-moon', 'fa-sun');
109+
}
110+
111+
themeToggleBtn.addEventListener('click', () => {
112+
body.classList.toggle('dark-mode');
113+
if (body.classList.contains('dark-mode')) {
114+
themeIcon.classList.replace('fa-moon', 'fa-sun');
115+
localStorage.setItem('theme', 'dark');
116+
} else {
117+
themeIcon.classList.replace('fa-sun', 'fa-moon');
118+
localStorage.setItem('theme', 'light');
119+
}
120+
});
121+
</script>
122+
</body>
123+
</html>
124+
"""

src/utils/google_sheets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ def update_google_sheet(service, data, sheet_id=0):
447447

448448
# Add info row at row 1
449449
total_problems = len(data) - 1 # Subtract 1 since the first row of `data` is the header.
450-
info_row1 = [f"🫠 Total Problems: {total_problems}", "", "", '=HYPERLINK("https://github.com/noworneverev/leetcode-api", "⭐ Star me on GitHub")']
450+
info_row1 = [f"🧩 Total Problems: {total_problems}", "", "", '=HYPERLINK("https://github.com/noworneverev/leetcode-api", "⭐ Star me on GitHub")']
451451
info_row2 = [f"🕰️ Last Updated: {now_str}", "", "", '=HYPERLINK("https://www.linkedin.com/in/yan-ying-liao/", "🦙 Follow/Connect with me on LinkedIn")']
452452
info_row3 = ['Choose the Programming Language for the Prompt']
453453
info_row4 = ['Choose the Language for the Answer']

0 commit comments

Comments
 (0)