From c4f63553db9ac28a4911e46c1b829e76b928d979 Mon Sep 17 00:00:00 2001 From: Huynh Phuc Date: Tue, 27 Jan 2026 07:37:27 +0700 Subject: [PATCH 1/2] Huynh Trong Phuc - Setup 00 and Fixed Beast Mode Manual --- .github/chatmodes/beast-mode.md | 3 +++ .vscode/settings.json | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 .github/chatmodes/beast-mode.md create mode 100644 .vscode/settings.json diff --git a/.github/chatmodes/beast-mode.md b/.github/chatmodes/beast-mode.md new file mode 100644 index 0000000..c981381 --- /dev/null +++ b/.github/chatmodes/beast-mode.md @@ -0,0 +1,3 @@ +- Always provide high-performance code solutions. +- Focus on logic and security, no conversational fillers. +- Deeply analyze project structure before suggesting edits. diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..51a564d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "chat.tools.autoApprove": true, + "chat.agent.maxRequests": 100 +} \ No newline at end of file From 4a065652dc92f42be8e998952d0b598e96725c0d Mon Sep 17 00:00:00 2001 From: Huynh Phuc Date: Thu, 29 Jan 2026 13:47:39 +0700 Subject: [PATCH 2/2] =?UTF-8?q?xong=20m=E1=BB=A5c=2002?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 +- .vscode/settings.json | 9 +- complete/README.md | 42 +- complete/openapi.yaml | 208 ++--- docs/02-javascript.md | 95 +- javascript/.gitignore | 24 + javascript/README.md | 5 - javascript/index.html | 12 + javascript/package-lock.json | 890 +++++++++++++++++++ javascript/package.json | 21 + javascript/public/vite.svg | 1 + javascript/src/App.jsx | 57 ++ javascript/src/components/Home.jsx | 22 + javascript/src/components/NameInputModal.jsx | 22 + javascript/src/components/PostDetails.jsx | 20 + javascript/src/components/PostModal.jsx | 21 + javascript/src/components/Search.jsx | 15 + javascript/src/counter.js | 9 + javascript/src/javascript.svg | 1 + javascript/src/main.jsx | 10 + javascript/src/style.css | 96 ++ openapi.yaml | 130 +++ python/main.py | 109 +++ sns_api.db | Bin 0 -> 28672 bytes 24 files changed, 1647 insertions(+), 180 deletions(-) create mode 100644 javascript/.gitignore delete mode 100644 javascript/README.md create mode 100644 javascript/index.html create mode 100644 javascript/package-lock.json create mode 100644 javascript/package.json create mode 100644 javascript/public/vite.svg create mode 100644 javascript/src/App.jsx create mode 100644 javascript/src/components/Home.jsx create mode 100644 javascript/src/components/NameInputModal.jsx create mode 100644 javascript/src/components/PostDetails.jsx create mode 100644 javascript/src/components/PostModal.jsx create mode 100644 javascript/src/components/Search.jsx create mode 100644 javascript/src/counter.js create mode 100644 javascript/src/javascript.svg create mode 100644 javascript/src/main.jsx create mode 100644 javascript/src/style.css create mode 100644 openapi.yaml create mode 100644 python/main.py create mode 100644 sns_api.db diff --git a/.gitignore b/.gitignore index 1720c36..e643075 100644 --- a/.gitignore +++ b/.gitignore @@ -440,4 +440,10 @@ mtj.tmp/ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -replay_pid* \ No newline at end of file +replay_pid* + +# Workshop Specific +python/sns_api.db +python/.venv/ +javascript/node_modules/ +javascript/dist/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 51a564d..6f5444d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,9 @@ { "chat.tools.autoApprove": true, - "chat.agent.maxRequests": 100 -} \ No newline at end of file + "chat.agent.maxRequests": 100, + "chat.mcp.serverSampling": { + "github-copilot-vibe-coding-workshop/.vscode/mcp.json: Framelink Figma MCP": { + "allowedModels": ["copilot/auto"] + } + } +} diff --git a/complete/README.md b/complete/README.md index 1be3ce8..500dc1e 100644 --- a/complete/README.md +++ b/complete/README.md @@ -3,7 +3,7 @@ Here are the list of complete app samples. Because they are also vibe-coded, you can check out how they're built. | Application | Location | -|-------------|-----------------------------| +| ----------- | --------------------------- | | FastAPI | [python](./python/) | | React | [javascript](./javascript/) | | Spring Boot | [java](./java/) | @@ -19,38 +19,38 @@ Refer to the [README](../README.md) doc for preparation. 1. Make sure that Docker is running. - ```bash - docker info - ``` + ```bash + docker info + ``` 1. Get the repository root. - ```bash - # bash/zsh - REPOSITORY_ROOT=$(git rev-parse --show-toplevel) - ``` + ```bash + # bash/zsh + REPOSITORY_ROOT=$(git rev-parse --show-toplevel) + ``` - ```powershell - # PowerShell - $REPOSITORY_ROOT = git rev-parse --show-toplevel - ``` + ```powershell + # PowerShell + $REPOSITORY_ROOT = git rev-parse --show-toplevel + ``` 1. Navigate to the `complete` directory. - ```bash - cd $REPOSITORY_ROOT/complete - ``` + ```bash + cd $REPOSITORY_ROOT/complete + ``` 1. Run the containerized apps. - ```bash - docker compose up --build -d - ``` + ```bash + docker compose up --build -d + ``` 1. Open a web browser and navigate to `http://localhost:3030`. 1. Verify if the web application is running properly. 1. Clean up by running the following command to remove the containerized apps. - ```bash - docker compose down --rmi all - ``` + ```bash + docker compose down --rmi all + ``` diff --git a/complete/openapi.yaml b/complete/openapi.yaml index d058b78..fc16691 100644 --- a/complete/openapi.yaml +++ b/complete/openapi.yaml @@ -23,17 +23,17 @@ paths: tags: - Posts responses: - '200': + "200": description: Successfully retrieved posts content: application/json: schema: type: array items: - $ref: '#/components/schemas/Post' - '500': - $ref: '#/components/responses/InternalServerError' - + $ref: "#/components/schemas/Post" + "500": + $ref: "#/components/responses/InternalServerError" + post: summary: Create a new post description: Create a new post to share something with others @@ -45,18 +45,18 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NewPostRequest' + $ref: "#/components/schemas/NewPostRequest" responses: - '201': + "201": description: Post created successfully content: application/json: schema: - $ref: '#/components/schemas/Post' - '400': - $ref: '#/components/responses/BadRequest' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/Post" + "400": + $ref: "#/components/responses/BadRequest" + "500": + $ref: "#/components/responses/InternalServerError" /posts/{postId}: get: @@ -66,19 +66,19 @@ paths: tags: - Posts parameters: - - $ref: '#/components/parameters/PostIdPath' + - $ref: "#/components/parameters/PostIdPath" responses: - '200': + "200": description: Successfully retrieved the post content: application/json: schema: - $ref: '#/components/schemas/Post' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' - + $ref: "#/components/schemas/Post" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" + patch: summary: Update a post description: Update an existing post if you made a mistake or have something to add @@ -86,27 +86,27 @@ paths: tags: - Posts parameters: - - $ref: '#/components/parameters/PostIdPath' + - $ref: "#/components/parameters/PostIdPath" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdatePostRequest' + $ref: "#/components/schemas/UpdatePostRequest" responses: - '200': + "200": description: Post updated successfully content: application/json: schema: - $ref: '#/components/schemas/Post' - '400': - $ref: '#/components/responses/BadRequest' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' - + $ref: "#/components/schemas/Post" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" + delete: summary: Delete a post description: Delete a post if you no longer want it shared @@ -114,14 +114,14 @@ paths: tags: - Posts parameters: - - $ref: '#/components/parameters/PostIdPath' + - $ref: "#/components/parameters/PostIdPath" responses: - '204': + "204": description: Post deleted successfully - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" /posts/{postId}/comments: get: @@ -131,21 +131,21 @@ paths: tags: - Comments parameters: - - $ref: '#/components/parameters/PostIdPath' + - $ref: "#/components/parameters/PostIdPath" responses: - '200': + "200": description: Successfully retrieved comments content: application/json: schema: type: array items: - $ref: '#/components/schemas/Comment' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' - + $ref: "#/components/schemas/Comment" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" + post: summary: Create a comment description: Add a comment to a post to share your thoughts @@ -153,26 +153,26 @@ paths: tags: - Comments parameters: - - $ref: '#/components/parameters/PostIdPath' + - $ref: "#/components/parameters/PostIdPath" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/NewCommentRequest' + $ref: "#/components/schemas/NewCommentRequest" responses: - '201': + "201": description: Comment created successfully content: application/json: schema: - $ref: '#/components/schemas/Comment' - '400': - $ref: '#/components/responses/BadRequest' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: "#/components/schemas/Comment" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" /posts/{postId}/comments/{commentId}: get: @@ -182,20 +182,20 @@ paths: tags: - Comments parameters: - - $ref: '#/components/parameters/PostIdPath' - - $ref: '#/components/parameters/CommentIdPath' + - $ref: "#/components/parameters/PostIdPath" + - $ref: "#/components/parameters/CommentIdPath" responses: - '200': + "200": description: Successfully retrieved the comment content: application/json: schema: - $ref: '#/components/schemas/Comment' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' - + $ref: "#/components/schemas/Comment" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" + patch: summary: Update a comment description: Update an existing comment to correct or revise it @@ -203,28 +203,28 @@ paths: tags: - Comments parameters: - - $ref: '#/components/parameters/PostIdPath' - - $ref: '#/components/parameters/CommentIdPath' + - $ref: "#/components/parameters/PostIdPath" + - $ref: "#/components/parameters/CommentIdPath" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/UpdateCommentRequest' + $ref: "#/components/schemas/UpdateCommentRequest" responses: - '200': + "200": description: Comment updated successfully content: application/json: schema: - $ref: '#/components/schemas/Comment' - '400': - $ref: '#/components/responses/BadRequest' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' - + $ref: "#/components/schemas/Comment" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" + delete: summary: Delete a comment description: Delete a comment if necessary @@ -232,15 +232,15 @@ paths: tags: - Comments parameters: - - $ref: '#/components/parameters/PostIdPath' - - $ref: '#/components/parameters/CommentIdPath' + - $ref: "#/components/parameters/PostIdPath" + - $ref: "#/components/parameters/CommentIdPath" responses: - '204': + "204": description: Comment deleted successfully - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" /posts/{postId}/likes: post: @@ -250,27 +250,27 @@ paths: tags: - Likes parameters: - - $ref: '#/components/parameters/PostIdPath' + - $ref: "#/components/parameters/PostIdPath" requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/LikeRequest' + $ref: "#/components/schemas/LikeRequest" responses: - '201': + "201": description: Post liked successfully content: application/json: schema: - $ref: '#/components/schemas/LikeResponse' - '400': - $ref: '#/components/responses/BadRequest' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' - + $ref: "#/components/schemas/LikeResponse" + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" + delete: summary: Unlike a post description: Remove your like from a post if you change your mind @@ -278,14 +278,14 @@ paths: tags: - Likes parameters: - - $ref: '#/components/parameters/PostIdPath' + - $ref: "#/components/parameters/PostIdPath" responses: - '204': + "204": description: Like removed successfully - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' + "404": + $ref: "#/components/responses/NotFound" + "500": + $ref: "#/components/responses/InternalServerError" components: parameters: @@ -298,7 +298,7 @@ components: type: string format: uuid example: "123e4567-e89b-12d3-a456-426614174000" - + CommentIdPath: name: commentId in: path @@ -539,7 +539,7 @@ components: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: "#/components/schemas/Error" example: error: "VALIDATION_ERROR" message: "The request body is invalid" @@ -550,7 +550,7 @@ components: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: "#/components/schemas/Error" example: error: "NOT_FOUND" message: "The requested resource was not found" @@ -560,7 +560,7 @@ components: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: "#/components/schemas/Error" example: error: "INTERNAL_ERROR" message: "An unexpected error occurred" diff --git a/docs/02-javascript.md b/docs/02-javascript.md index 97e5b6a..e0d9234 100644 --- a/docs/02-javascript.md +++ b/docs/02-javascript.md @@ -51,17 +51,17 @@ Refer to the [README](../README.md) doc for preparation. 1. Copy custom instructions. - ```bash - # bash/zsh - cp -r $REPOSITORY_ROOT/docs/custom-instructions/javascript/. \ - $REPOSITORY_ROOT/.github/ - ``` - - ```powershell - # PowerShell - Copy-Item -Path $REPOSITORY_ROOT/docs/custom-instructions/javascript/* ` - -Destination $REPOSITORY_ROOT/.github/ -Recurse -Force - ``` + ```bash + # bash/zsh + cp -r $REPOSITORY_ROOT/docs/custom-instructions/javascript/. \ + $REPOSITORY_ROOT/.github/ + ``` + + ```powershell + # PowerShell + Copy-Item -Path $REPOSITORY_ROOT/docs/custom-instructions/javascript/* ` + -Destination $REPOSITORY_ROOT/.github/ -Recurse -Force + ``` ### Prepare Application Project @@ -69,18 +69,18 @@ Refer to the [README](../README.md) doc for preparation. 1. Make sure that the `context7` MCP server is up and running. 1. Use prompt like below to scaffold a React web app project. - ```text - I'd like to scaffold a React web app. Follow the instructions below. - - - Make sure it's the web app, not the mobile app. - - Your working directory is `javascript`. - - Identify all the steps first, which you're going to do. - - Use ViteJS as the frontend app framework. - - Use default settings when initializing the project. - - Use `SimpleSocialMediaApplication` as the name of the project while initializing. - - Use the port number of `3000`. - - Only initialize the project. DO NOT go further. - ``` + ```text + I'd like to scaffold a React web app. Follow the instructions below. + + - Make sure it's the web app, not the mobile app. + - Your working directory is `javascript`. + - Identify all the steps first, which you're going to do. + - Use ViteJS as the frontend app framework. + - Use default settings when initializing the project. + - Use `SimpleSocialMediaApplication` as the name of the project while initializing. + - Use the port number of `3000`. + - Only initialize the project. DO NOT go further. + ``` 1. Click the ![the "keep" button image](https://img.shields.io/badge/keep-blue) button of GitHub Copilot to take the changes. @@ -106,9 +106,10 @@ Refer to the [README](../README.md) doc for preparation. 1. Make sure that the FastAPI backend app is up and running. - ```text - Run the FastAPI backend API, which is located at the `python` directory. - ``` + ```text + + Run the FastAPI backend API, which is located at the `python` directory. + ``` > **NOTE**: You may use the [`complete/python`](../complete/python/) sample app instead. @@ -122,20 +123,20 @@ Refer to the [README](../README.md) doc for preparation. 1. Add [`product-requirements.md`](../product-requirements.md) and [`openapi.yaml`](../openapi.yaml) to GitHub Copilot. 1. Use prompt like below to build the application based on the requirements and OpenAPI document. - ```text - I'd like to build a React web app. Follow the instructions below. - - - Your working directory is `javascript`. - - Identify all the steps first, which you're going to do. - - There's a backend API app running on `http://localhost:8000`. - - Use `openapi.yaml` that describes all the endpoints and data schema. - - Use the port number of `3000`. - - Create all the UI components defined in this link: {{FIGMA_SECTION_LINK}}. - - DO NOT add anything not related to the UI components. - - DO NOT add anything not defined in `openapi.yaml`. - - DO NOT modify anything defined in `openapi.yaml`. - - Give visual indication when the backend API is unavailable or unreachable for any reason. - ``` + ```text + I'd like to build a React web app. Follow the instructions below. + + - Your working directory is `javascript`. + - Identify all the steps first, which you're going to do. + - There's a backend API app running on `http://localhost:8000`. + - Use `openapi.yaml` that describes all the endpoints and data schema. + - Use the port number of `3000`. + - Create all the UI components defined in this link: {{FIGMA_SECTION_LINK}}. + - DO NOT add anything not related to the UI components. + - DO NOT add anything not defined in `openapi.yaml`. + - DO NOT modify anything defined in `openapi.yaml`. + - Give visual indication when the backend API is unavailable or unreachable for any reason. + ``` 1. Repeat four more times for the rest four Figma design links. 1. Click the ![the "keep" button image](https://img.shields.io/badge/keep-blue) button of GitHub Copilot to take the changes. @@ -144,17 +145,17 @@ Refer to the [README](../README.md) doc for preparation. 1. Make sure that the FastAPI backend app is up and running. - ```text - Run the FastAPI backend API, which is located at the `python` directory. - ``` + ```text + Run the FastAPI backend API, which is located at the `python` directory. + ``` 1. Verify if it's built properly or not. - ```text - Run the React app and verify if the app is properly running. + ```text + Run the React app and verify if the app is properly running. - If app running fails, analyze the issues and fix them. - ``` + If app running fails, analyze the issues and fix them. + ``` 1. Open a web browser and navigate to `http://localhost:3000`. 1. Verify if both frontend and backend apps are running properly. diff --git a/javascript/.gitignore b/javascript/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/javascript/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/javascript/README.md b/javascript/README.md deleted file mode 100644 index 8358ffb..0000000 --- a/javascript/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Getting Started with JavaScript - -Here's the starting point for your JavaScript app development. - -If you want to see the complete example, check out this directory, [/complete/javascript](../complete/javascript/). diff --git a/javascript/index.html b/javascript/index.html new file mode 100644 index 0000000..6c02a08 --- /dev/null +++ b/javascript/index.html @@ -0,0 +1,12 @@ + + + + + + Contoso Social + + +
+ + + diff --git a/javascript/package-lock.json b/javascript/package-lock.json new file mode 100644 index 0000000..74e222a --- /dev/null +++ b/javascript/package-lock.json @@ -0,0 +1,890 @@ +{ + "name": "javascript", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "javascript", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.4", + "react-dom": "^19.2.4" + }, + "devDependencies": { + "vite": "npm:rolldown-vite@7.2.5" + } + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.97.0.tgz", + "integrity": "sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.97.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.97.0.tgz", + "integrity": "sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.50.tgz", + "integrity": "sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.50.tgz", + "integrity": "sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.50.tgz", + "integrity": "sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.50.tgz", + "integrity": "sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.50.tgz", + "integrity": "sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.50.tgz", + "integrity": "sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.50.tgz", + "integrity": "sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.50.tgz", + "integrity": "sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.50.tgz", + "integrity": "sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.50.tgz", + "integrity": "sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.50.tgz", + "integrity": "sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.50.tgz", + "integrity": "sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.50.tgz", + "integrity": "sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.50.tgz", + "integrity": "sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz", + "integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.50", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.50.tgz", + "integrity": "sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.97.0", + "@rolldown/pluginutils": "1.0.0-beta.50" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.50", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.50", + "@rolldown/binding-darwin-x64": "1.0.0-beta.50", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.50", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.50", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.50", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.50", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.50", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.50", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.50", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.50", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.50", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.50", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.50" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/vite": { + "name": "rolldown-vite", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.2.5.tgz", + "integrity": "sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "0.97.0", + "fdir": "^6.5.0", + "lightningcss": "^1.30.2", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rolldown": "1.0.0-beta.50", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "esbuild": "^0.25.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/javascript/package.json b/javascript/package.json new file mode 100644 index 0000000..53df1a8 --- /dev/null +++ b/javascript/package.json @@ -0,0 +1,21 @@ +{ + "name": "javascript", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "npm:rolldown-vite@7.2.5" + }, + "overrides": { + "vite": "npm:rolldown-vite@7.2.5" + }, + "dependencies": { + "react": "^19.2.4", + "react-dom": "^19.2.4" + } +} diff --git a/javascript/public/vite.svg b/javascript/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/javascript/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/javascript/src/App.jsx b/javascript/src/App.jsx new file mode 100644 index 0000000..3ff2723 --- /dev/null +++ b/javascript/src/App.jsx @@ -0,0 +1,57 @@ +import React, { useState, useEffect } from "react"; +import Home from "./components/Home"; +import Search from "./components/Search"; +import PostModal from "./components/PostModal"; +import NameInputModal from "./components/NameInputModal"; + +function App() { + const [posts, setPosts] = useState([]); + const [username, setUsername] = useState( + localStorage.getItem("username") || "", + ); + const [isModalOpen, setIsModalOpen] = useState(false); + + // Gọi API lấy bài đăng từ Backend Python + const fetchPosts = async () => { + try { + const response = await fetch("http://localhost:8000/posts"); + const data = await response.json(); + setPosts(data); + } catch (err) { + console.error("Không thể kết nối Backend:", err); + } + }; + + useEffect(() => { + if (username) fetchPosts(); + }, [username]); + + if (!username) { + return ( + { + localStorage.setItem("username", name); + setUsername(name); + }} + /> + ); + } + + return ( +
+ + { + /* Logic tìm kiếm */ + }} + /> + + setIsModalOpen(false)} /> +
+ ); +} + +export default App; diff --git a/javascript/src/components/Home.jsx b/javascript/src/components/Home.jsx new file mode 100644 index 0000000..e281e2e --- /dev/null +++ b/javascript/src/components/Home.jsx @@ -0,0 +1,22 @@ +import React from "react"; + +const Home = ({ posts }) => { + return ( +
+
+

Contoso Social

+
+
+ {posts.map((post) => ( +
+

@{post.username}

+

{post.content}

+ {new Date(post.createdAt).toLocaleString()} +
+ ))} +
+
+ ); +}; + +export default Home; diff --git a/javascript/src/components/NameInputModal.jsx b/javascript/src/components/NameInputModal.jsx new file mode 100644 index 0000000..3ab9be3 --- /dev/null +++ b/javascript/src/components/NameInputModal.jsx @@ -0,0 +1,22 @@ +import React from "react"; + +const NameInputModal = ({ onSubmit }) => { + return ( +
+
+

Chào mừng!

+

Vui lòng nhập tên của bạn để tiếp tục:

+ + +
+
+ ); +}; + +export default NameInputModal; diff --git a/javascript/src/components/PostDetails.jsx b/javascript/src/components/PostDetails.jsx new file mode 100644 index 0000000..2a5095c --- /dev/null +++ b/javascript/src/components/PostDetails.jsx @@ -0,0 +1,20 @@ +import React from "react"; + +const PostDetails = ({ post, comments }) => { + if (!post) return null; + return ( +
+

{post.username}

+

{post.content}

+
+
Bình luận:
+ {comments.map((c) => ( +
+ {c.username}: {c.content} +
+ ))} +
+ ); +}; + +export default PostDetails; diff --git a/javascript/src/components/PostModal.jsx b/javascript/src/components/PostModal.jsx new file mode 100644 index 0000000..50b4b51 --- /dev/null +++ b/javascript/src/components/PostModal.jsx @@ -0,0 +1,21 @@ +import React from "react"; + +const PostModal = ({ isOpen, onClose, onSubmit }) => { + if (!isOpen) return null; + return ( +
+
+

Tạo bài viết mới

+ + + +
+
+ ); +}; + +export default PostModal; diff --git a/javascript/src/components/Search.jsx b/javascript/src/components/Search.jsx new file mode 100644 index 0000000..8e65575 --- /dev/null +++ b/javascript/src/components/Search.jsx @@ -0,0 +1,15 @@ +import React from "react"; + +const Search = ({ onSearch }) => { + return ( +
+ onSearch(e.target.value)} + /> +
+ ); +}; + +export default Search; diff --git a/javascript/src/counter.js b/javascript/src/counter.js new file mode 100644 index 0000000..881e2d7 --- /dev/null +++ b/javascript/src/counter.js @@ -0,0 +1,9 @@ +export function setupCounter(element) { + let counter = 0 + const setCounter = (count) => { + counter = count + element.innerHTML = `count is ${counter}` + } + element.addEventListener('click', () => setCounter(counter + 1)) + setCounter(0) +} diff --git a/javascript/src/javascript.svg b/javascript/src/javascript.svg new file mode 100644 index 0000000..f9abb2b --- /dev/null +++ b/javascript/src/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/javascript/src/main.jsx b/javascript/src/main.jsx new file mode 100644 index 0000000..50fda9b --- /dev/null +++ b/javascript/src/main.jsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App.jsx"; +import "./style.css"; + +ReactDOM.createRoot(document.getElementById("root")).render( + + + , +); diff --git a/javascript/src/style.css b/javascript/src/style.css new file mode 100644 index 0000000..8df73e3 --- /dev/null +++ b/javascript/src/style.css @@ -0,0 +1,96 @@ +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.vanilla:hover { + filter: drop-shadow(0 0 2em #f7df1eaa); +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..6f301b1 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,130 @@ +openapi: 3.0.1 +info: + title: Contoso Product API + description: API quản lý sản phẩm cho ứng dụng mạng xã hội Contoso. + version: 1.0.0 + +servers: + - url: http://localhost:8000 + +tags: + - name: Products + description: Quản lý danh mục sản phẩm của Contoso + - name: Posts + description: Các hoạt động liên quan đến bài viết + - name: Comments + description: Quản lý bình luận + - name: Likes + description: Hệ thống tương tác lượt thích + +paths: + /products: + get: + tags: + - Products + summary: Lấy danh sách sản phẩm + responses: + "200": + description: Thành công + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Product" + post: + tags: + - Products + summary: Tạo sản phẩm mới + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/NewProductRequest" + responses: + "201": + description: Đã tạo thành công + "400": + $ref: "#/components/responses/BadRequest" + + /products/{productId}: + get: + tags: + - Products + summary: Lấy chi tiết một sản phẩm + parameters: + - name: productId + in: path + required: true + schema: + type: string + responses: + "200": + description: Thành công + "404": + $ref: "#/components/responses/NotFound" + +components: + parameters: + PostIdPath: + name: postId + in: path + required: true + schema: + type: string + format: uuid + CommentIdPath: + name: commentId + in: path + required: true + schema: + type: string + format: uuid + + schemas: + Product: + type: object + required: [id, name, price, createdAt] + properties: + id: { type: string } + name: { type: string } + description: { type: string } + price: { type: number, format: float } + createdAt: { type: string, format: date-time } + + NewProductRequest: + type: object + required: [name, price] + properties: + name: { type: string } + description: { type: string } + price: { type: number, format: float } + + Error: + type: object + required: [error, message] + properties: + error: { type: string } + message: { type: string } + details: { type: array, items: { type: string } } + + responses: + BadRequest: + description: Lỗi yêu cầu không hợp lệ + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + NotFound: + description: Không tìm thấy tài nguyên + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + InternalServerError: + description: Lỗi máy chủ nội bộ + content: + application/json: + schema: + $ref: "#/components/schemas/Error" diff --git a/python/main.py b/python/main.py new file mode 100644 index 0000000..20b034e --- /dev/null +++ b/python/main.py @@ -0,0 +1,109 @@ +import os +import yaml +import uuid +import aiosqlite +from datetime import datetime +from pathlib import Path +from typing import List, Optional +from fastapi import FastAPI, HTTPException, Request, status +from fastapi.responses import JSONResponse +from fastapi.middleware.cors import CORSMiddleware +from fastapi.openapi.docs import get_swagger_ui_html + +# --- CẤU HÌNH ĐƯỜNG DẪN --- +DB_PATH = "sns_api.db" +# Tìm file openapi.yaml ở thư mục gốc +OPENAPI_PATH = Path(__file__).parent.parent / "openapi.yaml" + +if not OPENAPI_PATH.exists(): + OPENAPI_PATH = Path(__file__).parent / "openapi.yaml" + +# Nạp file đặc tả OpenAPI +with open(OPENAPI_PATH.resolve(), encoding="utf-8") as f: + openapi_yaml = yaml.safe_load(f) + +app = FastAPI(title="Contoso Social API", openapi_url="/openapi.json") + +# --- CẤU HÌNH CORS (Cho phép React từ cổng 3000 truy cập) --- +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# --- KHỞI TẠO DATABASE --- +@app.on_event("startup") +async def startup_db(): + async with aiosqlite.connect(DB_PATH) as db: + # Tạo bảng Sản phẩm + await db.execute(""" + CREATE TABLE IF NOT EXISTS products ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + price REAL NOT NULL, + createdAt TEXT NOT NULL + ) + """) + # Tạo bảng Bài đăng (Posts) + await db.execute(""" + CREATE TABLE IF NOT EXISTS posts ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL, + content TEXT NOT NULL, + createdAt TEXT NOT NULL, + likesCount INTEGER DEFAULT 0 + ) + """) + await db.commit() + app.openapi_schema = openapi_yaml + +# --- ENDPOINTS CHO PRODUCTS --- +@app.get("/products", tags=["Products"]) +async def get_products(): + async with aiosqlite.connect(DB_PATH) as db: + db.row_factory = aiosqlite.Row + cursor = await db.execute("SELECT * FROM products") + rows = await cursor.fetchall() + return [dict(row) for row in rows] + +@app.post("/products", status_code=201, tags=["Products"]) +async def create_product(product: dict): + product_id = str(uuid.uuid4()) + created_at = datetime.utcnow().isoformat() + "Z" + async with aiosqlite.connect(DB_PATH) as db: + await db.execute( + "INSERT INTO products (id, name, description, price, createdAt) VALUES (?, ?, ?, ?, ?)", + (product_id, product['name'], product.get('description', ''), product['price'], created_at) + ) + await db.commit() + return {"id": product_id, **product, "createdAt": created_at} + +# --- ENDPOINTS CHO POSTS (Dùng cho React Frontend) --- +@app.get("/posts", tags=["Posts"]) +async def get_posts(): + async with aiosqlite.connect(DB_PATH) as db: + db.row_factory = aiosqlite.Row + cursor = await db.execute("SELECT * FROM posts ORDER BY createdAt DESC") + rows = await cursor.fetchall() + return [dict(row) for row in rows] + +@app.post("/posts", status_code=201, tags=["Posts"]) +async def create_post(post: dict): + post_id = str(uuid.uuid4()) + created_at = datetime.utcnow().isoformat() + "Z" + async with aiosqlite.connect(DB_PATH) as db: + await db.execute( + "INSERT INTO posts (id, username, content, createdAt) VALUES (?, ?, ?, ?)", + (post_id, post['username'], post['content'], created_at) + ) + await db.commit() + return {"id": post_id, **post, "createdAt": created_at} + +# --- KHỞI CHẠY SERVER --- +if __name__ == "__main__": + import uvicorn + print("Đang khởi động Server tại cổng 8000...") + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/sns_api.db b/sns_api.db new file mode 100644 index 0000000000000000000000000000000000000000..428195fe7d61caf8cd9200d07d239c2bfab63816 GIT binary patch literal 28672 zcmeI%(QDH{0LSsm+NM(0b#Hkp+};)#YVlbx)@&5x)S7{P3bBn)Y@69!!51I4f)D;N z{saCE{tf##d^EZ4WP{Yf@F{!)xl8WSG{4VX9>TqQ8%$LqKc>rhtmG51FDy$ulTrv# z%lBr!o2@vno0Gh@ZX9nrtci!;+;-=;uy?P--p|gj_K&^qyI1Y?wja=r00IagfB*sr zAbhnlHTGZ23^I}wMI^C|dZs<7UbUsfODl>KcRw&bYoojlX>Bi%1 zV%;zBI9;gxzsmxTmr1O)X{^pCWt}beQQda>-E9meOw=<+jmwUu$9DZyV-ztmUF_Uq z&`oz4nBgdm^M8|W(Dius(^W1wI1T+1fB1K@$rt{ycNT>5@#az!_^zw-nY8n-nWQ12 zp00{-`3J>2)h>$tqF_S+0R#|0009ILKmY**5I_KdstUAW?uA5kLR|1Q0*~0R#|0009IL_yf7G B_H+OM literal 0 HcmV?d00001