Skip to content

Commit 1667f94

Browse files
committed
feat: new endpoint
1 parent add2e11 commit 1667f94

File tree

8 files changed

+185
-12
lines changed

8 files changed

+185
-12
lines changed

apps/docs/app/api/hooks/[title]/route.ts apps/docs/app/api/next/[title]/route.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { NextResponse } from "next/server";
44
import { join } from "path";
55
import { readFile } from "fs";
66

7-
const filePath = join(process.cwd(), "lib", "hooks.json");
7+
const filePath = join(process.cwd(), "lib", "db", "next.json");
88

99
async function loadData(): Promise<Hook[]> {
1010
return new Promise((resolve, reject) => {

apps/docs/app/api/hooks/route.ts apps/docs/app/api/next/route.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { readFile } from "fs";
66

77
export const dynamic = "force-dynamic";
88

9-
const filePath = join(process.cwd(), "lib", "hooks.json");
9+
const filePath = join(process.cwd(), "lib", "db", "next.json");
1010

1111
async function loadData(): Promise<Hook[]> {
1212
return new Promise((resolve, reject) => {
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { getClientIp, type Hook } from "@rehooks/utils";
2+
import { ratelimit } from "@/lib/redis/ratelimit";
3+
import { NextResponse } from "next/server";
4+
import { join } from "path";
5+
import { readFile } from "fs";
6+
7+
const filePath = join(process.cwd(), "lib", "db", "react.json");
8+
9+
async function loadData(): Promise<Hook[]> {
10+
return new Promise((resolve, reject) => {
11+
readFile(filePath, "utf8", (err, data) => {
12+
if (err) {
13+
console.error("Error loading data:", err);
14+
reject(err);
15+
} else {
16+
resolve(JSON.parse(data));
17+
}
18+
});
19+
});
20+
}
21+
22+
export async function GET(
23+
request: Request,
24+
{ params }: { params: { title: string } },
25+
) {
26+
const { title } = params;
27+
28+
const clientIp = await getClientIp();
29+
30+
const identifier = clientIp;
31+
const rateLimitResult = await ratelimit.limit(identifier);
32+
33+
NextResponse.next().headers.set(
34+
"X-RateLimit-Limit",
35+
rateLimitResult.limit.toString(),
36+
);
37+
NextResponse.next().headers.set(
38+
"X-RateLimit-Remaining",
39+
rateLimitResult.remaining.toString(),
40+
);
41+
42+
try {
43+
const data: Hook[] = await loadData();
44+
const hook = data.find((hook) => hook.title === title);
45+
46+
if (!hook) {
47+
return NextResponse.json(
48+
{ error: "Couldn't find the requested hook." },
49+
{ status: 404 },
50+
);
51+
}
52+
53+
if (!rateLimitResult.success) {
54+
return NextResponse.json(
55+
{ error: "Ratelimit exceeded. Please try again in a few seconds." },
56+
{ status: 429 },
57+
);
58+
}
59+
60+
return NextResponse.json(hook, {
61+
status: 200,
62+
headers: {
63+
"Access-Control-Allow-Origin": "*",
64+
"Access-Control-Allow-Methods": "GET",
65+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
66+
},
67+
});
68+
} catch (error) {
69+
console.error("Error:", error);
70+
return NextResponse.json(
71+
{ error: "Internal Server Error" },
72+
{ status: 500 },
73+
);
74+
}
75+
}

apps/docs/app/api/react/route.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { getClientIp, type Hook } from "@rehooks/utils";
3+
import { ratelimit } from "@/lib/redis/ratelimit";
4+
import { join } from "path";
5+
import { readFile } from "fs";
6+
7+
export const dynamic = "force-dynamic";
8+
9+
const filePath = join(process.cwd(), "lib", "db", "react.json");
10+
11+
async function loadData(): Promise<Hook[]> {
12+
return new Promise((resolve, reject) => {
13+
readFile(filePath, "utf8", (err, data) => {
14+
if (err) {
15+
console.error("Error loading data:", err);
16+
reject(err);
17+
} else {
18+
resolve(JSON.parse(data));
19+
}
20+
});
21+
});
22+
}
23+
24+
export async function GET(req: NextRequest) {
25+
const clientIp = await getClientIp();
26+
const identifier = clientIp;
27+
const rateLimitResult = await ratelimit.limit(identifier);
28+
29+
NextResponse.next().headers.set(
30+
"X-RateLimit-Limit",
31+
rateLimitResult.limit.toString(),
32+
);
33+
NextResponse.next().headers.set(
34+
"X-RateLimit-Remaining",
35+
rateLimitResult.remaining.toString(),
36+
);
37+
38+
try {
39+
const url = new URL(req.url);
40+
const limit = url.searchParams.get("limit");
41+
const search = url.searchParams.get("search");
42+
const data: Hook[] = await loadData();
43+
let result = data;
44+
if (search) {
45+
result = data.filter((hook) =>
46+
hook.title.toLowerCase().includes(search.toLowerCase()),
47+
);
48+
}
49+
50+
if (limit) {
51+
const parsedLimit = Number(limit);
52+
if (isNaN(parsedLimit) || parsedLimit <= 0) {
53+
return NextResponse.json(
54+
{ error: "Invalid limit. It must be a positive number." },
55+
{ status: 400 },
56+
);
57+
}
58+
result = result.slice(0, parsedLimit);
59+
}
60+
61+
if (!rateLimitResult.success) {
62+
return NextResponse.json(
63+
{ error: "Ratelimit exceeded. Please try again in a few seconds." },
64+
{ status: 429 },
65+
);
66+
}
67+
68+
return NextResponse.json(result, {
69+
status: 200,
70+
headers: {
71+
"Access-Control-Allow-Origin": "*",
72+
"Access-Control-Allow-Methods": "GET",
73+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
74+
},
75+
});
76+
} catch (error) {
77+
console.error("Error:", error);
78+
return NextResponse.json(
79+
{ error: "Internal Server Error" },
80+
{
81+
status: 500,
82+
},
83+
);
84+
}
85+
}

apps/docs/app/api/route.ts

-7
This file was deleted.

apps/docs/lib/db/next.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[
2+
{
3+
"id": 1,
4+
"title": "useActiveRoute",
5+
"description": "Checks if current pathname is equal to the routes array object that we passed in.",
6+
"content": {
7+
"server": "type Route<T = {}> = (\n | { path: string; href?: never }\n | { href: string; path?: never }\n) &\n T;\n\ntype Props<T = {}> = {\n routes: Route<T>[];\n pathname: string;\n};\n\nexport function isActiveRoute<T = {}>({ routes, pathname }: Props<T>): boolean {\n const isActive = routes.some((route) => {\n const routePath = route.path || route.href;\n return routePath === pathname;\n });\n\n return isActive;\n}\n",
8+
"client": "\"use client\";\n\nimport { usePathname } from \"next/navigation\";\n\ntype Route<T = {}> = (\n | { path: string; href?: never }\n | { href: string; path?: never }\n) &\n T;\n\ntype Props<T = {}> = {\n routes: Route<T>[];\n};\n\nexport function useActiveRoute<T = {}>({ routes }: Props<T>): boolean {\n const pathname = usePathname();\n\n const isActive = routes.some((route) => {\n const routePath = route.path || route.href;\n return routePath === pathname;\n });\n\n return isActive;\n}\n"
9+
}
10+
},
11+
{
12+
"id": 2,
13+
"title": "useGetQueries",
14+
"description": "Returns the query params from the URL with both Server and Client support.",
15+
"content": {
16+
"server": "import { URLSearchParams } from \"url\";\n\ntype QueryParams<T extends Record<string, unknown>> = {\n [K in keyof T]: T[K];\n};\n\nexport function getQueries<T extends Record<string, unknown>>(\n searchParams: URLSearchParams | Record<string, string | string[]>,\n): QueryParams<T> {\n const params: Partial<QueryParams<T>> = {};\n\n const paramsObject =\n searchParams instanceof URLSearchParams\n ? Object.fromEntries(searchParams.entries())\n : searchParams;\n\n for (const [key, value] of Object.entries(paramsObject)) {\n const stringValue = Array.isArray(value) ? value.join(\",\") : value;\n\n if (stringValue === \"true\") {\n params[key as keyof T] = true as T[keyof T];\n } else if (stringValue.includes(\",\")) {\n params[key as keyof T] = stringValue.split(\",\") as T[keyof T];\n } else {\n params[key as keyof T] = stringValue as T[keyof T];\n }\n }\n\n return params as QueryParams<T>;\n}\n",
17+
"client": "\"use client\";\n\nimport { useSearchParams } from \"next/navigation\";\n\ntype QueryParams<T extends Record<string, unknown>> = {\n [K in keyof T]: T[K];\n};\n\nexport function useGetQueries<\n T extends Record<string, unknown>,\n>(): QueryParams<T> {\n const searchParams = useSearchParams();\n const params: Partial<QueryParams<T>> = {};\n\n searchParams.forEach((value, key) => {\n if (value === \"true\") {\n params[key as keyof T] = true as T[keyof T];\n } else if (value.includes(\",\")) {\n params[key as keyof T] = value.split(\",\") as T[keyof T];\n } else {\n params[key as keyof T] = value as T[keyof T];\n }\n });\n\n return params as QueryParams<T>;\n}\n"
18+
}
19+
}
20+
]
File renamed without changes.

scripts/importer/src/main.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ fn main() {
55
let next_dir = "../../packages/core/src/next";
66

77

8-
let react_output_path = "../../apps/api/react.json";
9-
let next_output_path = "../../apps/api/next.json";
8+
let react_output_path = "../../apps/docs/lib/db/react.json";
9+
let next_output_path = "../../apps/docs/lib/db/next.json";
1010

1111

1212
match import_hooks_from_src(react_dir, next_dir) {
@@ -29,4 +29,4 @@ fn main() {
2929
}
3030
Err(e) => eprintln!("Error importing hooks: {}", e),
3131
}
32-
}
32+
}

0 commit comments

Comments
 (0)