commit a798066049ad59761dbebe3282a8501ad612aa1a Author: hOLOlu Date: Fri Feb 27 07:53:41 2026 +0300 İlk sürüm: hDiyanetProxy v1.0.0 - Backend: Node.js + Express + MySQL + JWT auth - 8 MySQL tablosu (users, countries, states, cities, prayer_times, ramadan_times, eid_times, fetch_logs) - Diyanet API entegrasyonu (auth + token yönetimi) - Tüm API endpointleri (places, prayer-times, ramadan, eid, admin) - Rate limiting, CORS, input validation - Cron job (gece 02:00 otomatik veri çekme) - Frontend: Login, Dashboard, Fetch Panel, Namaz Vakitleri, Ramazan, Admin, Profil - Admin kullanıcı: admin/admin123 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..799587e --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# hDiyanetProxy - Environment Variables Example + +# MySQL Veritabanı +DB_HOST=localhost +DB_PORT=3306 +DB_NAME=hdiyanetproxy +DB_USER=root +DB_PASS=your_password + +# JWT +JWT_SECRET=change_this_to_a_random_secret_key +JWT_EXPIRES_IN=24h + +# Server +PORT=3000 +NODE_ENV=development + +# Diyanet API +DIYANET_API_URL=https://awqatsalah.diyanet.gov.tr +DIYANET_EMAIL=your_diyanet_email +DIYANET_PASSWORD=your_diyanet_password diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..266a2d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.env +*.log +node_modules/ +.opencode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..541d6e9 --- /dev/null +++ b/README.md @@ -0,0 +1,176 @@ +# hDiyanetProxy + +Diyanet İşleri Başkanlığı namaz vakitleri API'sinden veri çeken, yerel veritabanında saklayan ve güvenli API üzerinden sunan proxy uygulaması. + +## Özellikler + +- 🕌 Namaz vakitleri sorgulama (yıllık, günlük) +- 🌙 Ramazan imsakiyesi ve iftar vakitleri +- 🎉 Bayram namaz vakitleri +- 📍 Ülke/İl/Şehir yer bilgileri yönetimi +- 🔐 JWT tabanlı kimlik doğrulama +- 👥 Kullanıcı yönetimi (admin paneli) +- ⏰ Cron job ile otomatik veri güncelleme (gece 02:00) +- 📊 Dashboard ve veri çekme logları +- 📥 Manuel veri çekme paneli +- 📤 CSV/JSON dışa aktarma + +## Teknoloji + +- **Backend:** Node.js + Express.js +- **Veritabanı:** MySQL (mysql2) +- **Auth:** JWT (jsonwebtoken + bcrypt) +- **Frontend:** Vanilla HTML/CSS/JS +- **Kaynak API:** [Diyanet Awqat Salah](https://awqatsalah.diyanet.gov.tr) + +## Kurulum + +### 1. Bağımlılıkları Kur + +```bash +cd backend +npm install +``` + +### 2. Ortam Değişkenleri + +`.env.example` dosyasını `.env` olarak kopyalayın ve değerlerini güncelleyin: + +```bash +cp .env.example .env +# .env dosyasını düzenleyin +``` + +Ayrıca backend dizininde de `.env` gereklidir: +```bash +cp .env backend/.env +``` + +### 3. Veritabanı Migration + +```bash +cd backend +npm run migrate +``` + +### 4. İlk Admin Kullanıcı + +```bash +cd backend +npm run seed +``` + +Varsayılan giriş: `admin` / `admin123` + +### 5. Sunucuyu Başlat + +```bash +cd backend +npm start +# veya geliştirme modu: +npm run dev +``` + +Uygulama `http://localhost:3000` adresinde çalışır. + +## API Endpointleri + +### Auth +| Method | Endpoint | Açıklama | +|--------|----------|----------| +| POST | `/api/v1/auth/login` | Giriş yap, JWT token al | +| POST | `/api/v1/auth/register` | Yeni kullanıcı kaydı | +| GET | `/api/v1/auth/profile` | Profil bilgisi | +| POST | `/api/v1/auth/refresh` | Token yenile | +| POST | `/api/v1/auth/change-password` | Şifre değiştir | + +### Yer Bilgileri +| Method | Endpoint | Açıklama | +|--------|----------|----------| +| GET | `/api/v1/places/countries` | Ülkeleri listele | +| GET | `/api/v1/places/states?countryId={id}` | Eyaletleri listele | +| GET | `/api/v1/places/cities?stateId={id}` | Şehirleri listele | +| GET | `/api/v1/places/city/{cityId}` | Şehir detayı | + +### Namaz Vakitleri +| Method | Endpoint | Açıklama | +|--------|----------|----------| +| GET | `/api/v1/prayer-times?cityId={id}&startDate=&endDate=` | Tarih aralığı | +| GET | `/api/v1/prayer-times/today?cityId={id}` | Bugünkü vakit | + +### Ramazan +| Method | Endpoint | Açıklama | +|--------|----------|----------| +| GET | `/api/v1/ramadan?cityId={id}` | Ramazan takvimi | +| GET | `/api/v1/ramadan/today?cityId={id}` | Bugünkü iftar | + +### Bayram +| Method | Endpoint | Açıklama | +|--------|----------|----------| +| GET | `/api/v1/eid?cityId={id}` | Bayram vakitleri | + +### Admin (Admin yetkisi gerekli) +| Method | Endpoint | Açıklama | +|--------|----------|----------| +| GET | `/api/v1/admin/dashboard` | İstatistikler | +| POST | `/api/v1/admin/fetch/places` | Yer bilgilerini çek | +| POST | `/api/v1/admin/fetch/yearly` | Yıllık namaz vakitlerini çek | +| POST | `/api/v1/admin/fetch/ramadan` | Ramazan vakitlerini çek | +| POST | `/api/v1/admin/fetch/eid` | Bayram vakitlerini çek | +| POST | `/api/v1/admin/cache/clear` | Diyanet API cache temizle | +| GET | `/api/v1/admin/logs` | Veri çekme logları | +| GET | `/api/v1/admin/users` | Kullanıcı listesi | +| POST | `/api/v1/admin/users` | Kullanıcı oluştur | +| PATCH | `/api/v1/admin/users/:id/toggle` | Aktif/Pasif yap | +| DELETE | `/api/v1/admin/users/:id` | Kullanıcı sil | + +### Örnek Kullanım + +```bash +# Giriş yap +TOKEN=$(curl -s -X POST http://localhost:3000/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' | jq -r '.token') + +# Bugünkü namaz vakti +curl -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/v1/prayer-times/today?cityId=9541 +``` + +## Güvenlik + +- Şifreler bcrypt ile hash'lenir +- JWT token süresi: 24 saat +- Rate limiting: 120 istek/dakika (genel), 20 istek/15dk (login) +- CORS koruması aktif +- Input validation (express-validator) + +## Dosya Yapısı + +``` +hDiyanetProxy/ +├── backend/ +│ ├── src/ +│ │ ├── controllers/ # Route handler'ları +│ │ ├── services/ # İş mantığı servisleri +│ │ ├── models/ # Veritabanı modelleri +│ │ ├── routes/ # Express route tanımları +│ │ ├── middleware/ # Auth middleware +│ │ ├── jobs/ # Cron job'lar +│ │ └── utils/ # DB bağlantısı, migration +│ ├── .env +│ └── package.json +├── frontend/ +│ └── src/ +│ ├── pages/ # HTML sayfaları +│ ├── components/ # Sidebar vb. bileşenler +│ └── services/ # API çağrıları +├── .env +├── .env.example +├── .gitignore +└── README.md +``` + +## Lisans + +ISC diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md new file mode 100644 index 0000000..58745da --- /dev/null +++ b/REQUIREMENTS.md @@ -0,0 +1,240 @@ +## hDiyanetProxy Projesi - AI Agent Prompt'u + +--- + +**Proje Adı:** hDiyanetProxy + +**Görev:** Aşağıdaki gereksinimlere göre tam kapsamlı bir web uygulaması ve API servisi geliştir. + +--- + +### 📌 GENEL TANIM + +Türkiye Diyanet İşleri Başkanlığı'nın namaz vakitleri API'sinden (`https://awqatsalah.diyanet.gov.tr`) veri çeken, bu verileri yerel veritabanında saklayan ve dışarıya kendi güvenli API'si üzerinden sunan **hDiyanetProxy** adlı bir uygulama geliştir. + +--- + +### 🏗️ TEKNOLOJİ YIĞINI + +- **Backend:** Node.js + Express.js (veya Python + FastAPI — tercihe bırak) +- **Veritabanı:** PostgreSQL (ya da SQLite geliştirme ortamı için) +- **Auth:** JWT tabanlı token sistemi +- **Frontend:** Sade, modern bir web arayüzü (HTML/CSS/JS veya React) +- **Ortam:** Docker Compose ile ayağa kalkabilir yapı (opsiyonel ama önerilir) + +--- + +### 🔌 KAYNAK API BİLGİLERİ + +**Base URL:** `https://awqatsalah.diyanet.gov.tr` + +Kullanılacak endpointler: + +#### Namaz Vakitleri +``` +POST /api/AwqatSalah/Yearly +Body: +{ + "cityId": {cityId}, + "startDate": "2026-01-01T00:00:00.0Z", + "endDate": "2026-12-31T23:59:59.0Z" +} +``` + +#### Ramazan İmsakiyesi +``` +GET /api/AwqatSalah/Ramadan/{cityId} +``` + +#### Bayram Vakti ve Bayram Namazı +``` +GET /api/AwqatSalah/Eid/{cityId} +``` + +#### Yer Bilgileri +``` +GET /api/Place/Countries +GET /api/Place/States +GET /api/Place/States/{countryId} +GET /api/Place/Cities +GET /api/Place/Cities/{stateId} +GET /api/Place/CityDetail/{cityId} +``` + +#### Cache Temizleme +``` +GET /api/Cache/ClearCache +``` + +--- + +### 🗄️ VERİTABANI YAPISI + +Aşağıdaki tabloları oluştur: + +1. **users** — Kullanıcı adı, şifre (hash'li), rol, oluşturulma tarihi +2. **countries** — Ülke id, adı +3. **states** — İl/Eyalet id, adı, ülke foreign key +4. **cities** — Şehir id, adı, eyalet foreign key, koordinatlar +5. **prayer_times** — cityId, tarih, imsak, güneş, öğle, ikindi, akşam, yatsı, kıble vakti +6. **ramadan_times** — cityId, tarih, imsak, iftar, sahur saati gibi alanlar +7. **eid_times** — cityId, bayram adı, tarih, namaz saati +8. **fetch_logs** — Ne zaman, kim tarafından, hangi şehir için veri çekildiği logu + +--- + +### ⚙️ BACKEND FONKSİYONELLİK + +#### 1. Veri Çekme Servisi +- Manuel tetikleme: Kullanıcı arayüzden şehir ve tarih aralığı seçip "Veri Çek" butonuna basar +- Zamanlanmış çekme: Cron job ile belirli aralıklarla otomatik çekme (örn. her gün gece 02:00) +- Çekilen veriler önce kontrol edilir; aynı şehir + tarih için kayıt varsa güncellenir, yoksa eklenir (upsert) +- Yer bilgileri (ülke, eyalet, şehir) ayrı tablolarda saklanır ve periyodik olarak güncellenir + +#### 2. Kimlik Doğrulama +- `POST /auth/register` — Yeni kullanıcı kaydı (admin onaylı veya açık, tercihe göre) +- `POST /auth/login` — Kullanıcı adı + şifre ile giriş, JWT token döner +- Token süresi: 24 saat (refresh token desteği eklenebilir) +- Tüm veri endpointleri `Authorization: Bearer {token}` header'ı zorunlu tutar + +#### 3. Cache Yönetimi +- Kaynak API'nin cache'ini temizlemek için `/api/Cache/ClearCache` çağrısını proxy eden bir endpoint yaz +- Yerel cache için Redis entegrasyonu eklenebilir (opsiyonel) + +--- + +### 🌐 hDiyanetProxy API ENDPOINTLERİ + +Token ile korunan aşağıdaki endpointleri oluştur: + +``` +# Auth +POST /api/v1/auth/login +POST /api/v1/auth/register + +# Yer Bilgileri +GET /api/v1/places/countries +GET /api/v1/places/states?countryId={id} +GET /api/v1/places/cities?stateId={id} +GET /api/v1/places/city/{cityId} + +# Namaz Vakitleri +GET /api/v1/prayer-times?cityId={id}&startDate={date}&endDate={date} +GET /api/v1/prayer-times/today?cityId={id} + +# Ramazan / İftar +GET /api/v1/ramadan?cityId={id} +GET /api/v1/ramadan/today?cityId={id} + +# Bayram +GET /api/v1/eid?cityId={id} + +# Veri Çekme (Admin) +POST /api/v1/admin/fetch/yearly Body: { cityId, startDate, endDate } +POST /api/v1/admin/fetch/ramadan Body: { cityId } +POST /api/v1/admin/fetch/eid Body: { cityId } +POST /api/v1/admin/fetch/places (Tüm yer bilgilerini günceller) + +# Cache +POST /api/v1/admin/cache/clear + +# Loglar +GET /api/v1/admin/logs +``` + +--- + +### 🖥️ WEB ARAYÜZÜ + +Aşağıdaki sayfaları içeren sade ve işlevsel bir web arayüzü oluştur: + +#### 1. Giriş Sayfası (`/login`) +- Kullanıcı adı ve şifre formu +- Başarılı girişte token'ı localStorage'a kaydet +- Hata mesajlarını göster + +#### 2. Dashboard (`/dashboard`) +- Sistemdeki toplam kayıt sayıları (şehir, namaz vakti, ramazan kaydı) +- Son veri çekme logları tablosu +- Hızlı veri sorgulama alanı + +#### 3. Veri Çekme Paneli (`/fetch`) +- Ülke → Eyalet → Şehir dropdown seçimi (dinamik, birbirini tetikleyen) +- Tarih aralığı seçici +- "Yıllık Namaz Vakitlerini Çek", "Ramazan Verilerini Çek", "Bayram Verilerini Çek" butonları +- İşlem sonucunu canlı gösteren log/konsol alanı + +#### 4. Namaz Vakitleri Sorgulama (`/prayer-times`) +- Şehir ve tarih aralığı seçimi +- Tablo şeklinde listeleme +- CSV/JSON dışa aktarma butonu + +#### 5. Ramazan / İftar Vakitleri (`/ramadan`) +- Şehir seçimi +- Ramazan takvimi tablo görünümü +- İftar vakti vurgulu satır tasarımı + +#### 6. Kullanıcı Yönetimi (`/admin/users`) — Sadece admin rolü +- Kullanıcı listeleme, ekleme, pasif etme + +#### 7. API Token Bilgisi (`/profile`) +- Mevcut token'ı göster +- Yeni token üret butonu +- API kullanım dokümantasyonu linki + +--- + +### 🔐 GÜVENLİK GEREKSİNİMLERİ + +- Şifreler `bcrypt` ile hash'lenmeli +- JWT secret `.env` dosyasında saklanmalı +- Rate limiting eklenmeli (örn. dakikada 60 istek) +- CORS ayarları yapılmalı +- Input validasyonu tüm endpointlerde olmalı +- `.env.example` dosyası oluşturulmalı + +--- + +### 📁 PROJE DOSYA YAPISI + +``` +hDiyanetProxy/ +├── backend/ +│ ├── src/ +│ │ ├── controllers/ +│ │ ├── services/ +│ │ ├── models/ +│ │ ├── routes/ +│ │ ├── middleware/ +│ │ ├── jobs/ (cron jobs) +│ │ └── utils/ +│ ├── .env.example +│ └── package.json +├── frontend/ +│ ├── src/ +│ │ ├── pages/ +│ │ ├── components/ +│ │ └── services/ (API çağrıları) +│ └── package.json +├── docker-compose.yml +└── README.md +``` + +--- + +### 📋 TAMAMLANMA KRİTERLERİ + +- [ ] Kaynak Diyanet API'sinden başarıyla veri çekilebiliyor +- [ ] Çekilen veriler veritabanına doğru şekilde kaydediliyor +- [ ] JWT ile kullanıcı girişi ve token alma çalışıyor +- [ ] Token ile korunan tüm endpointler çalışıyor +- [ ] Web arayüzünden veri çekme ve sorgulama yapılabiliyor +- [ ] Şehir listesi dinamik dropdown'larla yüklenebiliyor +- [ ] Ramazan ve bayram verileri ayrı ayrı sorgulanabiliyor +- [ ] Admin panelinden kullanıcı yönetimi yapılabiliyor +- [ ] Tüm işlemler loglanıyor +- [ ] README.md kurulum adımlarını içeriyor + +--- + +Projeyi adım adım geliştir. Önce veritabanı şemasını ve backend iskeletini kur, ardından API endpointlerini yaz, en son web arayüzünü tamamla. Her adımda çalışır durumda kod üret. diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..9475b0b --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,1905 @@ +{ + "name": "hdiyanetproxy-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hdiyanetproxy-backend", + "version": "1.0.0", + "dependencies": { + "axios": "^1.7.7", + "bcrypt": "^5.1.1", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.0", + "express-rate-limit": "^7.4.0", + "express-validator": "^7.2.0", + "jsonwebtoken": "^9.0.2", + "mysql2": "^3.18.2", + "node-cron": "^3.0.3" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@types/node": { + "version": "25.3.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.2.tgz", + "integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/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/agent-base/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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "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.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "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/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express-validator": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.1.tgz", + "integrity": "sha512-IGenaSf+DnWc69lKuqlRE9/i/2t5/16VpH5bXoqdxWz1aCpRvEdrBuu1y95i/iL5QP8ZYVATiwLFhwk3EDl5vg==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.15.23" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "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/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "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/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "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/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, + "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/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/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/https-proxy-agent/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/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/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/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "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.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "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/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.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/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=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/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.18.2.tgz", + "integrity": "sha512-UfEShBFAZZEAKjySnTUuE7BgqkYT4mx+RjoJ5aqtmwSSvNcJ/QxQPXz/y3jSxNiVRedPfgccmuBtiPCSiEEytw==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "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/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "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.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT", + "peer": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "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.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..d81e959 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,24 @@ +{ + "name": "hdiyanetproxy-backend", + "version": "1.0.0", + "description": "hDiyanetProxy Backend API", + "main": "src/app.js", + "scripts": { + "start": "node src/app.js", + "dev": "node --watch src/app.js", + "migrate": "node src/utils/migrate.js", + "seed": "node src/utils/seed.js" + }, + "dependencies": { + "mysql2": "^3.18.2", + "express": "^4.21.0", + "jsonwebtoken": "^9.0.2", + "bcrypt": "^5.1.1", + "cors": "^2.8.5", + "express-rate-limit": "^7.4.0", + "express-validator": "^7.2.0", + "node-cron": "^3.0.3", + "axios": "^1.7.7", + "dotenv": "^16.4.5" + } +} diff --git a/backend/src/app.js b/backend/src/app.js new file mode 100644 index 0000000..191d14f --- /dev/null +++ b/backend/src/app.js @@ -0,0 +1,98 @@ +// hDiyanetProxy - Ana Uygulama +const express = require('express'); +const cors = require('cors'); +const rateLimit = require('express-rate-limit'); +const path = require('path'); +require('dotenv').config(); + +const app = express(); +const PORT = process.env.PORT || 3000; + +// --- Middleware --- + +// CORS ayarları +app.use(cors({ + origin: '*', + methods: ['GET', 'POST', 'PATCH', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'] +})); + +// Body parser +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +// Rate limiting - dakikada 120 istek +const limiter = rateLimit({ + windowMs: 1 * 60 * 1000, // 1 dakika + max: 120, + message: { error: 'Çok fazla istek gönderdiniz, lütfen bir süre bekleyin' }, + standardHeaders: true, + legacyHeaders: false +}); +app.use('/api/', limiter); + +// Auth endpointleri için daha sıkı rate limiting +const authLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 dakika + max: 20, + message: { error: 'Çok fazla giriş denemesi, 15 dakika bekleyin' } +}); +app.use('/api/v1/auth/login', authLimiter); + +// --- Frontend Statik Dosyalar --- +app.use(express.static(path.join(__dirname, '../../frontend/src'))); + +// --- API Routes --- +app.use('/api/v1/auth', require('./routes/auth')); +app.use('/api/v1/places', require('./routes/places')); +app.use('/api/v1/prayer-times', require('./routes/prayerTimes')); +app.use('/api/v1/ramadan', require('./routes/ramadan')); +app.use('/api/v1/eid', require('./routes/eid')); +app.use('/api/v1/admin', require('./routes/admin')); + +// --- API Durum Endpoint'i --- +app.get('/api/v1/status', (req, res) => { + res.json({ + name: 'hDiyanetProxy', + version: '1.0.0', + status: 'running', + timestamp: new Date().toISOString() + }); +}); + +// --- Frontend SPA Route'ları --- +app.get('/', (req, res) => res.sendFile(path.join(__dirname, '../../frontend/src/pages/login.html'))); +app.get('/login', (req, res) => res.sendFile(path.join(__dirname, '../../frontend/src/pages/login.html'))); +app.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../../frontend/src/pages/dashboard.html'))); +app.get('/fetch', (req, res) => res.sendFile(path.join(__dirname, '../../frontend/src/pages/fetch.html'))); +app.get('/prayer-times', (req, res) => res.sendFile(path.join(__dirname, '../../frontend/src/pages/prayer-times.html'))); +app.get('/ramadan', (req, res) => res.sendFile(path.join(__dirname, '../../frontend/src/pages/ramadan.html'))); +app.get('/admin/users', (req, res) => res.sendFile(path.join(__dirname, '../../frontend/src/pages/admin-users.html'))); +app.get('/profile', (req, res) => res.sendFile(path.join(__dirname, '../../frontend/src/pages/profile.html'))); + +// --- 404 Handler --- +app.use((req, res) => { + if (req.path.startsWith('/api/')) { + return res.status(404).json({ error: 'Endpoint bulunamadı' }); + } + res.status(404).send('Sayfa bulunamadı'); +}); + +// --- Error Handler --- +app.use((err, req, res, next) => { + console.error('Sunucu hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); +}); + +// --- Sunucuyu Başlat --- +app.listen(PORT, () => { + console.log(`\n🚀 hDiyanetProxy sunucusu çalışıyor: http://localhost:${PORT}`); + console.log(`📡 API: http://localhost:${PORT}/api/v1`); + console.log(`🖥️ Frontend: http://localhost:${PORT}/\n`); + + // Cron job'ları başlat + const { startCronJobs } = require('./jobs/cronJobs'); + startCronJobs(); +}); + +module.exports = app; diff --git a/backend/src/controllers/adminController.js b/backend/src/controllers/adminController.js new file mode 100644 index 0000000..7ddd6c3 --- /dev/null +++ b/backend/src/controllers/adminController.js @@ -0,0 +1,178 @@ +// hDiyanetProxy - Admin Controller +const FetchService = require('../services/fetchService'); +const FetchLogModel = require('../models/FetchLog'); +const UserModel = require('../models/User'); +const PlaceModel = require('../models/Place'); +const DiyanetService = require('../services/diyanetService'); +const { validationResult } = require('express-validator'); + +const AdminController = { + // Dashboard istatistikleri + async getDashboard(req, res) { + try { + const stats = await PlaceModel.getStats(); + const recentLogs = await FetchLogModel.getRecent(10); + res.json({ stats, recentLogs }); + } catch (err) { + console.error('Dashboard hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Yer bilgilerini çek + async fetchPlaces(req, res) { + try { + const result = await FetchService.fetchAndSavePlaces(req.user.id); + res.json(result); + } catch (err) { + console.error('Fetch places hatası:', err); + res.status(500).json({ error: `Yer bilgileri çekilirken hata: ${err.message}` }); + } + }, + + // Yıllık namaz vakitlerini çek + async fetchYearly(req, res) { + try { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + const { cityId, startDate, endDate } = req.body; + const result = await FetchService.fetchAndSaveYearly(cityId, startDate, endDate, req.user.id); + res.json(result); + } catch (err) { + console.error('Fetch yearly hatası:', err); + res.status(500).json({ error: `Namaz vakitleri çekilirken hata: ${err.message}` }); + } + }, + + // Ramazan vakitlerini çek + async fetchRamadan(req, res) { + try { + const { cityId } = req.body; + if (!cityId) { + return res.status(400).json({ error: 'cityId gerekli' }); + } + const result = await FetchService.fetchAndSaveRamadan(cityId, req.user.id); + res.json(result); + } catch (err) { + console.error('Fetch ramadan hatası:', err); + res.status(500).json({ error: `Ramazan vakitleri çekilirken hata: ${err.message}` }); + } + }, + + // Bayram vakitlerini çek + async fetchEid(req, res) { + try { + const { cityId } = req.body; + if (!cityId) { + return res.status(400).json({ error: 'cityId gerekli' }); + } + const result = await FetchService.fetchAndSaveEid(cityId, req.user.id); + res.json(result); + } catch (err) { + console.error('Fetch eid hatası:', err); + res.status(500).json({ error: `Bayram vakitleri çekilirken hata: ${err.message}` }); + } + }, + + // Cache temizle + async clearCache(req, res) { + try { + const result = await DiyanetService.clearCache(); + await FetchLogModel.create(req.user.id, 'clear_cache', null, null, 'Diyanet API cache temizlendi', 'success', 0); + res.json({ success: true, message: 'Cache temizlendi', data: result }); + } catch (err) { + console.error('Clear cache hatası:', err); + res.status(500).json({ error: `Cache temizlenirken hata: ${err.message}` }); + } + }, + + // Logları getir + async getLogs(req, res) { + try { + const page = parseInt(req.query.page) || 1; + const limit = parseInt(req.query.limit) || 50; + const result = await FetchLogModel.getAll(page, limit); + res.json(result); + } catch (err) { + console.error('Logs hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // --- Kullanıcı Yönetimi --- + + // Tüm kullanıcıları listele + async getUsers(req, res) { + try { + const users = await UserModel.findAll(); + res.json(users); + } catch (err) { + console.error('Users hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Kullanıcı oluştur + async createUser(req, res) { + try { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + const { username, password, role } = req.body; + + const existing = await UserModel.findByUsername(username); + if (existing) { + return res.status(409).json({ error: 'Bu kullanıcı adı zaten kullanılıyor' }); + } + + const user = await UserModel.create(username, password, role || 'user'); + res.status(201).json({ message: 'Kullanıcı oluşturuldu', user }); + } catch (err) { + console.error('Create user hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Kullanıcı aktif/pasif yap + async toggleUserActive(req, res) { + try { + const { id } = req.params; + const { isActive } = req.body; + + // Kendini pasif yapamasın + if (parseInt(id) === req.user.id) { + return res.status(400).json({ error: 'Kendinizi pasif yapamazsınız' }); + } + + await UserModel.toggleActive(parseInt(id), isActive); + res.json({ message: isActive ? 'Kullanıcı aktif edildi' : 'Kullanıcı pasif edildi' }); + } catch (err) { + console.error('Toggle user hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Kullanıcı sil + async deleteUser(req, res) { + try { + const { id } = req.params; + + if (parseInt(id) === req.user.id) { + return res.status(400).json({ error: 'Kendinizi silemezsiniz' }); + } + + await UserModel.delete(parseInt(id)); + res.json({ message: 'Kullanıcı silindi' }); + } catch (err) { + console.error('Delete user hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + } +}; + +module.exports = AdminController; diff --git a/backend/src/controllers/authController.js b/backend/src/controllers/authController.js new file mode 100644 index 0000000..a5694f9 --- /dev/null +++ b/backend/src/controllers/authController.js @@ -0,0 +1,136 @@ +// hDiyanetProxy - Auth Controller +const jwt = require('jsonwebtoken'); +const UserModel = require('../models/User'); +const { validationResult } = require('express-validator'); + +const AuthController = { + // Kullanıcı girişi + async login(req, res) { + try { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + const { username, password } = req.body; + + // Kullanıcıyı bul + const user = await UserModel.findByUsername(username); + if (!user) { + return res.status(401).json({ error: 'Kullanıcı adı veya şifre hatalı' }); + } + + // Aktif mi kontrol et + if (!user.is_active) { + return res.status(403).json({ error: 'Hesabınız pasif durumda' }); + } + + // Şifreyi doğrula + const isValid = await UserModel.verifyPassword(password, user.password); + if (!isValid) { + return res.status(401).json({ error: 'Kullanıcı adı veya şifre hatalı' }); + } + + // JWT token oluştur + const token = jwt.sign( + { id: user.id, username: user.username, role: user.role }, + process.env.JWT_SECRET, + { expiresIn: process.env.JWT_EXPIRES_IN || '24h' } + ); + + res.json({ + message: 'Giriş başarılı', + token, + user: { + id: user.id, + username: user.username, + role: user.role + } + }); + } catch (err) { + console.error('Login hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Kullanıcı kaydı + async register(req, res) { + try { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + const { username, password } = req.body; + + // Kullanıcı adı zaten var mı kontrol et + const existing = await UserModel.findByUsername(username); + if (existing) { + return res.status(409).json({ error: 'Bu kullanıcı adı zaten kullanılıyor' }); + } + + // Yeni kullanıcı oluştur + const user = await UserModel.create(username, password, 'user'); + + res.status(201).json({ + message: 'Kullanıcı başarıyla oluşturuldu', + user: { id: user.id, username: user.username, role: user.role } + }); + } catch (err) { + console.error('Register hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Profil bilgisi + async profile(req, res) { + try { + const user = await UserModel.findById(req.user.id); + if (!user) { + return res.status(404).json({ error: 'Kullanıcı bulunamadı' }); + } + res.json({ user }); + } catch (err) { + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Yeni token oluştur + async refreshToken(req, res) { + try { + const token = jwt.sign( + { id: req.user.id, username: req.user.username, role: req.user.role }, + process.env.JWT_SECRET, + { expiresIn: process.env.JWT_EXPIRES_IN || '24h' } + ); + res.json({ token }); + } catch (err) { + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Şifre değiştir + async changePassword(req, res) { + try { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + + const { currentPassword, newPassword } = req.body; + const user = await UserModel.findByUsername(req.user.username); + + const isValid = await UserModel.verifyPassword(currentPassword, user.password); + if (!isValid) { + return res.status(401).json({ error: 'Mevcut şifre hatalı' }); + } + + await UserModel.updatePassword(req.user.id, newPassword); + res.json({ message: 'Şifre başarıyla güncellendi' }); + } catch (err) { + res.status(500).json({ error: 'Sunucu hatası' }); + } + } +}; + +module.exports = AuthController; diff --git a/backend/src/controllers/eidController.js b/backend/src/controllers/eidController.js new file mode 100644 index 0000000..25d8a7a --- /dev/null +++ b/backend/src/controllers/eidController.js @@ -0,0 +1,21 @@ +// hDiyanetProxy - Eid Controller +const EidModel = require('../models/EidTime'); + +const EidController = { + // Şehre göre bayram vakitlerini getir + async getByCityId(req, res) { + try { + const { cityId } = req.query; + if (!cityId) { + return res.status(400).json({ error: 'cityId parametresi gerekli' }); + } + const times = await EidModel.getByCityId(parseInt(cityId)); + res.json(times); + } catch (err) { + console.error('Eid hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + } +}; + +module.exports = EidController; diff --git a/backend/src/controllers/placesController.js b/backend/src/controllers/placesController.js new file mode 100644 index 0000000..47e5aa8 --- /dev/null +++ b/backend/src/controllers/placesController.js @@ -0,0 +1,62 @@ +// hDiyanetProxy - Places Controller +const PlaceModel = require('../models/Place'); + +const PlacesController = { + // Tüm ülkeleri getir + async getCountries(req, res) { + try { + const countries = await PlaceModel.getAllCountries(); + res.json(countries); + } catch (err) { + console.error('Countries hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Ülkeye göre eyaletleri getir + async getStates(req, res) { + try { + const { countryId } = req.query; + if (!countryId) { + return res.status(400).json({ error: 'countryId parametresi gerekli' }); + } + const states = await PlaceModel.getStatesByCountry(parseInt(countryId)); + res.json(states); + } catch (err) { + console.error('States hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Eyalete göre şehirleri getir + async getCities(req, res) { + try { + const { stateId } = req.query; + if (!stateId) { + return res.status(400).json({ error: 'stateId parametresi gerekli' }); + } + const cities = await PlaceModel.getCitiesByState(parseInt(stateId)); + res.json(cities); + } catch (err) { + console.error('Cities hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Şehir detayı + async getCityDetail(req, res) { + try { + const { cityId } = req.params; + const city = await PlaceModel.getCityDetail(parseInt(cityId)); + if (!city) { + return res.status(404).json({ error: 'Şehir bulunamadı' }); + } + res.json(city); + } catch (err) { + console.error('City detail hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + } +}; + +module.exports = PlacesController; diff --git a/backend/src/controllers/prayerTimesController.js b/backend/src/controllers/prayerTimesController.js new file mode 100644 index 0000000..f58e686 --- /dev/null +++ b/backend/src/controllers/prayerTimesController.js @@ -0,0 +1,46 @@ +// hDiyanetProxy - Prayer Times Controller +const PrayerTimeModel = require('../models/PrayerTime'); +const { validationResult } = require('express-validator'); + +const PrayerTimesController = { + // Tarih aralığına göre namaz vakitlerini getir + async getByDateRange(req, res) { + try { + const { cityId, startDate, endDate } = req.query; + + if (!cityId) { + return res.status(400).json({ error: 'cityId parametresi gerekli' }); + } + + const start = startDate || new Date().toISOString().split('T')[0]; + const end = endDate || start; + + const times = await PrayerTimeModel.getByDateRange(parseInt(cityId), start, end); + res.json(times); + } catch (err) { + console.error('Prayer times hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Bugünkü namaz vaktini getir + async getToday(req, res) { + try { + const { cityId } = req.query; + if (!cityId) { + return res.status(400).json({ error: 'cityId parametresi gerekli' }); + } + + const today = await PrayerTimeModel.getToday(parseInt(cityId)); + if (!today) { + return res.status(404).json({ error: 'Bugün için namaz vakti bulunamadı' }); + } + res.json(today); + } catch (err) { + console.error('Prayer times today hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + } +}; + +module.exports = PrayerTimesController; diff --git a/backend/src/controllers/ramadanController.js b/backend/src/controllers/ramadanController.js new file mode 100644 index 0000000..49573d0 --- /dev/null +++ b/backend/src/controllers/ramadanController.js @@ -0,0 +1,39 @@ +// hDiyanetProxy - Ramadan Controller +const RamadanModel = require('../models/RamadanTime'); + +const RamadanController = { + // Şehre göre ramazan vakitlerini getir + async getByCityId(req, res) { + try { + const { cityId } = req.query; + if (!cityId) { + return res.status(400).json({ error: 'cityId parametresi gerekli' }); + } + const times = await RamadanModel.getByCityId(parseInt(cityId)); + res.json(times); + } catch (err) { + console.error('Ramadan hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + }, + + // Bugünkü ramazan vaktini getir + async getToday(req, res) { + try { + const { cityId } = req.query; + if (!cityId) { + return res.status(400).json({ error: 'cityId parametresi gerekli' }); + } + const today = await RamadanModel.getToday(parseInt(cityId)); + if (!today) { + return res.status(404).json({ error: 'Bugün için ramazan vakti bulunamadı' }); + } + res.json(today); + } catch (err) { + console.error('Ramadan today hatası:', err); + res.status(500).json({ error: 'Sunucu hatası' }); + } + } +}; + +module.exports = RamadanController; diff --git a/backend/src/jobs/cronJobs.js b/backend/src/jobs/cronJobs.js new file mode 100644 index 0000000..a1a9dac --- /dev/null +++ b/backend/src/jobs/cronJobs.js @@ -0,0 +1,47 @@ +// hDiyanetProxy - Cron Jobs +// Her gece 02:00'de otomatik veri çekme +const cron = require('node-cron'); +const FetchService = require('../services/fetchService'); +const pool = require('../utils/db'); + +function startCronJobs() { + // Her gece saat 02:00'de çalışır (Türkiye saati) + cron.schedule('0 2 * * *', async () => { + console.log('\n⏰ [CRON] Otomatik veri çekme başladı:', new Date().toISOString()); + + try { + // Veritabanındaki tüm şehirler için günlük namaz vakitlerini güncelle + const [cities] = await pool.execute('SELECT DISTINCT city_id FROM prayer_times LIMIT 50'); + + if (cities.length === 0) { + console.log(' ℹ️ Güncellenecek şehir bulunamadı'); + return; + } + + const currentYear = new Date().getFullYear(); + const startDate = `${currentYear}-01-01T00:00:00.0Z`; + const endDate = `${currentYear}-12-31T23:59:59.0Z`; + + for (const city of cities) { + try { + await FetchService.fetchAndSaveYearly(city.city_id, startDate, endDate, null); + console.log(` ✅ Şehir ${city.city_id} güncellendi`); + } catch (err) { + console.error(` ❌ Şehir ${city.city_id} güncellenemedi: ${err.message}`); + } + // API'yi yormamak için bekleme + await new Promise(r => setTimeout(r, 500)); + } + + console.log('⏰ [CRON] Otomatik veri çekme tamamlandı\n'); + } catch (err) { + console.error('⏰ [CRON] Hata:', err.message); + } + }, { + timezone: 'Europe/Istanbul' + }); + + console.log('⏰ Cron job ayarlandı: Her gece 02:00 (Europe/Istanbul)'); +} + +module.exports = { startCronJobs }; diff --git a/backend/src/middleware/auth.js b/backend/src/middleware/auth.js new file mode 100644 index 0000000..460b562 --- /dev/null +++ b/backend/src/middleware/auth.js @@ -0,0 +1,31 @@ +// hDiyanetProxy - JWT Auth Middleware +const jwt = require('jsonwebtoken'); + +// Token doğrulama middleware'i +function authMiddleware(req, res, next) { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ error: 'Yetkilendirme token\'ı gerekli' }); + } + + const token = authHeader.split(' ')[1]; + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + req.user = decoded; + next(); + } catch (err) { + return res.status(401).json({ error: 'Geçersiz veya süresi dolmuş token' }); + } +} + +// Admin rolü kontrolü middleware'i +function adminMiddleware(req, res, next) { + if (req.user.role !== 'admin') { + return res.status(403).json({ error: 'Bu işlem için admin yetkisi gerekli' }); + } + next(); +} + +module.exports = { authMiddleware, adminMiddleware }; diff --git a/backend/src/models/EidTime.js b/backend/src/models/EidTime.js new file mode 100644 index 0000000..d750ced --- /dev/null +++ b/backend/src/models/EidTime.js @@ -0,0 +1,51 @@ +// hDiyanetProxy - Bayram Vakitleri Modeli +const pool = require('../utils/db'); + +const EidModel = { + // Bayram vakti ekle veya güncelle + async upsert(cityId, eidName, date, times) { + await pool.execute( + `INSERT INTO eid_times (city_id, eid_name, date, prayer_time, fajr, sunrise, dhuhr, asr, maghrib, isha) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + prayer_time = VALUES(prayer_time), fajr = VALUES(fajr), sunrise = VALUES(sunrise), + dhuhr = VALUES(dhuhr), asr = VALUES(asr), maghrib = VALUES(maghrib), isha = VALUES(isha)`, + [cityId, eidName, date, times.prayerTime, times.fajr, times.sunrise, times.dhuhr, times.asr, times.maghrib, times.isha] + ); + }, + + // Şehre göre bayram vakitlerini getir + async getByCityId(cityId) { + const [rows] = await pool.execute( + 'SELECT * FROM eid_times WHERE city_id = ? ORDER BY date', + [cityId] + ); + return rows; + }, + + // Toplu ekleme + async bulkUpsert(cityId, items) { + const conn = await pool.getConnection(); + try { + await conn.beginTransaction(); + for (const item of items) { + await conn.execute( + `INSERT INTO eid_times (city_id, eid_name, date, prayer_time, fajr, sunrise, dhuhr, asr, maghrib, isha) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + prayer_time = VALUES(prayer_time), fajr = VALUES(fajr), sunrise = VALUES(sunrise), + dhuhr = VALUES(dhuhr), asr = VALUES(asr), maghrib = VALUES(maghrib), isha = VALUES(isha)`, + [cityId, item.eidName, item.date, item.prayerTime, item.fajr, item.sunrise, item.dhuhr, item.asr, item.maghrib, item.isha] + ); + } + await conn.commit(); + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } + } +}; + +module.exports = EidModel; diff --git a/backend/src/models/FetchLog.js b/backend/src/models/FetchLog.js new file mode 100644 index 0000000..f90f6f8 --- /dev/null +++ b/backend/src/models/FetchLog.js @@ -0,0 +1,43 @@ +// hDiyanetProxy - Fetch Log Modeli +const pool = require('../utils/db'); + +const FetchLogModel = { + // Log kaydı oluştur + async create(userId, action, cityId, cityName, details, status = 'success', recordsCount = 0) { + const [result] = await pool.execute( + 'INSERT INTO fetch_logs (user_id, action, city_id, city_name, details, status, records_count) VALUES (?, ?, ?, ?, ?, ?, ?)', + [userId, action, cityId, cityName, details, status, recordsCount] + ); + return result.insertId; + }, + + // Son logları getir + async getRecent(limit = 50) { + const [rows] = await pool.execute( + `SELECT fl.*, u.username + FROM fetch_logs fl + LEFT JOIN users u ON fl.user_id = u.id + ORDER BY fl.created_at DESC + LIMIT ?`, + [limit] + ); + return rows; + }, + + // Tüm logları getir (sayfalı) + async getAll(page = 1, limit = 50) { + const offset = (page - 1) * limit; + const [rows] = await pool.execute( + `SELECT fl.*, u.username + FROM fetch_logs fl + LEFT JOIN users u ON fl.user_id = u.id + ORDER BY fl.created_at DESC + LIMIT ? OFFSET ?`, + [limit, offset] + ); + const [[{ total }]] = await pool.execute('SELECT COUNT(*) as total FROM fetch_logs'); + return { logs: rows, total, page, limit }; + } +}; + +module.exports = FetchLogModel; diff --git a/backend/src/models/Place.js b/backend/src/models/Place.js new file mode 100644 index 0000000..4fccf95 --- /dev/null +++ b/backend/src/models/Place.js @@ -0,0 +1,80 @@ +// hDiyanetProxy - Yer Bilgisi Modeli +const pool = require('../utils/db'); + +const PlaceModel = { + // --- Ülkeler --- + async upsertCountry(id, name) { + await pool.execute( + 'INSERT INTO countries (id, name) VALUES (?, ?) ON DUPLICATE KEY UPDATE name = VALUES(name)', + [id, name] + ); + }, + + async getAllCountries() { + const [rows] = await pool.execute('SELECT * FROM countries ORDER BY name'); + return rows; + }, + + // --- Eyaletler / İller --- + async upsertState(id, name, countryId) { + await pool.execute( + 'INSERT INTO states (id, name, country_id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name = VALUES(name)', + [id, name, countryId] + ); + }, + + async getStatesByCountry(countryId) { + const [rows] = await pool.execute('SELECT * FROM states WHERE country_id = ? ORDER BY name', [countryId]); + return rows; + }, + + async getAllStates() { + const [rows] = await pool.execute('SELECT * FROM states ORDER BY name'); + return rows; + }, + + // --- Şehirler / İlçeler --- + async upsertCity(id, name, stateId, lat = null, lng = null) { + await pool.execute( + 'INSERT INTO cities (id, name, state_id, latitude, longitude) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE name = VALUES(name), latitude = VALUES(latitude), longitude = VALUES(longitude)', + [id, name, stateId, lat, lng] + ); + }, + + async getCitiesByState(stateId) { + const [rows] = await pool.execute('SELECT * FROM cities WHERE state_id = ? ORDER BY name', [stateId]); + return rows; + }, + + async getCityDetail(cityId) { + const [rows] = await pool.execute( + `SELECT c.*, s.name as state_name, co.name as country_name + FROM cities c + JOIN states s ON c.state_id = s.id + JOIN countries co ON s.country_id = co.id + WHERE c.id = ?`, + [cityId] + ); + return rows[0] || null; + }, + + // İstatistikler + async getStats() { + const [[countries]] = await pool.execute('SELECT COUNT(*) as count FROM countries'); + const [[states]] = await pool.execute('SELECT COUNT(*) as count FROM states'); + const [[cities]] = await pool.execute('SELECT COUNT(*) as count FROM cities'); + const [[prayerTimes]] = await pool.execute('SELECT COUNT(*) as count FROM prayer_times'); + const [[ramadanTimes]] = await pool.execute('SELECT COUNT(*) as count FROM ramadan_times'); + const [[eidTimes]] = await pool.execute('SELECT COUNT(*) as count FROM eid_times'); + return { + countries: countries.count, + states: states.count, + cities: cities.count, + prayerTimes: prayerTimes.count, + ramadanTimes: ramadanTimes.count, + eidTimes: eidTimes.count + }; + } +}; + +module.exports = PlaceModel; diff --git a/backend/src/models/PrayerTime.js b/backend/src/models/PrayerTime.js new file mode 100644 index 0000000..2a0f77d --- /dev/null +++ b/backend/src/models/PrayerTime.js @@ -0,0 +1,61 @@ +// hDiyanetProxy - Namaz Vakitleri Modeli +const pool = require('../utils/db'); + +const PrayerTimeModel = { + // Namaz vakti ekle veya güncelle (upsert) + async upsert(cityId, date, times) { + await pool.execute( + `INSERT INTO prayer_times (city_id, date, fajr, sunrise, dhuhr, asr, maghrib, isha, qibla) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + fajr = VALUES(fajr), sunrise = VALUES(sunrise), dhuhr = VALUES(dhuhr), + asr = VALUES(asr), maghrib = VALUES(maghrib), isha = VALUES(isha), qibla = VALUES(qibla)`, + [cityId, date, times.fajr, times.sunrise, times.dhuhr, times.asr, times.maghrib, times.isha, times.qibla || null] + ); + }, + + // Tarih aralığına göre namaz vakitlerini getir + async getByDateRange(cityId, startDate, endDate) { + const [rows] = await pool.execute( + `SELECT * FROM prayer_times WHERE city_id = ? AND date BETWEEN ? AND ? ORDER BY date`, + [cityId, startDate, endDate] + ); + return rows; + }, + + // Bugünkü namaz vaktini getir + async getToday(cityId) { + const today = new Date().toISOString().split('T')[0]; + const [rows] = await pool.execute( + 'SELECT * FROM prayer_times WHERE city_id = ? AND date = ?', + [cityId, today] + ); + return rows[0] || null; + }, + + // Toplu ekleme (batch upsert) + async bulkUpsert(cityId, timesArray) { + const conn = await pool.getConnection(); + try { + await conn.beginTransaction(); + for (const item of timesArray) { + await conn.execute( + `INSERT INTO prayer_times (city_id, date, fajr, sunrise, dhuhr, asr, maghrib, isha, qibla) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + fajr = VALUES(fajr), sunrise = VALUES(sunrise), dhuhr = VALUES(dhuhr), + asr = VALUES(asr), maghrib = VALUES(maghrib), isha = VALUES(isha), qibla = VALUES(qibla)`, + [cityId, item.date, item.fajr, item.sunrise, item.dhuhr, item.asr, item.maghrib, item.isha, item.qibla || null] + ); + } + await conn.commit(); + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } + } +}; + +module.exports = PrayerTimeModel; diff --git a/backend/src/models/RamadanTime.js b/backend/src/models/RamadanTime.js new file mode 100644 index 0000000..ff4b203 --- /dev/null +++ b/backend/src/models/RamadanTime.js @@ -0,0 +1,61 @@ +// hDiyanetProxy - Ramazan Vakitleri Modeli +const pool = require('../utils/db'); + +const RamadanModel = { + // Ramazan vakti ekle veya güncelle + async upsert(cityId, date, times) { + await pool.execute( + `INSERT INTO ramadan_times (city_id, date, fajr, sunrise, dhuhr, asr, maghrib, isha) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + fajr = VALUES(fajr), sunrise = VALUES(sunrise), dhuhr = VALUES(dhuhr), + asr = VALUES(asr), maghrib = VALUES(maghrib), isha = VALUES(isha)`, + [cityId, date, times.fajr, times.sunrise, times.dhuhr, times.asr, times.maghrib, times.isha] + ); + }, + + // Şehre göre ramazan vakitlerini getir + async getByCityId(cityId) { + const [rows] = await pool.execute( + 'SELECT * FROM ramadan_times WHERE city_id = ? ORDER BY date', + [cityId] + ); + return rows; + }, + + // Bugünkü ramazan vaktini getir + async getToday(cityId) { + const today = new Date().toISOString().split('T')[0]; + const [rows] = await pool.execute( + 'SELECT * FROM ramadan_times WHERE city_id = ? AND date = ?', + [cityId, today] + ); + return rows[0] || null; + }, + + // Toplu ekleme + async bulkUpsert(cityId, timesArray) { + const conn = await pool.getConnection(); + try { + await conn.beginTransaction(); + for (const item of timesArray) { + await conn.execute( + `INSERT INTO ramadan_times (city_id, date, fajr, sunrise, dhuhr, asr, maghrib, isha) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + fajr = VALUES(fajr), sunrise = VALUES(sunrise), dhuhr = VALUES(dhuhr), + asr = VALUES(asr), maghrib = VALUES(maghrib), isha = VALUES(isha)`, + [cityId, item.date, item.fajr, item.sunrise, item.dhuhr, item.asr, item.maghrib, item.isha] + ); + } + await conn.commit(); + } catch (err) { + await conn.rollback(); + throw err; + } finally { + conn.release(); + } + } +}; + +module.exports = RamadanModel; diff --git a/backend/src/models/User.js b/backend/src/models/User.js new file mode 100644 index 0000000..7589a1c --- /dev/null +++ b/backend/src/models/User.js @@ -0,0 +1,56 @@ +// hDiyanetProxy - Kullanıcı Modeli +const pool = require('../utils/db'); +const bcrypt = require('bcrypt'); + +const UserModel = { + // Kullanıcı adına göre bul + async findByUsername(username) { + const [rows] = await pool.execute('SELECT * FROM users WHERE username = ?', [username]); + return rows[0] || null; + }, + + // ID'ye göre bul + async findById(id) { + const [rows] = await pool.execute('SELECT id, username, role, is_active, created_at FROM users WHERE id = ?', [id]); + return rows[0] || null; + }, + + // Yeni kullanıcı oluştur + async create(username, password, role = 'user') { + const hashedPassword = await bcrypt.hash(password, 10); + const [result] = await pool.execute( + 'INSERT INTO users (username, password, role) VALUES (?, ?, ?)', + [username, hashedPassword, role] + ); + return { id: result.insertId, username, role }; + }, + + // Şifre doğrula + async verifyPassword(plainPassword, hashedPassword) { + return bcrypt.compare(plainPassword, hashedPassword); + }, + + // Tüm kullanıcıları listele + async findAll() { + const [rows] = await pool.execute('SELECT id, username, role, is_active, created_at, updated_at FROM users ORDER BY id'); + return rows; + }, + + // Kullanıcıyı aktif/pasif yap + async toggleActive(id, isActive) { + await pool.execute('UPDATE users SET is_active = ? WHERE id = ?', [isActive ? 1 : 0, id]); + }, + + // Kullanıcı sil + async delete(id) { + await pool.execute('DELETE FROM users WHERE id = ?', [id]); + }, + + // Şifre güncelle + async updatePassword(id, newPassword) { + const hashedPassword = await bcrypt.hash(newPassword, 10); + await pool.execute('UPDATE users SET password = ? WHERE id = ?', [hashedPassword, id]); + } +}; + +module.exports = UserModel; diff --git a/backend/src/routes/admin.js b/backend/src/routes/admin.js new file mode 100644 index 0000000..d29090d --- /dev/null +++ b/backend/src/routes/admin.js @@ -0,0 +1,55 @@ +// hDiyanetProxy - Admin Routes +const express = require('express'); +const router = express.Router(); +const { body } = require('express-validator'); +const AdminController = require('../controllers/adminController'); +const { authMiddleware, adminMiddleware } = require('../middleware/auth'); + +// Tüm admin endpointleri auth + admin yetkisi gerektirir +router.use(authMiddleware); +router.use(adminMiddleware); + +// GET /api/v1/admin/dashboard +router.get('/dashboard', AdminController.getDashboard); + +// POST /api/v1/admin/fetch/places +router.post('/fetch/places', AdminController.fetchPlaces); + +// POST /api/v1/admin/fetch/yearly +router.post('/fetch/yearly', [ + body('cityId').isInt().withMessage('cityId sayı olmalı'), + body('startDate').notEmpty().withMessage('startDate gerekli'), + body('endDate').notEmpty().withMessage('endDate gerekli') +], AdminController.fetchYearly); + +// POST /api/v1/admin/fetch/ramadan +router.post('/fetch/ramadan', AdminController.fetchRamadan); + +// POST /api/v1/admin/fetch/eid +router.post('/fetch/eid', AdminController.fetchEid); + +// POST /api/v1/admin/cache/clear +router.post('/cache/clear', AdminController.clearCache); + +// GET /api/v1/admin/logs +router.get('/logs', AdminController.getLogs); + +// --- Kullanıcı Yönetimi --- + +// GET /api/v1/admin/users +router.get('/users', AdminController.getUsers); + +// POST /api/v1/admin/users +router.post('/users', [ + body('username').trim().isLength({ min: 3, max: 50 }).withMessage('Kullanıcı adı 3-50 karakter olmalı'), + body('password').isLength({ min: 6 }).withMessage('Şifre en az 6 karakter olmalı'), + body('role').optional().isIn(['admin', 'user']).withMessage('Rol admin veya user olmalı') +], AdminController.createUser); + +// PATCH /api/v1/admin/users/:id/toggle +router.patch('/users/:id/toggle', AdminController.toggleUserActive); + +// DELETE /api/v1/admin/users/:id +router.delete('/users/:id', AdminController.deleteUser); + +module.exports = router; diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js new file mode 100644 index 0000000..8e17a85 --- /dev/null +++ b/backend/src/routes/auth.js @@ -0,0 +1,32 @@ +// hDiyanetProxy - Auth Routes +const express = require('express'); +const router = express.Router(); +const { body } = require('express-validator'); +const AuthController = require('../controllers/authController'); +const { authMiddleware } = require('../middleware/auth'); + +// POST /api/v1/auth/login +router.post('/login', [ + body('username').trim().notEmpty().withMessage('Kullanıcı adı gerekli'), + body('password').notEmpty().withMessage('Şifre gerekli') +], AuthController.login); + +// POST /api/v1/auth/register +router.post('/register', [ + body('username').trim().isLength({ min: 3, max: 50 }).withMessage('Kullanıcı adı 3-50 karakter olmalı'), + body('password').isLength({ min: 6 }).withMessage('Şifre en az 6 karakter olmalı') +], AuthController.register); + +// GET /api/v1/auth/profile +router.get('/profile', authMiddleware, AuthController.profile); + +// POST /api/v1/auth/refresh +router.post('/refresh', authMiddleware, AuthController.refreshToken); + +// POST /api/v1/auth/change-password +router.post('/change-password', authMiddleware, [ + body('currentPassword').notEmpty().withMessage('Mevcut şifre gerekli'), + body('newPassword').isLength({ min: 6 }).withMessage('Yeni şifre en az 6 karakter olmalı') +], AuthController.changePassword); + +module.exports = router; diff --git a/backend/src/routes/eid.js b/backend/src/routes/eid.js new file mode 100644 index 0000000..72bfe62 --- /dev/null +++ b/backend/src/routes/eid.js @@ -0,0 +1,12 @@ +// hDiyanetProxy - Eid Routes +const express = require('express'); +const router = express.Router(); +const EidController = require('../controllers/eidController'); +const { authMiddleware } = require('../middleware/auth'); + +router.use(authMiddleware); + +// GET /api/v1/eid?cityId={id} +router.get('/', EidController.getByCityId); + +module.exports = router; diff --git a/backend/src/routes/places.js b/backend/src/routes/places.js new file mode 100644 index 0000000..7b2b6c2 --- /dev/null +++ b/backend/src/routes/places.js @@ -0,0 +1,22 @@ +// hDiyanetProxy - Places Routes +const express = require('express'); +const router = express.Router(); +const PlacesController = require('../controllers/placesController'); +const { authMiddleware } = require('../middleware/auth'); + +// Tüm endpointler auth gerektirir +router.use(authMiddleware); + +// GET /api/v1/places/countries +router.get('/countries', PlacesController.getCountries); + +// GET /api/v1/places/states?countryId={id} +router.get('/states', PlacesController.getStates); + +// GET /api/v1/places/cities?stateId={id} +router.get('/cities', PlacesController.getCities); + +// GET /api/v1/places/city/:cityId +router.get('/city/:cityId', PlacesController.getCityDetail); + +module.exports = router; diff --git a/backend/src/routes/prayerTimes.js b/backend/src/routes/prayerTimes.js new file mode 100644 index 0000000..aed76fa --- /dev/null +++ b/backend/src/routes/prayerTimes.js @@ -0,0 +1,15 @@ +// hDiyanetProxy - Prayer Times Routes +const express = require('express'); +const router = express.Router(); +const PrayerTimesController = require('../controllers/prayerTimesController'); +const { authMiddleware } = require('../middleware/auth'); + +router.use(authMiddleware); + +// GET /api/v1/prayer-times?cityId={id}&startDate={date}&endDate={date} +router.get('/', PrayerTimesController.getByDateRange); + +// GET /api/v1/prayer-times/today?cityId={id} +router.get('/today', PrayerTimesController.getToday); + +module.exports = router; diff --git a/backend/src/routes/ramadan.js b/backend/src/routes/ramadan.js new file mode 100644 index 0000000..f473990 --- /dev/null +++ b/backend/src/routes/ramadan.js @@ -0,0 +1,15 @@ +// hDiyanetProxy - Ramadan Routes +const express = require('express'); +const router = express.Router(); +const RamadanController = require('../controllers/ramadanController'); +const { authMiddleware } = require('../middleware/auth'); + +router.use(authMiddleware); + +// GET /api/v1/ramadan?cityId={id} +router.get('/', RamadanController.getByCityId); + +// GET /api/v1/ramadan/today?cityId={id} +router.get('/today', RamadanController.getToday); + +module.exports = router; diff --git a/backend/src/services/diyanetService.js b/backend/src/services/diyanetService.js new file mode 100644 index 0000000..db884ee --- /dev/null +++ b/backend/src/services/diyanetService.js @@ -0,0 +1,220 @@ +// hDiyanetProxy - Diyanet API Servisi +// Diyanet İşleri Başkanlığı API'sinden veri çekme işlemleri +const axios = require('axios'); + +const BASE_URL = process.env.DIYANET_API_URL || 'https://awqatsalah.diyanet.gov.tr'; +const DIYANET_EMAIL = process.env.DIYANET_EMAIL; +const DIYANET_PASSWORD = process.env.DIYANET_PASSWORD; + +// Token bilgileri bellekte tutulur +let accessToken = null; +let refreshToken = null; +let tokenExpiry = null; + +// Axios instance oluştur +const api = axios.create({ + baseURL: BASE_URL, + timeout: 30000, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + // SSL sertifika sorunları için + httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) +}); + +// Her istekten önce token ekle +api.interceptors.request.use(async (config) => { + // Login isteğine token ekleme + if (config.url && config.url.includes('/Auth/Login')) { + return config; + } + + // Token yoksa veya süresi dolmuşsa yenile + if (!accessToken || (tokenExpiry && Date.now() >= tokenExpiry)) { + await DiyanetService.authenticate(); + } + + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + return config; +}); + +// 401 hatası alırsa token yenile ve tekrar dene +api.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + try { + // Önce refresh token dene + if (refreshToken) { + await DiyanetService.refreshAccessToken(); + } else { + await DiyanetService.authenticate(); + } + originalRequest.headers.Authorization = `Bearer ${accessToken}`; + return api(originalRequest); + } catch (authError) { + // Refresh da başarısız olursa, sıfırdan login ol + await DiyanetService.authenticate(); + originalRequest.headers.Authorization = `Bearer ${accessToken}`; + return api(originalRequest); + } + } + return Promise.reject(error); + } +); + +const DiyanetService = { + // --- Kimlik Doğrulama --- + + // Email ve şifre ile giriş yap, token al + async authenticate() { + try { + const response = await axios.post(`${BASE_URL}/Auth/Login`, { + email: DIYANET_EMAIL, + password: DIYANET_PASSWORD + }, { + headers: { 'Content-Type': 'application/json' }, + httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) + }); + + if (response.data.success) { + accessToken = response.data.data.accessToken; + refreshToken = response.data.data.refreshToken; + + // JWT'den expiry süresini çıkar (güvenli tarafta kalmak için 5 dk erken yenile) + try { + const payload = JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString()); + tokenExpiry = (payload.exp * 1000) - (5 * 60 * 1000); + } catch { + // 55 dakika sonra yenile (varsayılan) + tokenExpiry = Date.now() + (55 * 60 * 1000); + } + + console.log('✅ Diyanet API token alındı'); + return true; + } else { + throw new Error(`Diyanet auth hatası: ${response.data.message}`); + } + } catch (error) { + console.error('❌ Diyanet API kimlik doğrulama hatası:', error.message); + throw error; + } + }, + + // Refresh token ile yeni access token al + async refreshAccessToken() { + try { + const response = await axios.get(`${BASE_URL}/Auth/RefreshToken/${refreshToken}`, { + httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }) + }); + + if (response.data.success) { + accessToken = response.data.data.accessToken; + refreshToken = response.data.data.refreshToken; + + try { + const payload = JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString()); + tokenExpiry = (payload.exp * 1000) - (5 * 60 * 1000); + } catch { + tokenExpiry = Date.now() + (55 * 60 * 1000); + } + + console.log('🔄 Diyanet API token yenilendi'); + return true; + } + throw new Error('Token yenileme başarısız'); + } catch (error) { + console.error('❌ Token yenileme hatası:', error.message); + // Refresh başarısızsa sıfırdan login ol + accessToken = null; + refreshToken = null; + tokenExpiry = null; + throw error; + } + }, + + // --- Yer Bilgileri --- + + // Yardımcı: API yanıtından veriyi çıkar + // Diyanet API {data: [...], success: true} formatında dönüyor + _extractData(response) { + const body = response.data; + if (body && typeof body === 'object' && 'data' in body) { + return body.data; + } + return body; + }, + + // Tüm ülkeleri çek + async getCountries() { + const response = await api.get('/api/Place/Countries'); + return this._extractData(response); + }, + + // Ülkeye göre eyaletleri çek + async getStates(countryId) { + const response = await api.get(`/api/Place/States/${countryId}`); + return this._extractData(response); + }, + + // Tüm eyaletleri çek + async getAllStates() { + const response = await api.get('/api/Place/States'); + return this._extractData(response); + }, + + // Eyalete göre şehirleri çek + async getCities(stateId) { + const response = await api.get(`/api/Place/Cities/${stateId}`); + return this._extractData(response); + }, + + // Tüm şehirleri çek + async getAllCities() { + const response = await api.get('/api/Place/Cities'); + return this._extractData(response); + }, + + // Şehir detayı çek + async getCityDetail(cityId) { + const response = await api.get(`/api/Place/CityDetail/${cityId}`); + return this._extractData(response); + }, + + // --- Namaz Vakitleri --- + + // Yıllık namaz vakitlerini çek + async getYearlyPrayerTimes(cityId, startDate, endDate) { + const response = await api.post('/api/AwqatSalah/Yearly', { + cityId: parseInt(cityId), + startDate, + endDate + }); + return this._extractData(response); + }, + + // --- Ramazan İmsakiyesi --- + async getRamadanTimes(cityId) { + const response = await api.get(`/api/AwqatSalah/Ramadan/${cityId}`); + return this._extractData(response); + }, + + // --- Bayram Vakitleri --- + async getEidTimes(cityId) { + const response = await api.get(`/api/AwqatSalah/Eid/${cityId}`); + return this._extractData(response); + }, + + // --- Cache Temizleme --- + async clearCache() { + const response = await api.get('/api/Cache/ClearCache'); + return this._extractData(response); + } +}; + +module.exports = DiyanetService; diff --git a/backend/src/services/fetchService.js b/backend/src/services/fetchService.js new file mode 100644 index 0000000..ec8691e --- /dev/null +++ b/backend/src/services/fetchService.js @@ -0,0 +1,191 @@ +// hDiyanetProxy - Veri Çekme ve Senkronizasyon Servisi +const DiyanetService = require('./diyanetService'); +const PlaceModel = require('../models/Place'); +const PrayerTimeModel = require('../models/PrayerTime'); +const RamadanModel = require('../models/RamadanTime'); +const EidModel = require('../models/EidTime'); +const FetchLogModel = require('../models/FetchLog'); + +const FetchService = { + // Tüm yer bilgilerini çek ve veritabanına kaydet + async fetchAndSavePlaces(userId = null) { + let totalRecords = 0; + try { + // 1. Ülkeleri çek + console.log('📍 Ülkeler çekiliyor...'); + const countries = await DiyanetService.getCountries(); + if (Array.isArray(countries)) { + for (const c of countries) { + await PlaceModel.upsertCountry(c.id, c.name); + } + totalRecords += countries.length; + console.log(` ✅ ${countries.length} ülke kaydedildi`); + } + + // 2. Eyaletleri çek (her ülke için) + console.log('📍 Eyaletler/İller çekiliyor...'); + if (Array.isArray(countries)) { + for (const c of countries) { + try { + const states = await DiyanetService.getStates(c.id); + if (Array.isArray(states)) { + for (const s of states) { + await PlaceModel.upsertState(s.id, s.name, c.id); + } + totalRecords += states.length; + } + } catch (e) { + console.log(` ⚠️ ${c.name} eyaletleri alınamadı: ${e.message}`); + } + // Rate limiting - API'yi yormamak için küçük bekleme + await new Promise(r => setTimeout(r, 100)); + } + console.log(` ✅ Eyaletler kaydedildi`); + } + + // 3. Şehirleri çek - tüm eyaletler için + console.log('📍 Şehirler/İlçeler çekiliyor...'); + const pool = require('../utils/db'); + const [allStates] = await pool.execute('SELECT id, name FROM states'); + for (const s of allStates) { + try { + const cities = await DiyanetService.getCities(s.id); + if (Array.isArray(cities)) { + for (const ci of cities) { + await PlaceModel.upsertCity(ci.id, ci.name, s.id, ci.latitude || null, ci.longitude || null); + } + totalRecords += cities.length; + } + } catch (e) { + // Bazı eyaletlerin şehirleri olmayabilir + } + await new Promise(r => setTimeout(r, 50)); + } + console.log(` ✅ Şehirler kaydedildi`); + + // Log kaydı + await FetchLogModel.create(userId, 'fetch_places', null, null, 'Tüm yer bilgileri güncellendi', 'success', totalRecords); + return { success: true, message: 'Yer bilgileri güncellendi', totalRecords }; + } catch (err) { + await FetchLogModel.create(userId, 'fetch_places', null, null, `Hata: ${err.message}`, 'error', 0); + throw err; + } + }, + + // Yıllık namaz vakitlerini çek ve kaydet + async fetchAndSaveYearly(cityId, startDate, endDate, userId = null) { + try { + console.log(`🕌 Namaz vakitleri çekiliyor: cityId=${cityId}, ${startDate} - ${endDate}`); + const data = await DiyanetService.getYearlyPrayerTimes(cityId, startDate, endDate); + + if (!data || !Array.isArray(data)) { + throw new Error('API\'den geçersiz veri döndü'); + } + + // Verileri dönüştür ve kaydet + const timesArray = data.map(item => ({ + date: item.gregorianDateLong ? item.gregorianDateLong.split('T')[0] : item.gregorianDate, + fajr: item.fajr || item.imsak, + sunrise: item.sunrise || item.gunes, + dhuhr: item.dhuhr || item.ogle, + asr: item.asr || item.ikindi, + maghrib: item.maghrib || item.aksam, + isha: item.isha || item.yatsi, + qibla: item.qibla || null + })); + + await PrayerTimeModel.bulkUpsert(cityId, timesArray); + + // Şehir adını al + const cityDetail = await PlaceModel.getCityDetail(cityId); + const cityName = cityDetail ? cityDetail.name : `City ${cityId}`; + + await FetchLogModel.create(userId, 'fetch_yearly', cityId, cityName, + `${startDate} - ${endDate} arası namaz vakitleri çekildi`, 'success', timesArray.length); + + console.log(` ✅ ${timesArray.length} namaz vakti kaydedildi`); + return { success: true, message: `${timesArray.length} namaz vakti kaydedildi`, count: timesArray.length }; + } catch (err) { + await FetchLogModel.create(userId, 'fetch_yearly', cityId, null, `Hata: ${err.message}`, 'error', 0); + throw err; + } + }, + + // Ramazan vakitlerini çek ve kaydet + async fetchAndSaveRamadan(cityId, userId = null) { + try { + console.log(`🌙 Ramazan vakitleri çekiliyor: cityId=${cityId}`); + const data = await DiyanetService.getRamadanTimes(cityId); + + if (!data || !Array.isArray(data)) { + throw new Error('API\'den geçersiz veri döndü'); + } + + const timesArray = data.map(item => ({ + date: item.gregorianDateLong ? item.gregorianDateLong.split('T')[0] : item.gregorianDate, + fajr: item.fajr || item.imsak, + sunrise: item.sunrise || item.gunes, + dhuhr: item.dhuhr || item.ogle, + asr: item.asr || item.ikindi, + maghrib: item.maghrib || item.aksam, + isha: item.isha || item.yatsi + })); + + await RamadanModel.bulkUpsert(cityId, timesArray); + + const cityDetail = await PlaceModel.getCityDetail(cityId); + const cityName = cityDetail ? cityDetail.name : `City ${cityId}`; + + await FetchLogModel.create(userId, 'fetch_ramadan', cityId, cityName, + 'Ramazan vakitleri çekildi', 'success', timesArray.length); + + console.log(` ✅ ${timesArray.length} ramazan vakti kaydedildi`); + return { success: true, message: `${timesArray.length} ramazan vakti kaydedildi`, count: timesArray.length }; + } catch (err) { + await FetchLogModel.create(userId, 'fetch_ramadan', cityId, null, `Hata: ${err.message}`, 'error', 0); + throw err; + } + }, + + // Bayram vakitlerini çek ve kaydet + async fetchAndSaveEid(cityId, userId = null) { + try { + console.log(`🎉 Bayram vakitleri çekiliyor: cityId=${cityId}`); + const data = await DiyanetService.getEidTimes(cityId); + + if (!data) { + throw new Error('API\'den geçersiz veri döndü'); + } + + // Bayram verisi tek obje veya dizi olabilir + const items = Array.isArray(data) ? data : [data]; + const eidItems = items.map(item => ({ + eidName: item.eidName || item.bayramAdi || 'Bayram', + date: item.gregorianDateLong ? item.gregorianDateLong.split('T')[0] : item.gregorianDate || item.date, + prayerTime: item.eidPrayerTime || item.bayramNamazi || null, + fajr: item.fajr || item.imsak || null, + sunrise: item.sunrise || item.gunes || null, + dhuhr: item.dhuhr || item.ogle || null, + asr: item.asr || item.ikindi || null, + maghrib: item.maghrib || item.aksam || null, + isha: item.isha || item.yatsi || null + })); + + await EidModel.bulkUpsert(cityId, eidItems); + + const cityDetail = await PlaceModel.getCityDetail(cityId); + const cityName = cityDetail ? cityDetail.name : `City ${cityId}`; + + await FetchLogModel.create(userId, 'fetch_eid', cityId, cityName, + 'Bayram vakitleri çekildi', 'success', eidItems.length); + + console.log(` ✅ ${eidItems.length} bayram vakti kaydedildi`); + return { success: true, message: `${eidItems.length} bayram vakti kaydedildi`, count: eidItems.length }; + } catch (err) { + await FetchLogModel.create(userId, 'fetch_eid', cityId, null, `Hata: ${err.message}`, 'error', 0); + throw err; + } + } +}; + +module.exports = FetchService; diff --git a/backend/src/utils/db.js b/backend/src/utils/db.js new file mode 100644 index 0000000..1a91815 --- /dev/null +++ b/backend/src/utils/db.js @@ -0,0 +1,27 @@ +// hDiyanetProxy - Veritabanı bağlantı modülü +const mysql = require('mysql2/promise'); +require('dotenv').config(); + +const pool = mysql.createPool({ + host: process.env.DB_HOST, + port: process.env.DB_PORT || 3306, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0, + charset: 'utf8mb4' +}); + +// Bağlantı testi +pool.getConnection() + .then(conn => { + console.log('✅ MySQL veritabanına bağlandı'); + conn.release(); + }) + .catch(err => { + console.error('❌ MySQL bağlantı hatası:', err.message); + }); + +module.exports = pool; diff --git a/backend/src/utils/migrate.js b/backend/src/utils/migrate.js new file mode 100644 index 0000000..b516b9c --- /dev/null +++ b/backend/src/utils/migrate.js @@ -0,0 +1,134 @@ +// hDiyanetProxy - Migration Script +// Veritabanı tablolarını oluşturur +const pool = require('./db'); + +const migrations = [ + // Kullanıcılar tablosu + `CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + role ENUM('admin', 'user') DEFAULT 'user', + is_active TINYINT(1) DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`, + + // Ülkeler tablosu + `CREATE TABLE IF NOT EXISTS countries ( + id INT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`, + + // Eyaletler/İller tablosu + `CREATE TABLE IF NOT EXISTS states ( + id INT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + country_id INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_country (country_id), + FOREIGN KEY (country_id) REFERENCES countries(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`, + + // Şehirler/İlçeler tablosu + `CREATE TABLE IF NOT EXISTS cities ( + id INT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + state_id INT NOT NULL, + latitude DECIMAL(10,7) NULL, + longitude DECIMAL(10,7) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_state (state_id), + FOREIGN KEY (state_id) REFERENCES states(id) ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`, + + // Namaz vakitleri tablosu + `CREATE TABLE IF NOT EXISTS prayer_times ( + id INT AUTO_INCREMENT PRIMARY KEY, + city_id INT NOT NULL, + date DATE NOT NULL, + fajr VARCHAR(10), + sunrise VARCHAR(10), + dhuhr VARCHAR(10), + asr VARCHAR(10), + maghrib VARCHAR(10), + isha VARCHAR(10), + qibla VARCHAR(10), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_city_date (city_id, date), + INDEX idx_city (city_id), + INDEX idx_date (date) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`, + + // Ramazan vakitleri tablosu + `CREATE TABLE IF NOT EXISTS ramadan_times ( + id INT AUTO_INCREMENT PRIMARY KEY, + city_id INT NOT NULL, + date DATE NOT NULL, + fajr VARCHAR(10), + sunrise VARCHAR(10), + dhuhr VARCHAR(10), + asr VARCHAR(10), + maghrib VARCHAR(10), + isha VARCHAR(10), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_city_date (city_id, date), + INDEX idx_city (city_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`, + + // Bayram vakitleri tablosu + `CREATE TABLE IF NOT EXISTS eid_times ( + id INT AUTO_INCREMENT PRIMARY KEY, + city_id INT NOT NULL, + eid_name VARCHAR(100), + date DATE NOT NULL, + prayer_time VARCHAR(10), + fajr VARCHAR(10), + sunrise VARCHAR(10), + dhuhr VARCHAR(10), + asr VARCHAR(10), + maghrib VARCHAR(10), + isha VARCHAR(10), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_city_eid_date (city_id, eid_name, date), + INDEX idx_city (city_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`, + + // Veri çekme logları tablosu + `CREATE TABLE IF NOT EXISTS fetch_logs ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NULL, + action VARCHAR(50) NOT NULL, + city_id INT NULL, + city_name VARCHAR(255) NULL, + details TEXT NULL, + status ENUM('success', 'error') DEFAULT 'success', + records_count INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_user (user_id), + INDEX idx_created (created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4` +]; + +async function runMigrations() { + console.log('🔄 Migration başlatılıyor...\n'); + + for (let i = 0; i < migrations.length; i++) { + const tableName = migrations[i].match(/CREATE TABLE IF NOT EXISTS (\w+)/)?.[1] || `migration_${i}`; + try { + await pool.execute(migrations[i]); + console.log(` ✅ ${tableName} tablosu oluşturuldu`); + } catch (err) { + console.error(` ❌ ${tableName} hatası: ${err.message}`); + } + } + + console.log('\n✅ Migration tamamlandı!'); + process.exit(0); +} + +runMigrations(); diff --git a/backend/src/utils/seed.js b/backend/src/utils/seed.js new file mode 100644 index 0000000..7bb28d4 --- /dev/null +++ b/backend/src/utils/seed.js @@ -0,0 +1,31 @@ +// hDiyanetProxy - Seed Script +// İlk admin kullanıcıyı oluşturur +const pool = require('./db'); +const bcrypt = require('bcrypt'); + +async function seed() { + console.log('🌱 Seed başlatılıyor...\n'); + + try { + // Admin kullanıcı kontrolü + const [existing] = await pool.execute('SELECT id FROM users WHERE username = ?', ['admin']); + + if (existing.length > 0) { + console.log(' ℹ️ Admin kullanıcı zaten mevcut, atlanıyor'); + } else { + const hashedPassword = await bcrypt.hash('admin123', 10); + await pool.execute( + 'INSERT INTO users (username, password, role, is_active) VALUES (?, ?, ?, ?)', + ['admin', hashedPassword, 'admin', 1] + ); + console.log(' ✅ Admin kullanıcı oluşturuldu (admin / admin123)'); + } + } catch (err) { + console.error(' ❌ Seed hatası:', err.message); + } + + console.log('\n✅ Seed tamamlandı!'); + process.exit(0); +} + +seed(); diff --git a/frontend/src/components/sidebar.js b/frontend/src/components/sidebar.js new file mode 100644 index 0000000..0e35e3d --- /dev/null +++ b/frontend/src/components/sidebar.js @@ -0,0 +1,75 @@ +// hDiyanetProxy - Sidebar Bileşeni +function renderSidebar(activePage) { + const user = getUser(); + const isAdmin = user && user.role === 'admin'; + + return ` + + + `; +} + +// Ülke-Eyalet-Şehir dropdown zinciri yükleme +async function loadCountryDropdown(selectId) { + try { + const countries = await apiFetch('/places/countries'); + const select = document.getElementById(selectId); + select.innerHTML = ''; + countries.forEach(c => { + select.innerHTML += ``; + }); + } catch (err) { + console.error('Ülkeler yüklenemedi:', err); + } +} + +async function loadStateDropdown(countryId, selectId) { + try { + const states = await apiFetch(`/places/states?countryId=${countryId}`); + const select = document.getElementById(selectId); + select.innerHTML = ''; + states.forEach(s => { + select.innerHTML += ``; + }); + } catch (err) { + console.error('Eyaletler yüklenemedi:', err); + } +} + +async function loadCityDropdown(stateId, selectId) { + try { + const cities = await apiFetch(`/places/cities?stateId=${stateId}`); + const select = document.getElementById(selectId); + select.innerHTML = ''; + cities.forEach(c => { + select.innerHTML += ``; + }); + } catch (err) { + console.error('Şehirler yüklenemedi:', err); + } +} diff --git a/frontend/src/pages/admin-users.html b/frontend/src/pages/admin-users.html new file mode 100644 index 0000000..7ec4f21 --- /dev/null +++ b/frontend/src/pages/admin-users.html @@ -0,0 +1,157 @@ + + + + + + Kullanıcı Yönetimi - hDiyanetProxy + + + + + +
+ + + + + + diff --git a/frontend/src/pages/dashboard.html b/frontend/src/pages/dashboard.html new file mode 100644 index 0000000..d205237 --- /dev/null +++ b/frontend/src/pages/dashboard.html @@ -0,0 +1,97 @@ + + + + + + Dashboard - hDiyanetProxy + + + + + +
+ + + + + + diff --git a/frontend/src/pages/fetch.html b/frontend/src/pages/fetch.html new file mode 100644 index 0000000..69a6813 --- /dev/null +++ b/frontend/src/pages/fetch.html @@ -0,0 +1,237 @@ + + + + + + Veri Çekme - hDiyanetProxy + + + + + +
+ + + + + + diff --git a/frontend/src/pages/login.html b/frontend/src/pages/login.html new file mode 100644 index 0000000..4ba220a --- /dev/null +++ b/frontend/src/pages/login.html @@ -0,0 +1,71 @@ + + + + + + Giriş - hDiyanetProxy + + + + + + +
+ +
+ + + + + diff --git a/frontend/src/pages/prayer-times.html b/frontend/src/pages/prayer-times.html new file mode 100644 index 0000000..eb860b6 --- /dev/null +++ b/frontend/src/pages/prayer-times.html @@ -0,0 +1,157 @@ + + + + + + Namaz Vakitleri - hDiyanetProxy + + + + + +
+ + + + + + diff --git a/frontend/src/pages/profile.html b/frontend/src/pages/profile.html new file mode 100644 index 0000000..f574c4c --- /dev/null +++ b/frontend/src/pages/profile.html @@ -0,0 +1,155 @@ + + + + + + Profil & API - hDiyanetProxy + + + + + +
+ + + + + + diff --git a/frontend/src/pages/ramadan.html b/frontend/src/pages/ramadan.html new file mode 100644 index 0000000..a0894bb --- /dev/null +++ b/frontend/src/pages/ramadan.html @@ -0,0 +1,141 @@ + + + + + + Ramazan / İftar - hDiyanetProxy + + + + + +
+ + + + + + diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js new file mode 100644 index 0000000..d1938d7 --- /dev/null +++ b/frontend/src/services/api.js @@ -0,0 +1,127 @@ +// hDiyanetProxy - Frontend API Servisi +const API_BASE = '/api/v1'; + +// Token yönetimi +function getToken() { + return localStorage.getItem('hdiyanet_token'); +} + +function setToken(token) { + localStorage.setItem('hdiyanet_token', token); +} + +function getUser() { + const user = localStorage.getItem('hdiyanet_user'); + return user ? JSON.parse(user) : null; +} + +function setUser(user) { + localStorage.setItem('hdiyanet_user', JSON.stringify(user)); +} + +function logout() { + localStorage.removeItem('hdiyanet_token'); + localStorage.removeItem('hdiyanet_user'); + window.location.href = '/login'; +} + +// Auth kontrolü - token yoksa login'e yönlendir +function requireAuth() { + if (!getToken()) { + window.location.href = '/login'; + return false; + } + return true; +} + +// API çağrısı yardımcı fonksiyonu +async function apiFetch(endpoint, options = {}) { + const token = getToken(); + const headers = { + 'Content-Type': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}), + ...options.headers + }; + + const response = await fetch(`${API_BASE}${endpoint}`, { + ...options, + headers + }); + + // 401 ise login'e yönlendir + if (response.status === 401) { + logout(); + throw new Error('Oturum süresi doldu'); + } + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || data.errors?.[0]?.msg || 'Bir hata oluştu'); + } + + return data; +} + +// Toast bildirimi göster +function showToast(message, type = 'info') { + let container = document.querySelector('.toast-container'); + if (!container) { + container = document.createElement('div'); + container.className = 'toast-container'; + document.body.appendChild(container); + } + + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + toast.textContent = message; + container.appendChild(toast); + + setTimeout(() => { + toast.style.opacity = '0'; + toast.style.transform = 'translateX(100%)'; + setTimeout(() => toast.remove(), 300); + }, 3000); +} + +// Tarih formatlama +function formatDate(dateStr) { + if (!dateStr) return '-'; + const d = new Date(dateStr); + return d.toLocaleDateString('tr-TR'); +} + +function formatDateTime(dateStr) { + if (!dateStr) return '-'; + const d = new Date(dateStr); + return d.toLocaleString('tr-TR'); +} + +// CSV dışa aktarma +function exportCSV(data, filename) { + if (!data || data.length === 0) return; + const headers = Object.keys(data[0]); + const csv = [ + headers.join(','), + ...data.map(row => headers.map(h => `"${row[h] || ''}"`).join(',')) + ].join('\n'); + + const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${filename}.csv`; + a.click(); + URL.revokeObjectURL(url); +} + +// JSON dışa aktarma +function exportJSON(data, filename) { + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${filename}.json`; + a.click(); + URL.revokeObjectURL(url); +} diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..b6f7d40 --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,567 @@ +/* hDiyanetProxy - Ana Stil Dosyası */ +:root { + --primary: #1a6b4e; + --primary-light: #2d9d6f; + --primary-dark: #0f4a35; + --accent: #e8a838; + --accent-dark: #c48a1f; + --bg: #f5f7fa; + --bg-card: #ffffff; + --text: #1e293b; + --text-light: #64748b; + --text-lighter: #94a3b8; + --border: #e2e8f0; + --success: #16a34a; + --error: #dc2626; + --warning: #f59e0b; + --info: #3b82f6; + --shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.06); + --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.08), 0 2px 4px -1px rgba(0,0,0,0.04); + --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.08), 0 4px 6px -2px rgba(0,0,0,0.04); + --radius: 8px; + --radius-lg: 12px; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + background: var(--bg); + color: var(--text); + line-height: 1.6; + min-height: 100vh; +} + +a { color: var(--primary); text-decoration: none; } +a:hover { color: var(--primary-light); } + +/* --- Layout --- */ +.app-layout { + display: flex; + min-height: 100vh; +} + +.sidebar { + width: 260px; + background: var(--primary-dark); + color: #fff; + padding: 0; + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 100; + display: flex; + flex-direction: column; + transition: transform 0.3s; +} + +.sidebar-header { + padding: 20px 20px 16px; + border-bottom: 1px solid rgba(255,255,255,0.1); +} + +.sidebar-header h2 { + font-size: 18px; + font-weight: 700; + display: flex; + align-items: center; + gap: 8px; +} + +.sidebar-header small { + display: block; + font-size: 11px; + color: rgba(255,255,255,0.5); + margin-top: 4px; +} + +.sidebar-nav { + flex: 1; + padding: 12px 0; + overflow-y: auto; +} + +.sidebar-nav a { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 20px; + color: rgba(255,255,255,0.7); + font-size: 14px; + font-weight: 500; + transition: all 0.2s; + border-left: 3px solid transparent; +} + +.sidebar-nav a:hover { + color: #fff; + background: rgba(255,255,255,0.08); +} + +.sidebar-nav a.active { + color: #fff; + background: rgba(255,255,255,0.12); + border-left-color: var(--accent); +} + +.sidebar-nav .nav-section { + padding: 16px 20px 6px; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 1px; + color: rgba(255,255,255,0.35); + font-weight: 600; +} + +.sidebar-footer { + padding: 16px 20px; + border-top: 1px solid rgba(255,255,255,0.1); + font-size: 13px; +} + +.sidebar-footer .user-info { + display: flex; + align-items: center; + gap: 8px; + color: rgba(255,255,255,0.7); +} + +.sidebar-footer .user-badge { + background: var(--accent); + color: var(--primary-dark); + font-size: 10px; + padding: 2px 6px; + border-radius: 4px; + font-weight: 700; + text-transform: uppercase; +} + +.main-content { + flex: 1; + margin-left: 260px; + padding: 24px 32px; + min-height: 100vh; +} + +.page-header { + margin-bottom: 24px; +} + +.page-header h1 { + font-size: 24px; + font-weight: 700; + color: var(--text); +} + +.page-header p { + color: var(--text-light); + font-size: 14px; + margin-top: 4px; +} + +/* --- Cards --- */ +.card { + background: var(--bg-card); + border-radius: var(--radius-lg); + box-shadow: var(--shadow); + padding: 20px 24px; + margin-bottom: 20px; + border: 1px solid var(--border); +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border); +} + +.card-header h3 { + font-size: 16px; + font-weight: 600; +} + +/* --- Stats Grid --- */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 16px; + margin-bottom: 24px; +} + +.stat-card { + background: var(--bg-card); + border-radius: var(--radius-lg); + padding: 20px; + box-shadow: var(--shadow); + border: 1px solid var(--border); + text-align: center; +} + +.stat-card .stat-icon { + font-size: 28px; + margin-bottom: 8px; +} + +.stat-card .stat-value { + font-size: 28px; + font-weight: 700; + color: var(--primary); +} + +.stat-card .stat-label { + font-size: 12px; + color: var(--text-light); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-top: 4px; +} + +/* --- Buttons --- */ +.btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 10px 20px; + border: none; + border-radius: var(--radius); + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + text-decoration: none; +} + +.btn-primary { + background: var(--primary); + color: #fff; +} +.btn-primary:hover { background: var(--primary-light); } + +.btn-accent { + background: var(--accent); + color: var(--primary-dark); +} +.btn-accent:hover { background: var(--accent-dark); color: #fff; } + +.btn-danger { + background: var(--error); + color: #fff; +} +.btn-danger:hover { background: #b91c1c; } + +.btn-outline { + background: transparent; + border: 1px solid var(--border); + color: var(--text); +} +.btn-outline:hover { border-color: var(--primary); color: var(--primary); } + +.btn-sm { + padding: 6px 12px; + font-size: 12px; +} + +.btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* --- Forms --- */ +.form-group { + margin-bottom: 16px; +} + +.form-group label { + display: block; + font-size: 13px; + font-weight: 600; + color: var(--text); + margin-bottom: 6px; +} + +.form-control { + width: 100%; + padding: 10px 14px; + border: 1px solid var(--border); + border-radius: var(--radius); + font-size: 14px; + font-family: inherit; + color: var(--text); + background: var(--bg-card); + transition: border-color 0.2s; +} + +.form-control:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(26,107,78,0.1); +} + +select.form-control { + cursor: pointer; +} + +.form-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; +} + +/* --- Tables --- */ +.table-wrapper { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +thead th { + background: var(--bg); + padding: 10px 12px; + text-align: left; + font-weight: 600; + color: var(--text-light); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 2px solid var(--border); + white-space: nowrap; +} + +tbody td { + padding: 10px 12px; + border-bottom: 1px solid var(--border); + color: var(--text); +} + +tbody tr:hover { + background: rgba(26,107,78,0.03); +} + +/* --- Badges --- */ +.badge { + display: inline-block; + padding: 3px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; +} + +.badge-success { background: #dcfce7; color: #166534; } +.badge-error { background: #fef2f2; color: #991b1b; } +.badge-info { background: #dbeafe; color: #1e40af; } +.badge-warning { background: #fef9c3; color: #854d0e; } +.badge-admin { background: var(--accent); color: var(--primary-dark); } +.badge-user { background: #e2e8f0; color: #475569; } + +/* --- Toast / Alert --- */ +.toast-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 10000; + display: flex; + flex-direction: column; + gap: 8px; +} + +.toast { + padding: 12px 20px; + border-radius: var(--radius); + color: #fff; + font-size: 14px; + font-weight: 500; + box-shadow: var(--shadow-lg); + animation: slideIn 0.3s ease; + max-width: 400px; +} + +.toast-success { background: var(--success); } +.toast-error { background: var(--error); } +.toast-info { background: var(--info); } +.toast-warning { background: var(--warning); } + +@keyframes slideIn { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +} + +/* --- Login Page --- */ +.login-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 50%, var(--primary-light) 100%); + padding: 20px; +} + +.login-card { + background: #fff; + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + padding: 40px; + width: 100%; + max-width: 420px; +} + +.login-card h1 { + text-align: center; + font-size: 22px; + margin-bottom: 4px; + color: var(--primary-dark); +} + +.login-card .subtitle { + text-align: center; + color: var(--text-light); + font-size: 13px; + margin-bottom: 28px; +} + +.login-card .btn-primary { + width: 100%; + padding: 12px; + font-size: 15px; + margin-top: 8px; +} + +.login-error { + background: #fef2f2; + color: var(--error); + padding: 10px 14px; + border-radius: var(--radius); + font-size: 13px; + margin-bottom: 16px; + display: none; +} + +/* --- Log Console --- */ +.log-console { + background: #1e293b; + color: #a5f3fc; + border-radius: var(--radius); + padding: 16px; + font-family: 'Courier New', monospace; + font-size: 13px; + max-height: 300px; + overflow-y: auto; + white-space: pre-wrap; + line-height: 1.8; +} + +.log-console .log-success { color: #4ade80; } +.log-console .log-error { color: #f87171; } +.log-console .log-info { color: #93c5fd; } + +/* --- Loading --- */ +.spinner { + display: inline-block; + width: 18px; + height: 18px; + border: 2px solid rgba(255,255,255,0.3); + border-top-color: #fff; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.loading-overlay { + position: fixed; + inset: 0; + background: rgba(255,255,255,0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +/* --- Modal --- */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; +} + +.modal { + background: #fff; + border-radius: var(--radius-lg); + padding: 24px; + width: 90%; + max-width: 480px; + box-shadow: var(--shadow-lg); +} + +.modal h3 { + margin-bottom: 16px; + font-size: 18px; +} + +.modal-actions { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 20px; +} + +/* --- Responsive --- */ +@media (max-width: 768px) { + .sidebar { + transform: translateX(-100%); + } + .sidebar.open { + transform: translateX(0); + } + .main-content { + margin-left: 0; + padding: 16px; + } + .mobile-toggle { + display: block !important; + } +} + +.mobile-toggle { + display: none; + position: fixed; + top: 12px; + left: 12px; + z-index: 200; + background: var(--primary); + color: #fff; + border: none; + border-radius: var(--radius); + padding: 8px 12px; + font-size: 18px; + cursor: pointer; +} + +/* Iftar vurgulu satır */ +tr.iftar-row { + background: rgba(232,168,56,0.08) !important; +} + +tr.iftar-row td { + font-weight: 600; +} + +/* Export butonları */ +.export-btns { + display: flex; + gap: 8px; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..09623ae --- /dev/null +++ b/package-lock.json @@ -0,0 +1,158 @@ +{ + "name": "hdiyanetproxy", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hdiyanetproxy", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "mysql2": "^3.18.2" + } + }, + "node_modules/@types/node": { + "version": "25.3.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.2.tgz", + "integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "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/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/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/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/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/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.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "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/mysql2": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.18.2.tgz", + "integrity": "sha512-UfEShBFAZZEAKjySnTUuE7BgqkYT4mx+RjoJ5aqtmwSSvNcJ/QxQPXz/y3jSxNiVRedPfgccmuBtiPCSiEEytw==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT", + "peer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a4bb8d0 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "hdiyanetproxy", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "mysql2": "^3.18.2" + } +}