Commit ·
082811f
1
Parent(s): 5efbe3f
Deploy to Hugging Face Spaces
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +21 -0
- .editorconfig +18 -0
- .env +19 -0
- .env.docker +29 -0
- .env.example +15 -0
- .gitattributes +16 -33
- .github/workflows/api-tests.yml +140 -0
- .github/workflows/integration-test.yml +556 -0
- .gitignore +10 -0
- .scribe/.filehashes +4 -0
- .scribe/auth.md +3 -0
- .scribe/endpoints.cache/00.yaml +1503 -0
- .scribe/endpoints/00.yaml +1501 -0
- .scribe/endpoints/custom.0.yaml +53 -0
- .scribe/intro.md +13 -0
- API.md +3095 -0
- ARCHITECTURE.md +224 -0
- DOCKER.md +63 -0
- DOCKER_FIXES.md +231 -0
- DOCUMENTATION_STRUCTURE.md +449 -0
- Dockerfile +116 -0
- README.md +105 -8
- _ide_helper.php +0 -0
- _ide_helper_models.php +715 -0
- app/Helpers/OtpHelper.php +28 -0
- app/Http/Controllers/Api/AdmetController.php +29 -0
- app/Http/Controllers/Api/AiServicesIntegrationController.php +119 -0
- app/Http/Controllers/Api/AuthController.php +117 -0
- app/Http/Controllers/Api/AwardController.php +52 -0
- app/Http/Controllers/Api/BaseController.php +13 -0
- app/Http/Controllers/Api/ChemicalSearchController.php +79 -0
- app/Http/Controllers/Api/ChemistryController.php +415 -0
- app/Http/Controllers/Api/ConvertSmilesController.php +180 -0
- app/Http/Controllers/Api/DockingController.php +254 -0
- app/Http/Controllers/Api/EmailVerificationController.php +85 -0
- app/Http/Controllers/Api/GenerationController.php +254 -0
- app/Http/Controllers/Api/LigandsController.php +65 -0
- app/Http/Controllers/Api/MdFileController.php +81 -0
- app/Http/Controllers/Api/NewsController.php +257 -0
- app/Http/Controllers/Api/OtpController.php +66 -0
- app/Http/Controllers/Api/PasswordResetController.php +50 -0
- app/Http/Controllers/Api/ScientistController.php +82 -0
- app/Http/Controllers/Api/ScreeningController.php +151 -0
- app/Http/Controllers/Api/SimulationController.php +116 -0
- app/Http/Controllers/Api/UserController.php +68 -0
- app/Http/Controllers/ApiDocsController.php +24 -0
- app/Http/Controllers/DockingTestController.php +158 -0
- app/Http/Middleware/CheckJobOwnership.php +44 -0
- app/Http/Middleware/EnsureChemistryApiAvailable.php +20 -0
- app/Http/Requests/Ai/GenerationRequest.php +31 -0
.dockerignore
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.github
|
| 3 |
+
.idea
|
| 4 |
+
.vscode
|
| 5 |
+
.env
|
| 6 |
+
.env.*
|
| 7 |
+
!.env.docker
|
| 8 |
+
node_modules
|
| 9 |
+
vendor
|
| 10 |
+
storage/logs/*
|
| 11 |
+
storage/framework/cache/*
|
| 12 |
+
storage/framework/sessions/*
|
| 13 |
+
storage/framework/views/*
|
| 14 |
+
bootstrap/cache/*
|
| 15 |
+
.phpunit.result.cache
|
| 16 |
+
ai_apps
|
| 17 |
+
*.md
|
| 18 |
+
Homestead.json
|
| 19 |
+
Homestead.yaml
|
| 20 |
+
Thumbs.db
|
| 21 |
+
.DS_Store
|
.editorconfig
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
root = true
|
| 2 |
+
|
| 3 |
+
[*]
|
| 4 |
+
charset = utf-8
|
| 5 |
+
end_of_line = lf
|
| 6 |
+
indent_size = 4
|
| 7 |
+
indent_style = space
|
| 8 |
+
insert_final_newline = true
|
| 9 |
+
trim_trailing_whitespace = true
|
| 10 |
+
|
| 11 |
+
[*.md]
|
| 12 |
+
trim_trailing_whitespace = false
|
| 13 |
+
|
| 14 |
+
[*.{yml,yaml}]
|
| 15 |
+
indent_size = 2
|
| 16 |
+
|
| 17 |
+
[compose.yaml]
|
| 18 |
+
indent_size = 4
|
.env
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
APP_NAME=AILIXIR
|
| 2 |
+
APP_ENV=production
|
| 3 |
+
APP_DEBUG=false
|
| 4 |
+
APP_URL=https://RottenShadow-ailixir-api.hf.space
|
| 5 |
+
LOG_CHANNEL=stderr
|
| 6 |
+
LOG_LEVEL=info
|
| 7 |
+
DB_CONNECTION=sqlite
|
| 8 |
+
DB_DATABASE=/var/www/html/database/ailixir.sqlite
|
| 9 |
+
SESSION_DRIVER=database
|
| 10 |
+
SESSION_LIFETIME=120
|
| 11 |
+
CACHE_STORE=database
|
| 12 |
+
QUEUE_CONNECTION=database
|
| 13 |
+
AI_INTEGRATION_ROUTES_ENABLED=true
|
| 14 |
+
MAIL_MAILER=log
|
| 15 |
+
CLOUDINARY_URL=cloudinary://dummy_key:dummy_secret@dummy_cloud
|
| 16 |
+
CLOUDINARY_CLOUD_NAME=dummy_cloud
|
| 17 |
+
CLOUDINARY_API_KEY=dummy_key
|
| 18 |
+
CLOUDINARY_API_SECRET=dummy_secret
|
| 19 |
+
RUN_MODE=hf
|
.env.docker
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
APP_NAME=AILIXIR
|
| 2 |
+
APP_ENV=production
|
| 3 |
+
APP_KEY=base64:CHANGEME_GENERATE_ON_FIRST_BOOT=
|
| 4 |
+
APP_DEBUG=false
|
| 5 |
+
APP_URL=http://localhost:8080
|
| 6 |
+
|
| 7 |
+
LOG_CHANNEL=stderr
|
| 8 |
+
LOG_LEVEL=info
|
| 9 |
+
|
| 10 |
+
DB_CONNECTION=sqlite
|
| 11 |
+
DB_DATABASE=/var/www/html/database/ailixir.sqlite
|
| 12 |
+
|
| 13 |
+
SESSION_DRIVER=database
|
| 14 |
+
SESSION_LIFETIME=120
|
| 15 |
+
|
| 16 |
+
CACHE_STORE=database
|
| 17 |
+
QUEUE_CONNECTION=database
|
| 18 |
+
|
| 19 |
+
# AI microservices (Docker internal DNS)
|
| 20 |
+
GENERATION_SERVICE_URL=http://generation:8000
|
| 21 |
+
CHEMICAL_AI_URL=http://chemical-rag:5000
|
| 22 |
+
ADMET_AI_URL=http://admet:8000
|
| 23 |
+
AI_ADMET_SERVICE_URL=http://admet:8000
|
| 24 |
+
DRUG_REPURPOSING_URL=http://drug-repurposing:8000
|
| 25 |
+
AI_SERVICE_URL=http://drug-repurposing:8000
|
| 26 |
+
|
| 27 |
+
AI_INTEGRATION_ROUTES_ENABLED=true
|
| 28 |
+
|
| 29 |
+
MAIL_MAILER=log
|
.env.example
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AILIXIR API Test Configuration
|
| 2 |
+
# Copy this to .env and fill in your values
|
| 3 |
+
|
| 4 |
+
# API URLs
|
| 5 |
+
BASE_URL=https://america-hyperlipemic-grazyna.ngrok-free.dev/api
|
| 6 |
+
AUTH_BASE_URL=https://america-hyperlipemic-grazyna.ngrok-free.dev/api
|
| 7 |
+
|
| 8 |
+
# Test User Credentials
|
| 9 |
+
TEST_EMAIL=your_email@example.com
|
| 10 |
+
TEST_PASSWORD=your_password
|
| 11 |
+
TEST_NAME=Your Name
|
| 12 |
+
|
| 13 |
+
# Database (SQLite)
|
| 14 |
+
DB_CONNECTION=sqlite
|
| 15 |
+
DB_DATABASE=database/ailixir.sqlite
|
.gitattributes
CHANGED
|
@@ -1,35 +1,18 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 3 |
-
*.
|
| 4 |
-
*.
|
| 5 |
-
*.
|
| 6 |
-
*.
|
| 7 |
-
*.
|
| 8 |
-
*.
|
| 9 |
-
*.
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.
|
| 24 |
-
*.
|
| 25 |
-
*.
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.
|
| 35 |
-
*
|
|
|
|
|
|
|
|
|
| 1 |
+
*.pdf filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ico filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.woff filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.ttf filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.eot filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.chkpt filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
'*.pt' filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
'*.pkl' filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
'*.chkpt' filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
'*.ckpt' filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/api-tests.yml
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: AILIXIR API Tests
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [main, develop]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [main]
|
| 8 |
+
schedule:
|
| 9 |
+
- cron: "0 6 * * *"
|
| 10 |
+
|
| 11 |
+
jobs:
|
| 12 |
+
test:
|
| 13 |
+
runs-on: ubuntu-latest
|
| 14 |
+
|
| 15 |
+
steps:
|
| 16 |
+
- name: Checkout code
|
| 17 |
+
uses: actions/checkout@v4
|
| 18 |
+
with:
|
| 19 |
+
lfs: true
|
| 20 |
+
|
| 21 |
+
- name: Pull and verify Git LFS files
|
| 22 |
+
run: |
|
| 23 |
+
git lfs version
|
| 24 |
+
git lfs pull
|
| 25 |
+
git lfs checkout
|
| 26 |
+
git lfs ls-files
|
| 27 |
+
|
| 28 |
+
python - <<'PY'
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
|
| 31 |
+
paths = [
|
| 32 |
+
"ai_apps/generation/bundle/models/affinity/config.pkl",
|
| 33 |
+
"ai_apps/generation/bundle/models/affinity/model.pt",
|
| 34 |
+
"ai_apps/generation/bundle/models/generator/egfr_generator.chkpt",
|
| 35 |
+
"ai_apps/generation/bundle/models/generator/reinvent.prior",
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
for p in paths:
|
| 39 |
+
path = Path(p)
|
| 40 |
+
if not path.exists():
|
| 41 |
+
raise SystemExit(f"Missing required artifact: {p}")
|
| 42 |
+
|
| 43 |
+
data = path.read_bytes()[:80]
|
| 44 |
+
size = path.stat().st_size
|
| 45 |
+
|
| 46 |
+
print(f"{p}: {size} bytes")
|
| 47 |
+
|
| 48 |
+
if data.startswith(b"version https://git-lfs.github.com"):
|
| 49 |
+
raise SystemExit(f"{p} is still a Git LFS pointer file")
|
| 50 |
+
PY
|
| 51 |
+
- name: Set up Docker Buildx
|
| 52 |
+
uses: docker/setup-buildx-action@v3
|
| 53 |
+
|
| 54 |
+
- name: Create .env file for Docker
|
| 55 |
+
run: |
|
| 56 |
+
cp docker/laravel.env .env
|
| 57 |
+
|
| 58 |
+
- name: Start Docker containers
|
| 59 |
+
run: |
|
| 60 |
+
docker compose up -d --build
|
| 61 |
+
|
| 62 |
+
- name: Wait for services to be healthy
|
| 63 |
+
run: |
|
| 64 |
+
echo "Waiting for Laravel API to be ready..."
|
| 65 |
+
for i in {1..60}; do
|
| 66 |
+
if docker exec ailixir-laravel curl -f http://127.0.0.1:8000 > /dev/null 2>&1; then
|
| 67 |
+
echo "Laravel API is ready"
|
| 68 |
+
break
|
| 69 |
+
fi
|
| 70 |
+
echo "Attempt $i/60: Laravel API not ready yet..."
|
| 71 |
+
sleep 3
|
| 72 |
+
done
|
| 73 |
+
|
| 74 |
+
echo "Waiting for ADMET service to be healthy..."
|
| 75 |
+
for i in {1..120}; do
|
| 76 |
+
if docker exec ailixir-admet python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" > /dev/null 2>&1; then
|
| 77 |
+
echo "ADMET service is healthy internally inside container"
|
| 78 |
+
echo "Waiting additional 10 seconds for service to fully initialize..."
|
| 79 |
+
sleep 10
|
| 80 |
+
break
|
| 81 |
+
fi
|
| 82 |
+
echo "Attempt $i/120: ADMET service not ready yet..."
|
| 83 |
+
sleep 5
|
| 84 |
+
done
|
| 85 |
+
|
| 86 |
+
echo "All services are up and running!"
|
| 87 |
+
docker compose ps
|
| 88 |
+
|
| 89 |
+
- name: Run database migrations
|
| 90 |
+
run: |
|
| 91 |
+
docker exec ailixir-laravel php artisan migrate --force
|
| 92 |
+
|
| 93 |
+
- name: Run API tests
|
| 94 |
+
env:
|
| 95 |
+
BASE_URL: http://localhost:8080/api
|
| 96 |
+
TEST_EMAIL: test@example.com
|
| 97 |
+
TEST_PASSWORD: password123
|
| 98 |
+
TEST_NAME: testuser
|
| 99 |
+
run: |
|
| 100 |
+
docker exec ailixir-laravel \
|
| 101 |
+
python -m pytest tests/ -v --tb=short --junitxml=junit.xml || true
|
| 102 |
+
|
| 103 |
+
- name: Copy test results from container
|
| 104 |
+
if: always()
|
| 105 |
+
run: |
|
| 106 |
+
docker cp ailixir-laravel:/var/www/html/junit.xml ./junit.xml || true
|
| 107 |
+
|
| 108 |
+
- name: Upload test results
|
| 109 |
+
uses: actions/upload-artifact@v4
|
| 110 |
+
if: always()
|
| 111 |
+
with:
|
| 112 |
+
name: test-results
|
| 113 |
+
path: junit.xml
|
| 114 |
+
|
| 115 |
+
- name: Test Summary
|
| 116 |
+
uses: test-summary/action@v2
|
| 117 |
+
with:
|
| 118 |
+
paths: "junit.xml"
|
| 119 |
+
if: always()
|
| 120 |
+
|
| 121 |
+
- name: Collect Docker logs
|
| 122 |
+
if: failure()
|
| 123 |
+
run: |
|
| 124 |
+
mkdir -p logs
|
| 125 |
+
docker compose logs laravel > logs/laravel.log
|
| 126 |
+
docker compose logs admet > logs/admet.log
|
| 127 |
+
docker compose logs drug-repurposing > logs/drug-repurposing.log
|
| 128 |
+
docker compose logs chemical-rag > logs/chemical-rag.log
|
| 129 |
+
|
| 130 |
+
- name: Upload Docker logs
|
| 131 |
+
uses: actions/upload-artifact@v4
|
| 132 |
+
if: failure()
|
| 133 |
+
with:
|
| 134 |
+
name: docker-logs
|
| 135 |
+
path: logs/
|
| 136 |
+
|
| 137 |
+
- name: Cleanup Docker containers
|
| 138 |
+
if: always()
|
| 139 |
+
run: |
|
| 140 |
+
docker compose down -v
|
.github/workflows/integration-test.yml
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Docker Integration Test
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [main, develop, "feature/**"]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [main, develop]
|
| 8 |
+
workflow_dispatch:
|
| 9 |
+
|
| 10 |
+
concurrency:
|
| 11 |
+
group: docker-integration-${{ github.ref }}
|
| 12 |
+
cancel-in-progress: true
|
| 13 |
+
|
| 14 |
+
jobs:
|
| 15 |
+
laravel-to-ai-services:
|
| 16 |
+
runs-on: ubuntu-latest
|
| 17 |
+
timeout-minutes: 90
|
| 18 |
+
|
| 19 |
+
steps:
|
| 20 |
+
- name: Checkout
|
| 21 |
+
uses: actions/checkout@v4
|
| 22 |
+
with:
|
| 23 |
+
lfs: true
|
| 24 |
+
- name: Pull and verify Git LFS files
|
| 25 |
+
run: |
|
| 26 |
+
git lfs version
|
| 27 |
+
git lfs pull
|
| 28 |
+
git lfs checkout
|
| 29 |
+
git lfs ls-files
|
| 30 |
+
|
| 31 |
+
python - <<'PY'
|
| 32 |
+
from pathlib import Path
|
| 33 |
+
|
| 34 |
+
paths = [
|
| 35 |
+
"ai_apps/generation/bundle/models/affinity/config.pkl",
|
| 36 |
+
"ai_apps/generation/bundle/models/affinity/model.pt",
|
| 37 |
+
"ai_apps/generation/bundle/models/generator/egfr_generator.chkpt",
|
| 38 |
+
"ai_apps/generation/bundle/models/generator/reinvent.prior",
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
for p in paths:
|
| 42 |
+
path = Path(p)
|
| 43 |
+
if not path.exists():
|
| 44 |
+
raise SystemExit(f"Missing required artifact: {p}")
|
| 45 |
+
|
| 46 |
+
data = path.read_bytes()[:80]
|
| 47 |
+
size = path.stat().st_size
|
| 48 |
+
|
| 49 |
+
print(f"{p}: {size} bytes")
|
| 50 |
+
|
| 51 |
+
if data.startswith(b"version https://git-lfs.github.com"):
|
| 52 |
+
raise SystemExit(f"{p} is still a Git LFS pointer file")
|
| 53 |
+
PY
|
| 54 |
+
- name: Set up Docker Buildx
|
| 55 |
+
uses: docker/setup-buildx-action@v3
|
| 56 |
+
|
| 57 |
+
- name: Build and start stack
|
| 58 |
+
run: |
|
| 59 |
+
# Clean up volumes from previous runs
|
| 60 |
+
docker compose down -v 2>/dev/null || true
|
| 61 |
+
|
| 62 |
+
docker builder prune -af || true
|
| 63 |
+
docker system df
|
| 64 |
+
|
| 65 |
+
COMPOSE_PARALLEL_LIMIT=1 docker compose build
|
| 66 |
+
docker system df
|
| 67 |
+
|
| 68 |
+
docker compose up -d --wait
|
| 69 |
+
|
| 70 |
+
# Run fresh migrations to ensure clean database state
|
| 71 |
+
docker compose exec -T laravel php artisan migrate:fresh --force
|
| 72 |
+
|
| 73 |
+
- name: Seed verified test user
|
| 74 |
+
run: |
|
| 75 |
+
echo "=== Creating verified test user via PHP script ==="
|
| 76 |
+
docker compose exec -T laravel php -r "
|
| 77 |
+
require 'vendor/autoload.php';
|
| 78 |
+
\$app = require_once 'bootstrap/app.php';
|
| 79 |
+
\$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
|
| 80 |
+
\$kernel->bootstrap();
|
| 81 |
+
|
| 82 |
+
use App\Models\User;
|
| 83 |
+
use Illuminate\Support\Facades\Hash;
|
| 84 |
+
|
| 85 |
+
\$user = User::firstOrCreate(
|
| 86 |
+
['email' => 'salehyasmeen080@gmail.com'],
|
| 87 |
+
[
|
| 88 |
+
'name' => 'Test User',
|
| 89 |
+
'password' => Hash::make('123456789'),
|
| 90 |
+
'role' => 'normal',
|
| 91 |
+
'is_verified' => true,
|
| 92 |
+
'email_verified_at' => now(),
|
| 93 |
+
'created_at' => now(),
|
| 94 |
+
'updated_at' => now()
|
| 95 |
+
]
|
| 96 |
+
);
|
| 97 |
+
echo 'User ID: ' . \$user->id . PHP_EOL;
|
| 98 |
+
echo 'Email: ' . \$user->email . PHP_EOL;
|
| 99 |
+
echo 'Verified: ' . (\$user->is_verified ? 'YES' : 'NO') . PHP_EOL;
|
| 100 |
+
"
|
| 101 |
+
|
| 102 |
+
- name: Login and get Bearer token
|
| 103 |
+
run: |
|
| 104 |
+
echo "POST /api/user/login"
|
| 105 |
+
HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST http://localhost:8080/api/user/login \
|
| 106 |
+
-H "Content-Type: application/json" \
|
| 107 |
+
-d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}')
|
| 108 |
+
echo "HTTP Status: $HTTP_CODE"
|
| 109 |
+
cat login.json
|
| 110 |
+
echo ""
|
| 111 |
+
|
| 112 |
+
if [ "$HTTP_CODE" != "200" ]; then
|
| 113 |
+
echo "Login failed with HTTP $HTTP_CODE"
|
| 114 |
+
docker compose logs laravel
|
| 115 |
+
exit 1
|
| 116 |
+
fi
|
| 117 |
+
|
| 118 |
+
python3 - <<'PY'
|
| 119 |
+
import json, sys
|
| 120 |
+
try:
|
| 121 |
+
data = json.load(open("login.json"))
|
| 122 |
+
except json.JSONDecodeError as e:
|
| 123 |
+
print("Invalid JSON response:", e)
|
| 124 |
+
with open("login.json") as f:
|
| 125 |
+
print("Raw response:", f.read())
|
| 126 |
+
sys.exit(1)
|
| 127 |
+
|
| 128 |
+
if not data.get("success"):
|
| 129 |
+
print("Login failed:", json.dumps(data, indent=2))
|
| 130 |
+
sys.exit(1)
|
| 131 |
+
|
| 132 |
+
token = data.get("data", {}).get("token")
|
| 133 |
+
if not token:
|
| 134 |
+
print("No token in response:", json.dumps(data, indent=2))
|
| 135 |
+
sys.exit(1)
|
| 136 |
+
|
| 137 |
+
with open("token.txt", "w") as f:
|
| 138 |
+
f.write(token)
|
| 139 |
+
print("Login OK - token saved")
|
| 140 |
+
PY
|
| 141 |
+
|
| 142 |
+
- name: Show service status
|
| 143 |
+
if: always()
|
| 144 |
+
run: docker compose ps -a
|
| 145 |
+
|
| 146 |
+
- name: Wait for Laravel API
|
| 147 |
+
run: |
|
| 148 |
+
for i in $(seq 1 60); do
|
| 149 |
+
if curl -sf http://localhost:8080/ > /dev/null 2>&1; then
|
| 150 |
+
echo "Laravel is up"
|
| 151 |
+
exit 0
|
| 152 |
+
fi
|
| 153 |
+
echo "Waiting for Laravel ($i/60)..."
|
| 154 |
+
sleep 5
|
| 155 |
+
done
|
| 156 |
+
docker compose logs laravel
|
| 157 |
+
exit 1
|
| 158 |
+
|
| 159 |
+
- name: Wait for ADMET Service to be Healthy
|
| 160 |
+
run: |
|
| 161 |
+
echo "Waiting for ADMET service to be healthy..."
|
| 162 |
+
for i in $(seq 1 120); do
|
| 163 |
+
if curl -sf http://localhost:8002/health > /dev/null 2>&1; then
|
| 164 |
+
echo "ADMET service is healthy and responding on port 8002"
|
| 165 |
+
echo "Waiting additional 10 seconds for service to fully initialize..."
|
| 166 |
+
sleep 10
|
| 167 |
+
exit 0
|
| 168 |
+
fi
|
| 169 |
+
if docker exec ailixir-admet python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" > /dev/null 2>&1; then
|
| 170 |
+
echo "ADMET service is healthy internally inside container"
|
| 171 |
+
echo "Waiting additional 10 seconds for service to fully initialize..."
|
| 172 |
+
sleep 10
|
| 173 |
+
exit 0
|
| 174 |
+
fi
|
| 175 |
+
echo "Attempt $i/120: ADMET service not ready yet..."
|
| 176 |
+
sleep 5
|
| 177 |
+
done
|
| 178 |
+
echo "ADMET service failed to become healthy in time"
|
| 179 |
+
docker compose logs admet
|
| 180 |
+
exit 1
|
| 181 |
+
|
| 182 |
+
- name: Test aggregated AI health via Laravel
|
| 183 |
+
run: |
|
| 184 |
+
TOKEN=$(cat token.txt)
|
| 185 |
+
echo "GET /api/ai-services/health"
|
| 186 |
+
curl -sf http://localhost:8080/api/ai-services/health \
|
| 187 |
+
-H "Authorization: Bearer $TOKEN" | tee health.json
|
| 188 |
+
python3 - <<'PY'
|
| 189 |
+
import json, sys
|
| 190 |
+
data = json.load(open("health.json"))
|
| 191 |
+
if not data.get("success"):
|
| 192 |
+
print("AI services health check failed:", json.dumps(data, indent=2))
|
| 193 |
+
sys.exit(1)
|
| 194 |
+
print("All AI services healthy via Laravel")
|
| 195 |
+
PY
|
| 196 |
+
|
| 197 |
+
- name: Test ADMET prediction via
|
| 198 |
+
run: |
|
| 199 |
+
TOKEN=$(cat token.txt)
|
| 200 |
+
echo "POST /api/admet/predict"
|
| 201 |
+
HTTP_CODE=$(curl -s -o admet.json -w "%{http_code}" -X POST http://localhost:8080/api/admet/predict \
|
| 202 |
+
-H "Authorization: Bearer $TOKEN" \
|
| 203 |
+
-H "Content-Type: application/json" \
|
| 204 |
+
-d '{"smiles": "c1ccccc1, CCO, CCC"}')
|
| 205 |
+
|
| 206 |
+
echo "HTTP Status: $HTTP_CODE"
|
| 207 |
+
cat admet.json
|
| 208 |
+
echo ""
|
| 209 |
+
|
| 210 |
+
if [ "$HTTP_CODE" != "200" ]; then
|
| 211 |
+
echo "ADMET prediction request failed with HTTP $HTTP_CODE"
|
| 212 |
+
docker compose logs laravel
|
| 213 |
+
exit 1
|
| 214 |
+
fi
|
| 215 |
+
|
| 216 |
+
python3 - <<'PY'
|
| 217 |
+
import json, sys
|
| 218 |
+
try:
|
| 219 |
+
data = json.load(open("admet.json"))
|
| 220 |
+
except json.JSONDecodeError as e:
|
| 221 |
+
print("Invalid JSON response:", e)
|
| 222 |
+
with open("admet.json") as f:
|
| 223 |
+
print("Raw response:", f.read())
|
| 224 |
+
sys.exit(1)
|
| 225 |
+
|
| 226 |
+
if not data.get("success"):
|
| 227 |
+
print("ADMET prediction test failed:", json.dumps(data, indent=2))
|
| 228 |
+
sys.exit(1)
|
| 229 |
+
|
| 230 |
+
results = data.get("data", {}).get("results", [])
|
| 231 |
+
if len(results) < 3:
|
| 232 |
+
print(f"Expected at least 3 results, got {len(results)}")
|
| 233 |
+
sys.exit(1)
|
| 234 |
+
|
| 235 |
+
print(f"ADMET prediction OK - {len(results)} compounds processed")
|
| 236 |
+
PY
|
| 237 |
+
|
| 238 |
+
- name: Test Chemical RAG proxy via Laravel
|
| 239 |
+
run: |
|
| 240 |
+
TOKEN=$(cat token.txt)
|
| 241 |
+
echo "POST /api/ai-services/test/chemical-search"
|
| 242 |
+
HTTP_CODE=$(curl -s -o chemical.json -w "%{http_code}" -X POST http://localhost:8080/api/ai-services/test/chemical-search \
|
| 243 |
+
-H "Authorization: Bearer $TOKEN" \
|
| 244 |
+
-H "Content-Type: application/json" \
|
| 245 |
+
-d '{"query": "malaria drug"}')
|
| 246 |
+
echo "HTTP Status: $HTTP_CODE"
|
| 247 |
+
cat chemical.json
|
| 248 |
+
echo ""
|
| 249 |
+
python3 - <<'PY'
|
| 250 |
+
import json, sys
|
| 251 |
+
data = json.load(open("chemical.json"))
|
| 252 |
+
if not data.get("success"):
|
| 253 |
+
print("Chemical RAG proxy test failed:", json.dumps(data, indent=2))
|
| 254 |
+
sys.exit(1)
|
| 255 |
+
print("Chemical RAG proxy OK")
|
| 256 |
+
PY
|
| 257 |
+
|
| 258 |
+
- name: Test Drug Repurposing proxy via Laravel
|
| 259 |
+
run: |
|
| 260 |
+
TOKEN=$(cat token.txt)
|
| 261 |
+
echo "GET /api/ai-services/test/drug-repurposing"
|
| 262 |
+
HTTP_CODE=$(curl -s -o drug.json -w "%{http_code}" http://localhost:8080/api/ai-services/test/drug-repurposing \
|
| 263 |
+
-H "Authorization: Bearer $TOKEN")
|
| 264 |
+
echo "HTTP Status: $HTTP_CODE"
|
| 265 |
+
cat drug.json
|
| 266 |
+
echo ""
|
| 267 |
+
python3 - <<'PY'
|
| 268 |
+
import json, sys
|
| 269 |
+
data = json.load(open("drug.json"))
|
| 270 |
+
if not data.get("success"):
|
| 271 |
+
print("Drug Repurposing proxy test failed:", json.dumps(data, indent=2))
|
| 272 |
+
sys.exit(1)
|
| 273 |
+
print("Drug Repurposing proxy OK")
|
| 274 |
+
PY
|
| 275 |
+
|
| 276 |
+
- name: Dump logs on failure
|
| 277 |
+
if: failure()
|
| 278 |
+
run: |
|
| 279 |
+
docker compose logs --no-color
|
| 280 |
+
|
| 281 |
+
- name: Tear down
|
| 282 |
+
if: always()
|
| 283 |
+
run: docker compose down -v
|
| 284 |
+
|
| 285 |
+
drug-repurposing-integration-test:
|
| 286 |
+
runs-on: ubuntu-latest
|
| 287 |
+
timeout-minutes: 45
|
| 288 |
+
|
| 289 |
+
steps:
|
| 290 |
+
- name: Checkout
|
| 291 |
+
uses: actions/checkout@v4
|
| 292 |
+
with:
|
| 293 |
+
lfs: true
|
| 294 |
+
- name: Set up Docker Buildx
|
| 295 |
+
uses: docker/setup-buildx-action@v3
|
| 296 |
+
|
| 297 |
+
- name: Build and start stack
|
| 298 |
+
|
| 299 |
+
run: |
|
| 300 |
+
docker compose down -v 2>/dev/null || true
|
| 301 |
+
docker builder prune -af || true
|
| 302 |
+
docker system df
|
| 303 |
+
|
| 304 |
+
COMPOSE_PARALLEL_LIMIT=1 docker compose build
|
| 305 |
+
docker system df
|
| 306 |
+
|
| 307 |
+
docker compose up -d --wait
|
| 308 |
+
|
| 309 |
+
- name: Verify drug-repurposing reachable from Laravel container
|
| 310 |
+
run: |
|
| 311 |
+
echo "=== Laravel → drug-repurposing DNS ==="
|
| 312 |
+
docker compose exec -T laravel getent hosts drug-repurposing
|
| 313 |
+
|
| 314 |
+
echo "=== Health check from Laravel container ==="
|
| 315 |
+
docker compose exec -T laravel curl -sf http://drug-repurposing:8000/health | tee /tmp/drug-repurposing-health.json
|
| 316 |
+
python3 - <<'PY'
|
| 317 |
+
import json, sys
|
| 318 |
+
data = json.load(open("/tmp/drug-repurposing-health.json"))
|
| 319 |
+
if data.get("status") != "healthy":
|
| 320 |
+
print("Expected healthy status:", data)
|
| 321 |
+
sys.exit(1)
|
| 322 |
+
print("drug-repurposing reachable from Laravel:", data.get("service"))
|
| 323 |
+
PY
|
| 324 |
+
|
| 325 |
+
echo "=== DRUG_REPURPOSING_URL in Laravel env ==="
|
| 326 |
+
docker compose exec -T laravel php -r 'echo "DRUG_REPURPOSING_URL=" . getenv("DRUG_REPURPOSING_URL") . PHP_EOL;'
|
| 327 |
+
|
| 328 |
+
- name: Run drug repurposing integration tests
|
| 329 |
+
run: |
|
| 330 |
+
# Match phpunit.xml: sync queue + sqlite so jobs run inside the test process
|
| 331 |
+
docker compose exec -T \
|
| 332 |
+
-e QUEUE_CONNECTION=sync \
|
| 333 |
+
laravel php artisan test --filter=DrugRepurposingIntegrationTest
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
- name: Dump logs on failure
|
| 337 |
+
if: failure()
|
| 338 |
+
run: |
|
| 339 |
+
docker compose logs --no-color laravel drug-repurposing queue
|
| 340 |
+
|
| 341 |
+
- name: Tear down
|
| 342 |
+
if: always()
|
| 343 |
+
run: docker compose down -v
|
| 344 |
+
|
| 345 |
+
docking-integration-test:
|
| 346 |
+
runs-on: ubuntu-latest
|
| 347 |
+
timeout-minutes: 45
|
| 348 |
+
|
| 349 |
+
steps:
|
| 350 |
+
- name: Checkout
|
| 351 |
+
uses: actions/checkout@v4
|
| 352 |
+
with:
|
| 353 |
+
lfs: true
|
| 354 |
+
- name: Set up Docker Buildx
|
| 355 |
+
uses: docker/setup-buildx-action@v3
|
| 356 |
+
|
| 357 |
+
- name: Build and start stack
|
| 358 |
+
run: |
|
| 359 |
+
docker compose down -v 2>/dev/null || true
|
| 360 |
+
docker compose build laravel
|
| 361 |
+
docker compose up -d laravel
|
| 362 |
+
|
| 363 |
+
- name: Verify RDKit, Vina, and Open Babel in container
|
| 364 |
+
run: |
|
| 365 |
+
echo "Verifying python3 import rdkit"
|
| 366 |
+
docker compose exec -T laravel python3 -c "import rdkit; print(rdkit.__version__)"
|
| 367 |
+
echo "Verifying vina --help"
|
| 368 |
+
docker compose exec -T laravel vina --help
|
| 369 |
+
echo "Verifying obabel -V"
|
| 370 |
+
docker compose exec -T laravel obabel -V
|
| 371 |
+
|
| 372 |
+
- name: Run Laravel Feature Tests
|
| 373 |
+
run: |
|
| 374 |
+
docker compose exec -T laravel php artisan test --filter=DockingSubmitTest
|
| 375 |
+
|
| 376 |
+
- name: Tear down
|
| 377 |
+
if: always()
|
| 378 |
+
run: docker compose down -v
|
| 379 |
+
|
| 380 |
+
rag-integration-test:
|
| 381 |
+
runs-on: ubuntu-latest
|
| 382 |
+
timeout-minutes: 90
|
| 383 |
+
|
| 384 |
+
steps:
|
| 385 |
+
- name: Checkout
|
| 386 |
+
uses: actions/checkout@v4
|
| 387 |
+
with:
|
| 388 |
+
lfs: true
|
| 389 |
+
- name: Set up Docker Buildx
|
| 390 |
+
uses: docker/setup-buildx-action@v3
|
| 391 |
+
|
| 392 |
+
- name: Build and start stack
|
| 393 |
+
run: |
|
| 394 |
+
docker compose down -v 2>/dev/null || true
|
| 395 |
+
docker builder prune -af || true
|
| 396 |
+
docker system df
|
| 397 |
+
|
| 398 |
+
COMPOSE_PARALLEL_LIMIT=1 docker compose build
|
| 399 |
+
docker system df
|
| 400 |
+
|
| 401 |
+
docker compose up -d --wait
|
| 402 |
+
|
| 403 |
+
# Run fresh migrations to ensure clean database state
|
| 404 |
+
docker compose exec -T laravel php artisan migrate:fresh --force
|
| 405 |
+
|
| 406 |
+
- name: Seed verified test user
|
| 407 |
+
run: |
|
| 408 |
+
echo "=== Creating verified test user via PHP script ==="
|
| 409 |
+
docker compose exec -T laravel php -r "
|
| 410 |
+
require 'vendor/autoload.php';
|
| 411 |
+
\$app = require_once 'bootstrap/app.php';
|
| 412 |
+
\$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
|
| 413 |
+
\$kernel->bootstrap();
|
| 414 |
+
|
| 415 |
+
use App\Models\User;
|
| 416 |
+
use Illuminate\Support\Facades\Hash;
|
| 417 |
+
|
| 418 |
+
\$user = User::firstOrCreate(
|
| 419 |
+
['email' => 'salehyasmeen080@gmail.com'],
|
| 420 |
+
[
|
| 421 |
+
'name' => 'Test User',
|
| 422 |
+
'password' => Hash::make('123456789'),
|
| 423 |
+
'role' => 'normal',
|
| 424 |
+
'is_verified' => true,
|
| 425 |
+
'email_verified_at' => now(),
|
| 426 |
+
'created_at' => now(),
|
| 427 |
+
'updated_at' => now()
|
| 428 |
+
]
|
| 429 |
+
);
|
| 430 |
+
echo 'User ID: ' . \$user->id . PHP_EOL;
|
| 431 |
+
echo 'Email: ' . \$user->email . PHP_EOL;
|
| 432 |
+
echo 'Verified: ' . (\$user->is_verified ? 'YES' : 'NO') . PHP_EOL;
|
| 433 |
+
"
|
| 434 |
+
|
| 435 |
+
- name: Login and get Bearer token
|
| 436 |
+
run: |
|
| 437 |
+
echo "POST /api/user/login"
|
| 438 |
+
HTTP_CODE=$(curl -s -o login.json -w "%{http_code}" -X POST http://localhost:8080/api/user/login \
|
| 439 |
+
-H "Content-Type: application/json" \
|
| 440 |
+
-d '{"email":"salehyasmeen080@gmail.com","password":"123456789"}')
|
| 441 |
+
echo "HTTP Status: $HTTP_CODE"
|
| 442 |
+
cat login.json
|
| 443 |
+
echo ""
|
| 444 |
+
|
| 445 |
+
if [ "$HTTP_CODE" != "200" ]; then
|
| 446 |
+
echo "Login failed with HTTP $HTTP_CODE"
|
| 447 |
+
docker compose logs laravel
|
| 448 |
+
exit 1
|
| 449 |
+
fi
|
| 450 |
+
|
| 451 |
+
python3 - <<'PY'
|
| 452 |
+
import json, sys
|
| 453 |
+
try:
|
| 454 |
+
data = json.load(open("login.json"))
|
| 455 |
+
except json.JSONDecodeError as e:
|
| 456 |
+
print("Invalid JSON response:", e)
|
| 457 |
+
with open("login.json") as f:
|
| 458 |
+
print("Raw response:", f.read())
|
| 459 |
+
sys.exit(1)
|
| 460 |
+
|
| 461 |
+
if not data.get("success"):
|
| 462 |
+
print("Login failed:", json.dumps(data, indent=2))
|
| 463 |
+
sys.exit(1)
|
| 464 |
+
|
| 465 |
+
token = data.get("data", {}).get("token")
|
| 466 |
+
if not token:
|
| 467 |
+
print("No token in response:", json.dumps(data, indent=2))
|
| 468 |
+
sys.exit(1)
|
| 469 |
+
|
| 470 |
+
with open("token.txt", "w") as f:
|
| 471 |
+
f.write(token)
|
| 472 |
+
print("Login OK - token saved")
|
| 473 |
+
PY
|
| 474 |
+
|
| 475 |
+
- name: Show service status
|
| 476 |
+
if: always()
|
| 477 |
+
run: docker compose ps -a
|
| 478 |
+
|
| 479 |
+
- name: Wait for Laravel API
|
| 480 |
+
run: |
|
| 481 |
+
for i in $(seq 1 60); do
|
| 482 |
+
if curl -sf http://localhost:8080/ > /dev/null 2>&1; then
|
| 483 |
+
echo "Laravel is up"
|
| 484 |
+
exit 0
|
| 485 |
+
fi
|
| 486 |
+
echo "Waiting for Laravel ($i/60)..."
|
| 487 |
+
sleep 5
|
| 488 |
+
done
|
| 489 |
+
docker compose logs laravel
|
| 490 |
+
exit 1
|
| 491 |
+
|
| 492 |
+
- name: Test retrieval-only chemical search
|
| 493 |
+
run: |
|
| 494 |
+
TOKEN=$(cat token.txt)
|
| 495 |
+
echo "POST /api/chemical-search (retrieval-only)"
|
| 496 |
+
HTTP_CODE=$(curl -s -o chemical_search.json -w "%{http_code}" -X POST http://localhost:8080/api/chemical-search \
|
| 497 |
+
-H "Authorization: Bearer $TOKEN" \
|
| 498 |
+
-H "Content-Type: application/json" \
|
| 499 |
+
-d '{"smiles":"CC(=O)O"}')
|
| 500 |
+
echo "HTTP Status: $HTTP_CODE"
|
| 501 |
+
cat chemical_search.json
|
| 502 |
+
echo ""
|
| 503 |
+
python3 - <<'PY'
|
| 504 |
+
import json, sys
|
| 505 |
+
data = json.load(open("chemical_search.json"))
|
| 506 |
+
if not data.get("success"):
|
| 507 |
+
print("Chemical search (retrieval-only) failed:", json.dumps(data, indent=2))
|
| 508 |
+
sys.exit(1)
|
| 509 |
+
if not isinstance(data.get("compounds"), list):
|
| 510 |
+
print("Missing 'compounds' array in response")
|
| 511 |
+
sys.exit(1)
|
| 512 |
+
if data.get("metadata", {}).get("source") != "retrieval":
|
| 513 |
+
print("Expected source='retrieval', got:", data.get("metadata", {}).get("source"))
|
| 514 |
+
sys.exit(1)
|
| 515 |
+
print("Chemical search (retrieval-only) OK - found", len(data["compounds"]), "compounds")
|
| 516 |
+
PY
|
| 517 |
+
|
| 518 |
+
- name: Test full RAG chemical search
|
| 519 |
+
run: |
|
| 520 |
+
TOKEN=$(cat token.txt)
|
| 521 |
+
echo "POST /api/chemical-search/full-rag"
|
| 522 |
+
HTTP_CODE=$(curl -s -o chemical_rag.json -w "%{http_code}" -X POST http://localhost:8080/api/chemical-search/full-rag \
|
| 523 |
+
-H "Authorization: Bearer $TOKEN" \
|
| 524 |
+
-H "Content-Type: application/json" \
|
| 525 |
+
-d '{"smiles":"CC(=O)O"}')
|
| 526 |
+
echo "HTTP Status: $HTTP_CODE"
|
| 527 |
+
cat chemical_rag.json
|
| 528 |
+
echo ""
|
| 529 |
+
python3 - <<'PY'
|
| 530 |
+
import json, sys
|
| 531 |
+
data = json.load(open("chemical_rag.json"))
|
| 532 |
+
if not data.get("success"):
|
| 533 |
+
print("Chemical search (full RAG) failed:", json.dumps(data, indent=2))
|
| 534 |
+
sys.exit(1)
|
| 535 |
+
if not isinstance(data.get("compounds"), list):
|
| 536 |
+
print("Missing 'compounds' array in response")
|
| 537 |
+
sys.exit(1)
|
| 538 |
+
if data.get("metadata", {}).get("source") != "full_rag":
|
| 539 |
+
print("Expected source='full_rag', got:", data.get("metadata", {}).get("source"))
|
| 540 |
+
sys.exit(1)
|
| 541 |
+
# Verify explanations exist for full RAG
|
| 542 |
+
for compound in data["compounds"]:
|
| 543 |
+
if not compound.get("explanation"):
|
| 544 |
+
print("Missing explanation for compound:", compound.get("name"))
|
| 545 |
+
sys.exit(1)
|
| 546 |
+
print("Chemical search (full RAG) OK - found", len(data["compounds"]), "compounds with explanations")
|
| 547 |
+
PY
|
| 548 |
+
|
| 549 |
+
- name: Dump logs on failure
|
| 550 |
+
if: failure()
|
| 551 |
+
run: |
|
| 552 |
+
docker compose logs --no-color
|
| 553 |
+
|
| 554 |
+
- name: Tear down
|
| 555 |
+
if: always()
|
| 556 |
+
run: docker compose down -v
|
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
.env.local
|
| 4 |
+
*.sqlite
|
| 5 |
+
*.sqlite-wal
|
| 6 |
+
*.sqlite-shm
|
| 7 |
+
*.pt
|
| 8 |
+
*.pkl
|
| 9 |
+
*.chkpt
|
| 10 |
+
*.ckpt
|
.scribe/.filehashes
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE.
|
| 2 |
+
# Scribe uses this file to know when you change something manually in your docs.
|
| 3 |
+
.scribe/intro.md=ac7eb5bb370d74c7b07631610e041687
|
| 4 |
+
.scribe/auth.md=9bee2b1ef8a238b2e58613fa636d5f39
|
.scribe/auth.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Authenticating requests
|
| 2 |
+
|
| 3 |
+
This API is not authenticated.
|
.scribe/endpoints.cache/00.yaml
ADDED
|
@@ -0,0 +1,1503 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Autogenerated by Scribe. DO NOT MODIFY.
|
| 2 |
+
|
| 3 |
+
name: Endpoints
|
| 4 |
+
description: ''
|
| 5 |
+
endpoints:
|
| 6 |
+
-
|
| 7 |
+
custom: []
|
| 8 |
+
httpMethods:
|
| 9 |
+
- GET
|
| 10 |
+
uri: api/awards
|
| 11 |
+
metadata:
|
| 12 |
+
custom: []
|
| 13 |
+
groupName: Endpoints
|
| 14 |
+
groupDescription: ''
|
| 15 |
+
subgroup: ''
|
| 16 |
+
subgroupDescription: ''
|
| 17 |
+
title: ''
|
| 18 |
+
description: ''
|
| 19 |
+
authenticated: false
|
| 20 |
+
deprecated: false
|
| 21 |
+
headers:
|
| 22 |
+
Content-Type: application/json
|
| 23 |
+
Accept: application/json
|
| 24 |
+
urlParameters: []
|
| 25 |
+
cleanUrlParameters: []
|
| 26 |
+
queryParameters: []
|
| 27 |
+
cleanQueryParameters: []
|
| 28 |
+
bodyParameters: []
|
| 29 |
+
cleanBodyParameters: []
|
| 30 |
+
fileParameters: []
|
| 31 |
+
responses:
|
| 32 |
+
-
|
| 33 |
+
custom: []
|
| 34 |
+
status: 404
|
| 35 |
+
content: '{"success":false,"message":"The route api\/awards could not be found."}'
|
| 36 |
+
headers:
|
| 37 |
+
cache-control: 'no-cache, private'
|
| 38 |
+
content-type: application/json
|
| 39 |
+
access-control-allow-origin: '*'
|
| 40 |
+
description: null
|
| 41 |
+
responseFields: []
|
| 42 |
+
auth: []
|
| 43 |
+
controller: null
|
| 44 |
+
method: null
|
| 45 |
+
route: null
|
| 46 |
+
-
|
| 47 |
+
custom: []
|
| 48 |
+
httpMethods:
|
| 49 |
+
- GET
|
| 50 |
+
uri: 'api/awards/{id}'
|
| 51 |
+
metadata:
|
| 52 |
+
custom: []
|
| 53 |
+
groupName: Endpoints
|
| 54 |
+
groupDescription: ''
|
| 55 |
+
subgroup: ''
|
| 56 |
+
subgroupDescription: ''
|
| 57 |
+
title: ''
|
| 58 |
+
description: ''
|
| 59 |
+
authenticated: false
|
| 60 |
+
deprecated: false
|
| 61 |
+
headers:
|
| 62 |
+
Content-Type: application/json
|
| 63 |
+
Accept: application/json
|
| 64 |
+
urlParameters:
|
| 65 |
+
id:
|
| 66 |
+
custom: []
|
| 67 |
+
name: id
|
| 68 |
+
description: 'The ID of the award.'
|
| 69 |
+
required: true
|
| 70 |
+
example: consequatur
|
| 71 |
+
type: string
|
| 72 |
+
enumValues: []
|
| 73 |
+
exampleWasSpecified: false
|
| 74 |
+
nullable: false
|
| 75 |
+
deprecated: false
|
| 76 |
+
cleanUrlParameters:
|
| 77 |
+
id: consequatur
|
| 78 |
+
queryParameters: []
|
| 79 |
+
cleanQueryParameters: []
|
| 80 |
+
bodyParameters: []
|
| 81 |
+
cleanBodyParameters: []
|
| 82 |
+
fileParameters: []
|
| 83 |
+
responses:
|
| 84 |
+
-
|
| 85 |
+
custom: []
|
| 86 |
+
status: 404
|
| 87 |
+
content: '{"success":false,"message":"The route api\/awards\/consequatur could not be found."}'
|
| 88 |
+
headers:
|
| 89 |
+
cache-control: 'no-cache, private'
|
| 90 |
+
content-type: application/json
|
| 91 |
+
access-control-allow-origin: '*'
|
| 92 |
+
description: null
|
| 93 |
+
responseFields: []
|
| 94 |
+
auth: []
|
| 95 |
+
controller: null
|
| 96 |
+
method: null
|
| 97 |
+
route: null
|
| 98 |
+
-
|
| 99 |
+
custom: []
|
| 100 |
+
httpMethods:
|
| 101 |
+
- GET
|
| 102 |
+
uri: 'api/awards/{id}/scientists'
|
| 103 |
+
metadata:
|
| 104 |
+
custom: []
|
| 105 |
+
groupName: Endpoints
|
| 106 |
+
groupDescription: ''
|
| 107 |
+
subgroup: ''
|
| 108 |
+
subgroupDescription: ''
|
| 109 |
+
title: ''
|
| 110 |
+
description: ''
|
| 111 |
+
authenticated: false
|
| 112 |
+
deprecated: false
|
| 113 |
+
headers:
|
| 114 |
+
Content-Type: application/json
|
| 115 |
+
Accept: application/json
|
| 116 |
+
urlParameters:
|
| 117 |
+
id:
|
| 118 |
+
custom: []
|
| 119 |
+
name: id
|
| 120 |
+
description: 'The ID of the award.'
|
| 121 |
+
required: true
|
| 122 |
+
example: consequatur
|
| 123 |
+
type: string
|
| 124 |
+
enumValues: []
|
| 125 |
+
exampleWasSpecified: false
|
| 126 |
+
nullable: false
|
| 127 |
+
deprecated: false
|
| 128 |
+
cleanUrlParameters:
|
| 129 |
+
id: consequatur
|
| 130 |
+
queryParameters: []
|
| 131 |
+
cleanQueryParameters: []
|
| 132 |
+
bodyParameters: []
|
| 133 |
+
cleanBodyParameters: []
|
| 134 |
+
fileParameters: []
|
| 135 |
+
responses:
|
| 136 |
+
-
|
| 137 |
+
custom: []
|
| 138 |
+
status: 404
|
| 139 |
+
content: '{"success":false,"message":"The route api\/awards\/consequatur\/scientists could not be found."}'
|
| 140 |
+
headers:
|
| 141 |
+
cache-control: 'no-cache, private'
|
| 142 |
+
content-type: application/json
|
| 143 |
+
access-control-allow-origin: '*'
|
| 144 |
+
description: null
|
| 145 |
+
responseFields: []
|
| 146 |
+
auth: []
|
| 147 |
+
controller: null
|
| 148 |
+
method: null
|
| 149 |
+
route: null
|
| 150 |
+
-
|
| 151 |
+
custom: []
|
| 152 |
+
httpMethods:
|
| 153 |
+
- GET
|
| 154 |
+
uri: api/scientists
|
| 155 |
+
metadata:
|
| 156 |
+
custom: []
|
| 157 |
+
groupName: Endpoints
|
| 158 |
+
groupDescription: ''
|
| 159 |
+
subgroup: ''
|
| 160 |
+
subgroupDescription: ''
|
| 161 |
+
title: ''
|
| 162 |
+
description: ''
|
| 163 |
+
authenticated: false
|
| 164 |
+
deprecated: false
|
| 165 |
+
headers:
|
| 166 |
+
Content-Type: application/json
|
| 167 |
+
Accept: application/json
|
| 168 |
+
urlParameters: []
|
| 169 |
+
cleanUrlParameters: []
|
| 170 |
+
queryParameters: []
|
| 171 |
+
cleanQueryParameters: []
|
| 172 |
+
bodyParameters: []
|
| 173 |
+
cleanBodyParameters: []
|
| 174 |
+
fileParameters: []
|
| 175 |
+
responses:
|
| 176 |
+
-
|
| 177 |
+
custom: []
|
| 178 |
+
status: 404
|
| 179 |
+
content: '{"success":false,"message":"The route api\/scientists could not be found."}'
|
| 180 |
+
headers:
|
| 181 |
+
cache-control: 'no-cache, private'
|
| 182 |
+
content-type: application/json
|
| 183 |
+
access-control-allow-origin: '*'
|
| 184 |
+
description: null
|
| 185 |
+
responseFields: []
|
| 186 |
+
auth: []
|
| 187 |
+
controller: null
|
| 188 |
+
method: null
|
| 189 |
+
route: null
|
| 190 |
+
-
|
| 191 |
+
custom: []
|
| 192 |
+
httpMethods:
|
| 193 |
+
- GET
|
| 194 |
+
uri: 'api/scientists/{id}'
|
| 195 |
+
metadata:
|
| 196 |
+
custom: []
|
| 197 |
+
groupName: Endpoints
|
| 198 |
+
groupDescription: ''
|
| 199 |
+
subgroup: ''
|
| 200 |
+
subgroupDescription: ''
|
| 201 |
+
title: ''
|
| 202 |
+
description: ''
|
| 203 |
+
authenticated: false
|
| 204 |
+
deprecated: false
|
| 205 |
+
headers:
|
| 206 |
+
Content-Type: application/json
|
| 207 |
+
Accept: application/json
|
| 208 |
+
urlParameters:
|
| 209 |
+
id:
|
| 210 |
+
custom: []
|
| 211 |
+
name: id
|
| 212 |
+
description: 'The ID of the scientist.'
|
| 213 |
+
required: true
|
| 214 |
+
example: consequatur
|
| 215 |
+
type: string
|
| 216 |
+
enumValues: []
|
| 217 |
+
exampleWasSpecified: false
|
| 218 |
+
nullable: false
|
| 219 |
+
deprecated: false
|
| 220 |
+
cleanUrlParameters:
|
| 221 |
+
id: consequatur
|
| 222 |
+
queryParameters: []
|
| 223 |
+
cleanQueryParameters: []
|
| 224 |
+
bodyParameters: []
|
| 225 |
+
cleanBodyParameters: []
|
| 226 |
+
fileParameters: []
|
| 227 |
+
responses:
|
| 228 |
+
-
|
| 229 |
+
custom: []
|
| 230 |
+
status: 404
|
| 231 |
+
content: '{"success":false,"message":"The route api\/scientists\/consequatur could not be found."}'
|
| 232 |
+
headers:
|
| 233 |
+
cache-control: 'no-cache, private'
|
| 234 |
+
content-type: application/json
|
| 235 |
+
access-control-allow-origin: '*'
|
| 236 |
+
description: null
|
| 237 |
+
responseFields: []
|
| 238 |
+
auth: []
|
| 239 |
+
controller: null
|
| 240 |
+
method: null
|
| 241 |
+
route: null
|
| 242 |
+
-
|
| 243 |
+
custom: []
|
| 244 |
+
httpMethods:
|
| 245 |
+
- GET
|
| 246 |
+
uri: 'api/scientists/{id}/awards'
|
| 247 |
+
metadata:
|
| 248 |
+
custom: []
|
| 249 |
+
groupName: Endpoints
|
| 250 |
+
groupDescription: ''
|
| 251 |
+
subgroup: ''
|
| 252 |
+
subgroupDescription: ''
|
| 253 |
+
title: ''
|
| 254 |
+
description: ''
|
| 255 |
+
authenticated: false
|
| 256 |
+
deprecated: false
|
| 257 |
+
headers:
|
| 258 |
+
Content-Type: application/json
|
| 259 |
+
Accept: application/json
|
| 260 |
+
urlParameters:
|
| 261 |
+
id:
|
| 262 |
+
custom: []
|
| 263 |
+
name: id
|
| 264 |
+
description: 'The ID of the scientist.'
|
| 265 |
+
required: true
|
| 266 |
+
example: consequatur
|
| 267 |
+
type: string
|
| 268 |
+
enumValues: []
|
| 269 |
+
exampleWasSpecified: false
|
| 270 |
+
nullable: false
|
| 271 |
+
deprecated: false
|
| 272 |
+
cleanUrlParameters:
|
| 273 |
+
id: consequatur
|
| 274 |
+
queryParameters: []
|
| 275 |
+
cleanQueryParameters: []
|
| 276 |
+
bodyParameters: []
|
| 277 |
+
cleanBodyParameters: []
|
| 278 |
+
fileParameters: []
|
| 279 |
+
responses:
|
| 280 |
+
-
|
| 281 |
+
custom: []
|
| 282 |
+
status: 404
|
| 283 |
+
content: '{"success":false,"message":"The route api\/scientists\/consequatur\/awards could not be found."}'
|
| 284 |
+
headers:
|
| 285 |
+
cache-control: 'no-cache, private'
|
| 286 |
+
content-type: application/json
|
| 287 |
+
access-control-allow-origin: '*'
|
| 288 |
+
description: null
|
| 289 |
+
responseFields: []
|
| 290 |
+
auth: []
|
| 291 |
+
controller: null
|
| 292 |
+
method: null
|
| 293 |
+
route: null
|
| 294 |
+
-
|
| 295 |
+
custom: []
|
| 296 |
+
httpMethods:
|
| 297 |
+
- POST
|
| 298 |
+
uri: api/user/register
|
| 299 |
+
metadata:
|
| 300 |
+
custom: []
|
| 301 |
+
groupName: Endpoints
|
| 302 |
+
groupDescription: ''
|
| 303 |
+
subgroup: ''
|
| 304 |
+
subgroupDescription: ''
|
| 305 |
+
title: '---------------- Auth ----------------'
|
| 306 |
+
description: ''
|
| 307 |
+
authenticated: false
|
| 308 |
+
deprecated: false
|
| 309 |
+
headers:
|
| 310 |
+
Content-Type: application/json
|
| 311 |
+
Accept: application/json
|
| 312 |
+
urlParameters: []
|
| 313 |
+
cleanUrlParameters: []
|
| 314 |
+
queryParameters: []
|
| 315 |
+
cleanQueryParameters: []
|
| 316 |
+
bodyParameters:
|
| 317 |
+
name:
|
| 318 |
+
custom: []
|
| 319 |
+
name: name
|
| 320 |
+
description: 'Must not be greater than 255 characters.'
|
| 321 |
+
required: true
|
| 322 |
+
example: vmqeopfuudtdsufvyvddq
|
| 323 |
+
type: string
|
| 324 |
+
enumValues: []
|
| 325 |
+
exampleWasSpecified: false
|
| 326 |
+
nullable: false
|
| 327 |
+
deprecated: false
|
| 328 |
+
email:
|
| 329 |
+
custom: []
|
| 330 |
+
name: email
|
| 331 |
+
description: 'Must be a valid email address.'
|
| 332 |
+
required: true
|
| 333 |
+
example: kunde.eloisa@example.com
|
| 334 |
+
type: string
|
| 335 |
+
enumValues: []
|
| 336 |
+
exampleWasSpecified: false
|
| 337 |
+
nullable: false
|
| 338 |
+
deprecated: false
|
| 339 |
+
password:
|
| 340 |
+
custom: []
|
| 341 |
+
name: password
|
| 342 |
+
description: 'Must be at least 6 characters.'
|
| 343 |
+
required: true
|
| 344 |
+
example: '4[*UyPJ"}6'
|
| 345 |
+
type: string
|
| 346 |
+
enumValues: []
|
| 347 |
+
exampleWasSpecified: false
|
| 348 |
+
nullable: false
|
| 349 |
+
deprecated: false
|
| 350 |
+
cleanBodyParameters:
|
| 351 |
+
name: vmqeopfuudtdsufvyvddq
|
| 352 |
+
email: kunde.eloisa@example.com
|
| 353 |
+
password: '4[*UyPJ"}6'
|
| 354 |
+
fileParameters: []
|
| 355 |
+
responses: []
|
| 356 |
+
responseFields: []
|
| 357 |
+
auth: []
|
| 358 |
+
controller: null
|
| 359 |
+
method: null
|
| 360 |
+
route: null
|
| 361 |
+
-
|
| 362 |
+
custom: []
|
| 363 |
+
httpMethods:
|
| 364 |
+
- POST
|
| 365 |
+
uri: api/user/login
|
| 366 |
+
metadata:
|
| 367 |
+
custom: []
|
| 368 |
+
groupName: Endpoints
|
| 369 |
+
groupDescription: ''
|
| 370 |
+
subgroup: ''
|
| 371 |
+
subgroupDescription: ''
|
| 372 |
+
title: ''
|
| 373 |
+
description: ''
|
| 374 |
+
authenticated: false
|
| 375 |
+
deprecated: false
|
| 376 |
+
headers:
|
| 377 |
+
Content-Type: application/json
|
| 378 |
+
Accept: application/json
|
| 379 |
+
urlParameters: []
|
| 380 |
+
cleanUrlParameters: []
|
| 381 |
+
queryParameters: []
|
| 382 |
+
cleanQueryParameters: []
|
| 383 |
+
bodyParameters:
|
| 384 |
+
email:
|
| 385 |
+
custom: []
|
| 386 |
+
name: email
|
| 387 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 388 |
+
required: true
|
| 389 |
+
example: qkunze@example.com
|
| 390 |
+
type: string
|
| 391 |
+
enumValues: []
|
| 392 |
+
exampleWasSpecified: false
|
| 393 |
+
nullable: false
|
| 394 |
+
deprecated: false
|
| 395 |
+
password:
|
| 396 |
+
custom: []
|
| 397 |
+
name: password
|
| 398 |
+
description: ''
|
| 399 |
+
required: true
|
| 400 |
+
example: 'O[2UZ5ij-e/dl4m{o,'
|
| 401 |
+
type: string
|
| 402 |
+
enumValues: []
|
| 403 |
+
exampleWasSpecified: false
|
| 404 |
+
nullable: false
|
| 405 |
+
deprecated: false
|
| 406 |
+
cleanBodyParameters:
|
| 407 |
+
email: qkunze@example.com
|
| 408 |
+
password: 'O[2UZ5ij-e/dl4m{o,'
|
| 409 |
+
fileParameters: []
|
| 410 |
+
responses: []
|
| 411 |
+
responseFields: []
|
| 412 |
+
auth: []
|
| 413 |
+
controller: null
|
| 414 |
+
method: null
|
| 415 |
+
route: null
|
| 416 |
+
-
|
| 417 |
+
custom: []
|
| 418 |
+
httpMethods:
|
| 419 |
+
- POST
|
| 420 |
+
uri: api/user/verify-email
|
| 421 |
+
metadata:
|
| 422 |
+
custom: []
|
| 423 |
+
groupName: Endpoints
|
| 424 |
+
groupDescription: ''
|
| 425 |
+
subgroup: ''
|
| 426 |
+
subgroupDescription: ''
|
| 427 |
+
title: '---------------- Email Verification ----------------'
|
| 428 |
+
description: ''
|
| 429 |
+
authenticated: false
|
| 430 |
+
deprecated: false
|
| 431 |
+
headers:
|
| 432 |
+
Content-Type: application/json
|
| 433 |
+
Accept: application/json
|
| 434 |
+
urlParameters: []
|
| 435 |
+
cleanUrlParameters: []
|
| 436 |
+
queryParameters: []
|
| 437 |
+
cleanQueryParameters: []
|
| 438 |
+
bodyParameters:
|
| 439 |
+
email:
|
| 440 |
+
custom: []
|
| 441 |
+
name: email
|
| 442 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 443 |
+
required: true
|
| 444 |
+
example: qkunze@example.com
|
| 445 |
+
type: string
|
| 446 |
+
enumValues: []
|
| 447 |
+
exampleWasSpecified: false
|
| 448 |
+
nullable: false
|
| 449 |
+
deprecated: false
|
| 450 |
+
otp:
|
| 451 |
+
custom: []
|
| 452 |
+
name: otp
|
| 453 |
+
description: 'Must be 6 digits.'
|
| 454 |
+
required: true
|
| 455 |
+
example: '810798'
|
| 456 |
+
type: string
|
| 457 |
+
enumValues: []
|
| 458 |
+
exampleWasSpecified: false
|
| 459 |
+
nullable: false
|
| 460 |
+
deprecated: false
|
| 461 |
+
cleanBodyParameters:
|
| 462 |
+
email: qkunze@example.com
|
| 463 |
+
otp: '810798'
|
| 464 |
+
fileParameters: []
|
| 465 |
+
responses: []
|
| 466 |
+
responseFields: []
|
| 467 |
+
auth: []
|
| 468 |
+
controller: null
|
| 469 |
+
method: null
|
| 470 |
+
route: null
|
| 471 |
+
-
|
| 472 |
+
custom: []
|
| 473 |
+
httpMethods:
|
| 474 |
+
- POST
|
| 475 |
+
uri: api/user/resend-otp
|
| 476 |
+
metadata:
|
| 477 |
+
custom: []
|
| 478 |
+
groupName: Endpoints
|
| 479 |
+
groupDescription: ''
|
| 480 |
+
subgroup: ''
|
| 481 |
+
subgroupDescription: ''
|
| 482 |
+
title: ''
|
| 483 |
+
description: ''
|
| 484 |
+
authenticated: false
|
| 485 |
+
deprecated: false
|
| 486 |
+
headers:
|
| 487 |
+
Content-Type: application/json
|
| 488 |
+
Accept: application/json
|
| 489 |
+
urlParameters: []
|
| 490 |
+
cleanUrlParameters: []
|
| 491 |
+
queryParameters: []
|
| 492 |
+
cleanQueryParameters: []
|
| 493 |
+
bodyParameters:
|
| 494 |
+
email:
|
| 495 |
+
custom: []
|
| 496 |
+
name: email
|
| 497 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 498 |
+
required: true
|
| 499 |
+
example: qkunze@example.com
|
| 500 |
+
type: string
|
| 501 |
+
enumValues: []
|
| 502 |
+
exampleWasSpecified: false
|
| 503 |
+
nullable: false
|
| 504 |
+
deprecated: false
|
| 505 |
+
cleanBodyParameters:
|
| 506 |
+
email: qkunze@example.com
|
| 507 |
+
fileParameters: []
|
| 508 |
+
responses: []
|
| 509 |
+
responseFields: []
|
| 510 |
+
auth: []
|
| 511 |
+
controller: null
|
| 512 |
+
method: null
|
| 513 |
+
route: null
|
| 514 |
+
-
|
| 515 |
+
custom: []
|
| 516 |
+
httpMethods:
|
| 517 |
+
- POST
|
| 518 |
+
uri: api/user/forgot-password
|
| 519 |
+
metadata:
|
| 520 |
+
custom: []
|
| 521 |
+
groupName: Endpoints
|
| 522 |
+
groupDescription: ''
|
| 523 |
+
subgroup: ''
|
| 524 |
+
subgroupDescription: ''
|
| 525 |
+
title: '---------------- Password Reset ----------------'
|
| 526 |
+
description: ''
|
| 527 |
+
authenticated: false
|
| 528 |
+
deprecated: false
|
| 529 |
+
headers:
|
| 530 |
+
Content-Type: application/json
|
| 531 |
+
Accept: application/json
|
| 532 |
+
urlParameters: []
|
| 533 |
+
cleanUrlParameters: []
|
| 534 |
+
queryParameters: []
|
| 535 |
+
cleanQueryParameters: []
|
| 536 |
+
bodyParameters:
|
| 537 |
+
email:
|
| 538 |
+
custom: []
|
| 539 |
+
name: email
|
| 540 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 541 |
+
required: true
|
| 542 |
+
example: qkunze@example.com
|
| 543 |
+
type: string
|
| 544 |
+
enumValues: []
|
| 545 |
+
exampleWasSpecified: false
|
| 546 |
+
nullable: false
|
| 547 |
+
deprecated: false
|
| 548 |
+
cleanBodyParameters:
|
| 549 |
+
email: qkunze@example.com
|
| 550 |
+
fileParameters: []
|
| 551 |
+
responses: []
|
| 552 |
+
responseFields: []
|
| 553 |
+
auth: []
|
| 554 |
+
controller: null
|
| 555 |
+
method: null
|
| 556 |
+
route: null
|
| 557 |
+
-
|
| 558 |
+
custom: []
|
| 559 |
+
httpMethods:
|
| 560 |
+
- POST
|
| 561 |
+
uri: api/user/reset-password
|
| 562 |
+
metadata:
|
| 563 |
+
custom: []
|
| 564 |
+
groupName: Endpoints
|
| 565 |
+
groupDescription: ''
|
| 566 |
+
subgroup: ''
|
| 567 |
+
subgroupDescription: ''
|
| 568 |
+
title: ''
|
| 569 |
+
description: ''
|
| 570 |
+
authenticated: false
|
| 571 |
+
deprecated: false
|
| 572 |
+
headers:
|
| 573 |
+
Content-Type: application/json
|
| 574 |
+
Accept: application/json
|
| 575 |
+
urlParameters: []
|
| 576 |
+
cleanUrlParameters: []
|
| 577 |
+
queryParameters: []
|
| 578 |
+
cleanQueryParameters: []
|
| 579 |
+
bodyParameters:
|
| 580 |
+
email:
|
| 581 |
+
custom: []
|
| 582 |
+
name: email
|
| 583 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 584 |
+
required: true
|
| 585 |
+
example: qkunze@example.com
|
| 586 |
+
type: string
|
| 587 |
+
enumValues: []
|
| 588 |
+
exampleWasSpecified: false
|
| 589 |
+
nullable: false
|
| 590 |
+
deprecated: false
|
| 591 |
+
otp:
|
| 592 |
+
custom: []
|
| 593 |
+
name: otp
|
| 594 |
+
description: 'Must be 6 digits.'
|
| 595 |
+
required: true
|
| 596 |
+
example: '810798'
|
| 597 |
+
type: string
|
| 598 |
+
enumValues: []
|
| 599 |
+
exampleWasSpecified: false
|
| 600 |
+
nullable: false
|
| 601 |
+
deprecated: false
|
| 602 |
+
password:
|
| 603 |
+
custom: []
|
| 604 |
+
name: password
|
| 605 |
+
description: 'Must be at least 6 characters.'
|
| 606 |
+
required: true
|
| 607 |
+
example: '[2UZ5ij-e/dl4'
|
| 608 |
+
type: string
|
| 609 |
+
enumValues: []
|
| 610 |
+
exampleWasSpecified: false
|
| 611 |
+
nullable: false
|
| 612 |
+
deprecated: false
|
| 613 |
+
cleanBodyParameters:
|
| 614 |
+
email: qkunze@example.com
|
| 615 |
+
otp: '810798'
|
| 616 |
+
password: '[2UZ5ij-e/dl4'
|
| 617 |
+
fileParameters: []
|
| 618 |
+
responses: []
|
| 619 |
+
responseFields: []
|
| 620 |
+
auth: []
|
| 621 |
+
controller: null
|
| 622 |
+
method: null
|
| 623 |
+
route: null
|
| 624 |
+
-
|
| 625 |
+
custom: []
|
| 626 |
+
httpMethods:
|
| 627 |
+
- POST
|
| 628 |
+
uri: api/user/resend-reset-password-otp
|
| 629 |
+
metadata:
|
| 630 |
+
custom: []
|
| 631 |
+
groupName: Endpoints
|
| 632 |
+
groupDescription: ''
|
| 633 |
+
subgroup: ''
|
| 634 |
+
subgroupDescription: ''
|
| 635 |
+
title: ''
|
| 636 |
+
description: ''
|
| 637 |
+
authenticated: false
|
| 638 |
+
deprecated: false
|
| 639 |
+
headers:
|
| 640 |
+
Content-Type: application/json
|
| 641 |
+
Accept: application/json
|
| 642 |
+
urlParameters: []
|
| 643 |
+
cleanUrlParameters: []
|
| 644 |
+
queryParameters: []
|
| 645 |
+
cleanQueryParameters: []
|
| 646 |
+
bodyParameters:
|
| 647 |
+
email:
|
| 648 |
+
custom: []
|
| 649 |
+
name: email
|
| 650 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 651 |
+
required: true
|
| 652 |
+
example: qkunze@example.com
|
| 653 |
+
type: string
|
| 654 |
+
enumValues: []
|
| 655 |
+
exampleWasSpecified: false
|
| 656 |
+
nullable: false
|
| 657 |
+
deprecated: false
|
| 658 |
+
cleanBodyParameters:
|
| 659 |
+
email: qkunze@example.com
|
| 660 |
+
fileParameters: []
|
| 661 |
+
responses: []
|
| 662 |
+
responseFields: []
|
| 663 |
+
auth: []
|
| 664 |
+
controller: null
|
| 665 |
+
method: null
|
| 666 |
+
route: null
|
| 667 |
+
-
|
| 668 |
+
custom: []
|
| 669 |
+
httpMethods:
|
| 670 |
+
- POST
|
| 671 |
+
uri: api/user/auth/google
|
| 672 |
+
metadata:
|
| 673 |
+
custom: []
|
| 674 |
+
groupName: Endpoints
|
| 675 |
+
groupDescription: ''
|
| 676 |
+
subgroup: ''
|
| 677 |
+
subgroupDescription: ''
|
| 678 |
+
title: '---------------- Google OAuth ----------------'
|
| 679 |
+
description: ''
|
| 680 |
+
authenticated: false
|
| 681 |
+
deprecated: false
|
| 682 |
+
headers:
|
| 683 |
+
Content-Type: application/json
|
| 684 |
+
Accept: application/json
|
| 685 |
+
urlParameters: []
|
| 686 |
+
cleanUrlParameters: []
|
| 687 |
+
queryParameters: []
|
| 688 |
+
cleanQueryParameters: []
|
| 689 |
+
bodyParameters:
|
| 690 |
+
access_token:
|
| 691 |
+
custom: []
|
| 692 |
+
name: access_token
|
| 693 |
+
description: ''
|
| 694 |
+
required: true
|
| 695 |
+
example: consequatur
|
| 696 |
+
type: string
|
| 697 |
+
enumValues: []
|
| 698 |
+
exampleWasSpecified: false
|
| 699 |
+
nullable: false
|
| 700 |
+
deprecated: false
|
| 701 |
+
cleanBodyParameters:
|
| 702 |
+
access_token: consequatur
|
| 703 |
+
fileParameters: []
|
| 704 |
+
responses: []
|
| 705 |
+
responseFields: []
|
| 706 |
+
auth: []
|
| 707 |
+
controller: null
|
| 708 |
+
method: null
|
| 709 |
+
route: null
|
| 710 |
+
-
|
| 711 |
+
custom: []
|
| 712 |
+
httpMethods:
|
| 713 |
+
- GET
|
| 714 |
+
uri: api/user/profile
|
| 715 |
+
metadata:
|
| 716 |
+
custom: []
|
| 717 |
+
groupName: Endpoints
|
| 718 |
+
groupDescription: ''
|
| 719 |
+
subgroup: ''
|
| 720 |
+
subgroupDescription: ''
|
| 721 |
+
title: '---------------- Profile ----------------'
|
| 722 |
+
description: ''
|
| 723 |
+
authenticated: false
|
| 724 |
+
deprecated: false
|
| 725 |
+
headers:
|
| 726 |
+
Content-Type: application/json
|
| 727 |
+
Accept: application/json
|
| 728 |
+
urlParameters: []
|
| 729 |
+
cleanUrlParameters: []
|
| 730 |
+
queryParameters: []
|
| 731 |
+
cleanQueryParameters: []
|
| 732 |
+
bodyParameters: []
|
| 733 |
+
cleanBodyParameters: []
|
| 734 |
+
fileParameters: []
|
| 735 |
+
responses:
|
| 736 |
+
-
|
| 737 |
+
custom: []
|
| 738 |
+
status: 404
|
| 739 |
+
content: '{"success":false,"message":"The route api\/user\/profile could not be found."}'
|
| 740 |
+
headers:
|
| 741 |
+
cache-control: 'no-cache, private'
|
| 742 |
+
content-type: application/json
|
| 743 |
+
access-control-allow-origin: '*'
|
| 744 |
+
description: null
|
| 745 |
+
responseFields: []
|
| 746 |
+
auth: []
|
| 747 |
+
controller: null
|
| 748 |
+
method: null
|
| 749 |
+
route: null
|
| 750 |
+
-
|
| 751 |
+
custom: []
|
| 752 |
+
httpMethods:
|
| 753 |
+
- POST
|
| 754 |
+
uri: api/user/logout
|
| 755 |
+
metadata:
|
| 756 |
+
custom: []
|
| 757 |
+
groupName: Endpoints
|
| 758 |
+
groupDescription: ''
|
| 759 |
+
subgroup: ''
|
| 760 |
+
subgroupDescription: ''
|
| 761 |
+
title: ''
|
| 762 |
+
description: ''
|
| 763 |
+
authenticated: false
|
| 764 |
+
deprecated: false
|
| 765 |
+
headers:
|
| 766 |
+
Content-Type: application/json
|
| 767 |
+
Accept: application/json
|
| 768 |
+
urlParameters: []
|
| 769 |
+
cleanUrlParameters: []
|
| 770 |
+
queryParameters: []
|
| 771 |
+
cleanQueryParameters: []
|
| 772 |
+
bodyParameters: []
|
| 773 |
+
cleanBodyParameters: []
|
| 774 |
+
fileParameters: []
|
| 775 |
+
responses: []
|
| 776 |
+
responseFields: []
|
| 777 |
+
auth: []
|
| 778 |
+
controller: null
|
| 779 |
+
method: null
|
| 780 |
+
route: null
|
| 781 |
+
-
|
| 782 |
+
custom: []
|
| 783 |
+
httpMethods:
|
| 784 |
+
- GET
|
| 785 |
+
uri: api/news
|
| 786 |
+
metadata:
|
| 787 |
+
custom: []
|
| 788 |
+
groupName: Endpoints
|
| 789 |
+
groupDescription: ''
|
| 790 |
+
subgroup: ''
|
| 791 |
+
subgroupDescription: ''
|
| 792 |
+
title: ''
|
| 793 |
+
description: ''
|
| 794 |
+
authenticated: false
|
| 795 |
+
deprecated: false
|
| 796 |
+
headers:
|
| 797 |
+
Content-Type: application/json
|
| 798 |
+
Accept: application/json
|
| 799 |
+
urlParameters: []
|
| 800 |
+
cleanUrlParameters: []
|
| 801 |
+
queryParameters: []
|
| 802 |
+
cleanQueryParameters: []
|
| 803 |
+
bodyParameters: []
|
| 804 |
+
cleanBodyParameters: []
|
| 805 |
+
fileParameters: []
|
| 806 |
+
responses:
|
| 807 |
+
-
|
| 808 |
+
custom: []
|
| 809 |
+
status: 404
|
| 810 |
+
content: '{"success":false,"message":"The route api\/news could not be found."}'
|
| 811 |
+
headers:
|
| 812 |
+
cache-control: 'no-cache, private'
|
| 813 |
+
content-type: application/json
|
| 814 |
+
access-control-allow-origin: '*'
|
| 815 |
+
description: null
|
| 816 |
+
responseFields: []
|
| 817 |
+
auth: []
|
| 818 |
+
controller: null
|
| 819 |
+
method: null
|
| 820 |
+
route: null
|
| 821 |
+
-
|
| 822 |
+
custom: []
|
| 823 |
+
httpMethods:
|
| 824 |
+
- GET
|
| 825 |
+
uri: api/news/refresh
|
| 826 |
+
metadata:
|
| 827 |
+
custom: []
|
| 828 |
+
groupName: Endpoints
|
| 829 |
+
groupDescription: ''
|
| 830 |
+
subgroup: ''
|
| 831 |
+
subgroupDescription: ''
|
| 832 |
+
title: ''
|
| 833 |
+
description: ''
|
| 834 |
+
authenticated: false
|
| 835 |
+
deprecated: false
|
| 836 |
+
headers:
|
| 837 |
+
Content-Type: application/json
|
| 838 |
+
Accept: application/json
|
| 839 |
+
urlParameters: []
|
| 840 |
+
cleanUrlParameters: []
|
| 841 |
+
queryParameters: []
|
| 842 |
+
cleanQueryParameters: []
|
| 843 |
+
bodyParameters: []
|
| 844 |
+
cleanBodyParameters: []
|
| 845 |
+
fileParameters: []
|
| 846 |
+
responses:
|
| 847 |
+
-
|
| 848 |
+
custom: []
|
| 849 |
+
status: 404
|
| 850 |
+
content: '{"success":false,"message":"The route api\/news\/refresh could not be found."}'
|
| 851 |
+
headers:
|
| 852 |
+
cache-control: 'no-cache, private'
|
| 853 |
+
content-type: application/json
|
| 854 |
+
access-control-allow-origin: '*'
|
| 855 |
+
description: null
|
| 856 |
+
responseFields: []
|
| 857 |
+
auth: []
|
| 858 |
+
controller: null
|
| 859 |
+
method: null
|
| 860 |
+
route: null
|
| 861 |
+
-
|
| 862 |
+
custom: []
|
| 863 |
+
httpMethods:
|
| 864 |
+
- GET
|
| 865 |
+
uri: api/news/clear
|
| 866 |
+
metadata:
|
| 867 |
+
custom: []
|
| 868 |
+
groupName: Endpoints
|
| 869 |
+
groupDescription: ''
|
| 870 |
+
subgroup: ''
|
| 871 |
+
subgroupDescription: ''
|
| 872 |
+
title: ''
|
| 873 |
+
description: ''
|
| 874 |
+
authenticated: false
|
| 875 |
+
deprecated: false
|
| 876 |
+
headers:
|
| 877 |
+
Content-Type: application/json
|
| 878 |
+
Accept: application/json
|
| 879 |
+
urlParameters: []
|
| 880 |
+
cleanUrlParameters: []
|
| 881 |
+
queryParameters: []
|
| 882 |
+
cleanQueryParameters: []
|
| 883 |
+
bodyParameters: []
|
| 884 |
+
cleanBodyParameters: []
|
| 885 |
+
fileParameters: []
|
| 886 |
+
responses:
|
| 887 |
+
-
|
| 888 |
+
custom: []
|
| 889 |
+
status: 404
|
| 890 |
+
content: '{"success":false,"message":"The route api\/news\/clear could not be found."}'
|
| 891 |
+
headers:
|
| 892 |
+
cache-control: 'no-cache, private'
|
| 893 |
+
content-type: application/json
|
| 894 |
+
access-control-allow-origin: '*'
|
| 895 |
+
description: null
|
| 896 |
+
responseFields: []
|
| 897 |
+
auth: []
|
| 898 |
+
controller: null
|
| 899 |
+
method: null
|
| 900 |
+
route: null
|
| 901 |
+
-
|
| 902 |
+
custom: []
|
| 903 |
+
httpMethods:
|
| 904 |
+
- GET
|
| 905 |
+
uri: api/news/categories
|
| 906 |
+
metadata:
|
| 907 |
+
custom: []
|
| 908 |
+
groupName: Endpoints
|
| 909 |
+
groupDescription: ''
|
| 910 |
+
subgroup: ''
|
| 911 |
+
subgroupDescription: ''
|
| 912 |
+
title: ''
|
| 913 |
+
description: ''
|
| 914 |
+
authenticated: false
|
| 915 |
+
deprecated: false
|
| 916 |
+
headers:
|
| 917 |
+
Content-Type: application/json
|
| 918 |
+
Accept: application/json
|
| 919 |
+
urlParameters: []
|
| 920 |
+
cleanUrlParameters: []
|
| 921 |
+
queryParameters: []
|
| 922 |
+
cleanQueryParameters: []
|
| 923 |
+
bodyParameters: []
|
| 924 |
+
cleanBodyParameters: []
|
| 925 |
+
fileParameters: []
|
| 926 |
+
responses:
|
| 927 |
+
-
|
| 928 |
+
custom: []
|
| 929 |
+
status: 404
|
| 930 |
+
content: '{"success":false,"message":"The route api\/news\/categories could not be found."}'
|
| 931 |
+
headers:
|
| 932 |
+
cache-control: 'no-cache, private'
|
| 933 |
+
content-type: application/json
|
| 934 |
+
access-control-allow-origin: '*'
|
| 935 |
+
description: null
|
| 936 |
+
responseFields: []
|
| 937 |
+
auth: []
|
| 938 |
+
controller: null
|
| 939 |
+
method: null
|
| 940 |
+
route: null
|
| 941 |
+
-
|
| 942 |
+
custom: []
|
| 943 |
+
httpMethods:
|
| 944 |
+
- POST
|
| 945 |
+
uri: 'api/news/{articleId}/share'
|
| 946 |
+
metadata:
|
| 947 |
+
custom: []
|
| 948 |
+
groupName: Endpoints
|
| 949 |
+
groupDescription: ''
|
| 950 |
+
subgroup: ''
|
| 951 |
+
subgroupDescription: ''
|
| 952 |
+
title: ''
|
| 953 |
+
description: ''
|
| 954 |
+
authenticated: false
|
| 955 |
+
deprecated: false
|
| 956 |
+
headers:
|
| 957 |
+
Content-Type: application/json
|
| 958 |
+
Accept: application/json
|
| 959 |
+
urlParameters:
|
| 960 |
+
articleId:
|
| 961 |
+
custom: []
|
| 962 |
+
name: articleId
|
| 963 |
+
description: ''
|
| 964 |
+
required: true
|
| 965 |
+
example: consequatur
|
| 966 |
+
type: string
|
| 967 |
+
enumValues: []
|
| 968 |
+
exampleWasSpecified: false
|
| 969 |
+
nullable: false
|
| 970 |
+
deprecated: false
|
| 971 |
+
cleanUrlParameters:
|
| 972 |
+
articleId: consequatur
|
| 973 |
+
queryParameters: []
|
| 974 |
+
cleanQueryParameters: []
|
| 975 |
+
bodyParameters: []
|
| 976 |
+
cleanBodyParameters: []
|
| 977 |
+
fileParameters: []
|
| 978 |
+
responses: []
|
| 979 |
+
responseFields: []
|
| 980 |
+
auth: []
|
| 981 |
+
controller: null
|
| 982 |
+
method: null
|
| 983 |
+
route: null
|
| 984 |
+
-
|
| 985 |
+
custom: []
|
| 986 |
+
httpMethods:
|
| 987 |
+
- POST
|
| 988 |
+
uri: 'api/news/{articleId}/save'
|
| 989 |
+
metadata:
|
| 990 |
+
custom: []
|
| 991 |
+
groupName: Endpoints
|
| 992 |
+
groupDescription: ''
|
| 993 |
+
subgroup: ''
|
| 994 |
+
subgroupDescription: ''
|
| 995 |
+
title: ''
|
| 996 |
+
description: ''
|
| 997 |
+
authenticated: false
|
| 998 |
+
deprecated: false
|
| 999 |
+
headers:
|
| 1000 |
+
Content-Type: application/json
|
| 1001 |
+
Accept: application/json
|
| 1002 |
+
urlParameters:
|
| 1003 |
+
articleId:
|
| 1004 |
+
custom: []
|
| 1005 |
+
name: articleId
|
| 1006 |
+
description: ''
|
| 1007 |
+
required: true
|
| 1008 |
+
example: consequatur
|
| 1009 |
+
type: string
|
| 1010 |
+
enumValues: []
|
| 1011 |
+
exampleWasSpecified: false
|
| 1012 |
+
nullable: false
|
| 1013 |
+
deprecated: false
|
| 1014 |
+
cleanUrlParameters:
|
| 1015 |
+
articleId: consequatur
|
| 1016 |
+
queryParameters: []
|
| 1017 |
+
cleanQueryParameters: []
|
| 1018 |
+
bodyParameters: []
|
| 1019 |
+
cleanBodyParameters: []
|
| 1020 |
+
fileParameters: []
|
| 1021 |
+
responses: []
|
| 1022 |
+
responseFields: []
|
| 1023 |
+
auth: []
|
| 1024 |
+
controller: null
|
| 1025 |
+
method: null
|
| 1026 |
+
route: null
|
| 1027 |
+
-
|
| 1028 |
+
custom: []
|
| 1029 |
+
httpMethods:
|
| 1030 |
+
- GET
|
| 1031 |
+
uri: api/news/saved
|
| 1032 |
+
metadata:
|
| 1033 |
+
custom: []
|
| 1034 |
+
groupName: Endpoints
|
| 1035 |
+
groupDescription: ''
|
| 1036 |
+
subgroup: ''
|
| 1037 |
+
subgroupDescription: ''
|
| 1038 |
+
title: ''
|
| 1039 |
+
description: ''
|
| 1040 |
+
authenticated: false
|
| 1041 |
+
deprecated: false
|
| 1042 |
+
headers:
|
| 1043 |
+
Content-Type: application/json
|
| 1044 |
+
Accept: application/json
|
| 1045 |
+
urlParameters: []
|
| 1046 |
+
cleanUrlParameters: []
|
| 1047 |
+
queryParameters: []
|
| 1048 |
+
cleanQueryParameters: []
|
| 1049 |
+
bodyParameters: []
|
| 1050 |
+
cleanBodyParameters: []
|
| 1051 |
+
fileParameters: []
|
| 1052 |
+
responses:
|
| 1053 |
+
-
|
| 1054 |
+
custom: []
|
| 1055 |
+
status: 404
|
| 1056 |
+
content: '{"success":false,"message":"The route api\/news\/saved could not be found."}'
|
| 1057 |
+
headers:
|
| 1058 |
+
cache-control: 'no-cache, private'
|
| 1059 |
+
content-type: application/json
|
| 1060 |
+
access-control-allow-origin: '*'
|
| 1061 |
+
description: null
|
| 1062 |
+
responseFields: []
|
| 1063 |
+
auth: []
|
| 1064 |
+
controller: null
|
| 1065 |
+
method: null
|
| 1066 |
+
route: null
|
| 1067 |
+
-
|
| 1068 |
+
custom: []
|
| 1069 |
+
httpMethods:
|
| 1070 |
+
- DELETE
|
| 1071 |
+
uri: 'api/news/saved/{savedArticleId}'
|
| 1072 |
+
metadata:
|
| 1073 |
+
custom: []
|
| 1074 |
+
groupName: Endpoints
|
| 1075 |
+
groupDescription: ''
|
| 1076 |
+
subgroup: ''
|
| 1077 |
+
subgroupDescription: ''
|
| 1078 |
+
title: ''
|
| 1079 |
+
description: ''
|
| 1080 |
+
authenticated: false
|
| 1081 |
+
deprecated: false
|
| 1082 |
+
headers:
|
| 1083 |
+
Content-Type: application/json
|
| 1084 |
+
Accept: application/json
|
| 1085 |
+
urlParameters:
|
| 1086 |
+
savedArticleId:
|
| 1087 |
+
custom: []
|
| 1088 |
+
name: savedArticleId
|
| 1089 |
+
description: ''
|
| 1090 |
+
required: true
|
| 1091 |
+
example: consequatur
|
| 1092 |
+
type: string
|
| 1093 |
+
enumValues: []
|
| 1094 |
+
exampleWasSpecified: false
|
| 1095 |
+
nullable: false
|
| 1096 |
+
deprecated: false
|
| 1097 |
+
cleanUrlParameters:
|
| 1098 |
+
savedArticleId: consequatur
|
| 1099 |
+
queryParameters: []
|
| 1100 |
+
cleanQueryParameters: []
|
| 1101 |
+
bodyParameters: []
|
| 1102 |
+
cleanBodyParameters: []
|
| 1103 |
+
fileParameters: []
|
| 1104 |
+
responses: []
|
| 1105 |
+
responseFields: []
|
| 1106 |
+
auth: []
|
| 1107 |
+
controller: null
|
| 1108 |
+
method: null
|
| 1109 |
+
route: null
|
| 1110 |
+
-
|
| 1111 |
+
custom: []
|
| 1112 |
+
httpMethods:
|
| 1113 |
+
- POST
|
| 1114 |
+
uri: api/ai/run
|
| 1115 |
+
metadata:
|
| 1116 |
+
custom: []
|
| 1117 |
+
groupName: Endpoints
|
| 1118 |
+
groupDescription: ''
|
| 1119 |
+
subgroup: ''
|
| 1120 |
+
subgroupDescription: ''
|
| 1121 |
+
title: ''
|
| 1122 |
+
description: ''
|
| 1123 |
+
authenticated: false
|
| 1124 |
+
deprecated: false
|
| 1125 |
+
headers:
|
| 1126 |
+
Content-Type: application/json
|
| 1127 |
+
Accept: application/json
|
| 1128 |
+
urlParameters: []
|
| 1129 |
+
cleanUrlParameters: []
|
| 1130 |
+
queryParameters: []
|
| 1131 |
+
cleanQueryParameters: []
|
| 1132 |
+
bodyParameters:
|
| 1133 |
+
num_smiles:
|
| 1134 |
+
custom: []
|
| 1135 |
+
name: num_smiles
|
| 1136 |
+
description: ''
|
| 1137 |
+
required: true
|
| 1138 |
+
example: 17
|
| 1139 |
+
type: integer
|
| 1140 |
+
enumValues: []
|
| 1141 |
+
exampleWasSpecified: false
|
| 1142 |
+
nullable: false
|
| 1143 |
+
deprecated: false
|
| 1144 |
+
top_k:
|
| 1145 |
+
custom: []
|
| 1146 |
+
name: top_k
|
| 1147 |
+
description: ''
|
| 1148 |
+
required: true
|
| 1149 |
+
example: 17
|
| 1150 |
+
type: integer
|
| 1151 |
+
enumValues: []
|
| 1152 |
+
exampleWasSpecified: false
|
| 1153 |
+
nullable: false
|
| 1154 |
+
deprecated: false
|
| 1155 |
+
Docking:
|
| 1156 |
+
custom: []
|
| 1157 |
+
name: Docking
|
| 1158 |
+
description: ''
|
| 1159 |
+
required: true
|
| 1160 |
+
example: 11613.31890586
|
| 1161 |
+
type: number
|
| 1162 |
+
enumValues: []
|
| 1163 |
+
exampleWasSpecified: false
|
| 1164 |
+
nullable: false
|
| 1165 |
+
deprecated: false
|
| 1166 |
+
DeepPurpose_Affinity:
|
| 1167 |
+
custom: []
|
| 1168 |
+
name: DeepPurpose_Affinity
|
| 1169 |
+
description: ''
|
| 1170 |
+
required: true
|
| 1171 |
+
example: 11613.31890586
|
| 1172 |
+
type: number
|
| 1173 |
+
enumValues: []
|
| 1174 |
+
exampleWasSpecified: false
|
| 1175 |
+
nullable: false
|
| 1176 |
+
deprecated: false
|
| 1177 |
+
SA:
|
| 1178 |
+
custom: []
|
| 1179 |
+
name: SA
|
| 1180 |
+
description: ''
|
| 1181 |
+
required: true
|
| 1182 |
+
example: 11613.31890586
|
| 1183 |
+
type: number
|
| 1184 |
+
enumValues: []
|
| 1185 |
+
exampleWasSpecified: false
|
| 1186 |
+
nullable: false
|
| 1187 |
+
deprecated: false
|
| 1188 |
+
TPSA:
|
| 1189 |
+
custom: []
|
| 1190 |
+
name: TPSA
|
| 1191 |
+
description: ''
|
| 1192 |
+
required: true
|
| 1193 |
+
example: 11613.31890586
|
| 1194 |
+
type: number
|
| 1195 |
+
enumValues: []
|
| 1196 |
+
exampleWasSpecified: false
|
| 1197 |
+
nullable: false
|
| 1198 |
+
deprecated: false
|
| 1199 |
+
MW:
|
| 1200 |
+
custom: []
|
| 1201 |
+
name: MW
|
| 1202 |
+
description: ''
|
| 1203 |
+
required: true
|
| 1204 |
+
example: 11613.31890586
|
| 1205 |
+
type: number
|
| 1206 |
+
enumValues: []
|
| 1207 |
+
exampleWasSpecified: false
|
| 1208 |
+
nullable: false
|
| 1209 |
+
deprecated: false
|
| 1210 |
+
cleanBodyParameters:
|
| 1211 |
+
num_smiles: 17
|
| 1212 |
+
top_k: 17
|
| 1213 |
+
Docking: 11613.31890586
|
| 1214 |
+
DeepPurpose_Affinity: 11613.31890586
|
| 1215 |
+
SA: 11613.31890586
|
| 1216 |
+
TPSA: 11613.31890586
|
| 1217 |
+
MW: 11613.31890586
|
| 1218 |
+
fileParameters: []
|
| 1219 |
+
responses: []
|
| 1220 |
+
responseFields: []
|
| 1221 |
+
auth: []
|
| 1222 |
+
controller: null
|
| 1223 |
+
method: null
|
| 1224 |
+
route: null
|
| 1225 |
+
-
|
| 1226 |
+
custom: []
|
| 1227 |
+
httpMethods:
|
| 1228 |
+
- GET
|
| 1229 |
+
uri: 'api/ai/status/{job_job_id}'
|
| 1230 |
+
metadata:
|
| 1231 |
+
custom: []
|
| 1232 |
+
groupName: Endpoints
|
| 1233 |
+
groupDescription: ''
|
| 1234 |
+
subgroup: ''
|
| 1235 |
+
subgroupDescription: ''
|
| 1236 |
+
title: ''
|
| 1237 |
+
description: ''
|
| 1238 |
+
authenticated: false
|
| 1239 |
+
deprecated: false
|
| 1240 |
+
headers:
|
| 1241 |
+
Content-Type: application/json
|
| 1242 |
+
Accept: application/json
|
| 1243 |
+
urlParameters:
|
| 1244 |
+
job_job_id:
|
| 1245 |
+
custom: []
|
| 1246 |
+
name: job_job_id
|
| 1247 |
+
description: 'The ID of the job job.'
|
| 1248 |
+
required: true
|
| 1249 |
+
example: consequatur
|
| 1250 |
+
type: string
|
| 1251 |
+
enumValues: []
|
| 1252 |
+
exampleWasSpecified: false
|
| 1253 |
+
nullable: false
|
| 1254 |
+
deprecated: false
|
| 1255 |
+
cleanUrlParameters:
|
| 1256 |
+
job_job_id: consequatur
|
| 1257 |
+
queryParameters: []
|
| 1258 |
+
cleanQueryParameters: []
|
| 1259 |
+
bodyParameters: []
|
| 1260 |
+
cleanBodyParameters: []
|
| 1261 |
+
fileParameters: []
|
| 1262 |
+
responses:
|
| 1263 |
+
-
|
| 1264 |
+
custom: []
|
| 1265 |
+
status: 404
|
| 1266 |
+
content: '{"success":false,"message":"The route api\/ai\/status\/consequatur could not be found."}'
|
| 1267 |
+
headers:
|
| 1268 |
+
cache-control: 'no-cache, private'
|
| 1269 |
+
content-type: application/json
|
| 1270 |
+
access-control-allow-origin: '*'
|
| 1271 |
+
description: null
|
| 1272 |
+
responseFields: []
|
| 1273 |
+
auth: []
|
| 1274 |
+
controller: null
|
| 1275 |
+
method: null
|
| 1276 |
+
route: null
|
| 1277 |
+
-
|
| 1278 |
+
custom: []
|
| 1279 |
+
httpMethods:
|
| 1280 |
+
- GET
|
| 1281 |
+
uri: 'api/ai/preview/{job_job_id}'
|
| 1282 |
+
metadata:
|
| 1283 |
+
custom: []
|
| 1284 |
+
groupName: Endpoints
|
| 1285 |
+
groupDescription: ''
|
| 1286 |
+
subgroup: ''
|
| 1287 |
+
subgroupDescription: ''
|
| 1288 |
+
title: ''
|
| 1289 |
+
description: ''
|
| 1290 |
+
authenticated: false
|
| 1291 |
+
deprecated: false
|
| 1292 |
+
headers:
|
| 1293 |
+
Content-Type: application/json
|
| 1294 |
+
Accept: application/json
|
| 1295 |
+
urlParameters:
|
| 1296 |
+
job_job_id:
|
| 1297 |
+
custom: []
|
| 1298 |
+
name: job_job_id
|
| 1299 |
+
description: 'The ID of the job job.'
|
| 1300 |
+
required: true
|
| 1301 |
+
example: consequatur
|
| 1302 |
+
type: string
|
| 1303 |
+
enumValues: []
|
| 1304 |
+
exampleWasSpecified: false
|
| 1305 |
+
nullable: false
|
| 1306 |
+
deprecated: false
|
| 1307 |
+
cleanUrlParameters:
|
| 1308 |
+
job_job_id: consequatur
|
| 1309 |
+
queryParameters: []
|
| 1310 |
+
cleanQueryParameters: []
|
| 1311 |
+
bodyParameters: []
|
| 1312 |
+
cleanBodyParameters: []
|
| 1313 |
+
fileParameters: []
|
| 1314 |
+
responses:
|
| 1315 |
+
-
|
| 1316 |
+
custom: []
|
| 1317 |
+
status: 404
|
| 1318 |
+
content: '{"success":false,"message":"The route api\/ai\/preview\/consequatur could not be found."}'
|
| 1319 |
+
headers:
|
| 1320 |
+
cache-control: 'no-cache, private'
|
| 1321 |
+
content-type: application/json
|
| 1322 |
+
access-control-allow-origin: '*'
|
| 1323 |
+
description: null
|
| 1324 |
+
responseFields: []
|
| 1325 |
+
auth: []
|
| 1326 |
+
controller: null
|
| 1327 |
+
method: null
|
| 1328 |
+
route: null
|
| 1329 |
+
-
|
| 1330 |
+
custom: []
|
| 1331 |
+
httpMethods:
|
| 1332 |
+
- GET
|
| 1333 |
+
uri: 'api/ai/download/top/{job_job_id}'
|
| 1334 |
+
metadata:
|
| 1335 |
+
custom: []
|
| 1336 |
+
groupName: Endpoints
|
| 1337 |
+
groupDescription: ''
|
| 1338 |
+
subgroup: ''
|
| 1339 |
+
subgroupDescription: ''
|
| 1340 |
+
title: ''
|
| 1341 |
+
description: ''
|
| 1342 |
+
authenticated: false
|
| 1343 |
+
deprecated: false
|
| 1344 |
+
headers:
|
| 1345 |
+
Content-Type: application/json
|
| 1346 |
+
Accept: application/json
|
| 1347 |
+
urlParameters:
|
| 1348 |
+
job_job_id:
|
| 1349 |
+
custom: []
|
| 1350 |
+
name: job_job_id
|
| 1351 |
+
description: 'The ID of the job job.'
|
| 1352 |
+
required: true
|
| 1353 |
+
example: consequatur
|
| 1354 |
+
type: string
|
| 1355 |
+
enumValues: []
|
| 1356 |
+
exampleWasSpecified: false
|
| 1357 |
+
nullable: false
|
| 1358 |
+
deprecated: false
|
| 1359 |
+
cleanUrlParameters:
|
| 1360 |
+
job_job_id: consequatur
|
| 1361 |
+
queryParameters: []
|
| 1362 |
+
cleanQueryParameters: []
|
| 1363 |
+
bodyParameters: []
|
| 1364 |
+
cleanBodyParameters: []
|
| 1365 |
+
fileParameters: []
|
| 1366 |
+
responses:
|
| 1367 |
+
-
|
| 1368 |
+
custom: []
|
| 1369 |
+
status: 404
|
| 1370 |
+
content: '{"success":false,"message":"The route api\/ai\/download\/top\/consequatur could not be found."}'
|
| 1371 |
+
headers:
|
| 1372 |
+
cache-control: 'no-cache, private'
|
| 1373 |
+
content-type: application/json
|
| 1374 |
+
access-control-allow-origin: '*'
|
| 1375 |
+
description: null
|
| 1376 |
+
responseFields: []
|
| 1377 |
+
auth: []
|
| 1378 |
+
controller: null
|
| 1379 |
+
method: null
|
| 1380 |
+
route: null
|
| 1381 |
+
-
|
| 1382 |
+
custom: []
|
| 1383 |
+
httpMethods:
|
| 1384 |
+
- GET
|
| 1385 |
+
uri: 'api/ai/download/full/{job_job_id}'
|
| 1386 |
+
metadata:
|
| 1387 |
+
custom: []
|
| 1388 |
+
groupName: Endpoints
|
| 1389 |
+
groupDescription: ''
|
| 1390 |
+
subgroup: ''
|
| 1391 |
+
subgroupDescription: ''
|
| 1392 |
+
title: ''
|
| 1393 |
+
description: ''
|
| 1394 |
+
authenticated: false
|
| 1395 |
+
deprecated: false
|
| 1396 |
+
headers:
|
| 1397 |
+
Content-Type: application/json
|
| 1398 |
+
Accept: application/json
|
| 1399 |
+
urlParameters:
|
| 1400 |
+
job_job_id:
|
| 1401 |
+
custom: []
|
| 1402 |
+
name: job_job_id
|
| 1403 |
+
description: 'The ID of the job job.'
|
| 1404 |
+
required: true
|
| 1405 |
+
example: consequatur
|
| 1406 |
+
type: string
|
| 1407 |
+
enumValues: []
|
| 1408 |
+
exampleWasSpecified: false
|
| 1409 |
+
nullable: false
|
| 1410 |
+
deprecated: false
|
| 1411 |
+
cleanUrlParameters:
|
| 1412 |
+
job_job_id: consequatur
|
| 1413 |
+
queryParameters: []
|
| 1414 |
+
cleanQueryParameters: []
|
| 1415 |
+
bodyParameters: []
|
| 1416 |
+
cleanBodyParameters: []
|
| 1417 |
+
fileParameters: []
|
| 1418 |
+
responses:
|
| 1419 |
+
-
|
| 1420 |
+
custom: []
|
| 1421 |
+
status: 404
|
| 1422 |
+
content: '{"success":false,"message":"The route api\/ai\/download\/full\/consequatur could not be found."}'
|
| 1423 |
+
headers:
|
| 1424 |
+
cache-control: 'no-cache, private'
|
| 1425 |
+
content-type: application/json
|
| 1426 |
+
access-control-allow-origin: '*'
|
| 1427 |
+
description: null
|
| 1428 |
+
responseFields: []
|
| 1429 |
+
auth: []
|
| 1430 |
+
controller: null
|
| 1431 |
+
method: null
|
| 1432 |
+
route: null
|
| 1433 |
+
-
|
| 1434 |
+
custom: []
|
| 1435 |
+
httpMethods:
|
| 1436 |
+
- GET
|
| 1437 |
+
uri: api/ai/history
|
| 1438 |
+
metadata:
|
| 1439 |
+
custom: []
|
| 1440 |
+
groupName: Endpoints
|
| 1441 |
+
groupDescription: ''
|
| 1442 |
+
subgroup: ''
|
| 1443 |
+
subgroupDescription: ''
|
| 1444 |
+
title: ''
|
| 1445 |
+
description: ''
|
| 1446 |
+
authenticated: false
|
| 1447 |
+
deprecated: false
|
| 1448 |
+
headers:
|
| 1449 |
+
Content-Type: application/json
|
| 1450 |
+
Accept: application/json
|
| 1451 |
+
urlParameters: []
|
| 1452 |
+
cleanUrlParameters: []
|
| 1453 |
+
queryParameters: []
|
| 1454 |
+
cleanQueryParameters: []
|
| 1455 |
+
bodyParameters: []
|
| 1456 |
+
cleanBodyParameters: []
|
| 1457 |
+
fileParameters: []
|
| 1458 |
+
responses:
|
| 1459 |
+
-
|
| 1460 |
+
custom: []
|
| 1461 |
+
status: 404
|
| 1462 |
+
content: '{"success":false,"message":"The route api\/ai\/history could not be found."}'
|
| 1463 |
+
headers:
|
| 1464 |
+
cache-control: 'no-cache, private'
|
| 1465 |
+
content-type: application/json
|
| 1466 |
+
access-control-allow-origin: '*'
|
| 1467 |
+
description: null
|
| 1468 |
+
responseFields: []
|
| 1469 |
+
auth: []
|
| 1470 |
+
controller: null
|
| 1471 |
+
method: null
|
| 1472 |
+
route: null
|
| 1473 |
+
-
|
| 1474 |
+
custom: []
|
| 1475 |
+
httpMethods:
|
| 1476 |
+
- POST
|
| 1477 |
+
uri: api/upload-file
|
| 1478 |
+
metadata:
|
| 1479 |
+
custom: []
|
| 1480 |
+
groupName: Endpoints
|
| 1481 |
+
groupDescription: ''
|
| 1482 |
+
subgroup: ''
|
| 1483 |
+
subgroupDescription: ''
|
| 1484 |
+
title: ''
|
| 1485 |
+
description: ''
|
| 1486 |
+
authenticated: false
|
| 1487 |
+
deprecated: false
|
| 1488 |
+
headers:
|
| 1489 |
+
Content-Type: application/json
|
| 1490 |
+
Accept: application/json
|
| 1491 |
+
urlParameters: []
|
| 1492 |
+
cleanUrlParameters: []
|
| 1493 |
+
queryParameters: []
|
| 1494 |
+
cleanQueryParameters: []
|
| 1495 |
+
bodyParameters: []
|
| 1496 |
+
cleanBodyParameters: []
|
| 1497 |
+
fileParameters: []
|
| 1498 |
+
responses: []
|
| 1499 |
+
responseFields: []
|
| 1500 |
+
auth: []
|
| 1501 |
+
controller: null
|
| 1502 |
+
method: null
|
| 1503 |
+
route: null
|
.scribe/endpoints/00.yaml
ADDED
|
@@ -0,0 +1,1501 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Endpoints
|
| 2 |
+
description: ''
|
| 3 |
+
endpoints:
|
| 4 |
+
-
|
| 5 |
+
custom: []
|
| 6 |
+
httpMethods:
|
| 7 |
+
- GET
|
| 8 |
+
uri: api/awards
|
| 9 |
+
metadata:
|
| 10 |
+
custom: []
|
| 11 |
+
groupName: Endpoints
|
| 12 |
+
groupDescription: ''
|
| 13 |
+
subgroup: ''
|
| 14 |
+
subgroupDescription: ''
|
| 15 |
+
title: ''
|
| 16 |
+
description: ''
|
| 17 |
+
authenticated: false
|
| 18 |
+
deprecated: false
|
| 19 |
+
headers:
|
| 20 |
+
Content-Type: application/json
|
| 21 |
+
Accept: application/json
|
| 22 |
+
urlParameters: []
|
| 23 |
+
cleanUrlParameters: []
|
| 24 |
+
queryParameters: []
|
| 25 |
+
cleanQueryParameters: []
|
| 26 |
+
bodyParameters: []
|
| 27 |
+
cleanBodyParameters: []
|
| 28 |
+
fileParameters: []
|
| 29 |
+
responses:
|
| 30 |
+
-
|
| 31 |
+
custom: []
|
| 32 |
+
status: 404
|
| 33 |
+
content: '{"success":false,"message":"The route api\/awards could not be found."}'
|
| 34 |
+
headers:
|
| 35 |
+
cache-control: 'no-cache, private'
|
| 36 |
+
content-type: application/json
|
| 37 |
+
access-control-allow-origin: '*'
|
| 38 |
+
description: null
|
| 39 |
+
responseFields: []
|
| 40 |
+
auth: []
|
| 41 |
+
controller: null
|
| 42 |
+
method: null
|
| 43 |
+
route: null
|
| 44 |
+
-
|
| 45 |
+
custom: []
|
| 46 |
+
httpMethods:
|
| 47 |
+
- GET
|
| 48 |
+
uri: 'api/awards/{id}'
|
| 49 |
+
metadata:
|
| 50 |
+
custom: []
|
| 51 |
+
groupName: Endpoints
|
| 52 |
+
groupDescription: ''
|
| 53 |
+
subgroup: ''
|
| 54 |
+
subgroupDescription: ''
|
| 55 |
+
title: ''
|
| 56 |
+
description: ''
|
| 57 |
+
authenticated: false
|
| 58 |
+
deprecated: false
|
| 59 |
+
headers:
|
| 60 |
+
Content-Type: application/json
|
| 61 |
+
Accept: application/json
|
| 62 |
+
urlParameters:
|
| 63 |
+
id:
|
| 64 |
+
custom: []
|
| 65 |
+
name: id
|
| 66 |
+
description: 'The ID of the award.'
|
| 67 |
+
required: true
|
| 68 |
+
example: consequatur
|
| 69 |
+
type: string
|
| 70 |
+
enumValues: []
|
| 71 |
+
exampleWasSpecified: false
|
| 72 |
+
nullable: false
|
| 73 |
+
deprecated: false
|
| 74 |
+
cleanUrlParameters:
|
| 75 |
+
id: consequatur
|
| 76 |
+
queryParameters: []
|
| 77 |
+
cleanQueryParameters: []
|
| 78 |
+
bodyParameters: []
|
| 79 |
+
cleanBodyParameters: []
|
| 80 |
+
fileParameters: []
|
| 81 |
+
responses:
|
| 82 |
+
-
|
| 83 |
+
custom: []
|
| 84 |
+
status: 404
|
| 85 |
+
content: '{"success":false,"message":"The route api\/awards\/consequatur could not be found."}'
|
| 86 |
+
headers:
|
| 87 |
+
cache-control: 'no-cache, private'
|
| 88 |
+
content-type: application/json
|
| 89 |
+
access-control-allow-origin: '*'
|
| 90 |
+
description: null
|
| 91 |
+
responseFields: []
|
| 92 |
+
auth: []
|
| 93 |
+
controller: null
|
| 94 |
+
method: null
|
| 95 |
+
route: null
|
| 96 |
+
-
|
| 97 |
+
custom: []
|
| 98 |
+
httpMethods:
|
| 99 |
+
- GET
|
| 100 |
+
uri: 'api/awards/{id}/scientists'
|
| 101 |
+
metadata:
|
| 102 |
+
custom: []
|
| 103 |
+
groupName: Endpoints
|
| 104 |
+
groupDescription: ''
|
| 105 |
+
subgroup: ''
|
| 106 |
+
subgroupDescription: ''
|
| 107 |
+
title: ''
|
| 108 |
+
description: ''
|
| 109 |
+
authenticated: false
|
| 110 |
+
deprecated: false
|
| 111 |
+
headers:
|
| 112 |
+
Content-Type: application/json
|
| 113 |
+
Accept: application/json
|
| 114 |
+
urlParameters:
|
| 115 |
+
id:
|
| 116 |
+
custom: []
|
| 117 |
+
name: id
|
| 118 |
+
description: 'The ID of the award.'
|
| 119 |
+
required: true
|
| 120 |
+
example: consequatur
|
| 121 |
+
type: string
|
| 122 |
+
enumValues: []
|
| 123 |
+
exampleWasSpecified: false
|
| 124 |
+
nullable: false
|
| 125 |
+
deprecated: false
|
| 126 |
+
cleanUrlParameters:
|
| 127 |
+
id: consequatur
|
| 128 |
+
queryParameters: []
|
| 129 |
+
cleanQueryParameters: []
|
| 130 |
+
bodyParameters: []
|
| 131 |
+
cleanBodyParameters: []
|
| 132 |
+
fileParameters: []
|
| 133 |
+
responses:
|
| 134 |
+
-
|
| 135 |
+
custom: []
|
| 136 |
+
status: 404
|
| 137 |
+
content: '{"success":false,"message":"The route api\/awards\/consequatur\/scientists could not be found."}'
|
| 138 |
+
headers:
|
| 139 |
+
cache-control: 'no-cache, private'
|
| 140 |
+
content-type: application/json
|
| 141 |
+
access-control-allow-origin: '*'
|
| 142 |
+
description: null
|
| 143 |
+
responseFields: []
|
| 144 |
+
auth: []
|
| 145 |
+
controller: null
|
| 146 |
+
method: null
|
| 147 |
+
route: null
|
| 148 |
+
-
|
| 149 |
+
custom: []
|
| 150 |
+
httpMethods:
|
| 151 |
+
- GET
|
| 152 |
+
uri: api/scientists
|
| 153 |
+
metadata:
|
| 154 |
+
custom: []
|
| 155 |
+
groupName: Endpoints
|
| 156 |
+
groupDescription: ''
|
| 157 |
+
subgroup: ''
|
| 158 |
+
subgroupDescription: ''
|
| 159 |
+
title: ''
|
| 160 |
+
description: ''
|
| 161 |
+
authenticated: false
|
| 162 |
+
deprecated: false
|
| 163 |
+
headers:
|
| 164 |
+
Content-Type: application/json
|
| 165 |
+
Accept: application/json
|
| 166 |
+
urlParameters: []
|
| 167 |
+
cleanUrlParameters: []
|
| 168 |
+
queryParameters: []
|
| 169 |
+
cleanQueryParameters: []
|
| 170 |
+
bodyParameters: []
|
| 171 |
+
cleanBodyParameters: []
|
| 172 |
+
fileParameters: []
|
| 173 |
+
responses:
|
| 174 |
+
-
|
| 175 |
+
custom: []
|
| 176 |
+
status: 404
|
| 177 |
+
content: '{"success":false,"message":"The route api\/scientists could not be found."}'
|
| 178 |
+
headers:
|
| 179 |
+
cache-control: 'no-cache, private'
|
| 180 |
+
content-type: application/json
|
| 181 |
+
access-control-allow-origin: '*'
|
| 182 |
+
description: null
|
| 183 |
+
responseFields: []
|
| 184 |
+
auth: []
|
| 185 |
+
controller: null
|
| 186 |
+
method: null
|
| 187 |
+
route: null
|
| 188 |
+
-
|
| 189 |
+
custom: []
|
| 190 |
+
httpMethods:
|
| 191 |
+
- GET
|
| 192 |
+
uri: 'api/scientists/{id}'
|
| 193 |
+
metadata:
|
| 194 |
+
custom: []
|
| 195 |
+
groupName: Endpoints
|
| 196 |
+
groupDescription: ''
|
| 197 |
+
subgroup: ''
|
| 198 |
+
subgroupDescription: ''
|
| 199 |
+
title: ''
|
| 200 |
+
description: ''
|
| 201 |
+
authenticated: false
|
| 202 |
+
deprecated: false
|
| 203 |
+
headers:
|
| 204 |
+
Content-Type: application/json
|
| 205 |
+
Accept: application/json
|
| 206 |
+
urlParameters:
|
| 207 |
+
id:
|
| 208 |
+
custom: []
|
| 209 |
+
name: id
|
| 210 |
+
description: 'The ID of the scientist.'
|
| 211 |
+
required: true
|
| 212 |
+
example: consequatur
|
| 213 |
+
type: string
|
| 214 |
+
enumValues: []
|
| 215 |
+
exampleWasSpecified: false
|
| 216 |
+
nullable: false
|
| 217 |
+
deprecated: false
|
| 218 |
+
cleanUrlParameters:
|
| 219 |
+
id: consequatur
|
| 220 |
+
queryParameters: []
|
| 221 |
+
cleanQueryParameters: []
|
| 222 |
+
bodyParameters: []
|
| 223 |
+
cleanBodyParameters: []
|
| 224 |
+
fileParameters: []
|
| 225 |
+
responses:
|
| 226 |
+
-
|
| 227 |
+
custom: []
|
| 228 |
+
status: 404
|
| 229 |
+
content: '{"success":false,"message":"The route api\/scientists\/consequatur could not be found."}'
|
| 230 |
+
headers:
|
| 231 |
+
cache-control: 'no-cache, private'
|
| 232 |
+
content-type: application/json
|
| 233 |
+
access-control-allow-origin: '*'
|
| 234 |
+
description: null
|
| 235 |
+
responseFields: []
|
| 236 |
+
auth: []
|
| 237 |
+
controller: null
|
| 238 |
+
method: null
|
| 239 |
+
route: null
|
| 240 |
+
-
|
| 241 |
+
custom: []
|
| 242 |
+
httpMethods:
|
| 243 |
+
- GET
|
| 244 |
+
uri: 'api/scientists/{id}/awards'
|
| 245 |
+
metadata:
|
| 246 |
+
custom: []
|
| 247 |
+
groupName: Endpoints
|
| 248 |
+
groupDescription: ''
|
| 249 |
+
subgroup: ''
|
| 250 |
+
subgroupDescription: ''
|
| 251 |
+
title: ''
|
| 252 |
+
description: ''
|
| 253 |
+
authenticated: false
|
| 254 |
+
deprecated: false
|
| 255 |
+
headers:
|
| 256 |
+
Content-Type: application/json
|
| 257 |
+
Accept: application/json
|
| 258 |
+
urlParameters:
|
| 259 |
+
id:
|
| 260 |
+
custom: []
|
| 261 |
+
name: id
|
| 262 |
+
description: 'The ID of the scientist.'
|
| 263 |
+
required: true
|
| 264 |
+
example: consequatur
|
| 265 |
+
type: string
|
| 266 |
+
enumValues: []
|
| 267 |
+
exampleWasSpecified: false
|
| 268 |
+
nullable: false
|
| 269 |
+
deprecated: false
|
| 270 |
+
cleanUrlParameters:
|
| 271 |
+
id: consequatur
|
| 272 |
+
queryParameters: []
|
| 273 |
+
cleanQueryParameters: []
|
| 274 |
+
bodyParameters: []
|
| 275 |
+
cleanBodyParameters: []
|
| 276 |
+
fileParameters: []
|
| 277 |
+
responses:
|
| 278 |
+
-
|
| 279 |
+
custom: []
|
| 280 |
+
status: 404
|
| 281 |
+
content: '{"success":false,"message":"The route api\/scientists\/consequatur\/awards could not be found."}'
|
| 282 |
+
headers:
|
| 283 |
+
cache-control: 'no-cache, private'
|
| 284 |
+
content-type: application/json
|
| 285 |
+
access-control-allow-origin: '*'
|
| 286 |
+
description: null
|
| 287 |
+
responseFields: []
|
| 288 |
+
auth: []
|
| 289 |
+
controller: null
|
| 290 |
+
method: null
|
| 291 |
+
route: null
|
| 292 |
+
-
|
| 293 |
+
custom: []
|
| 294 |
+
httpMethods:
|
| 295 |
+
- POST
|
| 296 |
+
uri: api/user/register
|
| 297 |
+
metadata:
|
| 298 |
+
custom: []
|
| 299 |
+
groupName: Endpoints
|
| 300 |
+
groupDescription: ''
|
| 301 |
+
subgroup: ''
|
| 302 |
+
subgroupDescription: ''
|
| 303 |
+
title: '---------------- Auth ----------------'
|
| 304 |
+
description: ''
|
| 305 |
+
authenticated: false
|
| 306 |
+
deprecated: false
|
| 307 |
+
headers:
|
| 308 |
+
Content-Type: application/json
|
| 309 |
+
Accept: application/json
|
| 310 |
+
urlParameters: []
|
| 311 |
+
cleanUrlParameters: []
|
| 312 |
+
queryParameters: []
|
| 313 |
+
cleanQueryParameters: []
|
| 314 |
+
bodyParameters:
|
| 315 |
+
name:
|
| 316 |
+
custom: []
|
| 317 |
+
name: name
|
| 318 |
+
description: 'Must not be greater than 255 characters.'
|
| 319 |
+
required: true
|
| 320 |
+
example: vmqeopfuudtdsufvyvddq
|
| 321 |
+
type: string
|
| 322 |
+
enumValues: []
|
| 323 |
+
exampleWasSpecified: false
|
| 324 |
+
nullable: false
|
| 325 |
+
deprecated: false
|
| 326 |
+
email:
|
| 327 |
+
custom: []
|
| 328 |
+
name: email
|
| 329 |
+
description: 'Must be a valid email address.'
|
| 330 |
+
required: true
|
| 331 |
+
example: kunde.eloisa@example.com
|
| 332 |
+
type: string
|
| 333 |
+
enumValues: []
|
| 334 |
+
exampleWasSpecified: false
|
| 335 |
+
nullable: false
|
| 336 |
+
deprecated: false
|
| 337 |
+
password:
|
| 338 |
+
custom: []
|
| 339 |
+
name: password
|
| 340 |
+
description: 'Must be at least 6 characters.'
|
| 341 |
+
required: true
|
| 342 |
+
example: '4[*UyPJ"}6'
|
| 343 |
+
type: string
|
| 344 |
+
enumValues: []
|
| 345 |
+
exampleWasSpecified: false
|
| 346 |
+
nullable: false
|
| 347 |
+
deprecated: false
|
| 348 |
+
cleanBodyParameters:
|
| 349 |
+
name: vmqeopfuudtdsufvyvddq
|
| 350 |
+
email: kunde.eloisa@example.com
|
| 351 |
+
password: '4[*UyPJ"}6'
|
| 352 |
+
fileParameters: []
|
| 353 |
+
responses: []
|
| 354 |
+
responseFields: []
|
| 355 |
+
auth: []
|
| 356 |
+
controller: null
|
| 357 |
+
method: null
|
| 358 |
+
route: null
|
| 359 |
+
-
|
| 360 |
+
custom: []
|
| 361 |
+
httpMethods:
|
| 362 |
+
- POST
|
| 363 |
+
uri: api/user/login
|
| 364 |
+
metadata:
|
| 365 |
+
custom: []
|
| 366 |
+
groupName: Endpoints
|
| 367 |
+
groupDescription: ''
|
| 368 |
+
subgroup: ''
|
| 369 |
+
subgroupDescription: ''
|
| 370 |
+
title: ''
|
| 371 |
+
description: ''
|
| 372 |
+
authenticated: false
|
| 373 |
+
deprecated: false
|
| 374 |
+
headers:
|
| 375 |
+
Content-Type: application/json
|
| 376 |
+
Accept: application/json
|
| 377 |
+
urlParameters: []
|
| 378 |
+
cleanUrlParameters: []
|
| 379 |
+
queryParameters: []
|
| 380 |
+
cleanQueryParameters: []
|
| 381 |
+
bodyParameters:
|
| 382 |
+
email:
|
| 383 |
+
custom: []
|
| 384 |
+
name: email
|
| 385 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 386 |
+
required: true
|
| 387 |
+
example: qkunze@example.com
|
| 388 |
+
type: string
|
| 389 |
+
enumValues: []
|
| 390 |
+
exampleWasSpecified: false
|
| 391 |
+
nullable: false
|
| 392 |
+
deprecated: false
|
| 393 |
+
password:
|
| 394 |
+
custom: []
|
| 395 |
+
name: password
|
| 396 |
+
description: ''
|
| 397 |
+
required: true
|
| 398 |
+
example: 'O[2UZ5ij-e/dl4m{o,'
|
| 399 |
+
type: string
|
| 400 |
+
enumValues: []
|
| 401 |
+
exampleWasSpecified: false
|
| 402 |
+
nullable: false
|
| 403 |
+
deprecated: false
|
| 404 |
+
cleanBodyParameters:
|
| 405 |
+
email: qkunze@example.com
|
| 406 |
+
password: 'O[2UZ5ij-e/dl4m{o,'
|
| 407 |
+
fileParameters: []
|
| 408 |
+
responses: []
|
| 409 |
+
responseFields: []
|
| 410 |
+
auth: []
|
| 411 |
+
controller: null
|
| 412 |
+
method: null
|
| 413 |
+
route: null
|
| 414 |
+
-
|
| 415 |
+
custom: []
|
| 416 |
+
httpMethods:
|
| 417 |
+
- POST
|
| 418 |
+
uri: api/user/verify-email
|
| 419 |
+
metadata:
|
| 420 |
+
custom: []
|
| 421 |
+
groupName: Endpoints
|
| 422 |
+
groupDescription: ''
|
| 423 |
+
subgroup: ''
|
| 424 |
+
subgroupDescription: ''
|
| 425 |
+
title: '---------------- Email Verification ----------------'
|
| 426 |
+
description: ''
|
| 427 |
+
authenticated: false
|
| 428 |
+
deprecated: false
|
| 429 |
+
headers:
|
| 430 |
+
Content-Type: application/json
|
| 431 |
+
Accept: application/json
|
| 432 |
+
urlParameters: []
|
| 433 |
+
cleanUrlParameters: []
|
| 434 |
+
queryParameters: []
|
| 435 |
+
cleanQueryParameters: []
|
| 436 |
+
bodyParameters:
|
| 437 |
+
email:
|
| 438 |
+
custom: []
|
| 439 |
+
name: email
|
| 440 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 441 |
+
required: true
|
| 442 |
+
example: qkunze@example.com
|
| 443 |
+
type: string
|
| 444 |
+
enumValues: []
|
| 445 |
+
exampleWasSpecified: false
|
| 446 |
+
nullable: false
|
| 447 |
+
deprecated: false
|
| 448 |
+
otp:
|
| 449 |
+
custom: []
|
| 450 |
+
name: otp
|
| 451 |
+
description: 'Must be 6 digits.'
|
| 452 |
+
required: true
|
| 453 |
+
example: '810798'
|
| 454 |
+
type: string
|
| 455 |
+
enumValues: []
|
| 456 |
+
exampleWasSpecified: false
|
| 457 |
+
nullable: false
|
| 458 |
+
deprecated: false
|
| 459 |
+
cleanBodyParameters:
|
| 460 |
+
email: qkunze@example.com
|
| 461 |
+
otp: '810798'
|
| 462 |
+
fileParameters: []
|
| 463 |
+
responses: []
|
| 464 |
+
responseFields: []
|
| 465 |
+
auth: []
|
| 466 |
+
controller: null
|
| 467 |
+
method: null
|
| 468 |
+
route: null
|
| 469 |
+
-
|
| 470 |
+
custom: []
|
| 471 |
+
httpMethods:
|
| 472 |
+
- POST
|
| 473 |
+
uri: api/user/resend-otp
|
| 474 |
+
metadata:
|
| 475 |
+
custom: []
|
| 476 |
+
groupName: Endpoints
|
| 477 |
+
groupDescription: ''
|
| 478 |
+
subgroup: ''
|
| 479 |
+
subgroupDescription: ''
|
| 480 |
+
title: ''
|
| 481 |
+
description: ''
|
| 482 |
+
authenticated: false
|
| 483 |
+
deprecated: false
|
| 484 |
+
headers:
|
| 485 |
+
Content-Type: application/json
|
| 486 |
+
Accept: application/json
|
| 487 |
+
urlParameters: []
|
| 488 |
+
cleanUrlParameters: []
|
| 489 |
+
queryParameters: []
|
| 490 |
+
cleanQueryParameters: []
|
| 491 |
+
bodyParameters:
|
| 492 |
+
email:
|
| 493 |
+
custom: []
|
| 494 |
+
name: email
|
| 495 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 496 |
+
required: true
|
| 497 |
+
example: qkunze@example.com
|
| 498 |
+
type: string
|
| 499 |
+
enumValues: []
|
| 500 |
+
exampleWasSpecified: false
|
| 501 |
+
nullable: false
|
| 502 |
+
deprecated: false
|
| 503 |
+
cleanBodyParameters:
|
| 504 |
+
email: qkunze@example.com
|
| 505 |
+
fileParameters: []
|
| 506 |
+
responses: []
|
| 507 |
+
responseFields: []
|
| 508 |
+
auth: []
|
| 509 |
+
controller: null
|
| 510 |
+
method: null
|
| 511 |
+
route: null
|
| 512 |
+
-
|
| 513 |
+
custom: []
|
| 514 |
+
httpMethods:
|
| 515 |
+
- POST
|
| 516 |
+
uri: api/user/forgot-password
|
| 517 |
+
metadata:
|
| 518 |
+
custom: []
|
| 519 |
+
groupName: Endpoints
|
| 520 |
+
groupDescription: ''
|
| 521 |
+
subgroup: ''
|
| 522 |
+
subgroupDescription: ''
|
| 523 |
+
title: '---------------- Password Reset ----------------'
|
| 524 |
+
description: ''
|
| 525 |
+
authenticated: false
|
| 526 |
+
deprecated: false
|
| 527 |
+
headers:
|
| 528 |
+
Content-Type: application/json
|
| 529 |
+
Accept: application/json
|
| 530 |
+
urlParameters: []
|
| 531 |
+
cleanUrlParameters: []
|
| 532 |
+
queryParameters: []
|
| 533 |
+
cleanQueryParameters: []
|
| 534 |
+
bodyParameters:
|
| 535 |
+
email:
|
| 536 |
+
custom: []
|
| 537 |
+
name: email
|
| 538 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 539 |
+
required: true
|
| 540 |
+
example: qkunze@example.com
|
| 541 |
+
type: string
|
| 542 |
+
enumValues: []
|
| 543 |
+
exampleWasSpecified: false
|
| 544 |
+
nullable: false
|
| 545 |
+
deprecated: false
|
| 546 |
+
cleanBodyParameters:
|
| 547 |
+
email: qkunze@example.com
|
| 548 |
+
fileParameters: []
|
| 549 |
+
responses: []
|
| 550 |
+
responseFields: []
|
| 551 |
+
auth: []
|
| 552 |
+
controller: null
|
| 553 |
+
method: null
|
| 554 |
+
route: null
|
| 555 |
+
-
|
| 556 |
+
custom: []
|
| 557 |
+
httpMethods:
|
| 558 |
+
- POST
|
| 559 |
+
uri: api/user/reset-password
|
| 560 |
+
metadata:
|
| 561 |
+
custom: []
|
| 562 |
+
groupName: Endpoints
|
| 563 |
+
groupDescription: ''
|
| 564 |
+
subgroup: ''
|
| 565 |
+
subgroupDescription: ''
|
| 566 |
+
title: ''
|
| 567 |
+
description: ''
|
| 568 |
+
authenticated: false
|
| 569 |
+
deprecated: false
|
| 570 |
+
headers:
|
| 571 |
+
Content-Type: application/json
|
| 572 |
+
Accept: application/json
|
| 573 |
+
urlParameters: []
|
| 574 |
+
cleanUrlParameters: []
|
| 575 |
+
queryParameters: []
|
| 576 |
+
cleanQueryParameters: []
|
| 577 |
+
bodyParameters:
|
| 578 |
+
email:
|
| 579 |
+
custom: []
|
| 580 |
+
name: email
|
| 581 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 582 |
+
required: true
|
| 583 |
+
example: qkunze@example.com
|
| 584 |
+
type: string
|
| 585 |
+
enumValues: []
|
| 586 |
+
exampleWasSpecified: false
|
| 587 |
+
nullable: false
|
| 588 |
+
deprecated: false
|
| 589 |
+
otp:
|
| 590 |
+
custom: []
|
| 591 |
+
name: otp
|
| 592 |
+
description: 'Must be 6 digits.'
|
| 593 |
+
required: true
|
| 594 |
+
example: '810798'
|
| 595 |
+
type: string
|
| 596 |
+
enumValues: []
|
| 597 |
+
exampleWasSpecified: false
|
| 598 |
+
nullable: false
|
| 599 |
+
deprecated: false
|
| 600 |
+
password:
|
| 601 |
+
custom: []
|
| 602 |
+
name: password
|
| 603 |
+
description: 'Must be at least 6 characters.'
|
| 604 |
+
required: true
|
| 605 |
+
example: '[2UZ5ij-e/dl4'
|
| 606 |
+
type: string
|
| 607 |
+
enumValues: []
|
| 608 |
+
exampleWasSpecified: false
|
| 609 |
+
nullable: false
|
| 610 |
+
deprecated: false
|
| 611 |
+
cleanBodyParameters:
|
| 612 |
+
email: qkunze@example.com
|
| 613 |
+
otp: '810798'
|
| 614 |
+
password: '[2UZ5ij-e/dl4'
|
| 615 |
+
fileParameters: []
|
| 616 |
+
responses: []
|
| 617 |
+
responseFields: []
|
| 618 |
+
auth: []
|
| 619 |
+
controller: null
|
| 620 |
+
method: null
|
| 621 |
+
route: null
|
| 622 |
+
-
|
| 623 |
+
custom: []
|
| 624 |
+
httpMethods:
|
| 625 |
+
- POST
|
| 626 |
+
uri: api/user/resend-reset-password-otp
|
| 627 |
+
metadata:
|
| 628 |
+
custom: []
|
| 629 |
+
groupName: Endpoints
|
| 630 |
+
groupDescription: ''
|
| 631 |
+
subgroup: ''
|
| 632 |
+
subgroupDescription: ''
|
| 633 |
+
title: ''
|
| 634 |
+
description: ''
|
| 635 |
+
authenticated: false
|
| 636 |
+
deprecated: false
|
| 637 |
+
headers:
|
| 638 |
+
Content-Type: application/json
|
| 639 |
+
Accept: application/json
|
| 640 |
+
urlParameters: []
|
| 641 |
+
cleanUrlParameters: []
|
| 642 |
+
queryParameters: []
|
| 643 |
+
cleanQueryParameters: []
|
| 644 |
+
bodyParameters:
|
| 645 |
+
email:
|
| 646 |
+
custom: []
|
| 647 |
+
name: email
|
| 648 |
+
description: 'Must be a valid email address. The <code>email</code> of an existing record in the users table.'
|
| 649 |
+
required: true
|
| 650 |
+
example: qkunze@example.com
|
| 651 |
+
type: string
|
| 652 |
+
enumValues: []
|
| 653 |
+
exampleWasSpecified: false
|
| 654 |
+
nullable: false
|
| 655 |
+
deprecated: false
|
| 656 |
+
cleanBodyParameters:
|
| 657 |
+
email: qkunze@example.com
|
| 658 |
+
fileParameters: []
|
| 659 |
+
responses: []
|
| 660 |
+
responseFields: []
|
| 661 |
+
auth: []
|
| 662 |
+
controller: null
|
| 663 |
+
method: null
|
| 664 |
+
route: null
|
| 665 |
+
-
|
| 666 |
+
custom: []
|
| 667 |
+
httpMethods:
|
| 668 |
+
- POST
|
| 669 |
+
uri: api/user/auth/google
|
| 670 |
+
metadata:
|
| 671 |
+
custom: []
|
| 672 |
+
groupName: Endpoints
|
| 673 |
+
groupDescription: ''
|
| 674 |
+
subgroup: ''
|
| 675 |
+
subgroupDescription: ''
|
| 676 |
+
title: '---------------- Google OAuth ----------------'
|
| 677 |
+
description: ''
|
| 678 |
+
authenticated: false
|
| 679 |
+
deprecated: false
|
| 680 |
+
headers:
|
| 681 |
+
Content-Type: application/json
|
| 682 |
+
Accept: application/json
|
| 683 |
+
urlParameters: []
|
| 684 |
+
cleanUrlParameters: []
|
| 685 |
+
queryParameters: []
|
| 686 |
+
cleanQueryParameters: []
|
| 687 |
+
bodyParameters:
|
| 688 |
+
access_token:
|
| 689 |
+
custom: []
|
| 690 |
+
name: access_token
|
| 691 |
+
description: ''
|
| 692 |
+
required: true
|
| 693 |
+
example: consequatur
|
| 694 |
+
type: string
|
| 695 |
+
enumValues: []
|
| 696 |
+
exampleWasSpecified: false
|
| 697 |
+
nullable: false
|
| 698 |
+
deprecated: false
|
| 699 |
+
cleanBodyParameters:
|
| 700 |
+
access_token: consequatur
|
| 701 |
+
fileParameters: []
|
| 702 |
+
responses: []
|
| 703 |
+
responseFields: []
|
| 704 |
+
auth: []
|
| 705 |
+
controller: null
|
| 706 |
+
method: null
|
| 707 |
+
route: null
|
| 708 |
+
-
|
| 709 |
+
custom: []
|
| 710 |
+
httpMethods:
|
| 711 |
+
- GET
|
| 712 |
+
uri: api/user/profile
|
| 713 |
+
metadata:
|
| 714 |
+
custom: []
|
| 715 |
+
groupName: Endpoints
|
| 716 |
+
groupDescription: ''
|
| 717 |
+
subgroup: ''
|
| 718 |
+
subgroupDescription: ''
|
| 719 |
+
title: '---------------- Profile ----------------'
|
| 720 |
+
description: ''
|
| 721 |
+
authenticated: false
|
| 722 |
+
deprecated: false
|
| 723 |
+
headers:
|
| 724 |
+
Content-Type: application/json
|
| 725 |
+
Accept: application/json
|
| 726 |
+
urlParameters: []
|
| 727 |
+
cleanUrlParameters: []
|
| 728 |
+
queryParameters: []
|
| 729 |
+
cleanQueryParameters: []
|
| 730 |
+
bodyParameters: []
|
| 731 |
+
cleanBodyParameters: []
|
| 732 |
+
fileParameters: []
|
| 733 |
+
responses:
|
| 734 |
+
-
|
| 735 |
+
custom: []
|
| 736 |
+
status: 404
|
| 737 |
+
content: '{"success":false,"message":"The route api\/user\/profile could not be found."}'
|
| 738 |
+
headers:
|
| 739 |
+
cache-control: 'no-cache, private'
|
| 740 |
+
content-type: application/json
|
| 741 |
+
access-control-allow-origin: '*'
|
| 742 |
+
description: null
|
| 743 |
+
responseFields: []
|
| 744 |
+
auth: []
|
| 745 |
+
controller: null
|
| 746 |
+
method: null
|
| 747 |
+
route: null
|
| 748 |
+
-
|
| 749 |
+
custom: []
|
| 750 |
+
httpMethods:
|
| 751 |
+
- POST
|
| 752 |
+
uri: api/user/logout
|
| 753 |
+
metadata:
|
| 754 |
+
custom: []
|
| 755 |
+
groupName: Endpoints
|
| 756 |
+
groupDescription: ''
|
| 757 |
+
subgroup: ''
|
| 758 |
+
subgroupDescription: ''
|
| 759 |
+
title: ''
|
| 760 |
+
description: ''
|
| 761 |
+
authenticated: false
|
| 762 |
+
deprecated: false
|
| 763 |
+
headers:
|
| 764 |
+
Content-Type: application/json
|
| 765 |
+
Accept: application/json
|
| 766 |
+
urlParameters: []
|
| 767 |
+
cleanUrlParameters: []
|
| 768 |
+
queryParameters: []
|
| 769 |
+
cleanQueryParameters: []
|
| 770 |
+
bodyParameters: []
|
| 771 |
+
cleanBodyParameters: []
|
| 772 |
+
fileParameters: []
|
| 773 |
+
responses: []
|
| 774 |
+
responseFields: []
|
| 775 |
+
auth: []
|
| 776 |
+
controller: null
|
| 777 |
+
method: null
|
| 778 |
+
route: null
|
| 779 |
+
-
|
| 780 |
+
custom: []
|
| 781 |
+
httpMethods:
|
| 782 |
+
- GET
|
| 783 |
+
uri: api/news
|
| 784 |
+
metadata:
|
| 785 |
+
custom: []
|
| 786 |
+
groupName: Endpoints
|
| 787 |
+
groupDescription: ''
|
| 788 |
+
subgroup: ''
|
| 789 |
+
subgroupDescription: ''
|
| 790 |
+
title: ''
|
| 791 |
+
description: ''
|
| 792 |
+
authenticated: false
|
| 793 |
+
deprecated: false
|
| 794 |
+
headers:
|
| 795 |
+
Content-Type: application/json
|
| 796 |
+
Accept: application/json
|
| 797 |
+
urlParameters: []
|
| 798 |
+
cleanUrlParameters: []
|
| 799 |
+
queryParameters: []
|
| 800 |
+
cleanQueryParameters: []
|
| 801 |
+
bodyParameters: []
|
| 802 |
+
cleanBodyParameters: []
|
| 803 |
+
fileParameters: []
|
| 804 |
+
responses:
|
| 805 |
+
-
|
| 806 |
+
custom: []
|
| 807 |
+
status: 404
|
| 808 |
+
content: '{"success":false,"message":"The route api\/news could not be found."}'
|
| 809 |
+
headers:
|
| 810 |
+
cache-control: 'no-cache, private'
|
| 811 |
+
content-type: application/json
|
| 812 |
+
access-control-allow-origin: '*'
|
| 813 |
+
description: null
|
| 814 |
+
responseFields: []
|
| 815 |
+
auth: []
|
| 816 |
+
controller: null
|
| 817 |
+
method: null
|
| 818 |
+
route: null
|
| 819 |
+
-
|
| 820 |
+
custom: []
|
| 821 |
+
httpMethods:
|
| 822 |
+
- GET
|
| 823 |
+
uri: api/news/refresh
|
| 824 |
+
metadata:
|
| 825 |
+
custom: []
|
| 826 |
+
groupName: Endpoints
|
| 827 |
+
groupDescription: ''
|
| 828 |
+
subgroup: ''
|
| 829 |
+
subgroupDescription: ''
|
| 830 |
+
title: ''
|
| 831 |
+
description: ''
|
| 832 |
+
authenticated: false
|
| 833 |
+
deprecated: false
|
| 834 |
+
headers:
|
| 835 |
+
Content-Type: application/json
|
| 836 |
+
Accept: application/json
|
| 837 |
+
urlParameters: []
|
| 838 |
+
cleanUrlParameters: []
|
| 839 |
+
queryParameters: []
|
| 840 |
+
cleanQueryParameters: []
|
| 841 |
+
bodyParameters: []
|
| 842 |
+
cleanBodyParameters: []
|
| 843 |
+
fileParameters: []
|
| 844 |
+
responses:
|
| 845 |
+
-
|
| 846 |
+
custom: []
|
| 847 |
+
status: 404
|
| 848 |
+
content: '{"success":false,"message":"The route api\/news\/refresh could not be found."}'
|
| 849 |
+
headers:
|
| 850 |
+
cache-control: 'no-cache, private'
|
| 851 |
+
content-type: application/json
|
| 852 |
+
access-control-allow-origin: '*'
|
| 853 |
+
description: null
|
| 854 |
+
responseFields: []
|
| 855 |
+
auth: []
|
| 856 |
+
controller: null
|
| 857 |
+
method: null
|
| 858 |
+
route: null
|
| 859 |
+
-
|
| 860 |
+
custom: []
|
| 861 |
+
httpMethods:
|
| 862 |
+
- GET
|
| 863 |
+
uri: api/news/clear
|
| 864 |
+
metadata:
|
| 865 |
+
custom: []
|
| 866 |
+
groupName: Endpoints
|
| 867 |
+
groupDescription: ''
|
| 868 |
+
subgroup: ''
|
| 869 |
+
subgroupDescription: ''
|
| 870 |
+
title: ''
|
| 871 |
+
description: ''
|
| 872 |
+
authenticated: false
|
| 873 |
+
deprecated: false
|
| 874 |
+
headers:
|
| 875 |
+
Content-Type: application/json
|
| 876 |
+
Accept: application/json
|
| 877 |
+
urlParameters: []
|
| 878 |
+
cleanUrlParameters: []
|
| 879 |
+
queryParameters: []
|
| 880 |
+
cleanQueryParameters: []
|
| 881 |
+
bodyParameters: []
|
| 882 |
+
cleanBodyParameters: []
|
| 883 |
+
fileParameters: []
|
| 884 |
+
responses:
|
| 885 |
+
-
|
| 886 |
+
custom: []
|
| 887 |
+
status: 404
|
| 888 |
+
content: '{"success":false,"message":"The route api\/news\/clear could not be found."}'
|
| 889 |
+
headers:
|
| 890 |
+
cache-control: 'no-cache, private'
|
| 891 |
+
content-type: application/json
|
| 892 |
+
access-control-allow-origin: '*'
|
| 893 |
+
description: null
|
| 894 |
+
responseFields: []
|
| 895 |
+
auth: []
|
| 896 |
+
controller: null
|
| 897 |
+
method: null
|
| 898 |
+
route: null
|
| 899 |
+
-
|
| 900 |
+
custom: []
|
| 901 |
+
httpMethods:
|
| 902 |
+
- GET
|
| 903 |
+
uri: api/news/categories
|
| 904 |
+
metadata:
|
| 905 |
+
custom: []
|
| 906 |
+
groupName: Endpoints
|
| 907 |
+
groupDescription: ''
|
| 908 |
+
subgroup: ''
|
| 909 |
+
subgroupDescription: ''
|
| 910 |
+
title: ''
|
| 911 |
+
description: ''
|
| 912 |
+
authenticated: false
|
| 913 |
+
deprecated: false
|
| 914 |
+
headers:
|
| 915 |
+
Content-Type: application/json
|
| 916 |
+
Accept: application/json
|
| 917 |
+
urlParameters: []
|
| 918 |
+
cleanUrlParameters: []
|
| 919 |
+
queryParameters: []
|
| 920 |
+
cleanQueryParameters: []
|
| 921 |
+
bodyParameters: []
|
| 922 |
+
cleanBodyParameters: []
|
| 923 |
+
fileParameters: []
|
| 924 |
+
responses:
|
| 925 |
+
-
|
| 926 |
+
custom: []
|
| 927 |
+
status: 404
|
| 928 |
+
content: '{"success":false,"message":"The route api\/news\/categories could not be found."}'
|
| 929 |
+
headers:
|
| 930 |
+
cache-control: 'no-cache, private'
|
| 931 |
+
content-type: application/json
|
| 932 |
+
access-control-allow-origin: '*'
|
| 933 |
+
description: null
|
| 934 |
+
responseFields: []
|
| 935 |
+
auth: []
|
| 936 |
+
controller: null
|
| 937 |
+
method: null
|
| 938 |
+
route: null
|
| 939 |
+
-
|
| 940 |
+
custom: []
|
| 941 |
+
httpMethods:
|
| 942 |
+
- POST
|
| 943 |
+
uri: 'api/news/{articleId}/share'
|
| 944 |
+
metadata:
|
| 945 |
+
custom: []
|
| 946 |
+
groupName: Endpoints
|
| 947 |
+
groupDescription: ''
|
| 948 |
+
subgroup: ''
|
| 949 |
+
subgroupDescription: ''
|
| 950 |
+
title: ''
|
| 951 |
+
description: ''
|
| 952 |
+
authenticated: false
|
| 953 |
+
deprecated: false
|
| 954 |
+
headers:
|
| 955 |
+
Content-Type: application/json
|
| 956 |
+
Accept: application/json
|
| 957 |
+
urlParameters:
|
| 958 |
+
articleId:
|
| 959 |
+
custom: []
|
| 960 |
+
name: articleId
|
| 961 |
+
description: ''
|
| 962 |
+
required: true
|
| 963 |
+
example: consequatur
|
| 964 |
+
type: string
|
| 965 |
+
enumValues: []
|
| 966 |
+
exampleWasSpecified: false
|
| 967 |
+
nullable: false
|
| 968 |
+
deprecated: false
|
| 969 |
+
cleanUrlParameters:
|
| 970 |
+
articleId: consequatur
|
| 971 |
+
queryParameters: []
|
| 972 |
+
cleanQueryParameters: []
|
| 973 |
+
bodyParameters: []
|
| 974 |
+
cleanBodyParameters: []
|
| 975 |
+
fileParameters: []
|
| 976 |
+
responses: []
|
| 977 |
+
responseFields: []
|
| 978 |
+
auth: []
|
| 979 |
+
controller: null
|
| 980 |
+
method: null
|
| 981 |
+
route: null
|
| 982 |
+
-
|
| 983 |
+
custom: []
|
| 984 |
+
httpMethods:
|
| 985 |
+
- POST
|
| 986 |
+
uri: 'api/news/{articleId}/save'
|
| 987 |
+
metadata:
|
| 988 |
+
custom: []
|
| 989 |
+
groupName: Endpoints
|
| 990 |
+
groupDescription: ''
|
| 991 |
+
subgroup: ''
|
| 992 |
+
subgroupDescription: ''
|
| 993 |
+
title: ''
|
| 994 |
+
description: ''
|
| 995 |
+
authenticated: false
|
| 996 |
+
deprecated: false
|
| 997 |
+
headers:
|
| 998 |
+
Content-Type: application/json
|
| 999 |
+
Accept: application/json
|
| 1000 |
+
urlParameters:
|
| 1001 |
+
articleId:
|
| 1002 |
+
custom: []
|
| 1003 |
+
name: articleId
|
| 1004 |
+
description: ''
|
| 1005 |
+
required: true
|
| 1006 |
+
example: consequatur
|
| 1007 |
+
type: string
|
| 1008 |
+
enumValues: []
|
| 1009 |
+
exampleWasSpecified: false
|
| 1010 |
+
nullable: false
|
| 1011 |
+
deprecated: false
|
| 1012 |
+
cleanUrlParameters:
|
| 1013 |
+
articleId: consequatur
|
| 1014 |
+
queryParameters: []
|
| 1015 |
+
cleanQueryParameters: []
|
| 1016 |
+
bodyParameters: []
|
| 1017 |
+
cleanBodyParameters: []
|
| 1018 |
+
fileParameters: []
|
| 1019 |
+
responses: []
|
| 1020 |
+
responseFields: []
|
| 1021 |
+
auth: []
|
| 1022 |
+
controller: null
|
| 1023 |
+
method: null
|
| 1024 |
+
route: null
|
| 1025 |
+
-
|
| 1026 |
+
custom: []
|
| 1027 |
+
httpMethods:
|
| 1028 |
+
- GET
|
| 1029 |
+
uri: api/news/saved
|
| 1030 |
+
metadata:
|
| 1031 |
+
custom: []
|
| 1032 |
+
groupName: Endpoints
|
| 1033 |
+
groupDescription: ''
|
| 1034 |
+
subgroup: ''
|
| 1035 |
+
subgroupDescription: ''
|
| 1036 |
+
title: ''
|
| 1037 |
+
description: ''
|
| 1038 |
+
authenticated: false
|
| 1039 |
+
deprecated: false
|
| 1040 |
+
headers:
|
| 1041 |
+
Content-Type: application/json
|
| 1042 |
+
Accept: application/json
|
| 1043 |
+
urlParameters: []
|
| 1044 |
+
cleanUrlParameters: []
|
| 1045 |
+
queryParameters: []
|
| 1046 |
+
cleanQueryParameters: []
|
| 1047 |
+
bodyParameters: []
|
| 1048 |
+
cleanBodyParameters: []
|
| 1049 |
+
fileParameters: []
|
| 1050 |
+
responses:
|
| 1051 |
+
-
|
| 1052 |
+
custom: []
|
| 1053 |
+
status: 404
|
| 1054 |
+
content: '{"success":false,"message":"The route api\/news\/saved could not be found."}'
|
| 1055 |
+
headers:
|
| 1056 |
+
cache-control: 'no-cache, private'
|
| 1057 |
+
content-type: application/json
|
| 1058 |
+
access-control-allow-origin: '*'
|
| 1059 |
+
description: null
|
| 1060 |
+
responseFields: []
|
| 1061 |
+
auth: []
|
| 1062 |
+
controller: null
|
| 1063 |
+
method: null
|
| 1064 |
+
route: null
|
| 1065 |
+
-
|
| 1066 |
+
custom: []
|
| 1067 |
+
httpMethods:
|
| 1068 |
+
- DELETE
|
| 1069 |
+
uri: 'api/news/saved/{savedArticleId}'
|
| 1070 |
+
metadata:
|
| 1071 |
+
custom: []
|
| 1072 |
+
groupName: Endpoints
|
| 1073 |
+
groupDescription: ''
|
| 1074 |
+
subgroup: ''
|
| 1075 |
+
subgroupDescription: ''
|
| 1076 |
+
title: ''
|
| 1077 |
+
description: ''
|
| 1078 |
+
authenticated: false
|
| 1079 |
+
deprecated: false
|
| 1080 |
+
headers:
|
| 1081 |
+
Content-Type: application/json
|
| 1082 |
+
Accept: application/json
|
| 1083 |
+
urlParameters:
|
| 1084 |
+
savedArticleId:
|
| 1085 |
+
custom: []
|
| 1086 |
+
name: savedArticleId
|
| 1087 |
+
description: ''
|
| 1088 |
+
required: true
|
| 1089 |
+
example: consequatur
|
| 1090 |
+
type: string
|
| 1091 |
+
enumValues: []
|
| 1092 |
+
exampleWasSpecified: false
|
| 1093 |
+
nullable: false
|
| 1094 |
+
deprecated: false
|
| 1095 |
+
cleanUrlParameters:
|
| 1096 |
+
savedArticleId: consequatur
|
| 1097 |
+
queryParameters: []
|
| 1098 |
+
cleanQueryParameters: []
|
| 1099 |
+
bodyParameters: []
|
| 1100 |
+
cleanBodyParameters: []
|
| 1101 |
+
fileParameters: []
|
| 1102 |
+
responses: []
|
| 1103 |
+
responseFields: []
|
| 1104 |
+
auth: []
|
| 1105 |
+
controller: null
|
| 1106 |
+
method: null
|
| 1107 |
+
route: null
|
| 1108 |
+
-
|
| 1109 |
+
custom: []
|
| 1110 |
+
httpMethods:
|
| 1111 |
+
- POST
|
| 1112 |
+
uri: api/ai/run
|
| 1113 |
+
metadata:
|
| 1114 |
+
custom: []
|
| 1115 |
+
groupName: Endpoints
|
| 1116 |
+
groupDescription: ''
|
| 1117 |
+
subgroup: ''
|
| 1118 |
+
subgroupDescription: ''
|
| 1119 |
+
title: ''
|
| 1120 |
+
description: ''
|
| 1121 |
+
authenticated: false
|
| 1122 |
+
deprecated: false
|
| 1123 |
+
headers:
|
| 1124 |
+
Content-Type: application/json
|
| 1125 |
+
Accept: application/json
|
| 1126 |
+
urlParameters: []
|
| 1127 |
+
cleanUrlParameters: []
|
| 1128 |
+
queryParameters: []
|
| 1129 |
+
cleanQueryParameters: []
|
| 1130 |
+
bodyParameters:
|
| 1131 |
+
num_smiles:
|
| 1132 |
+
custom: []
|
| 1133 |
+
name: num_smiles
|
| 1134 |
+
description: ''
|
| 1135 |
+
required: true
|
| 1136 |
+
example: 17
|
| 1137 |
+
type: integer
|
| 1138 |
+
enumValues: []
|
| 1139 |
+
exampleWasSpecified: false
|
| 1140 |
+
nullable: false
|
| 1141 |
+
deprecated: false
|
| 1142 |
+
top_k:
|
| 1143 |
+
custom: []
|
| 1144 |
+
name: top_k
|
| 1145 |
+
description: ''
|
| 1146 |
+
required: true
|
| 1147 |
+
example: 17
|
| 1148 |
+
type: integer
|
| 1149 |
+
enumValues: []
|
| 1150 |
+
exampleWasSpecified: false
|
| 1151 |
+
nullable: false
|
| 1152 |
+
deprecated: false
|
| 1153 |
+
Docking:
|
| 1154 |
+
custom: []
|
| 1155 |
+
name: Docking
|
| 1156 |
+
description: ''
|
| 1157 |
+
required: true
|
| 1158 |
+
example: 11613.31890586
|
| 1159 |
+
type: number
|
| 1160 |
+
enumValues: []
|
| 1161 |
+
exampleWasSpecified: false
|
| 1162 |
+
nullable: false
|
| 1163 |
+
deprecated: false
|
| 1164 |
+
DeepPurpose_Affinity:
|
| 1165 |
+
custom: []
|
| 1166 |
+
name: DeepPurpose_Affinity
|
| 1167 |
+
description: ''
|
| 1168 |
+
required: true
|
| 1169 |
+
example: 11613.31890586
|
| 1170 |
+
type: number
|
| 1171 |
+
enumValues: []
|
| 1172 |
+
exampleWasSpecified: false
|
| 1173 |
+
nullable: false
|
| 1174 |
+
deprecated: false
|
| 1175 |
+
SA:
|
| 1176 |
+
custom: []
|
| 1177 |
+
name: SA
|
| 1178 |
+
description: ''
|
| 1179 |
+
required: true
|
| 1180 |
+
example: 11613.31890586
|
| 1181 |
+
type: number
|
| 1182 |
+
enumValues: []
|
| 1183 |
+
exampleWasSpecified: false
|
| 1184 |
+
nullable: false
|
| 1185 |
+
deprecated: false
|
| 1186 |
+
TPSA:
|
| 1187 |
+
custom: []
|
| 1188 |
+
name: TPSA
|
| 1189 |
+
description: ''
|
| 1190 |
+
required: true
|
| 1191 |
+
example: 11613.31890586
|
| 1192 |
+
type: number
|
| 1193 |
+
enumValues: []
|
| 1194 |
+
exampleWasSpecified: false
|
| 1195 |
+
nullable: false
|
| 1196 |
+
deprecated: false
|
| 1197 |
+
MW:
|
| 1198 |
+
custom: []
|
| 1199 |
+
name: MW
|
| 1200 |
+
description: ''
|
| 1201 |
+
required: true
|
| 1202 |
+
example: 11613.31890586
|
| 1203 |
+
type: number
|
| 1204 |
+
enumValues: []
|
| 1205 |
+
exampleWasSpecified: false
|
| 1206 |
+
nullable: false
|
| 1207 |
+
deprecated: false
|
| 1208 |
+
cleanBodyParameters:
|
| 1209 |
+
num_smiles: 17
|
| 1210 |
+
top_k: 17
|
| 1211 |
+
Docking: 11613.31890586
|
| 1212 |
+
DeepPurpose_Affinity: 11613.31890586
|
| 1213 |
+
SA: 11613.31890586
|
| 1214 |
+
TPSA: 11613.31890586
|
| 1215 |
+
MW: 11613.31890586
|
| 1216 |
+
fileParameters: []
|
| 1217 |
+
responses: []
|
| 1218 |
+
responseFields: []
|
| 1219 |
+
auth: []
|
| 1220 |
+
controller: null
|
| 1221 |
+
method: null
|
| 1222 |
+
route: null
|
| 1223 |
+
-
|
| 1224 |
+
custom: []
|
| 1225 |
+
httpMethods:
|
| 1226 |
+
- GET
|
| 1227 |
+
uri: 'api/ai/status/{job_job_id}'
|
| 1228 |
+
metadata:
|
| 1229 |
+
custom: []
|
| 1230 |
+
groupName: Endpoints
|
| 1231 |
+
groupDescription: ''
|
| 1232 |
+
subgroup: ''
|
| 1233 |
+
subgroupDescription: ''
|
| 1234 |
+
title: ''
|
| 1235 |
+
description: ''
|
| 1236 |
+
authenticated: false
|
| 1237 |
+
deprecated: false
|
| 1238 |
+
headers:
|
| 1239 |
+
Content-Type: application/json
|
| 1240 |
+
Accept: application/json
|
| 1241 |
+
urlParameters:
|
| 1242 |
+
job_job_id:
|
| 1243 |
+
custom: []
|
| 1244 |
+
name: job_job_id
|
| 1245 |
+
description: 'The ID of the job job.'
|
| 1246 |
+
required: true
|
| 1247 |
+
example: consequatur
|
| 1248 |
+
type: string
|
| 1249 |
+
enumValues: []
|
| 1250 |
+
exampleWasSpecified: false
|
| 1251 |
+
nullable: false
|
| 1252 |
+
deprecated: false
|
| 1253 |
+
cleanUrlParameters:
|
| 1254 |
+
job_job_id: consequatur
|
| 1255 |
+
queryParameters: []
|
| 1256 |
+
cleanQueryParameters: []
|
| 1257 |
+
bodyParameters: []
|
| 1258 |
+
cleanBodyParameters: []
|
| 1259 |
+
fileParameters: []
|
| 1260 |
+
responses:
|
| 1261 |
+
-
|
| 1262 |
+
custom: []
|
| 1263 |
+
status: 404
|
| 1264 |
+
content: '{"success":false,"message":"The route api\/ai\/status\/consequatur could not be found."}'
|
| 1265 |
+
headers:
|
| 1266 |
+
cache-control: 'no-cache, private'
|
| 1267 |
+
content-type: application/json
|
| 1268 |
+
access-control-allow-origin: '*'
|
| 1269 |
+
description: null
|
| 1270 |
+
responseFields: []
|
| 1271 |
+
auth: []
|
| 1272 |
+
controller: null
|
| 1273 |
+
method: null
|
| 1274 |
+
route: null
|
| 1275 |
+
-
|
| 1276 |
+
custom: []
|
| 1277 |
+
httpMethods:
|
| 1278 |
+
- GET
|
| 1279 |
+
uri: 'api/ai/preview/{job_job_id}'
|
| 1280 |
+
metadata:
|
| 1281 |
+
custom: []
|
| 1282 |
+
groupName: Endpoints
|
| 1283 |
+
groupDescription: ''
|
| 1284 |
+
subgroup: ''
|
| 1285 |
+
subgroupDescription: ''
|
| 1286 |
+
title: ''
|
| 1287 |
+
description: ''
|
| 1288 |
+
authenticated: false
|
| 1289 |
+
deprecated: false
|
| 1290 |
+
headers:
|
| 1291 |
+
Content-Type: application/json
|
| 1292 |
+
Accept: application/json
|
| 1293 |
+
urlParameters:
|
| 1294 |
+
job_job_id:
|
| 1295 |
+
custom: []
|
| 1296 |
+
name: job_job_id
|
| 1297 |
+
description: 'The ID of the job job.'
|
| 1298 |
+
required: true
|
| 1299 |
+
example: consequatur
|
| 1300 |
+
type: string
|
| 1301 |
+
enumValues: []
|
| 1302 |
+
exampleWasSpecified: false
|
| 1303 |
+
nullable: false
|
| 1304 |
+
deprecated: false
|
| 1305 |
+
cleanUrlParameters:
|
| 1306 |
+
job_job_id: consequatur
|
| 1307 |
+
queryParameters: []
|
| 1308 |
+
cleanQueryParameters: []
|
| 1309 |
+
bodyParameters: []
|
| 1310 |
+
cleanBodyParameters: []
|
| 1311 |
+
fileParameters: []
|
| 1312 |
+
responses:
|
| 1313 |
+
-
|
| 1314 |
+
custom: []
|
| 1315 |
+
status: 404
|
| 1316 |
+
content: '{"success":false,"message":"The route api\/ai\/preview\/consequatur could not be found."}'
|
| 1317 |
+
headers:
|
| 1318 |
+
cache-control: 'no-cache, private'
|
| 1319 |
+
content-type: application/json
|
| 1320 |
+
access-control-allow-origin: '*'
|
| 1321 |
+
description: null
|
| 1322 |
+
responseFields: []
|
| 1323 |
+
auth: []
|
| 1324 |
+
controller: null
|
| 1325 |
+
method: null
|
| 1326 |
+
route: null
|
| 1327 |
+
-
|
| 1328 |
+
custom: []
|
| 1329 |
+
httpMethods:
|
| 1330 |
+
- GET
|
| 1331 |
+
uri: 'api/ai/download/top/{job_job_id}'
|
| 1332 |
+
metadata:
|
| 1333 |
+
custom: []
|
| 1334 |
+
groupName: Endpoints
|
| 1335 |
+
groupDescription: ''
|
| 1336 |
+
subgroup: ''
|
| 1337 |
+
subgroupDescription: ''
|
| 1338 |
+
title: ''
|
| 1339 |
+
description: ''
|
| 1340 |
+
authenticated: false
|
| 1341 |
+
deprecated: false
|
| 1342 |
+
headers:
|
| 1343 |
+
Content-Type: application/json
|
| 1344 |
+
Accept: application/json
|
| 1345 |
+
urlParameters:
|
| 1346 |
+
job_job_id:
|
| 1347 |
+
custom: []
|
| 1348 |
+
name: job_job_id
|
| 1349 |
+
description: 'The ID of the job job.'
|
| 1350 |
+
required: true
|
| 1351 |
+
example: consequatur
|
| 1352 |
+
type: string
|
| 1353 |
+
enumValues: []
|
| 1354 |
+
exampleWasSpecified: false
|
| 1355 |
+
nullable: false
|
| 1356 |
+
deprecated: false
|
| 1357 |
+
cleanUrlParameters:
|
| 1358 |
+
job_job_id: consequatur
|
| 1359 |
+
queryParameters: []
|
| 1360 |
+
cleanQueryParameters: []
|
| 1361 |
+
bodyParameters: []
|
| 1362 |
+
cleanBodyParameters: []
|
| 1363 |
+
fileParameters: []
|
| 1364 |
+
responses:
|
| 1365 |
+
-
|
| 1366 |
+
custom: []
|
| 1367 |
+
status: 404
|
| 1368 |
+
content: '{"success":false,"message":"The route api\/ai\/download\/top\/consequatur could not be found."}'
|
| 1369 |
+
headers:
|
| 1370 |
+
cache-control: 'no-cache, private'
|
| 1371 |
+
content-type: application/json
|
| 1372 |
+
access-control-allow-origin: '*'
|
| 1373 |
+
description: null
|
| 1374 |
+
responseFields: []
|
| 1375 |
+
auth: []
|
| 1376 |
+
controller: null
|
| 1377 |
+
method: null
|
| 1378 |
+
route: null
|
| 1379 |
+
-
|
| 1380 |
+
custom: []
|
| 1381 |
+
httpMethods:
|
| 1382 |
+
- GET
|
| 1383 |
+
uri: 'api/ai/download/full/{job_job_id}'
|
| 1384 |
+
metadata:
|
| 1385 |
+
custom: []
|
| 1386 |
+
groupName: Endpoints
|
| 1387 |
+
groupDescription: ''
|
| 1388 |
+
subgroup: ''
|
| 1389 |
+
subgroupDescription: ''
|
| 1390 |
+
title: ''
|
| 1391 |
+
description: ''
|
| 1392 |
+
authenticated: false
|
| 1393 |
+
deprecated: false
|
| 1394 |
+
headers:
|
| 1395 |
+
Content-Type: application/json
|
| 1396 |
+
Accept: application/json
|
| 1397 |
+
urlParameters:
|
| 1398 |
+
job_job_id:
|
| 1399 |
+
custom: []
|
| 1400 |
+
name: job_job_id
|
| 1401 |
+
description: 'The ID of the job job.'
|
| 1402 |
+
required: true
|
| 1403 |
+
example: consequatur
|
| 1404 |
+
type: string
|
| 1405 |
+
enumValues: []
|
| 1406 |
+
exampleWasSpecified: false
|
| 1407 |
+
nullable: false
|
| 1408 |
+
deprecated: false
|
| 1409 |
+
cleanUrlParameters:
|
| 1410 |
+
job_job_id: consequatur
|
| 1411 |
+
queryParameters: []
|
| 1412 |
+
cleanQueryParameters: []
|
| 1413 |
+
bodyParameters: []
|
| 1414 |
+
cleanBodyParameters: []
|
| 1415 |
+
fileParameters: []
|
| 1416 |
+
responses:
|
| 1417 |
+
-
|
| 1418 |
+
custom: []
|
| 1419 |
+
status: 404
|
| 1420 |
+
content: '{"success":false,"message":"The route api\/ai\/download\/full\/consequatur could not be found."}'
|
| 1421 |
+
headers:
|
| 1422 |
+
cache-control: 'no-cache, private'
|
| 1423 |
+
content-type: application/json
|
| 1424 |
+
access-control-allow-origin: '*'
|
| 1425 |
+
description: null
|
| 1426 |
+
responseFields: []
|
| 1427 |
+
auth: []
|
| 1428 |
+
controller: null
|
| 1429 |
+
method: null
|
| 1430 |
+
route: null
|
| 1431 |
+
-
|
| 1432 |
+
custom: []
|
| 1433 |
+
httpMethods:
|
| 1434 |
+
- GET
|
| 1435 |
+
uri: api/ai/history
|
| 1436 |
+
metadata:
|
| 1437 |
+
custom: []
|
| 1438 |
+
groupName: Endpoints
|
| 1439 |
+
groupDescription: ''
|
| 1440 |
+
subgroup: ''
|
| 1441 |
+
subgroupDescription: ''
|
| 1442 |
+
title: ''
|
| 1443 |
+
description: ''
|
| 1444 |
+
authenticated: false
|
| 1445 |
+
deprecated: false
|
| 1446 |
+
headers:
|
| 1447 |
+
Content-Type: application/json
|
| 1448 |
+
Accept: application/json
|
| 1449 |
+
urlParameters: []
|
| 1450 |
+
cleanUrlParameters: []
|
| 1451 |
+
queryParameters: []
|
| 1452 |
+
cleanQueryParameters: []
|
| 1453 |
+
bodyParameters: []
|
| 1454 |
+
cleanBodyParameters: []
|
| 1455 |
+
fileParameters: []
|
| 1456 |
+
responses:
|
| 1457 |
+
-
|
| 1458 |
+
custom: []
|
| 1459 |
+
status: 404
|
| 1460 |
+
content: '{"success":false,"message":"The route api\/ai\/history could not be found."}'
|
| 1461 |
+
headers:
|
| 1462 |
+
cache-control: 'no-cache, private'
|
| 1463 |
+
content-type: application/json
|
| 1464 |
+
access-control-allow-origin: '*'
|
| 1465 |
+
description: null
|
| 1466 |
+
responseFields: []
|
| 1467 |
+
auth: []
|
| 1468 |
+
controller: null
|
| 1469 |
+
method: null
|
| 1470 |
+
route: null
|
| 1471 |
+
-
|
| 1472 |
+
custom: []
|
| 1473 |
+
httpMethods:
|
| 1474 |
+
- POST
|
| 1475 |
+
uri: api/upload-file
|
| 1476 |
+
metadata:
|
| 1477 |
+
custom: []
|
| 1478 |
+
groupName: Endpoints
|
| 1479 |
+
groupDescription: ''
|
| 1480 |
+
subgroup: ''
|
| 1481 |
+
subgroupDescription: ''
|
| 1482 |
+
title: ''
|
| 1483 |
+
description: ''
|
| 1484 |
+
authenticated: false
|
| 1485 |
+
deprecated: false
|
| 1486 |
+
headers:
|
| 1487 |
+
Content-Type: application/json
|
| 1488 |
+
Accept: application/json
|
| 1489 |
+
urlParameters: []
|
| 1490 |
+
cleanUrlParameters: []
|
| 1491 |
+
queryParameters: []
|
| 1492 |
+
cleanQueryParameters: []
|
| 1493 |
+
bodyParameters: []
|
| 1494 |
+
cleanBodyParameters: []
|
| 1495 |
+
fileParameters: []
|
| 1496 |
+
responses: []
|
| 1497 |
+
responseFields: []
|
| 1498 |
+
auth: []
|
| 1499 |
+
controller: null
|
| 1500 |
+
method: null
|
| 1501 |
+
route: null
|
.scribe/endpoints/custom.0.yaml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# To include an endpoint that isn't a part of your Laravel app (or belongs to a vendor package),
|
| 2 |
+
# you can define it in a custom.*.yaml file, like this one.
|
| 3 |
+
# Each custom file should contain an array of endpoints. Here's an example:
|
| 4 |
+
# See https://scribe.knuckles.wtf/laravel/documenting/custom-endpoints#extra-sorting-groups-in-custom-endpoint-files for more options
|
| 5 |
+
|
| 6 |
+
#- httpMethods:
|
| 7 |
+
# - POST
|
| 8 |
+
# uri: api/doSomething/{param}
|
| 9 |
+
# metadata:
|
| 10 |
+
# groupName: The group the endpoint belongs to. Can be a new group or an existing group.
|
| 11 |
+
# groupDescription: A description for the group. You don't need to set this for every endpoint; once is enough.
|
| 12 |
+
# subgroup: You can add a subgroup, too.
|
| 13 |
+
# title: Do something
|
| 14 |
+
# description: 'This endpoint allows you to do something.'
|
| 15 |
+
# authenticated: false
|
| 16 |
+
# headers:
|
| 17 |
+
# Content-Type: application/json
|
| 18 |
+
# Accept: application/json
|
| 19 |
+
# urlParameters:
|
| 20 |
+
# param:
|
| 21 |
+
# name: param
|
| 22 |
+
# description: A URL param for no reason.
|
| 23 |
+
# required: true
|
| 24 |
+
# example: 2
|
| 25 |
+
# type: integer
|
| 26 |
+
# queryParameters:
|
| 27 |
+
# speed:
|
| 28 |
+
# name: speed
|
| 29 |
+
# description: How fast the thing should be done. Can be `slow` or `fast`.
|
| 30 |
+
# required: false
|
| 31 |
+
# example: fast
|
| 32 |
+
# type: string
|
| 33 |
+
# bodyParameters:
|
| 34 |
+
# something:
|
| 35 |
+
# name: something
|
| 36 |
+
# description: The things we should do.
|
| 37 |
+
# required: true
|
| 38 |
+
# example:
|
| 39 |
+
# - string 1
|
| 40 |
+
# - string 2
|
| 41 |
+
# type: 'string[]'
|
| 42 |
+
# responses:
|
| 43 |
+
# - status: 200
|
| 44 |
+
# description: 'When the thing was done smoothly.'
|
| 45 |
+
# content: # Your response content can be an object, an array, a string or empty.
|
| 46 |
+
# {
|
| 47 |
+
# "hey": "ho ho ho"
|
| 48 |
+
# }
|
| 49 |
+
# responseFields:
|
| 50 |
+
# hey:
|
| 51 |
+
# name: hey
|
| 52 |
+
# description: Who knows?
|
| 53 |
+
# type: string # This is optional
|
.scribe/intro.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Introduction
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
<aside>
|
| 6 |
+
<strong>Base URL</strong>: <code>https://america-hyperlipemic-grazyna.ngrok-free.dev/</code>
|
| 7 |
+
</aside>
|
| 8 |
+
|
| 9 |
+
This documentation aims to provide all the information you need to work with our API.
|
| 10 |
+
|
| 11 |
+
<aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
|
| 12 |
+
You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
|
| 13 |
+
|
API.md
ADDED
|
@@ -0,0 +1,3095 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
content = '''# 📡 AILIXIR API Reference
|
| 2 |
+
|
| 3 |
+
**Version:** 2.1 | **Last Updated:** May 29, 2026 | **Status:** Production Ready
|
| 4 |
+
|
| 5 |
+
Complete API specification for AILIXIR-Backend, including all endpoints, authentication, request/response schemas, error handling, and integration examples.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 📋 Table of Contents
|
| 10 |
+
|
| 11 |
+
- [Overview](#overview)
|
| 12 |
+
- [Authentication](#authentication)
|
| 13 |
+
- [Base URLs](#base-urls)
|
| 14 |
+
- [Common Patterns](#common-patterns)
|
| 15 |
+
- [Health & Status](#health--status)
|
| 16 |
+
- [Authentication Endpoints](#authentication-endpoints)
|
| 17 |
+
- [AI Integration Endpoints](#ai-integration-endpoints)
|
| 18 |
+
- [AI Service Endpoints](#ai-service-endpoints)
|
| 19 |
+
- [AI Agent Endpoints (Chemistry AI)](#ai-agent-endpoints-chemistry-ai)
|
| 20 |
+
- [Chemical Search Endpoints](#chemical-search-endpoints)
|
| 21 |
+
- [Docking API](#docking-api)
|
| 22 |
+
- [Drug Repurposing API](#drug-repurposing-api)
|
| 23 |
+
- [Convert SMILES API](#convert-smiles-api)
|
| 24 |
+
- [User Management Endpoints](#user-management-endpoints)
|
| 25 |
+
- [Awards And Scientists API](#awards-and-scientists-api)
|
| 26 |
+
- [News API](#news-api)
|
| 27 |
+
- [Admet Prediction API](#admet-prediction-api)
|
| 28 |
+
- [AI Generation API](#ai-generation-api)
|
| 29 |
+
- [Error Handling](#error-handling)
|
| 30 |
+
- [Rate Limiting](#rate-limiting)
|
| 31 |
+
- [Integration Examples](#integration-examples)
|
| 32 |
+
- [Support](#support)
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## Overview
|
| 37 |
+
|
| 38 |
+
AILIXIR provides a RESTful API for accessing all drug discovery services. The API is built with:
|
| 39 |
+
|
| 40 |
+
- **Framework:** Laravel 11 with Sanctum authentication
|
| 41 |
+
- **Response Format:** JSON
|
| 42 |
+
- **Authentication:** JWT Bearer tokens
|
| 43 |
+
- **Rate Limiting:** In place (contact admin for limits)
|
| 44 |
+
- **Versioning:** URL-based (`/api/v1/...`)
|
| 45 |
+
|
| 46 |
+
**Key Features:**
|
| 47 |
+
|
| 48 |
+
- Asynchronous job processing (long-running AI tasks)
|
| 49 |
+
- Real-time health monitoring
|
| 50 |
+
- User authentication and authorization
|
| 51 |
+
- Result caching and versioning
|
| 52 |
+
- File upload/download support
|
| 53 |
+
- AI-powered chemistry analysis agent with conversation memory
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## Authentication
|
| 58 |
+
|
| 59 |
+
### Overview
|
| 60 |
+
|
| 61 |
+
AILIXIR uses **Laravel Sanctum** for API authentication. Most endpoints require a valid JWT bearer token.
|
| 62 |
+
|
| 63 |
+
### Types of Tokens
|
| 64 |
+
|
| 65 |
+
| Token Type | Purpose | Duration |
|
| 66 |
+
| ----------------- | ---------------------------- | ------------- |
|
| 67 |
+
| **Access Token** | Query user data, submit jobs | Session-based |
|
| 68 |
+
| **API Token** | Long-lived access (optional) | 365 days |
|
| 69 |
+
| **Refresh Token** | Renew access after expiry | 14 days |
|
| 70 |
+
|
| 71 |
+
### Authentication Header
|
| 72 |
+
|
| 73 |
+
All authenticated requests must include:
|
| 74 |
+
|
| 75 |
+
```http
|
| 76 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
### Token Generation
|
| 80 |
+
|
| 81 |
+
Tokens are issued after successful login or registration. Store securely (never in localStorage for sensitive apps).
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## Base URLs
|
| 86 |
+
|
| 87 |
+
### Production
|
| 88 |
+
|
| 89 |
+
```
|
| 90 |
+
https://ailixir.pharmaai.io/api
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### Development (Local)
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
http://localhost:8080/api
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
### Service-Specific Discovery
|
| 100 |
+
|
| 101 |
+
Each microservice exposes its own API discovery endpoint:
|
| 102 |
+
|
| 103 |
+
```
|
| 104 |
+
http://localhost:8002/docs # ADMET Service (FastAPI)
|
| 105 |
+
http://localhost:8001/docs # Drug Repurposing (FastAPI)
|
| 106 |
+
http://localhost:5000/docs # Chemical RAG (FastAPI)
|
| 107 |
+
http://localhost:8080/docs # Laravel API (Swagger)
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## Common Patterns
|
| 113 |
+
|
| 114 |
+
### Pagination
|
| 115 |
+
|
| 116 |
+
List endpoints support pagination:
|
| 117 |
+
|
| 118 |
+
```query
|
| 119 |
+
?page=1&per_page=20
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
**Response Field:** `meta.pagination`
|
| 123 |
+
|
| 124 |
+
```json
|
| 125 |
+
{
|
| 126 |
+
"data": [...],
|
| 127 |
+
"meta": {
|
| 128 |
+
"pagination": {
|
| 129 |
+
"current_page": 1,
|
| 130 |
+
"per_page": 20,
|
| 131 |
+
"total": 100,
|
| 132 |
+
"last_page": 5
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### Filtering
|
| 139 |
+
|
| 140 |
+
Endpoints support filtering (details per endpoint):
|
| 141 |
+
|
| 142 |
+
```query
|
| 143 |
+
?filter[status]=completed&filter[created_after]=2026-05-01
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### Sorting
|
| 147 |
+
|
| 148 |
+
```query
|
| 149 |
+
?sort=created_at&sort_order=desc
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
### Asynchronous Jobs
|
| 153 |
+
|
| 154 |
+
Long-running operations return `202 Accepted` with job tracking URL:
|
| 155 |
+
|
| 156 |
+
```json
|
| 157 |
+
{
|
| 158 |
+
"success": true,
|
| 159 |
+
"job_id": "abc-123-def",
|
| 160 |
+
"status": "pending",
|
| 161 |
+
"check_url": "/api/ai/status/abc-123-def"
|
| 162 |
+
}
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
**Check Status:**
|
| 166 |
+
|
| 167 |
+
```
|
| 168 |
+
GET /api/ai/status/{job_id}
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
---
|
| 172 |
+
|
| 173 |
+
## Health & Status
|
| 174 |
+
|
| 175 |
+
### System Health
|
| 176 |
+
|
| 177 |
+
Check overall system status:
|
| 178 |
+
|
| 179 |
+
```http
|
| 180 |
+
GET /health
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
**Response (200 OK):**
|
| 184 |
+
|
| 185 |
+
```json
|
| 186 |
+
{
|
| 187 |
+
"status": "healthy",
|
| 188 |
+
"timestamp": "2026-05-29T12:30:45Z",
|
| 189 |
+
"version": "2.0.0",
|
| 190 |
+
"uptime_seconds": 876543
|
| 191 |
+
}
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
### All Services Health
|
| 195 |
+
|
| 196 |
+
Check all AI microservices:
|
| 197 |
+
|
| 198 |
+
```http
|
| 199 |
+
GET /ai-services/health
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
**Response (200 OK):**
|
| 203 |
+
|
| 204 |
+
```json
|
| 205 |
+
{
|
| 206 |
+
"success": true,
|
| 207 |
+
"services": {
|
| 208 |
+
"admet": {
|
| 209 |
+
"status": "healthy",
|
| 210 |
+
"http_status": 200,
|
| 211 |
+
"body": {
|
| 212 |
+
"status": "ready",
|
| 213 |
+
"model_version": "MPNN_CNN_BindingDB"
|
| 214 |
+
}
|
| 215 |
+
},
|
| 216 |
+
"drug_repurposing": {
|
| 217 |
+
"status": "healthy",
|
| 218 |
+
"http_status": 200,
|
| 219 |
+
"body": {
|
| 220 |
+
"status": "ready",
|
| 221 |
+
"model": "DeepPurpose v0.1.5"
|
| 222 |
+
}
|
| 223 |
+
},
|
| 224 |
+
"chemical_rag": {
|
| 225 |
+
"status": "healthy",
|
| 226 |
+
"http_status": 200,
|
| 227 |
+
"body": {
|
| 228 |
+
"status": "ready",
|
| 229 |
+
"index_size": 1000000
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
**Response (503 Service Unavailable):** One or more services is down
|
| 237 |
+
|
| 238 |
+
---
|
| 239 |
+
|
| 240 |
+
## Authentication Endpoints
|
| 241 |
+
|
| 242 |
+
### Register User
|
| 243 |
+
|
| 244 |
+
```http
|
| 245 |
+
POST /user/register
|
| 246 |
+
Content-Type: application/json
|
| 247 |
+
|
| 248 |
+
{
|
| 249 |
+
"name": "Yasmeen Ahmed",
|
| 250 |
+
"email": "yasmeen@test.com",
|
| 251 |
+
"password": "password123",
|
| 252 |
+
"password_confirmation": "password123"
|
| 253 |
+
}
|
| 254 |
+
```
|
| 255 |
+
|
| 256 |
+
**Response (201 Created):**
|
| 257 |
+
|
| 258 |
+
```json
|
| 259 |
+
{
|
| 260 |
+
"success": true,
|
| 261 |
+
"message": "Registered successfully. Please check your email for OTP verification code.",
|
| 262 |
+
"data": {
|
| 263 |
+
"email": "yasmeen@test.com"
|
| 264 |
+
}
|
| 265 |
+
}
|
| 266 |
+
```
|
| 267 |
+
|
| 268 |
+
---
|
| 269 |
+
|
| 270 |
+
### Login
|
| 271 |
+
|
| 272 |
+
```http
|
| 273 |
+
POST /user/login
|
| 274 |
+
Content-Type: application/json
|
| 275 |
+
|
| 276 |
+
{
|
| 277 |
+
"email": "yasmeen@test.com",
|
| 278 |
+
"password": "password123"
|
| 279 |
+
}
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
**Response (200 OK):**
|
| 283 |
+
|
| 284 |
+
```json
|
| 285 |
+
{
|
| 286 |
+
"success": true,
|
| 287 |
+
"message": "Login successful",
|
| 288 |
+
"data": {
|
| 289 |
+
"token": "55|F4V06Yis3FcP35oGoEE2cwmNv3yTiVCpMEx9vYjya891dcb2",
|
| 290 |
+
"user": {
|
| 291 |
+
"id": 80,
|
| 292 |
+
"name": "yasmeen564",
|
| 293 |
+
"email": "salehyasmeen080@gmail.com",
|
| 294 |
+
"email_verified_at": null,
|
| 295 |
+
"last_otp_sent_at": null,
|
| 296 |
+
"role": "normal",
|
| 297 |
+
"created_at": "2026-05-25T00:21:51.000000Z",
|
| 298 |
+
"updated_at": "2026-05-30T00:02:06.000000Z",
|
| 299 |
+
"email_verification_otp_expires_at": null,
|
| 300 |
+
"is_verified": true,
|
| 301 |
+
"password_reset_otp_expires_at": null
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
```
|
| 306 |
+
|
| 307 |
+
---
|
| 308 |
+
|
| 309 |
+
### Verify Email
|
| 310 |
+
|
| 311 |
+
After account creation, verify email to activate account:
|
| 312 |
+
|
| 313 |
+
```http
|
| 314 |
+
POST /user/verify-email
|
| 315 |
+
Content-Type: application/json
|
| 316 |
+
|
| 317 |
+
{
|
| 318 |
+
"email": "yasmeen@test.com",
|
| 319 |
+
"otp": "123456"
|
| 320 |
+
}
|
| 321 |
+
```
|
| 322 |
+
|
| 323 |
+
**Response (200 OK):**
|
| 324 |
+
|
| 325 |
+
```json
|
| 326 |
+
{
|
| 327 |
+
"success": true,
|
| 328 |
+
"message": "Email verified successfully",
|
| 329 |
+
"data": {
|
| 330 |
+
"token": "54|dxcTq6KRZQfOESfbKcqEoXV4znbkU8IKCND1nX2H085c2309",
|
| 331 |
+
"user": {
|
| 332 |
+
"id": 80,
|
| 333 |
+
"name": "yasmeen564",
|
| 334 |
+
"email": "salehyasmeen080@gmail.com",
|
| 335 |
+
"email_verified_at": null,
|
| 336 |
+
"last_otp_sent_at": null,
|
| 337 |
+
"role": "normal",
|
| 338 |
+
"created_at": "2026-05-25T00:21:51.000000Z",
|
| 339 |
+
"updated_at": "2026-05-30T00:02:06.000000Z",
|
| 340 |
+
"email_verification_otp_expires_at": null,
|
| 341 |
+
"is_verified": true,
|
| 342 |
+
"password_reset_otp_expires_at": null
|
| 343 |
+
}
|
| 344 |
+
}
|
| 345 |
+
}
|
| 346 |
+
```
|
| 347 |
+
|
| 348 |
+
---
|
| 349 |
+
|
| 350 |
+
### Resend Verification Email
|
| 351 |
+
|
| 352 |
+
```http
|
| 353 |
+
POST /user/resend-verification
|
| 354 |
+
Content-Type: application/json
|
| 355 |
+
|
| 356 |
+
{
|
| 357 |
+
"email": "yasmeen@test.com"
|
| 358 |
+
}
|
| 359 |
+
```
|
| 360 |
+
|
| 361 |
+
**Response (200 OK):**
|
| 362 |
+
|
| 363 |
+
```json
|
| 364 |
+
{
|
| 365 |
+
"success": true,
|
| 366 |
+
"message": "OTP resent successfully",
|
| 367 |
+
"data": {
|
| 368 |
+
"email": "yasmeen111@example.com"
|
| 369 |
+
}
|
| 370 |
+
}
|
| 371 |
+
```
|
| 372 |
+
|
| 373 |
+
---
|
| 374 |
+
|
| 375 |
+
### Forgot Password
|
| 376 |
+
|
| 377 |
+
```http
|
| 378 |
+
POST /user/forgot-password
|
| 379 |
+
Content-Type: application/json
|
| 380 |
+
|
| 381 |
+
{
|
| 382 |
+
"email": "yasmeen@test.com"
|
| 383 |
+
}
|
| 384 |
+
```
|
| 385 |
+
|
| 386 |
+
**Response (200 OK):**
|
| 387 |
+
|
| 388 |
+
```json
|
| 389 |
+
{
|
| 390 |
+
"success": true,
|
| 391 |
+
"message": "OTP sent successfully",
|
| 392 |
+
"data": {
|
| 393 |
+
"email": "salehyasmeen080@gmail.com"
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
```
|
| 397 |
+
|
| 398 |
+
---
|
| 399 |
+
|
| 400 |
+
### Reset Password
|
| 401 |
+
|
| 402 |
+
```http
|
| 403 |
+
POST /user/reset-password
|
| 404 |
+
Content-Type: application/json
|
| 405 |
+
|
| 406 |
+
{
|
| 407 |
+
"email": "yasmeen@test.com",
|
| 408 |
+
"otp": "123456",
|
| 409 |
+
"password": "new_password123",
|
| 410 |
+
"password_confirmation": "new_password123"
|
| 411 |
+
}
|
| 412 |
+
```
|
| 413 |
+
|
| 414 |
+
**Response (200 OK):**
|
| 415 |
+
|
| 416 |
+
```json
|
| 417 |
+
{
|
| 418 |
+
"success": true,
|
| 419 |
+
"message": "Password reset successfully"
|
| 420 |
+
}
|
| 421 |
+
```
|
| 422 |
+
|
| 423 |
+
---
|
| 424 |
+
|
| 425 |
+
### Logout
|
| 426 |
+
|
| 427 |
+
```http
|
| 428 |
+
POST /user/logout
|
| 429 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 430 |
+
```
|
| 431 |
+
|
| 432 |
+
**Response (200 OK):**
|
| 433 |
+
|
| 434 |
+
```json
|
| 435 |
+
{
|
| 436 |
+
"success": true,
|
| 437 |
+
"message": "Logged out successfully"
|
| 438 |
+
}
|
| 439 |
+
```
|
| 440 |
+
|
| 441 |
+
## '''
|
| 442 |
+
|
| 443 |
+
## AI Integration Endpoints
|
| 444 |
+
|
| 445 |
+
These endpoints are for testing and monitoring AI microservices. Available only if `AI_INTEGRATION_ROUTES_ENABLED=true`.
|
| 446 |
+
|
| 447 |
+
### Test ADMET Prediction
|
| 448 |
+
|
| 449 |
+
Quick test of ADMET service:
|
| 450 |
+
|
| 451 |
+
```http
|
| 452 |
+
POST /ai-services/test/admet
|
| 453 |
+
Content-Type: application/json
|
| 454 |
+
|
| 455 |
+
{
|
| 456 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
|
| 457 |
+
"batch_size": 32
|
| 458 |
+
}
|
| 459 |
+
```
|
| 460 |
+
|
| 461 |
+
**Response (200 OK):**
|
| 462 |
+
|
| 463 |
+
```json
|
| 464 |
+
{
|
| 465 |
+
"success": true,
|
| 466 |
+
"service": "admet",
|
| 467 |
+
"upstream_status": 200,
|
| 468 |
+
"data": {
|
| 469 |
+
"status": "success",
|
| 470 |
+
"predictions": {
|
| 471 |
+
"absorption": 0.78,
|
| 472 |
+
"distribution": 0.65,
|
| 473 |
+
"metabolism": 0.45,
|
| 474 |
+
"excretion": 0.82,
|
| 475 |
+
"toxicity": 0.12
|
| 476 |
+
},
|
| 477 |
+
"processing_time_ms": 234
|
| 478 |
+
}
|
| 479 |
+
}
|
| 480 |
+
```
|
| 481 |
+
|
| 482 |
+
### Test Chemical Search
|
| 483 |
+
|
| 484 |
+
Quick test of Chemical RAG service:
|
| 485 |
+
|
| 486 |
+
```http
|
| 487 |
+
POST /ai-services/test/chemical-search
|
| 488 |
+
Content-Type: application/json
|
| 489 |
+
|
| 490 |
+
{
|
| 491 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
|
| 492 |
+
"top_k": 5
|
| 493 |
+
}
|
| 494 |
+
```
|
| 495 |
+
|
| 496 |
+
**Response (200 OK):**
|
| 497 |
+
|
| 498 |
+
```json
|
| 499 |
+
{
|
| 500 |
+
"success": true,
|
| 501 |
+
"service": "chemical_rag",
|
| 502 |
+
"upstream_status": 200,
|
| 503 |
+
"data": {
|
| 504 |
+
"query": {
|
| 505 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O"
|
| 506 |
+
},
|
| 507 |
+
"results": [
|
| 508 |
+
{
|
| 509 |
+
"rank": 1,
|
| 510 |
+
"cid": 2244,
|
| 511 |
+
"name": "Aspirin",
|
| 512 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
|
| 513 |
+
"similarity": 1.0,
|
| 514 |
+
"image_url": "https://..."
|
| 515 |
+
}
|
| 516 |
+
],
|
| 517 |
+
"processing_time_ms": 145
|
| 518 |
+
}
|
| 519 |
+
}
|
| 520 |
+
```
|
| 521 |
+
|
| 522 |
+
### Test Drug Repurposing
|
| 523 |
+
|
| 524 |
+
Quick test of Drug Repurposing service:
|
| 525 |
+
|
| 526 |
+
```http
|
| 527 |
+
GET /ai-services/test/drug-repurposing
|
| 528 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 529 |
+
```
|
| 530 |
+
|
| 531 |
+
**Response (200 OK):**
|
| 532 |
+
|
| 533 |
+
```json
|
| 534 |
+
{
|
| 535 |
+
"success": true,
|
| 536 |
+
"service": "drug_repurposing",
|
| 537 |
+
"health": {
|
| 538 |
+
"status": 200,
|
| 539 |
+
"body": {
|
| 540 |
+
"status": "healthy",
|
| 541 |
+
"model": "DeepPurpose",
|
| 542 |
+
"version": "0.1.5"
|
| 543 |
+
}
|
| 544 |
+
},
|
| 545 |
+
"model_status": {
|
| 546 |
+
"status": 200,
|
| 547 |
+
"body": {
|
| 548 |
+
"available_models": ["MPNN_CNN_BindingDB"]
|
| 549 |
+
}
|
| 550 |
+
}
|
| 551 |
+
}
|
| 552 |
+
```
|
| 553 |
+
|
| 554 |
+
---
|
| 555 |
+
|
| 556 |
+
## AI Service Endpoints
|
| 557 |
+
|
| 558 |
+
### Submit AI Job
|
| 559 |
+
|
| 560 |
+
Submit a drug discovery analysis job:
|
| 561 |
+
|
| 562 |
+
```http
|
| 563 |
+
POST /ai/run
|
| 564 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 565 |
+
Content-Type: application/json
|
| 566 |
+
|
| 567 |
+
{
|
| 568 |
+
"job_type": "admet_prediction",
|
| 569 |
+
"parameters": {
|
| 570 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
|
| 571 |
+
"batch_size": 32
|
| 572 |
+
},
|
| 573 |
+
"notification_email": "john@pharmaai.io"
|
| 574 |
+
}
|
| 575 |
+
```
|
| 576 |
+
|
| 577 |
+
**Response (202 Accepted):**
|
| 578 |
+
|
| 579 |
+
```json
|
| 580 |
+
{
|
| 581 |
+
"success": true,
|
| 582 |
+
"job_id": "job-abc-123-def",
|
| 583 |
+
"status": "pending",
|
| 584 |
+
"created_at": "2026-05-29T12:30:45Z",
|
| 585 |
+
"check_url": "/api/ai/status/job-abc-123-def"
|
| 586 |
+
}
|
| 587 |
+
```
|
| 588 |
+
|
| 589 |
+
### Get Job Status
|
| 590 |
+
|
| 591 |
+
```http
|
| 592 |
+
GET /ai/status/{job_id}
|
| 593 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 594 |
+
```
|
| 595 |
+
|
| 596 |
+
**Response (200 OK - Pending):**
|
| 597 |
+
|
| 598 |
+
```json
|
| 599 |
+
{
|
| 600 |
+
"success": true,
|
| 601 |
+
"job_id": "job-abc-123-def",
|
| 602 |
+
"status": "processing",
|
| 603 |
+
"progress": 45,
|
| 604 |
+
"estimated_completion": "2026-05-29T12:35:00Z"
|
| 605 |
+
}
|
| 606 |
+
```
|
| 607 |
+
|
| 608 |
+
**Response (200 OK - Completed):**
|
| 609 |
+
|
| 610 |
+
```json
|
| 611 |
+
{
|
| 612 |
+
"success": true,
|
| 613 |
+
"job_id": "job-abc-123-def",
|
| 614 |
+
"status": "completed",
|
| 615 |
+
"results": {
|
| 616 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
|
| 617 |
+
"predictions": {
|
| 618 |
+
"absorption": 0.78,
|
| 619 |
+
"distribution": 0.65,
|
| 620 |
+
"metabolism": 0.45,
|
| 621 |
+
"excretion": 0.82,
|
| 622 |
+
"toxicity": 0.12
|
| 623 |
+
}
|
| 624 |
+
},
|
| 625 |
+
"completed_at": "2026-05-29T12:32:15Z"
|
| 626 |
+
}
|
| 627 |
+
```
|
| 628 |
+
|
| 629 |
+
**Response (200 OK - Failed):**
|
| 630 |
+
|
| 631 |
+
```json
|
| 632 |
+
{
|
| 633 |
+
"success": false,
|
| 634 |
+
"job_id": "job-abc-123-def",
|
| 635 |
+
"status": "failed",
|
| 636 |
+
"error": "Invalid SMILES string",
|
| 637 |
+
"failed_at": "2026-05-29T12:30:50Z"
|
| 638 |
+
}
|
| 639 |
+
```
|
| 640 |
+
|
| 641 |
+
### Preview Results
|
| 642 |
+
|
| 643 |
+
Get a preview of results while job is processing:
|
| 644 |
+
|
| 645 |
+
```http
|
| 646 |
+
GET /ai/preview/{job_id}
|
| 647 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 648 |
+
```
|
| 649 |
+
|
| 650 |
+
**Response (200 OK):**
|
| 651 |
+
|
| 652 |
+
```json
|
| 653 |
+
{
|
| 654 |
+
"success": true,
|
| 655 |
+
"job_id": "job-abc-123-def",
|
| 656 |
+
"status": "processing",
|
| 657 |
+
"preview": {
|
| 658 |
+
"processed_compounds": 50,
|
| 659 |
+
"total_compounds": 100,
|
| 660 |
+
"average_prediction_time_ms": 234
|
| 661 |
+
}
|
| 662 |
+
}
|
| 663 |
+
```
|
| 664 |
+
|
| 665 |
+
### Download Results (Top)
|
| 666 |
+
|
| 667 |
+
Download first N results (useful for large datasets):
|
| 668 |
+
|
| 669 |
+
```http
|
| 670 |
+
GET /ai/download/top/{job_id}?limit=100
|
| 671 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 672 |
+
```
|
| 673 |
+
|
| 674 |
+
**Response (200 OK):**
|
| 675 |
+
|
| 676 |
+
```
|
| 677 |
+
Content-Type: text/csv
|
| 678 |
+
Content-Disposition: attachment; filename="results-top-100.csv"
|
| 679 |
+
|
| 680 |
+
smiles,absorption,distribution,metabolism,excretion,toxicity
|
| 681 |
+
CC(=O)Oc1ccccc1C(=O)O,0.78,0.65,0.45,0.82,0.12
|
| 682 |
+
...
|
| 683 |
+
```
|
| 684 |
+
|
| 685 |
+
### Download Full Results
|
| 686 |
+
|
| 687 |
+
Download complete results as CSV:
|
| 688 |
+
|
| 689 |
+
```http
|
| 690 |
+
GET /ai/download/full/{job_id}
|
| 691 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 692 |
+
```
|
| 693 |
+
|
| 694 |
+
**Response (200 OK):**
|
| 695 |
+
|
| 696 |
+
```
|
| 697 |
+
Content-Type: application/zip
|
| 698 |
+
Content-Disposition: attachment; filename="results-full.zip"
|
| 699 |
+
|
| 700 |
+
# Contains: predictions.csv, metadata.json, summary.txt
|
| 701 |
+
```
|
| 702 |
+
|
| 703 |
+
### Job History
|
| 704 |
+
|
| 705 |
+
Get all jobs submitted by user:
|
| 706 |
+
|
| 707 |
+
```http
|
| 708 |
+
GET /ai/history?page=1&per_page=20&filter[status]=completed&sort=created_at&sort_order=desc
|
| 709 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 710 |
+
```
|
| 711 |
+
|
| 712 |
+
**Response (200 OK):**
|
| 713 |
+
|
| 714 |
+
```json
|
| 715 |
+
{
|
| 716 |
+
"success": true,
|
| 717 |
+
"data": [
|
| 718 |
+
{
|
| 719 |
+
"id": "job-abc-123-def",
|
| 720 |
+
"job_type": "admet_prediction",
|
| 721 |
+
"status": "completed",
|
| 722 |
+
"created_at": "2026-05-29T12:00:00Z",
|
| 723 |
+
"completed_at": "2026-05-29T12:30:00Z",
|
| 724 |
+
"result_summary": {
|
| 725 |
+
"total_compounds": 100,
|
| 726 |
+
"average_absorption": 0.72
|
| 727 |
+
}
|
| 728 |
+
}
|
| 729 |
+
],
|
| 730 |
+
"meta": {
|
| 731 |
+
"pagination": {
|
| 732 |
+
"current_page": 1,
|
| 733 |
+
"per_page": 20,
|
| 734 |
+
"total": 42,
|
| 735 |
+
"last_page": 3
|
| 736 |
+
}
|
| 737 |
+
}
|
| 738 |
+
}
|
| 739 |
+
```
|
| 740 |
+
|
| 741 |
+
---
|
| 742 |
+
|
| 743 |
+
## AI Agent Endpoints (Chemistry AI)
|
| 744 |
+
|
| 745 |
+
AI-powered chemistry analysis agent with conversation memory. All endpoints require authentication.
|
| 746 |
+
|
| 747 |
+
**Base Path:** `/api/chemistry`
|
| 748 |
+
|
| 749 |
+
**Capabilities:**
|
| 750 |
+
|
| 751 |
+
- SMILES validation and property calculation
|
| 752 |
+
- Drug-likeness classification (Lipinski Ro5, Veber, Lead-likeness)
|
| 753 |
+
- ADMET profiling with structural toxicity alerts
|
| 754 |
+
- Molecular docking result ranking and recommendation
|
| 755 |
+
- Multi-molecule side-by-side comparison
|
| 756 |
+
- Async CSV batch processing
|
| 757 |
+
- Conversation context via thread_id
|
| 758 |
+
|
| 759 |
+
---
|
| 760 |
+
|
| 761 |
+
### Create Conversation Thread
|
| 762 |
+
|
| 763 |
+
Start a new isolated chemistry conversation thread.
|
| 764 |
+
|
| 765 |
+
```http
|
| 766 |
+
POST /api/chemistry/thread
|
| 767 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 768 |
+
```
|
| 769 |
+
|
| 770 |
+
**Response (200 OK):**
|
| 771 |
+
|
| 772 |
+
```json
|
| 773 |
+
{
|
| 774 |
+
"success": true,
|
| 775 |
+
"data": {
|
| 776 |
+
"thread_id": "3a148307-b97f-4910-88db-03eca6477419",
|
| 777 |
+
"id": 2,
|
| 778 |
+
"created_at": "2026-05-25T18:34:15.000000Z"
|
| 779 |
+
}
|
| 780 |
+
}
|
| 781 |
+
```
|
| 782 |
+
|
| 783 |
+
**Notes:**
|
| 784 |
+
|
| 785 |
+
- One thread_id per user session
|
| 786 |
+
- Do not share thread IDs across different users
|
| 787 |
+
- Pass the same thread_id across multiple calls to maintain conversation history
|
| 788 |
+
|
| 789 |
+
---
|
| 790 |
+
|
| 791 |
+
### List User Threads
|
| 792 |
+
|
| 793 |
+
Get all active threads for the authenticated user.
|
| 794 |
+
|
| 795 |
+
```http
|
| 796 |
+
GET /api/chemistry/threads
|
| 797 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 798 |
+
```
|
| 799 |
+
|
| 800 |
+
**Response (200 OK):**
|
| 801 |
+
|
| 802 |
+
```json
|
| 803 |
+
{
|
| 804 |
+
"success": true,
|
| 805 |
+
"data": [
|
| 806 |
+
{
|
| 807 |
+
"id": 2,
|
| 808 |
+
"thread_id": "3a148307-b97f-4910-88db-03eca6477419",
|
| 809 |
+
"title": "New Conversation",
|
| 810 |
+
"last_used_at": "2026-05-25T18:34:15.000000Z",
|
| 811 |
+
"created_at": "2026-05-25T18:34:15.000000Z"
|
| 812 |
+
},
|
| 813 |
+
{
|
| 814 |
+
"id": 1,
|
| 815 |
+
"thread_id": "0455ef45-faa0-4e82-b808-db1c574ad175",
|
| 816 |
+
"title": "New Conversation",
|
| 817 |
+
"last_used_at": "2026-05-25T18:21:23.000000Z",
|
| 818 |
+
"created_at": "2026-05-24T14:54:10.000000Z"
|
| 819 |
+
}
|
| 820 |
+
]
|
| 821 |
+
}
|
| 822 |
+
```
|
| 823 |
+
|
| 824 |
+
---
|
| 825 |
+
|
| 826 |
+
### Send Chat Message
|
| 827 |
+
|
| 828 |
+
Send a natural language chemistry question to the AI agent.
|
| 829 |
+
|
| 830 |
+
```http
|
| 831 |
+
POST /api/chemistry/chat
|
| 832 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 833 |
+
Content-Type: application/json
|
| 834 |
+
|
| 835 |
+
{
|
| 836 |
+
"message": "Is CC(=O)Oc1ccccc1C(=O)O a good drug candidate?",
|
| 837 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede"
|
| 838 |
+
}
|
| 839 |
+
```
|
| 840 |
+
|
| 841 |
+
**Response (200 OK):**
|
| 842 |
+
|
| 843 |
+
```json
|
| 844 |
+
{
|
| 845 |
+
"success": true,
|
| 846 |
+
"data": {
|
| 847 |
+
"reply": "Based on the analysis of CC(=O)Oc1ccccc1C(=O)O (Aspirin)...",
|
| 848 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
|
| 849 |
+
"processing_time_ms": 11888
|
| 850 |
+
}
|
| 851 |
+
}
|
| 852 |
+
```
|
| 853 |
+
|
| 854 |
+
**Example Messages:**
|
| 855 |
+
|
| 856 |
+
- `"Is CC(=O)Oc1ccccc1C(=O)O a good drug candidate?"`
|
| 857 |
+
- `"Compare aspirin and ibuprofen CC(C)Cc1ccc(cc1)C(C)C(=O)O"`
|
| 858 |
+
- `"What are the toxicity concerns for this molecule?"` (follow-up)
|
| 859 |
+
- `"Which molecule we discussed has the best CNS penetration?"` (follow-up)
|
| 860 |
+
|
| 861 |
+
**Notes:**
|
| 862 |
+
|
| 863 |
+
- `thread_id` is optional. Omit to start a fresh conversation automatically.
|
| 864 |
+
- The agent remembers all prior messages on the same thread.
|
| 865 |
+
- The agent automatically handles: SMILES validation, molecular property calculation, drug-likeness rules, ADMET profiling, docking ranking, and multi-molecule comparison.
|
| 866 |
+
|
| 867 |
+
---
|
| 868 |
+
|
| 869 |
+
### Analyze Single SMILES
|
| 870 |
+
|
| 871 |
+
Full molecular analysis pipeline in one call.
|
| 872 |
+
|
| 873 |
+
```http
|
| 874 |
+
POST /api/chemistry/analyze/smiles
|
| 875 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 876 |
+
Content-Type: application/json
|
| 877 |
+
|
| 878 |
+
{
|
| 879 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
|
| 880 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede"
|
| 881 |
+
}
|
| 882 |
+
```
|
| 883 |
+
|
| 884 |
+
**Response (200 OK):**
|
| 885 |
+
|
| 886 |
+
```json
|
| 887 |
+
{
|
| 888 |
+
"success": true,
|
| 889 |
+
"data": {
|
| 890 |
+
"reply": "Complete analysis text...",
|
| 891 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
|
| 892 |
+
"processing_time_ms": 5234
|
| 893 |
+
}
|
| 894 |
+
}
|
| 895 |
+
```
|
| 896 |
+
|
| 897 |
+
**Pipeline Steps:**
|
| 898 |
+
|
| 899 |
+
1. Validate SMILES
|
| 900 |
+
2. Compute molecular properties (MW, LogP, HBD, HBA, TPSA, QED, Fsp3)
|
| 901 |
+
3. Drug-likeness classification (Lipinski Ro5, Veber, Lead-likeness)
|
| 902 |
+
4. ADMET profile with structural toxicity alerts
|
| 903 |
+
|
| 904 |
+
**Notes:**
|
| 905 |
+
|
| 906 |
+
- `smiles` field is required in JSON body
|
| 907 |
+
- URL-encode special characters if sending via query string: `CC%28%3DO%29Oc1ccccc1C%28%3DO%29O`
|
| 908 |
+
|
| 909 |
+
---
|
| 910 |
+
|
| 911 |
+
### Compare Multiple Molecules
|
| 912 |
+
|
| 913 |
+
Side-by-side comparison of 2 or more molecules with recommendation.
|
| 914 |
+
|
| 915 |
+
```http
|
| 916 |
+
POST /api/chemistry/analyze/compare
|
| 917 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 918 |
+
Content-Type: application/json
|
| 919 |
+
|
| 920 |
+
{
|
| 921 |
+
"smiles": [
|
| 922 |
+
"CC(=O)Oc1ccccc1C(=O)O",
|
| 923 |
+
"CC(C)Cc1ccc(cc1)C(C)C(=O)O",
|
| 924 |
+
"Cn1cnc2c1c(=O)n(c(=O)n2C)C"
|
| 925 |
+
],
|
| 926 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede"
|
| 927 |
+
}
|
| 928 |
+
```
|
| 929 |
+
|
| 930 |
+
**Response (200 OK):**
|
| 931 |
+
|
| 932 |
+
```json
|
| 933 |
+
{
|
| 934 |
+
"success": true,
|
| 935 |
+
"data": {
|
| 936 |
+
"reply": "Side-by-side property table and recommendation...",
|
| 937 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
|
| 938 |
+
"processing_time_ms": 23543
|
| 939 |
+
}
|
| 940 |
+
}
|
| 941 |
+
```
|
| 942 |
+
|
| 943 |
+
**Agent Returns:**
|
| 944 |
+
|
| 945 |
+
- Side-by-side property table (MW, LogP, HBD, HBA, TPSA, QED, Fsp3)
|
| 946 |
+
- Drug-likeness pass/fail for each molecule
|
| 947 |
+
- Named recommendation with justification
|
| 948 |
+
|
| 949 |
+
---
|
| 950 |
+
|
| 951 |
+
### Analyze Docking Results
|
| 952 |
+
|
| 953 |
+
Rank and recommend best docking candidates.
|
| 954 |
+
|
| 955 |
+
```http
|
| 956 |
+
POST /api/chemistry/analyze/docking
|
| 957 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 958 |
+
Content-Type: application/json
|
| 959 |
+
|
| 960 |
+
{
|
| 961 |
+
"docking_data": "CC(=O)Oc1ccccc1C(=O)O | -7.2 | 1.1 | H-bond to Ser195\\nCC(C)Cc1ccc(cc1)C(C)C(=O)O | -8.9 | 0.8 | deep pocket binding\\nCn1cnc2c1c(=O)n(c(=O)n2C)C | -6.1 | 1.9 |",
|
| 962 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede"
|
| 963 |
+
}
|
| 964 |
+
```
|
| 965 |
+
|
| 966 |
+
**Docking Data Format:**
|
| 967 |
+
|
| 968 |
+
```
|
| 969 |
+
SMILES | binding_affinity_kcal_mol | rmsd_angstrom | optional_notes
|
| 970 |
+
```
|
| 971 |
+
|
| 972 |
+
**Response (200 OK):**
|
| 973 |
+
|
| 974 |
+
```json
|
| 975 |
+
{
|
| 976 |
+
"success": true,
|
| 977 |
+
"data": {
|
| 978 |
+
"reply": "Top Pick: CC(C)Cc1ccc(cc1)C(C)C(=O)O...",
|
| 979 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
|
| 980 |
+
"processing_time_ms": 5392
|
| 981 |
+
}
|
| 982 |
+
}
|
| 983 |
+
```
|
| 984 |
+
|
| 985 |
+
**Interpretation Rules:**
|
| 986 |
+
|
| 987 |
+
- ΔG more negative = stronger binding
|
| 988 |
+
- RMSD < 2 Å = reliable pose; > 2 Å = uncertain binding mode
|
| 989 |
+
- Best candidate = strongest binder that also passes Lipinski Ro5
|
| 990 |
+
|
| 991 |
+
---
|
| 992 |
+
|
| 993 |
+
### Upload CSV for Batch Analysis
|
| 994 |
+
|
| 995 |
+
Process multiple molecules asynchronously.
|
| 996 |
+
|
| 997 |
+
```http
|
| 998 |
+
POST /api/chemistry/csv/upload
|
| 999 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1000 |
+
Content-Type: multipart/form-data
|
| 1001 |
+
|
| 1002 |
+
file: [Choose File] molecules.csv
|
| 1003 |
+
analysis_type: full
|
| 1004 |
+
```
|
| 1005 |
+
|
| 1006 |
+
**Analysis Types:**
|
| 1007 |
+
|
| 1008 |
+
| Type | What It Runs | Speed |
|
| 1009 |
+
| ---------- | ---------------------------------- | ------ |
|
| 1010 |
+
| `full` | Properties + drug-likeness + ADMET | Slow |
|
| 1011 |
+
| `quick` | Lipinski pass/fail + QED only | Fast |
|
| 1012 |
+
| `admet` | ADMET profile only | Medium |
|
| 1013 |
+
| `classify` | Drug-likeness classification only | Fast |
|
| 1014 |
+
|
| 1015 |
+
**Required CSV Columns:**
|
| 1016 |
+
|
| 1017 |
+
- `smiles` — SMILES string (required)
|
| 1018 |
+
- `name` — compound name or ID (optional, auto-generated if missing)
|
| 1019 |
+
|
| 1020 |
+
**Example CSV:**
|
| 1021 |
+
|
| 1022 |
+
```csv
|
| 1023 |
+
name,smiles
|
| 1024 |
+
Aspirin,CC(=O)Oc1ccccc1C(=O)O
|
| 1025 |
+
Ibuprofen,CC(C)Cc1ccc(cc1)C(C)C(=O)O
|
| 1026 |
+
Caffeine,Cn1cnc2c1c(=O)n(c(=O)n2C)C
|
| 1027 |
+
```
|
| 1028 |
+
|
| 1029 |
+
**Response (200 OK):**
|
| 1030 |
+
|
| 1031 |
+
```json
|
| 1032 |
+
{
|
| 1033 |
+
"success": true,
|
| 1034 |
+
"data": {
|
| 1035 |
+
"job_id": "0ec86cee-6ce8-4e91-95f4-ad807f7ee283",
|
| 1036 |
+
"id": 1,
|
| 1037 |
+
"status": "queued",
|
| 1038 |
+
"total_rows": 4
|
| 1039 |
+
}
|
| 1040 |
+
}
|
| 1041 |
+
```
|
| 1042 |
+
|
| 1043 |
+
**Limits:** Maximum 100 rows per upload. Split larger datasets into batches.
|
| 1044 |
+
|
| 1045 |
+
---
|
| 1046 |
+
|
| 1047 |
+
### Check CSV Job Status
|
| 1048 |
+
|
| 1049 |
+
Poll for processing progress.
|
| 1050 |
+
|
| 1051 |
+
```http
|
| 1052 |
+
GET /api/chemistry/csv/status/{job_id}
|
| 1053 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1054 |
+
```
|
| 1055 |
+
|
| 1056 |
+
**Response (200 OK - Running):**
|
| 1057 |
+
|
| 1058 |
+
```json
|
| 1059 |
+
{
|
| 1060 |
+
"success": true,
|
| 1061 |
+
"data": {
|
| 1062 |
+
"job_id": "0ec86cee-6ce8-4e91-95f4-ad807f7ee283",
|
| 1063 |
+
"status": "running",
|
| 1064 |
+
"total": 4,
|
| 1065 |
+
"completed": 3,
|
| 1066 |
+
"failed_rows": 2,
|
| 1067 |
+
"progress_percent": 75
|
| 1068 |
+
}
|
| 1069 |
+
}
|
| 1070 |
+
```
|
| 1071 |
+
|
| 1072 |
+
**Status Values:**
|
| 1073 |
+
| Value | Meaning |
|
| 1074 |
+
|-------|---------|
|
| 1075 |
+
| `queued` | Job is waiting to start |
|
| 1076 |
+
| `running` | Actively processing rows |
|
| 1077 |
+
| `done` | All rows finished — results are ready |
|
| 1078 |
+
| `failed` | Job-level error (row errors are inside results) |
|
| 1079 |
+
|
| 1080 |
+
**Polling:** Poll every 5–10 seconds. Once status == "done", call GET /csv/results/{job_id}.
|
| 1081 |
+
|
| 1082 |
+
---
|
| 1083 |
+
|
| 1084 |
+
### Download CSV Results
|
| 1085 |
+
|
| 1086 |
+
Get completed analysis as CSV file download.
|
| 1087 |
+
|
| 1088 |
+
```http
|
| 1089 |
+
GET /api/chemistry/csv/results/{job_id}
|
| 1090 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1091 |
+
```
|
| 1092 |
+
|
| 1093 |
+
**Response (200 OK):**
|
| 1094 |
+
|
| 1095 |
+
```
|
| 1096 |
+
Content-Type: text/csv
|
| 1097 |
+
Content-Disposition: attachment; filename="results_{job_id}.csv"
|
| 1098 |
+
|
| 1099 |
+
row,name,smiles,status,error,analysis
|
| 1100 |
+
1,Aspirin,CC(=O)Oc1ccccc1C(=O)O,success,,"Full analysis text..."
|
| 1101 |
+
2,Ibuprofen,CC(C)Cc1ccc(cc1)C(C)C(=O)O,failed,"Quota exceeded",...
|
| 1102 |
+
```
|
| 1103 |
+
|
| 1104 |
+
**Output CSV Columns:**
|
| 1105 |
+
|
| 1106 |
+
- `row` — original row number in the uploaded file
|
| 1107 |
+
- `name` — compound name
|
| 1108 |
+
- `smiles` — input SMILES string
|
| 1109 |
+
- `status` — success or failed
|
| 1110 |
+
- `analysis` — full agent analysis text
|
| 1111 |
+
- `error` — error message (empty if status is success)
|
| 1112 |
+
|
| 1113 |
+
---
|
| 1114 |
+
|
| 1115 |
+
### List User CSV Jobs
|
| 1116 |
+
|
| 1117 |
+
```http
|
| 1118 |
+
GET /api/chemistry/csv/jobs
|
| 1119 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1120 |
+
```
|
| 1121 |
+
|
| 1122 |
+
**Response (200 OK):**
|
| 1123 |
+
|
| 1124 |
+
```json
|
| 1125 |
+
{
|
| 1126 |
+
"success": true,
|
| 1127 |
+
"data": [
|
| 1128 |
+
{
|
| 1129 |
+
"id": 1,
|
| 1130 |
+
"job_id": "0ec86cee-6ce8-4e91-95f4-ad807f7ee283",
|
| 1131 |
+
"filename": "molecules.csv",
|
| 1132 |
+
"analysis_type": "full",
|
| 1133 |
+
"status": "done",
|
| 1134 |
+
"total_rows": 4,
|
| 1135 |
+
"completed_rows": 4,
|
| 1136 |
+
"failed_rows": 3,
|
| 1137 |
+
"progress_percent": 100,
|
| 1138 |
+
"created_at": "2026-05-29T12:00:00Z"
|
| 1139 |
+
}
|
| 1140 |
+
]
|
| 1141 |
+
}
|
| 1142 |
+
```
|
| 1143 |
+
|
| 1144 |
+
---
|
| 1145 |
+
|
| 1146 |
+
### Delete CSV Job
|
| 1147 |
+
|
| 1148 |
+
Remove completed job and free storage.
|
| 1149 |
+
|
| 1150 |
+
```http
|
| 1151 |
+
DELETE /api/chemistry/csv/jobs/{job_id}
|
| 1152 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1153 |
+
```
|
| 1154 |
+
|
| 1155 |
+
**Response (200 OK):**
|
| 1156 |
+
|
| 1157 |
+
```json
|
| 1158 |
+
{
|
| 1159 |
+
"success": true,
|
| 1160 |
+
"message": "Job deleted successfully"
|
| 1161 |
+
}
|
| 1162 |
+
```
|
| 1163 |
+
|
| 1164 |
+
**Notes:**
|
| 1165 |
+
|
| 1166 |
+
- Call this after successfully downloading results to keep memory usage low
|
| 1167 |
+
- Trying to delete a non-existent job returns 404
|
| 1168 |
+
|
| 1169 |
+
---
|
| 1170 |
+
|
| 1171 |
+
### Get User Analysis History
|
| 1172 |
+
|
| 1173 |
+
Retrieve all past analyses with pagination.
|
| 1174 |
+
|
| 1175 |
+
```http
|
| 1176 |
+
GET /api/chemistry/history?type=smiles
|
| 1177 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1178 |
+
```
|
| 1179 |
+
|
| 1180 |
+
**Query Parameters:**
|
| 1181 |
+
|
| 1182 |
+
| Parameter | Type | Description |
|
| 1183 |
+
| --------- | ------- | ---------------------------------------------- |
|
| 1184 |
+
| `type` | string | Filter: `smiles`, `compare`, `docking`, `chat` |
|
| 1185 |
+
| `page` | integer | Page number |
|
| 1186 |
+
|
| 1187 |
+
**Response (200 OK):**
|
| 1188 |
+
|
| 1189 |
+
```json
|
| 1190 |
+
{
|
| 1191 |
+
"success": true,
|
| 1192 |
+
"data": {
|
| 1193 |
+
"current_page": 1,
|
| 1194 |
+
"data": [
|
| 1195 |
+
{
|
| 1196 |
+
"id": 1,
|
| 1197 |
+
"type": "smiles",
|
| 1198 |
+
"input_data": "CC(=O)Oc1ccccc1C(=O)O",
|
| 1199 |
+
"response": "Analysis text...",
|
| 1200 |
+
"status": "success",
|
| 1201 |
+
"created_at": "2026-05-29T12:00:00Z",
|
| 1202 |
+
"thread": {
|
| 1203 |
+
"id": 1,
|
| 1204 |
+
"thread_id": "aa761d2c-ecbc-4da0-abe4-189c2d77eede",
|
| 1205 |
+
"title": "New Conversation"
|
| 1206 |
+
}
|
| 1207 |
+
}
|
| 1208 |
+
],
|
| 1209 |
+
"per_page": 20,
|
| 1210 |
+
"total": 42
|
| 1211 |
+
}
|
| 1212 |
+
}
|
| 1213 |
+
```
|
| 1214 |
+
|
| 1215 |
+
---
|
| 1216 |
+
|
| 1217 |
+
## Chemical Search Endpoints
|
| 1218 |
+
|
| 1219 |
+
### Retrieval-Only Search
|
| 1220 |
+
|
| 1221 |
+
Query the chemical database for similar compounds (no explanations):
|
| 1222 |
+
|
| 1223 |
+
```http
|
| 1224 |
+
POST /chemical-search
|
| 1225 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1226 |
+
Content-Type: application/json
|
| 1227 |
+
|
| 1228 |
+
{
|
| 1229 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
|
| 1230 |
+
"top_k": 10
|
| 1231 |
+
}
|
| 1232 |
+
```
|
| 1233 |
+
|
| 1234 |
+
**Response (202 Accepted):**
|
| 1235 |
+
|
| 1236 |
+
```json
|
| 1237 |
+
{
|
| 1238 |
+
"success": true,
|
| 1239 |
+
"query": {
|
| 1240 |
+
"smiles": "CN1C=NC2=C1C(=O)N(C(=O)N2C)",
|
| 1241 |
+
"top_k": 3
|
| 1242 |
+
},
|
| 1243 |
+
"compounds": [
|
| 1244 |
+
{
|
| 1245 |
+
"rank": 1,
|
| 1246 |
+
"smiles": "CN1C=NC2=C1C(=O)NC(=O)N2C",
|
| 1247 |
+
"name": "Compound_5429",
|
| 1248 |
+
"cid": "5429",
|
| 1249 |
+
"similarity": 1,
|
| 1250 |
+
"explanation": null,
|
| 1251 |
+
"image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/6096554304416497818.png"
|
| 1252 |
+
},
|
| 1253 |
+
{
|
| 1254 |
+
"rank": 2,
|
| 1255 |
+
"smiles": "CN1C=NC2=C1C(=O)N(C(=O)N2C)C",
|
| 1256 |
+
"name": "Compound_2519",
|
| 1257 |
+
"cid": "2519",
|
| 1258 |
+
"similarity": 0.9922,
|
| 1259 |
+
"explanation": null,
|
| 1260 |
+
"image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/3925926930136341582.png"
|
| 1261 |
+
},
|
| 1262 |
+
{
|
| 1263 |
+
"rank": 3,
|
| 1264 |
+
"smiles": "CN1C=NC2=C1C(=O)N(C(=O)N2)C",
|
| 1265 |
+
"name": "Compound_4687",
|
| 1266 |
+
"cid": "4687",
|
| 1267 |
+
"similarity": 0.9922,
|
| 1268 |
+
"explanation": null,
|
| 1269 |
+
"image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/5189886293630140187.png"
|
| 1270 |
+
}
|
| 1271 |
+
],
|
| 1272 |
+
"metadata": {
|
| 1273 |
+
"total_results": 3,
|
| 1274 |
+
"search_time_ms": 443.08,
|
| 1275 |
+
"similarity_metric": "Tanimoto",
|
| 1276 |
+
"fingerprint": "Morgan (2048, radius=2)",
|
| 1277 |
+
"source": "retrieval"
|
| 1278 |
+
}
|
| 1279 |
+
}
|
| 1280 |
+
```
|
| 1281 |
+
|
| 1282 |
+
### Full RAG Search
|
| 1283 |
+
|
| 1284 |
+
Query with LLM-generated explanations:
|
| 1285 |
+
|
| 1286 |
+
```http
|
| 1287 |
+
POST /chemical-search/full-rag
|
| 1288 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1289 |
+
Content-Type: application/json
|
| 1290 |
+
|
| 1291 |
+
{
|
| 1292 |
+
"smiles": "CC(=O)Oc1ccccc1C(=O)O",
|
| 1293 |
+
"top_k": 5
|
| 1294 |
+
}
|
| 1295 |
+
```
|
| 1296 |
+
|
| 1297 |
+
**Response (202 Accepted):**
|
| 1298 |
+
|
| 1299 |
+
```json
|
| 1300 |
+
{
|
| 1301 |
+
"success": true,
|
| 1302 |
+
"query": {
|
| 1303 |
+
"smiles": "C1=CC=C(C(=C1)C(=O)O)",
|
| 1304 |
+
"top_k": 2
|
| 1305 |
+
},
|
| 1306 |
+
"compounds": [
|
| 1307 |
+
{
|
| 1308 |
+
"rank": 1,
|
| 1309 |
+
"smiles": "C1=CC=C(C=C1)C(=O)O",
|
| 1310 |
+
"name": "Compound_243",
|
| 1311 |
+
"cid": "243",
|
| 1312 |
+
"similarity": 1,
|
| 1313 |
+
"explanation": "These compounds are structurally identical, as indicated by the similarity score of 1.000. Both share the same core scaffold of a benzene ring (C1=CC=C(C=C1)) with a carboxylic acid group (C(=O)O) attached to it. The molecular formulas (C7H6O2) and heavy atom counts (9) are identical, confirming no atom substitutions or differences in structure. The only apparent difference is the SMILES representation, which is",
|
| 1314 |
+
"image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/2500971538592982398.png"
|
| 1315 |
+
},
|
| 1316 |
+
{
|
| 1317 |
+
"rank": 2,
|
| 1318 |
+
"smiles": "C1=CC=C(C=C1)[13C](=O)O",
|
| 1319 |
+
"name": "Compound_19759",
|
| 1320 |
+
"cid": "19759",
|
| 1321 |
+
"similarity": 1,
|
| 1322 |
+
"explanation": "These compounds are structurally identical, as indicated by the similarity score of 1.000. Both share the same core scaffold of a benzene ring (C1=CC=C(C=C1)) with a carboxylic acid group (C(=O)O) attached. The only difference is that the match compound has a ^13C isotope substitution at the carboxylic acid carbon, which does not alter the molecular structure or connectivity. Both compounds have the same molecular formula (C7H6O",
|
| 1323 |
+
"image_url": "https://unsteady-chlorine-imaginary.ngrok-free.dev/static/images/8561718417946188610.png"
|
| 1324 |
+
}
|
| 1325 |
+
],
|
| 1326 |
+
"metadata": {
|
| 1327 |
+
"total_results": 2,
|
| 1328 |
+
"search_time_ms": 7975.89,
|
| 1329 |
+
"similarity_metric": "Tanimoto",
|
| 1330 |
+
"fingerprint": "Morgan (2048, radius=2)",
|
| 1331 |
+
"source": "full_rag"
|
| 1332 |
+
}
|
| 1333 |
+
}
|
| 1334 |
+
```
|
| 1335 |
+
|
| 1336 |
+
## Docking API
|
| 1337 |
+
|
| 1338 |
+
### POST `/api/docking/submit`
|
| 1339 |
+
|
| 1340 |
+
- Auth required
|
| 1341 |
+
- Content type: `multipart/form-data`
|
| 1342 |
+
- Required fields:
|
| 1343 |
+
- `protein_name` (string)
|
| 1344 |
+
- `protein_file` (file)
|
| 1345 |
+
- `center_x` (numeric)
|
| 1346 |
+
- `center_y` (numeric)
|
| 1347 |
+
- `center_z` (numeric)
|
| 1348 |
+
- `box_size_x` (numeric)
|
| 1349 |
+
- `box_size_y` (numeric)
|
| 1350 |
+
- `box_size_z` (numeric)
|
| 1351 |
+
|
| 1352 |
+
### VERY IMPORTANT
|
| 1353 |
+
|
| 1354 |
+
### Optional fields:
|
| 1355 |
+
|
| 1356 |
+
- `ligand_name` (string)
|
| 1357 |
+
- `exhaustiveness` (integer)
|
| 1358 |
+
- `n_poses` (integer)
|
| 1359 |
+
|
| 1360 |
+
### Must include exactly one of:
|
| 1361 |
+
|
| 1362 |
+
- `ligand_file` (file)
|
| 1363 |
+
- `ligand_smiles` (string)
|
| 1364 |
+
|
| 1365 |
+
#### Example curl request
|
| 1366 |
+
|
| 1367 |
+
```bash
|
| 1368 |
+
curl -X POST "{base_url}/api/docking/submit" \\
|
| 1369 |
+
-H "Authorization: Bearer {token}" \\
|
| 1370 |
+
-F "protein_name=EGFR" \\
|
| 1371 |
+
-F "protein_file=@protein.pdbqt" \\
|
| 1372 |
+
-F "ligand_name=Erlotinib" \\
|
| 1373 |
+
-F "ligand_smiles=CC1=CC(=O)NC2=C1C=CC=C2" \\
|
| 1374 |
+
-F "center_x=10.0" \\
|
| 1375 |
+
-F "center_y=15.0" \\
|
| 1376 |
+
-F "center_z=20.0" \\
|
| 1377 |
+
-F "box_size_x=25.0" \\
|
| 1378 |
+
-F "box_size_y=25.0" \\
|
| 1379 |
+
-F "box_size_z=25.0" \\
|
| 1380 |
+
-F "exhaustiveness=8" \\
|
| 1381 |
+
-F "n_poses=5"
|
| 1382 |
+
```
|
| 1383 |
+
|
| 1384 |
+
#### Success response
|
| 1385 |
+
|
| 1386 |
+
```json
|
| 1387 |
+
{
|
| 1388 |
+
"success": true,
|
| 1389 |
+
"message": "Docking Job Successfully Queued",
|
| 1390 |
+
"data": {
|
| 1391 |
+
"job_id": 123,
|
| 1392 |
+
"status": "pending"
|
| 1393 |
+
}
|
| 1394 |
+
}
|
| 1395 |
+
```
|
| 1396 |
+
|
| 1397 |
+
---
|
| 1398 |
+
|
| 1399 |
+
### GET `/api/docking/{id}`
|
| 1400 |
+
|
| 1401 |
+
- Auth required
|
| 1402 |
+
- Path parameter:
|
| 1403 |
+
- `id` (integer)
|
| 1404 |
+
|
| 1405 |
+
#### Success response (pending or completed)
|
| 1406 |
+
|
| 1407 |
+
```json
|
| 1408 |
+
{
|
| 1409 |
+
"success": true,
|
| 1410 |
+
"message": "Job details retrieved successfully",
|
| 1411 |
+
"data": {
|
| 1412 |
+
"job_id": 14,
|
| 1413 |
+
"status": "completed",
|
| 1414 |
+
"inputs": {
|
| 1415 |
+
"protein": "EGFR",
|
| 1416 |
+
"ligand": "Erlotinib"
|
| 1417 |
+
},
|
| 1418 |
+
"created_at": "2026-04-21T18:29:45+00:00",
|
| 1419 |
+
"results": {
|
| 1420 |
+
"vina_scores": [0, 0.001, 0.002],
|
| 1421 |
+
"download_url": "{base_url}/api/docking/download/{job_id}"
|
| 1422 |
+
}
|
| 1423 |
+
}
|
| 1424 |
+
}
|
| 1425 |
+
```
|
| 1426 |
+
|
| 1427 |
+
If the job is not completed, the `results` block may be omitted.
|
| 1428 |
+
|
| 1429 |
+
---
|
| 1430 |
+
|
| 1431 |
+
### GET `/api/docking/history`
|
| 1432 |
+
|
| 1433 |
+
- Auth required
|
| 1434 |
+
- Query parameters:
|
| 1435 |
+
- `per_page` (integer, optional, default 15)
|
| 1436 |
+
|
| 1437 |
+
#### Response
|
| 1438 |
+
|
| 1439 |
+
```json
|
| 1440 |
+
{
|
| 1441 |
+
"success": true,
|
| 1442 |
+
"message": "Docking history retrieved successfully",
|
| 1443 |
+
"data": {
|
| 1444 |
+
"data": [
|
| 1445 |
+
{
|
| 1446 |
+
"job_id": 14,
|
| 1447 |
+
"status": "completed",
|
| 1448 |
+
"inputs": {
|
| 1449 |
+
"protein": "EGFR",
|
| 1450 |
+
"ligand": "Erlotinib"
|
| 1451 |
+
},
|
| 1452 |
+
"created_at": "2026-04-21T18:29:45+00:00",
|
| 1453 |
+
"results": {
|
| 1454 |
+
"vina_scores": [0, 0.001, 0.002],
|
| 1455 |
+
"download_url": "{base_url}/api/docking/download/{job_id}"
|
| 1456 |
+
}
|
| 1457 |
+
}
|
| 1458 |
+
],
|
| 1459 |
+
"pagination": {
|
| 1460 |
+
"current_page": 1,
|
| 1461 |
+
"per_page": 2,
|
| 1462 |
+
"total": 3,
|
| 1463 |
+
"last_page": 2,
|
| 1464 |
+
"has_more": true
|
| 1465 |
+
}
|
| 1466 |
+
}
|
| 1467 |
+
}
|
| 1468 |
+
```
|
| 1469 |
+
|
| 1470 |
+
---
|
| 1471 |
+
|
| 1472 |
+
### GET `/api/docking/download/{id}`
|
| 1473 |
+
|
| 1474 |
+
- Auth required
|
| 1475 |
+
- Path parameter:
|
| 1476 |
+
- `id` (integer)
|
| 1477 |
+
- Returns a file download for the completed docking result.
|
| 1478 |
+
- Content disposition filename: `docking_result_{id}.pdbqt`
|
| 1479 |
+
|
| 1480 |
+
---
|
| 1481 |
+
|
| 1482 |
+
## Drug Repurposing API
|
| 1483 |
+
|
| 1484 |
+
### POST `/api/drug-repurposing/targets`
|
| 1485 |
+
|
| 1486 |
+
- Auth required
|
| 1487 |
+
- Content type: `application/json`
|
| 1488 |
+
- Request body:
|
| 1489 |
+
- `disease_name` (string, required)
|
| 1490 |
+
- `top_n` (integer, optional, default 10, range 1–100)
|
| 1491 |
+
|
| 1492 |
+
#### Example curl request
|
| 1493 |
+
|
| 1494 |
+
```bash
|
| 1495 |
+
curl -X POST "{base_url}/api/drug-repurposing/targets" \\
|
| 1496 |
+
-H "Authorization: Bearer {token}" \\
|
| 1497 |
+
-H "Content-Type: application/json" \\
|
| 1498 |
+
-d '{"disease_name": "Type 2 Diabetes", "top_n": 10}'
|
| 1499 |
+
```
|
| 1500 |
+
|
| 1501 |
+
#### Success response
|
| 1502 |
+
|
| 1503 |
+
```json
|
| 1504 |
+
{
|
| 1505 |
+
"success": true,
|
| 1506 |
+
"message": "Target lookup queued successfully",
|
| 1507 |
+
"data": {
|
| 1508 |
+
"job_id": 15,
|
| 1509 |
+
"status": "pending"
|
| 1510 |
+
}
|
| 1511 |
+
}
|
| 1512 |
+
```
|
| 1513 |
+
|
| 1514 |
+
---
|
| 1515 |
+
|
| 1516 |
+
### GET `/api/drug-repurposing/targets/{id}`
|
| 1517 |
+
|
| 1518 |
+
- Auth required
|
| 1519 |
+
- Path parameter:
|
| 1520 |
+
- `id` (integer)
|
| 1521 |
+
|
| 1522 |
+
#### Success response (completed)
|
| 1523 |
+
|
| 1524 |
+
```json
|
| 1525 |
+
{
|
| 1526 |
+
"success": true,
|
| 1527 |
+
"message": "Target lookup status retrieved successfully",
|
| 1528 |
+
"data": {
|
| 1529 |
+
"job_id": 4,
|
| 1530 |
+
"status": "completed",
|
| 1531 |
+
"input": {
|
| 1532 |
+
"disease_name": "Type 2 Diabetes",
|
| 1533 |
+
"top_n": 10
|
| 1534 |
+
},
|
| 1535 |
+
"output": {
|
| 1536 |
+
"disease": "Type 2 Diabetes",
|
| 1537 |
+
"total_targets": 10,
|
| 1538 |
+
"targets": [
|
| 1539 |
+
{"symbol": "KCNJ11", "name": "potassium inwardly rectifying channel subfamily J member 11", "score": 0.8651},
|
| 1540 |
+
{"symbol": "ABCC8", "name": "ATP binding cassette subfamily C member 8", "score": 0.8648},
|
| 1541 |
+
{"symbol": "GCK", "name": "glucokinase", "score": 0.8612},
|
| 1542 |
+
{"symbol": "PPARG", "name": "peroxisome proliferator activated receptor gamma", "score": 0.8486},
|
| 1543 |
+
{"symbol": "INSR", "name": "insulin receptor", "score": 0.7887},
|
| 1544 |
+
{"symbol": "HNF1B", "name": "HNF1 homeobox B", "score": 0.7846},
|
| 1545 |
+
{"symbol": "HNF1A", "name": "HNF1 homeobox A", "score": 0.7796},
|
| 1546 |
+
{"symbol": "HNF4A", "name": "hepatocyte nuclear factor 4 alpha", "score": 0.7763},
|
| 1547 |
+
{"symbol": "WFS1", "name": "wolframin ER transmembrane glycoprotein", "score": 0.7695},
|
| 1548 |
+
{"symbol": "GLP1R", "name": "glucagon like peptide 1 receptor", "score": 0.7667}
|
| 1549 |
+
]
|
| 1550 |
+
},
|
| 1551 |
+
"created_at": "2026-06-03T07:31:42.000000Z"
|
| 1552 |
+
}
|
| 1553 |
+
}
|
| 1554 |
+
```
|
| 1555 |
+
|
| 1556 |
+
If the job is not completed, the `output` field will be `null`.
|
| 1557 |
+
|
| 1558 |
+
---
|
| 1559 |
+
|
| 1560 |
+
### GET `/api/drug-repurposing/targets/history`
|
| 1561 |
+
|
| 1562 |
+
- Auth required
|
| 1563 |
+
- Query parameters:
|
| 1564 |
+
- `per_page` (integer, optional, default 15)
|
| 1565 |
+
|
| 1566 |
+
#### Response
|
| 1567 |
+
|
| 1568 |
+
```json
|
| 1569 |
+
{
|
| 1570 |
+
"success": true,
|
| 1571 |
+
"message": "Target lookup history retrieved successfully",
|
| 1572 |
+
"data": {
|
| 1573 |
+
"data": [
|
| 1574 |
+
{
|
| 1575 |
+
"id": 4,
|
| 1576 |
+
"input": {
|
| 1577 |
+
"disease_name": "Type 2 Diabetes",
|
| 1578 |
+
"top_n": 10
|
| 1579 |
+
},
|
| 1580 |
+
"output": {
|
| 1581 |
+
"disease": "Type 2 Diabetes",
|
| 1582 |
+
"total_targets": 10,
|
| 1583 |
+
"targets": [
|
| 1584 |
+
{"symbol": "KCNJ11", "name": "potassium inwardly rectifying channel subfamily J member 11", "score": 0.8651},
|
| 1585 |
+
{"symbol": "GCK", "name": "glucokinase", "score": 0.8612}
|
| 1586 |
+
]
|
| 1587 |
+
},
|
| 1588 |
+
"status": "completed",
|
| 1589 |
+
"created_at": "2026-06-03T07:31:42.000000Z"
|
| 1590 |
+
}
|
| 1591 |
+
],
|
| 1592 |
+
"pagination": {
|
| 1593 |
+
"current_page": 1,
|
| 1594 |
+
"per_page": 15,
|
| 1595 |
+
"total": 3,
|
| 1596 |
+
"last_page": 1,
|
| 1597 |
+
"has_more": false
|
| 1598 |
+
}
|
| 1599 |
+
}
|
| 1600 |
+
}
|
| 1601 |
+
```
|
| 1602 |
+
|
| 1603 |
+
---
|
| 1604 |
+
|
| 1605 |
+
### POST `/api/drug-repurposing/screen`
|
| 1606 |
+
|
| 1607 |
+
- Auth required
|
| 1608 |
+
- Content type: `application/json`
|
| 1609 |
+
- Request body:
|
| 1610 |
+
- `disease_name` (string, required)
|
| 1611 |
+
- `known_drugs` (array of strings, optional)
|
| 1612 |
+
- `min_score` (numeric, optional, range 0–1)
|
| 1613 |
+
- `top_n_targets` (integer, optional, range 1–100)
|
| 1614 |
+
|
| 1615 |
+
#### Example curl request
|
| 1616 |
+
|
| 1617 |
+
```bash
|
| 1618 |
+
curl -X POST "{base_url}/api/drug-repurposing/screen" \\
|
| 1619 |
+
-H "Authorization: Bearer {token}" \\
|
| 1620 |
+
-H "Content-Type: application/json" \\
|
| 1621 |
+
-d '{
|
| 1622 |
+
"disease_name": "Type 2 Diabetes",
|
| 1623 |
+
"known_drugs": ["Metformin", "Insulin"],
|
| 1624 |
+
"min_score": 0.5,
|
| 1625 |
+
"top_n_targets": 10
|
| 1626 |
+
}'
|
| 1627 |
+
```
|
| 1628 |
+
|
| 1629 |
+
#### Success response
|
| 1630 |
+
|
| 1631 |
+
```json
|
| 1632 |
+
{
|
| 1633 |
+
"success": true,
|
| 1634 |
+
"message": "Screening queued successfully",
|
| 1635 |
+
"data": {
|
| 1636 |
+
"job_id": 22,
|
| 1637 |
+
"status": "pending"
|
| 1638 |
+
}
|
| 1639 |
+
}
|
| 1640 |
+
```
|
| 1641 |
+
|
| 1642 |
+
---
|
| 1643 |
+
|
| 1644 |
+
### GET `/api/drug-repurposing/screen/{id}`
|
| 1645 |
+
|
| 1646 |
+
- Auth required
|
| 1647 |
+
- Path parameter:
|
| 1648 |
+
- `id` (integer)
|
| 1649 |
+
|
| 1650 |
+
#### Success response (completed)
|
| 1651 |
+
|
| 1652 |
+
```json
|
| 1653 |
+
{
|
| 1654 |
+
"success": true,
|
| 1655 |
+
"message": "Screening status retrieved successfully",
|
| 1656 |
+
"data": {
|
| 1657 |
+
"job_id": 22,
|
| 1658 |
+
"status": "completed",
|
| 1659 |
+
"input": {
|
| 1660 |
+
"disease_name": "Type 2 Diabetes",
|
| 1661 |
+
"min_score": 0.5,
|
| 1662 |
+
"top_n_targets": 10,
|
| 1663 |
+
"known_drugs": ["Metformin", "Insulin"]
|
| 1664 |
+
},
|
| 1665 |
+
"output": {
|
| 1666 |
+
"disease": "Type 2 Diabetes",
|
| 1667 |
+
"total_drugs": 200,
|
| 1668 |
+
"total_predictions": 2000,
|
| 1669 |
+
"top_results": [
|
| 1670 |
+
{"drug_name": "Drug_CHEMBL1754", "target_symbol": "KCNJ11", "score": 1, "status": "Potential Discovery"},
|
| 1671 |
+
{"drug_name": "Drug_CHEMBL1754", "target_symbol": "ABCC8", "score": 1, "status": "Potential Discovery"},
|
| 1672 |
+
{"drug_name": "Drug_CHEMBL1754", "target_symbol": "GCK", "score": 1, "status": "Potential Discovery"},
|
| 1673 |
+
{"drug_name": "Drug_CHEMBL1754", "target_symbol": "PPARG", "score": 1, "status": "Potential Discovery"},
|
| 1674 |
+
{"drug_name": "Drug_CHEMBL1754", "target_symbol": "INSR", "score": 1, "status": "Potential Discovery"}
|
| 1675 |
+
],
|
| 1676 |
+
"success": true,
|
| 1677 |
+
"message": "✅ Screening completed in 47.30s using CPU. Found 2000 candidates (10 in top results)."
|
| 1678 |
+
},
|
| 1679 |
+
"created_at": "2026-06-02T21:35:24.000000Z"
|
| 1680 |
+
}
|
| 1681 |
+
}
|
| 1682 |
+
```
|
| 1683 |
+
|
| 1684 |
+
If the job is not completed, the `output` field will be `null`.
|
| 1685 |
+
|
| 1686 |
+
---
|
| 1687 |
+
|
| 1688 |
+
### GET `/api/drug-repurposing/screen/history`
|
| 1689 |
+
|
| 1690 |
+
- Auth required
|
| 1691 |
+
- Query parameters:
|
| 1692 |
+
- `per_page` (integer, optional, default 15)
|
| 1693 |
+
|
| 1694 |
+
#### Response
|
| 1695 |
+
|
| 1696 |
+
```json
|
| 1697 |
+
{
|
| 1698 |
+
"success": true,
|
| 1699 |
+
"message": "Screening history retrieved successfully",
|
| 1700 |
+
"data": {
|
| 1701 |
+
"data": [
|
| 1702 |
+
{
|
| 1703 |
+
"id": 22,
|
| 1704 |
+
"input": {
|
| 1705 |
+
"disease_name": "Type 2 Diabetes",
|
| 1706 |
+
"min_score": 0.5,
|
| 1707 |
+
"top_n_targets": 10,
|
| 1708 |
+
"known_drugs": ["Metformin", "Insulin"]
|
| 1709 |
+
},
|
| 1710 |
+
"output": {
|
| 1711 |
+
"disease": "Type 2 Diabetes",
|
| 1712 |
+
"total_drugs": 200,
|
| 1713 |
+
"total_predictions": 2000,
|
| 1714 |
+
"top_results": [
|
| 1715 |
+
{"drug_name": "Drug_CHEMBL1754", "target_symbol": "KCNJ11", "score": 1, "status": "Potential Discovery"},
|
| 1716 |
+
{"drug_name": "Drug_CHEMBL1754", "target_symbol": "ABCC8", "score": 1, "status": "Potential Discovery"}
|
| 1717 |
+
],
|
| 1718 |
+
"success": true,
|
| 1719 |
+
"message": "✅ Screening completed in 47.30s using CPU. Found 2000 candidates (10 in top results)."
|
| 1720 |
+
},
|
| 1721 |
+
"status": "completed",
|
| 1722 |
+
"created_at": "2026-06-02T21:35:24.000000Z"
|
| 1723 |
+
}
|
| 1724 |
+
],
|
| 1725 |
+
"pagination": {
|
| 1726 |
+
"current_page": 1,
|
| 1727 |
+
"per_page": 15,
|
| 1728 |
+
"total": 5,
|
| 1729 |
+
"last_page": 1,
|
| 1730 |
+
"has_more": false
|
| 1731 |
+
}
|
| 1732 |
+
}
|
| 1733 |
+
}
|
| 1734 |
+
```
|
| 1735 |
+
|
| 1736 |
+
---
|
| 1737 |
+
|
| 1738 |
+
## Convert SMILES API
|
| 1739 |
+
|
| 1740 |
+
### GET `/api/convert-smiles/history`
|
| 1741 |
+
|
| 1742 |
+
- Auth required
|
| 1743 |
+
- Query parameters:
|
| 1744 |
+
- `per_page` (integer, optional, default 15)
|
| 1745 |
+
|
| 1746 |
+
#### Response
|
| 1747 |
+
|
| 1748 |
+
```json
|
| 1749 |
+
{
|
| 1750 |
+
"success": true,
|
| 1751 |
+
"message": "Conversion history retrieved successfully",
|
| 1752 |
+
"data": {
|
| 1753 |
+
"items": [
|
| 1754 |
+
{
|
| 1755 |
+
"job_id": 16,
|
| 1756 |
+
"status": "completed",
|
| 1757 |
+
"smiles": "CCN1CC(CCN2CCOCC2)C(c2ccccc2)(c2ccccc2)Cl=0",
|
| 1758 |
+
"created_at": "2026-04-21T18:43:58+00:00",
|
| 1759 |
+
"results": {
|
| 1760 |
+
"download_url": "{base_url}/api/convert-smiles/download/{job_id}"
|
| 1761 |
+
}
|
| 1762 |
+
}
|
| 1763 |
+
],
|
| 1764 |
+
"pagination": {
|
| 1765 |
+
"current_page": 1,
|
| 1766 |
+
"per_page": 3,
|
| 1767 |
+
"total": 13,
|
| 1768 |
+
"last_page": 5,
|
| 1769 |
+
"has_more": true
|
| 1770 |
+
}
|
| 1771 |
+
}
|
| 1772 |
+
}
|
| 1773 |
+
```
|
| 1774 |
+
|
| 1775 |
+
---
|
| 1776 |
+
|
| 1777 |
+
### POST `/api/convert-smiles/convert`
|
| 1778 |
+
|
| 1779 |
+
- Auth required
|
| 1780 |
+
- Content type: `application/json`
|
| 1781 |
+
- Request body:
|
| 1782 |
+
- `ligand_smiles` (string, required)
|
| 1783 |
+
|
| 1784 |
+
#### Example curl request
|
| 1785 |
+
|
| 1786 |
+
```bash
|
| 1787 |
+
curl -X POST "{base_url}/api/convert-smiles/convert" \\
|
| 1788 |
+
-H "Authorization: Bearer {token}" \\
|
| 1789 |
+
-H "Content-Type: application/json" \\
|
| 1790 |
+
-d '{"ligand_smiles":"CCN1CC(CCN2CCOCC2)C(c2ccccc2)(c2ccccc2)Cl=0"}'
|
| 1791 |
+
```
|
| 1792 |
+
|
| 1793 |
+
#### Success response
|
| 1794 |
+
|
| 1795 |
+
```json
|
| 1796 |
+
{
|
| 1797 |
+
"success": true,
|
| 1798 |
+
"message": "SMILES converted to PDBQT successfully",
|
| 1799 |
+
"data": {
|
| 1800 |
+
"job_id": 16,
|
| 1801 |
+
"download_url": "{base_url}/api/convert-smiles/download/{job_id}",
|
| 1802 |
+
"smiles": "CCN1CC(CCN2CCOCC2)C(c2ccccc2)(c2ccccc2)Cl=0"
|
| 1803 |
+
}
|
| 1804 |
+
}
|
| 1805 |
+
```
|
| 1806 |
+
|
| 1807 |
+
---
|
| 1808 |
+
|
| 1809 |
+
### GET `/api/convert-smiles/download/{id}`
|
| 1810 |
+
|
| 1811 |
+
- Auth required
|
| 1812 |
+
- Path parameter:
|
| 1813 |
+
- `id` (integer)
|
| 1814 |
+
- Returns a file download for the converted PDBQT.
|
| 1815 |
+
- Content disposition filename: `converted_ligand_{id}.pdbqt`
|
| 1816 |
+
|
| 1817 |
+
---
|
| 1818 |
+
|
| 1819 |
+
## User Management Endpoints
|
| 1820 |
+
|
| 1821 |
+
### Get Profile
|
| 1822 |
+
|
| 1823 |
+
```http
|
| 1824 |
+
GET /user/profile
|
| 1825 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1826 |
+
```
|
| 1827 |
+
|
| 1828 |
+
**Response (200 OK):**
|
| 1829 |
+
|
| 1830 |
+
```json
|
| 1831 |
+
{
|
| 1832 |
+
"success": true,
|
| 1833 |
+
"user": {
|
| 1834 |
+
"id": 1,
|
| 1835 |
+
"name": "John Researcher",
|
| 1836 |
+
"email": "john@pharmaai.io",
|
| 1837 |
+
"email_verified_at": "2026-05-29T10:00:00Z",
|
| 1838 |
+
"profile": {
|
| 1839 |
+
"institution": "MIT",
|
| 1840 |
+
"research_focus": "drug_discovery",
|
| 1841 |
+
"bio": "Computational chemist..."
|
| 1842 |
+
},
|
| 1843 |
+
"created_at": "2026-05-01T00:00:00Z"
|
| 1844 |
+
}
|
| 1845 |
+
}
|
| 1846 |
+
```
|
| 1847 |
+
|
| 1848 |
+
### Update Profile
|
| 1849 |
+
|
| 1850 |
+
```http
|
| 1851 |
+
POST /user/update-profile
|
| 1852 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 1853 |
+
Content-Type: application/json
|
| 1854 |
+
|
| 1855 |
+
{
|
| 1856 |
+
"name": "John Researcher, PhD",
|
| 1857 |
+
"profile": {
|
| 1858 |
+
"institution": "Stanford",
|
| 1859 |
+
"research_focus": "drug_discovery",
|
| 1860 |
+
"bio": "Senior researcher..."
|
| 1861 |
+
}
|
| 1862 |
+
}
|
| 1863 |
+
```
|
| 1864 |
+
|
| 1865 |
+
**Response (200 OK):**
|
| 1866 |
+
|
| 1867 |
+
```json
|
| 1868 |
+
{
|
| 1869 |
+
"success": true,
|
| 1870 |
+
"message": "Profile updated successfully",
|
| 1871 |
+
"user": {...}
|
| 1872 |
+
}
|
| 1873 |
+
```
|
| 1874 |
+
|
| 1875 |
+
---
|
| 1876 |
+
|
| 1877 |
+
## Awards And Scientists API
|
| 1878 |
+
|
| 1879 |
+
### List Awards
|
| 1880 |
+
|
| 1881 |
+
```http
|
| 1882 |
+
GET /awards?page=1&per_page=10
|
| 1883 |
+
```
|
| 1884 |
+
|
| 1885 |
+
**Response (200 OK):**
|
| 1886 |
+
|
| 1887 |
+
```json
|
| 1888 |
+
{
|
| 1889 |
+
"success": true,
|
| 1890 |
+
"message": "Awards retrieved successfully",
|
| 1891 |
+
"data": {
|
| 1892 |
+
"results": [
|
| 1893 |
+
{
|
| 1894 |
+
"id": 1,
|
| 1895 |
+
"name": "Nobel Prize in Physiology or Medicine",
|
| 1896 |
+
"category": "Medicine",
|
| 1897 |
+
"images": ["https://..."],
|
| 1898 |
+
"short_description": "The Nobel Prize in Physiology or Medicine is the world's most prestigious award...",
|
| 1899 |
+
"scientists_count": 9,
|
| 1900 |
+
"scientists": [
|
| 1901 |
+
{
|
| 1902 |
+
"id": 14,
|
| 1903 |
+
"name": "Alexander Fleming",
|
| 1904 |
+
"nationality": "British",
|
| 1905 |
+
"birth_year": null,
|
| 1906 |
+
"death_year": null,
|
| 1907 |
+
"field": "Microbiology",
|
| 1908 |
+
"images": ["https://..."],
|
| 1909 |
+
"bio": null,
|
| 1910 |
+
"short_bio": null,
|
| 1911 |
+
"impact": null
|
| 1912 |
+
}
|
| 1913 |
+
]
|
| 1914 |
+
}
|
| 1915 |
+
],
|
| 1916 |
+
"pagination": {
|
| 1917 |
+
"currentPage": 1,
|
| 1918 |
+
"totalPages": 1,
|
| 1919 |
+
"totalResults": 10,
|
| 1920 |
+
"perPage": 10,
|
| 1921 |
+
"hasNextPage": false,
|
| 1922 |
+
"hasPrevPage": false
|
| 1923 |
+
}
|
| 1924 |
+
}
|
| 1925 |
+
}
|
| 1926 |
+
```
|
| 1927 |
+
|
| 1928 |
+
---
|
| 1929 |
+
|
| 1930 |
+
### Get Award Details
|
| 1931 |
+
|
| 1932 |
+
```http
|
| 1933 |
+
GET /awards/{award_id}
|
| 1934 |
+
```
|
| 1935 |
+
|
| 1936 |
+
**Example:** `GET /awards/2`
|
| 1937 |
+
|
| 1938 |
+
**Response (200 OK):**
|
| 1939 |
+
|
| 1940 |
+
```json
|
| 1941 |
+
{
|
| 1942 |
+
"success": true,
|
| 1943 |
+
"message": "Award retrieved successfully",
|
| 1944 |
+
"data": {
|
| 1945 |
+
"id": 2,
|
| 1946 |
+
"name": "Lasker Award",
|
| 1947 |
+
"category": "Medical Research",
|
| 1948 |
+
"images": ["https://..."],
|
| 1949 |
+
"short_description": "The Lasker Award is often called the 'American Nobel Prize'...",
|
| 1950 |
+
"scientists": [
|
| 1951 |
+
{
|
| 1952 |
+
"id": 17,
|
| 1953 |
+
"name": "Tu Youyou",
|
| 1954 |
+
"nationality": "Chinese",
|
| 1955 |
+
"birth_year": null,
|
| 1956 |
+
"death_year": null,
|
| 1957 |
+
"field": "Pharmaceutical Chemistry",
|
| 1958 |
+
"images": ["https://..."],
|
| 1959 |
+
"bio": null,
|
| 1960 |
+
"short_bio": null,
|
| 1961 |
+
"impact": null
|
| 1962 |
+
}
|
| 1963 |
+
]
|
| 1964 |
+
}
|
| 1965 |
+
}
|
| 1966 |
+
```
|
| 1967 |
+
|
| 1968 |
+
---
|
| 1969 |
+
|
| 1970 |
+
### Get Award Scientists
|
| 1971 |
+
|
| 1972 |
+
```http
|
| 1973 |
+
GET /awards/{award_id}/scientists
|
| 1974 |
+
```
|
| 1975 |
+
|
| 1976 |
+
**Example:** `GET /awards/2/scientists`
|
| 1977 |
+
|
| 1978 |
+
**Response (200 OK):**
|
| 1979 |
+
|
| 1980 |
+
```json
|
| 1981 |
+
{
|
| 1982 |
+
"success": true,
|
| 1983 |
+
"message": "Scientists retrieved successfully",
|
| 1984 |
+
"data": [
|
| 1985 |
+
{
|
| 1986 |
+
"id": 17,
|
| 1987 |
+
"name": "Tu Youyou",
|
| 1988 |
+
"nationality": "Chinese",
|
| 1989 |
+
"birth_year": null,
|
| 1990 |
+
"death_year": null,
|
| 1991 |
+
"field": "Pharmaceutical Chemistry",
|
| 1992 |
+
"images": ["https://..."],
|
| 1993 |
+
"bio": null,
|
| 1994 |
+
"short_bio": null,
|
| 1995 |
+
"impact": null
|
| 1996 |
+
}
|
| 1997 |
+
]
|
| 1998 |
+
}
|
| 1999 |
+
```
|
| 2000 |
+
|
| 2001 |
+
---
|
| 2002 |
+
|
| 2003 |
+
### List Scientists
|
| 2004 |
+
|
| 2005 |
+
```http
|
| 2006 |
+
GET /scientists?page=1&per_page=50
|
| 2007 |
+
```
|
| 2008 |
+
|
| 2009 |
+
**Response (200 OK):**
|
| 2010 |
+
|
| 2011 |
+
```json
|
| 2012 |
+
{
|
| 2013 |
+
"success": true,
|
| 2014 |
+
"message": "Scientists retrieved successfully",
|
| 2015 |
+
"data": {
|
| 2016 |
+
"results": [
|
| 2017 |
+
{
|
| 2018 |
+
"id": 14,
|
| 2019 |
+
"name": "Alexander Fleming",
|
| 2020 |
+
"nationality": "British",
|
| 2021 |
+
"birth_year": null,
|
| 2022 |
+
"death_year": null,
|
| 2023 |
+
"field": "Microbiology",
|
| 2024 |
+
"images": ["https://..."],
|
| 2025 |
+
"bio": null,
|
| 2026 |
+
"short_bio": null,
|
| 2027 |
+
"impact": null
|
| 2028 |
+
}
|
| 2029 |
+
],
|
| 2030 |
+
"pagination": {
|
| 2031 |
+
"currentPage": 1,
|
| 2032 |
+
"totalPages": 1,
|
| 2033 |
+
"totalResults": 24,
|
| 2034 |
+
"perPage": 50,
|
| 2035 |
+
"hasNextPage": false,
|
| 2036 |
+
"hasPrevPage": false
|
| 2037 |
+
}
|
| 2038 |
+
}
|
| 2039 |
+
}
|
| 2040 |
+
```
|
| 2041 |
+
|
| 2042 |
+
---
|
| 2043 |
+
|
| 2044 |
+
### Get Scientist Details
|
| 2045 |
+
|
| 2046 |
+
```http
|
| 2047 |
+
GET /scientists/{scientist_id}
|
| 2048 |
+
```
|
| 2049 |
+
|
| 2050 |
+
**Response (200 OK):**
|
| 2051 |
+
|
| 2052 |
+
```json
|
| 2053 |
+
{
|
| 2054 |
+
"success": true,
|
| 2055 |
+
"message": "Scientist retrieved successfully",
|
| 2056 |
+
"data": {
|
| 2057 |
+
"id": 14,
|
| 2058 |
+
"name": "Alexander Fleming",
|
| 2059 |
+
"nationality": "British",
|
| 2060 |
+
"birth_year": null,
|
| 2061 |
+
"death_year": null,
|
| 2062 |
+
"field": "Microbiology",
|
| 2063 |
+
"images": ["https://..."],
|
| 2064 |
+
"bio": "Full biography text...",
|
| 2065 |
+
"short_bio": "Short biography...",
|
| 2066 |
+
"impact": "Scientific impact description...",
|
| 2067 |
+
"awards": [
|
| 2068 |
+
{
|
| 2069 |
+
"id": 1,
|
| 2070 |
+
"name": "Nobel Prize in Physiology or Medicine",
|
| 2071 |
+
"year": 1945
|
| 2072 |
+
}
|
| 2073 |
+
]
|
| 2074 |
+
}
|
| 2075 |
+
}
|
| 2076 |
+
```
|
| 2077 |
+
|
| 2078 |
+
---
|
| 2079 |
+
|
| 2080 |
+
### Get Scientist Awards
|
| 2081 |
+
|
| 2082 |
+
```http
|
| 2083 |
+
GET /scientists/{scientist_id}/awards
|
| 2084 |
+
```
|
| 2085 |
+
|
| 2086 |
+
**Response (200 OK):**
|
| 2087 |
+
|
| 2088 |
+
```json
|
| 2089 |
+
{
|
| 2090 |
+
"success": true,
|
| 2091 |
+
"message": "Awards retrieved successfully",
|
| 2092 |
+
"data": [
|
| 2093 |
+
{
|
| 2094 |
+
"id": 1,
|
| 2095 |
+
"name": "Nobel Prize in Physiology or Medicine",
|
| 2096 |
+
"category": "Medicine",
|
| 2097 |
+
"year": 1945,
|
| 2098 |
+
"images": ["https://..."]
|
| 2099 |
+
}
|
| 2100 |
+
]
|
| 2101 |
+
}
|
| 2102 |
+
```
|
| 2103 |
+
|
| 2104 |
+
---
|
| 2105 |
+
|
| 2106 |
+
## News API
|
| 2107 |
+
|
| 2108 |
+
### Get News Feed
|
| 2109 |
+
|
| 2110 |
+
```http
|
| 2111 |
+
GET /news?page=1&per_page=10
|
| 2112 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 2113 |
+
```
|
| 2114 |
+
|
| 2115 |
+
**Response (200 OK):**
|
| 2116 |
+
|
| 2117 |
+
```json
|
| 2118 |
+
{
|
| 2119 |
+
"success": true,
|
| 2120 |
+
"message": "Articles retrieved successfully",
|
| 2121 |
+
"data": {
|
| 2122 |
+
"results": [
|
| 2123 |
+
{
|
| 2124 |
+
"id": 13,
|
| 2125 |
+
"title": "AAPS National Biotechnology Conference",
|
| 2126 |
+
"summary": "11 May 2026 - 14 May 2026 - All Day Sheraton San Diego...",
|
| 2127 |
+
"source": "European Pharmaceutical Review",
|
| 2128 |
+
"url": "https://...",
|
| 2129 |
+
"published_at": "2026-05-11T07:00:00+00:00"
|
| 2130 |
+
}
|
| 2131 |
+
],
|
| 2132 |
+
"pagination": {
|
| 2133 |
+
"currentPage": 1,
|
| 2134 |
+
"totalPages": 44,
|
| 2135 |
+
"totalResults": 434,
|
| 2136 |
+
"perPage": 10,
|
| 2137 |
+
"hasNextPage": true,
|
| 2138 |
+
"hasPrevPage": false
|
| 2139 |
+
}
|
| 2140 |
+
}
|
| 2141 |
+
}
|
| 2142 |
+
```
|
| 2143 |
+
|
| 2144 |
+
---
|
| 2145 |
+
|
| 2146 |
+
### Get News with Pagination
|
| 2147 |
+
|
| 2148 |
+
```http
|
| 2149 |
+
GET /news?page=24&per_page=10
|
| 2150 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 2151 |
+
```
|
| 2152 |
+
|
| 2153 |
+
**Response (200 OK):**
|
| 2154 |
+
|
| 2155 |
+
```json
|
| 2156 |
+
{
|
| 2157 |
+
"success": true,
|
| 2158 |
+
"message": "Articles retrieved successfully",
|
| 2159 |
+
"data": {
|
| 2160 |
+
"results": [
|
| 2161 |
+
{
|
| 2162 |
+
"id": 21,
|
| 2163 |
+
"title": "EU kicks off one-year pilot to expedite multinational trials",
|
| 2164 |
+
"summary": "The European Union unveiled details of a pilot project...",
|
| 2165 |
+
"source": "Endpoints News",
|
| 2166 |
+
"url": "https://...",
|
| 2167 |
+
"published_at": "2026-01-23T19:24:15+00:00"
|
| 2168 |
+
}
|
| 2169 |
+
],
|
| 2170 |
+
"pagination": {
|
| 2171 |
+
"currentPage": 24,
|
| 2172 |
+
"totalPages": 44,
|
| 2173 |
+
"totalResults": 434,
|
| 2174 |
+
"perPage": 10,
|
| 2175 |
+
"hasNextPage": true,
|
| 2176 |
+
"hasPrevPage": true
|
| 2177 |
+
}
|
| 2178 |
+
}
|
| 2179 |
+
}
|
| 2180 |
+
```
|
| 2181 |
+
|
| 2182 |
+
---
|
| 2183 |
+
|
| 2184 |
+
### Refresh News
|
| 2185 |
+
|
| 2186 |
+
```http
|
| 2187 |
+
GET /news/refresh
|
| 2188 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 2189 |
+
```
|
| 2190 |
+
|
| 2191 |
+
**Response (200 OK):**
|
| 2192 |
+
|
| 2193 |
+
```json
|
| 2194 |
+
{
|
| 2195 |
+
"success": true,
|
| 2196 |
+
"message": "News refreshed successfully",
|
| 2197 |
+
"data": {
|
| 2198 |
+
"new_articles": 5,
|
| 2199 |
+
"total_articles": 439
|
| 2200 |
+
}
|
| 2201 |
+
}
|
| 2202 |
+
```
|
| 2203 |
+
|
| 2204 |
+
---
|
| 2205 |
+
|
| 2206 |
+
### Get News Categories
|
| 2207 |
+
|
| 2208 |
+
```http
|
| 2209 |
+
GET /news/categories
|
| 2210 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 2211 |
+
```
|
| 2212 |
+
|
| 2213 |
+
**Response (200 OK):**
|
| 2214 |
+
|
| 2215 |
+
```json
|
| 2216 |
+
{
|
| 2217 |
+
"success": true,
|
| 2218 |
+
"message": "Categories retrieved successfully",
|
| 2219 |
+
"data": [
|
| 2220 |
+
"chemistry",
|
| 2221 |
+
"pharma",
|
| 2222 |
+
"biotech",
|
| 2223 |
+
"medicine",
|
| 2224 |
+
"research",
|
| 2225 |
+
"clinical_trials"
|
| 2226 |
+
]
|
| 2227 |
+
}
|
| 2228 |
+
```
|
| 2229 |
+
|
| 2230 |
+
---
|
| 2231 |
+
|
| 2232 |
+
### Save Article
|
| 2233 |
+
|
| 2234 |
+
```http
|
| 2235 |
+
POST /news/{article_id}/save
|
| 2236 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 2237 |
+
```
|
| 2238 |
+
|
| 2239 |
+
**Response (200 OK):**
|
| 2240 |
+
|
| 2241 |
+
```json
|
| 2242 |
+
{
|
| 2243 |
+
"success": true,
|
| 2244 |
+
"message": "Article saved successfully",
|
| 2245 |
+
"data": {
|
| 2246 |
+
"saved_article_id": 123,
|
| 2247 |
+
"article_id": 13,
|
| 2248 |
+
"title": "AAPS National Biotechnology Conference",
|
| 2249 |
+
"saved_at": "2026-05-29T12:00:00Z"
|
| 2250 |
+
}
|
| 2251 |
+
}
|
| 2252 |
+
```
|
| 2253 |
+
|
| 2254 |
+
---
|
| 2255 |
+
|
| 2256 |
+
### Share Article
|
| 2257 |
+
|
| 2258 |
+
```http
|
| 2259 |
+
POST /news/{article_id}/share
|
| 2260 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 2261 |
+
Content-Type: application/json
|
| 2262 |
+
|
| 2263 |
+
{
|
| 2264 |
+
"share_with": "colleague@pharmaai.io",
|
| 2265 |
+
"message": "Check out this interesting article!"
|
| 2266 |
+
}
|
| 2267 |
+
```
|
| 2268 |
+
|
| 2269 |
+
**Response (200 OK):**
|
| 2270 |
+
|
| 2271 |
+
```json
|
| 2272 |
+
{
|
| 2273 |
+
"success": true,
|
| 2274 |
+
"message": "Article shared successfully",
|
| 2275 |
+
"data": {
|
| 2276 |
+
"share_id": 456,
|
| 2277 |
+
"article_id": 13,
|
| 2278 |
+
"shared_with": "colleague@pharmaai.io",
|
| 2279 |
+
"shared_at": "2026-05-29T12:00:00Z"
|
| 2280 |
+
}
|
| 2281 |
+
}
|
| 2282 |
+
```
|
| 2283 |
+
|
| 2284 |
+
---
|
| 2285 |
+
|
| 2286 |
+
### Get Saved Articles
|
| 2287 |
+
|
| 2288 |
+
```http
|
| 2289 |
+
GET /news/saved
|
| 2290 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 2291 |
+
```
|
| 2292 |
+
|
| 2293 |
+
**Response (200 OK):**
|
| 2294 |
+
|
| 2295 |
+
```json
|
| 2296 |
+
{
|
| 2297 |
+
"success": true,
|
| 2298 |
+
"message": "Saved articles retrieved successfully",
|
| 2299 |
+
"data": {
|
| 2300 |
+
"results": [
|
| 2301 |
+
{
|
| 2302 |
+
"id": 123,
|
| 2303 |
+
"article_id": 13,
|
| 2304 |
+
"title": "AAPS National Biotechnology Conference",
|
| 2305 |
+
"summary": "11 May 2026 - 14 May 2026...",
|
| 2306 |
+
"source": "European Pharmaceutical Review",
|
| 2307 |
+
"saved_at": "2026-05-29T12:00:00Z"
|
| 2308 |
+
}
|
| 2309 |
+
],
|
| 2310 |
+
"pagination": {
|
| 2311 |
+
"currentPage": 1,
|
| 2312 |
+
"totalPages": 1,
|
| 2313 |
+
"totalResults": 5,
|
| 2314 |
+
"perPage": 10,
|
| 2315 |
+
"hasNextPage": false,
|
| 2316 |
+
"hasPrevPage": false
|
| 2317 |
+
}
|
| 2318 |
+
}
|
| 2319 |
+
}
|
| 2320 |
+
```
|
| 2321 |
+
|
| 2322 |
+
---
|
| 2323 |
+
|
| 2324 |
+
### Unsave Article
|
| 2325 |
+
|
| 2326 |
+
```http
|
| 2327 |
+
DELETE /news/saved/{saved_article_id}
|
| 2328 |
+
Authorization: Bearer YOUR_ACCESS_TOKEN
|
| 2329 |
+
```
|
| 2330 |
+
|
| 2331 |
+
**Response (200 OK):**
|
| 2332 |
+
|
| 2333 |
+
```json
|
| 2334 |
+
{
|
| 2335 |
+
"success": true,
|
| 2336 |
+
"message": "Article unsaved successfully"
|
| 2337 |
+
}
|
| 2338 |
+
```
|
| 2339 |
+
|
| 2340 |
+
'''
|
| 2341 |
+
|
| 2342 |
+
## Admet Prediction API
|
| 2343 |
+
|
| 2344 |
+
### POST `/api/admet/predict`
|
| 2345 |
+
|
| 2346 |
+
Predict ADMET properties for chemical compounds from SMILES strings or file uploads.
|
| 2347 |
+
|
| 2348 |
+
**Authentication:** Bearer token required
|
| 2349 |
+
|
| 2350 |
+
**Input Options:**
|
| 2351 |
+
|
| 2352 |
+
| Option | Type | Description |
|
| 2353 |
+
| -------- | ------ | ------------------------------------------ |
|
| 2354 |
+
| `smiles` | string | Comma-separated SMILES strings (max 6) |
|
| 2355 |
+
| `file` | file | CSV or TXT file with SMILES (max 100 rows) |
|
| 2356 |
+
|
| 2357 |
+
**Example 1: JSON Input**
|
| 2358 |
+
|
| 2359 |
+
```bash
|
| 2360 |
+
curl -X POST /api/admet/predict \\
|
| 2361 |
+
-H "Authorization: Bearer {token}" \\
|
| 2362 |
+
-H "Content-Type: application/json" \\
|
| 2363 |
+
-d '{"smiles": "c1ccccc1, CCO, CCC"}'
|
| 2364 |
+
```
|
| 2365 |
+
|
| 2366 |
+
**Example 2: File Upload Input (CSV or TXT)**
|
| 2367 |
+
|
| 2368 |
+
```bash
|
| 2369 |
+
curl -X POST /api/admet/predict \\
|
| 2370 |
+
-H "Authorization: Bearer {token}" \\
|
| 2371 |
+
-F "file=@/path/to/file.csv"
|
| 2372 |
+
```
|
| 2373 |
+
|
| 2374 |
+
**Response (200 OK):**
|
| 2375 |
+
|
| 2376 |
+
```json
|
| 2377 |
+
{
|
| 2378 |
+
"success": true,
|
| 2379 |
+
"message": "ADMET predictions generated successfully",
|
| 2380 |
+
"data": [
|
| 2381 |
+
{
|
| 2382 |
+
"smiles": "c1ccccc1",
|
| 2383 |
+
"absorption": -2.9907076358795166,
|
| 2384 |
+
"distribution": 0.8750113248825073,
|
| 2385 |
+
"metabolism": -0.11453431844711304,
|
| 2386 |
+
"excretion": 7.360866546630859,
|
| 2387 |
+
"toxicity": 0.8492187261581421
|
| 2388 |
+
},
|
| 2389 |
+
{
|
| 2390 |
+
"smiles": "CCO",
|
| 2391 |
+
"absorption": -3.7663559913635254,
|
| 2392 |
+
"distribution": 0.8921501040458679,
|
| 2393 |
+
"metabolism": -0.2787337601184845,
|
| 2394 |
+
"excretion": 20.92142677307129,
|
| 2395 |
+
"toxicity": -0.6471661329269409
|
| 2396 |
+
},
|
| 2397 |
+
{
|
| 2398 |
+
"smiles": "CCC",
|
| 2399 |
+
"absorption": -3.09336519241333,
|
| 2400 |
+
"distribution": 1.2096238136291504,
|
| 2401 |
+
"metabolism": -0.17122718691825867,
|
| 2402 |
+
"excretion": 20.524127960205078,
|
| 2403 |
+
"toxicity": -0.25303196907043457
|
| 2404 |
+
}
|
| 2405 |
+
]
|
| 2406 |
+
}
|
| 2407 |
+
```
|
| 2408 |
+
|
| 2409 |
+
'''
|
| 2410 |
+
|
| 2411 |
+
## AI Generation API
|
| 2412 |
+
|
| 2413 |
+
### POST `/api/ai/generation/run`
|
| 2414 |
+
|
| 2415 |
+
**Authentication:** Bearer token required
|
| 2416 |
+
|
| 2417 |
+
**Input Options:**
|
| 2418 |
+
|
| 2419 |
+
| Option | Type | Description |
|
| 2420 |
+
| --------------- | ------- | --------------------------------------------------------------------------- |
|
| 2421 |
+
| `num_molecules` | integer | Number of molecules to generate |
|
| 2422 |
+
| `return_top_k` | integer | Number of top molecules to return |
|
| 2423 |
+
| `docking_mode` | string | Docking mode: "all" or "off" or "top_k" |
|
| 2424 |
+
| `dock_top_k` | integer | Number of top molecules to dock is only required when docking mode is top_k |
|
| 2425 |
+
|
| 2426 |
+
**Example:**
|
| 2427 |
+
|
| 2428 |
+
```bash
|
| 2429 |
+
curl -X POST /api/ai/generation/run \\
|
| 2430 |
+
-H "Authorization: Bearer {token}" \\
|
| 2431 |
+
-H "Content-Type: application/json" \\
|
| 2432 |
+
-d '{"num_molecules": 5, "return_top_k": 5, "docking_mode": "all", "dock_top_k": 5}'
|
| 2433 |
+
```
|
| 2434 |
+
|
| 2435 |
+
**Response (200 OK):**
|
| 2436 |
+
|
| 2437 |
+
```json
|
| 2438 |
+
{
|
| 2439 |
+
"success": true,
|
| 2440 |
+
"message": "Generation job started successfully",
|
| 2441 |
+
"job_id": "gen_20260602_174612_90c1b1",
|
| 2442 |
+
"status": "running",
|
| 2443 |
+
"preset": "egfr_generator",
|
| 2444 |
+
"num_molecules": 5,
|
| 2445 |
+
"return_top_k": 5,
|
| 2446 |
+
"docking_mode": "all",
|
| 2447 |
+
"dock_top_k": 5,
|
| 2448 |
+
"created_at": "2026-06-02 17:46:10"
|
| 2449 |
+
}
|
| 2450 |
+
```
|
| 2451 |
+
|
| 2452 |
+
---
|
| 2453 |
+
|
| 2454 |
+
**GET `/api/ai/generation/status/{job_id}`**
|
| 2455 |
+
|
| 2456 |
+
**Authentication:** Bearer token required
|
| 2457 |
+
|
| 2458 |
+
**Example:**
|
| 2459 |
+
|
| 2460 |
+
```bash
|
| 2461 |
+
curl -X GET /api/ai/generation/status/gen_20260602_174612_90c1b1 \\
|
| 2462 |
+
-H "Authorization: Bearer {token}"
|
| 2463 |
+
```
|
| 2464 |
+
|
| 2465 |
+
**Response (200 OK):**
|
| 2466 |
+
|
| 2467 |
+
```json
|
| 2468 |
+
{
|
| 2469 |
+
"success": true,
|
| 2470 |
+
"job_id": "gen_20260602_174612_90c1b1",
|
| 2471 |
+
"status": "completed",
|
| 2472 |
+
"preset": "egfr_generator",
|
| 2473 |
+
"num_molecules": 5,
|
| 2474 |
+
"return_top_k": 5,
|
| 2475 |
+
"docking_mode": "all",
|
| 2476 |
+
"dock_top_k": 5,
|
| 2477 |
+
"created_at": "2026-06-02 17:46:10"
|
| 2478 |
+
}
|
| 2479 |
+
```
|
| 2480 |
+
|
| 2481 |
+
---
|
| 2482 |
+
|
| 2483 |
+
**GET `/api/ai/generation/jobs/{job_id}/results`**
|
| 2484 |
+
|
| 2485 |
+
**Authentication:** Bearer token required
|
| 2486 |
+
|
| 2487 |
+
**Example:**
|
| 2488 |
+
|
| 2489 |
+
```bash
|
| 2490 |
+
curl -X GET /api/ai/generation/jobs/gen_20260602_174612_90c1b1/results \\
|
| 2491 |
+
-H "Authorization: Bearer {token}"
|
| 2492 |
+
```
|
| 2493 |
+
|
| 2494 |
+
**Response (200 OK):**
|
| 2495 |
+
|
| 2496 |
+
```json
|
| 2497 |
+
{
|
| 2498 |
+
"success": true,
|
| 2499 |
+
"job_id": "gen_20260602_174612_90c1b1",
|
| 2500 |
+
"status": "completed",
|
| 2501 |
+
"preset": "egfr_generator",
|
| 2502 |
+
"num_molecules": 5,
|
| 2503 |
+
"return_top_k": 5,
|
| 2504 |
+
"docking_mode": "all",
|
| 2505 |
+
"dock_top_k": 5,
|
| 2506 |
+
"summary": {
|
| 2507 |
+
"num_requested": 5,
|
| 2508 |
+
"num_generated": 5,
|
| 2509 |
+
"num_valid": 5,
|
| 2510 |
+
"num_returned": 5,
|
| 2511 |
+
"num_docked": 5
|
| 2512 |
+
},
|
| 2513 |
+
"files": {
|
| 2514 |
+
"csv": {
|
| 2515 |
+
"filename": "generated_results.csv"
|
| 2516 |
+
},
|
| 2517 |
+
"json": {
|
| 2518 |
+
"filename": "generated_results.json"
|
| 2519 |
+
}
|
| 2520 |
+
},
|
| 2521 |
+
"ligands": [
|
| 2522 |
+
{
|
| 2523 |
+
"SMILES": "CCN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5ncncc5c4)c3c2)C1",
|
| 2524 |
+
"SMILES_state": 1,
|
| 2525 |
+
"NLL": 8.24,
|
| 2526 |
+
"valid": true,
|
| 2527 |
+
"canonical_smiles": "CCN1CCN(CCC(=O)Nc2ccc3nncc(-c4ccc5ncncc5c4)c3c2)C1",
|
| 2528 |
+
"mw": 427.5120000000002,
|
| 2529 |
+
"logp": 3.1636000000000006,
|
| 2530 |
+
"tpsa": 87.14,
|
| 2531 |
+
"hbd": 1,
|
| 2532 |
+
"hba": 7,
|
| 2533 |
+
"rot_bonds": 6,
|
| 2534 |
+
"qed": 0.505688822717197,
|
| 2535 |
+
"sa_score": 2.715858141741272,
|
| 2536 |
+
"pred_pAff_mean": 10.734132766723633,
|
| 2537 |
+
"docking_score": -8.91,
|
| 2538 |
+
"docking_status": "completed",
|
| 2539 |
+
"rank": 1
|
| 2540 |
+
},
|
| 2541 |
+
{
|
| 2542 |
+
"SMILES": "Cn1cnc2ccc(Nc3ncnc4ccc(NC(=O)CCN5CCC5)cc34)cc21",
|
| 2543 |
+
"SMILES_state": 1,
|
| 2544 |
+
"NLL": 5.32,
|
| 2545 |
+
"valid": true,
|
| 2546 |
+
"canonical_smiles": "Cn1cnc2ccc(Nc3ncnc4ccc(NC(=O)CCN5CCC5)cc34)cc21",
|
| 2547 |
+
"mw": 401.4740000000003,
|
| 2548 |
+
"logp": 3.2944000000000013,
|
| 2549 |
+
"tpsa": 87.96999999999998,
|
| 2550 |
+
"hbd": 2,
|
| 2551 |
+
"hba": 6,
|
| 2552 |
+
"rot_bonds": 6,
|
| 2553 |
+
"qed": 0.5154635202823702,
|
| 2554 |
+
"sa_score": 2.404394987645352,
|
| 2555 |
+
"pred_pAff_mean": 9.775605201721191,
|
| 2556 |
+
"docking_score": -8.27,
|
| 2557 |
+
"docking_status": "completed",
|
| 2558 |
+
"rank": 2
|
| 2559 |
+
},
|
| 2560 |
+
{
|
| 2561 |
+
"SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnccc5c4)c3c2)C1",
|
| 2562 |
+
"SMILES_state": 1,
|
| 2563 |
+
"NLL": 5.95,
|
| 2564 |
+
"valid": true,
|
| 2565 |
+
"canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnccc5c4)c3c2)C1",
|
| 2566 |
+
"mw": 426.5240000000002,
|
| 2567 |
+
"logp": 3.768600000000002,
|
| 2568 |
+
"tpsa": 74.25,
|
| 2569 |
+
"hbd": 1,
|
| 2570 |
+
"hba": 6,
|
| 2571 |
+
"rot_bonds": 5,
|
| 2572 |
+
"qed": 0.523710400747619,
|
| 2573 |
+
"sa_score": 2.669701840595609,
|
| 2574 |
+
"pred_pAff_mean": 9.587095260620115,
|
| 2575 |
+
"docking_score": -10.25,
|
| 2576 |
+
"docking_status": "completed",
|
| 2577 |
+
"rank": 3
|
| 2578 |
+
},
|
| 2579 |
+
{
|
| 2580 |
+
"SMILES": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnnnc5c4)c3c2)C1",
|
| 2581 |
+
"SMILES_state": 1,
|
| 2582 |
+
"NLL": 8.32,
|
| 2583 |
+
"valid": true,
|
| 2584 |
+
"canonical_smiles": "CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cnnnc5c4)c3c2)C1",
|
| 2585 |
+
"mw": 428.5000000000002,
|
| 2586 |
+
"logp": 2.5586,
|
| 2587 |
+
"tpsa": 100.03,
|
| 2588 |
+
"hbd": 1,
|
| 2589 |
+
"hba": 8,
|
| 2590 |
+
"rot_bonds": 5,
|
| 2591 |
+
"qed": 0.5177099598248629,
|
| 2592 |
+
"sa_score": 2.917413951888572,
|
| 2593 |
+
"pred_pAff_mean": 8.804740905761719,
|
| 2594 |
+
"docking_score": -10.15,
|
| 2595 |
+
"docking_status": "completed",
|
| 2596 |
+
"rank": 4
|
| 2597 |
+
},
|
| 2598 |
+
{
|
| 2599 |
+
"SMILES": "O=C(CN1CCNCC1)Nc1ccc2nncc(-c3ccc4cnncc4c3)c2c1",
|
| 2600 |
+
"SMILES_state": 1,
|
| 2601 |
+
"NLL": 8.41,
|
| 2602 |
+
"valid": true,
|
| 2603 |
+
"canonical_smiles": "O=C(CN1CCNCC1)Nc1ccc2nncc(-c3ccc4cnncc4c3)c2c1",
|
| 2604 |
+
"mw": 399.45800000000014,
|
| 2605 |
+
"logp": 2.0836999999999994,
|
| 2606 |
+
"tpsa": 95.93,
|
| 2607 |
+
"hbd": 2,
|
| 2608 |
+
"hba": 7,
|
| 2609 |
+
"rot_bonds": 4,
|
| 2610 |
+
"qed": 0.5423758682727866,
|
| 2611 |
+
"sa_score": 2.665545251407164,
|
| 2612 |
+
"pred_pAff_mean": 7.531160354614258,
|
| 2613 |
+
"docking_score": -9.5,
|
| 2614 |
+
"docking_status": "completed",
|
| 2615 |
+
"rank": 5
|
| 2616 |
+
}
|
| 2617 |
+
],
|
| 2618 |
+
"created_at": "2026-06-02 17:46:10"
|
| 2619 |
+
}
|
| 2620 |
+
```
|
| 2621 |
+
|
| 2622 |
+
---
|
| 2623 |
+
|
| 2624 |
+
## ligands export
|
| 2625 |
+
|
| 2626 |
+
### POST `/api/ai/ligands/export`
|
| 2627 |
+
|
| 2628 |
+
**Authentication:** Bearer token required
|
| 2629 |
+
|
| 2630 |
+
**Input Options:**
|
| 2631 |
+
|
| 2632 |
+
| Name | Type | Description |
|
| 2633 |
+
| -------- | ------ | -------------------------------------- |
|
| 2634 |
+
| `smiles` | string | SMILES strings |
|
| 2635 |
+
| `format` | string | Supported formats: pdbqt or pdb or sdf |
|
| 2636 |
+
|
| 2637 |
+
**Example:**
|
| 2638 |
+
|
| 2639 |
+
```bash
|
| 2640 |
+
curl -X POST http://localhost:8080/api/ai/ligands/export \
|
| 2641 |
+
-H "Authorization: Bearer {token}" \
|
| 2642 |
+
-H "Content-Type: application/json" \
|
| 2643 |
+
-d '{"smiles": "CCO", "format": "pdbqt"}'
|
| 2644 |
+
```
|
| 2645 |
+
|
| 2646 |
+
**Response:**
|
| 2647 |
+
|
| 2648 |
+
```json
|
| 2649 |
+
{
|
| 2650 |
+
"success": true,
|
| 2651 |
+
"data": {
|
| 2652 |
+
"job_id": "lig_20260602_184722_a19639",
|
| 2653 |
+
"status": "completed",
|
| 2654 |
+
"smiles": "CCO",
|
| 2655 |
+
"format": "pdbqt",
|
| 2656 |
+
"filename": "ligand_3d.pdbqt",
|
| 2657 |
+
"created_at": "2026-06-02 18:47:22"
|
| 2658 |
+
}
|
| 2659 |
+
}
|
| 2660 |
+
```
|
| 2661 |
+
|
| 2662 |
+
---
|
| 2663 |
+
|
| 2664 |
+
## ligands download files or Generation files
|
| 2665 |
+
|
| 2666 |
+
### GET `/api/ai/files/{job_id}/{filename}`
|
| 2667 |
+
|
| 2668 |
+
**Authentication:** Bearer token required
|
| 2669 |
+
|
| 2670 |
+
**Example ligands download:**
|
| 2671 |
+
|
| 2672 |
+
```bash
|
| 2673 |
+
curl -X GET http://localhost:8080/api/ai/files/lig_20260602_181751_0227c4/ligand_3d.pdbqt \
|
| 2674 |
+
-H "Authorization: Bearer {token}"
|
| 2675 |
+
```
|
| 2676 |
+
|
| 2677 |
+
**Response:**
|
| 2678 |
+
|
| 2679 |
+
```pdbqt
|
| 2680 |
+
REMARK SMILES CCC
|
| 2681 |
+
REMARK SMILES IDX 1 1 2 2 3 3
|
| 2682 |
+
REMARK H PARENT
|
| 2683 |
+
ROOT
|
| 2684 |
+
ATOM 1 C UNL 1 1.223 -0.144 -0.355 1.00 0.00 0.003 C
|
| 2685 |
+
ATOM 2 C UNL 1 0.031 -0.177 0.596 1.00 0.00 -0.007 C
|
| 2686 |
+
ATOM 3 C UNL 1 -1.247 0.279 -0.101 1.00 0.00 0.003 C
|
| 2687 |
+
ENDROOT
|
| 2688 |
+
TORSDOF 0
|
| 2689 |
+
|
| 2690 |
+
```
|
| 2691 |
+
|
| 2692 |
+
**Example Generation files download:**
|
| 2693 |
+
|
| 2694 |
+
```bash
|
| 2695 |
+
curl -X GET http://127.0.0.1:8000/api/ai/files/gen_20260602_185419_15fc7b/generated_results.csv \
|
| 2696 |
+
-H "Authorization: Bearer {token}"
|
| 2697 |
+
```
|
| 2698 |
+
|
| 2699 |
+
**Response:**
|
| 2700 |
+
|
| 2701 |
+
```csv
|
| 2702 |
+
SMILES,SMILES_state,NLL,valid,canonical_smiles,mw,logp,tpsa,hbd,hba,rot_bonds,qed,sa_score,pred_pAff_mean,docking_score,docking_status
|
| 2703 |
+
CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1,1,4.14,True,CN1CCCN(CCC(=O)Nc2ccc3nncc(-c4ccc5cncnc5c4)c3c2)C1,427.5120000000002,3.1636000000000006,87.14,1,7,5,0.5234216428527144,2.7645745083533857,10.726750373840332,-9.16,completed
|
| 2704 |
+
O=C(CCN1CCCC1)Nc1ccc2c(Nc3cccc(Cl)c3)ncnc2c1,1,6.06,True,O=C(CCN1CCCC1)Nc1ccc2c(Nc3cccc(Cl)c3)ncnc2c1,395.89400000000006,4.451200000000003,70.15,2,5,6,0.6447750051881624,2.11252799494409,8.844084739685059,-8.23,completed
|
| 2705 |
+
CN1CCC2C1CCN2CCC(=O)Nc1ccc2ncnc(Nc3ccc4ncncc4c3)c2c1,1,22.74,True,CN1CCC2C1CCN2CCC(=O)Nc1ccc2ncnc(Nc3ccc4ncncc4c3)c2c1,468.5650000000001,3.4236000000000013,99.17,2,8,6,0.4441562846144389,3.462932738106919,8.693540573120117,-9.2,completed
|
| 2706 |
+
```
|
| 2707 |
+
|
| 2708 |
+
---
|
| 2709 |
+
|
| 2710 |
+
'''
|
| 2711 |
+
|
| 2712 |
+
## Error Handling
|
| 2713 |
+
|
| 2714 |
+
### Error Response Format
|
| 2715 |
+
|
| 2716 |
+
All errors follow this standard format:
|
| 2717 |
+
|
| 2718 |
+
```json
|
| 2719 |
+
{
|
| 2720 |
+
"success": false,
|
| 2721 |
+
"message": "Human-readable error message",
|
| 2722 |
+
"error_code": "SPECIFIC_ERROR_CODE",
|
| 2723 |
+
"details": {
|
| 2724 |
+
"field_errors": {...}
|
| 2725 |
+
},
|
| 2726 |
+
"timestamp": "2026-05-29T12:30:45Z"
|
| 2727 |
+
}
|
| 2728 |
+
```
|
| 2729 |
+
|
| 2730 |
+
### HTTP Status Codes
|
| 2731 |
+
|
| 2732 |
+
| Code | Meaning | Example |
|
| 2733 |
+
| ------- | ------------------- | ------------------------ |
|
| 2734 |
+
| **200** | OK | Request successful |
|
| 2735 |
+
| **201** | Created | Resource created |
|
| 2736 |
+
| **202** | Accepted | Async job queued |
|
| 2737 |
+
| **400** | Bad Request | Invalid parameters |
|
| 2738 |
+
| **401** | Unauthorized | Missing/invalid token |
|
| 2739 |
+
| **403** | Forbidden | Insufficient permissions |
|
| 2740 |
+
| **404** | Not Found | Resource not found |
|
| 2741 |
+
| **422** | Unprocessable | Validation failed |
|
| 2742 |
+
| **429** | Too Many Requests | Rate limit exceeded |
|
| 2743 |
+
| **500** | Server Error | Internal error |
|
| 2744 |
+
| **502** | Bad Gateway | Upstream service down |
|
| 2745 |
+
| **503** | Service Unavailable | System maintenance |
|
| 2746 |
+
|
| 2747 |
+
### Common Error Codes
|
| 2748 |
+
|
| 2749 |
+
| Code | Description |
|
| 2750 |
+
| ----------------------- | --------------------------------------- |
|
| 2751 |
+
| `INVALID_SMILES` | Given SMILES string is invalid |
|
| 2752 |
+
| `JOB_NOT_FOUND` | Job ID doesn't exist |
|
| 2753 |
+
| `SERVICE_UNAVAILABLE` | AI service not responding |
|
| 2754 |
+
| `AUTHENTICATION_FAILED` | Invalid credentials |
|
| 2755 |
+
| `INSUFFICIENT_QUOTA` | User quota exceeded |
|
| 2756 |
+
| `UNSUPPORTED_OPERATION` | Feature not available |
|
| 2757 |
+
| `QUOTA_EXCEEDED` | LLM API daily quota reached |
|
| 2758 |
+
| `THREAD_NOT_FOUND` | Thread ID doesn't exist or unauthorized |
|
| 2759 |
+
| `JOB_NOT_COMPLETE` | Results requested before job completion |
|
| 2760 |
+
| `CSV_TOO_LARGE` | Exceeds 100 row limit |
|
| 2761 |
+
| `MISSING_SMILES` | smiles field not provided |
|
| 2762 |
+
|
| 2763 |
+
### Example Error Response
|
| 2764 |
+
|
| 2765 |
+
```http
|
| 2766 |
+
POST /ai/run
|
| 2767 |
+
Authorization: Bearer invalid_token
|
| 2768 |
+
|
| 2769 |
+
HTTP 401 Unauthorized
|
| 2770 |
+
Content-Type: application/json
|
| 2771 |
+
|
| 2772 |
+
{
|
| 2773 |
+
"success": false,
|
| 2774 |
+
"message": "Unauthenticated",
|
| 2775 |
+
"error_code": "AUTHENTICATION_FAILED",
|
| 2776 |
+
"timestamp": "2026-05-29T12:30:45Z"
|
| 2777 |
+
}
|
| 2778 |
+
```
|
| 2779 |
+
|
| 2780 |
+
---
|
| 2781 |
+
|
| 2782 |
+
## Rate Limiting
|
| 2783 |
+
|
| 2784 |
+
### Rate Limit Headers
|
| 2785 |
+
|
| 2786 |
+
All responses include rate limit information:
|
| 2787 |
+
|
| 2788 |
+
```http
|
| 2789 |
+
X-RateLimit-Limit: 1000
|
| 2790 |
+
X-RateLimit-Remaining: 987
|
| 2791 |
+
X-RateLimit-Reset: 1716954645
|
| 2792 |
+
```
|
| 2793 |
+
|
| 2794 |
+
### Limits by Endpoint Category
|
| 2795 |
+
|
| 2796 |
+
| Category | Limit | Window |
|
| 2797 |
+
| ----------- | ------------- | ---------- |
|
| 2798 |
+
| **Auth** | 10 requests | 15 minutes |
|
| 2799 |
+
| **AI Jobs** | 100 requests | 1 hour |
|
| 2800 |
+
| **Search** | 500 requests | 1 hour |
|
| 2801 |
+
| **General** | 1000 requests | 1 hour |
|
| 2802 |
+
|
| 2803 |
+
### AI Agent Rate Limits
|
| 2804 |
+
|
| 2805 |
+
| Endpoint | Limit | Window |
|
| 2806 |
+
| ------------------ | ------------- | ------------------------ |
|
| 2807 |
+
| `/chat` | 20 requests | Per day (upstream quota) |
|
| 2808 |
+
| `/analyze/smiles` | 20 requests | Per day (upstream quota) |
|
| 2809 |
+
| `/analyze/compare` | 20 requests | Per day (upstream quota) |
|
| 2810 |
+
| `/analyze/docking` | 20 requests | Per day (upstream quota) |
|
| 2811 |
+
| `/csv/upload` | 10 uploads | Per day |
|
| 2812 |
+
| All others | 1000 requests | 1 hour |
|
| 2813 |
+
|
| 2814 |
+
**Note:** AI-powered endpoints are limited by upstream Google Gemini free tier (20 requests/day). Use `analysis_type: quick` to reduce consumption.
|
| 2815 |
+
|
| 2816 |
+
### Handling Rate Limits
|
| 2817 |
+
|
| 2818 |
+
When rate limited (HTTP 429):
|
| 2819 |
+
|
| 2820 |
+
```json
|
| 2821 |
+
{
|
| 2822 |
+
"success": false,
|
| 2823 |
+
"message": "Rate limit exceeded",
|
| 2824 |
+
"retry_after": 120,
|
| 2825 |
+
"error_code": "RATE_LIMIT_EXCEEDED"
|
| 2826 |
+
}
|
| 2827 |
+
```
|
| 2828 |
+
|
| 2829 |
+
**Retry-After Header:** Seconds to wait before retry
|
| 2830 |
+
|
| 2831 |
+
---
|
| 2832 |
+
|
| 2833 |
+
## Integration Examples
|
| 2834 |
+
|
| 2835 |
+
### Example 1: Complete ADMET Prediction Workflow
|
| 2836 |
+
|
| 2837 |
+
```bash
|
| 2838 |
+
#!/bin/bash
|
| 2839 |
+
|
| 2840 |
+
BASE_URL="http://localhost:8080/api"
|
| 2841 |
+
TOKEN="your_access_token_here"
|
| 2842 |
+
SMILES="CC(=O)Oc1ccccc1C(=O)O"
|
| 2843 |
+
|
| 2844 |
+
# Step 1: Submit job
|
| 2845 |
+
echo "1. Submitting ADMET job..."
|
| 2846 |
+
JOB_RESPONSE=$(curl -s -X POST "$BASE_URL/ai/run" \\
|
| 2847 |
+
-H "Authorization: Bearer $TOKEN" \\
|
| 2848 |
+
-H "Content-Type: application/json" \\
|
| 2849 |
+
-d "{\\"job_type\\": \\"admet_prediction\\", \\"parameters\\": {\\"smiles\\": \\"$SMILES\\"}}")
|
| 2850 |
+
|
| 2851 |
+
JOB_ID=$(echo $JOB_RESPONSE | jq -r '.job_id')
|
| 2852 |
+
echo "Job ID: $JOB_ID"
|
| 2853 |
+
|
| 2854 |
+
# Step 2: Poll for completion
|
| 2855 |
+
echo "2. Waiting for results..."
|
| 2856 |
+
while true; do
|
| 2857 |
+
STATUS_RESPONSE=$(curl -s -X GET "$BASE_URL/ai/status/$JOB_ID" \\
|
| 2858 |
+
-H "Authorization: Bearer $TOKEN")
|
| 2859 |
+
|
| 2860 |
+
STATUS=$(echo $STATUS_RESPONSE | jq -r '.status')
|
| 2861 |
+
|
| 2862 |
+
if [ "$STATUS" == "completed" ]; then
|
| 2863 |
+
echo "Job completed!"
|
| 2864 |
+
echo $STATUS_RESPONSE | jq '.results'
|
| 2865 |
+
break
|
| 2866 |
+
elif [ "$STATUS" == "failed" ]; then
|
| 2867 |
+
echo "Job failed!"
|
| 2868 |
+
echo $STATUS_RESPONSE | jq '.error'
|
| 2869 |
+
exit 1
|
| 2870 |
+
else
|
| 2871 |
+
echo "Status: $STATUS, waiting..."
|
| 2872 |
+
sleep 5
|
| 2873 |
+
fi
|
| 2874 |
+
done
|
| 2875 |
+
|
| 2876 |
+
# Step 3: Download results
|
| 2877 |
+
echo "3. Downloading full results..."
|
| 2878 |
+
curl -X GET "$BASE_URL/ai/download/full/$JOB_ID" \\
|
| 2879 |
+
-H "Authorization: Bearer $TOKEN" \\
|
| 2880 |
+
-o results.zip
|
| 2881 |
+
|
| 2882 |
+
echo "✓ Results saved to results.zip"
|
| 2883 |
+
```
|
| 2884 |
+
|
| 2885 |
+
### Example 2: Chemical Similarity Search
|
| 2886 |
+
|
| 2887 |
+
```python
|
| 2888 |
+
import requests
|
| 2889 |
+
import json
|
| 2890 |
+
import time
|
| 2891 |
+
|
| 2892 |
+
class AILIXIRClient:
|
| 2893 |
+
def __init__(self, base_url, token):
|
| 2894 |
+
self.base_url = base_url
|
| 2895 |
+
self.token = token
|
| 2896 |
+
self.headers = {
|
| 2897 |
+
"Authorization": f"Bearer {token}",
|
| 2898 |
+
"Content-Type": "application/json"
|
| 2899 |
+
}
|
| 2900 |
+
|
| 2901 |
+
def search_similar_compounds(self, smiles, top_k=10, use_rag=True):
|
| 2902 |
+
"""Search for compounds similar to query SMILES"""
|
| 2903 |
+
|
| 2904 |
+
endpoint = "full-rag" if use_rag else "retrieval-only"
|
| 2905 |
+
url = f"{self.base_url}/chemical-search/{endpoint}"
|
| 2906 |
+
|
| 2907 |
+
response = requests.post(url, headers=self.headers, json={
|
| 2908 |
+
"smiles": smiles,
|
| 2909 |
+
"top_k": top_k
|
| 2910 |
+
})
|
| 2911 |
+
|
| 2912 |
+
if response.status_code != 202:
|
| 2913 |
+
raise Exception(f"Failed to submit search: {response.text}")
|
| 2914 |
+
|
| 2915 |
+
job_id = response.json()["job_id"]
|
| 2916 |
+
print(f"Search submitted: {job_id}")
|
| 2917 |
+
|
| 2918 |
+
# Poll for results
|
| 2919 |
+
while True:
|
| 2920 |
+
status_url = f"{self.base_url}/chemical-search/{job_id}/status"
|
| 2921 |
+
status_response = requests.get(status_url, headers=self.headers)
|
| 2922 |
+
status_data = status_response.json()
|
| 2923 |
+
|
| 2924 |
+
if status_data["status"] == "completed":
|
| 2925 |
+
return status_data["compounds"]
|
| 2926 |
+
elif status_data["status"] == "failed":
|
| 2927 |
+
raise Exception(f"Search failed: {status_data.get('error')}")
|
| 2928 |
+
|
| 2929 |
+
print(f"Status: {status_data['status']}, waiting...")
|
| 2930 |
+
time.sleep(2)
|
| 2931 |
+
|
| 2932 |
+
# Usage
|
| 2933 |
+
client = AILIXIRClient("http://localhost:8080/api", "your_token_here")
|
| 2934 |
+
results = client.search_similar_compounds("CC(=O)Oc1ccccc1C(=O)O", top_k=5)
|
| 2935 |
+
|
| 2936 |
+
for compound in results:
|
| 2937 |
+
print(f"Rank {compound['rank']}: {compound['name']} (similarity: {compound['similarity']:.2f})")
|
| 2938 |
+
if 'explanation' in compound:
|
| 2939 |
+
print(f" Explanation: {compound['explanation'][:100]}...")
|
| 2940 |
+
```
|
| 2941 |
+
|
| 2942 |
+
### Example 3: Health Monitoring
|
| 2943 |
+
|
| 2944 |
+
```javascript
|
| 2945 |
+
// Node.js / JavaScript example
|
| 2946 |
+
const axios = require("axios");
|
| 2947 |
+
|
| 2948 |
+
async function checkSystemHealth() {
|
| 2949 |
+
const baseURL = "http://localhost:8080/api";
|
| 2950 |
+
|
| 2951 |
+
try {
|
| 2952 |
+
// Check overall system
|
| 2953 |
+
const healthResponse = await axios.get(`${baseURL}/health`);
|
| 2954 |
+
console.log("System Health:", healthResponse.data);
|
| 2955 |
+
|
| 2956 |
+
// Check all AI services
|
| 2957 |
+
const servicesResponse = await axios.get(
|
| 2958 |
+
`${baseURL}/ai-services/health`,
|
| 2959 |
+
);
|
| 2960 |
+
|
| 2961 |
+
const allHealthy = servicesResponse.data.success;
|
| 2962 |
+
const services = servicesResponse.data.services;
|
| 2963 |
+
|
| 2964 |
+
console.log("\\nAI Services Status:");
|
| 2965 |
+
Object.entries(services).forEach(([name, status]) => {
|
| 2966 |
+
const indicator = status.status === "healthy" ? "✓" : "✗";
|
| 2967 |
+
console.log(`${indicator} ${name}: ${status.status}`);
|
| 2968 |
+
});
|
| 2969 |
+
|
| 2970 |
+
return {
|
| 2971 |
+
healthy: allHealthy,
|
| 2972 |
+
timestamp: new Date(),
|
| 2973 |
+
};
|
| 2974 |
+
} catch (error) {
|
| 2975 |
+
console.error("Health check failed:", error.message);
|
| 2976 |
+
}
|
| 2977 |
+
}
|
| 2978 |
+
|
| 2979 |
+
// Run every 60 seconds
|
| 2980 |
+
setInterval(checkSystemHealth, 60000);
|
| 2981 |
+
```
|
| 2982 |
+
|
| 2983 |
+
### Example 4: AI Agent Drug Discovery Workflow
|
| 2984 |
+
|
| 2985 |
+
```bash
|
| 2986 |
+
#!/bin/bash
|
| 2987 |
+
|
| 2988 |
+
BASE_URL="http://localhost:8080/api"
|
| 2989 |
+
TOKEN="your_access_token_here"
|
| 2990 |
+
|
| 2991 |
+
# Step 1: Create thread
|
| 2992 |
+
echo "1. Creating thread..."
|
| 2993 |
+
THREAD_RESPONSE=$(curl -s -X POST "$BASE_URL/chemistry/thread" \\
|
| 2994 |
+
-H "Authorization: Bearer $TOKEN")
|
| 2995 |
+
|
| 2996 |
+
THREAD_ID=$(echo $THREAD_RESPONSE | jq -r '.data.thread_id')
|
| 2997 |
+
echo "Thread ID: $THREAD_ID"
|
| 2998 |
+
|
| 2999 |
+
# Step 2: Analyze SMILES
|
| 3000 |
+
echo "2. Analyzing Aspirin..."
|
| 3001 |
+
curl -s -X POST "$BASE_URL/chemistry/analyze/smiles" \\
|
| 3002 |
+
-H "Authorization: Bearer $TOKEN" \\
|
| 3003 |
+
-H "Content-Type: application/json" \\
|
| 3004 |
+
-d "{\\"smiles\\": \\"CC(=O)Oc1ccccc1C(=O)O\\", \\"thread_id\\": \\"$THREAD_ID\\"}" | jq '.data.reply'
|
| 3005 |
+
|
| 3006 |
+
# Step 3: Compare with Ibuprofen
|
| 3007 |
+
echo "3. Comparing molecules..."
|
| 3008 |
+
curl -s -X POST "$BASE_URL/chemistry/analyze/compare" \\
|
| 3009 |
+
-H "Authorization: Bearer $TOKEN" \\
|
| 3010 |
+
-H "Content-Type: application/json" \\
|
| 3011 |
+
-d "{\\"smiles\\": [\\"CC(=O)Oc1ccccc1C(=O)O\\", \\"CC(C)Cc1ccc(cc1)C(C)C(=O)O\\"], \\"thread_id\\": \\"$THREAD_ID\\"}" | jq '.data.reply'
|
| 3012 |
+
|
| 3013 |
+
# Step 4: Chat follow-up
|
| 3014 |
+
echo "4. Follow-up question..."
|
| 3015 |
+
curl -s -X POST "$BASE_URL/chemistry/chat" \\
|
| 3016 |
+
-H "Authorization: Bearer $TOKEN" \\
|
| 3017 |
+
-H "Content-Type: application/json" \\
|
| 3018 |
+
-d "{\\"message\\": \\"Which molecule has better CNS penetration?\\", \\"thread_id\\": \\"$THREAD_ID\\"}" | jq '.data.reply'
|
| 3019 |
+
|
| 3020 |
+
echo "✓ Workflow complete!"
|
| 3021 |
+
```
|
| 3022 |
+
|
| 3023 |
+
### Example 5: Flutter Integration
|
| 3024 |
+
|
| 3025 |
+
```dart
|
| 3026 |
+
class ChemistryApiService {
|
| 3027 |
+
final String baseUrl = 'http://your-domain.com/api/chemistry';
|
| 3028 |
+
String? token;
|
| 3029 |
+
|
| 3030 |
+
Future<Map<String, dynamic>> analyzeSmiles(String smiles, {String? threadId}) async {
|
| 3031 |
+
final response = await http.post(
|
| 3032 |
+
Uri.parse('$baseUrl/analyze/smiles'),
|
| 3033 |
+
headers: {
|
| 3034 |
+
'Authorization': 'Bearer $token',
|
| 3035 |
+
'Content-Type': 'application/json',
|
| 3036 |
+
},
|
| 3037 |
+
body: jsonEncode({
|
| 3038 |
+
'smiles': smiles,
|
| 3039 |
+
'thread_id': threadId,
|
| 3040 |
+
}),
|
| 3041 |
+
);
|
| 3042 |
+
return jsonDecode(response.body);
|
| 3043 |
+
}
|
| 3044 |
+
|
| 3045 |
+
Future<Map<String, dynamic>> chat(String message, {String? threadId}) async {
|
| 3046 |
+
final response = await http.post(
|
| 3047 |
+
Uri.parse('$baseUrl/chat'),
|
| 3048 |
+
headers: {
|
| 3049 |
+
'Authorization': 'Bearer $token',
|
| 3050 |
+
'Content-Type': 'application/json',
|
| 3051 |
+
},
|
| 3052 |
+
body: jsonEncode({
|
| 3053 |
+
'message': message,
|
| 3054 |
+
'thread_id': threadId,
|
| 3055 |
+
}),
|
| 3056 |
+
);
|
| 3057 |
+
return jsonDecode(response.body);
|
| 3058 |
+
}
|
| 3059 |
+
|
| 3060 |
+
Future<Map<String, dynamic>> createThread() async {
|
| 3061 |
+
final response = await http.post(
|
| 3062 |
+
Uri.parse('$baseUrl/thread'),
|
| 3063 |
+
headers: {
|
| 3064 |
+
'Authorization': 'Bearer $token',
|
| 3065 |
+
'Content-Type': 'application/json',
|
| 3066 |
+
},
|
| 3067 |
+
);
|
| 3068 |
+
return jsonDecode(response.body);
|
| 3069 |
+
}
|
| 3070 |
+
}
|
| 3071 |
+
```
|
| 3072 |
+
|
| 3073 |
+
---
|
| 3074 |
+
|
| 3075 |
+
## Support
|
| 3076 |
+
|
| 3077 |
+
For API issues or questions:
|
| 3078 |
+
|
| 3079 |
+
1. **Check Logs:** `docker compose logs -f laravel`
|
| 3080 |
+
2. **Test Endpoint:** Use provided curl examples
|
| 3081 |
+
3. **Version:** Confirm you're using latest API version
|
| 3082 |
+
4. **Rate Limits:** Check `X-RateLimit-*` headers
|
| 3083 |
+
5. **Contact:** pharma-support@ailixir.io
|
| 3084 |
+
|
| 3085 |
+
---
|
| 3086 |
+
|
| 3087 |
+
**Created:** May 29, 2026 | **Version:** 2.1.0 | **Last Updated:** May 29, 2026
|
| 3088 |
+
'''
|
| 3089 |
+
|
| 3090 |
+
with open('/mnt/agents/output/AILIXIR_API_Reference_v2.1.md', 'w', encoding='utf-8') as f:
|
| 3091 |
+
f.write(content)
|
| 3092 |
+
|
| 3093 |
+
print("File saved successfully!")
|
| 3094 |
+
print(f"Total lines: {len(content.splitlines())}")
|
| 3095 |
+
print(f"Total characters: {len(content)}")
|
ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AILIXIR Architecture & Component Documentation
|
| 2 |
+
|
| 3 |
+
Last updated: 2026-05-29
|
| 4 |
+
|
| 5 |
+
This document provides detailed diagrams and per-component explanations for the AILIXIR BackEnd. It is intended for developers and SREs onboarding onto the project and for inclusion in production runbooks.
|
| 6 |
+
|
| 7 |
+
Contents
|
| 8 |
+
--------
|
| 9 |
+
- System-level architecture (diagram + explanation)
|
| 10 |
+
- Data flow and request lifecycle
|
| 11 |
+
- Per-service component diagrams and responsibilities
|
| 12 |
+
- Laravel (orchestration)
|
| 13 |
+
- ADMET inference (FastAPI)
|
| 14 |
+
- Drug Repurposing (FastAPI)
|
| 15 |
+
- Chemical RAG (FAISS + RAG)
|
| 16 |
+
- Environment variables & configuration locations
|
| 17 |
+
- Deployment notes and scaling considerations
|
| 18 |
+
- Troubleshooting checkpoints
|
| 19 |
+
|
| 20 |
+
System-level architecture (Mermaid)
|
| 21 |
+
----------------------------------
|
| 22 |
+
|
| 23 |
+
```mermaid
|
| 24 |
+
flowchart LR
|
| 25 |
+
subgraph CLIENTS [Clients]
|
| 26 |
+
A[Web UI / Frontend]
|
| 27 |
+
B[External Integrations / APIs]
|
| 28 |
+
end
|
| 29 |
+
|
| 30 |
+
subgraph DOCKER_NET [Docker network: ailixir]
|
| 31 |
+
direction TB
|
| 32 |
+
LAR(Laravel API)
|
| 33 |
+
Q(Queue Worker)
|
| 34 |
+
MYSQL[(MariaDB)]
|
| 35 |
+
ADMET(ADMET Inference)
|
| 36 |
+
DRUG(Drug Repurposing)
|
| 37 |
+
CRAG(Chemical RAG)
|
| 38 |
+
end
|
| 39 |
+
|
| 40 |
+
A -->|HTTP| LAR
|
| 41 |
+
B -->|HTTP| LAR
|
| 42 |
+
LAR -->|DB| MYSQL
|
| 43 |
+
LAR -->|enqueue| Q
|
| 44 |
+
LAR -->|AI calls| ADMET
|
| 45 |
+
LAR -->|AI calls| DRUG
|
| 46 |
+
LAR -->|AI calls| CRAG
|
| 47 |
+
ADMET -->|models stored on disk| ADMET
|
| 48 |
+
CRAG -->|FAISS index on volume| CRAG
|
| 49 |
+
DRUG -->|model artifacts| DRUG
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
Explanation
|
| 53 |
+
-----------
|
| 54 |
+
|
| 55 |
+
- Clients (web, CLI, external services) interact with the `laravel` orchestration API. The Laravel layer handles authentication, request validation, and orchestration of multi-step workflows.
|
| 56 |
+
- Long-running tasks are delegated to the `queue` worker via Laravel's queue system. The queue worker communicates with the same Laravel codebase and uses the internal database for job bookkeeping.
|
| 57 |
+
- AI microservices are independent FastAPI services: `admet`, `drug-repurposing`, and `chemical-rag`. Laravel calls these services over the internal Docker network by service name.
|
| 58 |
+
- Persistent storage: `mysql` holds application state, users, and job metadata; AI services use container volumes for model artifacts and FAISS indexes.
|
| 59 |
+
|
| 60 |
+
Request lifecycle — typical screening flow
|
| 61 |
+
---------------------------------------
|
| 62 |
+
|
| 63 |
+
1. Client POSTs request to Laravel endpoint (e.g., run screening).
|
| 64 |
+
2. Laravel validates and creates a job record in MySQL and either performs synchronous work or enqueues a background job.
|
| 65 |
+
3. Background job triggers calls to AI services in parallel or sequence depending on pipeline stage:
|
| 66 |
+
- `drug-repurposing` to run virtual screening
|
| 67 |
+
- `admet` to score ADMET properties
|
| 68 |
+
- `chemical-rag` to fetch similar compounds or augment results
|
| 69 |
+
4. AI services return raw predictions to Laravel which aggregates results, persists summary, and notifies the client.
|
| 70 |
+
|
| 71 |
+
Per-service diagrams & responsibilities
|
| 72 |
+
--------------------------------------
|
| 73 |
+
|
| 74 |
+
1) Laravel (orchestration)
|
| 75 |
+
|
| 76 |
+
```mermaid
|
| 77 |
+
flowchart TD
|
| 78 |
+
In[HTTP Request]
|
| 79 |
+
Auth[Auth Layer]
|
| 80 |
+
Validate[Validation]
|
| 81 |
+
Controller[Controller]
|
| 82 |
+
DB[(MySQL)]
|
| 83 |
+
Queue[Queue Worker]
|
| 84 |
+
AIcalls[AI Service Calls]
|
| 85 |
+
Response[Response to Client]
|
| 86 |
+
|
| 87 |
+
In --> Auth --> Validate --> Controller --> DB
|
| 88 |
+
Controller -->|enqueue| Queue
|
| 89 |
+
Controller -->|direct call| AIcalls
|
| 90 |
+
AIcalls --> Response
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
Responsibilities:
|
| 94 |
+
|
| 95 |
+
- Authentication and authorization (API tokens, OAuth, or guards configured in `config/auth.php`).
|
| 96 |
+
- Centralized error handling and logging.
|
| 97 |
+
- Exposes REST endpoints used by UI and third-party clients; routes live under `routes/`.
|
| 98 |
+
- Job dispatching and scheduling; queue worker executes heavy pipelines.
|
| 99 |
+
|
| 100 |
+
Where to look in repository:
|
| 101 |
+
|
| 102 |
+
- `app/Http/Controllers/` — controllers and endpoints
|
| 103 |
+
- `routes/api.php` — API routes and versioning
|
| 104 |
+
- `jobs/` and `app/Jobs` — background job implementations
|
| 105 |
+
- `docker/laravel.env` — example env values
|
| 106 |
+
|
| 107 |
+
2) ADMET inference (FastAPI)
|
| 108 |
+
|
| 109 |
+
```mermaid
|
| 110 |
+
flowchart TD
|
| 111 |
+
Req[POST /predict]
|
| 112 |
+
Validate[SMILES Validation (RDKit)]
|
| 113 |
+
Feat[Featurization / Graph builder]
|
| 114 |
+
Models[MPNN Models (5 tasks)]
|
| 115 |
+
Aggregate[Aggregate predictions]
|
| 116 |
+
Return[JSON response]
|
| 117 |
+
|
| 118 |
+
Req --> Validate --> Feat --> Models --> Aggregate --> Return
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
Responsibilities:
|
| 122 |
+
|
| 123 |
+
- Load pre-trained MPNN models on startup (check `models/` inside service).
|
| 124 |
+
- Validate SMILES strings with RDKit and sanitize input.
|
| 125 |
+
- Support single and batch prediction endpoints; expose `/health` and `/docs`.
|
| 126 |
+
- CPU-optimized inference (no GPU assumed by default) and async handling via Uvicorn.
|
| 127 |
+
|
| 128 |
+
Where to look:
|
| 129 |
+
|
| 130 |
+
- `ai_apps/ADMIT/admet_inference/` — service code, `README.md`, `Dockerfile`, `requirements.txt`.
|
| 131 |
+
- Health and docs endpoints available at `http://<host>:<port>/health` and `/docs`.
|
| 132 |
+
|
| 133 |
+
3) Drug Repurposing (FastAPI)
|
| 134 |
+
|
| 135 |
+
```mermaid
|
| 136 |
+
flowchart TD
|
| 137 |
+
Req[POST /api/v1/screen]
|
| 138 |
+
Targets[OpenTargets integration]
|
| 139 |
+
Sequences[UniProt fetch]
|
| 140 |
+
Drugs[Drug library (TDC / fallback)]
|
| 141 |
+
Model[DeepPurpose MPNN-CNN]
|
| 142 |
+
Results[Rank & postprocess]
|
| 143 |
+
Return[JSON summary]
|
| 144 |
+
|
| 145 |
+
Req --> Targets --> Sequences --> Drugs --> Model --> Results --> Return
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
Responsibilities:
|
| 149 |
+
|
| 150 |
+
- Full screening pipeline: disease→targets→proteins→screening→results.
|
| 151 |
+
- Integrates external APIs (OpenTargets, UniProt) and TDC for drug libraries.
|
| 152 |
+
- Provides mock modes for offline testing (use env `USE_MOCK_MODEL` and `USE_MOCK_DRUGS`).
|
| 153 |
+
|
| 154 |
+
Where to look:
|
| 155 |
+
|
| 156 |
+
- `ai_apps/Drug Reporposing/` — service implementation, `docker/` folder, `requirements.txt`, `README.md` and helper scripts (`start.sh`, `start.bat`).
|
| 157 |
+
|
| 158 |
+
4) Chemical RAG (FAISS + LLM)
|
| 159 |
+
|
| 160 |
+
```mermaid
|
| 161 |
+
flowchart TD
|
| 162 |
+
Req[/search/full-rag]
|
| 163 |
+
Validate[SMILES validation]
|
| 164 |
+
FP[Morgan fingerprint / embedding]
|
| 165 |
+
FAISS[FAISS-IVF index lookup]
|
| 166 |
+
LLM[LLM explanation (optional)]
|
| 167 |
+
Format[Result formatting]
|
| 168 |
+
Return[JSON results]
|
| 169 |
+
|
| 170 |
+
Req --> Validate --> FP --> FAISS --> LLM --> Format --> Return
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
Responsibilities:
|
| 174 |
+
|
| 175 |
+
- High-performance similarity search for large compound libraries (1M+).
|
| 176 |
+
- Persistent FAISS index stored on volume (`chemical-rag-data`).
|
| 177 |
+
- Two endpoints: retrieval-only and full RAG (with LLM explanations); auto-detects and ingests compounds on first run.
|
| 178 |
+
|
| 179 |
+
Where to look:
|
| 180 |
+
|
| 181 |
+
- `ai_apps/chemical-rag-system/chemical-rag-system/` — engine, ingestion scripts, FAISS index code, `run_server.py`, and `README.md`.
|
| 182 |
+
|
| 183 |
+
Environment variables & configuration locations
|
| 184 |
+
---------------------------------------------
|
| 185 |
+
|
| 186 |
+
- Root compose: `docker-compose.yml` — service names, build contexts, ports, and env files.
|
| 187 |
+
- Laravel env template: `docker/laravel.env` — application and DB defaults.
|
| 188 |
+
- Each AI service: check `ai_apps/<service>/` for `requirements.txt`, `Dockerfile`, and service-specific config (e.g., `app/config.py`).
|
| 189 |
+
|
| 190 |
+
Production deployment notes
|
| 191 |
+
-------------------------
|
| 192 |
+
|
| 193 |
+
- Resource limits: `docker-compose.yml` contains `deploy.resources.limits` for AI services; tune these for production (memory, CPU).
|
| 194 |
+
- Secrets: move sensitive env values into secret managers (Kubernetes Secrets, Vault) in production.
|
| 195 |
+
- HTTPS: terminate TLS at a reverse proxy in front of `laravel` (Nginx/Load Balancer) and restrict internal-only exposure for AI services.
|
| 196 |
+
|
| 197 |
+
Scaling considerations
|
| 198 |
+
---------------------
|
| 199 |
+
|
| 200 |
+
- Horizontal scaling of AI services: deploy multiple replicas behind a load balancer. Ensure models are loaded on container start and center model size is within available RAM.
|
| 201 |
+
- FAISS index: store on a shared volume or use a dedicated retrieval service with replicated indices.
|
| 202 |
+
- Database: use managed RDS/MariaDB clusters or replicas for HA.
|
| 203 |
+
|
| 204 |
+
Troubleshooting checkpoints
|
| 205 |
+
--------------------------
|
| 206 |
+
|
| 207 |
+
- Service not healthy: `docker compose logs -f <service>` and `docker compose ps` to check ports and status.
|
| 208 |
+
- Model load errors: inspect service logs, confirm model files under `models/` or configured model path.
|
| 209 |
+
- FAISS ingestion slow: watch `chemical-rag` logs on first-run ingestion; ensure sufficient CPU and disk I/O.
|
| 210 |
+
|
| 211 |
+
Appendix — quick visual references
|
| 212 |
+
---------------------------------
|
| 213 |
+
|
| 214 |
+
- Root system diagram: see top of this file (Mermaid block).
|
| 215 |
+
- Per-service diagrams: each service section above contains its own Mermaid block.
|
| 216 |
+
|
| 217 |
+
Next steps I can implement for you
|
| 218 |
+
----------------------------------
|
| 219 |
+
|
| 220 |
+
1. Generate PNG/SVG exports of these Mermaid diagrams and add them to the repo docs folder.
|
| 221 |
+
2. Standardize and replace `ai_apps/*/README.md` with a consistent template including: purpose, architecture diagram, setup (Docker + local), env vars, run commands, API endpoints, troubleshooting.
|
| 222 |
+
3. Create `docs/` folder and add `QUICK_START.md`, per-service `.env.example`, and the generated diagrams.
|
| 223 |
+
|
| 224 |
+
Tell me which of the next steps you'd like me to do now (1, 2, 3), or if you want all of them performed.
|
DOCKER.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AILIXIR Docker Stack
|
| 2 |
+
|
| 3 |
+
Monorepo stack: **Laravel API** + **ADMET** + **Drug Repurposing** + **Chemical RAG**.
|
| 4 |
+
|
| 5 |
+
## Requirements
|
| 6 |
+
|
| 7 |
+
- Docker 24+ with Compose v2
|
| 8 |
+
- ~16 GB RAM recommended (PyTorch + ADMET models)
|
| 9 |
+
- Model artifacts present locally:
|
| 10 |
+
- `ai_apps/ADMIT/admet_inference/models/*/best_model.ckpt`
|
| 11 |
+
- `ai_apps/Drug Reporposing/save_folder/pretrained_models/...`
|
| 12 |
+
|
| 13 |
+
## Quick start
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
# From repository root
|
| 17 |
+
docker compose build
|
| 18 |
+
docker compose up -d
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
| Service | Host URL |
|
| 22 |
+
|---------|----------|
|
| 23 |
+
| Laravel API | http://localhost:8080 |
|
| 24 |
+
| ADMET | http://localhost:8002 |
|
| 25 |
+
| Drug Repurposing | http://localhost:8001 |
|
| 26 |
+
| Chemical RAG | http://localhost:5000 |
|
| 27 |
+
|
| 28 |
+
## Laravel → AI integration endpoints
|
| 29 |
+
|
| 30 |
+
Enabled when `AI_INTEGRATION_ROUTES_ENABLED=true` (set in `docker/laravel.env`).
|
| 31 |
+
|
| 32 |
+
| Method | Endpoint | Description |
|
| 33 |
+
|--------|----------|-------------|
|
| 34 |
+
| GET | `/api/ai-services/health` | Ping all AI `/health` endpoints |
|
| 35 |
+
| POST | `/api/ai-services/test/admet` | Proxy ADMET `/predict` |
|
| 36 |
+
| POST | `/api/ai-services/test/chemical-search` | Proxy Chemical RAG retrieval |
|
| 37 |
+
| GET | `/api/ai-services/test/drug-repurposing` | Health + model status |
|
| 38 |
+
|
| 39 |
+
Example:
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
curl http://localhost:8080/api/ai-services/health
|
| 43 |
+
curl -X POST http://localhost:8080/api/ai-services/test/admet \
|
| 44 |
+
-H "Content-Type: application/json" \
|
| 45 |
+
-d '{"smiles":"c1ccccc1"}'
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
## Images
|
| 49 |
+
|
| 50 |
+
- **Laravel**: multi-stage (`composer` + `node` + `php:8.3-cli-alpine`), runs as `www-data`
|
| 51 |
+
- **ADMET / Drug Repurposing**: existing multi-stage Python slim images, non-root UID 1000
|
| 52 |
+
- **Chemical RAG**: multi-stage `python:3.11-slim`, non-root `raguser`
|
| 53 |
+
- **MySQL**: `mariadb:11.4`
|
| 54 |
+
|
| 55 |
+
## CI
|
| 56 |
+
|
| 57 |
+
GitHub Actions workflow: `.github/workflows/integration-test.yml`
|
| 58 |
+
|
| 59 |
+
Builds the full stack and tests Laravel proxy endpoints against all AI services.
|
| 60 |
+
|
| 61 |
+
## Environment
|
| 62 |
+
|
| 63 |
+
Copy and adjust `docker/laravel.env` for container-specific values. For local (non-Docker) development, set URLs in `.env` using `.env.example` as reference.
|
DOCKER_FIXES.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🔧 Docker Build Fixes - AILIXIR v2.0
|
| 2 |
+
|
| 3 |
+
**Last Updated**: 2026 | **Status**: ✅ All Issues Resolved
|
| 4 |
+
|
| 5 |
+
This document outlines the Docker build failures discovered in CI/CD and the solutions applied.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 🚨 Issues Resolved
|
| 10 |
+
|
| 11 |
+
### Issue #1: PyTorch Version Incompatibility (CRITICAL)
|
| 12 |
+
|
| 13 |
+
#### Problem
|
| 14 |
+
```
|
| 15 |
+
ERROR: Could not find a version that satisfies the requirement
|
| 16 |
+
torchaudio==0.15.2
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
#### Root Cause
|
| 20 |
+
- **File**: `ai_apps/Drug Reporposing/requirements.txt`
|
| 21 |
+
- **Line**: 24 (originally specified `torchaudio==0.15.2`)
|
| 22 |
+
- **Issue**: `torchaudio==0.15.2` does not exist on PyPI
|
| 23 |
+
- **Version Gap**: PyPI contains versions 0.11.0 through 2.11.0, but the 0.15.x and 1.x ranges were never released
|
| 24 |
+
- **Incompatibility**: The version was inconsistent with torch==2.0.1 and torchvision==0.15.2
|
| 25 |
+
|
| 26 |
+
#### Solution
|
| 27 |
+
Updated to compatible PyTorch audio library version:
|
| 28 |
+
|
| 29 |
+
**Before:**
|
| 30 |
+
```txt
|
| 31 |
+
torch==2.0.1
|
| 32 |
+
torchvision==0.15.2
|
| 33 |
+
torchaudio==0.15.2 # ❌ Does not exist
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
**After:**
|
| 37 |
+
```txt
|
| 38 |
+
torch==2.0.1
|
| 39 |
+
torchvision==0.15.2
|
| 40 |
+
torchaudio==2.0.1 # ✅ Compatible with torch 2.0.1
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
#### Files Changed
|
| 44 |
+
- `ai_apps/Drug Reporposing/requirements.txt` (line 24)
|
| 45 |
+
|
| 46 |
+
#### Verification
|
| 47 |
+
```bash
|
| 48 |
+
# These versions are now available and compatible
|
| 49 |
+
pip install torch==2.0.1 torchvision==0.15.2 torchaudio==2.0.1
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
---
|
| 53 |
+
|
| 54 |
+
### Issue #2: Dockerfile Casing Warnings (STYLE)
|
| 55 |
+
|
| 56 |
+
#### Problem
|
| 57 |
+
```
|
| 58 |
+
FromAsCasing: 'as' and 'FROM' keywords' casing do not match
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
#### Root Cause
|
| 62 |
+
- Docker Dockerfile linter enforces consistent keyword casing
|
| 63 |
+
- Multi-stage build syntax uses `FROM ... as builder` (mixed case)
|
| 64 |
+
- Should be `FROM ... AS builder` (uppercase AS)
|
| 65 |
+
- **Affected Files**: 2 of 4 Dockerfiles had this warning
|
| 66 |
+
|
| 67 |
+
#### Solution
|
| 68 |
+
Updated all multi-stage Dockerfile build syntax to use uppercase `AS`:
|
| 69 |
+
|
| 70 |
+
**Before:**
|
| 71 |
+
```dockerfile
|
| 72 |
+
FROM python:3.10-slim as builder # ❌ Mixed case
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
**After:**
|
| 76 |
+
```dockerfile
|
| 77 |
+
FROM python:3.10-slim AS builder # ✅ Proper casing
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
#### Files Changed
|
| 81 |
+
1. `ai_apps/ADMIT/admet_inference/Dockerfile` (line 4)
|
| 82 |
+
- Changed: `FROM python:3.11-slim as builder` → `FROM python:3.11-slim AS builder`
|
| 83 |
+
|
| 84 |
+
2. `ai_apps/Drug Reporposing/docker/Dockerfile` (line 1)
|
| 85 |
+
- Changed: `FROM python:3.10-slim as builder` → `FROM python:3.10-slim AS builder`
|
| 86 |
+
|
| 87 |
+
#### Status Note
|
| 88 |
+
- `ai_apps/chemical-rag-system/chemical-rag-system/Dockerfile` already had correct casing (`AS builder`)
|
| 89 |
+
- No changes were needed for this file
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
## 📊 Fix Summary
|
| 94 |
+
|
| 95 |
+
| Issue | File(s) | Change | Status |
|
| 96 |
+
|-------|---------|--------|--------|
|
| 97 |
+
| PyTorch incompatibility | Drug Reporposing/requirements.txt | torchaudio 0.15.2 → 2.0.1 | ✅ Fixed |
|
| 98 |
+
| Dockerfile casing | ADMET/Dockerfile | `as` → `AS` | ✅ Fixed |
|
| 99 |
+
| Dockerfile casing | Drug Repurposing/Dockerfile | `as` → `AS` | ✅ Fixed |
|
| 100 |
+
| Dockerfile casing | Chemical-RAG/Dockerfile | Already correct | ✅ OK |
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## ✅ Verification Steps
|
| 105 |
+
|
| 106 |
+
After fixes, verify the Docker build succeeds:
|
| 107 |
+
|
| 108 |
+
```bash
|
| 109 |
+
# Clear Docker cache (optional)
|
| 110 |
+
docker compose down
|
| 111 |
+
docker system prune -a
|
| 112 |
+
|
| 113 |
+
# Build all services with fixes applied
|
| 114 |
+
cd AILIXIR_BackEnd
|
| 115 |
+
docker compose build --parallel
|
| 116 |
+
|
| 117 |
+
# Expected output:
|
| 118 |
+
# [+] Building 5/5
|
| 119 |
+
# - Image ailixir-admet Built
|
| 120 |
+
# - Image ailixir-drug-repurposing Built
|
| 121 |
+
# - Image ailixir-chemical-rag Built
|
| 122 |
+
# - Image ailixir-laravel Built
|
| 123 |
+
# - Image ailixir-queue Built
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### Verify Individual Services
|
| 127 |
+
```bash
|
| 128 |
+
# Check each image built successfully
|
| 129 |
+
docker images | grep ailixir
|
| 130 |
+
|
| 131 |
+
# Test container startup
|
| 132 |
+
docker compose up -d
|
| 133 |
+
docker compose ps
|
| 134 |
+
# All services should show "Up" status ✅
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
---
|
| 138 |
+
|
| 139 |
+
## 🔍 Technical Details
|
| 140 |
+
|
| 141 |
+
### PyTorch Version Compatibility Matrix
|
| 142 |
+
|
| 143 |
+
For Python 3.10 with CUDA-free (CPU) installation:
|
| 144 |
+
|
| 145 |
+
| torch | torchvision | torchaudio | Status |
|
| 146 |
+
|-------|-------------|-----------|--------|
|
| 147 |
+
| 2.0.1 | 0.15.2 | 0.15.2 | ❌ torchaudio doesn't exist |
|
| 148 |
+
| 2.0.1 | 0.15.2 | 2.0.1 | ✅ Valid (current) |
|
| 149 |
+
| 2.0.1 | 0.15.2 | 2.0.2 | ✅ Valid alternative |
|
| 150 |
+
| 1.13.1 | 0.14.1 | 0.13.1 | ✅ Legacy option |
|
| 151 |
+
|
| 152 |
+
**Selected**: torch 2.0.1 + torchaudio 2.0.1 (latest stable, best compatibility)
|
| 153 |
+
|
| 154 |
+
### Dockerfile Multi-Stage Build Syntax
|
| 155 |
+
|
| 156 |
+
The styleguide requires proper case:
|
| 157 |
+
|
| 158 |
+
```dockerfile
|
| 159 |
+
# Correct syntax
|
| 160 |
+
FROM <image> AS <name>
|
| 161 |
+
COPY --from=<name> /path /path
|
| 162 |
+
|
| 163 |
+
# Example (Drug Repurposing)
|
| 164 |
+
FROM python:3.10-slim AS builder
|
| 165 |
+
RUN apt-get update && pip install -r requirements.txt
|
| 166 |
+
FROM python:3.10-slim
|
| 167 |
+
COPY --from=builder /opt/venv /opt/venv
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
---
|
| 171 |
+
|
| 172 |
+
## 📋 Checklist for Future Updates
|
| 173 |
+
|
| 174 |
+
When updating dependencies or Dockerfiles:
|
| 175 |
+
|
| 176 |
+
- [ ] Verify PyPI has the exact version (search on pypi.org)
|
| 177 |
+
- [ ] Check PyTorch compatibility: https://pytorch.org/get-started/locally/
|
| 178 |
+
- [ ] Test locally: `docker compose build --parallel`
|
| 179 |
+
- [ ] Check for Dockerfile linter warnings: `docker build --check`
|
| 180 |
+
- [ ] Run services: `docker compose up -d`
|
| 181 |
+
- [ ] Verify API endpoints respond: `curl http://localhost:8000`
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
## 🆘 Troubleshooting
|
| 186 |
+
|
| 187 |
+
### If Docker Build Still Fails
|
| 188 |
+
|
| 189 |
+
1. **Clear Docker cache**:
|
| 190 |
+
```bash
|
| 191 |
+
docker compose down
|
| 192 |
+
docker system prune -a -f
|
| 193 |
+
docker compose build --no-cache --parallel
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
2. **Check individual service logs**:
|
| 197 |
+
```bash
|
| 198 |
+
docker compose build admet 2>&1 | tail -50
|
| 199 |
+
docker compose build drug-repurposing 2>&1 | tail -50
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
3. **Verify file changes**:
|
| 203 |
+
```bash
|
| 204 |
+
# Check torchaudio version
|
| 205 |
+
grep -n "torchaudio" ai_apps/Drug\ Reporposing/requirements.txt
|
| 206 |
+
|
| 207 |
+
# Check Dockerfile casing
|
| 208 |
+
grep "FROM.*as\|FROM.*AS" ai_apps/*/*/Dockerfile ai_apps/*/*/Dockerfile
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
4. **Network/Registry Issues**:
|
| 212 |
+
- Ensure internet connectivity for PyPI, Docker Hub
|
| 213 |
+
- Try building at different time (registry mirrors may have delays)
|
| 214 |
+
|
| 215 |
+
---
|
| 216 |
+
|
| 217 |
+
## 📚 References
|
| 218 |
+
|
| 219 |
+
- PyPI Package Index: https://pypi.org/
|
| 220 |
+
- PyTorch Installation: https://pytorch.org/get-started/locally/
|
| 221 |
+
- Docker Best Practices: https://docs.docker.com/develop/dev-best-practices/
|
| 222 |
+
- Dockerfile Reference: https://docs.docker.com/engine/reference/builder/
|
| 223 |
+
|
| 224 |
+
---
|
| 225 |
+
|
| 226 |
+
## 🎯 Next Steps
|
| 227 |
+
|
| 228 |
+
- Monitor CI/CD pipeline for successful builds: `docker compose build --parallel`
|
| 229 |
+
- Add pre-commit hooks to validate Dockerfile syntax
|
| 230 |
+
- Document dependency update procedures
|
| 231 |
+
- Set up automated version compatibility checks
|
DOCUMENTATION_STRUCTURE.md
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AILIXIR Documentation Structure (Recommended)
|
| 2 |
+
|
| 3 |
+
**Status:** Documentation refactoring complete | **Last Updated:** May 2026
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 📋 Overview
|
| 8 |
+
|
| 9 |
+
The AILIXIR project has been refactored to follow modern open-source documentation standards:
|
| 10 |
+
|
| 11 |
+
- **Concise main README** (~800 lines, down from 2500+) focused on high-level overview
|
| 12 |
+
- **Dedicated documentation files** for specific topics
|
| 13 |
+
- **Service-specific READMEs** in each microservice directory
|
| 14 |
+
- **Clear navigation** with links between related documents
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## 📁 Recommended Documentation File Structure
|
| 19 |
+
|
| 20 |
+
### Root Documentation Files
|
| 21 |
+
|
| 22 |
+
```
|
| 23 |
+
ailixir-backend/
|
| 24 |
+
├── README.md ✅ REFACTORED (concise, high-level)
|
| 25 |
+
├── QUICK_START.md 📝 REFERENCED (needs creation/verification)
|
| 26 |
+
├── ARCHITECTURE.md ✅ EXISTS (link from README)
|
| 27 |
+
├── DOCKER.md ✅ EXISTS (link from README)
|
| 28 |
+
├── PRODUCTION_GUIDE.md 📝 REFERENCED (needs creation/verification)
|
| 29 |
+
├── TROUBLESHOOTING.md 📝 REFERENCED (needs creation)
|
| 30 |
+
├── API_REFERENCE.md 📝 REFERENCED (needs creation)
|
| 31 |
+
├── CONTRIBUTING.md ✅ REFERENCED (guidelines included in README)
|
| 32 |
+
├── DOCKER_FIXES.md ✅ EXISTS (known issues)
|
| 33 |
+
└── LICENSE ✅ EXISTS
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
### Service Documentation Files
|
| 37 |
+
|
| 38 |
+
```
|
| 39 |
+
ai_apps/
|
| 40 |
+
├── ADMIT/
|
| 41 |
+
│ ├── README.md ✅ UPDATED (training + inference)
|
| 42 |
+
│ └── train_ADMET_model.ipynb ✅ EXISTS
|
| 43 |
+
│
|
| 44 |
+
├── Drug Reporposing/
|
| 45 |
+
│ ├── README.md ✅ GOOD (keep as-is)
|
| 46 |
+
│ ├── QUICK_START.md ✅ EXISTS
|
| 47 |
+
│ ├── PRODUCTION_GUIDE.md ✅ EXISTS
|
| 48 |
+
│ └── IMPLEMENTATION_SUMMARY.md ✅ EXISTS
|
| 49 |
+
│
|
| 50 |
+
└── chemical-rag-system/
|
| 51 |
+
└── README.md ✅ UPDATED (v2.1)
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## 📄 Documentation Files Detail
|
| 57 |
+
|
| 58 |
+
### 1. **README.md** ✅ (Completed)
|
| 59 |
+
|
| 60 |
+
**Purpose:** Landing page, high-level overview
|
| 61 |
+
|
| 62 |
+
**Current Status:** ✅ Refactored and polished
|
| 63 |
+
|
| 64 |
+
**Length:** ~800 lines (reduced from 2500+)
|
| 65 |
+
|
| 66 |
+
**Content:**
|
| 67 |
+
- System overview and positioning
|
| 68 |
+
- Feature table
|
| 69 |
+
- Architecture diagram (ASCII, fixed alignment)
|
| 70 |
+
- Quick start (minimal, links to detailed guides)
|
| 71 |
+
- Services overview table
|
| 72 |
+
- API summary with examples
|
| 73 |
+
- Environment variables reference
|
| 74 |
+
- Project structure tree
|
| 75 |
+
- Links to all detailed documentation
|
| 76 |
+
|
| 77 |
+
**Strengths:**
|
| 78 |
+
- Professional and concise
|
| 79 |
+
- Clear navigation to other docs
|
| 80 |
+
- Suitable for GitHub landing page
|
| 81 |
+
- Impresses recruiters and contributors
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
### 2. **QUICK_START.md** 📝 (Reference, needs verification)
|
| 86 |
+
|
| 87 |
+
**Purpose:** 5-minute setup for developers
|
| 88 |
+
|
| 89 |
+
**Recommended Content:**
|
| 90 |
+
- System requirements (minimal)
|
| 91 |
+
- Docker setup (3 commands)
|
| 92 |
+
- Local setup (per service, brief)
|
| 93 |
+
- Health check verification
|
| 94 |
+
- "Next Steps" links to detailed docs
|
| 95 |
+
|
| 96 |
+
**File Structure:**
|
| 97 |
+
```markdown
|
| 98 |
+
# Quick Start
|
| 99 |
+
|
| 100 |
+
## Prerequisites
|
| 101 |
+
- Docker & Docker Compose v2+
|
| 102 |
+
- OR: PHP 8.2+, Python 3.10+, MariaDB
|
| 103 |
+
|
| 104 |
+
## Docker (5 minutes)
|
| 105 |
+
# 3-4 commands
|
| 106 |
+
|
| 107 |
+
## Local Development
|
| 108 |
+
# Brief per-service setup
|
| 109 |
+
|
| 110 |
+
## Verify Installation
|
| 111 |
+
# Health checks
|
| 112 |
+
|
| 113 |
+
## Next Steps
|
| 114 |
+
# Links to detailed guides
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
**Cross-references:** README.md → QUICK_START.md → DOCKER.md
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
### 3. **ARCHITECTURE.md** ✅ (Exists, referenced)
|
| 122 |
+
|
| 123 |
+
**Purpose:** System design, diagrams, component details
|
| 124 |
+
|
| 125 |
+
**Current Status:** ✅ Already comprehensive (use as-is)
|
| 126 |
+
|
| 127 |
+
**Content:**
|
| 128 |
+
- System-level architecture diagram (Mermaid)
|
| 129 |
+
- Component responsibilities table
|
| 130 |
+
- Data flow explanation
|
| 131 |
+
- Request lifecycle walkthrough
|
| 132 |
+
- Per-service component details
|
| 133 |
+
- Environment variables location guide
|
| 134 |
+
- Deployment notes and scaling
|
| 135 |
+
|
| 136 |
+
**Cross-references:** README.md ← ARCHITECTURE.md
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
### 4. **DOCKER.md** ✅ (Exists, referenced)
|
| 141 |
+
|
| 142 |
+
**Purpose:** Docker-specific setup and configuration
|
| 143 |
+
|
| 144 |
+
**Current Status:** ✅ Comprehensive (use as-is)
|
| 145 |
+
|
| 146 |
+
**Content:**
|
| 147 |
+
- Docker requirements
|
| 148 |
+
- Quick start with docker-compose
|
| 149 |
+
- Service URLs and ports table
|
| 150 |
+
- Laravel AI integration endpoints
|
| 151 |
+
- Docker image details
|
| 152 |
+
- CI/CD workflow
|
| 153 |
+
- Environment configuration
|
| 154 |
+
|
| 155 |
+
**Cross-references:** README.md → DOCKER.md
|
| 156 |
+
|
| 157 |
+
---
|
| 158 |
+
|
| 159 |
+
### 5. **PRODUCTION_GUIDE.md** 📝 (Reference, needs verification)
|
| 160 |
+
|
| 161 |
+
**Purpose:** Deployment architecture, scaling, monitoring, security
|
| 162 |
+
|
| 163 |
+
**Recommended Content:**
|
| 164 |
+
```markdown
|
| 165 |
+
# Production Guide
|
| 166 |
+
|
| 167 |
+
## Pre-Deployment Checklist
|
| 168 |
+
- Configuration review
|
| 169 |
+
- Security setup
|
| 170 |
+
- Backup strategy
|
| 171 |
+
- Monitoring setup
|
| 172 |
+
|
| 173 |
+
## Deployment Architecture
|
| 174 |
+
- Architecture diagram (Kubernetes vs Docker Swarm)
|
| 175 |
+
- Service replication strategies
|
| 176 |
+
- Load balancing setup
|
| 177 |
+
- Database replication
|
| 178 |
+
|
| 179 |
+
## Scaling Strategies
|
| 180 |
+
- Horizontal scaling for stateless services
|
| 181 |
+
- Database scaling and replication
|
| 182 |
+
- Queue worker scaling
|
| 183 |
+
- Memory and resource limits
|
| 184 |
+
|
| 185 |
+
## Monitoring & Observability
|
| 186 |
+
- Health check endpoints
|
| 187 |
+
- Log aggregation setup
|
| 188 |
+
- Metrics collection (Prometheus)
|
| 189 |
+
- Alert configuration
|
| 190 |
+
|
| 191 |
+
## Security Hardening
|
| 192 |
+
- Environment variable management
|
| 193 |
+
- HTTPS/TLS setup
|
| 194 |
+
- API authentication
|
| 195 |
+
- Database security
|
| 196 |
+
|
| 197 |
+
## Performance Tuning
|
| 198 |
+
- Connection pooling
|
| 199 |
+
- Cache configuration
|
| 200 |
+
- Query optimization
|
| 201 |
+
- Rate limiting
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
**Cross-references:** README.md → PRODUCTION_GUIDE.md
|
| 205 |
+
|
| 206 |
+
---
|
| 207 |
+
|
| 208 |
+
### 6. **TROUBLESHOOTING.md** 📝 (Reference, needs creation)
|
| 209 |
+
|
| 210 |
+
**Purpose:** Common issues and solutions
|
| 211 |
+
|
| 212 |
+
**Recommended Content:**
|
| 213 |
+
```markdown
|
| 214 |
+
# Troubleshooting
|
| 215 |
+
|
| 216 |
+
## Quick Reference Table
|
| 217 |
+
| Problem | Cause | Solution |
|
| 218 |
+
| ... |
|
| 219 |
+
|
| 220 |
+
## Container Issues
|
| 221 |
+
- Service won't start
|
| 222 |
+
- Port already in use
|
| 223 |
+
- Out of memory
|
| 224 |
+
|
| 225 |
+
## Database Issues
|
| 226 |
+
- Connection failures
|
| 227 |
+
- Migration errors
|
| 228 |
+
- Data persistence
|
| 229 |
+
|
| 230 |
+
## AI Service Issues
|
| 231 |
+
- Model loading errors
|
| 232 |
+
- GPU out of memory
|
| 233 |
+
- Timeout issues
|
| 234 |
+
- FAISS index slow
|
| 235 |
+
|
| 236 |
+
## Development Issues
|
| 237 |
+
- Python environment setup
|
| 238 |
+
- Dependency conflicts
|
| 239 |
+
- CUDA not found
|
| 240 |
+
- Permission errors
|
| 241 |
+
|
| 242 |
+
## Debugging
|
| 243 |
+
- How to check logs
|
| 244 |
+
- Health check procedures
|
| 245 |
+
- Debug mode configuration
|
| 246 |
+
|
| 247 |
+
## FAQ
|
| 248 |
+
- Common questions
|
| 249 |
+
- Best practices
|
| 250 |
+
- Performance optimization
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
**Cross-references:** README.md → TROUBLESHOOTING.md
|
| 254 |
+
|
| 255 |
+
---
|
| 256 |
+
|
| 257 |
+
### 7. **API_REFERENCE.md** 📝 (Reference, needs creation)
|
| 258 |
+
|
| 259 |
+
**Purpose:** Complete API endpoint documentation
|
| 260 |
+
|
| 261 |
+
**Recommended Content:**
|
| 262 |
+
```markdown
|
| 263 |
+
# API Reference
|
| 264 |
+
|
| 265 |
+
## Overview
|
| 266 |
+
- Base URLs
|
| 267 |
+
- Authentication
|
| 268 |
+
- Error handling
|
| 269 |
+
- Rate limiting
|
| 270 |
+
|
| 271 |
+
## Laravel Orchestration API
|
| 272 |
+
- Health endpoints
|
| 273 |
+
- AI service proxy endpoints
|
| 274 |
+
- Job management endpoints
|
| 275 |
+
- Result retrieval endpoints
|
| 276 |
+
|
| 277 |
+
## ADMET Service API
|
| 278 |
+
- Health endpoint
|
| 279 |
+
- Info endpoint
|
| 280 |
+
- Predict endpoints (single & batch)
|
| 281 |
+
- Model status endpoint
|
| 282 |
+
|
| 283 |
+
## Drug Repurposing Service API
|
| 284 |
+
- Disease targets endpoint
|
| 285 |
+
- Drug library endpoint
|
| 286 |
+
- Screening endpoint
|
| 287 |
+
- Model status endpoint
|
| 288 |
+
|
| 289 |
+
## Chemical RAG Service API
|
| 290 |
+
- Retrieval-only endpoint
|
| 291 |
+
- Full RAG endpoint
|
| 292 |
+
- Health endpoint
|
| 293 |
+
- Stats endpoint
|
| 294 |
+
|
| 295 |
+
## Response Formats
|
| 296 |
+
- Success responses
|
| 297 |
+
- Error responses
|
| 298 |
+
- Status codes
|
| 299 |
+
|
| 300 |
+
## Examples
|
| 301 |
+
- Real curl requests for each endpoint
|
| 302 |
+
- Request/response pairs
|
| 303 |
+
- Error scenarios
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
**Cross-references:** README.md → API_REFERENCE.md, Service READMEs
|
| 307 |
+
|
| 308 |
+
---
|
| 309 |
+
|
| 310 |
+
### 8. **CONTRIBUTING.md** ✅ (Referenced in README)
|
| 311 |
+
|
| 312 |
+
**Purpose:** Development guidelines
|
| 313 |
+
|
| 314 |
+
**Current Status:** ✅ Guidelines included in README.md
|
| 315 |
+
|
| 316 |
+
**Recommended Dedicated File:**
|
| 317 |
+
```markdown
|
| 318 |
+
# Contributing
|
| 319 |
+
|
| 320 |
+
## Getting Started
|
| 321 |
+
- Fork and clone
|
| 322 |
+
- Setup development environment
|
| 323 |
+
- Create feature branch
|
| 324 |
+
|
| 325 |
+
## Code Style
|
| 326 |
+
- PHP: PSR-12
|
| 327 |
+
- Python: PEP 8
|
| 328 |
+
- Commits: Conventional format
|
| 329 |
+
|
| 330 |
+
## Development Workflow
|
| 331 |
+
- Local development
|
| 332 |
+
- Testing requirements
|
| 333 |
+
- Documentation updates
|
| 334 |
+
- Pull request process
|
| 335 |
+
|
| 336 |
+
## Testing
|
| 337 |
+
- Unit tests
|
| 338 |
+
- Integration tests
|
| 339 |
+
- Running test suites
|
| 340 |
+
|
| 341 |
+
## Commit Messages
|
| 342 |
+
- Format guidelines
|
| 343 |
+
- Examples
|
| 344 |
+
|
| 345 |
+
## Pull Request Process
|
| 346 |
+
- Description template
|
| 347 |
+
- Code review expectations
|
| 348 |
+
- Merge requirements
|
| 349 |
+
```
|
| 350 |
+
|
| 351 |
+
---
|
| 352 |
+
|
| 353 |
+
## 🎯 Implementation Checklist
|
| 354 |
+
|
| 355 |
+
### Completed ✅
|
| 356 |
+
- [x] README.md refactored (concise, professional, links to docs)
|
| 357 |
+
- [x] ARCHITECTURE.md (exists, comprehensive)
|
| 358 |
+
- [x] DOCKER.md (exists, comprehensive)
|
| 359 |
+
- [x] ADMET/README.md (updated with full training + inference)
|
| 360 |
+
- [x] Chemical-RAG/README.md (updated with v2.1 focus)
|
| 361 |
+
- [x] Drug Reporposing/README.md (already excellent, kept as-is)
|
| 362 |
+
|
| 363 |
+
### Recommended Verification/Creation 📝
|
| 364 |
+
- [ ] QUICK_START.md - Verify or create based on template
|
| 365 |
+
- [ ] PRODUCTION_GUIDE.md - Create based on template
|
| 366 |
+
- [ ] TROUBLESHOOTING.md - Create based on template
|
| 367 |
+
- [ ] API_REFERENCE.md - Create based on template
|
| 368 |
+
- [ ] CONTRIBUTING.md - Extract to standalone file (optional)
|
| 369 |
+
|
| 370 |
+
---
|
| 371 |
+
|
| 372 |
+
## 📊 Documentation Standards Applied
|
| 373 |
+
|
| 374 |
+
### Format & Structure
|
| 375 |
+
✅ Consistent markdown formatting across all files
|
| 376 |
+
✅ Clear section hierarchy (H1-H4)
|
| 377 |
+
✅ Emoji headers for visual organization
|
| 378 |
+
✅ Markdown best practices (lists, tables, code blocks)
|
| 379 |
+
|
| 380 |
+
### Navigation
|
| 381 |
+
✅ Table of contents in root README
|
| 382 |
+
✅ Cross-references between related docs
|
| 383 |
+
✅ Clear "Next Steps" links at end of each section
|
| 384 |
+
✅ Service-specific READMEs with complete info
|
| 385 |
+
|
| 386 |
+
### Content Quality
|
| 387 |
+
✅ Professional tone (enterprise-grade)
|
| 388 |
+
✅ Concise main README with detailed docs
|
| 389 |
+
✅ Real examples (ports, commands, endpoints)
|
| 390 |
+
✅ Troubleshooting guides with solutions
|
| 391 |
+
|
| 392 |
+
### GitHub Readability
|
| 393 |
+
✅ Clean whitespace between sections
|
| 394 |
+
✅ Compact tables
|
| 395 |
+
✅ Reduced code block sizes
|
| 396 |
+
✅ Links instead of repetition
|
| 397 |
+
|
| 398 |
+
---
|
| 399 |
+
|
| 400 |
+
## 🎓 Best Practices References
|
| 401 |
+
|
| 402 |
+
This documentation structure follows conventions from:
|
| 403 |
+
- **Apache Software Foundation** - Modular documentation
|
| 404 |
+
- **Kubernetes** - Clear architecture docs with links
|
| 405 |
+
- **Popular open-source projects** - Concise main README with satellite docs
|
| 406 |
+
- **Enterprise software standards** - Professional, scalable approach
|
| 407 |
+
|
| 408 |
+
---
|
| 409 |
+
|
| 410 |
+
## 📋 File Status Summary
|
| 411 |
+
|
| 412 |
+
| File | Status | Purpose |
|
| 413 |
+
|------|--------|---------|
|
| 414 |
+
| README.md | ✅ Complete | High-level overview + navigation |
|
| 415 |
+
| ARCHITECTURE.md | ✅ Exists | System design details |
|
| 416 |
+
| DOCKER.md | ✅ Exists | Container setup |
|
| 417 |
+
| PRODUCTION_GUIDE.md | 📝 Recommended | Deployment & scaling |
|
| 418 |
+
| TROUBLESHOOTING.md | 📝 Recommended | Problem solving |
|
| 419 |
+
| API_REFERENCE.md | 📝 Recommended | Endpoint documentation |
|
| 420 |
+
| CONTRIBUTING.md | ✅ Included in README | Contributing guidelines |
|
| 421 |
+
| Service READMEs | ✅ All updated | Component-specific docs |
|
| 422 |
+
|
| 423 |
+
---
|
| 424 |
+
|
| 425 |
+
## 🚀 Next Steps
|
| 426 |
+
|
| 427 |
+
1. **Verify** QUICK_START.md exists and is up-to-date
|
| 428 |
+
2. **Create or verify** PRODUCTION_GUIDE.md using template
|
| 429 |
+
3. **Create** TROUBLESHOOTING.md from existing content
|
| 430 |
+
4. **Create** API_REFERENCE.md for clarity
|
| 431 |
+
5. **(Optional) Extract** CONTRIBUTING.md as standalone file
|
| 432 |
+
6. **Update** navigation links once all files are created
|
| 433 |
+
7. **Test** all links render correctly on GitHub
|
| 434 |
+
|
| 435 |
+
---
|
| 436 |
+
|
| 437 |
+
## 📞 Questions?
|
| 438 |
+
|
| 439 |
+
Reference this document when:
|
| 440 |
+
- Creating new documentation files
|
| 441 |
+
- Updating existing docs
|
| 442 |
+
- Organizing repository documentation
|
| 443 |
+
- Onboarding new contributors
|
| 444 |
+
|
| 445 |
+
**Maintain consistency:** Keep all docs at the same quality level and style for professional appearance.
|
| 446 |
+
|
| 447 |
+
---
|
| 448 |
+
|
| 449 |
+
**Last Updated:** May 2026 | **Status:** Documentation structure defined ✅
|
Dockerfile
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# syntax=docker/dockerfile:1
|
| 2 |
+
|
| 3 |
+
# ============================================================
|
| 4 |
+
# Stage 1: Composer dependencies
|
| 5 |
+
# ============================================================
|
| 6 |
+
FROM composer:2.8 AS vendor
|
| 7 |
+
|
| 8 |
+
WORKDIR /app
|
| 9 |
+
|
| 10 |
+
RUN apk add --no-cache icu-dev libzip-dev oniguruma-dev \
|
| 11 |
+
&& docker-php-ext-install intl zip mbstring bcmath pcntl
|
| 12 |
+
|
| 13 |
+
# Add build argument for composer dev dependencies
|
| 14 |
+
|
| 15 |
+
COPY composer.json composer.lock ./
|
| 16 |
+
RUN composer install \
|
| 17 |
+
--no-interaction \
|
| 18 |
+
--no-scripts \
|
| 19 |
+
--prefer-dist \
|
| 20 |
+
--optimize-autoloader
|
| 21 |
+
|
| 22 |
+
COPY . .
|
| 23 |
+
RUN composer dump-autoload --optimize --classmap-authoritative
|
| 24 |
+
|
| 25 |
+
# ============================================================
|
| 26 |
+
# Stage 2: Frontend assets (Vite)
|
| 27 |
+
# ============================================================
|
| 28 |
+
FROM node:22-alpine AS assets
|
| 29 |
+
|
| 30 |
+
WORKDIR /app
|
| 31 |
+
|
| 32 |
+
COPY package.json package-lock.json ./
|
| 33 |
+
RUN npm ci
|
| 34 |
+
|
| 35 |
+
COPY vite.config.js ./
|
| 36 |
+
COPY resources ./resources
|
| 37 |
+
COPY public ./public
|
| 38 |
+
|
| 39 |
+
RUN npm run build
|
| 40 |
+
|
| 41 |
+
# ============================================================
|
| 42 |
+
# Stage 3: Runtime (PHP CLI on Debian — artisan serve)
|
| 43 |
+
# ============================================================
|
| 44 |
+
FROM php:8.3-cli AS runtime
|
| 45 |
+
|
| 46 |
+
RUN apt-get update && apt-get install -y \
|
| 47 |
+
libicu-dev \
|
| 48 |
+
libzip-dev \
|
| 49 |
+
libonig-dev \
|
| 50 |
+
libsqlite3-dev \
|
| 51 |
+
wget \
|
| 52 |
+
bzip2 \
|
| 53 |
+
ca-certificates \
|
| 54 |
+
openbabel \
|
| 55 |
+
supervisor \
|
| 56 |
+
&& docker-php-ext-install \
|
| 57 |
+
pdo_sqlite \
|
| 58 |
+
zip \
|
| 59 |
+
intl \
|
| 60 |
+
mbstring \
|
| 61 |
+
bcmath \
|
| 62 |
+
opcache \
|
| 63 |
+
pcntl \
|
| 64 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 65 |
+
|
| 66 |
+
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
|
| 67 |
+
|
| 68 |
+
# Install Miniconda
|
| 69 |
+
ENV CONDA_DIR=/opt/conda
|
| 70 |
+
|
| 71 |
+
RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && \
|
| 72 |
+
/bin/bash /tmp/miniconda.sh -b -p $CONDA_DIR && \
|
| 73 |
+
rm /tmp/miniconda.sh
|
| 74 |
+
|
| 75 |
+
ENV PATH=$CONDA_DIR/bin:$PATH
|
| 76 |
+
|
| 77 |
+
# Accept Anaconda ToS (required in CI/Docker)
|
| 78 |
+
RUN conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main && \
|
| 79 |
+
conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r
|
| 80 |
+
|
| 81 |
+
# Create environment
|
| 82 |
+
RUN conda create -y -p /var/www/html/vina_env -c conda-forge \
|
| 83 |
+
vina=1.2.5 \
|
| 84 |
+
python=3.10 \
|
| 85 |
+
pip
|
| 86 |
+
|
| 87 |
+
# Install RDKit via pip to bypass Boost C++ solver compatibility conflicts
|
| 88 |
+
RUN /var/www/html/vina_env/bin/pip install rdkit==2025.09.5
|
| 89 |
+
|
| 90 |
+
# Install testing and service dependencies from requirements.txt
|
| 91 |
+
COPY requirements.txt /tmp/requirements.txt
|
| 92 |
+
RUN /var/www/html/vina_env/bin/pip install -r /tmp/requirements.txt
|
| 93 |
+
|
| 94 |
+
ENV PATH=/var/www/html/vina_env/bin:$PATH
|
| 95 |
+
|
| 96 |
+
WORKDIR /var/www/html
|
| 97 |
+
|
| 98 |
+
COPY --from=vendor --chown=www-data:www-data /app /var/www/html
|
| 99 |
+
COPY --from=assets --chown=www-data:www-data /app/public/build /var/www/html/public/build
|
| 100 |
+
|
| 101 |
+
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
| 102 |
+
COPY docker/laravel.env .env
|
| 103 |
+
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
| 104 |
+
RUN chmod +x /usr/local/bin/entrypoint.sh \
|
| 105 |
+
&& mkdir -p storage/framework/{cache,sessions,views} storage/logs bootstrap/cache database \
|
| 106 |
+
&& chown -R www-data:www-data storage bootstrap/cache .env database /var/www/html/vina_env
|
| 107 |
+
|
| 108 |
+
USER www-data
|
| 109 |
+
|
| 110 |
+
EXPOSE 8000 7860
|
| 111 |
+
|
| 112 |
+
HEALTHCHECK --interval=15s --timeout=5s --start-period=40s --retries=5 \
|
| 113 |
+
CMD php -r "exit((int)(@file_get_contents('http://127.0.0.1:8000') === false));"
|
| 114 |
+
|
| 115 |
+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
| 116 |
+
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]
|
README.md
CHANGED
|
@@ -1,10 +1,107 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
| 1 |
+
# AILIXIR API Test Suite
|
| 2 |
+
|
| 3 |
+
> Automated test suite for AILIXIR Drug Discovery API v2.1
|
| 4 |
+
|
| 5 |
+
## Quick Start
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
# 1. Install dependencies
|
| 9 |
+
pip install -r requirements.txt
|
| 10 |
+
|
| 11 |
+
# 2. Set environment variables
|
| 12 |
+
cp .env.example .env
|
| 13 |
+
# Edit .env with your credentials
|
| 14 |
+
|
| 15 |
+
# 3. Run all tests
|
| 16 |
+
pytest
|
| 17 |
+
|
| 18 |
+
# 4. Run specific module
|
| 19 |
+
pytest tests/test_auth.py -v
|
| 20 |
+
|
| 21 |
+
# 5. Run with coverage
|
| 22 |
+
pytest --cov=tests --cov-report=html
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
## Test Coverage
|
| 26 |
+
|
| 27 |
+
| Module | Endpoints | Tests |
|
| 28 |
+
|--------|-----------|-------|
|
| 29 |
+
| Authentication | 7 | ~25 |
|
| 30 |
+
| AI Agent | 12 | ~45 |
|
| 31 |
+
| Chemical Search | 4 | ~15 |
|
| 32 |
+
| User Management | 2 | ~8 |
|
| 33 |
+
| Awards & Scientists | 6 | ~18 |
|
| 34 |
+
| News | 8 | ~24 |
|
| 35 |
+
| **Total** | **39** | **~135** |
|
| 36 |
+
|
| 37 |
+
## Environment Variables
|
| 38 |
+
|
| 39 |
+
| Variable | Description | Required |
|
| 40 |
+
|----------|-------------|----------|
|
| 41 |
+
| `BASE_URL` | API base URL | Yes |
|
| 42 |
+
| `AUTH_BASE_URL` | Auth service URL | Yes |
|
| 43 |
+
| `TEST_EMAIL` | Test user email | Yes |
|
| 44 |
+
| `TEST_PASSWORD` | Test user password | Yes |
|
| 45 |
+
| `TEST_NAME` | Test user name | Yes |
|
| 46 |
+
|
| 47 |
+
## CI/CD
|
| 48 |
+
|
| 49 |
+
Tests run automatically on:
|
| 50 |
+
- Push to `main` or `develop`
|
| 51 |
+
- Pull requests to `main`
|
| 52 |
+
- Daily at 6 AM UTC
|
| 53 |
+
|
| 54 |
+
## Notes
|
| 55 |
+
|
| 56 |
+
- AI Agent endpoints have daily quotas (20 req/day for chat/analyze)
|
| 57 |
+
- CSV uploads limited to 100 rows
|
| 58 |
+
- Some tests may be slow due to async processing
|
| 59 |
+
- Use `-m "not slow"` to skip slow tests
|
| 60 |
+
|
| 61 |
+
## Project Structure
|
| 62 |
+
|
| 63 |
+
```
|
| 64 |
+
ailixir-api-tests/
|
| 65 |
+
├── .github/workflows/api-tests.yml
|
| 66 |
+
├── tests/
|
| 67 |
+
│ ├── conftest.py
|
| 68 |
+
│ ├── test_auth.py
|
| 69 |
+
│ ├── test_ai_agent.py
|
| 70 |
+
│ ├── test_chemical_search.py
|
| 71 |
+
│ ├── test_user_management.py
|
| 72 |
+
│ ├── test_awards_scientists.py
|
| 73 |
+
│ └── test_news.py
|
| 74 |
+
├── requirements.txt
|
| 75 |
+
├── pytest.ini
|
| 76 |
+
├── .env.example
|
| 77 |
+
└── README.md
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
## Running Tests
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
# All tests
|
| 84 |
+
pytest
|
| 85 |
+
|
| 86 |
+
# Specific module
|
| 87 |
+
pytest tests/test_auth.py
|
| 88 |
+
|
| 89 |
+
# With verbose output
|
| 90 |
+
pytest -v
|
| 91 |
+
|
| 92 |
+
# Skip slow tests
|
| 93 |
+
pytest -m "not slow"
|
| 94 |
+
|
| 95 |
+
# Specific test class
|
| 96 |
+
pytest tests/test_auth.py::TestLogin
|
| 97 |
+
|
| 98 |
+
# Specific test method
|
| 99 |
+
pytest tests/test_auth.py::TestLogin::test_login_success
|
| 100 |
+
|
| 101 |
+
# With HTML report
|
| 102 |
+
pytest --html=report.html
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
---
|
| 106 |
|
| 107 |
+
*Generated from AILIXIR API Reference v2.1*
|
_ide_helper.php
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
_ide_helper_models.php
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
// @formatter:off
|
| 4 |
+
// phpcs:ignoreFile
|
| 5 |
+
/**
|
| 6 |
+
* A helper file for your Eloquent Models
|
| 7 |
+
* Copy the phpDocs from this file to the correct Model,
|
| 8 |
+
* And remove them from this file, to prevent double declarations.
|
| 9 |
+
*
|
| 10 |
+
* @author Barry vd. Heuvel <barryvdh@gmail.com>
|
| 11 |
+
*/
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
namespace App\Models{
|
| 15 |
+
/**
|
| 16 |
+
* @property int $id
|
| 17 |
+
* @property string $smiles
|
| 18 |
+
* @property int $user_id
|
| 19 |
+
* @property float|null $absorption
|
| 20 |
+
* @property float|null $distribution
|
| 21 |
+
* @property float|null $metabolism
|
| 22 |
+
* @property float|null $excretion
|
| 23 |
+
* @property float|null $toxicity
|
| 24 |
+
* @property \Illuminate\Support\Carbon $created_at
|
| 25 |
+
* @property \Illuminate\Support\Carbon $updated_at
|
| 26 |
+
* @property-read \App\Models\User $user
|
| 27 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet newModelQuery()
|
| 28 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet newQuery()
|
| 29 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet query()
|
| 30 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereAbsorption($value)
|
| 31 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereCreatedAt($value)
|
| 32 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereDistribution($value)
|
| 33 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereExcretion($value)
|
| 34 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereId($value)
|
| 35 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereMetabolism($value)
|
| 36 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereSmiles($value)
|
| 37 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereToxicity($value)
|
| 38 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereUpdatedAt($value)
|
| 39 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admet whereUserId($value)
|
| 40 |
+
*/
|
| 41 |
+
class Admet extends \Eloquent {}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
namespace App\Models{
|
| 45 |
+
/**
|
| 46 |
+
* @property int $id
|
| 47 |
+
* @property int $user_id
|
| 48 |
+
* @property string $job_id
|
| 49 |
+
* @property string $status
|
| 50 |
+
* @property string $preset
|
| 51 |
+
* @property int $num_molecules
|
| 52 |
+
* @property int $return_top_k
|
| 53 |
+
* @property string $docking_mode
|
| 54 |
+
* @property int $dock_top_k
|
| 55 |
+
* @property array<array-key, mixed>|null $summary
|
| 56 |
+
* @property array<array-key, mixed>|null $files
|
| 57 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 58 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 59 |
+
* @property array<array-key, mixed>|null $ligands
|
| 60 |
+
* @property-read \App\Models\User $user
|
| 61 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob completed()
|
| 62 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob failed()
|
| 63 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob newModelQuery()
|
| 64 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob newQuery()
|
| 65 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob query()
|
| 66 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob running()
|
| 67 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereCreatedAt($value)
|
| 68 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereDockTopK($value)
|
| 69 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereDockingMode($value)
|
| 70 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereFiles($value)
|
| 71 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereId($value)
|
| 72 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereJobId($value)
|
| 73 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereLigands($value)
|
| 74 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereNumMolecules($value)
|
| 75 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob wherePreset($value)
|
| 76 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereReturnTopK($value)
|
| 77 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereStatus($value)
|
| 78 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereSummary($value)
|
| 79 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereUpdatedAt($value)
|
| 80 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|AiJob whereUserId($value)
|
| 81 |
+
*/
|
| 82 |
+
class AiJob extends \Eloquent {}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
namespace App\Models{
|
| 86 |
+
/**
|
| 87 |
+
* @property int $id
|
| 88 |
+
* @property string $name
|
| 89 |
+
* @property string|null $category
|
| 90 |
+
* @property array<array-key, mixed>|null $images
|
| 91 |
+
* @property string|null $description
|
| 92 |
+
* @property string|null $notable_winners
|
| 93 |
+
* @property string|null $country
|
| 94 |
+
* @property int|null $year_started
|
| 95 |
+
* @property string|null $website
|
| 96 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 97 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 98 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Scientist> $scientists
|
| 99 |
+
* @property-read int|null $scientists_count
|
| 100 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award newModelQuery()
|
| 101 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award newQuery()
|
| 102 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award query()
|
| 103 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereCategory($value)
|
| 104 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereCountry($value)
|
| 105 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereCreatedAt($value)
|
| 106 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereDescription($value)
|
| 107 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereId($value)
|
| 108 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereImages($value)
|
| 109 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereName($value)
|
| 110 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereNotableWinners($value)
|
| 111 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereUpdatedAt($value)
|
| 112 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereWebsite($value)
|
| 113 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Award whereYearStarted($value)
|
| 114 |
+
*/
|
| 115 |
+
class Award extends \Eloquent {}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
namespace App\Models{
|
| 119 |
+
/**
|
| 120 |
+
* @property int $id
|
| 121 |
+
* @property int $job_id
|
| 122 |
+
* @property int $rank
|
| 123 |
+
* @property string $smiles
|
| 124 |
+
* @property string|null $name
|
| 125 |
+
* @property string|null $cid
|
| 126 |
+
* @property float|null $similarity
|
| 127 |
+
* @property string|null $explanation
|
| 128 |
+
* @property string|null $image_url
|
| 129 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 130 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 131 |
+
* @property-read \App\Models\ChemicalSearchJob $job
|
| 132 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound newModelQuery()
|
| 133 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound newQuery()
|
| 134 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound query()
|
| 135 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereCid($value)
|
| 136 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereCreatedAt($value)
|
| 137 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereExplanation($value)
|
| 138 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereId($value)
|
| 139 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereImageUrl($value)
|
| 140 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereJobId($value)
|
| 141 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereName($value)
|
| 142 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereRank($value)
|
| 143 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereSimilarity($value)
|
| 144 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereSmiles($value)
|
| 145 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalCompound whereUpdatedAt($value)
|
| 146 |
+
*/
|
| 147 |
+
class ChemicalCompound extends \Eloquent {}
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
namespace App\Models{
|
| 151 |
+
/**
|
| 152 |
+
* @property int $id
|
| 153 |
+
* @property int $user_id
|
| 154 |
+
* @property string $query_smiles
|
| 155 |
+
* @property int $top_k
|
| 156 |
+
* @property string $status
|
| 157 |
+
* @property array<array-key, mixed>|null $results
|
| 158 |
+
* @property array<array-key, mixed>|null $reason
|
| 159 |
+
* @property array<array-key, mixed>|null $image_urls
|
| 160 |
+
* @property array<array-key, mixed>|null $metadata
|
| 161 |
+
* @property string|null $error_message
|
| 162 |
+
* @property \Illuminate\Support\Carbon|null $started_at
|
| 163 |
+
* @property \Illuminate\Support\Carbon|null $completed_at
|
| 164 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 165 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 166 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ChemicalCompound> $compounds
|
| 167 |
+
* @property-read int|null $compounds_count
|
| 168 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob newModelQuery()
|
| 169 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob newQuery()
|
| 170 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob query()
|
| 171 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereCompletedAt($value)
|
| 172 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereCreatedAt($value)
|
| 173 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereErrorMessage($value)
|
| 174 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereId($value)
|
| 175 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereImageUrls($value)
|
| 176 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereMetadata($value)
|
| 177 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereQuerySmiles($value)
|
| 178 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereReason($value)
|
| 179 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereResults($value)
|
| 180 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereStartedAt($value)
|
| 181 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereStatus($value)
|
| 182 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereTopK($value)
|
| 183 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereUpdatedAt($value)
|
| 184 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemicalSearchJob whereUserId($value)
|
| 185 |
+
*/
|
| 186 |
+
class ChemicalSearchJob extends \Eloquent {}
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
namespace App\Models{
|
| 190 |
+
/**
|
| 191 |
+
* @property int $id
|
| 192 |
+
* @property int $user_id
|
| 193 |
+
* @property int|null $chemistry_thread_id
|
| 194 |
+
* @property string $type
|
| 195 |
+
* @property string $input_data
|
| 196 |
+
* @property string|null $response
|
| 197 |
+
* @property array<array-key, mixed>|null $properties
|
| 198 |
+
* @property array<array-key, mixed>|null $drug_likeness
|
| 199 |
+
* @property array<array-key, mixed>|null $admet
|
| 200 |
+
* @property int|null $processing_time_ms
|
| 201 |
+
* @property string $status
|
| 202 |
+
* @property string|null $error_message
|
| 203 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 204 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 205 |
+
* @property-read \App\Models\ChemistryThread|null $thread
|
| 206 |
+
* @property-read \App\Models\User $user
|
| 207 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis newModelQuery()
|
| 208 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis newQuery()
|
| 209 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis query()
|
| 210 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereAdmet($value)
|
| 211 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereChemistryThreadId($value)
|
| 212 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereCreatedAt($value)
|
| 213 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereDrugLikeness($value)
|
| 214 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereErrorMessage($value)
|
| 215 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereId($value)
|
| 216 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereInputData($value)
|
| 217 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereProcessingTimeMs($value)
|
| 218 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereProperties($value)
|
| 219 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereResponse($value)
|
| 220 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereStatus($value)
|
| 221 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereType($value)
|
| 222 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereUpdatedAt($value)
|
| 223 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryAnalysis whereUserId($value)
|
| 224 |
+
*/
|
| 225 |
+
class ChemistryAnalysis extends \Eloquent {}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
namespace App\Models{
|
| 229 |
+
/**
|
| 230 |
+
* @property int $id
|
| 231 |
+
* @property int $user_id
|
| 232 |
+
* @property string $job_id
|
| 233 |
+
* @property string $filename
|
| 234 |
+
* @property string $analysis_type
|
| 235 |
+
* @property int $total_rows
|
| 236 |
+
* @property int $completed_rows
|
| 237 |
+
* @property int $failed_rows
|
| 238 |
+
* @property int $progress_percent
|
| 239 |
+
* @property string $status
|
| 240 |
+
* @property string|null $result_file_path
|
| 241 |
+
* @property string|null $error_message
|
| 242 |
+
* @property \Illuminate\Support\Carbon|null $started_at
|
| 243 |
+
* @property \Illuminate\Support\Carbon|null $completed_at
|
| 244 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 245 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 246 |
+
* @property-read \App\Models\User $user
|
| 247 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob newModelQuery()
|
| 248 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob newQuery()
|
| 249 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob query()
|
| 250 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereAnalysisType($value)
|
| 251 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereCompletedAt($value)
|
| 252 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereCompletedRows($value)
|
| 253 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereCreatedAt($value)
|
| 254 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereErrorMessage($value)
|
| 255 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereFailedRows($value)
|
| 256 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereFilename($value)
|
| 257 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereId($value)
|
| 258 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereJobId($value)
|
| 259 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereProgressPercent($value)
|
| 260 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereResultFilePath($value)
|
| 261 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereStartedAt($value)
|
| 262 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereStatus($value)
|
| 263 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereTotalRows($value)
|
| 264 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereUpdatedAt($value)
|
| 265 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryCsvJob whereUserId($value)
|
| 266 |
+
*/
|
| 267 |
+
class ChemistryCsvJob extends \Eloquent {}
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
namespace App\Models{
|
| 271 |
+
/**
|
| 272 |
+
* @property int $id
|
| 273 |
+
* @property int $user_id
|
| 274 |
+
* @property string $thread_id
|
| 275 |
+
* @property string|null $title
|
| 276 |
+
* @property string $status
|
| 277 |
+
* @property \Illuminate\Support\Carbon|null $last_used_at
|
| 278 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 279 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 280 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ChemistryAnalysis> $analyses
|
| 281 |
+
* @property-read int|null $analyses_count
|
| 282 |
+
* @property-read \App\Models\User $user
|
| 283 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread newModelQuery()
|
| 284 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread newQuery()
|
| 285 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread query()
|
| 286 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread whereCreatedAt($value)
|
| 287 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread whereId($value)
|
| 288 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread whereLastUsedAt($value)
|
| 289 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread whereStatus($value)
|
| 290 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread whereThreadId($value)
|
| 291 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread whereTitle($value)
|
| 292 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread whereUpdatedAt($value)
|
| 293 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ChemistryThread whereUserId($value)
|
| 294 |
+
*/
|
| 295 |
+
class ChemistryThread extends \Eloquent {}
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
namespace App\Models{
|
| 299 |
+
/**
|
| 300 |
+
* @property int $id
|
| 301 |
+
* @property int $user_id
|
| 302 |
+
* @property string $input_type
|
| 303 |
+
* @property string|null $smiles
|
| 304 |
+
* @property string|null $protein_name
|
| 305 |
+
* @property string|null $ligand_name
|
| 306 |
+
* @property string $protein_path
|
| 307 |
+
* @property string $ligand_path
|
| 308 |
+
* @property string $status
|
| 309 |
+
* @property array<array-key, mixed>|null $result_data
|
| 310 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 311 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 312 |
+
* @property-read \App\Models\User $user
|
| 313 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob newModelQuery()
|
| 314 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob newQuery()
|
| 315 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob query()
|
| 316 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereCreatedAt($value)
|
| 317 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereId($value)
|
| 318 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereInputType($value)
|
| 319 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereLigandName($value)
|
| 320 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereLigandPath($value)
|
| 321 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereProteinName($value)
|
| 322 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereProteinPath($value)
|
| 323 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereResultData($value)
|
| 324 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereSmiles($value)
|
| 325 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereStatus($value)
|
| 326 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereUpdatedAt($value)
|
| 327 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|DockingJob whereUserId($value)
|
| 328 |
+
*/
|
| 329 |
+
class DockingJob extends \Eloquent {}
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
namespace App\Models{
|
| 333 |
+
/**
|
| 334 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|LigandExport newModelQuery()
|
| 335 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|LigandExport newQuery()
|
| 336 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|LigandExport query()
|
| 337 |
+
*/
|
| 338 |
+
class LigandExport extends \Eloquent {}
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
namespace App\Models{
|
| 342 |
+
/**
|
| 343 |
+
* @property int $id
|
| 344 |
+
* @property string $experiment_name
|
| 345 |
+
* @property string|null $description
|
| 346 |
+
* @property string|null $SYS_pdb
|
| 347 |
+
* @property string|null $SYS_nw_pdb
|
| 348 |
+
* @property string|null $ligand_H_pdb
|
| 349 |
+
* @property string|null $ligand_gaff_pdb
|
| 350 |
+
* @property string|null $ligand_h_1_pdb
|
| 351 |
+
* @property string|null $ligand_h_nonprot_pdb
|
| 352 |
+
* @property string|null $protein_ligand_pdb
|
| 353 |
+
* @property string|null $prot_lig_equil_pdb
|
| 354 |
+
* @property string|null $starting_end_pdb
|
| 355 |
+
* @property string|null $prot_lig_equil_dcd
|
| 356 |
+
* @property string|null $prot_lig_prod1_1_nw_dcd
|
| 357 |
+
* @property string|null $SYS_gaff2_crd
|
| 358 |
+
* @property string|null $SYS_nw_crd
|
| 359 |
+
* @property string|null $SYS_gaff2_prmtop
|
| 360 |
+
* @property string|null $SYS_nw_prmtop
|
| 361 |
+
* @property string|null $prot_lig_equil_rst
|
| 362 |
+
* @property string|null $prepareforleap_in
|
| 363 |
+
* @property string|null $tleap_in
|
| 364 |
+
* @property string|null $lig_lib
|
| 365 |
+
* @property string|null $ligand_frcmod
|
| 366 |
+
* @property string|null $ligand_mol2
|
| 367 |
+
* @property string|null $ligand_h_renum_txt
|
| 368 |
+
* @property string|null $ligand_h_sslink
|
| 369 |
+
* @property string|null $prot_lig_equil_log
|
| 370 |
+
* @property string|null $_2D_rmsd_csv
|
| 371 |
+
* @property string|null $Interaction_energy_eelec_csv
|
| 372 |
+
* @property string|null $Interaction_energy_evdw_csv
|
| 373 |
+
* @property string|null $PC1_csv
|
| 374 |
+
* @property string|null $PC2_csv
|
| 375 |
+
* @property string|null $cross_correlation_csv
|
| 376 |
+
* @property string|null $distance_csv
|
| 377 |
+
* @property string|null $distance_select_csv
|
| 378 |
+
* @property string|null $radius_gyration_csv
|
| 379 |
+
* @property string|null $rmsd_ca_csv
|
| 380 |
+
* @property string|null $rmsf_ca_csv
|
| 381 |
+
* @property string|null $_2D_rmsd_png
|
| 382 |
+
* @property string|null $Interaction_energy_png
|
| 383 |
+
* @property string|null $PCA_png
|
| 384 |
+
* @property string|null $PCA_dist_png
|
| 385 |
+
* @property string|null $cross_correlation_png
|
| 386 |
+
* @property string|null $distance_png
|
| 387 |
+
* @property string|null $distance_select_png
|
| 388 |
+
* @property string|null $radius_gyration_png
|
| 389 |
+
* @property string|null $radius_gyration_dist_png
|
| 390 |
+
* @property string|null $rmsd_ca_png
|
| 391 |
+
* @property string|null $rmsd_dist_png
|
| 392 |
+
* @property string|null $rmsf_ca_png
|
| 393 |
+
* @property string|null $Interaction_html
|
| 394 |
+
* @property string|null $initial_html
|
| 395 |
+
* @property string|null $FINAL_RESULTS_MMPBSA_dat
|
| 396 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 397 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 398 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile newModelQuery()
|
| 399 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile newQuery()
|
| 400 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile query()
|
| 401 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile where2DRmsdCsv($value)
|
| 402 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile where2DRmsdPng($value)
|
| 403 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereCreatedAt($value)
|
| 404 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereCrossCorrelationCsv($value)
|
| 405 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereCrossCorrelationPng($value)
|
| 406 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereDescription($value)
|
| 407 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereDistanceCsv($value)
|
| 408 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereDistancePng($value)
|
| 409 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereDistanceSelectCsv($value)
|
| 410 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereDistanceSelectPng($value)
|
| 411 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereExperimentName($value)
|
| 412 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereFINALRESULTSMMPBSADat($value)
|
| 413 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereId($value)
|
| 414 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereInitialHtml($value)
|
| 415 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereInteractionEnergyEelecCsv($value)
|
| 416 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereInteractionEnergyEvdwCsv($value)
|
| 417 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereInteractionEnergyPng($value)
|
| 418 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereInteractionHtml($value)
|
| 419 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigLib($value)
|
| 420 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigandFrcmod($value)
|
| 421 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigandGaffPdb($value)
|
| 422 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigandH1Pdb($value)
|
| 423 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigandHNonprotPdb($value)
|
| 424 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigandHPdb($value)
|
| 425 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigandHRenumTxt($value)
|
| 426 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigandHSslink($value)
|
| 427 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereLigandMol2($value)
|
| 428 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile wherePC1Csv($value)
|
| 429 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile wherePC2Csv($value)
|
| 430 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile wherePCADistPng($value)
|
| 431 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile wherePCAPng($value)
|
| 432 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile wherePrepareforleapIn($value)
|
| 433 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereProtLigEquilDcd($value)
|
| 434 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereProtLigEquilLog($value)
|
| 435 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereProtLigEquilPdb($value)
|
| 436 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereProtLigEquilRst($value)
|
| 437 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereProtLigProd11NwDcd($value)
|
| 438 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereProteinLigandPdb($value)
|
| 439 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereRadiusGyrationCsv($value)
|
| 440 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereRadiusGyrationDistPng($value)
|
| 441 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereRadiusGyrationPng($value)
|
| 442 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereRmsdCaCsv($value)
|
| 443 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereRmsdCaPng($value)
|
| 444 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereRmsdDistPng($value)
|
| 445 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereRmsfCaCsv($value)
|
| 446 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereRmsfCaPng($value)
|
| 447 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereSYSGaff2Crd($value)
|
| 448 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereSYSGaff2Prmtop($value)
|
| 449 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereSYSNwCrd($value)
|
| 450 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereSYSNwPdb($value)
|
| 451 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereSYSNwPrmtop($value)
|
| 452 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereSYSPdb($value)
|
| 453 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereStartingEndPdb($value)
|
| 454 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereTleapIn($value)
|
| 455 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|MdFile whereUpdatedAt($value)
|
| 456 |
+
*/
|
| 457 |
+
class MdFile extends \Eloquent {}
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
namespace App\Models{
|
| 461 |
+
/**
|
| 462 |
+
* @property int $id
|
| 463 |
+
* @property string $title
|
| 464 |
+
* @property string|null $summary
|
| 465 |
+
* @property string $source
|
| 466 |
+
* @property string $url
|
| 467 |
+
* @property \Illuminate\Support\Carbon|null $published_at
|
| 468 |
+
* @property string|null $category
|
| 469 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 470 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 471 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News newModelQuery()
|
| 472 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News newQuery()
|
| 473 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News query()
|
| 474 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News whereCategory($value)
|
| 475 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News whereCreatedAt($value)
|
| 476 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News whereId($value)
|
| 477 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News wherePublishedAt($value)
|
| 478 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News whereSource($value)
|
| 479 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News whereSummary($value)
|
| 480 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News whereTitle($value)
|
| 481 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News whereUpdatedAt($value)
|
| 482 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|News whereUrl($value)
|
| 483 |
+
*/
|
| 484 |
+
class News extends \Eloquent {}
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
namespace App\Models{
|
| 488 |
+
/**
|
| 489 |
+
* @property int $id
|
| 490 |
+
* @property int $user_id
|
| 491 |
+
* @property string|null $specialization
|
| 492 |
+
* @property string|null $university
|
| 493 |
+
* @property int|null $years_of_experience
|
| 494 |
+
* @property string|null $bio
|
| 495 |
+
* @property string|null $photo
|
| 496 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 497 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 498 |
+
* @property-read \App\Models\User $user
|
| 499 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher newModelQuery()
|
| 500 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher newQuery()
|
| 501 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher query()
|
| 502 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher whereBio($value)
|
| 503 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher whereCreatedAt($value)
|
| 504 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher whereId($value)
|
| 505 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher wherePhoto($value)
|
| 506 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher whereSpecialization($value)
|
| 507 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher whereUniversity($value)
|
| 508 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher whereUpdatedAt($value)
|
| 509 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher whereUserId($value)
|
| 510 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Researcher whereYearsOfExperience($value)
|
| 511 |
+
*/
|
| 512 |
+
class Researcher extends \Eloquent {}
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
namespace App\Models{
|
| 516 |
+
/**
|
| 517 |
+
* @property int $id
|
| 518 |
+
* @property int $user_id
|
| 519 |
+
* @property int $news_id
|
| 520 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 521 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 522 |
+
* @property-read \App\Models\News $news
|
| 523 |
+
* @property-read \App\Models\User $user
|
| 524 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|SavedArticle newModelQuery()
|
| 525 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|SavedArticle newQuery()
|
| 526 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|SavedArticle query()
|
| 527 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|SavedArticle whereCreatedAt($value)
|
| 528 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|SavedArticle whereId($value)
|
| 529 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|SavedArticle whereNewsId($value)
|
| 530 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|SavedArticle whereUpdatedAt($value)
|
| 531 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|SavedArticle whereUserId($value)
|
| 532 |
+
*/
|
| 533 |
+
class SavedArticle extends \Eloquent {}
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
namespace App\Models{
|
| 537 |
+
/**
|
| 538 |
+
* @property int $id
|
| 539 |
+
* @property string $name
|
| 540 |
+
* @property string|null $nationality
|
| 541 |
+
* @property int|null $birth_year
|
| 542 |
+
* @property int|null $death_year
|
| 543 |
+
* @property array<array-key, mixed>|null $images
|
| 544 |
+
* @property string $bio
|
| 545 |
+
* @property string|null $impact
|
| 546 |
+
* @property string|null $field
|
| 547 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 548 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 549 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Award> $awards
|
| 550 |
+
* @property-read int|null $awards_count
|
| 551 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist newModelQuery()
|
| 552 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist newQuery()
|
| 553 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist query()
|
| 554 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereBio($value)
|
| 555 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereBirthYear($value)
|
| 556 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereCreatedAt($value)
|
| 557 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereDeathYear($value)
|
| 558 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereField($value)
|
| 559 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereId($value)
|
| 560 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereImages($value)
|
| 561 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereImpact($value)
|
| 562 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereName($value)
|
| 563 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereNationality($value)
|
| 564 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Scientist whereUpdatedAt($value)
|
| 565 |
+
*/
|
| 566 |
+
class Scientist extends \Eloquent {}
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
namespace App\Models{
|
| 570 |
+
/**
|
| 571 |
+
* @property int $id
|
| 572 |
+
* @property int $user_id
|
| 573 |
+
* @property string $status
|
| 574 |
+
* @property array<array-key, mixed> $input
|
| 575 |
+
* @property array<array-key, mixed>|null $output
|
| 576 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 577 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 578 |
+
* @property-read \App\Models\User $user
|
| 579 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult newModelQuery()
|
| 580 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult newQuery()
|
| 581 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult query()
|
| 582 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult whereCreatedAt($value)
|
| 583 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult whereId($value)
|
| 584 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult whereInput($value)
|
| 585 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult whereOutput($value)
|
| 586 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult whereStatus($value)
|
| 587 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult whereUpdatedAt($value)
|
| 588 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|ScreeningResult whereUserId($value)
|
| 589 |
+
*/
|
| 590 |
+
class ScreeningResult extends \Eloquent {}
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
namespace App\Models{
|
| 594 |
+
/**
|
| 595 |
+
* @property int $id
|
| 596 |
+
* @property int $user_id
|
| 597 |
+
* @property string $protein
|
| 598 |
+
* @property string|null $ligand
|
| 599 |
+
* @property string $status
|
| 600 |
+
* @property int $progress
|
| 601 |
+
* @property string|null $error_message
|
| 602 |
+
* @property string|null $trajectory
|
| 603 |
+
* @property string|null $log_file
|
| 604 |
+
* @property array<array-key, mixed>|null $analysis
|
| 605 |
+
* @property string $force_field
|
| 606 |
+
* @property float $temperature
|
| 607 |
+
* @property float $simulation_time_ns
|
| 608 |
+
* @property int $box_size
|
| 609 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 610 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 611 |
+
* @property-read \App\Models\User $user
|
| 612 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation newModelQuery()
|
| 613 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation newQuery()
|
| 614 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation query()
|
| 615 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereAnalysis($value)
|
| 616 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereBoxSize($value)
|
| 617 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereCreatedAt($value)
|
| 618 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereErrorMessage($value)
|
| 619 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereForceField($value)
|
| 620 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereId($value)
|
| 621 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereLigand($value)
|
| 622 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereLogFile($value)
|
| 623 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereProgress($value)
|
| 624 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereProtein($value)
|
| 625 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereSimulationTimeNs($value)
|
| 626 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereStatus($value)
|
| 627 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereTemperature($value)
|
| 628 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereTrajectory($value)
|
| 629 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereUpdatedAt($value)
|
| 630 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|Simulation whereUserId($value)
|
| 631 |
+
*/
|
| 632 |
+
class Simulation extends \Eloquent {}
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
namespace App\Models{
|
| 636 |
+
/**
|
| 637 |
+
* @property int $id
|
| 638 |
+
* @property int $user_id
|
| 639 |
+
* @property string $status
|
| 640 |
+
* @property array<array-key, mixed> $input
|
| 641 |
+
* @property array<array-key, mixed>|null $output
|
| 642 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 643 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 644 |
+
* @property-read \App\Models\User $user
|
| 645 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup newModelQuery()
|
| 646 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup newQuery()
|
| 647 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup query()
|
| 648 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup whereCreatedAt($value)
|
| 649 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup whereId($value)
|
| 650 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup whereInput($value)
|
| 651 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup whereOutput($value)
|
| 652 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup whereStatus($value)
|
| 653 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup whereUpdatedAt($value)
|
| 654 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|TargetLookup whereUserId($value)
|
| 655 |
+
*/
|
| 656 |
+
class TargetLookup extends \Eloquent {}
|
| 657 |
+
}
|
| 658 |
+
|
| 659 |
+
namespace App\Models{
|
| 660 |
+
/**
|
| 661 |
+
* @property int $id
|
| 662 |
+
* @property string $name
|
| 663 |
+
* @property string $email
|
| 664 |
+
* @property \Illuminate\Support\Carbon|null $email_verified_at
|
| 665 |
+
* @property \Illuminate\Support\Carbon|null $last_otp_sent_at
|
| 666 |
+
* @property string $password
|
| 667 |
+
* @property string $role
|
| 668 |
+
* @property string|null $remember_token
|
| 669 |
+
* @property \Illuminate\Support\Carbon|null $created_at
|
| 670 |
+
* @property \Illuminate\Support\Carbon|null $updated_at
|
| 671 |
+
* @property string|null $email_verification_otp
|
| 672 |
+
* @property \Illuminate\Support\Carbon|null $email_verification_otp_expires_at
|
| 673 |
+
* @property bool $is_verified
|
| 674 |
+
* @property string|null $password_reset_otp
|
| 675 |
+
* @property \Illuminate\Support\Carbon|null $password_reset_otp_expires_at
|
| 676 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Admet> $admets
|
| 677 |
+
* @property-read int|null $admets_count
|
| 678 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\AiJob> $aiJobs
|
| 679 |
+
* @property-read int|null $ai_jobs_count
|
| 680 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ChemistryAnalysis> $chemistryAnalyses
|
| 681 |
+
* @property-read int|null $chemistry_analyses_count
|
| 682 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ChemistryCsvJob> $chemistryCsvJobs
|
| 683 |
+
* @property-read int|null $chemistry_csv_jobs_count
|
| 684 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ChemistryThread> $chemistryThreads
|
| 685 |
+
* @property-read int|null $chemistry_threads_count
|
| 686 |
+
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
| 687 |
+
* @property-read int|null $notifications_count
|
| 688 |
+
* @property-read \App\Models\Researcher|null $researcher
|
| 689 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Simulation> $simulations
|
| 690 |
+
* @property-read int|null $simulations_count
|
| 691 |
+
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Laravel\Sanctum\PersonalAccessToken> $tokens
|
| 692 |
+
* @property-read int|null $tokens_count
|
| 693 |
+
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
|
| 694 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
|
| 695 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
|
| 696 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User query()
|
| 697 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereCreatedAt($value)
|
| 698 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmail($value)
|
| 699 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerificationOtp($value)
|
| 700 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerificationOtpExpiresAt($value)
|
| 701 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerifiedAt($value)
|
| 702 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereId($value)
|
| 703 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereIsVerified($value)
|
| 704 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereLastOtpSentAt($value)
|
| 705 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereName($value)
|
| 706 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePassword($value)
|
| 707 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePasswordResetOtp($value)
|
| 708 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePasswordResetOtpExpiresAt($value)
|
| 709 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRememberToken($value)
|
| 710 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRole($value)
|
| 711 |
+
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
|
| 712 |
+
*/
|
| 713 |
+
class User extends \Eloquent {}
|
| 714 |
+
}
|
| 715 |
+
|
app/Helpers/OtpHelper.php
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Helpers;
|
| 4 |
+
|
| 5 |
+
use App\Models\User;
|
| 6 |
+
use App\Services\OtpService;
|
| 7 |
+
|
| 8 |
+
class OtpHelper
|
| 9 |
+
{
|
| 10 |
+
/**
|
| 11 |
+
* Send OTP by type for a user email
|
| 12 |
+
*
|
| 13 |
+
* @param string $email
|
| 14 |
+
* @param string $type ('email_verification' or 'password_reset')
|
| 15 |
+
* @param OtpService $otpService
|
| 16 |
+
* @return array
|
| 17 |
+
*/
|
| 18 |
+
public static function sendOtpByType(string $email, string $type, OtpService $otpService): array
|
| 19 |
+
{
|
| 20 |
+
$user = User::where('email', $email)->firstOrFail();
|
| 21 |
+
|
| 22 |
+
$otpService->resendOtp($user, $type);
|
| 23 |
+
|
| 24 |
+
return [
|
| 25 |
+
'email' => $user->email
|
| 26 |
+
];
|
| 27 |
+
}
|
| 28 |
+
}
|
app/Http/Controllers/Api/AdmetController.php
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use Illuminate\Http\Request;
|
| 6 |
+
use App\Services\AdmetService;
|
| 7 |
+
|
| 8 |
+
class AdmetController extends BaseController
|
| 9 |
+
{
|
| 10 |
+
public function predict(Request $request, AdmetService $admetService)
|
| 11 |
+
{
|
| 12 |
+
try {
|
| 13 |
+
if ($request->hasFile('file')) {
|
| 14 |
+
$file = $request->file('file');
|
| 15 |
+
$extension = $file->getClientOriginalExtension();
|
| 16 |
+
$content = file_get_contents($file->getRealPath());
|
| 17 |
+
$result = $admetService->predictFromFile($content, $extension);
|
| 18 |
+
return $this->successResponse('ADMET predictions generated successfully', $result);
|
| 19 |
+
} elseif ($request->has('smiles') && !empty($request->input('smiles'))) {
|
| 20 |
+
$result = $admetService->predictBatchFromString($request->input('smiles'));
|
| 21 |
+
return $this->successResponse('ADMET predictions generated successfully', $result);
|
| 22 |
+
} else {
|
| 23 |
+
return $this->errorResponse('Please provide either a file or SMILES string', 400);
|
| 24 |
+
}
|
| 25 |
+
} catch (\Exception $e) {
|
| 26 |
+
return $this->errorResponse('Failed to generate ADMET predictions: ' . $e->getMessage(), 500);
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
}
|
app/Http/Controllers/Api/AiServicesIntegrationController.php
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use Illuminate\Http\JsonResponse;
|
| 6 |
+
use Illuminate\Http\Request;
|
| 7 |
+
use Illuminate\Routing\Controller;
|
| 8 |
+
use Illuminate\Support\Facades\Http;
|
| 9 |
+
|
| 10 |
+
class AiServicesIntegrationController extends Controller
|
| 11 |
+
{
|
| 12 |
+
public function health(): JsonResponse
|
| 13 |
+
{
|
| 14 |
+
$checks = [
|
| 15 |
+
'admet' => config('services.admet.url'),
|
| 16 |
+
'chemical_rag' => config('services.chemical_ai.url'),
|
| 17 |
+
'drug_repurposing' => config('services.drug_repurposing.url'),
|
| 18 |
+
];
|
| 19 |
+
|
| 20 |
+
$results = [];
|
| 21 |
+
|
| 22 |
+
foreach ($checks as $name => $baseUrl) {
|
| 23 |
+
$results[$name] = $this->probeHealth($baseUrl);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
$allHealthy = collect($results)->every(
|
| 27 |
+
fn (array $result) => ($result['status'] ?? '') === 'healthy'
|
| 28 |
+
);
|
| 29 |
+
|
| 30 |
+
return response()->json([
|
| 31 |
+
'success' => $allHealthy,
|
| 32 |
+
'services' => $results,
|
| 33 |
+
], $allHealthy ? 200 : 503);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
public function testAdmet(Request $request): JsonResponse
|
| 37 |
+
{
|
| 38 |
+
$baseUrl = rtrim((string) config('services.admet.url'), '/');
|
| 39 |
+
$smiles = (string) $request->input('smiles', 'c1ccccc1');
|
| 40 |
+
|
| 41 |
+
$response = Http::timeout(180)->post("{$baseUrl}/predict", [
|
| 42 |
+
'smiles' => $smiles,
|
| 43 |
+
]);
|
| 44 |
+
|
| 45 |
+
return $this->proxyResponse('admet', $response);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
public function testChemicalSearch(Request $request): JsonResponse
|
| 49 |
+
{
|
| 50 |
+
$baseUrl = rtrim((string) config('services.chemical_ai.url'), '/');
|
| 51 |
+
$smiles = (string) $request->input('smiles', 'CCO');
|
| 52 |
+
$topK = (int) $request->input('top_k', 3);
|
| 53 |
+
|
| 54 |
+
$response = Http::timeout(180)->post("{$baseUrl}/search/retrieval-only", [
|
| 55 |
+
'smiles' => $smiles,
|
| 56 |
+
'top_k' => $topK,
|
| 57 |
+
]);
|
| 58 |
+
|
| 59 |
+
return $this->proxyResponse('chemical_rag', $response);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
public function testDrugRepurposing(): JsonResponse
|
| 63 |
+
{
|
| 64 |
+
$baseUrl = rtrim((string) config('services.drug_repurposing.url'), '/');
|
| 65 |
+
|
| 66 |
+
$health = Http::timeout(60)->get("{$baseUrl}/health");
|
| 67 |
+
$modelStatus = Http::timeout(60)->get("{$baseUrl}/api/v1/model-status");
|
| 68 |
+
|
| 69 |
+
$success = $health->successful() && $modelStatus->successful();
|
| 70 |
+
|
| 71 |
+
return response()->json([
|
| 72 |
+
'success' => $success,
|
| 73 |
+
'service' => 'drug_repurposing',
|
| 74 |
+
'health' => [
|
| 75 |
+
'status' => $health->status(),
|
| 76 |
+
'body' => $health->json(),
|
| 77 |
+
],
|
| 78 |
+
'model_status' => [
|
| 79 |
+
'status' => $modelStatus->status(),
|
| 80 |
+
'body' => $modelStatus->json(),
|
| 81 |
+
],
|
| 82 |
+
], $success ? 200 : 502);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
private function probeHealth(?string $baseUrl): array
|
| 86 |
+
{
|
| 87 |
+
if (empty($baseUrl)) {
|
| 88 |
+
return [
|
| 89 |
+
'status' => 'misconfigured',
|
| 90 |
+
'error' => 'URL not configured',
|
| 91 |
+
];
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
try {
|
| 95 |
+
$response = Http::timeout(60)->get(rtrim($baseUrl, '/').'/health');
|
| 96 |
+
|
| 97 |
+
return [
|
| 98 |
+
'status' => $response->successful() ? 'healthy' : 'unhealthy',
|
| 99 |
+
'http_status' => $response->status(),
|
| 100 |
+
'body' => $response->json() ?? $response->body(),
|
| 101 |
+
];
|
| 102 |
+
} catch (\Throwable $e) {
|
| 103 |
+
return [
|
| 104 |
+
'status' => 'unreachable',
|
| 105 |
+
'error' => $e->getMessage(),
|
| 106 |
+
];
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
private function proxyResponse(string $service, \Illuminate\Http\Client\Response $response): JsonResponse
|
| 111 |
+
{
|
| 112 |
+
return response()->json([
|
| 113 |
+
'success' => $response->successful(),
|
| 114 |
+
'service' => $service,
|
| 115 |
+
'upstream_status' => $response->status(),
|
| 116 |
+
'data' => $response->json(),
|
| 117 |
+
], $response->successful() ? 200 : 502);
|
| 118 |
+
}
|
| 119 |
+
}
|
app/Http/Controllers/Api/AuthController.php
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Api\BaseController;
|
| 6 |
+
use App\Http\Requests\User\LoginRequest;
|
| 7 |
+
use App\Http\Requests\User\RegisterRequest;
|
| 8 |
+
use App\Http\Requests\User\LoginGoogleRequest;
|
| 9 |
+
use App\Http\Requests\User\CheckEmailRequest;
|
| 10 |
+
use Illuminate\Http\Request;
|
| 11 |
+
use App\Services\UserService;
|
| 12 |
+
use App\Services\OtpService;
|
| 13 |
+
use App\Models\User;
|
| 14 |
+
|
| 15 |
+
class AuthController extends BaseController
|
| 16 |
+
{
|
| 17 |
+
|
| 18 |
+
protected $userService;
|
| 19 |
+
protected $otpService;
|
| 20 |
+
|
| 21 |
+
public function __construct(UserService $userService, OtpService $otpService)
|
| 22 |
+
{
|
| 23 |
+
$this->userService = $userService;
|
| 24 |
+
$this->otpService = $otpService;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
/** ---------------- Register ---------------- */
|
| 28 |
+
|
| 29 |
+
public function register(RegisterRequest $request)
|
| 30 |
+
{
|
| 31 |
+
$user = $this->userService->registerUser($request->validated());
|
| 32 |
+
|
| 33 |
+
$this->otpService->sendOtp($user, 'email_verification');
|
| 34 |
+
|
| 35 |
+
return $this->successResponse(
|
| 36 |
+
'Registered successfully. Please check your email for OTP verification code.',
|
| 37 |
+
['email' => $user->email]
|
| 38 |
+
);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
public function login(LoginRequest $request)
|
| 43 |
+
{
|
| 44 |
+
$result = $this->userService->loginUser($request->validated());
|
| 45 |
+
|
| 46 |
+
if (isset($result['error'])) {
|
| 47 |
+
return $this->errorResponse($result['error'], $result['code'] ?? 400);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
return $this->successResponse('Login successful', [
|
| 51 |
+
'token' => $result['token'],
|
| 52 |
+
'user' => $result['user']
|
| 53 |
+
]);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
/** ---------------- Forgot Password ---------------- */
|
| 57 |
+
|
| 58 |
+
public function forgotPassword(CheckEmailRequest $request)
|
| 59 |
+
{
|
| 60 |
+
$user = User::where('email', $request->email)->first();
|
| 61 |
+
|
| 62 |
+
$this->otpService->sendOtp($user, 'password_reset');
|
| 63 |
+
|
| 64 |
+
return $this->successResponse(
|
| 65 |
+
'Password reset OTP has been sent to your email.',
|
| 66 |
+
['email' => $user->email]
|
| 67 |
+
);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
/** ---------------- Resend OTP ---------------- */
|
| 71 |
+
|
| 72 |
+
public function resendOtp(Request $request)
|
| 73 |
+
{
|
| 74 |
+
$request->validate([
|
| 75 |
+
'email' => 'required|email|exists:users,email',
|
| 76 |
+
'type' => 'required|in:email_verification,password_reset'
|
| 77 |
+
]);
|
| 78 |
+
|
| 79 |
+
$user = User::where('email', $request->email)->first();
|
| 80 |
+
|
| 81 |
+
try {
|
| 82 |
+
$this->otpService->resendOtp($user, $request->type);
|
| 83 |
+
|
| 84 |
+
return $this->successResponse(
|
| 85 |
+
'OTP has been resent to your email.',
|
| 86 |
+
['email' => $user->email]
|
| 87 |
+
);
|
| 88 |
+
} catch (\Illuminate\Validation\ValidationException $e) {
|
| 89 |
+
return $this->errorResponse($e->getMessage(), 429);
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
public function logout(Request $request)
|
| 94 |
+
{
|
| 95 |
+
$request->user()->currentAccessToken()->delete();
|
| 96 |
+
|
| 97 |
+
return $this->successResponse('Logged out successfully');
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/** ---------------- Google OAuth ---------------- */
|
| 101 |
+
|
| 102 |
+
public function handleGoogleCallback(LoginGoogleRequest $request)
|
| 103 |
+
{
|
| 104 |
+
$result = $this->userService->loginGoogleUser(
|
| 105 |
+
$request->validated()['access_token']
|
| 106 |
+
);
|
| 107 |
+
|
| 108 |
+
if (isset($result['error'])) {
|
| 109 |
+
return $this->errorResponse($result['error'], $result['code'] ?? 400);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
return $this->successResponse('Google login successful', [
|
| 113 |
+
'token' => $result['token'],
|
| 114 |
+
'user' => $result['user']
|
| 115 |
+
]);
|
| 116 |
+
}
|
| 117 |
+
}
|
app/Http/Controllers/Api/AwardController.php
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Api\BaseController;
|
| 6 |
+
use App\Http\Resources\ScientistResource;
|
| 7 |
+
use App\Http\Resources\AwardResource;
|
| 8 |
+
use App\Services\AwardService;
|
| 9 |
+
use Illuminate\Http\Request;
|
| 10 |
+
|
| 11 |
+
class AwardController extends BaseController
|
| 12 |
+
{
|
| 13 |
+
protected $service;
|
| 14 |
+
|
| 15 |
+
public function __construct(AwardService $service)
|
| 16 |
+
{
|
| 17 |
+
$this->service = $service;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
public function index(Request $request)
|
| 21 |
+
{
|
| 22 |
+
$perPage = min(100, max(1, (int)$request->get('per_page', 10)));
|
| 23 |
+
|
| 24 |
+
$awards = $this->service->getAwards($perPage);
|
| 25 |
+
|
| 26 |
+
return $this->paginatedResponse(
|
| 27 |
+
'Awards retrieved successfully',
|
| 28 |
+
$awards->through(fn($award) => new AwardResource($award)),
|
| 29 |
+
$awards
|
| 30 |
+
);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
public function show($id)
|
| 34 |
+
{
|
| 35 |
+
$award = $this->service->getAward($id);
|
| 36 |
+
|
| 37 |
+
return $this->successResponse(
|
| 38 |
+
'Award retrieved successfully',
|
| 39 |
+
new AwardResource($award)
|
| 40 |
+
);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
public function getScientistsByAward($id)
|
| 44 |
+
{
|
| 45 |
+
$award = $this->service->getAward($id);
|
| 46 |
+
|
| 47 |
+
return $this->successResponse(
|
| 48 |
+
'Scientists retrieved successfully',
|
| 49 |
+
ScientistResource::collection($award->scientists)
|
| 50 |
+
);
|
| 51 |
+
}
|
| 52 |
+
}
|
app/Http/Controllers/Api/BaseController.php
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
| 6 |
+
use Illuminate\Foundation\Validation\ValidatesRequests;
|
| 7 |
+
use Illuminate\Routing\Controller;
|
| 8 |
+
use App\Traits\ApiResponseTrait;
|
| 9 |
+
|
| 10 |
+
class BaseController extends Controller
|
| 11 |
+
{
|
| 12 |
+
use AuthorizesRequests, ValidatesRequests, ApiResponseTrait;
|
| 13 |
+
}
|
app/Http/Controllers/Api/ChemicalSearchController.php
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Requests\ChemicalSearchRequest;
|
| 6 |
+
use App\Services\ChemicalSearchService;
|
| 7 |
+
use Illuminate\Http\JsonResponse;
|
| 8 |
+
use Illuminate\Support\Facades\Auth;
|
| 9 |
+
|
| 10 |
+
class ChemicalSearchController extends BaseController
|
| 11 |
+
{
|
| 12 |
+
public function __construct(
|
| 13 |
+
private ChemicalSearchService $searchService
|
| 14 |
+
) {}
|
| 15 |
+
|
| 16 |
+
/**
|
| 17 |
+
* POST /api/chemical-search
|
| 18 |
+
* Retrieval Only - Synchronous
|
| 19 |
+
*/
|
| 20 |
+
public function store(ChemicalSearchRequest $request): JsonResponse
|
| 21 |
+
{
|
| 22 |
+
$user = Auth::user();
|
| 23 |
+
|
| 24 |
+
$result = $this->searchService->search(
|
| 25 |
+
smiles: $request->validated('smiles'),
|
| 26 |
+
topK: $request->input('top_k', 5),
|
| 27 |
+
userId: $user->id
|
| 28 |
+
);
|
| 29 |
+
|
| 30 |
+
if (!$result['success']) {
|
| 31 |
+
return response()->json([
|
| 32 |
+
'success' => false,
|
| 33 |
+
'message' => $result['error'] ?? 'Search failed',
|
| 34 |
+
], 500);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
return response()->json([
|
| 38 |
+
'success' => true,
|
| 39 |
+
'query' => [
|
| 40 |
+
'smiles' => $request->validated('smiles'),
|
| 41 |
+
'top_k' => $request->input('top_k', 5),
|
| 42 |
+
],
|
| 43 |
+
'compounds' => $result['compounds'],
|
| 44 |
+
'metadata' => $result['metadata'],
|
| 45 |
+
]);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
/**
|
| 49 |
+
* POST /api/chemical-search/full-rag
|
| 50 |
+
* Full RAG - Synchronous
|
| 51 |
+
*/
|
| 52 |
+
public function fullRag(ChemicalSearchRequest $request): JsonResponse
|
| 53 |
+
{
|
| 54 |
+
$user = Auth::user();
|
| 55 |
+
|
| 56 |
+
$result = $this->searchService->fullRag(
|
| 57 |
+
smiles: $request->validated('smiles'),
|
| 58 |
+
topK: $request->input('top_k', 5),
|
| 59 |
+
userId: $user->id
|
| 60 |
+
);
|
| 61 |
+
|
| 62 |
+
if (!$result['success']) {
|
| 63 |
+
return response()->json([
|
| 64 |
+
'success' => false,
|
| 65 |
+
'message' => $result['error'] ?? 'Search failed',
|
| 66 |
+
], 500);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
return response()->json([
|
| 70 |
+
'success' => true,
|
| 71 |
+
'query' => [
|
| 72 |
+
'smiles' => $request->validated('smiles'),
|
| 73 |
+
'top_k' => $request->input('top_k', 5),
|
| 74 |
+
],
|
| 75 |
+
'compounds' => $result['compounds'],
|
| 76 |
+
'metadata' => $result['metadata'],
|
| 77 |
+
]);
|
| 78 |
+
}
|
| 79 |
+
}
|
app/Http/Controllers/Api/ChemistryController.php
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Controller;
|
| 6 |
+
use App\Http\Requests\ChemistryChatRequest;
|
| 7 |
+
use App\Http\Requests\ChemistryCompareRequest;
|
| 8 |
+
use App\Http\Requests\ChemistryDockingRequest;
|
| 9 |
+
use App\Http\Resources\ChemistryResponseResource;
|
| 10 |
+
use App\Models\ChemistryAnalysis;
|
| 11 |
+
use App\Models\ChemistryCsvJob;
|
| 12 |
+
use App\Models\ChemistryThread;
|
| 13 |
+
use App\Services\ChemistryApiService;
|
| 14 |
+
use Illuminate\Http\JsonResponse;
|
| 15 |
+
use Illuminate\Http\Request;
|
| 16 |
+
use Illuminate\Support\Facades\Storage;
|
| 17 |
+
use Illuminate\Support\Facades\Auth;
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class ChemistryController extends BaseController
|
| 21 |
+
{
|
| 22 |
+
public function __construct(
|
| 23 |
+
protected ChemistryApiService $chemistryService
|
| 24 |
+
) {}
|
| 25 |
+
|
| 26 |
+
// ==================== Health ====================
|
| 27 |
+
public function health(): JsonResponse
|
| 28 |
+
{
|
| 29 |
+
$result = $this->chemistryService->healthCheck();
|
| 30 |
+
return response()->json(new ChemistryResponseResource($result));
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// ==================== Threads ====================
|
| 34 |
+
public function createThread(): JsonResponse
|
| 35 |
+
{
|
| 36 |
+
$user = Auth::user();
|
| 37 |
+
|
| 38 |
+
$result = $this->chemistryService->createThread();
|
| 39 |
+
|
| 40 |
+
if (!$result['success']) {
|
| 41 |
+
return response()->json(new ChemistryResponseResource($result), 500);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
$thread = ChemistryThread::create([
|
| 45 |
+
'user_id' => $user->id,
|
| 46 |
+
'thread_id' => $result['data']['thread_id'],
|
| 47 |
+
'title' => 'New Conversation',
|
| 48 |
+
'status' => 'active',
|
| 49 |
+
'last_used_at' => now(),
|
| 50 |
+
]);
|
| 51 |
+
|
| 52 |
+
return response()->json([
|
| 53 |
+
'success' => true,
|
| 54 |
+
'data' => [
|
| 55 |
+
'thread_id' => $thread->thread_id,
|
| 56 |
+
'id' => $thread->id,
|
| 57 |
+
'created_at' => $thread->created_at,
|
| 58 |
+
],
|
| 59 |
+
]);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
public function listThreads(): JsonResponse
|
| 63 |
+
{
|
| 64 |
+
$threads = Auth::user()->chemistryThreads()
|
| 65 |
+
->where('status', 'active')
|
| 66 |
+
->orderBy('last_used_at', 'desc')
|
| 67 |
+
->get(['id', 'thread_id', 'title', 'last_used_at', 'created_at']);
|
| 68 |
+
|
| 69 |
+
return response()->json([
|
| 70 |
+
'success' => true,
|
| 71 |
+
'data' => $threads,
|
| 72 |
+
]);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// ==================== Chat ====================
|
| 76 |
+
public function chat(ChemistryChatRequest $request): JsonResponse
|
| 77 |
+
{
|
| 78 |
+
$user = Auth::user();
|
| 79 |
+
$threadId = $request->input('thread_id');
|
| 80 |
+
|
| 81 |
+
if ($threadId) {
|
| 82 |
+
$thread = ChemistryThread::where('user_id', $user->id)
|
| 83 |
+
->where('thread_id', $threadId)
|
| 84 |
+
->first();
|
| 85 |
+
|
| 86 |
+
if (!$thread) {
|
| 87 |
+
return response()->json([
|
| 88 |
+
'success' => false,
|
| 89 |
+
'error' => 'Thread not found or access denied',
|
| 90 |
+
], 403);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
$thread->update(['last_used_at' => now()]);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
$result = $this->chemistryService->chat(
|
| 97 |
+
$request->input('message'),
|
| 98 |
+
$threadId
|
| 99 |
+
);
|
| 100 |
+
|
| 101 |
+
if (!$result['success']) {
|
| 102 |
+
return response()->json(new ChemistryResponseResource($result), $result['status']);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
ChemistryAnalysis::create([
|
| 106 |
+
'user_id' => $user->id,
|
| 107 |
+
'chemistry_thread_id' => $thread?->id,
|
| 108 |
+
'type' => 'chat',
|
| 109 |
+
'input_data' => $request->input('message'),
|
| 110 |
+
'response' => $result['data']['reply'] ?? null,
|
| 111 |
+
'processing_time_ms' => $result['data']['processing_time_ms'] ?? null,
|
| 112 |
+
'status' => 'success',
|
| 113 |
+
]);
|
| 114 |
+
|
| 115 |
+
return response()->json(new ChemistryResponseResource($result));
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
// ==================== Analyze SMILES ====================
|
| 119 |
+
public function analyzeSmiles(Request $request): JsonResponse
|
| 120 |
+
{
|
| 121 |
+
$user = auth()->user();
|
| 122 |
+
|
| 123 |
+
$request->validate([
|
| 124 |
+
'smiles' => 'required|string|max:1000',
|
| 125 |
+
'thread_id' => 'nullable|string|max:255',
|
| 126 |
+
]);
|
| 127 |
+
|
| 128 |
+
$threadId = $request->input('thread_id');
|
| 129 |
+
$thread = $this->validateThread($user->id, $threadId);
|
| 130 |
+
|
| 131 |
+
$smiles = $request->input('smiles');
|
| 132 |
+
|
| 133 |
+
$result = $this->chemistryService->analyzeSmiles($smiles, $threadId);
|
| 134 |
+
|
| 135 |
+
return $this->saveAndRespond($result, $user->id, 'smiles', $smiles, $thread?->id);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
// ==================== Compare ====================
|
| 139 |
+
public function compare(ChemistryCompareRequest $request): JsonResponse
|
| 140 |
+
{
|
| 141 |
+
$user = Auth::user();
|
| 142 |
+
$threadId = $request->input('thread_id');
|
| 143 |
+
$thread = $this->validateThread($user->id, $threadId);
|
| 144 |
+
|
| 145 |
+
$result = $this->chemistryService->compareMolecules(
|
| 146 |
+
$request->input('smiles'),
|
| 147 |
+
$threadId
|
| 148 |
+
);
|
| 149 |
+
|
| 150 |
+
$input = implode(', ', $request->input('smiles'));
|
| 151 |
+
return $this->saveAndRespond($result, $user->id, 'compare', $input, $thread?->id);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
// ==================== Docking ====================
|
| 155 |
+
public function docking(ChemistryDockingRequest $request): JsonResponse
|
| 156 |
+
{
|
| 157 |
+
$user = Auth::user();
|
| 158 |
+
$threadId = $request->input('thread_id');
|
| 159 |
+
$thread = $this->validateThread($user->id, $threadId);
|
| 160 |
+
|
| 161 |
+
$result = $this->chemistryService->analyzeDocking(
|
| 162 |
+
$request->input('docking_data'),
|
| 163 |
+
$threadId
|
| 164 |
+
);
|
| 165 |
+
|
| 166 |
+
return $this->saveAndRespond($result, $user->id, 'docking', $request->input('docking_data'), $thread?->id);
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
// ==================== CSV ====================
|
| 170 |
+
public function uploadCsv(Request $request): JsonResponse
|
| 171 |
+
{
|
| 172 |
+
$user = Auth::user();
|
| 173 |
+
|
| 174 |
+
$request->validate([
|
| 175 |
+
'file' => 'required|file|mimes:csv,txt|max:2048',
|
| 176 |
+
'analysis_type' => 'nullable|in:full,quick,admet,classify',
|
| 177 |
+
]);
|
| 178 |
+
|
| 179 |
+
$file = $request->file('file');
|
| 180 |
+
$analysisType = $request->input('analysis_type', 'full');
|
| 181 |
+
|
| 182 |
+
$result = $this->chemistryService->uploadCsv($file, $analysisType);
|
| 183 |
+
|
| 184 |
+
if (!$result['success']) {
|
| 185 |
+
return response()->json(new ChemistryResponseResource($result), $result['status']);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
$apiJobId = $result['data']['job_id'] ?? null;
|
| 189 |
+
$totalRows = $result['data']['total_rows'] ?? 0;
|
| 190 |
+
|
| 191 |
+
if (!$apiJobId) {
|
| 192 |
+
return response()->json([
|
| 193 |
+
'success' => false,
|
| 194 |
+
'error' => 'Failed to get job ID from API',
|
| 195 |
+
], 500);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
$job = ChemistryCsvJob::create([
|
| 199 |
+
'user_id' => $user->id,
|
| 200 |
+
'job_id' => $apiJobId,
|
| 201 |
+
'filename' => $file->getClientOriginalName(),
|
| 202 |
+
'analysis_type' => $analysisType,
|
| 203 |
+
'total_rows' => $totalRows,
|
| 204 |
+
'status' => 'queued',
|
| 205 |
+
]);
|
| 206 |
+
|
| 207 |
+
return response()->json([
|
| 208 |
+
'success' => true,
|
| 209 |
+
'data' => [
|
| 210 |
+
'job_id' => $job->job_id,
|
| 211 |
+
'id' => $job->id,
|
| 212 |
+
'status' => $job->status,
|
| 213 |
+
'total_rows' => $job->total_rows,
|
| 214 |
+
],
|
| 215 |
+
]);
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
public function csvStatus(string $jobId): JsonResponse
|
| 219 |
+
{
|
| 220 |
+
$user = Auth::user();
|
| 221 |
+
|
| 222 |
+
$job = ChemistryCsvJob::where('user_id', $user->id)
|
| 223 |
+
->where('job_id', $jobId)
|
| 224 |
+
->first();
|
| 225 |
+
|
| 226 |
+
if (!$job) {
|
| 227 |
+
return response()->json([
|
| 228 |
+
'success' => false,
|
| 229 |
+
'error' => 'Job not found or access denied',
|
| 230 |
+
], 404);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
$result = $this->chemistryService->getCsvStatus($jobId);
|
| 234 |
+
|
| 235 |
+
if ($result['success'] && isset($result['data'])) {
|
| 236 |
+
$apiData = $result['data'];
|
| 237 |
+
|
| 238 |
+
$job->update([
|
| 239 |
+
'status' => $apiData['status'] ?? $job->status,
|
| 240 |
+
'completed_rows' => $apiData['completed'] ?? $job->completed_rows,
|
| 241 |
+
'failed_rows' => $apiData['failed_rows'] ?? $job->failed_rows,
|
| 242 |
+
'progress_percent' => $apiData['progress_percent'] ?? $job->progress_percent,
|
| 243 |
+
]);
|
| 244 |
+
|
| 245 |
+
if ($apiData['status'] === 'done' && !$job->completed_at) {
|
| 246 |
+
$job->update(['completed_at' => now(), 'status' => 'done']);
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
return response()->json([
|
| 251 |
+
'success' => true,
|
| 252 |
+
'data' => [
|
| 253 |
+
'job_id' => $job->job_id,
|
| 254 |
+
'status' => $job->status,
|
| 255 |
+
'total' => $job->total_rows,
|
| 256 |
+
'completed' => $job->completed_rows,
|
| 257 |
+
'failed' => $job->failed_rows,
|
| 258 |
+
'progress_percent' => $job->progress_percent,
|
| 259 |
+
],
|
| 260 |
+
]);
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
public function csvResults(string $jobId)
|
| 264 |
+
{
|
| 265 |
+
$user = Auth::user();
|
| 266 |
+
|
| 267 |
+
$job = ChemistryCsvJob::where('user_id', $user->id)
|
| 268 |
+
->where('job_id', $jobId)
|
| 269 |
+
->first();
|
| 270 |
+
|
| 271 |
+
if (!$job) {
|
| 272 |
+
return response()->json([
|
| 273 |
+
'success' => false,
|
| 274 |
+
'error' => 'Job not found or access denied',
|
| 275 |
+
], 404);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
if ($job->status !== 'done') {
|
| 279 |
+
return response()->json([
|
| 280 |
+
'success' => false,
|
| 281 |
+
'error' => 'Job not completed yet',
|
| 282 |
+
], 400);
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
if ($job->result_file_path && Storage::exists($job->result_file_path)) {
|
| 286 |
+
return Storage::download($job->result_file_path);
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
$result = $this->chemistryService->getCsvResults($jobId);
|
| 290 |
+
|
| 291 |
+
if (is_string($result)) {
|
| 292 |
+
$path = "chemistry_results/{$jobId}.csv";
|
| 293 |
+
Storage::put($path, $result);
|
| 294 |
+
$job->update(['result_file_path' => $path]);
|
| 295 |
+
|
| 296 |
+
return response($result)
|
| 297 |
+
->header('Content-Type', 'text/csv')
|
| 298 |
+
->header('Content-Disposition', 'attachment; filename="results_' . $jobId . '.csv"');
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
return response()->json(new ChemistryResponseResource($result));
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
public function listJobs(): JsonResponse
|
| 305 |
+
{
|
| 306 |
+
$jobs = Auth::user()->chemistryCsvJobs()
|
| 307 |
+
->orderBy('created_at', 'desc')
|
| 308 |
+
->get([
|
| 309 |
+
'id',
|
| 310 |
+
'job_id',
|
| 311 |
+
'filename',
|
| 312 |
+
'analysis_type',
|
| 313 |
+
'status',
|
| 314 |
+
'total_rows',
|
| 315 |
+
'completed_rows',
|
| 316 |
+
'failed_rows',
|
| 317 |
+
'progress_percent',
|
| 318 |
+
'created_at'
|
| 319 |
+
]);
|
| 320 |
+
|
| 321 |
+
return response()->json([
|
| 322 |
+
'success' => true,
|
| 323 |
+
'data' => $jobs,
|
| 324 |
+
]);
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
public function deleteJob(string $jobId): JsonResponse
|
| 328 |
+
{
|
| 329 |
+
$user = Auth::user();
|
| 330 |
+
|
| 331 |
+
$job = ChemistryCsvJob::where('user_id', $user->id)
|
| 332 |
+
->where('job_id', $jobId)
|
| 333 |
+
->first();
|
| 334 |
+
|
| 335 |
+
if (!$job) {
|
| 336 |
+
return response()->json([
|
| 337 |
+
'success' => false,
|
| 338 |
+
'error' => 'Job not found or access denied',
|
| 339 |
+
], 404);
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
$result = $this->chemistryService->deleteCsvJob($jobId);
|
| 343 |
+
|
| 344 |
+
if ($job->result_file_path && Storage::exists($job->result_file_path)) {
|
| 345 |
+
Storage::delete($job->result_file_path);
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
$job->delete();
|
| 349 |
+
|
| 350 |
+
return response()->json([
|
| 351 |
+
'success' => true,
|
| 352 |
+
'message' => 'Job deleted successfully',
|
| 353 |
+
]);
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
// ==================== User History ====================
|
| 357 |
+
public function userHistory(Request $request): JsonResponse
|
| 358 |
+
{
|
| 359 |
+
$user = Auth::user();
|
| 360 |
+
$type = $request->input('type');
|
| 361 |
+
|
| 362 |
+
$query = $user->chemistryAnalyses()
|
| 363 |
+
->with('thread:id,thread_id,title')
|
| 364 |
+
->orderBy('created_at', 'desc');
|
| 365 |
+
|
| 366 |
+
if ($type) {
|
| 367 |
+
$query->where('type', $type);
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
$history = $query->paginate(20);
|
| 371 |
+
|
| 372 |
+
return response()->json([
|
| 373 |
+
'success' => true,
|
| 374 |
+
'data' => $history,
|
| 375 |
+
]);
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
// ==================== Helper Methods ====================
|
| 379 |
+
private function validateThread(int $userId, ?string $threadId): ?ChemistryThread
|
| 380 |
+
{
|
| 381 |
+
if (!$threadId) return null;
|
| 382 |
+
|
| 383 |
+
return ChemistryThread::where('user_id', $userId)
|
| 384 |
+
->where('thread_id', $threadId)
|
| 385 |
+
->first();
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
private function saveAndRespond(array $result, int $userId, string $type, string $input, ?int $threadId = null): JsonResponse
|
| 389 |
+
{
|
| 390 |
+
if (!$result['success']) {
|
| 391 |
+
ChemistryAnalysis::create([
|
| 392 |
+
'user_id' => $userId,
|
| 393 |
+
'chemistry_thread_id' => $threadId,
|
| 394 |
+
'type' => $type,
|
| 395 |
+
'input_data' => $input,
|
| 396 |
+
'status' => 'failed',
|
| 397 |
+
'error_message' => is_string($result['error']) ? $result['error'] : json_encode($result['error']),
|
| 398 |
+
]);
|
| 399 |
+
|
| 400 |
+
return response()->json(new ChemistryResponseResource($result), $result['status']);
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
ChemistryAnalysis::create([
|
| 404 |
+
'user_id' => $userId,
|
| 405 |
+
'chemistry_thread_id' => $threadId,
|
| 406 |
+
'type' => $type,
|
| 407 |
+
'input_data' => $input,
|
| 408 |
+
'response' => $result['data']['reply'] ?? null,
|
| 409 |
+
'processing_time_ms' => $result['data']['processing_time_ms'] ?? null,
|
| 410 |
+
'status' => 'success',
|
| 411 |
+
]);
|
| 412 |
+
|
| 413 |
+
return response()->json(new ChemistryResponseResource($result));
|
| 414 |
+
}
|
| 415 |
+
}
|
app/Http/Controllers/Api/ConvertSmilesController.php
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Models\DockingJob;
|
| 6 |
+
use App\Traits\ApiResponseTrait;
|
| 7 |
+
use Illuminate\Http\Request;
|
| 8 |
+
use Illuminate\Support\Facades\Log;
|
| 9 |
+
use Illuminate\Support\Facades\Process;
|
| 10 |
+
use Illuminate\Support\Facades\Validator;
|
| 11 |
+
use Illuminate\Support\Str;
|
| 12 |
+
|
| 13 |
+
class ConvertSmilesController
|
| 14 |
+
{
|
| 15 |
+
use ApiResponseTrait;
|
| 16 |
+
|
| 17 |
+
public function convert(Request $request)
|
| 18 |
+
{
|
| 19 |
+
$validator = Validator::make($request->all(), [
|
| 20 |
+
'ligand_smiles' => 'required|string|max:2000',
|
| 21 |
+
]);
|
| 22 |
+
|
| 23 |
+
if ($validator->fails()) {
|
| 24 |
+
return $this->errorResponse('Validation Error', 422, $validator->errors());
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
$conversion = $this->convertSmilesToPdbqt($request->ligand_smiles);
|
| 28 |
+
|
| 29 |
+
if ($conversion['status'] === 'error') {
|
| 30 |
+
return $this->errorResponse(
|
| 31 |
+
'SMILES conversion failed: ' . $conversion['message'],
|
| 32 |
+
422
|
| 33 |
+
);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// Create a completed job record scoped to SMILES-only conversions
|
| 37 |
+
$job = DockingJob::create([
|
| 38 |
+
'user_id' => $request->user()->id,
|
| 39 |
+
'input_type' => 'smiles',
|
| 40 |
+
'smiles' => $request->ligand_smiles,
|
| 41 |
+
'protein_name' => null,
|
| 42 |
+
'ligand_name' => 'generated_from_smiles.pdbqt',
|
| 43 |
+
'protein_path' => '',
|
| 44 |
+
'ligand_path' => $conversion['output_file'],
|
| 45 |
+
'status' => 'completed',
|
| 46 |
+
'result_data' => [
|
| 47 |
+
'output_file' => $conversion['output_file'],
|
| 48 |
+
'smiles' => $request->ligand_smiles,
|
| 49 |
+
],
|
| 50 |
+
]);
|
| 51 |
+
|
| 52 |
+
return $this->successResponse('SMILES converted to PDBQT successfully', [
|
| 53 |
+
'job_id' => $job->id,
|
| 54 |
+
'download_url' => url('/api/convert-smiles/download/' . $job->id),
|
| 55 |
+
'smiles' => $request->ligand_smiles,
|
| 56 |
+
]);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* Download the converted PDBQT file.
|
| 61 |
+
*/
|
| 62 |
+
public function download(Request $request, $id)
|
| 63 |
+
{
|
| 64 |
+
$job = DockingJob::where('id', $id)
|
| 65 |
+
->where('user_id', $request->user()->id)
|
| 66 |
+
->where('input_type', 'smiles')
|
| 67 |
+
->whereNull('protein_name') // ensure it's a convert-only job
|
| 68 |
+
->first();
|
| 69 |
+
|
| 70 |
+
if (! $job || $job->status !== 'completed') {
|
| 71 |
+
return $this->errorResponse('File not available or unauthorized', 404);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
$filePath = $job->result_data['output_file'] ?? $job->ligand_path ?? null;
|
| 75 |
+
|
| 76 |
+
if (! $filePath || ! file_exists($filePath)) {
|
| 77 |
+
return $this->errorResponse('File not found on server', 404);
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
return response()->download($filePath, 'converted_ligand_' . $job->id . '.pdbqt');
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/**
|
| 84 |
+
* List all SMILES conversion jobs for the authenticated user.
|
| 85 |
+
*/
|
| 86 |
+
public function history(Request $request)
|
| 87 |
+
{
|
| 88 |
+
$perPage = min((int) $request->query('per_page', 15), 100);
|
| 89 |
+
|
| 90 |
+
$paginator = DockingJob::where('user_id', $request->user()->id)
|
| 91 |
+
->where('input_type', 'smiles')
|
| 92 |
+
->whereNull('protein_name')
|
| 93 |
+
->orderBy('created_at', 'desc')
|
| 94 |
+
->paginate($perPage);
|
| 95 |
+
|
| 96 |
+
$items = collect($paginator->items())->map(function ($job) {
|
| 97 |
+
$data = [
|
| 98 |
+
'job_id' => $job->id,
|
| 99 |
+
'status' => $job->status,
|
| 100 |
+
'smiles' => $job->smiles,
|
| 101 |
+
'created_at' => $job->created_at->toIso8601String(),
|
| 102 |
+
];
|
| 103 |
+
|
| 104 |
+
if ($job->status === 'completed' && $job->result_data) {
|
| 105 |
+
$data['results'] = [
|
| 106 |
+
'download_url' => url('/api/convert-smiles/download/' . $job->id),
|
| 107 |
+
];
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
return $data;
|
| 111 |
+
});
|
| 112 |
+
|
| 113 |
+
return $this->successResponse('Conversion history retrieved successfully', [
|
| 114 |
+
'items' => $items,
|
| 115 |
+
'pagination' => [
|
| 116 |
+
'current_page' => $paginator->currentPage(),
|
| 117 |
+
'per_page' => $paginator->perPage(),
|
| 118 |
+
'total' => $paginator->total(),
|
| 119 |
+
'last_page' => $paginator->lastPage(),
|
| 120 |
+
'has_more' => $paginator->hasMorePages(),
|
| 121 |
+
],
|
| 122 |
+
]);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
/**
|
| 126 |
+
* Internal helper: run the SMILES → PDBQT Python conversion.
|
| 127 |
+
*
|
| 128 |
+
* @return array{status: string, output_file?: string, message?: string}
|
| 129 |
+
*/
|
| 130 |
+
private function convertSmilesToPdbqt(string $smiles): array
|
| 131 |
+
{
|
| 132 |
+
$pythonPath = env('DOCKING_PYTHON_PATH', base_path('vina_env/bin/python'));
|
| 133 |
+
$scriptPath = env('SMILES_SCRIPT_PATH', base_path('scripts/smiles_to_pdbqt.py'));
|
| 134 |
+
|
| 135 |
+
$outputFilename = Str::random(40) . '.pdbqt';
|
| 136 |
+
$outputDir = storage_path('app/private/docking/generated');
|
| 137 |
+
$outputPath = $outputDir . '/' . $outputFilename;
|
| 138 |
+
|
| 139 |
+
if (! is_dir($outputDir)) {
|
| 140 |
+
mkdir($outputDir, 0755, true);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
$cmd = sprintf(
|
| 144 |
+
'%s %s %s %s',
|
| 145 |
+
escapeshellarg($pythonPath),
|
| 146 |
+
escapeshellarg($scriptPath),
|
| 147 |
+
escapeshellarg($smiles),
|
| 148 |
+
escapeshellarg($outputPath)
|
| 149 |
+
);
|
| 150 |
+
|
| 151 |
+
try {
|
| 152 |
+
$result = Process::timeout(120)->run($cmd);
|
| 153 |
+
$fullOutput = $result->output();
|
| 154 |
+
$outputData = null;
|
| 155 |
+
|
| 156 |
+
if (preg_match('/\{"status":\s*"(success|error)".*\}/s', $fullOutput, $matches)) {
|
| 157 |
+
$outputData = json_decode($matches[0], true);
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
if (! $result->successful()) {
|
| 161 |
+
$errorMsg = $outputData['message'] ?? $result->errorOutput() ?: 'Unknown conversion error';
|
| 162 |
+
Log::error('SMILES conversion failed', ['smiles' => $smiles, 'error' => $errorMsg]);
|
| 163 |
+
return ['status' => 'error', 'message' => $errorMsg];
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
if ($outputData && $outputData['status'] === 'success') {
|
| 167 |
+
return $outputData;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
if ($outputData && $outputData['status'] === 'error') {
|
| 171 |
+
return $outputData;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
return ['status' => 'error', 'message' => 'Unexpected output from conversion script'];
|
| 175 |
+
} catch (\Exception $e) {
|
| 176 |
+
Log::error('SMILES conversion exception', ['smiles' => $smiles, 'error' => $e->getMessage()]);
|
| 177 |
+
return ['status' => 'error', 'message' => $e->getMessage()];
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
}
|
app/Http/Controllers/Api/DockingController.php
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Requests\Docking\SubmitDockingRequest;
|
| 6 |
+
use App\Jobs\RunDockingJob;
|
| 7 |
+
use App\Models\DockingJob;
|
| 8 |
+
use App\Traits\ApiResponseTrait;
|
| 9 |
+
use Illuminate\Http\Request;
|
| 10 |
+
use Illuminate\Support\Facades\Log;
|
| 11 |
+
use Illuminate\Support\Facades\Process;
|
| 12 |
+
use Illuminate\Support\Str;
|
| 13 |
+
|
| 14 |
+
class DockingController
|
| 15 |
+
{
|
| 16 |
+
use ApiResponseTrait;
|
| 17 |
+
|
| 18 |
+
/**
|
| 19 |
+
* Submit a docking job (accepts ligand file OR SMILES string).
|
| 20 |
+
*/
|
| 21 |
+
public function submit(SubmitDockingRequest $request)
|
| 22 |
+
{
|
| 23 |
+
$isSmiles = $request->filled('ligand_smiles');
|
| 24 |
+
|
| 25 |
+
// ---- Handle Ligand ----
|
| 26 |
+
if ($isSmiles) {
|
| 27 |
+
// SMILES → PDBQT conversion (synchronous, fast ~1-3s)
|
| 28 |
+
$conversion = $this->convertSmilesToPdbqt($request->ligand_smiles);
|
| 29 |
+
|
| 30 |
+
if ($conversion['status'] === 'error') {
|
| 31 |
+
return $this->errorResponse(
|
| 32 |
+
'SMILES conversion failed: ' . $conversion['message'],
|
| 33 |
+
422
|
| 34 |
+
);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
$ligandPath = $conversion['output_file'];
|
| 38 |
+
} else {
|
| 39 |
+
$ligandFilename = Str::random(40) . '.pdbqt';
|
| 40 |
+
$ligandPath = storage_path('app/private/' . $request->file('ligand_file')->storeAs('docking', $ligandFilename));
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// ---- Handle Protein ----
|
| 44 |
+
$proteinFilename = Str::random(40) . '.pdbqt';
|
| 45 |
+
$proteinPath = storage_path('app/private/' . $request->file('protein_file')->storeAs('docking', $proteinFilename));
|
| 46 |
+
|
| 47 |
+
// ---- Create Database Record ----
|
| 48 |
+
$job = DockingJob::create([
|
| 49 |
+
'user_id' => $request->user()->id,
|
| 50 |
+
'input_type' => $isSmiles ? 'smiles' : 'file',
|
| 51 |
+
'smiles' => $isSmiles ? $request->ligand_smiles : null,
|
| 52 |
+
'protein_name' => $request->protein_name,
|
| 53 |
+
'ligand_name' => $request->ligand_name,
|
| 54 |
+
'protein_path' => $proteinPath,
|
| 55 |
+
'ligand_path' => $ligandPath,
|
| 56 |
+
'status' => 'pending',
|
| 57 |
+
]);
|
| 58 |
+
|
| 59 |
+
// ---- Dispatch background docking job ----
|
| 60 |
+
RunDockingJob::dispatch($job, [
|
| 61 |
+
'center_x' => $request->center_x,
|
| 62 |
+
'center_y' => $request->center_y,
|
| 63 |
+
'center_z' => $request->center_z,
|
| 64 |
+
'box_size_x' => $request->box_size_x,
|
| 65 |
+
'box_size_y' => $request->box_size_y,
|
| 66 |
+
'box_size_z' => $request->box_size_z,
|
| 67 |
+
'exhaustiveness' => $request->exhaustiveness ?? 8,
|
| 68 |
+
'n_poses' => $request->n_poses ?? 5,
|
| 69 |
+
]);
|
| 70 |
+
|
| 71 |
+
return $this->successResponse('Docking Job Successfully Queued', [
|
| 72 |
+
'job_id' => $job->id,
|
| 73 |
+
'status' => $job->status,
|
| 74 |
+
]);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
/**
|
| 78 |
+
* List all docking jobs for the authenticated user (excludes SMILES-only conversion jobs).
|
| 79 |
+
*/
|
| 80 |
+
public function history(Request $request)
|
| 81 |
+
{
|
| 82 |
+
$perPage = min((int) $request->query('per_page', 15), 100);
|
| 83 |
+
|
| 84 |
+
$paginator = DockingJob::where('user_id', $request->user()->id)
|
| 85 |
+
->where(function ($q) {
|
| 86 |
+
// Exclude SMILES-only conversion jobs (those belong to convert-smiles)
|
| 87 |
+
$q->where('input_type', '!=', 'smiles')
|
| 88 |
+
->orWhereNotNull('protein_name');
|
| 89 |
+
})
|
| 90 |
+
->orderBy('created_at', 'desc')
|
| 91 |
+
->paginate($perPage);
|
| 92 |
+
|
| 93 |
+
$items = collect($paginator->items())->map(function ($job) {
|
| 94 |
+
$data = [
|
| 95 |
+
'job_id' => $job->id,
|
| 96 |
+
'status' => $job->status,
|
| 97 |
+
'inputs' => [
|
| 98 |
+
'protein' => $job->protein_name,
|
| 99 |
+
'ligand' => $job->ligand_name,
|
| 100 |
+
],
|
| 101 |
+
'created_at' => $job->created_at->toIso8601String(),
|
| 102 |
+
];
|
| 103 |
+
|
| 104 |
+
if ($job->status === 'completed' && $job->result_data) {
|
| 105 |
+
$data['results'] = [
|
| 106 |
+
'vina_scores' => $job->result_data['vina_score'] ?? [],
|
| 107 |
+
'download_url' => url('/api/docking/download/' . $job->id),
|
| 108 |
+
];
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
return $data;
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
return $this->successResponse('Docking history retrieved successfully', [
|
| 115 |
+
'data' => $items,
|
| 116 |
+
'pagination' => [
|
| 117 |
+
'current_page' => $paginator->currentPage(),
|
| 118 |
+
'per_page' => $paginator->perPage(),
|
| 119 |
+
'total' => $paginator->total(),
|
| 120 |
+
'last_page' => $paginator->lastPage(),
|
| 121 |
+
'has_more' => $paginator->hasMorePages(),
|
| 122 |
+
],
|
| 123 |
+
]);
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
/**
|
| 127 |
+
* Get docking job status.
|
| 128 |
+
*/
|
| 129 |
+
public function status(Request $request, $id)
|
| 130 |
+
{
|
| 131 |
+
$job = DockingJob::where('id', $id)
|
| 132 |
+
->where('user_id', $request->user()->id)
|
| 133 |
+
->where(function ($q) {
|
| 134 |
+
$q->where('input_type', '!=', 'smiles')
|
| 135 |
+
->orWhereNotNull('protein_name');
|
| 136 |
+
})
|
| 137 |
+
->first();
|
| 138 |
+
|
| 139 |
+
if (! $job) {
|
| 140 |
+
return $this->errorResponse('Docking job not found or unauthorized', 404);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// Build results block (only when job is completed)
|
| 144 |
+
$results = null;
|
| 145 |
+
if ($job->status === 'completed' && $job->result_data) {
|
| 146 |
+
$results = [
|
| 147 |
+
'vina_scores' => $job->result_data['vina_score'] ?? [],
|
| 148 |
+
'download_url' => url('/api/docking/download/' . $job->id),
|
| 149 |
+
];
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
$data = [
|
| 153 |
+
'job_id' => $job->id,
|
| 154 |
+
'status' => $job->status,
|
| 155 |
+
'inputs' => [
|
| 156 |
+
'protein' => $job->protein_name,
|
| 157 |
+
'ligand' => $job->ligand_name,
|
| 158 |
+
],
|
| 159 |
+
'created_at' => $job->created_at->toIso8601String(),
|
| 160 |
+
];
|
| 161 |
+
|
| 162 |
+
if ($results !== null) {
|
| 163 |
+
$data['results'] = $results;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
return $this->successResponse('Job details retrieved successfully', $data);
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
/**
|
| 170 |
+
* Download docking result or converted PDBQT.
|
| 171 |
+
*/
|
| 172 |
+
public function download(Request $request, $id)
|
| 173 |
+
{
|
| 174 |
+
$job = DockingJob::where('id', $id)
|
| 175 |
+
->where('user_id', $request->user()->id)
|
| 176 |
+
->where(function ($q) {
|
| 177 |
+
$q->where('input_type', '!=', 'smiles')
|
| 178 |
+
->orWhereNotNull('protein_name');
|
| 179 |
+
})
|
| 180 |
+
->first();
|
| 181 |
+
|
| 182 |
+
if (! $job || $job->status !== 'completed') {
|
| 183 |
+
return $this->errorResponse('Docking file not available or unauthorized', 404);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
$filePath = $job->result_data['output_file'] ?? null;
|
| 187 |
+
|
| 188 |
+
if (! $filePath || ! file_exists($filePath)) {
|
| 189 |
+
return $this->errorResponse('File not found on server', 404);
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
return response()->download($filePath, 'docking_result_' . $job->id . '.pdbqt');
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
/**
|
| 196 |
+
* Internal helper: run the SMILES → PDBQT Python conversion.
|
| 197 |
+
*
|
| 198 |
+
* @return array{status: string, output_file?: string, message?: string}
|
| 199 |
+
*/
|
| 200 |
+
private function convertSmilesToPdbqt(string $smiles): array
|
| 201 |
+
{
|
| 202 |
+
$pythonPath = env('DOCKING_PYTHON_PATH', base_path('vina_env/bin/python'));
|
| 203 |
+
$scriptPath = env('SMILES_SCRIPT_PATH', base_path('scripts/smiles_to_pdbqt.py'));
|
| 204 |
+
|
| 205 |
+
// Generate unique output path
|
| 206 |
+
$outputFilename = Str::random(40) . '.pdbqt';
|
| 207 |
+
$outputDir = storage_path('app/private/docking/generated');
|
| 208 |
+
$outputPath = $outputDir . '/' . $outputFilename;
|
| 209 |
+
|
| 210 |
+
// Ensure output directory exists
|
| 211 |
+
if (! is_dir($outputDir)) {
|
| 212 |
+
mkdir($outputDir, 0755, true);
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
$cmd = sprintf(
|
| 216 |
+
'%s %s %s %s',
|
| 217 |
+
escapeshellarg($pythonPath),
|
| 218 |
+
escapeshellarg($scriptPath),
|
| 219 |
+
escapeshellarg($smiles),
|
| 220 |
+
escapeshellarg($outputPath)
|
| 221 |
+
);
|
| 222 |
+
|
| 223 |
+
try {
|
| 224 |
+
$result = Process::timeout(120)->run($cmd);
|
| 225 |
+
|
| 226 |
+
$outputData = null;
|
| 227 |
+
$fullOutput = $result->output();
|
| 228 |
+
|
| 229 |
+
if (preg_match('/\{"status":\s*"(success|error)".*\}/s', $fullOutput, $matches)) {
|
| 230 |
+
$outputData = json_decode($matches[0], true);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
if (! $result->successful()) {
|
| 234 |
+
$errorMsg = $outputData['message'] ?? $result->errorOutput() ?: 'Unknown conversion error';
|
| 235 |
+
Log::error('SMILES conversion failed', ['smiles' => $smiles, 'error' => $errorMsg]);
|
| 236 |
+
return ['status' => 'error', 'message' => $errorMsg];
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
if ($outputData && $outputData['status'] === 'success') {
|
| 240 |
+
return $outputData;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
if ($outputData && $outputData['status'] === 'error') {
|
| 244 |
+
return $outputData;
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
return ['status' => 'error', 'message' => 'Unexpected output from conversion script'];
|
| 248 |
+
|
| 249 |
+
} catch (\Exception $e) {
|
| 250 |
+
Log::error('SMILES conversion exception', ['smiles' => $smiles, 'error' => $e->getMessage()]);
|
| 251 |
+
return ['status' => 'error', 'message' => $e->getMessage()];
|
| 252 |
+
}
|
| 253 |
+
}
|
| 254 |
+
}
|
app/Http/Controllers/Api/EmailVerificationController.php
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Controller;
|
| 6 |
+
use App\Http\Requests\VerifyOtpRequest;
|
| 7 |
+
use App\Services\OtpService;
|
| 8 |
+
use App\Models\User;
|
| 9 |
+
use Illuminate\Http\Request;
|
| 10 |
+
|
| 11 |
+
class EmailVerificationController extends BaseController
|
| 12 |
+
{
|
| 13 |
+
protected $otpService;
|
| 14 |
+
|
| 15 |
+
public function __construct(OtpService $otpService)
|
| 16 |
+
{
|
| 17 |
+
$this->otpService = $otpService;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
/**
|
| 21 |
+
* Verify Email OTP
|
| 22 |
+
*/
|
| 23 |
+
public function verify(Request $request)
|
| 24 |
+
{
|
| 25 |
+
$request->validate([
|
| 26 |
+
'email' => 'required|email|exists:users,email',
|
| 27 |
+
'otp' => 'required|digits:6',
|
| 28 |
+
]);
|
| 29 |
+
|
| 30 |
+
$user = User::where('email', $request->email)->first();
|
| 31 |
+
|
| 32 |
+
try {
|
| 33 |
+
$this->otpService->verifyOtp($user, 'email_verification', $request->otp);
|
| 34 |
+
|
| 35 |
+
$user->update([
|
| 36 |
+
'is_verified' => true,
|
| 37 |
+
'email_verification_otp' => null,
|
| 38 |
+
'email_verification_otp_expires_at' => null,
|
| 39 |
+
]);
|
| 40 |
+
|
| 41 |
+
$token = $user->createToken('auth_token')->plainTextToken;
|
| 42 |
+
|
| 43 |
+
return response()->json([
|
| 44 |
+
'success' => true,
|
| 45 |
+
'message' => 'Email verified successfully',
|
| 46 |
+
'data' => [
|
| 47 |
+
'token' => $token,
|
| 48 |
+
'user' => $user
|
| 49 |
+
]
|
| 50 |
+
]);
|
| 51 |
+
} catch (\Exception $e) {
|
| 52 |
+
return response()->json([
|
| 53 |
+
'success' => false,
|
| 54 |
+
'message' => $e->getMessage()
|
| 55 |
+
], 400);
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* Resend OTP
|
| 61 |
+
*/
|
| 62 |
+
public function resend(Request $request)
|
| 63 |
+
{
|
| 64 |
+
$request->validate([
|
| 65 |
+
'email' => 'required|email|exists:users,email'
|
| 66 |
+
]);
|
| 67 |
+
|
| 68 |
+
$user = User::where('email', $request->email)->first();
|
| 69 |
+
|
| 70 |
+
try {
|
| 71 |
+
$this->otpService->resendOtp($user, 'email_verification');
|
| 72 |
+
|
| 73 |
+
return response()->json([
|
| 74 |
+
'success' => true,
|
| 75 |
+
'message' => 'OTP resent successfully',
|
| 76 |
+
'data' => ['email' => $user->email]
|
| 77 |
+
]);
|
| 78 |
+
} catch (\Exception $e) {
|
| 79 |
+
return response()->json([
|
| 80 |
+
'success' => false,
|
| 81 |
+
'message' => $e->getMessage()
|
| 82 |
+
], 400);
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
}
|
app/Http/Controllers/Api/GenerationController.php
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use Illuminate\Http\Request;
|
| 6 |
+
use Illuminate\Support\Facades\Http;
|
| 7 |
+
use App\Models\AiJob;
|
| 8 |
+
use App\Services\AiServiceClient;
|
| 9 |
+
use App\Http\Requests\Ai\GenerationRequest;
|
| 10 |
+
use Illuminate\Http\JsonResponse;
|
| 11 |
+
use Illuminate\Http\Response;
|
| 12 |
+
use Illuminate\Support\Facades\Auth;
|
| 13 |
+
|
| 14 |
+
class GenerationController extends BaseController
|
| 15 |
+
{
|
| 16 |
+
private AiServiceClient $aiServiceClient;
|
| 17 |
+
|
| 18 |
+
public function __construct(AiServiceClient $aiServiceClient)
|
| 19 |
+
{
|
| 20 |
+
$this->aiServiceClient = $aiServiceClient;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/**
|
| 24 |
+
* Run a new generation job
|
| 25 |
+
*/
|
| 26 |
+
public function run(GenerationRequest $request): JsonResponse
|
| 27 |
+
{
|
| 28 |
+
$validated = $this->prepareDockingParameters($request->validated());
|
| 29 |
+
|
| 30 |
+
$response = $this->aiServiceClient->startGeneration($validated);
|
| 31 |
+
|
| 32 |
+
if ($response->failed()) {
|
| 33 |
+
return $this->aiServiceErrorResponse($response);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
$jobId = $response->json('job_id');
|
| 37 |
+
|
| 38 |
+
if (!$jobId) {
|
| 39 |
+
return $this->missingJobIdResponse();
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
$aiJob = $this->createAiJobRecord($jobId, $validated);
|
| 43 |
+
|
| 44 |
+
return $this->jobStartedResponse($aiJob);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* Get job status
|
| 49 |
+
*/
|
| 50 |
+
public function status(Request $request, string $jobId): JsonResponse
|
| 51 |
+
{
|
| 52 |
+
$aiJob = $this->getAiJobFromRequest($request, $jobId);
|
| 53 |
+
|
| 54 |
+
$this->syncJobStatusFromService($aiJob);
|
| 55 |
+
|
| 56 |
+
return $this->jobStatusResponse($aiJob);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
/**
|
| 60 |
+
* Get job results
|
| 61 |
+
*/
|
| 62 |
+
public function results(Request $request, string $jobId): JsonResponse
|
| 63 |
+
{
|
| 64 |
+
$aiJob = $this->getAiJobFromRequest($request, $jobId);
|
| 65 |
+
|
| 66 |
+
$response = $this->aiServiceClient->fetchResults($jobId);
|
| 67 |
+
|
| 68 |
+
if ($response->failed()) {
|
| 69 |
+
return $this->handleResultsFailure($response, $aiJob);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
$this->updateAiJobWithResults($aiJob, $response->json());
|
| 73 |
+
|
| 74 |
+
return $this->jobResultsResponse($aiJob, $response->json('results', []));
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
/**
|
| 78 |
+
* Download a specific file (Proxy to AI service)
|
| 79 |
+
*/
|
| 80 |
+
public function downloadFile(Request $request, string $jobId, string $filename): Response|JsonResponse
|
| 81 |
+
{
|
| 82 |
+
$safeFilename = basename($filename);
|
| 83 |
+
|
| 84 |
+
$response = $this->aiServiceClient->downloadFile($jobId, $safeFilename);
|
| 85 |
+
|
| 86 |
+
if ($response->failed()) {
|
| 87 |
+
return response()->json([
|
| 88 |
+
'success' => false,
|
| 89 |
+
'message' => 'File not found'
|
| 90 |
+
], 404);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
return $this->fileDownloadResponse($response, $safeFilename);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// ========== Private Helper Methods ==========
|
| 97 |
+
|
| 98 |
+
private function prepareDockingParameters(array $validated): array
|
| 99 |
+
{
|
| 100 |
+
if ($validated['docking_mode'] === 'all') {
|
| 101 |
+
$validated['dock_top_k'] = $validated['return_top_k'];
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
$validated['preset'] = $validated['preset'] ?? 'egfr_generator';
|
| 105 |
+
|
| 106 |
+
return $validated;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
private function createAiJobRecord(string $jobId, array $params): AiJob
|
| 110 |
+
{
|
| 111 |
+
return AiJob::create([
|
| 112 |
+
'user_id' => Auth::id(),
|
| 113 |
+
'job_id' => $jobId,
|
| 114 |
+
'status' => 'running',
|
| 115 |
+
'preset' => $params['preset'],
|
| 116 |
+
'num_molecules' => $params['num_molecules'],
|
| 117 |
+
'return_top_k' => $params['return_top_k'],
|
| 118 |
+
'docking_mode' => $params['docking_mode'],
|
| 119 |
+
'dock_top_k' => $params['dock_top_k'] ?? 0,
|
| 120 |
+
]);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
private function getAiJobFromRequest(Request $request, string $jobId): AiJob
|
| 124 |
+
{
|
| 125 |
+
return $request->get('ai_job');
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
private function syncJobStatusFromService(AiJob $aiJob): void
|
| 129 |
+
{
|
| 130 |
+
$response = $this->aiServiceClient->getJobStatus($aiJob->job_id);
|
| 131 |
+
|
| 132 |
+
if ($response->successful()) {
|
| 133 |
+
$newStatus = $response->json('status');
|
| 134 |
+
if ($newStatus && $newStatus !== $aiJob->status) {
|
| 135 |
+
$aiJob->update(['status' => $newStatus]);
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
private function updateAiJobWithResults(AiJob $aiJob, array $data): void
|
| 141 |
+
{
|
| 142 |
+
$aiJob->update([
|
| 143 |
+
'status' => $data['status'] ?? 'completed',
|
| 144 |
+
'summary' => $data['summary'] ?? null,
|
| 145 |
+
'files' => $data['files'] ?? null,
|
| 146 |
+
'ligands' => $data['results'] ?? null,
|
| 147 |
+
]);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
// ========== Response Builders ==========
|
| 151 |
+
|
| 152 |
+
private function aiServiceErrorResponse($response): JsonResponse
|
| 153 |
+
{
|
| 154 |
+
return response()->json([
|
| 155 |
+
'success' => false,
|
| 156 |
+
'message' => 'AI service error: ' . $response->body(),
|
| 157 |
+
], $response->status());
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
private function missingJobIdResponse(): JsonResponse
|
| 161 |
+
{
|
| 162 |
+
return response()->json([
|
| 163 |
+
'success' => false,
|
| 164 |
+
'message' => 'AI service did not return a job ID'
|
| 165 |
+
], 500);
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
private function jobStartedResponse(AiJob $aiJob): JsonResponse
|
| 169 |
+
{
|
| 170 |
+
return response()->json([
|
| 171 |
+
'success' => true,
|
| 172 |
+
'message' => 'Generation job started successfully',
|
| 173 |
+
'job_id' => $aiJob->job_id,
|
| 174 |
+
'status' => $aiJob->status,
|
| 175 |
+
'preset' => $aiJob->preset,
|
| 176 |
+
'num_molecules' => $aiJob->num_molecules,
|
| 177 |
+
'return_top_k' => $aiJob->return_top_k,
|
| 178 |
+
'docking_mode' => $aiJob->docking_mode,
|
| 179 |
+
'dock_top_k' => $aiJob->dock_top_k,
|
| 180 |
+
'created_at' => $aiJob->created_at->toDateTimeString(),
|
| 181 |
+
], 202);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
private function jobStatusResponse(AiJob $aiJob): JsonResponse
|
| 185 |
+
{
|
| 186 |
+
return response()->json([
|
| 187 |
+
'success' => true,
|
| 188 |
+
'job_id' => $aiJob->job_id,
|
| 189 |
+
'status' => $aiJob->status,
|
| 190 |
+
'preset' => $aiJob->preset,
|
| 191 |
+
'num_molecules' => $aiJob->num_molecules,
|
| 192 |
+
'return_top_k' => $aiJob->return_top_k,
|
| 193 |
+
'docking_mode' => $aiJob->docking_mode,
|
| 194 |
+
'dock_top_k' => $aiJob->dock_top_k,
|
| 195 |
+
'created_at' => $aiJob->created_at->toDateTimeString(),
|
| 196 |
+
]);
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
private function handleResultsFailure($response, AiJob $aiJob): JsonResponse
|
| 200 |
+
{
|
| 201 |
+
if ($response->status() === 404) {
|
| 202 |
+
return response()->json([
|
| 203 |
+
'success' => false,
|
| 204 |
+
'message' => 'Results not ready yet',
|
| 205 |
+
'status' => $aiJob->status
|
| 206 |
+
], 202);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
return response()->json([
|
| 210 |
+
'success' => false,
|
| 211 |
+
'message' => 'Failed to fetch results',
|
| 212 |
+
], $response->status());
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
private function jobResultsResponse(AiJob $aiJob, array $ligands): JsonResponse
|
| 216 |
+
{
|
| 217 |
+
return response()->json([
|
| 218 |
+
'success' => true,
|
| 219 |
+
'job_id' => $aiJob->job_id,
|
| 220 |
+
'status' => $aiJob->status,
|
| 221 |
+
'preset' => $aiJob->preset,
|
| 222 |
+
'num_molecules' => $aiJob->num_molecules,
|
| 223 |
+
'return_top_k' => $aiJob->return_top_k,
|
| 224 |
+
'docking_mode' => $aiJob->docking_mode,
|
| 225 |
+
'dock_top_k' => $aiJob->dock_top_k,
|
| 226 |
+
'summary' => $aiJob->getSummaryStats(),
|
| 227 |
+
'files' => $this->buildFileList($aiJob->files ?? []),
|
| 228 |
+
'ligands' => $ligands,
|
| 229 |
+
'created_at' => $aiJob->created_at->toDateTimeString(),
|
| 230 |
+
]);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
private function buildFileList(array $files): array
|
| 234 |
+
{
|
| 235 |
+
$fileList = [];
|
| 236 |
+
|
| 237 |
+
foreach ($files as $type => $fileInfo) {
|
| 238 |
+
$fileList[$type] = [
|
| 239 |
+
'filename' => $fileInfo['filename'] ?? "{$type}_results.csv"
|
| 240 |
+
];
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
return $fileList;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
private function fileDownloadResponse($response, string $filename): Response
|
| 247 |
+
{
|
| 248 |
+
$contentType = $response->header('Content-Type') ?? 'application/octet-stream';
|
| 249 |
+
|
| 250 |
+
return response($response->body(), 200)
|
| 251 |
+
->header('Content-Type', $contentType)
|
| 252 |
+
->header('Content-Disposition', "attachment; filename=\"{$filename}\"");
|
| 253 |
+
}
|
| 254 |
+
}
|
app/Http/Controllers/Api/LigandsController.php
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use Illuminate\Http\Request;
|
| 6 |
+
use Illuminate\Support\Facades\Http;
|
| 7 |
+
use App\Models\LigandExport;
|
| 8 |
+
use Illuminate\Support\Facades\Auth;
|
| 9 |
+
|
| 10 |
+
class LigandsController extends BaseController
|
| 11 |
+
{
|
| 12 |
+
|
| 13 |
+
private function aiServiceUrl(): string
|
| 14 |
+
{
|
| 15 |
+
return rtrim(config('services.generation.url', 'http://generation:8000'), '/');
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
public function exportLigands(Request $request)
|
| 19 |
+
{
|
| 20 |
+
$validated = $request->validate([
|
| 21 |
+
'smiles' => 'required|string',
|
| 22 |
+
'format' => 'required|string|in:pdbqt,pdb,mol2',
|
| 23 |
+
]);
|
| 24 |
+
|
| 25 |
+
$response = $this->callAiService('/ligands/export', $validated);
|
| 26 |
+
|
| 27 |
+
return $response;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
public function callAiService($endpoint, $validated)
|
| 31 |
+
{
|
| 32 |
+
$url = $this->aiServiceUrl() . $endpoint;
|
| 33 |
+
$response = Http::timeout(120)
|
| 34 |
+
->retry(5, 1000)
|
| 35 |
+
->post($url, $validated);
|
| 36 |
+
if ($response->failed()) {
|
| 37 |
+
return response()->json([
|
| 38 |
+
'success' => false,
|
| 39 |
+
'message' => 'AI service error: ' . $response->body(),
|
| 40 |
+
], $response->status());
|
| 41 |
+
}
|
| 42 |
+
$aiData = $response->json();
|
| 43 |
+
|
| 44 |
+
LigandExport::create([
|
| 45 |
+
'user_id' => Auth::id(),
|
| 46 |
+
'job_id' => $aiData['job_id'],
|
| 47 |
+
'status' => $aiData['status'],
|
| 48 |
+
'format' => $aiData['file']['format'],
|
| 49 |
+
'filename' => $aiData['file']['filename'],
|
| 50 |
+
'smiles' => $aiData['canonical_smiles']
|
| 51 |
+
]);
|
| 52 |
+
|
| 53 |
+
return response()->json([
|
| 54 |
+
'success' => true,
|
| 55 |
+
'data' => [
|
| 56 |
+
'job_id' => $aiData['job_id'],
|
| 57 |
+
'status' => $aiData['status'],
|
| 58 |
+
'smiles' => $aiData['canonical_smiles'],
|
| 59 |
+
'format' => $aiData['file']['format'],
|
| 60 |
+
'filename' => $aiData['file']['filename'],
|
| 61 |
+
'created_at' => now()->toDateTimeString()
|
| 62 |
+
]
|
| 63 |
+
]);
|
| 64 |
+
}
|
| 65 |
+
}
|
app/Http/Controllers/Api/MdFileController.php
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
// app/Http/Controllers/MdFileController.php
|
| 3 |
+
|
| 4 |
+
namespace App\Http\Controllers\Api;
|
| 5 |
+
|
| 6 |
+
use App\Models\MdFile;
|
| 7 |
+
use Illuminate\Http\JsonResponse;
|
| 8 |
+
|
| 9 |
+
class MdFileController extends BaseController
|
| 10 |
+
{
|
| 11 |
+
|
| 12 |
+
public function index(): JsonResponse
|
| 13 |
+
{
|
| 14 |
+
$mdFile = MdFile::first();
|
| 15 |
+
|
| 16 |
+
if (!$mdFile) {
|
| 17 |
+
return response()->json(['message' => 'No data found'], 404);
|
| 18 |
+
}
|
| 19 |
+
return response()->json([
|
| 20 |
+
'experiment' => $mdFile->experiment_name,
|
| 21 |
+
'description' => $mdFile->description,
|
| 22 |
+
'total_files' => count($mdFile->getAllFiles()),
|
| 23 |
+
'files' => $mdFile->getAllFiles(),
|
| 24 |
+
'by_type' => [
|
| 25 |
+
'pdb' => $mdFile->getFilesByExtension('pdb'),
|
| 26 |
+
'dcd' => $mdFile->getFilesByExtension('dcd'),
|
| 27 |
+
'crd' => $mdFile->getFilesByExtension('crd'),
|
| 28 |
+
'prmtop' => $mdFile->getFilesByExtension('prmtop'),
|
| 29 |
+
'rst' => $mdFile->getFilesByExtension('rst'),
|
| 30 |
+
'csv' => $mdFile->getFilesByExtension('csv'),
|
| 31 |
+
'png' => $mdFile->getFilesByExtension('png'),
|
| 32 |
+
'html' => $mdFile->getFilesByExtension('html'),
|
| 33 |
+
'dat' => $mdFile->getFilesByExtension('dat'),
|
| 34 |
+
]
|
| 35 |
+
]);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
public function show(string $filename): JsonResponse
|
| 40 |
+
{
|
| 41 |
+
$mdFile = MdFile::first();
|
| 42 |
+
|
| 43 |
+
if (!$mdFile) {
|
| 44 |
+
return response()->json(['message' => 'No data found'], 404);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
$decodedName = urldecode($filename);
|
| 48 |
+
|
| 49 |
+
if (!$mdFile->hasFile($decodedName)) {
|
| 50 |
+
return response()->json([
|
| 51 |
+
'message' => 'File not found',
|
| 52 |
+
'requested' => $decodedName,
|
| 53 |
+
'available' => array_keys($mdFile->getAllFiles())
|
| 54 |
+
], 404);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
return response()->json([
|
| 58 |
+
'filename' => $decodedName,
|
| 59 |
+
'url' => $mdFile->getFileUrl($decodedName),
|
| 60 |
+
'extension' => pathinfo($decodedName, PATHINFO_EXTENSION)
|
| 61 |
+
]);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
public function byType(string $type): JsonResponse
|
| 66 |
+
{
|
| 67 |
+
$mdFile = MdFile::first();
|
| 68 |
+
|
| 69 |
+
if (!$mdFile) {
|
| 70 |
+
return response()->json(['message' => 'No data found'], 404);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
$files = $mdFile->getFilesByExtension($type);
|
| 74 |
+
|
| 75 |
+
return response()->json([
|
| 76 |
+
'type' => $type,
|
| 77 |
+
'count' => count($files),
|
| 78 |
+
'files' => $files
|
| 79 |
+
]);
|
| 80 |
+
}
|
| 81 |
+
}
|
app/Http/Controllers/Api/NewsController.php
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Api\BaseController;
|
| 6 |
+
use App\Services\NewsService;
|
| 7 |
+
use Illuminate\Http\Request;
|
| 8 |
+
use Illuminate\Support\Facades\Log;
|
| 9 |
+
|
| 10 |
+
class NewsController extends BaseController
|
| 11 |
+
{
|
| 12 |
+
private NewsService $newsService;
|
| 13 |
+
|
| 14 |
+
public function __construct(NewsService $newsService)
|
| 15 |
+
{
|
| 16 |
+
$this->newsService = $newsService;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
public function index(Request $request)
|
| 20 |
+
{
|
| 21 |
+
try {
|
| 22 |
+
$page = max(1, (int) $request->get('page', 1));
|
| 23 |
+
$perPage = min(100, max(1, (int) $request->get('per_page', 10)));
|
| 24 |
+
$category = $request->get('category');
|
| 25 |
+
|
| 26 |
+
$result = $this->newsService->getArticles($page, $perPage, $category);
|
| 27 |
+
|
| 28 |
+
$currentPage = $result['meta']['current_page'] ?? $page;
|
| 29 |
+
$totalResults = $result['meta']['total'] ?? 0;
|
| 30 |
+
|
| 31 |
+
$lastPage = (int) ceil($totalResults / $perPage);
|
| 32 |
+
$lastPage = $lastPage > 0 ? $lastPage : 1;
|
| 33 |
+
|
| 34 |
+
return $this->successResponse('Articles retrieved successfully', [
|
| 35 |
+
'results' => $result['articles']->map->toArray(),
|
| 36 |
+
'pagination' => [
|
| 37 |
+
'currentPage' => $currentPage,
|
| 38 |
+
'totalPages' => $lastPage,
|
| 39 |
+
'totalResults' => $totalResults,
|
| 40 |
+
'perPage' => $perPage,
|
| 41 |
+
'hasNextPage' => $currentPage < $lastPage,
|
| 42 |
+
'hasPrevPage' => $currentPage > 1
|
| 43 |
+
]
|
| 44 |
+
]);
|
| 45 |
+
} catch (\Exception $e) {
|
| 46 |
+
Log::error('Index error: ' . $e->getMessage());
|
| 47 |
+
return $this->errorResponse($e->getMessage(), 500);
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
public function refresh()
|
| 52 |
+
{
|
| 53 |
+
try {
|
| 54 |
+
$articles = $this->newsService->fetchNews();
|
| 55 |
+
|
| 56 |
+
return $this->successResponse("Fetched {$articles->count()} new articles", [
|
| 57 |
+
'results' => $articles,
|
| 58 |
+
'pagination' => [
|
| 59 |
+
'currentPage' => 1,
|
| 60 |
+
'totalPages' => 1,
|
| 61 |
+
'totalResults' => $articles->count(),
|
| 62 |
+
'perPage' => $articles->count(),
|
| 63 |
+
'hasNextPage' => false,
|
| 64 |
+
'hasPrevPage' => false
|
| 65 |
+
]
|
| 66 |
+
]);
|
| 67 |
+
} catch (\Exception $e) {
|
| 68 |
+
Log::error('Refresh error: ' . $e->getMessage());
|
| 69 |
+
return $this->errorResponse($e->getMessage(), 500);
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
public function saveArticle(Request $request, int $articleId)
|
| 74 |
+
{
|
| 75 |
+
try {
|
| 76 |
+
$user = $request->user();
|
| 77 |
+
|
| 78 |
+
if (!$user) {
|
| 79 |
+
return $this->errorResponse('Unauthorized. Please login first.', 401);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
$saved = $this->newsService->saveArticle($user->id, $articleId);
|
| 83 |
+
|
| 84 |
+
return $this->successResponse('Article saved successfully', [
|
| 85 |
+
'results' => [
|
| 86 |
+
[
|
| 87 |
+
'id' => $saved->id,
|
| 88 |
+
'user_id' => $saved->user_id,
|
| 89 |
+
'news_id' => $saved->news_id,
|
| 90 |
+
'created_at' => $saved->created_at,
|
| 91 |
+
]
|
| 92 |
+
],
|
| 93 |
+
'pagination' => [
|
| 94 |
+
'currentPage' => 1,
|
| 95 |
+
'totalPages' => 1,
|
| 96 |
+
'totalResults' => 1,
|
| 97 |
+
'perPage' => 1,
|
| 98 |
+
'hasNextPage' => false,
|
| 99 |
+
'hasPrevPage' => false
|
| 100 |
+
]
|
| 101 |
+
]);
|
| 102 |
+
} catch (\Exception $e) {
|
| 103 |
+
Log::error('Save error: ' . $e->getMessage());
|
| 104 |
+
return $this->errorResponse($e->getMessage(), 500);
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
public function shareArticle(int $articleId)
|
| 109 |
+
{
|
| 110 |
+
try {
|
| 111 |
+
$this->newsService->shareArticle($articleId);
|
| 112 |
+
|
| 113 |
+
return $this->successResponse('Article shared successfully', [
|
| 114 |
+
'results' => [],
|
| 115 |
+
'pagination' => [
|
| 116 |
+
'currentPage' => 1,
|
| 117 |
+
'totalPages' => 1,
|
| 118 |
+
'totalResults' => 0,
|
| 119 |
+
'perPage' => 0,
|
| 120 |
+
'hasNextPage' => false,
|
| 121 |
+
'hasPrevPage' => false
|
| 122 |
+
]
|
| 123 |
+
]);
|
| 124 |
+
} catch (\Exception $e) {
|
| 125 |
+
Log::error('Share error: ' . $e->getMessage());
|
| 126 |
+
return $this->errorResponse($e->getMessage(), 500);
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
public function getCategories()
|
| 131 |
+
{
|
| 132 |
+
try {
|
| 133 |
+
$categories = $this->newsService->getCategories();
|
| 134 |
+
|
| 135 |
+
return $this->successResponse('Categories retrieved successfully', [
|
| 136 |
+
'results' => $categories,
|
| 137 |
+
'pagination' => [
|
| 138 |
+
'currentPage' => 1,
|
| 139 |
+
'totalPages' => 1,
|
| 140 |
+
'totalResults' => count($categories),
|
| 141 |
+
'perPage' => count($categories),
|
| 142 |
+
'hasNextPage' => false,
|
| 143 |
+
'hasPrevPage' => false
|
| 144 |
+
]
|
| 145 |
+
]);
|
| 146 |
+
} catch (\Exception $e) {
|
| 147 |
+
Log::error('Categories error: ' . $e->getMessage());
|
| 148 |
+
return $this->errorResponse($e->getMessage(), 500);
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
public function clear()
|
| 153 |
+
{
|
| 154 |
+
try {
|
| 155 |
+
\App\Models\News::truncate();
|
| 156 |
+
|
| 157 |
+
return $this->successResponse('All news deleted', [
|
| 158 |
+
'results' => [],
|
| 159 |
+
'pagination' => [
|
| 160 |
+
'currentPage' => 1,
|
| 161 |
+
'totalPages' => 1,
|
| 162 |
+
'totalResults' => 0,
|
| 163 |
+
'perPage' => 0,
|
| 164 |
+
'hasNextPage' => false,
|
| 165 |
+
'hasPrevPage' => false
|
| 166 |
+
]
|
| 167 |
+
]);
|
| 168 |
+
} catch (\Exception $e) {
|
| 169 |
+
Log::error('Clear error: ' . $e->getMessage());
|
| 170 |
+
return $this->errorResponse($e->getMessage(), 500);
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
public function getSavedArticles(Request $request)
|
| 175 |
+
{
|
| 176 |
+
try {
|
| 177 |
+
$user = $request->user();
|
| 178 |
+
|
| 179 |
+
if (!$user) {
|
| 180 |
+
return $this->errorResponse('Unauthorized. Please login first.', 401);
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
$perPage = min(100, max(1, (int) $request->get('per_page', 10)));
|
| 184 |
+
|
| 185 |
+
$saved = \App\Models\SavedArticle::with('news')
|
| 186 |
+
->where('user_id', $user->id)
|
| 187 |
+
->orderBy('created_at', 'desc')
|
| 188 |
+
->paginate($perPage);
|
| 189 |
+
|
| 190 |
+
$transformed = $saved->getCollection()->map(function ($item) {
|
| 191 |
+
return [
|
| 192 |
+
'saved_id' => $item->id,
|
| 193 |
+
'saved_at' => $item->created_at,
|
| 194 |
+
'article' => [
|
| 195 |
+
'id' => $item->news->id,
|
| 196 |
+
'title' => $item->news->title,
|
| 197 |
+
'summary' => $item->news->summary,
|
| 198 |
+
'source' => $item->news->source,
|
| 199 |
+
'url' => $item->news->url,
|
| 200 |
+
'published_at' => $item->news->published_at,
|
| 201 |
+
],
|
| 202 |
+
];
|
| 203 |
+
});
|
| 204 |
+
|
| 205 |
+
return $this->successResponse('Saved articles retrieved successfully', [
|
| 206 |
+
'results' => $transformed,
|
| 207 |
+
'pagination' => [
|
| 208 |
+
'currentPage' => $saved->currentPage(),
|
| 209 |
+
'totalPages' => $saved->lastPage(),
|
| 210 |
+
'totalResults' => $saved->total(),
|
| 211 |
+
'perPage' => $saved->perPage(),
|
| 212 |
+
'hasNextPage' => $saved->hasMorePages(),
|
| 213 |
+
'hasPrevPage' => !$saved->onFirstPage()
|
| 214 |
+
]
|
| 215 |
+
]);
|
| 216 |
+
} catch (\Exception $e) {
|
| 217 |
+
Log::error('GetSaved error: ' . $e->getMessage());
|
| 218 |
+
return $this->errorResponse($e->getMessage(), 500);
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
public function unsaveArticle(Request $request, int $savedArticleId)
|
| 223 |
+
{
|
| 224 |
+
try {
|
| 225 |
+
$user = $request->user();
|
| 226 |
+
|
| 227 |
+
if (!$user) {
|
| 228 |
+
return $this->errorResponse('Unauthorized. Please login first.', 401);
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
$saved = \App\Models\SavedArticle::where('id', $savedArticleId)
|
| 232 |
+
->where('user_id', $user->id)
|
| 233 |
+
->first();
|
| 234 |
+
|
| 235 |
+
if (!$saved) {
|
| 236 |
+
return $this->errorResponse('Saved article not found', 404);
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
$saved->delete();
|
| 240 |
+
|
| 241 |
+
return $this->successResponse('Article removed from saved', [
|
| 242 |
+
'results' => [],
|
| 243 |
+
'pagination' => [
|
| 244 |
+
'currentPage' => 1,
|
| 245 |
+
'totalPages' => 1,
|
| 246 |
+
'totalResults' => 0,
|
| 247 |
+
'perPage' => 0,
|
| 248 |
+
'hasNextPage' => false,
|
| 249 |
+
'hasPrevPage' => false
|
| 250 |
+
]
|
| 251 |
+
]);
|
| 252 |
+
} catch (\Exception $e) {
|
| 253 |
+
Log::error('Unsave error: ' . $e->getMessage());
|
| 254 |
+
return $this->errorResponse($e->getMessage(), 500);
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
}
|
app/Http/Controllers/Api/OtpController.php
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Api\BaseController;
|
| 6 |
+
use App\Http\Requests\User\EmailVerificationRequest;
|
| 7 |
+
use App\Http\Requests\User\ResetPasswordRequest;
|
| 8 |
+
use App\Models\User;
|
| 9 |
+
use App\Services\OtpService;
|
| 10 |
+
|
| 11 |
+
class OtpController extends BaseController
|
| 12 |
+
{
|
| 13 |
+
protected $otpService;
|
| 14 |
+
|
| 15 |
+
public function __construct(OtpService $otpService)
|
| 16 |
+
{
|
| 17 |
+
$this->otpService = $otpService;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
/**
|
| 21 |
+
* Verify Email OTP
|
| 22 |
+
*/
|
| 23 |
+
public function verifyEmail(EmailVerificationRequest $request)
|
| 24 |
+
{
|
| 25 |
+
$user = User::where('email', $request->email)->first();
|
| 26 |
+
|
| 27 |
+
if (!$user) {
|
| 28 |
+
return $this->errorResponse('User not found', 404);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
try {
|
| 32 |
+
$this->otpService->verifyOtp($user, 'email_verification', $request->otp);
|
| 33 |
+
|
| 34 |
+
$user->update(['is_verified' => true]);
|
| 35 |
+
|
| 36 |
+
$token = $user->createToken('auth_token')->plainTextToken;
|
| 37 |
+
|
| 38 |
+
return $this->successResponse('Email verified successfully', [
|
| 39 |
+
'token' => $token,
|
| 40 |
+
'user' => $user
|
| 41 |
+
]);
|
| 42 |
+
} catch (\Exception $e) {
|
| 43 |
+
return $this->errorResponse($e->getMessage(), 400);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
/**
|
| 48 |
+
* Verify Password Reset OTP and reset password
|
| 49 |
+
*/
|
| 50 |
+
public function verifyResetPassword(ResetPasswordRequest $request)
|
| 51 |
+
{
|
| 52 |
+
$user = User::where('email', $request->email)->first();
|
| 53 |
+
|
| 54 |
+
if (!$user) {
|
| 55 |
+
return $this->errorResponse('User not found', 404);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
try {
|
| 59 |
+
$this->otpService->resetPasswordWithOtp($user, $request->otp, $request->password);
|
| 60 |
+
|
| 61 |
+
return $this->successResponse('Password reset successfully');
|
| 62 |
+
} catch (\Exception $e) {
|
| 63 |
+
return $this->errorResponse($e->getMessage(), 400);
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
}
|
app/Http/Controllers/Api/PasswordResetController.php
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Api\BaseController;
|
| 6 |
+
use App\Http\Requests\User\ResetPasswordRequest;
|
| 7 |
+
use App\Models\User;
|
| 8 |
+
use App\Http\Requests\User\CheckEmailRequest;
|
| 9 |
+
use App\Services\OtpService;
|
| 10 |
+
use App\Helpers\OtpHelper;
|
| 11 |
+
|
| 12 |
+
class PasswordResetController extends BaseController
|
| 13 |
+
{
|
| 14 |
+
|
| 15 |
+
protected $otpService;
|
| 16 |
+
|
| 17 |
+
public function __construct(OtpService $otpService)
|
| 18 |
+
{
|
| 19 |
+
$this->otpService = $otpService;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
/** ---------------- Password Reset ---------------- */
|
| 23 |
+
|
| 24 |
+
public function sendForgotPasswordOtp(CheckEmailRequest $request)
|
| 25 |
+
{
|
| 26 |
+
$data = OtpHelper::sendOtpByType($request->validated()['email'], 'password_reset', $this->otpService);
|
| 27 |
+
return $this->successResponse('OTP sent successfully', $data);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
public function resendResetPasswordOtp(CheckEmailRequest $request)
|
| 31 |
+
{
|
| 32 |
+
$data = OtpHelper::sendOtpByType($request->validated()['email'], 'password_reset', $this->otpService);
|
| 33 |
+
return $this->successResponse('OTP resent successfully', $data);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
public function resetPassword(ResetPasswordRequest $request)
|
| 37 |
+
{
|
| 38 |
+
$validated = $request->validated();
|
| 39 |
+
|
| 40 |
+
$user = User::where('email', $validated['email'])->firstOrFail();
|
| 41 |
+
|
| 42 |
+
$this->otpService->resetPasswordWithOtp(
|
| 43 |
+
$user,
|
| 44 |
+
$validated['otp'],
|
| 45 |
+
$validated['password']
|
| 46 |
+
);
|
| 47 |
+
|
| 48 |
+
return $this->successResponse('Password reset successfully');
|
| 49 |
+
}
|
| 50 |
+
}
|
app/Http/Controllers/Api/ScientistController.php
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Api\BaseController;
|
| 6 |
+
use App\Http\Resources\ScientistResource;
|
| 7 |
+
use App\Services\ScientistService;
|
| 8 |
+
use Illuminate\Http\Request;
|
| 9 |
+
|
| 10 |
+
class ScientistController extends BaseController
|
| 11 |
+
{
|
| 12 |
+
protected $service;
|
| 13 |
+
|
| 14 |
+
public function __construct(ScientistService $service)
|
| 15 |
+
{
|
| 16 |
+
$this->service = $service;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
public function index(Request $request)
|
| 20 |
+
{
|
| 21 |
+
$perPage = min(100, max(1, (int)$request->get('per_page', 10)));
|
| 22 |
+
|
| 23 |
+
$scientists = $this->service->getScientists($perPage);
|
| 24 |
+
|
| 25 |
+
return $this->successResponse(
|
| 26 |
+
'Scientists retrieved successfully',
|
| 27 |
+
[
|
| 28 |
+
'results' => ScientistResource::collection($scientists),
|
| 29 |
+
'pagination' => [
|
| 30 |
+
'currentPage' => $scientists->currentPage(),
|
| 31 |
+
'totalPages' => $scientists->lastPage(),
|
| 32 |
+
'totalResults' => $scientists->total(),
|
| 33 |
+
'perPage' => $scientists->perPage(),
|
| 34 |
+
'hasNextPage' => $scientists->hasMorePages(),
|
| 35 |
+
'hasPrevPage' => !$scientists->onFirstPage()
|
| 36 |
+
]
|
| 37 |
+
]
|
| 38 |
+
);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
public function show($id)
|
| 42 |
+
{
|
| 43 |
+
$scientist = $this->service->getScientist($id);
|
| 44 |
+
|
| 45 |
+
return $this->successResponse(
|
| 46 |
+
'Scientist retrieved successfully',
|
| 47 |
+
[
|
| 48 |
+
'results' => [new ScientistResource($scientist)],
|
| 49 |
+
'pagination' => [
|
| 50 |
+
'currentPage' => 1,
|
| 51 |
+
'totalPages' => 1,
|
| 52 |
+
'totalResults' => 1,
|
| 53 |
+
'perPage' => 1,
|
| 54 |
+
'hasNextPage' => false,
|
| 55 |
+
'hasPrevPage' => false
|
| 56 |
+
]
|
| 57 |
+
]
|
| 58 |
+
);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
public function getAwardsByScientist($id)
|
| 62 |
+
{
|
| 63 |
+
$scientist = $this->service->getScientist($id);
|
| 64 |
+
|
| 65 |
+
$awards = $scientist->awards;
|
| 66 |
+
|
| 67 |
+
return $this->successResponse(
|
| 68 |
+
'Awards retrieved successfully',
|
| 69 |
+
[
|
| 70 |
+
'results' => $awards,
|
| 71 |
+
'pagination' => [
|
| 72 |
+
'currentPage' => 1,
|
| 73 |
+
'totalPages' => 1,
|
| 74 |
+
'totalResults' => $awards->count(),
|
| 75 |
+
'perPage' => $awards->count(),
|
| 76 |
+
'hasNextPage' => false,
|
| 77 |
+
'hasPrevPage' => false
|
| 78 |
+
]
|
| 79 |
+
]
|
| 80 |
+
);
|
| 81 |
+
}
|
| 82 |
+
}
|
app/Http/Controllers/Api/ScreeningController.php
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Requests\Screen\ScreenRequest;
|
| 6 |
+
use App\Http\Requests\Screen\TargetLookupRequest;
|
| 7 |
+
use App\Models\ScreeningResult;
|
| 8 |
+
use App\Models\TargetLookup;
|
| 9 |
+
use App\Jobs\RunTargetLookupJob;
|
| 10 |
+
use App\Jobs\RunScreeningJob;
|
| 11 |
+
use App\Services\ScreeningService;
|
| 12 |
+
use Illuminate\Http\JsonResponse;
|
| 13 |
+
use Illuminate\Http\Request;
|
| 14 |
+
use Illuminate\Support\Facades\Auth;
|
| 15 |
+
|
| 16 |
+
class ScreeningController extends BaseController
|
| 17 |
+
{
|
| 18 |
+
public function __construct(protected ScreeningService $screeningService) {}
|
| 19 |
+
|
| 20 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 21 |
+
// POST /api/drug-repurposing/targets → upstream POST /api/v1/disease-targets
|
| 22 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 23 |
+
public function targets(TargetLookupRequest $request): JsonResponse
|
| 24 |
+
{
|
| 25 |
+
$input = $request->validated();
|
| 26 |
+
|
| 27 |
+
$lookup = TargetLookup::create([
|
| 28 |
+
'user_id' => Auth::id(),
|
| 29 |
+
'input' => $input,
|
| 30 |
+
'status' => 'pending',
|
| 31 |
+
]);
|
| 32 |
+
|
| 33 |
+
RunTargetLookupJob::dispatch($lookup);
|
| 34 |
+
|
| 35 |
+
return $this->successResponse('Target lookup queued successfully', [
|
| 36 |
+
'job_id' => $lookup->id,
|
| 37 |
+
'status' => $lookup->status,
|
| 38 |
+
]);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 42 |
+
// POST /api/drug-repurposing/screen → upstream POST /api/v1/screen
|
| 43 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 44 |
+
public function screen(ScreenRequest $request): JsonResponse
|
| 45 |
+
{
|
| 46 |
+
$input = $request->validated();
|
| 47 |
+
|
| 48 |
+
$result = ScreeningResult::create([
|
| 49 |
+
'user_id' => Auth::id(),
|
| 50 |
+
'input' => $input,
|
| 51 |
+
'status' => 'pending',
|
| 52 |
+
]);
|
| 53 |
+
|
| 54 |
+
RunScreeningJob::dispatch($result);
|
| 55 |
+
|
| 56 |
+
return $this->successResponse('Screening queued successfully', [
|
| 57 |
+
'job_id' => $result->id,
|
| 58 |
+
'status' => $result->status,
|
| 59 |
+
]);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 63 |
+
// GET /api/v1/screen/history/targets
|
| 64 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 65 |
+
public function historyTargets(Request $request): JsonResponse
|
| 66 |
+
{
|
| 67 |
+
$perPage = min((int) $request->query('per_page', 15), 100);
|
| 68 |
+
|
| 69 |
+
$paginator = TargetLookup::where('user_id', Auth::id())
|
| 70 |
+
->orderBy('created_at', 'desc')
|
| 71 |
+
->paginate($perPage, ['id', 'input', 'output', 'status', 'created_at']);
|
| 72 |
+
|
| 73 |
+
return $this->successResponse('Target lookup history retrieved successfully', [
|
| 74 |
+
'data' => $paginator->items(),
|
| 75 |
+
'pagination' => [
|
| 76 |
+
'current_page' => $paginator->currentPage(),
|
| 77 |
+
'per_page' => $paginator->perPage(),
|
| 78 |
+
'total' => $paginator->total(),
|
| 79 |
+
'last_page' => $paginator->lastPage(),
|
| 80 |
+
'has_more' => $paginator->hasMorePages(),
|
| 81 |
+
],
|
| 82 |
+
]);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 86 |
+
// GET /api/v1/screen/history/screening
|
| 87 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 88 |
+
public function historyScreening(Request $request): JsonResponse
|
| 89 |
+
{
|
| 90 |
+
$perPage = min((int) $request->query('per_page', 15), 100);
|
| 91 |
+
|
| 92 |
+
$paginator = ScreeningResult::where('user_id', Auth::id())
|
| 93 |
+
->orderBy('created_at', 'desc')
|
| 94 |
+
->paginate($perPage, ['id', 'input', 'output', 'status', 'created_at']);
|
| 95 |
+
|
| 96 |
+
return $this->successResponse('Screening history retrieved successfully', [
|
| 97 |
+
'data' => $paginator->items(),
|
| 98 |
+
'pagination' => [
|
| 99 |
+
'current_page' => $paginator->currentPage(),
|
| 100 |
+
'per_page' => $paginator->perPage(),
|
| 101 |
+
'total' => $paginator->total(),
|
| 102 |
+
'last_page' => $paginator->lastPage(),
|
| 103 |
+
'has_more' => $paginator->hasMorePages(),
|
| 104 |
+
],
|
| 105 |
+
]);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 109 |
+
// GET /api/v1/screen/targets/{id}
|
| 110 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 111 |
+
public function statusTargets(int $id): JsonResponse
|
| 112 |
+
{
|
| 113 |
+
$lookup = TargetLookup::where('id', $id)
|
| 114 |
+
->where('user_id', Auth::id())
|
| 115 |
+
->first();
|
| 116 |
+
|
| 117 |
+
if (!$lookup) {
|
| 118 |
+
return $this->errorResponse('Target lookup not found or unauthorized', 404);
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
return $this->successResponse('Target lookup status retrieved successfully', [
|
| 122 |
+
'job_id' => $lookup->id,
|
| 123 |
+
'status' => $lookup->status,
|
| 124 |
+
'input' => $lookup->input,
|
| 125 |
+
'output' => $lookup->output,
|
| 126 |
+
'created_at' => $lookup->created_at,
|
| 127 |
+
]);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 131 |
+
// GET /api/v1/screen/{id}
|
| 132 |
+
// ──────────────────────────────────────────────────────────────────────────
|
| 133 |
+
public function statusScreening(int $id): JsonResponse
|
| 134 |
+
{
|
| 135 |
+
$result = ScreeningResult::where('id', $id)
|
| 136 |
+
->where('user_id', Auth::id())
|
| 137 |
+
->first();
|
| 138 |
+
|
| 139 |
+
if (!$result) {
|
| 140 |
+
return $this->errorResponse('Screening result not found or unauthorized', 404);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
return $this->successResponse('Screening status retrieved successfully', [
|
| 144 |
+
'job_id' => $result->id,
|
| 145 |
+
'status' => $result->status,
|
| 146 |
+
'input' => $result->input,
|
| 147 |
+
'output' => $result->output,
|
| 148 |
+
'created_at' => $result->created_at,
|
| 149 |
+
]);
|
| 150 |
+
}
|
| 151 |
+
}
|
app/Http/Controllers/Api/SimulationController.php
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Models\Simulation;
|
| 6 |
+
use App\Jobs\RunMolecularDynamicsJob;
|
| 7 |
+
use Illuminate\Http\Request;
|
| 8 |
+
use Illuminate\Support\Facades\Auth;
|
| 9 |
+
use Illuminate\Support\Facades\Storage;
|
| 10 |
+
use Illuminate\Support\Facades\Validator;
|
| 11 |
+
|
| 12 |
+
class SimulationController extends BaseController
|
| 13 |
+
{
|
| 14 |
+
public function run(Request $request)
|
| 15 |
+
{
|
| 16 |
+
|
| 17 |
+
$validator = Validator::make($request->all(), [
|
| 18 |
+
'protein' => 'required|file|max:20480',
|
| 19 |
+
'ligand' => 'nullable|file|max:10240',
|
| 20 |
+
'force_field' => 'nullable|in:ff19SB,ff14SB',
|
| 21 |
+
'temperature' => 'nullable|numeric|min:200|max:400',
|
| 22 |
+
'simulation_time_ns' => 'nullable|numeric|min:1|max:500',
|
| 23 |
+
'box_size' => 'nullable|integer|min:8|max:25',
|
| 24 |
+
]);
|
| 25 |
+
|
| 26 |
+
if ($validator->fails()) {
|
| 27 |
+
return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
$simulationId = uniqid('sim_');
|
| 31 |
+
|
| 32 |
+
$simulationDir = storage_path("app/simulations/{$simulationId}");
|
| 33 |
+
if (!is_dir($simulationDir)) {
|
| 34 |
+
mkdir($simulationDir, 0777, true);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
$proteinFile = $request->file('protein');
|
| 39 |
+
$proteinPath = "simulations/{$simulationId}/protein.pdb";
|
| 40 |
+
$proteinFile->move($simulationDir, 'protein.pdb');
|
| 41 |
+
|
| 42 |
+
$ligandPath = null;
|
| 43 |
+
if ($request->hasFile('ligand')) {
|
| 44 |
+
$ligandFile = $request->file('ligand');
|
| 45 |
+
$ligandPath = "simulations/{$simulationId}/ligand.pdb";
|
| 46 |
+
$ligandFile->move($simulationDir, 'ligand.pdb');
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
$simulation = Simulation::create([
|
| 50 |
+
'user_id' => Auth::id(),
|
| 51 |
+
'protein' => $proteinPath,
|
| 52 |
+
'ligand' => $ligandPath,
|
| 53 |
+
'status' => 'pending',
|
| 54 |
+
'progress' => 0,
|
| 55 |
+
'force_field' => $request->input('force_field', 'ff14SB'),
|
| 56 |
+
'temperature' => $request->input('temperature', 298),
|
| 57 |
+
'simulation_time_ns' => $request->input('simulation_time_ns', 10),
|
| 58 |
+
'box_size' => $request->input('box_size', 12),
|
| 59 |
+
]);
|
| 60 |
+
|
| 61 |
+
RunMolecularDynamicsJob::dispatch($simulation);
|
| 62 |
+
|
| 63 |
+
return response()->json([
|
| 64 |
+
'success' => true,
|
| 65 |
+
'message' => 'Simulation started',
|
| 66 |
+
'data' => ['simulation_id' => $simulation->id, 'status' => $simulation->status]
|
| 67 |
+
], 201);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
public function status($id)
|
| 71 |
+
{
|
| 72 |
+
$simulation = Simulation::where('user_id', Auth::id())->findOrFail($id);
|
| 73 |
+
|
| 74 |
+
return response()->json([
|
| 75 |
+
'success' => true,
|
| 76 |
+
'data' => [
|
| 77 |
+
'id' => $simulation->id,
|
| 78 |
+
'status' => $simulation->status,
|
| 79 |
+
'progress' => $simulation->progress,
|
| 80 |
+
'error_message' => $simulation->error_message,
|
| 81 |
+
'created_at' => $simulation->created_at,
|
| 82 |
+
'results' => $simulation->isCompleted() ? $simulation->analysis : null,
|
| 83 |
+
]
|
| 84 |
+
]);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
public function index(Request $request)
|
| 88 |
+
{
|
| 89 |
+
$simulations = Simulation::where('user_id', Auth::id())
|
| 90 |
+
->orderBy('created_at', 'desc')
|
| 91 |
+
->paginate($request->input('per_page', 15));
|
| 92 |
+
|
| 93 |
+
return response()->json(['success' => true, 'data' => $simulations->items()]);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
public function destroy($id)
|
| 97 |
+
{
|
| 98 |
+
$simulation = Simulation::where('user_id', Auth::id())->findOrFail($id);
|
| 99 |
+
|
| 100 |
+
if ($simulation->protein && Storage::exists($simulation->protein)) {
|
| 101 |
+
Storage::delete($simulation->protein);
|
| 102 |
+
}
|
| 103 |
+
if ($simulation->ligand && Storage::exists($simulation->ligand)) {
|
| 104 |
+
Storage::delete($simulation->ligand);
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
$simDir = dirname($simulation->protein);
|
| 108 |
+
if (Storage::exists($simDir)) {
|
| 109 |
+
Storage::deleteDirectory($simDir);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
$simulation->delete();
|
| 113 |
+
|
| 114 |
+
return response()->json(['success' => true, 'message' => 'Deleted']);
|
| 115 |
+
}
|
| 116 |
+
}
|
app/Http/Controllers/Api/UserController.php
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers\Api;
|
| 4 |
+
|
| 5 |
+
use App\Http\Controllers\Api\BaseController;
|
| 6 |
+
use App\Http\Requests\User\ProfileRequest;
|
| 7 |
+
use Illuminate\Http\Request;
|
| 8 |
+
use App\Services\ProfileService;
|
| 9 |
+
|
| 10 |
+
class UserController extends BaseController
|
| 11 |
+
{
|
| 12 |
+
|
| 13 |
+
protected $profileService;
|
| 14 |
+
|
| 15 |
+
public function __construct(ProfileService $profileService)
|
| 16 |
+
{
|
| 17 |
+
$this->profileService = $profileService;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
/** ---------------- Profile ---------------- */
|
| 21 |
+
|
| 22 |
+
public function profile(Request $request)
|
| 23 |
+
{
|
| 24 |
+
$user = $request->user()->load('researcher');
|
| 25 |
+
|
| 26 |
+
return $this->successResponse('Profile retrieved', [
|
| 27 |
+
'results' => [
|
| 28 |
+
[
|
| 29 |
+
'id' => $user->id,
|
| 30 |
+
'name' => $user->name,
|
| 31 |
+
'email' => $user->email,
|
| 32 |
+
'role' => $user->role,
|
| 33 |
+
'researcher' => $user->researcher,
|
| 34 |
+
'photo' => $user->photo,
|
| 35 |
+
]
|
| 36 |
+
],
|
| 37 |
+
'pagination' => [
|
| 38 |
+
'currentPage' => 1,
|
| 39 |
+
'totalPages' => 1,
|
| 40 |
+
'totalResults' => 1,
|
| 41 |
+
'perPage' => 1,
|
| 42 |
+
'hasNextPage' => false,
|
| 43 |
+
'hasPrevPage' => false
|
| 44 |
+
]
|
| 45 |
+
]);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
public function updateProfile(ProfileRequest $request)
|
| 49 |
+
{
|
| 50 |
+
$result = $this->profileService->updateProfile(
|
| 51 |
+
$request->user(),
|
| 52 |
+
$request->validated(),
|
| 53 |
+
$request
|
| 54 |
+
);
|
| 55 |
+
|
| 56 |
+
return $this->successResponse('Profile updated successfully', [
|
| 57 |
+
'results' => [$result],
|
| 58 |
+
'pagination' => [
|
| 59 |
+
'currentPage' => 1,
|
| 60 |
+
'totalPages' => 1,
|
| 61 |
+
'totalResults' => 1,
|
| 62 |
+
'perPage' => 1,
|
| 63 |
+
'hasNextPage' => false,
|
| 64 |
+
'hasPrevPage' => false
|
| 65 |
+
]
|
| 66 |
+
]);
|
| 67 |
+
}
|
| 68 |
+
}
|
app/Http/Controllers/ApiDocsController.php
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers;
|
| 4 |
+
|
| 5 |
+
use Illuminate\Routing\Controller;
|
| 6 |
+
use Illuminate\Support\Facades\Route;
|
| 7 |
+
|
| 8 |
+
class ApiDocsController extends Controller
|
| 9 |
+
{
|
| 10 |
+
public function index()
|
| 11 |
+
{
|
| 12 |
+
$routes = collect(Route::getRoutes())->map(function ($route) {
|
| 13 |
+
if (str_starts_with($route->uri(), 'api/')) {
|
| 14 |
+
return [
|
| 15 |
+
'method' => $route->methods()[0] === 'HEAD' ? 'GET' : $route->methods()[0],
|
| 16 |
+
'path' => '/' . $route->uri(),
|
| 17 |
+
];
|
| 18 |
+
}
|
| 19 |
+
return null;
|
| 20 |
+
})->filter()->unique('path')->sortBy('path')->values();
|
| 21 |
+
|
| 22 |
+
return view('api-endpoints', compact('routes'));
|
| 23 |
+
}
|
| 24 |
+
}
|
app/Http/Controllers/DockingTestController.php
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Controllers;
|
| 4 |
+
|
| 5 |
+
use Illuminate\Routing\Controller;
|
| 6 |
+
use App\Jobs\RunDockingJob;
|
| 7 |
+
use App\Models\DockingJob;
|
| 8 |
+
use App\Traits\ApiResponseTrait;
|
| 9 |
+
use Illuminate\Http\Request;
|
| 10 |
+
use Illuminate\Support\Facades\Log;
|
| 11 |
+
use Illuminate\Support\Facades\Process;
|
| 12 |
+
use Illuminate\Support\Facades\Validator;
|
| 13 |
+
use Illuminate\Support\Str;
|
| 14 |
+
|
| 15 |
+
class DockingTestController extends Controller
|
| 16 |
+
{
|
| 17 |
+
use ApiResponseTrait;
|
| 18 |
+
|
| 19 |
+
public function testDockingSubmit(Request $request)
|
| 20 |
+
{
|
| 21 |
+
$validator = Validator::make($request->all(), [
|
| 22 |
+
'protein_name' => 'required|string|max:255',
|
| 23 |
+
'ligand_name' => 'nullable|string|max:255',
|
| 24 |
+
'ligand_file' => 'nullable|file|required_without:ligand_smiles',
|
| 25 |
+
'ligand_smiles' => 'nullable|string|max:2000|required_without:ligand_file',
|
| 26 |
+
'protein_file' => 'required|file',
|
| 27 |
+
'center_x' => 'required|numeric',
|
| 28 |
+
'center_y' => 'required|numeric',
|
| 29 |
+
'center_z' => 'required|numeric',
|
| 30 |
+
'box_size_x' => 'required|numeric',
|
| 31 |
+
'box_size_y' => 'required|numeric',
|
| 32 |
+
'box_size_z' => 'required|numeric',
|
| 33 |
+
'exhaustiveness' => 'nullable|integer',
|
| 34 |
+
'n_poses' => 'nullable|integer',
|
| 35 |
+
]);
|
| 36 |
+
|
| 37 |
+
$validator->after(function ($validator) use ($request) {
|
| 38 |
+
if ($request->hasFile('ligand_file') && $request->filled('ligand_smiles')) {
|
| 39 |
+
$validator->errors()->add(
|
| 40 |
+
'ligand_input',
|
| 41 |
+
'Provide either a ligand file or a SMILES string, not both.'
|
| 42 |
+
);
|
| 43 |
+
}
|
| 44 |
+
});
|
| 45 |
+
|
| 46 |
+
if ($validator->fails()) {
|
| 47 |
+
return $this->errorResponse('Validation Error', 422, $validator->errors());
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
$isSmiles = $request->filled('ligand_smiles');
|
| 51 |
+
|
| 52 |
+
// ---- Handle Ligand ----
|
| 53 |
+
if ($isSmiles) {
|
| 54 |
+
// SMILES → PDBQT conversion (synchronous, fast ~1-3s)
|
| 55 |
+
$conversion = $this->convertSmilesToPdbqt($request->ligand_smiles);
|
| 56 |
+
|
| 57 |
+
if ($conversion['status'] === 'error') {
|
| 58 |
+
return $this->errorResponse(
|
| 59 |
+
'SMILES conversion failed: ' . $conversion['message'],
|
| 60 |
+
422
|
| 61 |
+
);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
$ligandPath = $conversion['output_file'];
|
| 65 |
+
} else {
|
| 66 |
+
$ligandFilename = Str::random(40) . '.pdbqt';
|
| 67 |
+
$ligandPath = storage_path('app/private/' . $request->file('ligand_file')->storeAs('docking', $ligandFilename));
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// ---- Handle Protein ----
|
| 71 |
+
$proteinFilename = Str::random(40) . '.pdbqt';
|
| 72 |
+
$proteinPath = storage_path('app/private/' . $request->file('protein_file')->storeAs('docking', $proteinFilename));
|
| 73 |
+
|
| 74 |
+
// ---- Create Database Record ----
|
| 75 |
+
$job = DockingJob::create([
|
| 76 |
+
'user_id' => $request->user()->id,
|
| 77 |
+
'input_type' => $isSmiles ? 'smiles' : 'file',
|
| 78 |
+
'smiles' => $isSmiles ? $request->ligand_smiles : null,
|
| 79 |
+
'protein_name' => $request->protein_name,
|
| 80 |
+
'ligand_name' => $request->ligand_name,
|
| 81 |
+
'protein_path' => $proteinPath,
|
| 82 |
+
'ligand_path' => $ligandPath,
|
| 83 |
+
'status' => 'pending',
|
| 84 |
+
]);
|
| 85 |
+
|
| 86 |
+
// ---- Dispatch background docking job ----
|
| 87 |
+
RunDockingJob::dispatch($job, [
|
| 88 |
+
'center_x' => $request->center_x,
|
| 89 |
+
'center_y' => $request->center_y,
|
| 90 |
+
'center_z' => $request->center_z,
|
| 91 |
+
'box_size_x' => $request->box_size_x,
|
| 92 |
+
'box_size_y' => $request->box_size_y,
|
| 93 |
+
'box_size_z' => $request->box_size_z,
|
| 94 |
+
'exhaustiveness' => $request->exhaustiveness ?? 8,
|
| 95 |
+
'n_poses' => $request->n_poses ?? 5,
|
| 96 |
+
]);
|
| 97 |
+
|
| 98 |
+
return $this->successResponse('Docking Job Successfully Queued', [
|
| 99 |
+
'job_id' => $job->id,
|
| 100 |
+
'status' => $job->status,
|
| 101 |
+
]);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
private function convertSmilesToPdbqt(string $smiles): array
|
| 105 |
+
{
|
| 106 |
+
$pythonPath = env('DOCKING_PYTHON_PATH', base_path('vina_env/bin/python'));
|
| 107 |
+
$scriptPath = env('SMILES_SCRIPT_PATH', base_path('scripts/smiles_to_pdbqt.py'));
|
| 108 |
+
|
| 109 |
+
// Generate unique output path
|
| 110 |
+
$outputFilename = Str::random(40) . '.pdbqt';
|
| 111 |
+
$outputDir = storage_path('app/private/docking/generated');
|
| 112 |
+
$outputPath = $outputDir . '/' . $outputFilename;
|
| 113 |
+
|
| 114 |
+
// Ensure output directory exists
|
| 115 |
+
if (! is_dir($outputDir)) {
|
| 116 |
+
mkdir($outputDir, 0755, true);
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
$cmd = sprintf(
|
| 120 |
+
'%s %s %s %s',
|
| 121 |
+
escapeshellarg($pythonPath),
|
| 122 |
+
escapeshellarg($scriptPath),
|
| 123 |
+
escapeshellarg($smiles),
|
| 124 |
+
escapeshellarg($outputPath)
|
| 125 |
+
);
|
| 126 |
+
|
| 127 |
+
try {
|
| 128 |
+
$result = Process::timeout(120)->run($cmd);
|
| 129 |
+
|
| 130 |
+
$outputData = null;
|
| 131 |
+
$fullOutput = $result->output();
|
| 132 |
+
|
| 133 |
+
if (preg_match('/\{"status":\s*"(success|error)".*\}/s', $fullOutput, $matches)) {
|
| 134 |
+
$outputData = json_decode($matches[0], true);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
if (! $result->successful()) {
|
| 138 |
+
$errorMsg = $outputData['message'] ?? $result->errorOutput() ?: 'Unknown conversion error';
|
| 139 |
+
Log::error('SMILES conversion failed', ['smiles' => $smiles, 'error' => $errorMsg]);
|
| 140 |
+
return ['status' => 'error', 'message' => $errorMsg];
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if ($outputData && $outputData['status'] === 'success') {
|
| 144 |
+
return $outputData;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
if ($outputData && $outputData['status'] === 'error') {
|
| 148 |
+
return $outputData;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
return ['status' => 'error', 'message' => 'Unexpected output from conversion script'];
|
| 152 |
+
|
| 153 |
+
} catch (\Exception $e) {
|
| 154 |
+
Log::error('SMILES conversion exception', ['smiles' => $smiles, 'error' => $e->getMessage()]);
|
| 155 |
+
return ['status' => 'error', 'message' => $e->getMessage()];
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
}
|
app/Http/Middleware/CheckJobOwnership.php
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Middleware;
|
| 4 |
+
|
| 5 |
+
use Closure;
|
| 6 |
+
use Illuminate\Http\Request;
|
| 7 |
+
use App\Models\AiJob;
|
| 8 |
+
use App\Models\LigandExport;
|
| 9 |
+
use Illuminate\Support\Facades\Auth;
|
| 10 |
+
|
| 11 |
+
class CheckJobOwnership
|
| 12 |
+
{
|
| 13 |
+
public function handle(Request $request, Closure $next)
|
| 14 |
+
{
|
| 15 |
+
$jobId = $request->route('job_id');
|
| 16 |
+
|
| 17 |
+
if (!$jobId) {
|
| 18 |
+
return response()->json([
|
| 19 |
+
'success' => false,
|
| 20 |
+
'message' => 'Job ID is required'
|
| 21 |
+
], 400);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
$aiJob = AiJob::where('job_id', $jobId)
|
| 25 |
+
->where('user_id', Auth::id())
|
| 26 |
+
->first();
|
| 27 |
+
|
| 28 |
+
$ligandExport = LigandExport::where('job_id', $jobId)
|
| 29 |
+
->where('user_id', Auth::id())
|
| 30 |
+
->first();
|
| 31 |
+
|
| 32 |
+
if (!$aiJob && !$ligandExport) {
|
| 33 |
+
return response()->json([
|
| 34 |
+
'success' => false,
|
| 35 |
+
'message' => 'You do not have permission to access this job'
|
| 36 |
+
], 403);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
$request->merge(['ai_job' => $aiJob]);
|
| 40 |
+
$request->merge(['ligand_export' => $ligandExport]);
|
| 41 |
+
|
| 42 |
+
return $next($request);
|
| 43 |
+
}
|
| 44 |
+
}
|
app/Http/Middleware/EnsureChemistryApiAvailable.php
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Middleware;
|
| 4 |
+
|
| 5 |
+
use Closure;
|
| 6 |
+
use Illuminate\Http\Request;
|
| 7 |
+
use Symfony\Component\HttpFoundation\Response;
|
| 8 |
+
|
| 9 |
+
class EnsureChemistryApiAvailable
|
| 10 |
+
{
|
| 11 |
+
/**
|
| 12 |
+
* Handle an incoming request.
|
| 13 |
+
*
|
| 14 |
+
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
| 15 |
+
*/
|
| 16 |
+
public function handle(Request $request, Closure $next): Response
|
| 17 |
+
{
|
| 18 |
+
return $next($request);
|
| 19 |
+
}
|
| 20 |
+
}
|
app/Http/Requests/Ai/GenerationRequest.php
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
|
| 3 |
+
namespace App\Http\Requests\Ai;
|
| 4 |
+
|
| 5 |
+
use Illuminate\Foundation\Http\FormRequest;
|
| 6 |
+
|
| 7 |
+
class GenerationRequest extends FormRequest
|
| 8 |
+
{
|
| 9 |
+
public function authorize(): bool
|
| 10 |
+
{
|
| 11 |
+
return true;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
public function rules(): array
|
| 15 |
+
{
|
| 16 |
+
return [
|
| 17 |
+
'num_molecules' => 'required|integer|min:1',
|
| 18 |
+
'return_top_k' => 'required|integer|min:1|lte:num_molecules',
|
| 19 |
+
'docking_mode' => 'required|string|in:top_k,off,all',
|
| 20 |
+
'dock_top_k' => 'exclude_unless:docking_mode,top_k|required|integer|min:0|lte:return_top_k',
|
| 21 |
+
];
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
public function messages(): array
|
| 25 |
+
{
|
| 26 |
+
return [
|
| 27 |
+
'dock_top_k.required' => 'Dock top K is required when docking mode is top_k',
|
| 28 |
+
'dock_top_k.lte' => 'Dock top K must be less than or equal to return top K',
|
| 29 |
+
];
|
| 30 |
+
}
|
| 31 |
+
}
|