diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..de763ea --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,4 @@ +name: runs test on PR + +on: + pull \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1800114..97933d1 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,8 @@ cover/ local_settings.py db.sqlite3 db.sqlite3-journal +media/ +staticfiles/ # Flask stuff: instance/ @@ -171,4 +173,35 @@ cython_debug/ .ruff_cache/ # PyPI configuration file -.pypirc \ No newline at end of file +.pypirc + +# Node +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnp/ +.pnp.js + +# Testing +coverage/ +.coverage +htmlcov/ +.pytest_cache/ +.tox/ + +# IDEs and editors +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store +Thumbs.db + +# Frontend build +frontend/build/ +frontend/dist/ +frontend/.env.local +frontend/.env.development.local +frontend/.env.test.local +frontend/.env.production.local \ No newline at end of file diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..d087a46 --- /dev/null +++ b/PRD.md @@ -0,0 +1,120 @@ +# 2D Metaverse - Product Requirements Document + +## 🎯 Project Overview +A 2D virtual world platform where users can interact, customize their avatars, and explore different spaces. + +## 🎯 Core Features + +### 1. User Management +- User registration and authentication +- Profile management +- Avatar customization +- User settings and preferences + +### 2. Virtual Spaces +- Multiple customizable rooms/spaces +- Space creation and management (admin) +- Space templates +- Space permissions and access control + +### 3. Avatar System +- Avatar creation and customization +- Real-time avatar movement +- Avatar animations +- Avatar interactions with objects + +### 4. Interactive Elements +- Furniture and decorative items +- Interactive objects (portals, games, etc.) +- Object placement and rotation +- Collision detection + +### 5. Real-time Communication +- WebSocket-based real-time updates +- Chat system +- User presence indicators +- Notifications + +### 6. Admin Features +- Space management +- User management +- Content moderation +- Analytics dashboard + +## 🎯 Technical Requirements + +### Backend +- Django REST Framework for API +- WebSocket support for real-time features +- PostgreSQL database +- Redis for caching +- Celery for background tasks + +### Frontend +- React.js for UI +- Phaser.js for 2D rendering +- WebSocket client +- Responsive design +- Progressive Web App capabilities + +### Security +- JWT authentication +- HTTPS +- Input validation +- Rate limiting +- CSRF protection + +## 🎯 User Stories + +### As a User +1. I want to create an account and customize my avatar +2. I want to join and explore different spaces +3. I want to chat with other users +4. I want to interact with objects in the space +5. I want to customize my profile + +### As an Admin +1. I want to manage users and spaces +2. I want to moderate content +3. I want to view analytics +4. I want to create and manage templates + +## 🎯 Non-Functional Requirements +- Performance: < 100ms response time +- Scalability: Support 1000+ concurrent users +- Availability: 99.9% uptime +- Security: Regular security audits +- Accessibility: WCAG 2.1 compliance + +## 🎯 Development Phases + +### Phase 1: Foundation +- Basic user authentication +- Avatar system +- Simple space creation +- Basic WebSocket implementation + +### Phase 2: Core Features +- Enhanced space management +- Interactive elements +- Real-time chat +- User interactions + +### Phase 3: Advanced Features +- Advanced customization +- Game mechanics +- Social features +- Analytics + +### Phase 4: Polish & Scale +- Performance optimization +- Enhanced security +- Advanced moderation tools +- Scalability improvements + +## 🎯 Success Metrics +- User engagement (time spent, interactions) +- User retention +- Space creation and usage +- System performance +- User satisfaction \ No newline at end of file diff --git a/README.md b/README.md index 7bbd060..16bae82 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,124 @@ -🚀 Emoji Commit Message Convention +# 2D Metaverse -We follow an emoji-based commit message convention to make our Git history more readable and organized. Each commit should begin with an appropriate emoji that represents the type of change. +A virtual world platform where users can interact, customize their avatars, and explore different spaces. +## Features -## **🔥 Emoji Commit Message Convention** +- User authentication and registration +- Avatar customization +- Real-time movement and interaction +- Multiple customizable spaces +- Interactive elements +- WebSocket-based communication +- Admin dashboard +## Tech Stack -### 📋 Emoji Guide +### Backend +- Django +- Django REST Framework +- Channels (WebSockets) +- PostgreSQL +- Redis -| Emoji | Name | Meaning | Example Commit Message | -| --- | --- | --- | --- | -| 🎉 **tada** | Initial commit | `🎉 feat: initial project setup` | -| ✨ **sparkles** | New feature | `✨ feat: add user authentication` | -| 🐛 **bug** | Bug fix | `🐛 fix: resolve issue with login form` | -| 🚑 **ambulance** | Critical hotfix | `🚑 fix: urgent API authentication issue` | -| 🛠️ **hammer_and_wrench** | Refactoring | `🛠️ refactor: optimize database queries` | -| 🚀 **rocket** | Performance improvement | `🚀 perf: speed up image processing` | -| 🎨 **art** | UI/UX improvements | `🎨 style: improve button hover effect` | -| 📝 **memo** | Documentation | `📝 docs: update README with setup guide` | -| ✅ **white_check_mark** | Adding tests | `✅ test: add unit test for user model` | -| 🔍 **mag** | Fixing or updating tests | `🔍 test: fix broken API test` | -| 🚨 **rotating_light** | Fixing warnings/errors | `🚨 fix: remove unused imports` | -| 🔥 **fire** | Removing code/files | `🔥 chore: remove unused config file` | -| 📦 **package** | Dependency changes | `📦 chore: upgrade Django to 4.2.1` | -| 🗃️ **card_file_box** | Database changes | `🗃️ db: add new field to User model` | -| 🔄 **repeat** | CI/CD updates | `🔄 ci: update GitHub Actions workflow` | -| 🏗️ **building_construction** | Work in progress (WIP) | `🏗️ wip: implement notifications feature` | -| 🔧 **wrench** | Configuration changes | `🔧 chore: update pytest settings` | -| ⚡ **zap** | Minor optimization | `⚡ perf: reduce API response time` | -| 🔊 **loud_sound** | Logging updates | `🔊 chore: improve error logging` | -| ♻️ **recycle** | Code restructuring without changing behavior | `♻️ refactor: simplify API response formatting` | +### Frontend +- React +- TypeScript +- Phaser.js +- Material-UI ---- +## Setup -### ✅ Usage Guidelines -- Keep commit messages **clear and concise**. -- Use **present tense** (e.g., `fix: update layout`, not `fixed` or `fixes`). -- Separate emoji and type with a space (e.g., `✨ feat: add dark mode`). -- Stick to the **convention** to maintain consistency in commit history. +### Prerequisites +- Python 3.8+ +- Node.js 14+ +- PostgreSQL +- Redis -This helps keep your Git history **clean, readable, and well-organized**! 🚀 +### Backend Setup ---- +1. Create a virtual environment: +```bash +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +``` -## **📌 Example Commit Messages Using This Convention** +2. Install dependencies: +```bash +pip install -r requirements.txt +``` -1. `✅ test: add API test for Avatar retrieval` -2. `🐛 fix: resolve 404 error in Avatar API` -3. `📝 docs: update API documentation` -4. `🚀 perf: optimize database queries for faster retrieval` -5. `🔍 test: fix failing test case in Avatar endpoint` +3. Set up environment variables: +```bash +cp .env.example .env +# Edit .env with your configuration +``` ---- \ No newline at end of file +4. Run migrations: +```bash +python manage.py migrate +``` + +5. Create a superuser: +```bash +python manage.py createsuperuser +``` + +### Frontend Setup + +1. Install dependencies: +```bash +cd frontend +npm install +``` + +2. Start the development server: +```bash +npm start +``` + +## Running the Application + +1. Start the backend server: +```bash +python manage.py runserver +``` + +2. Start the frontend development server: +```bash +cd frontend +npm start +``` + +3. Access the application at `http://localhost:3000` + +## API Documentation + +### Authentication +- POST `/api/auth/register/` - Register a new user +- POST `/api/auth/login/` - Login and get JWT tokens + +### Spaces +- GET `/api/spaces/all/` - List all spaces +- POST `/api/spaces/new/` - Create a new space +- DELETE `/api/spaces/delete//` - Delete a space + +### Avatars +- GET `/api/avatars//` - Get avatar details +- POST `/api/admin/avatar/new/` - Create a new avatar (admin only) + +### Elements +- POST `/api/admin/element/new/` - Create a new element (admin only) +- PUT `/api/admin/element/update//` - Update an element (admin only) + +## Contributing + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m '✨ feat: add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..d7b3270 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1583 @@ +{ + "name": "frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^7.0.2", + "@mui/material": "^7.0.2", + "@types/node": "^22.14.1", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "axios": "^1.8.4", + "phaser": "^3.88.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.5.0", + "socket.io-client": "^4.8.1", + "typescript": "^5.8.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.0.2.tgz", + "integrity": "sha512-TfeFU9TgN1N06hyb/pV/63FfO34nijZRMqgHk0TJ3gkl4Fbd+wZ73+ZtOd7jag6hMmzO9HSrBc6Vdn591nhkAg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.0.2.tgz", + "integrity": "sha512-Bo57PFLOqXOqPNrXjd8AhzH5s6TCsNUQbvnQ0VKZ8D+lIlteqKnrk/O1luMJUc/BXONK7BfIdTdc7qOnXYbMdw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.0.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.0.2.tgz", + "integrity": "sha512-rjJlJ13+3LdLfobRplkXbjIFEIkn6LgpetgU/Cs3Xd8qINCCQK9qXQIjjQ6P0FXFTPFzEVMj0VgBR1mN+FhOcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/core-downloads-tracker": "^7.0.2", + "@mui/system": "^7.0.2", + "@mui/types": "^7.4.1", + "@mui/utils": "^7.0.2", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.0.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.0.2.tgz", + "integrity": "sha512-6lt8heDC9wN8YaRqEdhqnm0cFCv08AMf4IlttFvOVn7ZdKd81PNpD/rEtPGLLwQAFyyKSxBG4/2XCgpbcdNKiA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/utils": "^7.0.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.0.2.tgz", + "integrity": "sha512-11Bt4YdHGlh7sB8P75S9mRCUxTlgv7HGbr0UKz6m6Z9KLeiw1Bm9y/t3iqLLVMvSHYB6zL8X8X+LmfTE++gyBw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.0.2.tgz", + "integrity": "sha512-yFUraAWYWuKIISPPEVPSQ1NLeqmTT4qiQ+ktmyS8LO/KwHxB+NNVOacEZaIofh5x1NxY8rzphvU5X2heRZ/RDA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/private-theming": "^7.0.2", + "@mui/styled-engine": "^7.0.2", + "@mui/types": "^7.4.1", + "@mui/utils": "^7.0.2", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.1.tgz", + "integrity": "sha512-gUL8IIAI52CRXP/MixT1tJKt3SI6tVv4U/9soFsTtAsHzaJQptZ42ffdHZV3niX1ei0aUgMvOxBBN0KYqdG39g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.0.2.tgz", + "integrity": "sha512-72gcuQjPzhj/MLmPHLCgZjy2VjOH4KniR/4qRtXTTXIEwbkgcN+Y5W/rC90rWtMmZbjt9svZev/z+QHUI4j74w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/types": "^7.4.1", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/phaser": { + "version": "3.88.2", + "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.88.2.tgz", + "integrity": "sha512-UBgd2sAFuRJbF2xKaQ5jpMWB8oETncChLnymLGHcrnT53vaqiGrQWbUKUDBawKLm24sghjKo4Bf+/xfv8espZQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.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==", + "license": "ISC" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" + }, + "node_modules/react-router": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.0.tgz", + "integrity": "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.0.tgz", + "integrity": "sha512-fFhGFCULy4vIseTtH5PNcY/VvDJK5gvOWcwJVHQp8JQcWVr85ENhJ3UpuF/zP1tQOIFYNRJHzXtyhU1Bdgw0RA==", + "license": "MIT", + "dependencies": { + "react-router": "7.5.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..404b3e7 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,45 @@ +{ + "name": "2d-metaverse-frontend", + "version": "1.0.0", + "private": true, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^7.0.2", + "@mui/material": "^7.0.2", + "@types/node": "^22.14.1", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "axios": "^1.8.4", + "phaser": "^3.88.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.5.0", + "socket.io-client": "^4.8.1", + "typescript": "^5.8.3" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..8e34791 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; + +// Pages +import Login from './pages/Login'; +import Register from './pages/Register'; +import Metaverse from './pages/Metaverse'; +import Space from './pages/Space'; +import Profile from './pages/Profile'; + +const theme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#90caf9', + }, + secondary: { + main: '#f48fb1', + }, + }, +}); + +function App() { + return ( + + + + + } /> + } /> + } /> + } /> + } /> + + + + ); +} + +export default App; \ No newline at end of file diff --git a/frontend/src/game/scenes/MainScene.tsx b/frontend/src/game/scenes/MainScene.tsx new file mode 100644 index 0000000..7de60d0 --- /dev/null +++ b/frontend/src/game/scenes/MainScene.tsx @@ -0,0 +1,103 @@ +import Phaser from 'phaser'; +import axios from 'axios'; + +export default class MainScene extends Phaser.Scene { + private player!: Phaser.Physics.Arcade.Sprite; + private cursors!: Phaser.Types.Input.Keyboard.CursorKeys; + private spaceElements: Phaser.GameObjects.Sprite[] = []; + private socket!: WebSocket; + + constructor() { + super({ key: 'MainScene' }); + } + + preload() { + // Load player avatar + this.load.image('player', '/static/avatars/default.png'); + + // Load space elements + this.load.image('chair', '/static/elements/chair.png'); + this.load.image('table', '/static/elements/table.png'); + } + + async create() { + // Initialize WebSocket connection + this.socket = new WebSocket('ws://localhost:8000/ws/metaverse/'); + + this.socket.onmessage = (event) => { + const data = JSON.parse(event.data); + this.handleWebSocketMessage(data); + }; + + // Create player + this.player = this.physics.add.sprite(400, 300, 'player'); + this.player.setCollideWorldBounds(true); + + // Set up keyboard input + this.cursors = this.input.keyboard.createCursorKeys(); + + // Load space elements + await this.loadSpaceElements(); + + // Set up collisions + this.physics.add.collider(this.player, this.spaceElements); + } + + async loadSpaceElements() { + try { + const response = await axios.get('/api/spaces/all/'); + const spaces = response.data; + + spaces.forEach((space: any) => { + const element = this.physics.add.sprite( + space.x * 32, + space.y * 32, + space.element.type + ); + this.spaceElements.push(element); + }); + } catch (error) { + console.error('Error loading space elements:', error); + } + } + + handleWebSocketMessage(data: any) { + // Handle real-time updates from other players + if (data.type === 'player_move') { + // Update other player positions + } else if (data.type === 'chat') { + // Handle chat messages + } + } + + update() { + // Handle player movement + const speed = 160; + const playerBody = this.player.body as Phaser.Physics.Arcade.Body; + + if (this.cursors.left.isDown) { + playerBody.setVelocityX(-speed); + } else if (this.cursors.right.isDown) { + playerBody.setVelocityX(speed); + } else { + playerBody.setVelocityX(0); + } + + if (this.cursors.up.isDown) { + playerBody.setVelocityY(-speed); + } else if (this.cursors.down.isDown) { + playerBody.setVelocityY(speed); + } else { + playerBody.setVelocityY(0); + } + + // Send player position to server + if (this.socket.readyState === WebSocket.OPEN) { + this.socket.send(JSON.stringify({ + type: 'player_move', + x: this.player.x, + y: this.player.y + })); + } + } +} \ No newline at end of file diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 0000000..c0ab386 --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,113 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Container, + Paper, + TextField, + Button, + Typography, + Box, + Link, +} from '@mui/material'; +import axios from 'axios'; + +const Login: React.FC = () => { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const response = await axios.post('/api/auth/login/', { + email, + password, + }); + + // Store the token + localStorage.setItem('token', response.data.token); + + // Redirect to metaverse + navigate('/'); + } catch (err) { + setError('Invalid email or password'); + } + }; + + return ( + + + + + Sign in to 2D Metaverse + + + {error && ( + + {error} + + )} + + + setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + + + + {"Don't have an account? Sign Up"} + + + + + + + ); +}; + +export default Login; \ No newline at end of file diff --git a/frontend/src/pages/Metaverse.tsx b/frontend/src/pages/Metaverse.tsx new file mode 100644 index 0000000..0c8b64d --- /dev/null +++ b/frontend/src/pages/Metaverse.tsx @@ -0,0 +1,67 @@ +import React, { useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; +import Phaser from 'phaser'; +import { Box, Typography, Button } from '@mui/material'; +import axios from 'axios'; + +// Game scenes +import MainScene from '../game/scenes/MainScene'; + +const Metaverse: React.FC = () => { + const gameRef = useRef(null); + const navigate = useNavigate(); + + useEffect(() => { + const config: Phaser.Types.Core.GameConfig = { + type: Phaser.AUTO, + width: window.innerWidth, + height: window.innerHeight, + parent: 'game-container', + scene: [MainScene], + physics: { + default: 'arcade', + arcade: { + gravity: { y: 0 }, + debug: false + } + } + }; + + gameRef.current = new Phaser.Game(config); + + return () => { + if (gameRef.current) { + gameRef.current.destroy(true); + } + }; + }, []); + + const handleCreateSpace = async () => { + try { + const response = await axios.post('/api/spaces/new/', { + name: 'New Space', + width: 800, + height: 600, + map: 1 // Default map ID + }); + + navigate(`/space/${response.data.space_id}`); + } catch (error) { + console.error('Error creating space:', error); + } + }; + + return ( + + + 2D Metaverse + + + + + ); +}; + +export default Metaverse; \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..b653b3d --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": "src" + }, + "include": ["src"] +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a397b07 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +Django==4.2.7 +djangorestframework==3.14.0 +djangorestframework-simplejwt==5.3.1 +channels==4.0.0 +channels-redis==4.1.0 +psycopg2-binary==2.9.9 +python-dotenv==1.0.0 +gunicorn==21.2.0 +whitenoise==6.6.0 +django-cors-headers==4.3.1 +django-filter==23.5 +pillow==10.1.0 \ No newline at end of file diff --git a/src/Modern tiles_Free/Characters_free/Adam_16x16.png b/src/Modern tiles_Free/Characters_free/Adam_16x16.png new file mode 100644 index 0000000..064b989 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Adam_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Adam_idle_16x16.png b/src/Modern tiles_Free/Characters_free/Adam_idle_16x16.png new file mode 100644 index 0000000..9ceac7f Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Adam_idle_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Adam_idle_anim_16x16.png b/src/Modern tiles_Free/Characters_free/Adam_idle_anim_16x16.png new file mode 100644 index 0000000..40cb1fd Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Adam_idle_anim_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Adam_phone_16x16.png b/src/Modern tiles_Free/Characters_free/Adam_phone_16x16.png new file mode 100644 index 0000000..5d45019 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Adam_phone_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Adam_run_16x16.png b/src/Modern tiles_Free/Characters_free/Adam_run_16x16.png new file mode 100644 index 0000000..f98a934 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Adam_run_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Adam_sit2_16x16.png b/src/Modern tiles_Free/Characters_free/Adam_sit2_16x16.png new file mode 100644 index 0000000..69d518f Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Adam_sit2_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Adam_sit3_16x16.png b/src/Modern tiles_Free/Characters_free/Adam_sit3_16x16.png new file mode 100644 index 0000000..61a8a2a Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Adam_sit3_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Adam_sit_16x16.png b/src/Modern tiles_Free/Characters_free/Adam_sit_16x16.png new file mode 100644 index 0000000..3a00634 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Adam_sit_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Alex_16x16.png b/src/Modern tiles_Free/Characters_free/Alex_16x16.png new file mode 100644 index 0000000..b9bc04a Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Alex_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Alex_idle_16x16.png b/src/Modern tiles_Free/Characters_free/Alex_idle_16x16.png new file mode 100644 index 0000000..604c54b Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Alex_idle_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Alex_idle_anim_16x16.png b/src/Modern tiles_Free/Characters_free/Alex_idle_anim_16x16.png new file mode 100644 index 0000000..9615cdc Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Alex_idle_anim_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Alex_phone_16x16.png b/src/Modern tiles_Free/Characters_free/Alex_phone_16x16.png new file mode 100644 index 0000000..60c9aaa Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Alex_phone_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Alex_run_16x16.png b/src/Modern tiles_Free/Characters_free/Alex_run_16x16.png new file mode 100644 index 0000000..3424961 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Alex_run_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Alex_sit2_16x16.png b/src/Modern tiles_Free/Characters_free/Alex_sit2_16x16.png new file mode 100644 index 0000000..472ec4d Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Alex_sit2_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Alex_sit3_16x16.png b/src/Modern tiles_Free/Characters_free/Alex_sit3_16x16.png new file mode 100644 index 0000000..7172807 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Alex_sit3_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Alex_sit_16x16.png b/src/Modern tiles_Free/Characters_free/Alex_sit_16x16.png new file mode 100644 index 0000000..28b453b Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Alex_sit_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Amelia_16x16.png b/src/Modern tiles_Free/Characters_free/Amelia_16x16.png new file mode 100644 index 0000000..d26ccd7 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Amelia_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Amelia_idle_16x16.png b/src/Modern tiles_Free/Characters_free/Amelia_idle_16x16.png new file mode 100644 index 0000000..ffc92b3 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Amelia_idle_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Amelia_idle_anim_16x16.png b/src/Modern tiles_Free/Characters_free/Amelia_idle_anim_16x16.png new file mode 100644 index 0000000..7c53b67 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Amelia_idle_anim_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Amelia_phone_16x16.png b/src/Modern tiles_Free/Characters_free/Amelia_phone_16x16.png new file mode 100644 index 0000000..3ddb496 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Amelia_phone_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Amelia_run_16x16.png b/src/Modern tiles_Free/Characters_free/Amelia_run_16x16.png new file mode 100644 index 0000000..e422c6f Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Amelia_run_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Amelia_sit2_16x16.png b/src/Modern tiles_Free/Characters_free/Amelia_sit2_16x16.png new file mode 100644 index 0000000..5d80524 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Amelia_sit2_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Amelia_sit3_16x16.png b/src/Modern tiles_Free/Characters_free/Amelia_sit3_16x16.png new file mode 100644 index 0000000..9785cfc Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Amelia_sit3_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Amelia_sit_16x16.png b/src/Modern tiles_Free/Characters_free/Amelia_sit_16x16.png new file mode 100644 index 0000000..1c5f5bf Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Amelia_sit_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Bob_16x16.png b/src/Modern tiles_Free/Characters_free/Bob_16x16.png new file mode 100644 index 0000000..387b966 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Bob_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Bob_idle_16x16.png b/src/Modern tiles_Free/Characters_free/Bob_idle_16x16.png new file mode 100644 index 0000000..3debe6e Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Bob_idle_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Bob_idle_anim_16x16.png b/src/Modern tiles_Free/Characters_free/Bob_idle_anim_16x16.png new file mode 100644 index 0000000..5c635f1 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Bob_idle_anim_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Bob_phone_16x16.png b/src/Modern tiles_Free/Characters_free/Bob_phone_16x16.png new file mode 100644 index 0000000..363d8aa Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Bob_phone_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Bob_run_16x16.png b/src/Modern tiles_Free/Characters_free/Bob_run_16x16.png new file mode 100644 index 0000000..0c760a2 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Bob_run_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Bob_sit2_16x16.png b/src/Modern tiles_Free/Characters_free/Bob_sit2_16x16.png new file mode 100644 index 0000000..bea3091 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Bob_sit2_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Bob_sit3_16x16.png b/src/Modern tiles_Free/Characters_free/Bob_sit3_16x16.png new file mode 100644 index 0000000..5c1a66f Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Bob_sit3_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/Bob_sit_16x16.png b/src/Modern tiles_Free/Characters_free/Bob_sit_16x16.png new file mode 100644 index 0000000..fbec5d6 Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/Bob_sit_16x16.png differ diff --git a/src/Modern tiles_Free/Characters_free/RPGMAKERMV/Characters_MV.png b/src/Modern tiles_Free/Characters_free/RPGMAKERMV/Characters_MV.png new file mode 100644 index 0000000..3a8d38e Binary files /dev/null and b/src/Modern tiles_Free/Characters_free/RPGMAKERMV/Characters_MV.png differ diff --git a/src/Modern tiles_Free/Interiors_free/16x16/Interiors_free_16x16.png b/src/Modern tiles_Free/Interiors_free/16x16/Interiors_free_16x16.png new file mode 100644 index 0000000..3a203b0 Binary files /dev/null and b/src/Modern tiles_Free/Interiors_free/16x16/Interiors_free_16x16.png differ diff --git a/src/Modern tiles_Free/Interiors_free/16x16/Room_Builder_free_16x16.png b/src/Modern tiles_Free/Interiors_free/16x16/Room_Builder_free_16x16.png new file mode 100644 index 0000000..026bca5 Binary files /dev/null and b/src/Modern tiles_Free/Interiors_free/16x16/Room_Builder_free_16x16.png differ diff --git a/src/Modern tiles_Free/Interiors_free/32x32/Interiors_free_32x32.png b/src/Modern tiles_Free/Interiors_free/32x32/Interiors_free_32x32.png new file mode 100644 index 0000000..7b99268 Binary files /dev/null and b/src/Modern tiles_Free/Interiors_free/32x32/Interiors_free_32x32.png differ diff --git a/src/Modern tiles_Free/Interiors_free/32x32/Room_Builder_free_32x32.png b/src/Modern tiles_Free/Interiors_free/32x32/Room_Builder_free_32x32.png new file mode 100644 index 0000000..d2f2a36 Binary files /dev/null and b/src/Modern tiles_Free/Interiors_free/32x32/Room_Builder_free_32x32.png differ diff --git a/src/Modern tiles_Free/Interiors_free/48x48/Interiors_free_48x48.png b/src/Modern tiles_Free/Interiors_free/48x48/Interiors_free_48x48.png new file mode 100644 index 0000000..5679118 Binary files /dev/null and b/src/Modern tiles_Free/Interiors_free/48x48/Interiors_free_48x48.png differ diff --git a/src/Modern tiles_Free/Interiors_free/48x48/Room_Builder_free_48x48.png b/src/Modern tiles_Free/Interiors_free/48x48/Room_Builder_free_48x48.png new file mode 100644 index 0000000..de0ae7d Binary files /dev/null and b/src/Modern tiles_Free/Interiors_free/48x48/Room_Builder_free_48x48.png differ diff --git a/src/Modern tiles_Free/LICENSE.txt b/src/Modern tiles_Free/LICENSE.txt new file mode 100644 index 0000000..43eeac9 --- /dev/null +++ b/src/Modern tiles_Free/LICENSE.txt @@ -0,0 +1,11 @@ + +FREE VERSION LICENSE: + +CAN: +YOU CAN USE THE ASSET IN NON COMMERCIAL PROJECTS +YOU CAN EDIT THE SPRITES AND USE THEM IN NON COMMERCIAL PROJECTS + +CAN'T: +YOU CAN'T USE THE ASSET IN COMMERCIAL PROJECTS +YOU CAN'T EDIT THE SPRITES AND USE THEM IN COMMERCIAL PROJECTS +YOU CAN'T EDIT AND RESELL THE SPRITES \ No newline at end of file diff --git a/src/Modern tiles_Free/Old/Tileset_16x16_1.png b/src/Modern tiles_Free/Old/Tileset_16x16_1.png new file mode 100644 index 0000000..8bc554d Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_16x16_1.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_16x16_16.png b/src/Modern tiles_Free/Old/Tileset_16x16_16.png new file mode 100644 index 0000000..b636a87 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_16x16_16.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_16x16_2.png b/src/Modern tiles_Free/Old/Tileset_16x16_2.png new file mode 100644 index 0000000..96fe65f Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_16x16_2.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_16x16_3.png b/src/Modern tiles_Free/Old/Tileset_16x16_3.png new file mode 100644 index 0000000..2f4513c Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_16x16_3.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_16x16_9.png b/src/Modern tiles_Free/Old/Tileset_16x16_9.png new file mode 100644 index 0000000..0cac549 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_16x16_9.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_32x32_1.png b/src/Modern tiles_Free/Old/Tileset_32x32_1.png new file mode 100644 index 0000000..0cddcb8 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_32x32_1.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_32x32_16.png b/src/Modern tiles_Free/Old/Tileset_32x32_16.png new file mode 100644 index 0000000..2857706 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_32x32_16.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_32x32_2.png b/src/Modern tiles_Free/Old/Tileset_32x32_2.png new file mode 100644 index 0000000..ec3ccd4 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_32x32_2.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_32x32_3.png b/src/Modern tiles_Free/Old/Tileset_32x32_3.png new file mode 100644 index 0000000..d8f8950 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_32x32_3.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_32x32_9.png b/src/Modern tiles_Free/Old/Tileset_32x32_9.png new file mode 100644 index 0000000..fa9a5e8 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_32x32_9.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_48x48_1.png b/src/Modern tiles_Free/Old/Tileset_48x48_1.png new file mode 100644 index 0000000..e6eb064 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_48x48_1.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_48x48_16.png b/src/Modern tiles_Free/Old/Tileset_48x48_16.png new file mode 100644 index 0000000..bf250dc Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_48x48_16.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_48x48_2.png b/src/Modern tiles_Free/Old/Tileset_48x48_2.png new file mode 100644 index 0000000..55d5fba Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_48x48_2.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_48x48_3.png b/src/Modern tiles_Free/Old/Tileset_48x48_3.png new file mode 100644 index 0000000..a75704f Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_48x48_3.png differ diff --git a/src/Modern tiles_Free/Old/Tileset_48x48_9.png b/src/Modern tiles_Free/Old/Tileset_48x48_9.png new file mode 100644 index 0000000..b893294 Binary files /dev/null and b/src/Modern tiles_Free/Old/Tileset_48x48_9.png differ diff --git a/src/Modern tiles_Free/Old/idle_16x16_2.png b/src/Modern tiles_Free/Old/idle_16x16_2.png new file mode 100644 index 0000000..0902f73 Binary files /dev/null and b/src/Modern tiles_Free/Old/idle_16x16_2.png differ diff --git a/src/Modern tiles_Free/Old/idle_32x32_2.png b/src/Modern tiles_Free/Old/idle_32x32_2.png new file mode 100644 index 0000000..a0daf6d Binary files /dev/null and b/src/Modern tiles_Free/Old/idle_32x32_2.png differ diff --git a/src/Modern tiles_Free/Old/idle_48x48_2.png b/src/Modern tiles_Free/Old/idle_48x48_2.png new file mode 100644 index 0000000..b426f06 Binary files /dev/null and b/src/Modern tiles_Free/Old/idle_48x48_2.png differ diff --git a/src/Modern tiles_Free/Old/mv/Character_2_16x16_RPGMAKER.png b/src/Modern tiles_Free/Old/mv/Character_2_16x16_RPGMAKER.png new file mode 100644 index 0000000..73cb9d8 Binary files /dev/null and b/src/Modern tiles_Free/Old/mv/Character_2_16x16_RPGMAKER.png differ diff --git a/src/Modern tiles_Free/Old/mv/Character_2_32x32_RPGMAKER.png b/src/Modern tiles_Free/Old/mv/Character_2_32x32_RPGMAKER.png new file mode 100644 index 0000000..ba47bdb Binary files /dev/null and b/src/Modern tiles_Free/Old/mv/Character_2_32x32_RPGMAKER.png differ diff --git a/src/Modern tiles_Free/Old/mv/Character_2_48x48_RPGMAKER.png b/src/Modern tiles_Free/Old/mv/Character_2_48x48_RPGMAKER.png new file mode 100644 index 0000000..7c03fb0 Binary files /dev/null and b/src/Modern tiles_Free/Old/mv/Character_2_48x48_RPGMAKER.png differ diff --git a/src/Modern tiles_Free/Old/run_horizontal_16x16_2.png b/src/Modern tiles_Free/Old/run_horizontal_16x16_2.png new file mode 100644 index 0000000..9c6cf3c Binary files /dev/null and b/src/Modern tiles_Free/Old/run_horizontal_16x16_2.png differ diff --git a/src/Modern tiles_Free/Old/run_horizontal_32x32_2.png b/src/Modern tiles_Free/Old/run_horizontal_32x32_2.png new file mode 100644 index 0000000..702b4bb Binary files /dev/null and b/src/Modern tiles_Free/Old/run_horizontal_32x32_2.png differ diff --git a/src/Modern tiles_Free/Old/run_horizontal_48x48_2.png b/src/Modern tiles_Free/Old/run_horizontal_48x48_2.png new file mode 100644 index 0000000..22bb72b Binary files /dev/null and b/src/Modern tiles_Free/Old/run_horizontal_48x48_2.png differ diff --git a/src/Modern tiles_Free/READ ME.txt b/src/Modern tiles_Free/READ ME.txt new file mode 100644 index 0000000..5305f31 --- /dev/null +++ b/src/Modern tiles_Free/READ ME.txt @@ -0,0 +1,5 @@ +Hi, thank you for downloading the free version! + +This version has around 1% of material of the full asset + +Consider buying the complete version (1.20$) if you like the asset :) diff --git a/src/Modern tiles_Free/free_overview.png b/src/Modern tiles_Free/free_overview.png new file mode 100644 index 0000000..fb66078 Binary files /dev/null and b/src/Modern tiles_Free/free_overview.png differ diff --git a/src/api/consumers.py b/src/api/consumers.py index 08e2739..69d6293 100644 --- a/src/api/consumers.py +++ b/src/api/consumers.py @@ -1,11 +1,75 @@ import json from channels.generic.websocket import AsyncWebsocketConsumer -from asgiref.sync import sync_to_async from channels.db import database_sync_to_async +from django.contrib.auth.models import AnonymousUser +from .models import CustomUser # Store connected players by room players = {} +class MetaverseConsumer(AsyncWebsocketConsumer): + async def connect(self): + self.room_name = 'metaverse' + self.room_group_name = f'chat_{self.room_name}' + + # Join room group + await self.channel_layer.group_add( + self.room_group_name, + self.channel_name + ) + + await self.accept() + + async def disconnect(self, close_code): + # Leave room group + await self.channel_layer.group_discard( + self.room_group_name, + self.channel_name + ) + + async def receive(self, text_data): + text_data_json = json.loads(text_data) + message_type = text_data_json['type'] + + if message_type == 'player_move': + # Broadcast player movement to all clients + await self.channel_layer.group_send( + self.room_group_name, + { + 'type': 'player_move', + 'user_id': self.scope['user'].id, + 'x': text_data_json['x'], + 'y': text_data_json['y'] + } + ) + elif message_type == 'chat': + # Broadcast chat message to all clients + await self.channel_layer.group_send( + self.room_group_name, + { + 'type': 'chat', + 'user_id': self.scope['user'].id, + 'message': text_data_json['message'] + } + ) + + async def player_move(self, event): + # Send player movement to WebSocket + await self.send(text_data=json.dumps({ + 'type': 'player_move', + 'user_id': event['user_id'], + 'x': event['x'], + 'y': event['y'] + })) + + async def chat(self, event): + # Send chat message to WebSocket + await self.send(text_data=json.dumps({ + 'type': 'chat', + 'user_id': event['user_id'], + 'message': event['message'] + })) + class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): self.room_name = self.scope.get('url_route', {}).get('kwargs', {}).get('room_name', 'default') diff --git a/src/api/migrations/0001_initial.py b/src/api/migrations/0001_initial.py index ea9e9b9..174c0a8 100644 --- a/src/api/migrations/0001_initial.py +++ b/src/api/migrations/0001_initial.py @@ -1,10 +1,10 @@ -# Generated by Django 5.1.5 on 2025-01-28 11:32 +# Generated by Django 5.1.6 on 2025-03-06 09:41 import django.contrib.auth.models import django.contrib.auth.validators import django.db.models.deletion import django.utils.timezone -import uuid +from django.conf import settings from django.db import migrations, models @@ -21,26 +21,30 @@ class Migration(migrations.Migration): name='Avatar', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('image_url', models.URLField(blank=True, null=True)), - ('name', models.CharField(blank=True, max_length=255, null=True)), + ('image_url', models.URLField()), + ('name', models.CharField(max_length=255)), ], ), migrations.CreateModel( name='Element', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('width', models.IntegerField()), - ('height', models.IntegerField()), - ('image_url', models.URLField()), + ('name', models.CharField(max_length=255)), + ('type', models.CharField(choices=[('furniture', 'Furniture'), ('interactive', 'Interactive Object'), ('decoration', 'Decoration'), ('portal', 'Portal'), ('spawn', 'Spawn Point')], max_length=20)), + ('sprite_url', models.URLField()), + ('is_walkable', models.BooleanField(default=False)), + ('interaction_script', models.TextField()), ], ), migrations.CreateModel( name='Map', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), ('width', models.IntegerField()), ('height', models.IntegerField()), - ('name', models.CharField(max_length=255)), + ('background_image', models.URLField()), + ('tile_size', models.IntegerField(default=32)), ], ), migrations.CreateModel( @@ -59,7 +63,6 @@ class Migration(migrations.Migration): ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), - ('avatar_id', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.avatar')), ], options={ 'verbose_name': 'user', @@ -74,20 +77,31 @@ class Migration(migrations.Migration): name='MapElement', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('x', models.IntegerField(blank=True, null=True)), - ('y', models.IntegerField(blank=True, null=True)), - ('element', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.element')), + ('x_coordinate', models.FloatField(default=0)), + ('y_coordinate', models.FloatField(default=0)), + ('rotation', models.FloatField(default=0)), + ('z_index', models.IntegerField(default=0)), + ('element', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.element')), ('map', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.map')), ], + options={ + 'unique_together': {('map', 'x_coordinate', 'y_coordinate')}, + }, + ), + migrations.AddField( + model_name='map', + name='elements', + field=models.ManyToManyField(related_name='maps', through='api.MapElement', to='api.element'), ), migrations.CreateModel( name='Space', fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255)), ('width', models.IntegerField()), - ('height', models.IntegerField(blank=True, null=True)), - ('thumbnail', models.URLField(blank=True, null=True)), + ('height', models.IntegerField()), + ('dimension', models.CharField(max_length=10)), + ('thumbnail', models.URLField()), ('map', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.map')), ], ), @@ -101,4 +115,12 @@ class Migration(migrations.Migration): ('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.space')), ], ), + migrations.CreateModel( + name='UserMetadata', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('avatar', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.avatar')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), ] diff --git a/src/api/routing.py b/src/api/routing.py index 8b19b37..cf65637 100644 --- a/src/api/routing.py +++ b/src/api/routing.py @@ -5,4 +5,5 @@ re_path(r'ws/$', consumers.ChatConsumer.as_asgi()), re_path(r'ws/chat/(?P\w+)/$', consumers.ChatConsumer.as_asgi()), re_path(r'ws/metaverse/(?P\w+)/$', consumers.ChatConsumer.as_asgi()), + re_path(r'ws/metaverse/$', consumers.MetaverseConsumer.as_asgi()), ] \ No newline at end of file diff --git a/src/api/serializers.py b/src/api/serializers.py index 90cafc3..54964a9 100644 --- a/src/api/serializers.py +++ b/src/api/serializers.py @@ -1,6 +1,8 @@ from rest_framework import serializers from api.models import Avatar,Space,SpaceElement,Element,CustomUser,Map ,MapElement +from django.contrib.auth import get_user_model +User = get_user_model() class CustomUserMetadataSerializer(serializers.ModelSerializer): class Meta: @@ -115,4 +117,19 @@ def create(self, validated_data): z_index=element_data.get('z_index', 0) ) - return map_instance \ No newline at end of file + return map_instance + +class UserSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True) + + class Meta: + model = User + fields = ('id', 'email', 'username', 'password') + + def create(self, validated_data): + user = User.objects.create_user( + email=validated_data['email'], + username=validated_data['username'], + password=validated_data['password'] + ) + return user \ No newline at end of file diff --git a/src/api/templates/api/hi.html b/src/api/templates/api/hi.html new file mode 100644 index 0000000..2d3fa2a --- /dev/null +++ b/src/api/templates/api/hi.html @@ -0,0 +1,33 @@ + + + + WebSocket Test + + +

WebSocket Test

+ +
+ + + + \ No newline at end of file diff --git a/src/api/templates/api/room.html b/src/api/templates/api/room.html new file mode 100644 index 0000000..89498c8 --- /dev/null +++ b/src/api/templates/api/room.html @@ -0,0 +1,50 @@ + + + + + Chat Room + + +
+
+ + {{ room_name|json_script:"room-name" }} + + + + \ No newline at end of file diff --git a/src/api/tests/test_views.py b/src/api/tests/test_views.py new file mode 100644 index 0000000..812c517 --- /dev/null +++ b/src/api/tests/test_views.py @@ -0,0 +1,104 @@ +from django.test import TestCase +from django.urls import reverse +from rest_framework.test import APIClient +from rest_framework import status +from api.models import CustomUser, Avatar, Space, Element, Map + +class AuthenticationTests(TestCase): + def setUp(self): + self.client = APIClient() + self.register_url = reverse('register') + self.login_url = reverse('login') + self.user_data = { + 'email': 'test@example.com', + 'username': 'testuser', + 'password': 'testpass123' + } + + def test_user_registration(self): + response = self.client.post(self.register_url, self.user_data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue('access' in response.data) + self.assertTrue('refresh' in response.data) + + def test_user_login(self): + # Create user first + CustomUser.objects.create_user(**self.user_data) + + # Try logging in + response = self.client.post(self.login_url, { + 'email': self.user_data['email'], + 'password': self.user_data['password'] + }) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertTrue('access' in response.data) + +class SpaceTests(TestCase): + def setUp(self): + self.client = APIClient() + self.user = CustomUser.objects.create_user( + email='test@example.com', + username='testuser', + password='testpass123' + ) + self.client.force_authenticate(user=self.user) + + # Create a test map + self.map = Map.objects.create( + name='Test Map', + width=800, + height=600, + background_image='http://example.com/bg.png', + tile_size=32 + ) + + def test_create_space(self): + url = reverse('space-create') + data = { + 'name': 'Test Space', + 'width': 800, + 'height': 600, + 'map': self.map.id, + 'thumbnail': 'http://example.com/thumb.png' + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(Space.objects.filter(name='Test Space').exists()) + + def test_list_spaces(self): + # Create a test space + Space.objects.create( + name='Test Space', + width=800, + height=600, + map=self.map, + thumbnail='http://example.com/thumb.png' + ) + + url = reverse('space-list') + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + +class ElementTests(TestCase): + def setUp(self): + self.client = APIClient() + self.admin_user = CustomUser.objects.create_superuser( + email='admin@example.com', + username='admin', + password='adminpass123' + ) + self.client.force_authenticate(user=self.admin_user) + + def test_create_element(self): + url = reverse('element-create') + data = { + 'name': 'Test Chair', + 'type': 'furniture', + 'sprite_url': 'http://example.com/chair.png', + 'is_walkable': False, + 'interaction_script': 'console.log("Sitting")' + } + response = self.client.post(url, data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertTrue(Element.objects.filter(name='Test Chair').exists()) \ No newline at end of file diff --git a/src/api/urls.py b/src/api/urls.py index 79c0317..c2aea0c 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -1,10 +1,11 @@ from django.urls import path from . import views -from .views import AvatarView , SpaceCreateAPIView ,AdminCreateAvatarView +from .views import AvatarView, SpaceCreateAPIView, AdminCreateAvatarView, register, login - -urlpatterns =[ +urlpatterns = [ path('', views.index, name='index'), + path('auth/register/', register, name='register'), + path('auth/login/', login, name='login'), path('avatars//', AvatarView, name='avatar-detail'), path('spaces/new/', SpaceCreateAPIView, name='space-create'), path('spaces/delete//', views.DestroySpaceView.as_view(), name='space-delete'), diff --git a/src/api/views.py b/src/api/views.py index 590598b..16ce70c 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -1,19 +1,21 @@ from django.shortcuts import get_object_or_404, render from rest_framework.response import Response -from rest_framework.decorators import api_view +from rest_framework.decorators import api_view, permission_classes from rest_framework import status ,generics from api.models import Avatar ,Space ,Element ,Map -from rest_framework.permissions import IsAuthenticated ,IsAdminUser +from rest_framework.permissions import IsAuthenticated ,IsAdminUser, AllowAny from rest_framework.exceptions import MethodNotAllowed from rest_framework import status from rest_framework.decorators import permission_classes from rest_framework.permissions import IsAdminUser - +from django.contrib.auth import authenticate +from rest_framework_simplejwt.tokens import RefreshToken from api.serializers import ( AvatarsSerializer, SpaceSerializer, ElementSerializer, - MapSerializer + MapSerializer, + UserSerializer ) from django.http import HttpResponse @@ -294,3 +296,32 @@ def metaverse_rooms(request): """Render the metaverse rooms selection page""" return render(request, "api/rooms.html") +@api_view(['POST']) +@permission_classes([AllowAny]) +def register(request): + serializer = UserSerializer(data=request.data) + if serializer.is_valid(): + user = serializer.save() + refresh = RefreshToken.for_user(user) + return Response({ + 'refresh': str(refresh), + 'access': str(refresh.access_token), + }, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['POST']) +@permission_classes([AllowAny]) +def login(request): + email = request.data.get('email') + password = request.data.get('password') + + user = authenticate(email=email, password=password) + + if user is not None: + refresh = RefreshToken.for_user(user) + return Response({ + 'refresh': str(refresh), + 'access': str(refresh.access_token), + }) + return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED) + diff --git a/src/api/websockets.py b/src/api/websockets.py new file mode 100644 index 0000000..3297bb2 --- /dev/null +++ b/src/api/websockets.py @@ -0,0 +1,18 @@ +async def websocket_application(scope, receive, send): + while True: + event= await receive() + + if event['type']=='websocket.connect': + await send({ + 'type': 'websocket.accept' + }) + + if event['type'] == 'websocket.disconnect': + break + + if event['type'] == 'websocket.receive': + if event['text'] == 'ping holla': + await send({ + 'type': 'websocket.send', + 'text': 'pong!' + }) \ No newline at end of file diff --git a/src/home/asgi.py b/src/home/asgi.py index 1fd4144..1861e90 100644 --- a/src/home/asgi.py +++ b/src/home/asgi.py @@ -1,16 +1,16 @@ -""" -ASGI config for home project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ -""" - import os - from django.core.asgi import get_asgi_application +from channels.routing import ProtocolTypeRouter, URLRouter +from channels.auth import AuthMiddlewareStack +from api.routing import websocket_urlpatterns os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'home.settings') -application = get_asgi_application() +application = ProtocolTypeRouter({ + "http": get_asgi_application(), + "websocket": AuthMiddlewareStack( + URLRouter( + websocket_urlpatterns + ) + ), +}) \ No newline at end of file diff --git a/src/home/settings.py b/src/home/settings.py index e6f419b..f696cfc 100644 --- a/src/home/settings.py +++ b/src/home/settings.py @@ -40,6 +40,7 @@ # Application definition INSTALLED_APPS = [ + 'daphne', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -54,7 +55,10 @@ 'allauth.socialaccount', 'allauth.socialaccount.providers.google', + ] +ASGI_APPLICATION = "home.asgi.application" + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', diff --git a/src/settings.py b/src/settings.py new file mode 100644 index 0000000..7f67e7b --- /dev/null +++ b/src/settings.py @@ -0,0 +1,25 @@ +from datetime import timedelta + +# JWT Settings +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': False, + 'BLACKLIST_AFTER_ROTATION': True, + 'ALGORITHM': 'HS256', + 'SIGNING_KEY': SECRET_KEY, + 'VERIFYING_KEY': None, + 'AUTH_HEADER_TYPES': ('Bearer',), + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', +} + +# Channels Settings +ASGI_APPLICATION = 'src.routing.application' +CHANNEL_LAYERS = { + 'default': { + 'BACKEND': 'channels.layers.InMemoryChannelLayer' + } +} \ No newline at end of file