diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f5eb41b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +node_modules +npm-debug.log +Dockerfile +.dockerignore +.git +.gitignore +dist +.env \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f3cb2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Dependencies +node_modules/ + +# Build output +dist/ + +# Environment variables +.env +.env.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Testing +coverage/ +.nyc_output/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3d95a47 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# syntax=docker/dockerfile:1 +FROM node:20-alpine AS base + +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm install --production=false + +COPY tsconfig.json ./ +COPY src ./src + +ENV PORT=20205 +ENV DB_NAME=Identity_verification +ENV DB_USER=root +ENV DB_PASS=changeme +ENV DB_HOST=mysql +ENV DB_PORT=3306 +ENV JIBIT_API_KEY=change_me +ENV JIBIT_SECRET_KEY=change_me + +EXPOSE 20205 + +CMD ["npx", "tsx", "src/index.ts"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3043ec0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: '3.9' +services: + mysql: + image: mysql:8.0 + container_name: iv_mysql + restart: unless-stopped + environment: + MYSQL_DATABASE: ${DB_NAME:-Identity_verification} + MYSQL_ROOT_PASSWORD: ${DB_PASS:-changeme} + ports: + - "3307:3306" + volumes: + - mysql_data:/var/lib/mysql + + app: + build: . + container_name: iv_app + restart: unless-stopped + env_file: + - .env + environment: + PORT: ${PORT:-20205} + DB_NAME: ${DB_NAME:-Identity_verification} + DB_USER: ${DB_USER:-root} + DB_PASS: ${DB_PASS:-changeme} + DB_HOST: ${DB_HOST:-mysql} + DB_PORT: ${DB_PORT:-3306} + JIBIT_API_KEY: ${JIBIT_API_KEY:-change_me} + JIBIT_SECRET_KEY: ${JIBIT_SECRET_KEY:-change_me} + ports: + - "${PORT:-20205}:${PORT:-20205}" + depends_on: + - mysql + +volumes: + mysql_data: \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..afcd145 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1322 @@ +{ + "name": "identity-verification-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "identity-verification-api", + "version": "1.0.0", + "dependencies": { + "@hono/node-server": "^1.8.0", + "@hono/swagger-ui": "^0.2.0", + "@hono/zod-openapi": "^1.1.4", + "axios": "^1.7.7", + "hono": "^4.0.0", + "mysql2": "^3.6.0", + "sequelize": "^6.35.0", + "zod": "^4.1.12" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "tsx": "^4.7.0", + "typescript": "^5.3.0" + } + }, + "node_modules/@asteasolutions/zod-to-openapi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@asteasolutions/zod-to-openapi/-/zod-to-openapi-8.1.0.tgz", + "integrity": "sha512-tQFxVs05J/6QXXqIzj6rTRk3nj1HFs4pe+uThwE95jL5II2JfpVXkK+CqkO7aT0Do5AYqO6LDrKpleLUFXgY+g==", + "license": "MIT", + "dependencies": { + "openapi3-ts": "^4.1.2" + }, + "peerDependencies": { + "zod": "^4.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.5.tgz", + "integrity": "sha512-iBuhh+uaaggeAuf+TftcjZyWh2GEgZcVGXkNtskLVoWaXhnJtC5HLHrU8W1KHDoucqO1MswwglmkWLFyiDn4WQ==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@hono/swagger-ui": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@hono/swagger-ui/-/swagger-ui-0.2.2.tgz", + "integrity": "sha512-Bco6XdKkTP7yIVWXpquLQayvjwzDmsmsESjF7VcrU/ZPTYafGvvpHf7Z6PHTWyp+JpdYORZXlyZ75T3At2KfAA==", + "license": "MIT", + "peerDependencies": { + "hono": "*" + } + }, + "node_modules/@hono/zod-openapi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@hono/zod-openapi/-/zod-openapi-1.1.4.tgz", + "integrity": "sha512-4BbOtd6oKg20yo6HLluVbEycBLLIfdKX5o/gUSoKZ2uBmeP4Og/VDfIX3k9pbNEX5W3fRkuPeVjGA+zaQDVY1A==", + "license": "MIT", + "dependencies": { + "@asteasolutions/zod-to-openapi": "^8.1.0", + "@hono/zod-validator": "^0.7.4", + "openapi3-ts": "^4.5.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "hono": ">=4.3.6", + "zod": "^4.0.0" + } + }, + "node_modules/@hono/zod-validator": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.7.4.tgz", + "integrity": "sha512-biKGn3BRJVaftZlIPMyK+HCe/UHAjJ6sH0UyXe3+v0OcgVr9xfImDROTJFLtn9e3XEEAHGZIM9U6evu85abm8Q==", + "license": "MIT", + "peerDependencies": { + "hono": ">=3.9.0", + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz", + "integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/validator": { + "version": "13.15.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz", + "integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==", + "license": "MIT" + }, + "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/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "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/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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==", + "license": "MIT" + }, + "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/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/esbuild": { + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "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.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/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/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "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/get-tsconfig": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz", + "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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/hono": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.1.tgz", + "integrity": "sha512-rpGNOfacO4WEPClfkEt1yfl8cbu10uB1lNpiI33AKoiAHwOS8lV748JiLx4b5ozO/u4qLjIvfpFsPXdY5Qjkmg==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "engines": [ + "node >= 0.4.0" + ], + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "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/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "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/mysql2": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.2.tgz", + "integrity": "sha512-kFm5+jbwR5mC+lo+3Cy46eHiykWSpUtTLOH3GE+AR7GeLq8PgfJcvpMiyVWk9/O53DjQsqm6a3VOOfq7gYWFRg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/openapi3-ts": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.5.0.tgz", + "integrity": "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==", + "license": "MIT", + "dependencies": { + "yaml": "^2.8.0" + } + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "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/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", + "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/sequelize": { + "version": "6.37.7", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.7.tgz", + "integrity": "sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.6", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.1", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.4", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "ibm_db": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "snowflake-sdk": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==", + "license": "MIT" + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "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/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0039871 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "identity-verification-api", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "start": "tsx src/index.ts" + }, + "dependencies": { + "@hono/node-server": "^1.8.0", + "@hono/swagger-ui": "^0.2.0", + "@hono/zod-openapi": "^1.1.4", + "dotenv": "^16.4.5", + "hono": "^4.0.0", + "mysql2": "^3.6.0", + "sequelize": "^6.35.0", + "zod": "^4.1.12" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "tsx": "^4.7.0", + "typescript": "^5.3.0" + } +} diff --git a/src/config/database.ts b/src/config/database.ts new file mode 100644 index 0000000..63da653 --- /dev/null +++ b/src/config/database.ts @@ -0,0 +1,32 @@ +import { Sequelize } from 'sequelize'; + +const DB_NAME = process.env.DB_NAME || 'Identity_verification'; +const DB_USER = process.env.DB_USER || 'root'; +const DB_PASS = process.env.DB_PASS || '@8@w!1o*8Aq@8P$L20'; +const DB_HOST = process.env.DB_HOST || '185.97.116.24'; +const DB_PORT = parseInt(process.env.DB_PORT || '3306', 10); + +// اتصال به دیتابیس MySQL +export const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, { + host: DB_HOST, + port: DB_PORT, + dialect: 'mysql', + logging: false, + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } +}); + +// تست اتصال +export const connectDatabase = async () => { + try { + await sequelize.authenticate(); + console.log('✅ Database connected'); + await sequelize.sync({ alter: true }); + } catch (error) { + console.error('❌ Database connection failed:', error); + } +}; diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts new file mode 100644 index 0000000..b539a01 --- /dev/null +++ b/src/controllers/user.controller.ts @@ -0,0 +1,117 @@ +import { Context } from 'hono'; +import { User } from '../models/User.model.js'; +import { Token } from '../models/Token.model.js'; +import { Person } from '../models/Person.model.js'; +import { Log } from '../models/Log.model.js'; +import { generateTokens, refreshTokens } from '../services/jibit.service.js'; +import { generateToken } from '../utils/token.js'; + +export const checkUser = async (c: Context) => { + try { + const authHeader = c.req.header('authorization') || ''; + const providedToken = authHeader.startsWith('Bearer ') ? authHeader.substring(7) : authHeader; + + const body = await c.req.json().catch(() => ({} as any)); + const number = String((body as any).number || ''); + const national = String((body as any).national || ''); + const hostBody = (body as any).host ? String((body as any).host) : null; + + if (!number || !national) { + return c.json({ matched: false, error: 'number and national are required' }); + } + + const userCount = await User.count(); + if (userCount === 0) { + await User.create({ username: 'admin', token: 'admin-token', domain: null, host: null, isAdmin: true }); + } + + const requester = await User.findOne({ where: { token: providedToken } }); + if (!requester) { + return c.json({ matched: false, error: 'unauthorized' }, 401); + } + + let subjectUser: User = requester as any; + if (hostBody) { + if (!(requester as any).isAdmin && (requester as any).host && (requester as any).host !== hostBody) { + return c.json({ matched: false, error: 'forbidden_host' }, 403); + } + const existing = await User.findOne({ where: { host: hostBody } }); + subjectUser = existing ? (existing as any) : (await User.create({ username: hostBody, host: hostBody, token: generateToken(24), isAdmin: false })) as any; + } + + const cached = await Person.findOne({ where: { national, number, userId: (subjectUser as any).id } }); + if (cached) { + return c.json({ matched: !!cached.matched }); + } + + let tokenRow = await Token.findOne(); + if (!tokenRow) { + const generated = await generateTokens(); + if (generated.ok) { + tokenRow = await Token.create({ accessToken: generated.accessToken!, refreshToken: generated.refreshToken! }); + } else { + return c.json({ matched: false, error: generated.error || 'no Jibit tokens available', code: generated.code, status: generated.status }); + } + } + + let response = await loggedRequestJibit((subjectUser as any).id, tokenRow.accessToken, national, number); + if ((response as any).code === 'forbidden' || (response as any).status === 403 || (response as any).status === 401) { + const refreshed = await refreshTokens(tokenRow.refreshToken); + if (refreshed.ok) { + await Token.update({ accessToken: refreshed.accessToken!, refreshToken: refreshed.refreshToken! }, { where: { id: (tokenRow as any).id } }); + response = await loggedRequestJibit((subjectUser as any).id, refreshed.accessToken!, national, number); + } else { + const generated = await generateTokens(); + if (generated.ok) { + await Token.update({ accessToken: generated.accessToken!, refreshToken: generated.refreshToken! }, { where: { id: (tokenRow as any).id } }); + response = await loggedRequestJibit((subjectUser as any).id, generated.accessToken!, national, number); + } else { + return c.json({ matched: false, error: generated.error || 'failed to refresh/generate token', code: generated.code, status: generated.status }); + } + } + } + + if (response && !(response as any).matched && ((response as any).error || (response as any).code || (response as any).status)) { + return c.json({ matched: false, error: (response as any).error || (response as any).message || 'jibit_error', code: (response as any).code, status: (response as any).status }); + } + + const matched = !!(response as any).matched; + + try { + await Person.create({ national, number, matched, userId: (subjectUser as any).id }); + } catch (e: any) { + const name = (e && e.name) || ''; + if (name.includes('Unique') || name.includes('Validation')) { + const existing = await Person.findOne({ where: { national, number, userId: (subjectUser as any).id } }); + return c.json({ matched: !!existing?.matched }); + } + console.error('Person.create error:', e); + return c.json({ matched }); + } + + return c.json({ matched }); + } catch (error: any) { + console.error('Error:', error); + return c.json({ matched: false, error: error?.message || 'internal_error' }); + } +}; + +async function loggedRequestJibit(userId: number, accessToken: string, nationalCode: string, mobileNumber: string) { + const targetUrl = 'https://napi.jibit.ir/ide/v1/services/matching?' + new URLSearchParams({ nationalCode, mobileNumber }).toString(); + const start = Date.now(); + let status = 0; + let respBody: any = null; + const reqBody = { nationalCode, mobileNumber }; + try { + const resp = await fetch(targetUrl, { method: 'GET', headers: { Authorization: `Bearer ${accessToken}` } }); + status = resp.status; + try { respBody = await resp.json(); } catch {} + await Log.create({ type: 'outgoing', method: 'GET', path: '/external/jibit', headers: JSON.stringify({ Authorization: 'Bearer ***' }), body: JSON.stringify(reqBody).slice(0, 200000), response: JSON.stringify(respBody || {}).slice(0, 200000), status, durationMs: Date.now() - start, ip: null, userId, targetUrl }); + if (status === 403 || status === 401) return { code: 'forbidden', status }; + if (!resp.ok) return { matched: false, error: (respBody && (respBody.error || respBody.message)) || `jibit_http_${status}`, code: respBody && respBody.code, status }; + return respBody || { matched: false }; + } catch (e: any) { + await Log.create({ type: 'outgoing', method: 'GET', path: '/external/jibit', headers: JSON.stringify({ Authorization: 'Bearer ***' }), body: JSON.stringify(reqBody).slice(0, 200000), response: JSON.stringify({ error: e?.message || 'network_error' }), status: status || 0, durationMs: Date.now() - start, ip: null, userId, targetUrl }); + return { matched: false, error: e?.message || 'network_error' }; + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..9a2e13b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,172 @@ +import 'dotenv/config'; +import { OpenAPIHono } from '@hono/zod-openapi'; +import { serve } from '@hono/node-server'; +import { swaggerUI } from '@hono/swagger-ui'; +import { cors } from 'hono/cors'; +import { registerUserRoutes } from './routes/user.routes.js'; +import { connectDatabase } from './config/database.js'; +import { Log } from './models/Log.model.js'; +import { User } from './models/User.model.js'; + +const app = new OpenAPIHono(); + +app.use('*', cors()); + +app.use('*', async (c, next) => { + const start = Date.now(); + const method = c.req.method; + const url = new URL(c.req.url); + const path = url.pathname; + const headersObj: Record = {}; + c.req.raw.headers.forEach((v, k) => { headersObj[k] = v; }); + let requestBody: any = null; + try { requestBody = await c.req.json(); } catch {} + + await next(); + + const durationMs = Date.now() - start; + const status = c.res.status || 200; + let responseBody: any = null; + try { responseBody = await c.res.clone().json(); } catch {} + + try { + const count = await Log.count(); + if (count > 85000) { + await Log.destroy({ where: {}, order: [['createdAt', 'ASC']], limit: count - 85000 } as any); + } + } catch {} + + try { + let userId: number | null = null; + const auth = headersObj['authorization'] || headersObj['Authorization']; + const token = auth ? (auth.startsWith('Bearer ') ? auth.substring(7) : auth) : null; + if (token) { + const user = await User.findOne({ where: { token } }); + if (user) userId = (user as any).id; + } + + const ipHeader = headersObj['x-forwarded-for'] || headersObj['cf-connecting-ip'] || headersObj['x-real-ip'] || ''; + + await Log.create({ + type: 'incoming', + method, + path, + headers: JSON.stringify(headersObj).slice(0, 100000), + body: requestBody ? JSON.stringify(requestBody).slice(0, 200000) : '', + response: responseBody ? JSON.stringify(responseBody).slice(0, 200000) : '', + status, + durationMs, + ip: ipHeader || null, + userId, + targetUrl: null + }); + } catch {} +}); + +registerUserRoutes(app); + +app.doc('/doc', { + openapi: '3.0.0', + info: { + version: '1.0.0', + title: 'سامانه استعلام کد ملی', + description: 'API اعتبارسنجی کد ملی و شماره موبایل' + } +}); + +app.get('/swagger', swaggerUI({ url: '/doc' })); + +app.get('/', (c) => { + return c.html(` + + + + + پنل ادمین - استعلام کد ملی + + + +
+

پنل ادمین / NationalID Checker

+
+
+ + +
+
+ + +
+
+
+ + +
+ Swagger +
+ +
+
+ + +
+
+ + +
+
+
+ + نتیجه در پایین نمایش داده می‌شود +
+ +
Ready.
+
+ + +`); +}); + +const port = parseInt(process.env.PORT || '20205', 10); + +connectDatabase().then(() => { + console.log(`🚀 Server: http://localhost:${port}`); + console.log(`📖 Docs: http://localhost:${port}/swagger`); + serve({ fetch: app.fetch, port }); +}); diff --git a/src/models/Card.model.ts b/src/models/Card.model.ts new file mode 100644 index 0000000..f31f49c --- /dev/null +++ b/src/models/Card.model.ts @@ -0,0 +1,23 @@ +import { DataTypes, Model } from 'sequelize'; +import { sequelize } from '../config/database.js'; + +export class Card extends Model { + declare id: number; + declare cardNumber: string; + declare bin?: string; + declare bank?: string; +} + +Card.init( + { + id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, + cardNumber: { type: DataTypes.STRING(16), allowNull: false, unique: true }, + bin: { type: DataTypes.STRING(6), allowNull: true }, + bank: { type: DataTypes.STRING(128), allowNull: true } + }, + { + sequelize, + tableName: 'cards', + timestamps: true + } +); \ No newline at end of file diff --git a/src/models/Log.model.ts b/src/models/Log.model.ts new file mode 100644 index 0000000..f216d33 --- /dev/null +++ b/src/models/Log.model.ts @@ -0,0 +1,44 @@ +import { DataTypes, Model } from 'sequelize'; +import { sequelize } from '../config/database.js'; + +export class Log extends Model { + declare id: number; + declare type: string; // incoming | outgoing + declare method: string; + declare path: string; + declare headers: string; + declare body: string; + declare response: string; + declare status: number; + declare durationMs: number; + declare ip: string | null; + declare userId: number | null; + declare targetUrl: string | null; +} + +Log.init( + { + id: { type: DataTypes.BIGINT, autoIncrement: true, primaryKey: true }, + type: { type: DataTypes.STRING(16), allowNull: false, defaultValue: 'incoming' }, + method: { type: DataTypes.STRING(10), allowNull: false }, + path: { type: DataTypes.STRING(512), allowNull: false }, + headers: { type: DataTypes.TEXT('long'), allowNull: false }, + body: { type: DataTypes.TEXT('long'), allowNull: true }, + response: { type: DataTypes.TEXT('long'), allowNull: true }, + status: { type: DataTypes.INTEGER, allowNull: false }, + durationMs: { type: DataTypes.INTEGER, allowNull: false }, + ip: { type: DataTypes.STRING(64), allowNull: true }, + userId: { type: DataTypes.INTEGER, allowNull: true }, + targetUrl: { type: DataTypes.STRING(1024), allowNull: true } + }, + { + sequelize, + tableName: 'logs', + timestamps: true, + indexes: [ + { fields: ['createdAt'] }, + { fields: ['path'] }, + { fields: ['type'] } + ] + } +); \ No newline at end of file diff --git a/src/models/Person.model.ts b/src/models/Person.model.ts new file mode 100644 index 0000000..2206dfc --- /dev/null +++ b/src/models/Person.model.ts @@ -0,0 +1,29 @@ +import { DataTypes, Model } from 'sequelize'; +import { sequelize } from '../config/database.js'; + +export class Person extends Model { + declare id: number; + declare national: string; + declare number: string; + declare matched: boolean; + declare userId: number | null; +} + +Person.init( + { + id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, + national: { type: DataTypes.STRING(10), allowNull: false }, + number: { type: DataTypes.STRING(11), allowNull: false }, + matched: { type: DataTypes.BOOLEAN, defaultValue: false }, + userId: { type: DataTypes.INTEGER, allowNull: true } + }, + { + sequelize, + tableName: 'persons', + timestamps: true, + indexes: [ + { unique: true, fields: ['national', 'number', 'userId'] }, + { fields: ['userId'] } + ] + } +); \ No newline at end of file diff --git a/src/models/Query.model.ts b/src/models/Query.model.ts new file mode 100644 index 0000000..efac942 --- /dev/null +++ b/src/models/Query.model.ts @@ -0,0 +1,63 @@ +import { DataTypes, Model } from 'sequelize'; +import { sequelize } from '../config/database.js'; +import { User } from './User.model.js'; + +// مدل استعلام +export class Query extends Model { + declare id: number; + declare nationalCode: string; + declare mobileNumber: string; + declare matched: boolean; + declare userId: number; + declare source: string; +} + +Query.init( + { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + nationalCode: { + type: DataTypes.STRING(10), + allowNull: false + }, + mobileNumber: { + type: DataTypes.STRING(11), + allowNull: false + }, + matched: { + type: DataTypes.BOOLEAN, + allowNull: false + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: 'users', + key: 'id' + } + }, + source: { + type: DataTypes.STRING(50), + defaultValue: 'cache', + comment: 'cache or jibit' + } + }, + { + sequelize, + tableName: 'queries', + timestamps: true, + indexes: [ + { + unique: true, + fields: ['nationalCode', 'mobileNumber'] + } + ] + } +); + +// Relations +Query.belongsTo(User, { foreignKey: 'userId' }); +User.hasMany(Query, { foreignKey: 'userId' }); diff --git a/src/models/Token.model.ts b/src/models/Token.model.ts new file mode 100644 index 0000000..6c58c62 --- /dev/null +++ b/src/models/Token.model.ts @@ -0,0 +1,21 @@ +import { DataTypes, Model } from 'sequelize'; +import { sequelize } from '../config/database.js'; + +export class Token extends Model { + declare id: number; + declare accessToken: string; + declare refreshToken: string; +} + +Token.init( + { + id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, + accessToken: { type: DataTypes.TEXT, allowNull: false }, + refreshToken: { type: DataTypes.TEXT, allowNull: false } + }, + { + sequelize, + tableName: 'tokens', + timestamps: true + } +); \ No newline at end of file diff --git a/src/models/User.model.ts b/src/models/User.model.ts new file mode 100644 index 0000000..79dd3b3 --- /dev/null +++ b/src/models/User.model.ts @@ -0,0 +1,34 @@ +import { DataTypes, Model } from 'sequelize'; +import { sequelize } from '../config/database.js'; + +// مدل کاربر (برای احراز هویت و محدودیت‌ها) +export class User extends Model { + declare id: number; + declare username: string; + declare domain: string | null; + declare host: string | null; + declare token: string; + declare isAdmin: boolean; + declare queryCount: number; +} + +User.init( + { + id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, + username: { type: DataTypes.STRING(100), allowNull: false }, + domain: { type: DataTypes.STRING(255), allowNull: true }, + host: { type: DataTypes.STRING(255), allowNull: true }, + token: { type: DataTypes.STRING(255), allowNull: false }, + isAdmin: { type: DataTypes.BOOLEAN, defaultValue: false }, + queryCount: { type: DataTypes.INTEGER, defaultValue: 0 } + }, + { + sequelize, + tableName: 'users', + timestamps: true, + indexes: [ + { fields: ['token'] }, + { fields: ['username'] } + ] + } +); diff --git a/src/routes/user.routes.ts b/src/routes/user.routes.ts new file mode 100644 index 0000000..81f735e --- /dev/null +++ b/src/routes/user.routes.ts @@ -0,0 +1,37 @@ +import { createRoute, OpenAPIHono } from '@hono/zod-openapi'; +import { CheckUserSchema, CheckUserResponseSchema } from '../schemas/user.schema.js'; +import { checkUser } from '../controllers/user.controller.js'; + +// Route definition +const checkUserRoute = createRoute({ + method: 'post', + path: '/check_user', + tags: ['User Verification'], + summary: 'بررسی تطابق کد ملی و شماره موبایل', + description: 'اعتبارسنجی کد ملی و شماره موبایل کاربر', + request: { + body: { + content: { + 'application/json': { + schema: CheckUserSchema + } + } + } + }, + responses: { + 200: { + description: 'موفق', + content: { + 'application/json': { + schema: CheckUserResponseSchema + } + } + } + } +}); + +// Register routes +export const registerUserRoutes = (app: OpenAPIHono) => { + // @ts-ignore: openapi handler generic typing mismatch + app.openapi(checkUserRoute, checkUser as any); +}; diff --git a/src/schemas/user.schema.ts b/src/schemas/user.schema.ts new file mode 100644 index 0000000..1166d40 --- /dev/null +++ b/src/schemas/user.schema.ts @@ -0,0 +1,21 @@ +import { z } from '@hono/zod-openapi'; + +// Request Schema +export const CheckUserSchema = z.object({ + number: z.string().regex(/^09\d{9}$/, 'شماره موبایل نامعتبر').openapi({ + example: '09123456789', + description: 'شماره موبایل' + }), + national: z.string().length(10, 'کد ملی باید 10 رقمی باشد').openapi({ + example: '0123456789', + description: 'کد ملی' + }) +}).openapi('CheckUserRequest'); + +// Response Schema +export const CheckUserResponseSchema = z.object({ + matched: z.boolean().openapi({ + example: true, + description: 'وضعیت تطابق اطلاعات' + }) +}).openapi('CheckUserResponse'); diff --git a/src/services/jibit.service.ts b/src/services/jibit.service.ts new file mode 100644 index 0000000..3500370 --- /dev/null +++ b/src/services/jibit.service.ts @@ -0,0 +1,40 @@ +export type JibitTokenResult = { ok: boolean; accessToken?: string; refreshToken?: string; error?: string; code?: string; status?: number }; + +const API_KEY = process.env.JIBIT_API_KEY || ''; +const SECRET_KEY = process.env.JIBIT_SECRET_KEY || ''; + +export async function generateTokens(): Promise { + if (!API_KEY || !SECRET_KEY) return { ok: false, error: 'jibit_keys_missing' }; + let resp: Response; + try { + resp = await fetch('https://napi.jibit.ir/ide/v1/tokens/generate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ apiKey: API_KEY, secretKey: SECRET_KEY }) + }); + } catch (e: any) { + return { ok: false, error: e?.message || 'network_error' }; + } + let data: any = null; + try { data = await resp.json(); } catch {} + if (!resp.ok) return { ok: false, error: (data && (data.error || data.message)) || `jibit_http_${resp.status}`, code: data && data.code, status: resp.status }; + return { ok: true, accessToken: data?.accessToken, refreshToken: data?.refreshToken }; +} + +export async function refreshTokens(refreshToken: string): Promise { + if (!API_KEY || !SECRET_KEY) return { ok: false, error: 'jibit_keys_missing' }; + let resp: Response; + try { + resp = await fetch('https://napi.jibit.ir/ide/v1/tokens/generate', { + method: 'POST', + headers: { Authorization: `Bearer ${refreshToken}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ apiKey: API_KEY, secretKey: SECRET_KEY }) + }); + } catch (e: any) { + return { ok: false, error: e?.message || 'network_error' }; + } + let data: any = null; + try { data = await resp.json(); } catch {} + if (!resp.ok) return { ok: false, error: (data && (data.error || data.message)) || `jibit_http_${resp.status}`, code: data && data.code, status: resp.status }; + return { ok: true, accessToken: data?.accessToken, refreshToken: data?.refreshToken }; +} \ No newline at end of file diff --git a/src/utils/token.ts b/src/utils/token.ts new file mode 100644 index 0000000..32121de --- /dev/null +++ b/src/utils/token.ts @@ -0,0 +1,7 @@ +import { randomBytes } from 'crypto'; + +export function generateToken(byteLength: number = 32): string { + const buf = randomBytes(byteLength); + // base64url encode + return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); +} \ No newline at end of file diff --git a/test.http b/test.http new file mode 100644 index 0000000..d25252e --- /dev/null +++ b/test.http @@ -0,0 +1,20 @@ +### بررسی اطلاعات کاربر +POST http://localhost:20205/check_user +Authorization: Bearer admin-token +Content-Type: application/json + +{ + "number": "09372281182", + "national": "1274467144", + "host": "golak" +} + +### + +### مشاهده مستندات +GET http://localhost:20205/ + +### + +### مشخصات OpenAPI +GET http://localhost:20205/doc diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..326f46e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}