Commit Β·
538e152
1
Parent(s): 018e5ef
Add MGZon logo to OAuth button
Browse files- public/images/mgzon-logo.png +0 -0
- views/index.ejs +385 -63
public/images/mgzon-logo.png
ADDED
|
views/index.ejs
CHANGED
|
@@ -5,21 +5,44 @@
|
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
<meta name="description" content="Ibrahim Al-Asfar Portfolio API - Full-stack web developer portfolio backend API">
|
| 8 |
-
<meta name="keywords" content="portfolio, API, Node.js, Express, MongoDB, developer">
|
| 9 |
<meta name="author" content="Ibrahim Al-Asfar">
|
|
|
|
|
|
|
|
|
|
| 10 |
<meta property="og:title" content="Ibrahim Al-Asfar Portfolio API">
|
| 11 |
-
<meta property="og:description"
|
|
|
|
| 12 |
<meta property="og:type" content="website">
|
| 13 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
<!-- Tailwind CSS -->
|
| 16 |
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
| 17 |
<!-- Font Awesome -->
|
| 18 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
| 19 |
|
| 20 |
-
|
| 21 |
<!-- Custom Styles -->
|
| 22 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
@keyframes fadeInUp {
|
| 24 |
from {
|
| 25 |
opacity: 0;
|
|
@@ -32,16 +55,39 @@
|
|
| 32 |
}
|
| 33 |
}
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
.animate-fadeInUp {
|
| 36 |
animation: fadeInUp 0.6s ease-out;
|
| 37 |
}
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
.gradient-bg {
|
| 40 |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 41 |
}
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
.card-hover {
|
| 44 |
-
transition: all 0.3s
|
| 45 |
}
|
| 46 |
|
| 47 |
.card-hover:hover {
|
|
@@ -49,18 +95,29 @@
|
|
| 49 |
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
| 50 |
}
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
/* Custom scrollbar */
|
| 53 |
::-webkit-scrollbar {
|
| 54 |
-
width:
|
| 55 |
}
|
| 56 |
|
| 57 |
::-webkit-scrollbar-track {
|
| 58 |
background: #f1f1f1;
|
|
|
|
| 59 |
}
|
| 60 |
|
| 61 |
::-webkit-scrollbar-thumb {
|
| 62 |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 63 |
-
border-radius:
|
| 64 |
}
|
| 65 |
|
| 66 |
::-webkit-scrollbar-thumb:hover {
|
|
@@ -69,13 +126,19 @@
|
|
| 69 |
|
| 70 |
/* Logo styling */
|
| 71 |
.logo-img {
|
| 72 |
-
width:
|
| 73 |
-
height:
|
| 74 |
object-fit: contain;
|
| 75 |
-
border-radius:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
}
|
| 77 |
|
| 78 |
-
/* Fallback styles
|
| 79 |
.bg-gradient-to-br {
|
| 80 |
background: linear-gradient(to bottom right, #f9fafb, #f3f4f6);
|
| 81 |
}
|
|
@@ -88,6 +151,10 @@
|
|
| 88 |
border-radius: 0.5rem;
|
| 89 |
}
|
| 90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
.p-2 {
|
| 92 |
padding: 0.5rem;
|
| 93 |
}
|
|
@@ -96,6 +163,10 @@
|
|
| 96 |
padding: 0.75rem;
|
| 97 |
}
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
.p-5 {
|
| 100 |
padding: 1.25rem;
|
| 101 |
}
|
|
@@ -109,6 +180,11 @@
|
|
| 109 |
padding-bottom: 2rem;
|
| 110 |
}
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
.py-16 {
|
| 113 |
padding-top: 4rem;
|
| 114 |
padding-bottom: 4rem;
|
|
@@ -124,6 +200,11 @@
|
|
| 124 |
padding-right: 1rem;
|
| 125 |
}
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
.text-center {
|
| 128 |
text-align: center;
|
| 129 |
}
|
|
@@ -176,6 +257,10 @@
|
|
| 176 |
background-color: #10b981;
|
| 177 |
}
|
| 178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
.mb-2 {
|
| 180 |
margin-bottom: 0.5rem;
|
| 181 |
}
|
|
@@ -204,6 +289,10 @@
|
|
| 204 |
margin-right: 0.5rem;
|
| 205 |
}
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
.flex {
|
| 208 |
display: flex;
|
| 209 |
}
|
|
@@ -261,6 +350,14 @@
|
|
| 261 |
height: 3rem;
|
| 262 |
}
|
| 263 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
.w-3 {
|
| 265 |
width: 0.75rem;
|
| 266 |
}
|
|
@@ -273,8 +370,8 @@
|
|
| 273 |
border-radius: 9999px;
|
| 274 |
}
|
| 275 |
|
| 276 |
-
.
|
| 277 |
-
|
| 278 |
}
|
| 279 |
|
| 280 |
.animate-pulse {
|
|
@@ -334,8 +431,12 @@
|
|
| 334 |
transition-duration: 150ms;
|
| 335 |
}
|
| 336 |
|
| 337 |
-
.hover\:shadow-
|
| 338 |
-
box-shadow: 0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
}
|
| 340 |
|
| 341 |
.hover\:bg-gray-900:hover {
|
|
@@ -346,6 +447,10 @@
|
|
| 346 |
color: #7c3aed;
|
| 347 |
}
|
| 348 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
@media (min-width: 640px) {
|
| 350 |
.sm\:px-6 {
|
| 351 |
padding-left: 1.5rem;
|
|
@@ -362,6 +467,10 @@
|
|
| 362 |
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 363 |
}
|
| 364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
.md\:text-6xl {
|
| 366 |
font-size: 3.75rem;
|
| 367 |
}
|
|
@@ -372,27 +481,67 @@
|
|
| 372 |
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 373 |
}
|
| 374 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
.lg\:px-8 {
|
| 376 |
padding-left: 2rem;
|
| 377 |
padding-right: 2rem;
|
| 378 |
}
|
| 379 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
</style>
|
| 381 |
</head>
|
| 382 |
|
| 383 |
<body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
|
| 384 |
|
| 385 |
<!-- Navigation -->
|
| 386 |
-
<nav class="bg-white shadow-lg sticky top-0 z-50">
|
| 387 |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 388 |
<div class="flex justify-between items-center h-16">
|
| 389 |
<div class="flex items-center space-x-3">
|
| 390 |
<!-- Logo Image -->
|
| 391 |
-
<img src="images/logo.png" alt="Logo" class="logo-img"
|
| 392 |
onerror="this.onerror=null; this.parentElement.innerHTML='<div class=\'gradient-bg rounded-lg p-2\'><i class=\'fas fa-code text-white text-xl\'></i></div>'">
|
| 393 |
-
<h1
|
| 394 |
-
|
| 395 |
-
Portfolio API
|
| 396 |
</h1>
|
| 397 |
</div>
|
| 398 |
<div class="flex items-center space-x-4">
|
|
@@ -420,10 +569,9 @@
|
|
| 420 |
<div class="text-center animate-fadeInUp">
|
| 421 |
<div class="inline-flex items-center justify-center p-2 bg-green-100 rounded-full mb-4">
|
| 422 |
<i class="fas fa-check-circle text-green-600 mr-2"></i>
|
| 423 |
-
<span class="text-green-600 text-sm font-medium">API is running</span>
|
| 424 |
</div>
|
| 425 |
-
<h1
|
| 426 |
-
class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-purple-600 via-blue-600 to-purple-600 bg-clip-text text-transparent mb-6">
|
| 427 |
Welcome to My Portfolio API
|
| 428 |
</h1>
|
| 429 |
<p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
|
|
@@ -432,14 +580,15 @@
|
|
| 432 |
</p>
|
| 433 |
<div class="flex flex-wrap justify-center gap-4">
|
| 434 |
<a href="/api-docs"
|
| 435 |
-
class="gradient-bg text-white px-8 py-3 rounded-lg font-semibold
|
| 436 |
<i class="fas fa-rocket mr-2"></i>
|
| 437 |
Explore API
|
| 438 |
</a>
|
| 439 |
-
<a href="
|
| 440 |
-
class="bg-gray-800 text-white px-8 py-3 rounded-lg font-semibold
|
| 441 |
-
|
| 442 |
-
|
|
|
|
| 443 |
</a>
|
| 444 |
</div>
|
| 445 |
</div>
|
|
@@ -456,7 +605,7 @@
|
|
| 456 |
<h3 class="text-2xl font-bold text-gray-800 mb-2">Database Status</h3>
|
| 457 |
<div class="flex items-center">
|
| 458 |
<div class="w-3 h-3 bg-green-500 rounded-full animate-pulse mr-2"></div>
|
| 459 |
-
<p class="text-gray-600" id="db-status">
|
| 460 |
</div>
|
| 461 |
</div>
|
| 462 |
|
|
@@ -465,7 +614,7 @@
|
|
| 465 |
<i class="fas fa-cloud-upload-alt text-white text-xl"></i>
|
| 466 |
</div>
|
| 467 |
<h3 class="text-2xl font-bold text-gray-800 mb-2">Cloudinary</h3>
|
| 468 |
-
<p class="text-gray-600" id="cloudinary-status">
|
| 469 |
</div>
|
| 470 |
|
| 471 |
<div class="bg-white rounded-xl shadow-lg p-6 card-hover animate-fadeInUp" style="animation-delay: 0.3s">
|
|
@@ -473,67 +622,176 @@
|
|
| 473 |
<i class="fas fa-chart-line text-white text-xl"></i>
|
| 474 |
</div>
|
| 475 |
<h3 class="text-2xl font-bold text-gray-800 mb-2">Analytics</h3>
|
| 476 |
-
<p class="text-gray-600" id="sentry-status">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
</div>
|
| 478 |
</div>
|
| 479 |
</div>
|
| 480 |
|
| 481 |
<!-- API Endpoints Section -->
|
| 482 |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
| 483 |
-
<h2 class="text-3xl font-bold text-center text-gray-800 mb-12">API Endpoints</h2>
|
| 484 |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 485 |
-
<div class="bg-white rounded-lg shadow-md p-5
|
| 486 |
<div class="flex items-center justify-between mb-3">
|
| 487 |
<span class="bg-green-100 text-green-600 text-xs font-semibold px-2 py-1 rounded">GET</span>
|
| 488 |
<i class="fas fa-users text-gray-400"></i>
|
| 489 |
</div>
|
| 490 |
<code class="text-sm text-gray-700">/api/projects</code>
|
| 491 |
<p class="text-gray-500 text-sm mt-2">Get all public projects</p>
|
|
|
|
|
|
|
| 492 |
</div>
|
| 493 |
|
| 494 |
-
<div class="bg-white rounded-lg shadow-md p-5
|
| 495 |
<div class="flex items-center justify-between mb-3">
|
| 496 |
<span class="bg-green-100 text-green-600 text-xs font-semibold px-2 py-1 rounded">GET</span>
|
| 497 |
<i class="fas fa-chart-simple text-gray-400"></i>
|
| 498 |
</div>
|
| 499 |
<code class="text-sm text-gray-700">/api/skills</code>
|
| 500 |
<p class="text-gray-500 text-sm mt-2">Get all skills</p>
|
|
|
|
|
|
|
| 501 |
</div>
|
| 502 |
|
| 503 |
-
<div class="bg-white rounded-lg shadow-md p-5
|
| 504 |
<div class="flex items-center justify-between mb-3">
|
| 505 |
<span class="bg-green-100 text-green-600 text-xs font-semibold px-2 py-1 rounded">GET</span>
|
| 506 |
<i class="fas fa-heart text-gray-400"></i>
|
| 507 |
</div>
|
| 508 |
<code class="text-sm text-gray-700">/api/health</code>
|
| 509 |
<p class="text-gray-500 text-sm mt-2">Check system health</p>
|
|
|
|
|
|
|
| 510 |
</div>
|
| 511 |
|
| 512 |
-
<div class="bg-white rounded-lg shadow-md p-5
|
| 513 |
<div class="flex items-center justify-between mb-3">
|
| 514 |
<span class="bg-blue-100 text-blue-600 text-xs font-semibold px-2 py-1 rounded">POST</span>
|
| 515 |
<i class="fas fa-user-plus text-gray-400"></i>
|
| 516 |
</div>
|
| 517 |
<code class="text-sm text-gray-700">/api/register</code>
|
| 518 |
<p class="text-gray-500 text-sm mt-2">Create new account</p>
|
|
|
|
|
|
|
|
|
|
| 519 |
</div>
|
| 520 |
|
| 521 |
-
<div class="bg-white rounded-lg shadow-md p-5
|
| 522 |
<div class="flex items-center justify-between mb-3">
|
| 523 |
<span class="bg-blue-100 text-blue-600 text-xs font-semibold px-2 py-1 rounded">POST</span>
|
| 524 |
<i class="fas fa-sign-in-alt text-gray-400"></i>
|
| 525 |
</div>
|
| 526 |
<code class="text-sm text-gray-700">/api/login</code>
|
| 527 |
<p class="text-gray-500 text-sm mt-2">Authenticate user</p>
|
|
|
|
|
|
|
| 528 |
</div>
|
| 529 |
|
| 530 |
-
<div class="bg-white rounded-lg shadow-md p-5
|
| 531 |
<div class="flex items-center justify-between mb-3">
|
| 532 |
-
<span class="bg-purple-100 text-purple-600 text-xs font-semibold px-2 py-1 rounded">
|
| 533 |
<i class="fab fa-google text-gray-400"></i>
|
| 534 |
</div>
|
| 535 |
<code class="text-sm text-gray-700">/auth/google</code>
|
| 536 |
-
<p class="text-gray-500 text-sm mt-2">Google OAuth</p>
|
|
|
|
|
|
|
| 537 |
</div>
|
| 538 |
</div>
|
| 539 |
</div>
|
|
@@ -541,7 +799,7 @@
|
|
| 541 |
<!-- Tech Stack Section -->
|
| 542 |
<div class="bg-white py-16">
|
| 543 |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 544 |
-
<h2 class="text-3xl font-bold text-center text-gray-800 mb-12">Technology Stack</h2>
|
| 545 |
<div class="flex flex-wrap justify-center gap-8">
|
| 546 |
<div class="text-center">
|
| 547 |
<i class="fab fa-node-js text-5xl text-green-600"></i>
|
|
@@ -559,7 +817,6 @@
|
|
| 559 |
<i class="fas fa-project-diagram text-5xl text-pink-500"></i>
|
| 560 |
<p class="mt-2 text-gray-600">GraphQL</p>
|
| 561 |
</div>
|
| 562 |
-
|
| 563 |
<div class="text-center">
|
| 564 |
<i class="fab fa-docker text-5xl text-blue-600"></i>
|
| 565 |
<p class="mt-2 text-gray-600">Docker</p>
|
|
@@ -568,6 +825,14 @@
|
|
| 568 |
<i class="fab fa-aws text-5xl text-orange-500"></i>
|
| 569 |
<p class="mt-2 text-gray-600">AWS</p>
|
| 570 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
</div>
|
| 572 |
</div>
|
| 573 |
</div>
|
|
@@ -583,7 +848,46 @@
|
|
| 583 |
</div>
|
| 584 |
</footer>
|
| 585 |
|
|
|
|
|
|
|
|
|
|
| 586 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 587 |
// Fetch real-time data
|
| 588 |
async function fetchStats() {
|
| 589 |
try {
|
|
@@ -591,25 +895,42 @@
|
|
| 591 |
const data = await response.json();
|
| 592 |
console.log('System Health:', data);
|
| 593 |
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
}
|
| 603 |
-
if (data.sentry === 'configured') {
|
| 604 |
-
document.getElementById('sentry-status').innerHTML = 'Sentry Active β
';
|
| 605 |
-
document.getElementById('sentry-status').className = 'text-green-600';
|
| 606 |
-
}
|
| 607 |
} catch (error) {
|
| 608 |
console.error('Error fetching stats:', error);
|
| 609 |
document.getElementById('db-status').innerHTML = 'Connection Error β';
|
| 610 |
document.getElementById('db-status').className = 'text-red-600';
|
|
|
|
| 611 |
}
|
| 612 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
fetchStats();
|
| 614 |
|
| 615 |
// Refresh stats every 30 seconds
|
|
@@ -622,14 +943,10 @@
|
|
| 622 |
document.documentElement.classList.remove('dark');
|
| 623 |
}
|
| 624 |
|
| 625 |
-
//
|
| 626 |
-
document.
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
document.querySelector(this.getAttribute('href')).scrollIntoView({
|
| 630 |
-
behavior: 'smooth'
|
| 631 |
-
});
|
| 632 |
-
});
|
| 633 |
});
|
| 634 |
|
| 635 |
// Add page load animation
|
|
@@ -640,6 +957,11 @@
|
|
| 640 |
document.body.style.opacity = '1';
|
| 641 |
}, 100);
|
| 642 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
</script>
|
| 644 |
</body>
|
| 645 |
|
|
|
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
<meta name="description" content="Ibrahim Al-Asfar Portfolio API - Full-stack web developer portfolio backend API">
|
| 8 |
+
<meta name="keywords" content="portfolio, API, Node.js, Express, MongoDB, developer, OAuth, JWT">
|
| 9 |
<meta name="author" content="Ibrahim Al-Asfar">
|
| 10 |
+
<meta name="robots" content="index, follow">
|
| 11 |
+
|
| 12 |
+
<!-- Open Graph / Social Media -->
|
| 13 |
<meta property="og:title" content="Ibrahim Al-Asfar Portfolio API">
|
| 14 |
+
<meta property="og:description"
|
| 15 |
+
content="Full-stack web developer portfolio API with authentication and AI features">
|
| 16 |
<meta property="og:type" content="website">
|
| 17 |
+
<meta property="og:url" content="https://mgzon-server.hf.space">
|
| 18 |
+
<meta property="og:image" content="https://mgzon-server.hf.space/images/logo.png">
|
| 19 |
+
|
| 20 |
+
<!-- Twitter Card -->
|
| 21 |
+
<meta name="twitter:card" content="summary_large_image">
|
| 22 |
+
<meta name="twitter:title" content="Ibrahim Al-Asfar Portfolio API">
|
| 23 |
+
<meta name="twitter:description" content="Full-stack web developer portfolio API">
|
| 24 |
+
<meta name="twitter:image" content="https://mgzon-server.hf.space/images/logo.png">
|
| 25 |
+
|
| 26 |
+
<!-- Favicon -->
|
| 27 |
+
<link rel="icon" type="image/png" href="/images/logo.png">
|
| 28 |
+
<link rel="apple-touch-icon" href="/images/logo.png">
|
| 29 |
+
|
| 30 |
+
<title>Ibrahim Al-Asfar Portfolio API</title>
|
| 31 |
|
| 32 |
<!-- Tailwind CSS -->
|
| 33 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 34 |
+
|
| 35 |
<!-- Font Awesome -->
|
| 36 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
| 37 |
|
|
|
|
| 38 |
<!-- Custom Styles -->
|
| 39 |
<style>
|
| 40 |
+
* {
|
| 41 |
+
margin: 0;
|
| 42 |
+
padding: 0;
|
| 43 |
+
box-sizing: border-box;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
@keyframes fadeInUp {
|
| 47 |
from {
|
| 48 |
opacity: 0;
|
|
|
|
| 55 |
}
|
| 56 |
}
|
| 57 |
|
| 58 |
+
@keyframes float {
|
| 59 |
+
|
| 60 |
+
0%,
|
| 61 |
+
100% {
|
| 62 |
+
transform: translateY(0px);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
50% {
|
| 66 |
+
transform: translateY(-10px);
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
.animate-fadeInUp {
|
| 71 |
animation: fadeInUp 0.6s ease-out;
|
| 72 |
}
|
| 73 |
|
| 74 |
+
.animate-float {
|
| 75 |
+
animation: float 3s ease-in-out infinite;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
.gradient-bg {
|
| 79 |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 80 |
}
|
| 81 |
|
| 82 |
+
.gradient-text {
|
| 83 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 84 |
+
-webkit-background-clip: text;
|
| 85 |
+
-webkit-text-fill-color: transparent;
|
| 86 |
+
background-clip: text;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
.card-hover {
|
| 90 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 91 |
}
|
| 92 |
|
| 93 |
.card-hover:hover {
|
|
|
|
| 95 |
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
| 96 |
}
|
| 97 |
|
| 98 |
+
.btn-hover {
|
| 99 |
+
transition: all 0.3s ease;
|
| 100 |
+
position: relative;
|
| 101 |
+
overflow: hidden;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.btn-hover:hover {
|
| 105 |
+
transform: scale(1.05);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
/* Custom scrollbar */
|
| 109 |
::-webkit-scrollbar {
|
| 110 |
+
width: 10px;
|
| 111 |
}
|
| 112 |
|
| 113 |
::-webkit-scrollbar-track {
|
| 114 |
background: #f1f1f1;
|
| 115 |
+
border-radius: 10px;
|
| 116 |
}
|
| 117 |
|
| 118 |
::-webkit-scrollbar-thumb {
|
| 119 |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 120 |
+
border-radius: 10px;
|
| 121 |
}
|
| 122 |
|
| 123 |
::-webkit-scrollbar-thumb:hover {
|
|
|
|
| 126 |
|
| 127 |
/* Logo styling */
|
| 128 |
.logo-img {
|
| 129 |
+
width: 40px;
|
| 130 |
+
height: 40px;
|
| 131 |
object-fit: contain;
|
| 132 |
+
border-radius: 10px;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
/* Glass effect */
|
| 136 |
+
.glass-card {
|
| 137 |
+
background: rgba(255, 255, 255, 0.9);
|
| 138 |
+
backdrop-filter: blur(10px);
|
| 139 |
}
|
| 140 |
|
| 141 |
+
/* Fallback styles */
|
| 142 |
.bg-gradient-to-br {
|
| 143 |
background: linear-gradient(to bottom right, #f9fafb, #f3f4f6);
|
| 144 |
}
|
|
|
|
| 151 |
border-radius: 0.5rem;
|
| 152 |
}
|
| 153 |
|
| 154 |
+
.rounded-xl {
|
| 155 |
+
border-radius: 0.75rem;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
.p-2 {
|
| 159 |
padding: 0.5rem;
|
| 160 |
}
|
|
|
|
| 163 |
padding: 0.75rem;
|
| 164 |
}
|
| 165 |
|
| 166 |
+
.p-4 {
|
| 167 |
+
padding: 1rem;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
.p-5 {
|
| 171 |
padding: 1.25rem;
|
| 172 |
}
|
|
|
|
| 180 |
padding-bottom: 2rem;
|
| 181 |
}
|
| 182 |
|
| 183 |
+
.py-12 {
|
| 184 |
+
padding-top: 3rem;
|
| 185 |
+
padding-bottom: 3rem;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
.py-16 {
|
| 189 |
padding-top: 4rem;
|
| 190 |
padding-bottom: 4rem;
|
|
|
|
| 200 |
padding-right: 1rem;
|
| 201 |
}
|
| 202 |
|
| 203 |
+
.px-8 {
|
| 204 |
+
padding-left: 2rem;
|
| 205 |
+
padding-right: 2rem;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
.text-center {
|
| 209 |
text-align: center;
|
| 210 |
}
|
|
|
|
| 257 |
background-color: #10b981;
|
| 258 |
}
|
| 259 |
|
| 260 |
+
.bg-red-500 {
|
| 261 |
+
background-color: #ef4444;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
.mb-2 {
|
| 265 |
margin-bottom: 0.5rem;
|
| 266 |
}
|
|
|
|
| 289 |
margin-right: 0.5rem;
|
| 290 |
}
|
| 291 |
|
| 292 |
+
.ml-2 {
|
| 293 |
+
margin-left: 0.5rem;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
.flex {
|
| 297 |
display: flex;
|
| 298 |
}
|
|
|
|
| 350 |
height: 3rem;
|
| 351 |
}
|
| 352 |
|
| 353 |
+
.w-16 {
|
| 354 |
+
width: 4rem;
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
.h-16 {
|
| 358 |
+
height: 4rem;
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
.w-3 {
|
| 362 |
width: 0.75rem;
|
| 363 |
}
|
|
|
|
| 370 |
border-radius: 9999px;
|
| 371 |
}
|
| 372 |
|
| 373 |
+
.cursor-pointer {
|
| 374 |
+
cursor: pointer;
|
| 375 |
}
|
| 376 |
|
| 377 |
.animate-pulse {
|
|
|
|
| 431 |
transition-duration: 150ms;
|
| 432 |
}
|
| 433 |
|
| 434 |
+
.hover\:shadow-xl:hover {
|
| 435 |
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
.hover\:shadow-2xl:hover {
|
| 439 |
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
| 440 |
}
|
| 441 |
|
| 442 |
.hover\:bg-gray-900:hover {
|
|
|
|
| 447 |
color: #7c3aed;
|
| 448 |
}
|
| 449 |
|
| 450 |
+
.hover\:scale-105:hover {
|
| 451 |
+
transform: scale(1.05);
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
@media (min-width: 640px) {
|
| 455 |
.sm\:px-6 {
|
| 456 |
padding-left: 1.5rem;
|
|
|
|
| 467 |
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 468 |
}
|
| 469 |
|
| 470 |
+
.md\:grid-cols-4 {
|
| 471 |
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
.md\:text-6xl {
|
| 475 |
font-size: 3.75rem;
|
| 476 |
}
|
|
|
|
| 481 |
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 482 |
}
|
| 483 |
|
| 484 |
+
.lg\:grid-cols-4 {
|
| 485 |
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
.lg\:px-8 {
|
| 489 |
padding-left: 2rem;
|
| 490 |
padding-right: 2rem;
|
| 491 |
}
|
| 492 |
}
|
| 493 |
+
|
| 494 |
+
/* Toast notification */
|
| 495 |
+
.toast {
|
| 496 |
+
position: fixed;
|
| 497 |
+
bottom: 20px;
|
| 498 |
+
right: 20px;
|
| 499 |
+
background: white;
|
| 500 |
+
border-radius: 8px;
|
| 501 |
+
padding: 12px 20px;
|
| 502 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
| 503 |
+
z-index: 1000;
|
| 504 |
+
animation: slideIn 0.3s ease;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
@keyframes slideIn {
|
| 508 |
+
from {
|
| 509 |
+
transform: translateX(100%);
|
| 510 |
+
opacity: 0;
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
to {
|
| 514 |
+
transform: translateX(0);
|
| 515 |
+
opacity: 1;
|
| 516 |
+
}
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
.toast.success {
|
| 520 |
+
border-left: 4px solid #10b981;
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
.toast.error {
|
| 524 |
+
border-left: 4px solid #ef4444;
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
.toast.info {
|
| 528 |
+
border-left: 4px solid #3b82f6;
|
| 529 |
+
}
|
| 530 |
</style>
|
| 531 |
</head>
|
| 532 |
|
| 533 |
<body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
|
| 534 |
|
| 535 |
<!-- Navigation -->
|
| 536 |
+
<nav class="bg-white/90 backdrop-blur-md shadow-lg sticky top-0 z-50">
|
| 537 |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 538 |
<div class="flex justify-between items-center h-16">
|
| 539 |
<div class="flex items-center space-x-3">
|
| 540 |
<!-- Logo Image -->
|
| 541 |
+
<img src="/images/logo.png" alt="Logo" class="logo-img"
|
| 542 |
onerror="this.onerror=null; this.parentElement.innerHTML='<div class=\'gradient-bg rounded-lg p-2\'><i class=\'fas fa-code text-white text-xl\'></i></div>'">
|
| 543 |
+
<h1 class="text-xl font-bold gradient-text">
|
| 544 |
+
Ibrahim Al-Asfar Portfolio
|
|
|
|
| 545 |
</h1>
|
| 546 |
</div>
|
| 547 |
<div class="flex items-center space-x-4">
|
|
|
|
| 569 |
<div class="text-center animate-fadeInUp">
|
| 570 |
<div class="inline-flex items-center justify-center p-2 bg-green-100 rounded-full mb-4">
|
| 571 |
<i class="fas fa-check-circle text-green-600 mr-2"></i>
|
| 572 |
+
<span class="text-green-600 text-sm font-medium" id="api-status">API is running</span>
|
| 573 |
</div>
|
| 574 |
+
<h1 class="text-5xl md:text-6xl font-bold gradient-text mb-6">
|
|
|
|
| 575 |
Welcome to My Portfolio API
|
| 576 |
</h1>
|
| 577 |
<p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
|
|
|
|
| 580 |
</p>
|
| 581 |
<div class="flex flex-wrap justify-center gap-4">
|
| 582 |
<a href="/api-docs"
|
| 583 |
+
class="gradient-bg text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center">
|
| 584 |
<i class="fas fa-rocket mr-2"></i>
|
| 585 |
Explore API
|
| 586 |
</a>
|
| 587 |
+
<a href="#test-api"
|
| 588 |
+
class="bg-gray-800 text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center"
|
| 589 |
+
id="test-api-btn">
|
| 590 |
+
<i class="fas fa-vial mr-2"></i>
|
| 591 |
+
Test API
|
| 592 |
</a>
|
| 593 |
</div>
|
| 594 |
</div>
|
|
|
|
| 605 |
<h3 class="text-2xl font-bold text-gray-800 mb-2">Database Status</h3>
|
| 606 |
<div class="flex items-center">
|
| 607 |
<div class="w-3 h-3 bg-green-500 rounded-full animate-pulse mr-2"></div>
|
| 608 |
+
<p class="text-gray-600" id="db-status">Checking...</p>
|
| 609 |
</div>
|
| 610 |
</div>
|
| 611 |
|
|
|
|
| 614 |
<i class="fas fa-cloud-upload-alt text-white text-xl"></i>
|
| 615 |
</div>
|
| 616 |
<h3 class="text-2xl font-bold text-gray-800 mb-2">Cloudinary</h3>
|
| 617 |
+
<p class="text-gray-600" id="cloudinary-status">Checking...</p>
|
| 618 |
</div>
|
| 619 |
|
| 620 |
<div class="bg-white rounded-xl shadow-lg p-6 card-hover animate-fadeInUp" style="animation-delay: 0.3s">
|
|
|
|
| 622 |
<i class="fas fa-chart-line text-white text-xl"></i>
|
| 623 |
</div>
|
| 624 |
<h3 class="text-2xl font-bold text-gray-800 mb-2">Analytics</h3>
|
| 625 |
+
<p class="text-gray-600" id="sentry-status">Checking...</p>
|
| 626 |
+
</div>
|
| 627 |
+
</div>
|
| 628 |
+
</div>
|
| 629 |
+
|
| 630 |
+
<!-- OAuth Authentication Section -->
|
| 631 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
| 632 |
+
<h2 class="text-3xl font-bold text-center text-gray-800 mb-12">π Authentication Providers</h2>
|
| 633 |
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
| 634 |
+
<!-- Google OAuth -->
|
| 635 |
+
<a href="/auth/google" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
|
| 636 |
+
target="_blank">
|
| 637 |
+
<div class="w-16 h-16 mx-auto mb-4">
|
| 638 |
+
<i class="fab fa-google text-5xl text-red-500 group-hover:scale-110 transition-transform"></i>
|
| 639 |
+
</div>
|
| 640 |
+
<h3 class="font-semibold text-gray-800">Sign in with Google</h3>
|
| 641 |
+
<p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
|
| 642 |
+
<span
|
| 643 |
+
class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
|
| 644 |
+
to test β</span>
|
| 645 |
+
</a>
|
| 646 |
+
|
| 647 |
+
<!-- Facebook OAuth -->
|
| 648 |
+
<a href="/auth/facebook" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
|
| 649 |
+
target="_blank">
|
| 650 |
+
<div class="w-16 h-16 mx-auto mb-4">
|
| 651 |
+
<i class="fab fa-facebook text-5xl text-blue-600 group-hover:scale-110 transition-transform"></i>
|
| 652 |
+
</div>
|
| 653 |
+
<h3 class="font-semibold text-gray-800">Sign in with Facebook</h3>
|
| 654 |
+
<p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
|
| 655 |
+
<span
|
| 656 |
+
class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
|
| 657 |
+
to test β</span>
|
| 658 |
+
</a>
|
| 659 |
+
|
| 660 |
+
<!-- GitHub OAuth -->
|
| 661 |
+
<a href="/auth/github" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
|
| 662 |
+
target="_blank">
|
| 663 |
+
<div class="w-16 h-16 mx-auto mb-4">
|
| 664 |
+
<i class="fab fa-github text-5xl text-gray-800 group-hover:scale-110 transition-transform"></i>
|
| 665 |
+
</div>
|
| 666 |
+
<h3 class="font-semibold text-gray-800">Sign in with GitHub</h3>
|
| 667 |
+
<p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
|
| 668 |
+
<span
|
| 669 |
+
class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
|
| 670 |
+
to test β</span>
|
| 671 |
+
</a>
|
| 672 |
+
|
| 673 |
+
<!-- MGZon OAuth -->
|
| 674 |
+
<a href="/auth/mgz" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
|
| 675 |
+
<div class="w-16 h-16 mx-auto mb-4">
|
| 676 |
+
<img src="https://mgzon-server.hf.space/images/logo.png" alt="MGZon"
|
| 677 |
+
class="w-16 h-16 rounded-full mx-auto group-hover:scale-110 transition-transform"
|
| 678 |
+
onerror="this.onerror=null; this.parentElement.innerHTML='<div class=\'w-16 h-16 gradient-bg rounded-full flex items-center justify-center mx-auto group-hover:scale-110 transition-transform\'><i class=\'fas fa-globe text-white text-2xl\'></i></div>'">
|
| 679 |
+
</div>
|
| 680 |
+
<h3 class="font-semibold text-gray-800">Sign in with MGZon</h3>
|
| 681 |
+
<p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
|
| 682 |
+
<span
|
| 683 |
+
class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
|
| 684 |
+
to test β</span>
|
| 685 |
+
</a>
|
| 686 |
+
</div>
|
| 687 |
+
</div>
|
| 688 |
+
|
| 689 |
+
<!-- API Test Section -->
|
| 690 |
+
<div id="test-api" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
| 691 |
+
<h2 class="text-3xl font-bold text-center text-gray-800 mb-12">π§ͺ API Test Panel</h2>
|
| 692 |
+
<div class="bg-white rounded-xl shadow-lg p-6">
|
| 693 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 694 |
+
<div>
|
| 695 |
+
<label class="block text-sm font-medium text-gray-700 mb-2">Endpoint</label>
|
| 696 |
+
<select id="api-endpoint"
|
| 697 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
|
| 698 |
+
<option value="/api/health">GET /api/health</option>
|
| 699 |
+
<option value="/api/projects">GET /api/projects</option>
|
| 700 |
+
<option value="/api/skills">GET /api/skills</option>
|
| 701 |
+
<option value="/api/register">POST /api/register</option>
|
| 702 |
+
<option value="/api/login">POST /api/login</option>
|
| 703 |
+
</select>
|
| 704 |
+
</div>
|
| 705 |
+
<div>
|
| 706 |
+
<label class="block text-sm font-medium text-gray-700 mb-2">Request Body (JSON)</label>
|
| 707 |
+
<textarea id="api-body" rows="3"
|
| 708 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg font-mono text-sm"
|
| 709 |
+
placeholder='{"email": "test@example.com", "password": "12345678"}'></textarea>
|
| 710 |
+
</div>
|
| 711 |
+
</div>
|
| 712 |
+
<div class="mt-4 flex justify-center">
|
| 713 |
+
<button id="send-request" class="gradient-bg text-white px-8 py-2 rounded-lg font-semibold btn-hover">
|
| 714 |
+
<i class="fas fa-paper-plane mr-2"></i>
|
| 715 |
+
Send Request
|
| 716 |
+
</button>
|
| 717 |
+
</div>
|
| 718 |
+
<div class="mt-6">
|
| 719 |
+
<label class="block text-sm font-medium text-gray-700 mb-2">Response</label>
|
| 720 |
+
<pre id="api-response"
|
| 721 |
+
class="bg-gray-900 text-green-400 p-4 rounded-lg overflow-x-auto font-mono text-sm max-h-96">Click "Send Request" to test API...</pre>
|
| 722 |
</div>
|
| 723 |
</div>
|
| 724 |
</div>
|
| 725 |
|
| 726 |
<!-- API Endpoints Section -->
|
| 727 |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
| 728 |
+
<h2 class="text-3xl font-bold text-center text-gray-800 mb-12">π‘ API Endpoints</h2>
|
| 729 |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 730 |
+
<div class="bg-white rounded-lg shadow-md p-5 card-hover">
|
| 731 |
<div class="flex items-center justify-between mb-3">
|
| 732 |
<span class="bg-green-100 text-green-600 text-xs font-semibold px-2 py-1 rounded">GET</span>
|
| 733 |
<i class="fas fa-users text-gray-400"></i>
|
| 734 |
</div>
|
| 735 |
<code class="text-sm text-gray-700">/api/projects</code>
|
| 736 |
<p class="text-gray-500 text-sm mt-2">Get all public projects</p>
|
| 737 |
+
<button onclick="testEndpoint('/api/projects')"
|
| 738 |
+
class="mt-3 text-purple-600 text-sm hover:underline">Test β</button>
|
| 739 |
</div>
|
| 740 |
|
| 741 |
+
<div class="bg-white rounded-lg shadow-md p-5 card-hover">
|
| 742 |
<div class="flex items-center justify-between mb-3">
|
| 743 |
<span class="bg-green-100 text-green-600 text-xs font-semibold px-2 py-1 rounded">GET</span>
|
| 744 |
<i class="fas fa-chart-simple text-gray-400"></i>
|
| 745 |
</div>
|
| 746 |
<code class="text-sm text-gray-700">/api/skills</code>
|
| 747 |
<p class="text-gray-500 text-sm mt-2">Get all skills</p>
|
| 748 |
+
<button onclick="testEndpoint('/api/skills')" class="mt-3 text-purple-600 text-sm hover:underline">Test
|
| 749 |
+
β</button>
|
| 750 |
</div>
|
| 751 |
|
| 752 |
+
<div class="bg-white rounded-lg shadow-md p-5 card-hover">
|
| 753 |
<div class="flex items-center justify-between mb-3">
|
| 754 |
<span class="bg-green-100 text-green-600 text-xs font-semibold px-2 py-1 rounded">GET</span>
|
| 755 |
<i class="fas fa-heart text-gray-400"></i>
|
| 756 |
</div>
|
| 757 |
<code class="text-sm text-gray-700">/api/health</code>
|
| 758 |
<p class="text-gray-500 text-sm mt-2">Check system health</p>
|
| 759 |
+
<button onclick="testEndpoint('/api/health')" class="mt-3 text-purple-600 text-sm hover:underline">Test
|
| 760 |
+
β</button>
|
| 761 |
</div>
|
| 762 |
|
| 763 |
+
<div class="bg-white rounded-lg shadow-md p-5 card-hover">
|
| 764 |
<div class="flex items-center justify-between mb-3">
|
| 765 |
<span class="bg-blue-100 text-blue-600 text-xs font-semibold px-2 py-1 rounded">POST</span>
|
| 766 |
<i class="fas fa-user-plus text-gray-400"></i>
|
| 767 |
</div>
|
| 768 |
<code class="text-sm text-gray-700">/api/register</code>
|
| 769 |
<p class="text-gray-500 text-sm mt-2">Create new account</p>
|
| 770 |
+
<button
|
| 771 |
+
onclick="testEndpoint('/api/register', 'POST', {email: 'test@example.com', password: '12345678', username: 'testuser'})"
|
| 772 |
+
class="mt-3 text-purple-600 text-sm hover:underline">Test β</button>
|
| 773 |
</div>
|
| 774 |
|
| 775 |
+
<div class="bg-white rounded-lg shadow-md p-5 card-hover">
|
| 776 |
<div class="flex items-center justify-between mb-3">
|
| 777 |
<span class="bg-blue-100 text-blue-600 text-xs font-semibold px-2 py-1 rounded">POST</span>
|
| 778 |
<i class="fas fa-sign-in-alt text-gray-400"></i>
|
| 779 |
</div>
|
| 780 |
<code class="text-sm text-gray-700">/api/login</code>
|
| 781 |
<p class="text-gray-500 text-sm mt-2">Authenticate user</p>
|
| 782 |
+
<button onclick="testEndpoint('/api/login', 'POST', {email: 'admin@elasfar.com', password: 'admin123'})"
|
| 783 |
+
class="mt-3 text-purple-600 text-sm hover:underline">Test β</button>
|
| 784 |
</div>
|
| 785 |
|
| 786 |
+
<div class="bg-white rounded-lg shadow-md p-5 card-hover">
|
| 787 |
<div class="flex items-center justify-between mb-3">
|
| 788 |
+
<span class="bg-purple-100 text-purple-600 text-xs font-semibold px-2 py-1 rounded">GET</span>
|
| 789 |
<i class="fab fa-google text-gray-400"></i>
|
| 790 |
</div>
|
| 791 |
<code class="text-sm text-gray-700">/auth/google</code>
|
| 792 |
+
<p class="text-gray-500 text-sm mt-2">Google OAuth Login</p>
|
| 793 |
+
<a href="/auth/google" target="_blank"
|
| 794 |
+
class="mt-3 text-purple-600 text-sm hover:underline inline-block">Open β</a>
|
| 795 |
</div>
|
| 796 |
</div>
|
| 797 |
</div>
|
|
|
|
| 799 |
<!-- Tech Stack Section -->
|
| 800 |
<div class="bg-white py-16">
|
| 801 |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 802 |
+
<h2 class="text-3xl font-bold text-center text-gray-800 mb-12">β‘ Technology Stack</h2>
|
| 803 |
<div class="flex flex-wrap justify-center gap-8">
|
| 804 |
<div class="text-center">
|
| 805 |
<i class="fab fa-node-js text-5xl text-green-600"></i>
|
|
|
|
| 817 |
<i class="fas fa-project-diagram text-5xl text-pink-500"></i>
|
| 818 |
<p class="mt-2 text-gray-600">GraphQL</p>
|
| 819 |
</div>
|
|
|
|
| 820 |
<div class="text-center">
|
| 821 |
<i class="fab fa-docker text-5xl text-blue-600"></i>
|
| 822 |
<p class="mt-2 text-gray-600">Docker</p>
|
|
|
|
| 825 |
<i class="fab fa-aws text-5xl text-orange-500"></i>
|
| 826 |
<p class="mt-2 text-gray-600">AWS</p>
|
| 827 |
</div>
|
| 828 |
+
<div class="text-center">
|
| 829 |
+
<i class="fas fa-shield-alt text-5xl text-purple-600"></i>
|
| 830 |
+
<p class="mt-2 text-gray-600">JWT Auth</p>
|
| 831 |
+
</div>
|
| 832 |
+
<div class="text-center">
|
| 833 |
+
<i class="fas fa-cloud-upload-alt text-5xl text-blue-400"></i>
|
| 834 |
+
<p class="mt-2 text-gray-600">Cloudinary</p>
|
| 835 |
+
</div>
|
| 836 |
</div>
|
| 837 |
</div>
|
| 838 |
</div>
|
|
|
|
| 848 |
</div>
|
| 849 |
</footer>
|
| 850 |
|
| 851 |
+
<!-- Toast Container -->
|
| 852 |
+
<div id="toast-container" class="fixed bottom-4 right-4 z-50"></div>
|
| 853 |
+
|
| 854 |
<script>
|
| 855 |
+
// Show toast notification
|
| 856 |
+
function showToast(message, type = 'success') {
|
| 857 |
+
const container = document.getElementById('toast-container');
|
| 858 |
+
const toast = document.createElement('div');
|
| 859 |
+
toast.className = `toast ${type} mb-2`;
|
| 860 |
+
toast.innerHTML = `
|
| 861 |
+
<div class="flex items-center gap-3">
|
| 862 |
+
<i class="fas ${type === 'success' ? 'fa-check-circle text-green-500' : type === 'error' ? 'fa-exclamation-circle text-red-500' : 'fa-info-circle text-blue-500'}"></i>
|
| 863 |
+
<span>${message}</span>
|
| 864 |
+
</div>
|
| 865 |
+
`;
|
| 866 |
+
container.appendChild(toast);
|
| 867 |
+
setTimeout(() => toast.remove(), 3000);
|
| 868 |
+
}
|
| 869 |
+
|
| 870 |
+
// Test API endpoint
|
| 871 |
+
async function testEndpoint(url, method = 'GET', body = null) {
|
| 872 |
+
const responseDiv = document.getElementById('api-response');
|
| 873 |
+
responseDiv.textContent = 'Loading...';
|
| 874 |
+
|
| 875 |
+
try {
|
| 876 |
+
const options = { method, headers: { 'Content-Type': 'application/json' } };
|
| 877 |
+
if (body && method !== 'GET') {
|
| 878 |
+
options.body = JSON.stringify(body);
|
| 879 |
+
}
|
| 880 |
+
|
| 881 |
+
const response = await fetch(url, options);
|
| 882 |
+
const data = await response.json();
|
| 883 |
+
responseDiv.textContent = JSON.stringify(data, null, 2);
|
| 884 |
+
showToast(`β
${url} - Status: ${response.status}`, 'success');
|
| 885 |
+
} catch (error) {
|
| 886 |
+
responseDiv.textContent = `Error: ${error.message}`;
|
| 887 |
+
showToast(`β ${url} - ${error.message}`, 'error');
|
| 888 |
+
}
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
// Fetch real-time data
|
| 892 |
async function fetchStats() {
|
| 893 |
try {
|
|
|
|
| 895 |
const data = await response.json();
|
| 896 |
console.log('System Health:', data);
|
| 897 |
|
| 898 |
+
document.getElementById('db-status').innerHTML = data.mongodb === 'connected' ? 'MongoDB Connected β
' : 'MongoDB Error β';
|
| 899 |
+
document.getElementById('db-status').className = data.mongodb === 'connected' ? 'text-green-600' : 'text-red-600';
|
| 900 |
+
|
| 901 |
+
document.getElementById('cloudinary-status').innerHTML = data.cloudinary === 'configured' ? 'Cloudinary Active β
' : 'Cloudinary Not Configured β οΈ';
|
| 902 |
+
document.getElementById('cloudinary-status').className = data.cloudinary === 'configured' ? 'text-green-600' : 'text-yellow-600';
|
| 903 |
+
|
| 904 |
+
document.getElementById('sentry-status').innerHTML = data.sentry === 'configured' ? 'Sentry Active β
' : 'Sentry Not Configured β οΈ';
|
| 905 |
+
document.getElementById('sentry-status').className = data.sentry === 'configured' ? 'text-green-600' : 'text-yellow-600';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 906 |
} catch (error) {
|
| 907 |
console.error('Error fetching stats:', error);
|
| 908 |
document.getElementById('db-status').innerHTML = 'Connection Error β';
|
| 909 |
document.getElementById('db-status').className = 'text-red-600';
|
| 910 |
+
showToast('Failed to fetch system health', 'error');
|
| 911 |
}
|
| 912 |
}
|
| 913 |
+
|
| 914 |
+
// API Test Panel
|
| 915 |
+
document.getElementById('send-request')?.addEventListener('click', async () => {
|
| 916 |
+
const endpoint = document.getElementById('api-endpoint').value;
|
| 917 |
+
const bodyText = document.getElementById('api-body').value;
|
| 918 |
+
let body = null;
|
| 919 |
+
|
| 920 |
+
if (bodyText && (endpoint.includes('/register') || endpoint.includes('/login'))) {
|
| 921 |
+
try {
|
| 922 |
+
body = JSON.parse(bodyText);
|
| 923 |
+
} catch (e) {
|
| 924 |
+
showToast('Invalid JSON format', 'error');
|
| 925 |
+
return;
|
| 926 |
+
}
|
| 927 |
+
}
|
| 928 |
+
|
| 929 |
+
const method = endpoint.includes('/register') || endpoint.includes('/login') ? 'POST' : 'GET';
|
| 930 |
+
await testEndpoint(endpoint, method, body);
|
| 931 |
+
});
|
| 932 |
+
|
| 933 |
+
// Load stats on page load
|
| 934 |
fetchStats();
|
| 935 |
|
| 936 |
// Refresh stats every 30 seconds
|
|
|
|
| 943 |
document.documentElement.classList.remove('dark');
|
| 944 |
}
|
| 945 |
|
| 946 |
+
// Scroll to test section
|
| 947 |
+
document.getElementById('test-api-btn')?.addEventListener('click', (e) => {
|
| 948 |
+
e.preventDefault();
|
| 949 |
+
document.getElementById('test-api').scrollIntoView({ behavior: 'smooth' });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 950 |
});
|
| 951 |
|
| 952 |
// Add page load animation
|
|
|
|
| 957 |
document.body.style.opacity = '1';
|
| 958 |
}, 100);
|
| 959 |
});
|
| 960 |
+
|
| 961 |
+
// Show welcome toast
|
| 962 |
+
setTimeout(() => {
|
| 963 |
+
showToast('Welcome to Portfolio API! π', 'info');
|
| 964 |
+
}, 1000);
|
| 965 |
</script>
|
| 966 |
</body>
|
| 967 |
|