RottenShadow commited on
Commit
082811f
·
1 Parent(s): 5efbe3f

Deploy to Hugging Face Spaces

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +21 -0
  2. .editorconfig +18 -0
  3. .env +19 -0
  4. .env.docker +29 -0
  5. .env.example +15 -0
  6. .gitattributes +16 -33
  7. .github/workflows/api-tests.yml +140 -0
  8. .github/workflows/integration-test.yml +556 -0
  9. .gitignore +10 -0
  10. .scribe/.filehashes +4 -0
  11. .scribe/auth.md +3 -0
  12. .scribe/endpoints.cache/00.yaml +1503 -0
  13. .scribe/endpoints/00.yaml +1501 -0
  14. .scribe/endpoints/custom.0.yaml +53 -0
  15. .scribe/intro.md +13 -0
  16. API.md +3095 -0
  17. ARCHITECTURE.md +224 -0
  18. DOCKER.md +63 -0
  19. DOCKER_FIXES.md +231 -0
  20. DOCUMENTATION_STRUCTURE.md +449 -0
  21. Dockerfile +116 -0
  22. README.md +105 -8
  23. _ide_helper.php +0 -0
  24. _ide_helper_models.php +715 -0
  25. app/Helpers/OtpHelper.php +28 -0
  26. app/Http/Controllers/Api/AdmetController.php +29 -0
  27. app/Http/Controllers/Api/AiServicesIntegrationController.php +119 -0
  28. app/Http/Controllers/Api/AuthController.php +117 -0
  29. app/Http/Controllers/Api/AwardController.php +52 -0
  30. app/Http/Controllers/Api/BaseController.php +13 -0
  31. app/Http/Controllers/Api/ChemicalSearchController.php +79 -0
  32. app/Http/Controllers/Api/ChemistryController.php +415 -0
  33. app/Http/Controllers/Api/ConvertSmilesController.php +180 -0
  34. app/Http/Controllers/Api/DockingController.php +254 -0
  35. app/Http/Controllers/Api/EmailVerificationController.php +85 -0
  36. app/Http/Controllers/Api/GenerationController.php +254 -0
  37. app/Http/Controllers/Api/LigandsController.php +65 -0
  38. app/Http/Controllers/Api/MdFileController.php +81 -0
  39. app/Http/Controllers/Api/NewsController.php +257 -0
  40. app/Http/Controllers/Api/OtpController.php +66 -0
  41. app/Http/Controllers/Api/PasswordResetController.php +50 -0
  42. app/Http/Controllers/Api/ScientistController.php +82 -0
  43. app/Http/Controllers/Api/ScreeningController.php +151 -0
  44. app/Http/Controllers/Api/SimulationController.php +116 -0
  45. app/Http/Controllers/Api/UserController.php +68 -0
  46. app/Http/Controllers/ApiDocsController.php +24 -0
  47. app/Http/Controllers/DockingTestController.php +158 -0
  48. app/Http/Middleware/CheckJobOwnership.php +44 -0
  49. app/Http/Middleware/EnsureChemistryApiAvailable.php +20 -0
  50. 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
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
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
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
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
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
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
- title: Ailixir Api
3
- emoji: 📚
4
- colorFrom: red
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
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
+ }