cosmoEagle/leetcode-api
0
1import express, { NextFunction, Response } from 'express';2import cors from 'cors';3import rateLimit from 'express-rate-limit';4import * as leetcode from './leetCode';5import { FetchUserDataRequest } from './types';6import apicache from 'apicache';7import axios from 'axios';8import {9 userContestRankingInfoQuery,10 discussCommentsQuery,11 discussTopicQuery,12 userProfileUserQuestionProgressV2Query,13 skillStatsQuery,14 getUserProfileQuery,15 userProfileCalendarQuery,16 officialSolutionQuery,17 dailyQeustion,18} from './GQLQueries/newQueries';19 20const app = express();21let cache = apicache.middleware;22const API_URL = process.env.LEETCODE_API_URL || 'https://leetcode.com/graphql';23 24const limiter = rateLimit({25 windowMs: 60 * 60 * 1000, // 1 hour26 limit: 60,27 standardHeaders: 'draft-7',28 legacyHeaders: false,29 message: 'Too many request from this IP, try again in 1 hour',30});31 32app.use(cache('5 minutes'));33app.use(cors()); //enable all CORS request34app.use(limiter); //limit to all API35app.use((req: express.Request, _res: Response, next: NextFunction) => {36 console.log('Requested URL:', req.originalUrl);37 next();38});39 40async function queryLeetCodeAPI(query: string, variables: any) {41 try {42 const response = await axios.post(API_URL, { query, variables });43 if (response.data.errors) {44 throw new Error(response.data.errors[0].message);45 }46 return response.data;47 } catch (error) {48 if (error.response) {49 throw new Error(`Error from LeetCode API: ${error.response.data}`);50 } else if (error.request) {51 throw new Error('No response received from LeetCode API');52 } else {53 throw new Error(`Error in setting up the request: ${error.message}`);54 }55 }56}57 58app.get('/', (_req, res) => {59 res.json({60 apiOverview:61 'Welcome to the Alfa-Leetcode-API! Alfa-Leetcode-Api is a custom solution born out of the need for a well-documented and detailed LeetCode API. This project is designed to provide developers with endpoints that offer insights into a user"s profile, badges, solved questions, contest details, contest history, submissions, and also daily questions, selected problem, list of problems.',62 apiEndpointsLink:63 'https://github.com/alfaarghya/alfa-leetcode-api?tab=readme-ov-file#endpoints-',64 routes: {65 userDetails: {66 description:67 'Endpoints for retrieving detailed user profile information on Leetcode.',68 Method: 'GET',69 '/:username': 'Get your leetcodevis profile Details',70 '/:username/badges': 'Get your badges',71 '/:username/solved': 'Get total number of question you solved',72 '/:username/contest': 'Get your contest details',73 '/:username/contest/history': 'Get all contest history',74 '/:username/submission': 'Get your last 20 submission',75 '/:username/acSubmission': 'Get your last 20 accepted submission',76 '/:username/calendar': 'Get your submission calendar',77 '/userProfile/:username': 'Get full profile details in one call',78 '/userProfileCalendar?username=yourname&year=2024':79 'Get your calendar details with year',80 '/languageStats?username=yourname': 'Get the language stats of a user',81 '/userProfileUserQuestionProgressV2/:userSlug':82 'Get your question progress',83 '/skillStats/:username': 'Get your skill stats',84 },85 contest: {86 description:87 'Endpoints for retrieving contest ranking and performance data.',88 Method: 'GET',89 '/userContestRankingInfo/:username': 'Get user contest ranking info',90 },91 discussion: {92 description: 'Endpoints for fetching discussion topics and comments.',93 Method: 'GET',94 '/trendingDiscuss?first=20': 'Get top 20 trending discussions',95 '/discussTopic/:topicId': 'Get discussion topic',96 '/discussComments/:topicId': 'Get discussion comments',97 },98 problems: {99 description:100 'Endpoints for fetching problem-related data, including lists, details, and solutions.',101 Method: 'GET',102 singleProblem: {103 '/select?titleSlug=two-sum': 'Get selected Problem',104 '/daily': 'Get daily Problem',105 '/dailyQuestion': 'Get raw daily question',106 },107 problemList: {108 '/problems': 'Get list of 20 problems',109 '/problems?limit=50': 'Get list of some problems',110 '/problems?tags=array+math': 'Get list problems on selected topics',111 '/problems?tags=array+math+string&limit=5':112 'Get list some problems on selected topics',113 '/officialSolution?titleSlug=two-sum':114 'Get official solution of selected problem',115 },116 },117 },118 });119});120 121app.get('/officialSolution', async (req, res) => {122 const { titleSlug } = req.query;123 124 if (!titleSlug) {125 return res.status(400).json({ error: 'Missing titleSlug query parameter' });126 }127 try {128 const data = await queryLeetCodeAPI(officialSolutionQuery, { titleSlug });129 return res.json(data);130 } catch (error) {131 return res.status(500).json({ error: error.message });132 }133});134 135app.get('/userProfileCalendar', async (req, res) => {136 const { username, year } = req.query;137 138 if (!username || !year || typeof year !== 'string') {139 return res140 .status(400)141 .json({ error: 'Missing or invalid username or year query parameter' });142 }143 144 try {145 const data = await queryLeetCodeAPI(userProfileCalendarQuery, {146 username,147 year: parseInt(year),148 });149 return res.json(data);150 } catch (error) {151 return res.status(500).json({ error: error.message });152 }153});154 155// Format data156const formatData = (data: any) => {157 return {158 totalSolved: data.matchedUser.submitStats.acSubmissionNum[0].count,159 totalSubmissions: data.matchedUser.submitStats.totalSubmissionNum,160 totalQuestions: data.allQuestionsCount[0].count,161 easySolved: data.matchedUser.submitStats.acSubmissionNum[1].count,162 totalEasy: data.allQuestionsCount[1].count,163 mediumSolved: data.matchedUser.submitStats.acSubmissionNum[2].count,164 totalMedium: data.allQuestionsCount[2].count,165 hardSolved: data.matchedUser.submitStats.acSubmissionNum[3].count,166 totalHard: data.allQuestionsCount[3].count,167 ranking: data.matchedUser.profile.ranking,168 contributionPoint: data.matchedUser.contributions.points,169 reputation: data.matchedUser.profile.reputation,170 submissionCalendar: JSON.parse(data.matchedUser.submissionCalendar),171 recentSubmissions: data.recentSubmissionList,172 matchedUserStats: data.matchedUser.submitStats,173 };174};175 176app.get('/userProfile/:id', async (req, res) => {177 const user = req.params.id;178 179 try {180 const data = await queryLeetCodeAPI(getUserProfileQuery, {181 username: user,182 });183 if (data.errors) {184 res.send(data);185 } else {186 res.send(formatData(data.data));187 }188 } catch (error) {189 res.send(error);190 }191});192 193const handleRequest = async (res: Response, query: string, params: any) => {194 try {195 const data = await queryLeetCodeAPI(query, params);196 res.json(data);197 } catch (error) {198 res.status(500).json({ error: error.message });199 }200};201app.get('/dailyQuestion', (_, res) => {202 handleRequest(res, dailyQeustion, {});203});204 205app.get('/skillStats/:username', (req, res) => {206 const { username } = req.params;207 handleRequest(res, skillStatsQuery, { username });208});209 210app.get('/userProfileUserQuestionProgressV2/:userSlug', (req, res) => {211 const { userSlug } = req.params;212 handleRequest(res, userProfileUserQuestionProgressV2Query, { userSlug });213});214 215app.get('/discussTopic/:topicId', (req, res) => {216 const topicId = parseInt(req.params.topicId);217 handleRequest(res, discussTopicQuery, { topicId });218});219 220app.get('/discussComments/:topicId', (req, res) => {221 const topicId = parseInt(req.params.topicId);222 const {223 orderBy = 'newest_to_oldest',224 pageNo = 1,225 numPerPage = 10,226 } = req.query;227 handleRequest(res, discussCommentsQuery, {228 topicId,229 orderBy,230 pageNo,231 numPerPage,232 });233});234 235app.get('/userContestRankingInfo/:username', (req, res) => {236 const { username } = req.params;237 handleRequest(res, userContestRankingInfoQuery, { username });238});239 240//get the daily leetCode problem241app.get('/daily', leetcode.dailyProblem);242 243//get the selected question244app.get('/select', leetcode.selectProblem);245 246//get list of problems247app.get('/problems', leetcode.problems);248 249//get 20 trending Discuss250app.get('/trendingDiscuss', leetcode.trendingCategoryTopics);251 252app.get('/languageStats', leetcode.languageStats);253 254// Construct options object on all user routes.255app.use(256 '/:username*',257 (req: FetchUserDataRequest, _res: Response, next: NextFunction) => {258 req.body = {259 username: req.params.username,260 limit: req.query.limit,261 };262 next();263 }264);265 266//get user profile details267app.get('/:username', leetcode.userData);268app.get('/:username/badges', leetcode.userBadges);269app.get('/:username/solved', leetcode.solvedProblem);270app.get('/:username/contest', leetcode.userContest);271app.get('/:username/contest/history', leetcode.userContestHistory);272app.get('/:username/submission', leetcode.submission);273app.get('/:username/acSubmission', leetcode.acSubmission);274app.get('/:username/calendar', leetcode.calendar);275 276export default app;277 