Skip to content

Commit 065ddea

Browse files
author
RooCode
committed
feat: add animals random API with swagger documentation
- Add /api/animals/random endpoint - Add swagger documentation and UI - Add configuration files for swagger - Add sample animal data - Update i18n messages Signed-off-by: RooCode
1 parent cb81227 commit 065ddea

File tree

16 files changed

+1791
-15
lines changed

16 files changed

+1791
-15
lines changed

.clinerules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ DO NOT read or modify:
3535

3636
- When create a new api route, remember create swagger api document
3737
- When create new UI component, priority to use shadcn@latest before create new component
38+
- When create new page, priority to use i18n with two languages: English and Vietnamese
3839

3940
## Development Environment
4041

.devcontainer/devcontainer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@
8585
]
8686
}
8787
},
88-
"postCreateCommand": "bash .devcontainer/setup.sh && bash .devcontainer/prerun.sh && pnpm dev",
88+
"onCreateCommand": "bash .devcontainer/setup.sh",
89+
"postCreateCommand": "bash .devcontainer/prerun.sh",
90+
"postStartCommand": "pnpm dev",
8991
"remoteUser": "node",
9092
"containerUser": "node"
9193
}

.devcontainer/prerun.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
echo "==> START INSTALL <=="
44
echo "==> Current user: $(whoami)"
55

6+
echo "==> Config git credentail ..."
7+
git config --global user.name "devcontainer"
8+
git config --global user.email "[email protected]"
9+
610
echo "==> Install latest corepack ..."
711
sudo npm install -g corepack@latest
812

.devcontainer/setup.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ if ! grep -q "HISTFILE" "$ZSHRC_PATH"; then
4848
echo "export HISTFILE=$HISTFILE_PATH" >> "$ZSHRC_PATH"
4949
fi
5050

51+
echo "==> Install uv (use for MCP server)..."
52+
if ! command -v uv &> /dev/null; then
53+
curl -LsSf https://astral.sh/uv/install.sh | sh
54+
else
55+
echo "==> uv already installed."
56+
fi
57+
5158
echo "==> Reloading .zshrc..."
5259
zsh -c "source $ZSHRC_PATH"
5360

app/[locale]/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import { LoadingComponent } from "@/components/custom/Loading";
12
import { Badge } from "@/components/ui/badge";
23
import { Separator } from "@/components/ui/separator";
3-
import { getTranslations, setRequestLocale } from "next-intl/server";
4-
import { Metadata } from "next";
54
import { locale } from "@/types/global";
5+
import { Metadata } from "next";
6+
import { getTranslations, setRequestLocale } from "next-intl/server";
67
import { Suspense } from "react";
78
import { UserList } from "./dynamic";
8-
import { LoadingComponent } from "@/components/custom/Loading";
99

1010
type PageType = {
1111
params: Promise<{ locale: locale }>;
@@ -18,7 +18,7 @@ export async function generateMetadata(): Promise<Metadata> {
1818
};
1919
}
2020

21-
const techs: string[] = ["NextJS 15", "Shadcn/ui", "TailwindCSS", "Prisma"];
21+
const techs: string[] = ["NextJS 15", "Shadcn", "TailwindCSS 3", "Prisma 6"];
2222

2323
export default async function Page({ params }: PageType) {
2424
const { locale } = await params;

app/api-docs/layout.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import "@/app/globals.css";
2+
3+
export default function ApiDocsLayout({ children }: { children: React.ReactNode }) {
4+
return (
5+
<html lang='en'>
6+
<body>
7+
<div className='min-h-screen'>{children}</div>
8+
</body>
9+
</html>
10+
);
11+
}

app/api-docs/page.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use client";
2+
3+
import SwaggerUI from "swagger-ui-react";
4+
import "swagger-ui-react/swagger-ui.css";
5+
6+
function ApiDoc() {
7+
return (
8+
<div className='container mx-auto p-4'>
9+
<h1 className='text-2xl font-bold mb-4'>API Documentation</h1>
10+
<div className='swagger-ui-wrapper bg-white rounded-lg shadow'>
11+
<SwaggerUI url='/api/docs' />
12+
</div>
13+
</div>
14+
);
15+
}
16+
17+
export default ApiDoc;

app/api/animals/random/route.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { animals, type Animal } from "@/configs/data/animals";
3+
4+
/**
5+
* @swagger
6+
* /api/animals/random:
7+
* get:
8+
* summary: Get random animal information
9+
* description: Retrieve information about a random animal from our collection
10+
* tags:
11+
* - Animals
12+
* parameters:
13+
* - in: query
14+
* name: type
15+
* schema:
16+
* type: string
17+
* enum: [mammal, bird, reptile, fish, amphibian]
18+
* description: Filter by animal type (optional)
19+
* responses:
20+
* 200:
21+
* description: Successfully retrieved random animal
22+
* content:
23+
* application/json:
24+
* schema:
25+
* type: object
26+
* properties:
27+
* error:
28+
* type: null
29+
* data:
30+
* type: object
31+
* properties:
32+
* status:
33+
* type: number
34+
* example: 200
35+
* payload:
36+
* type: object
37+
* properties:
38+
* id:
39+
* type: string
40+
* example: "lion"
41+
* name:
42+
* type: string
43+
* example: "Lion"
44+
* type:
45+
* type: string
46+
* example: "mammal"
47+
* image:
48+
* type: string
49+
* example: "https://source.unsplash.com/featured/?lion"
50+
* description:
51+
* type: string
52+
* example: "The king of the jungle, known for its majestic mane"
53+
* 400:
54+
* description: Bad request - Invalid animal type
55+
*/
56+
export async function GET(request: NextRequest) {
57+
try {
58+
const searchParams = request.nextUrl.searchParams;
59+
const type = searchParams.get("type") as Animal["type"] | null;
60+
61+
let filteredAnimals = animals;
62+
if (type) {
63+
filteredAnimals = animals.filter((animal) => animal.type === type);
64+
if (filteredAnimals.length === 0) {
65+
return NextResponse.json(
66+
{ error: { message: `No animals found of type: ${type}` }, data: null },
67+
{ status: 400 }
68+
);
69+
}
70+
}
71+
72+
const randomIndex = Math.floor(Math.random() * filteredAnimals.length);
73+
const animal = filteredAnimals[randomIndex];
74+
75+
return NextResponse.json({
76+
error: null,
77+
data: {
78+
status: 200,
79+
payload: animal
80+
}
81+
});
82+
} catch (error) {
83+
const errorMessage = error instanceof Error ? error.message : "Internal Server Error";
84+
return NextResponse.json({ error: { message: errorMessage }, data: null }, { status: 500 });
85+
}
86+
}

app/api/docs/route.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { getApiDocs } from "@/configs/swagger/config";
2+
import { NextResponse } from "next/server";
3+
4+
export async function GET() {
5+
return NextResponse.json(getApiDocs());
6+
}

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { ReactNode } from "react";
21
import "@/app/globals.css";
32
import { Metadata } from "next";
3+
import { ReactNode } from "react";
44

55
export const metadata: Metadata = {
66
title: {

0 commit comments

Comments
 (0)