ibrahimlasfar commited on
Commit
000cbfa
Β·
1 Parent(s): 99ee13f

Add privacy policy and terms of service pages for Google OAuth verification

Browse files
public/BingSiteAuth.xml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <users>
3
+ <user>141162DDE47688B1B207B9254CEC3383</user>
4
+ </users>
public/google620570ce87abd87a.html ADDED
@@ -0,0 +1 @@
 
 
1
+ google-site-verification: google620570ce87abd87a.html
public/images/profile.jpg ADDED

Git LFS Details

  • SHA256: c968d56bd3ef492378363c0a9332588dc1de61cd94fa1cbfc84687445f145800
  • Pointer size: 131 Bytes
  • Size of remote file: 255 kB
public/sitemap.xml ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
+ <url>
4
+ <loc>https://mgzon-server.hf.space/</loc>
5
+ <lastmod>2026-04-02</lastmod>
6
+ <changefreq>daily</changefreq>
7
+ <priority>1.0</priority>
8
+ </url>
9
+ <url>
10
+ <loc>https://mgzon-server.hf.space/api-docs</loc>
11
+ <lastmod>2026-04-02</lastmod>
12
+ <changefreq>weekly</changefreq>
13
+ <priority>0.8</priority>
14
+ </url>
15
+ <url>
16
+ <loc>https://mgzon-server.hf.space/privacy</loc>
17
+ <lastmod>2026-04-02</lastmod>
18
+ <changefreq>monthly</changefreq>
19
+ <priority>0.5</priority>
20
+ </url>
21
+ <url>
22
+ <loc>https://mgzon-server.hf.space/terms</loc>
23
+ <lastmod>2026-04-02</lastmod>
24
+ <changefreq>monthly</changefreq>
25
+ <priority>0.5</priority>
26
+ </url>
27
+ <url>
28
+ <loc>https://mgzon-server.hf.space/api/health</loc>
29
+ <lastmod>2026-04-02</lastmod>
30
+ <changefreq>hourly</changefreq>
31
+ <priority>0.3</priority>
32
+ </url>
33
+ <url>
34
+ <loc>https://mgzon-server.hf.space/api/projects</loc>
35
+ <lastmod>2026-04-02</lastmod>
36
+ <changefreq>daily</changefreq>
37
+ <priority>0.7</priority>
38
+ </url>
39
+ <url>
40
+ <loc>https://mgzon-server.hf.space/api/skills</loc>
41
+ <lastmod>2026-04-02</lastmod>
42
+ <changefreq>weekly</changefreq>
43
+ <priority>0.6</priority>
44
+ </url>
45
+ </urlset>
public/yandex_b820fb59d7fe880e.html ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <html>
2
+ <head>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
4
+ </head>
5
+ <body>Verification: b820fb59d7fe880e</body>
6
+ </html>
server.js CHANGED
@@ -16,6 +16,7 @@ const MGZonStrategy = require('passport-mgzon');
16
  const { jsPDF } = require('jspdf');
17
  const Jimp = require('jimp');
18
 
 
19
  require('jspdf-autotable');
20
  require('dotenv').config();
21
 
@@ -348,7 +349,7 @@ const userSchema = new mongoose.Schema({
348
  refreshTokens: [{ token: String, createdAt: { type: Date, default: Date.now } }],
349
  notifications: [{ type: String }],
350
  profile: {
351
- nickname: { type: String, sparse: true },
352
  avatar: String,
353
  status: { type: String, default: 'Available', enum: ['Available', 'Busy', 'Open to Work'] },
354
  jobTitle: String,
@@ -4059,6 +4060,83 @@ app.set('view engine', 'ejs');
4059
  app.set('views', './views');
4060
  app.use(express.static('public'));
4061
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4062
 
4063
 
4064
  app.get('/', (req, res) => {
 
16
  const { jsPDF } = require('jspdf');
17
  const Jimp = require('jimp');
18
 
19
+ const path = require('path');
20
  require('jspdf-autotable');
21
  require('dotenv').config();
22
 
 
349
  refreshTokens: [{ token: String, createdAt: { type: Date, default: Date.now } }],
350
  notifications: [{ type: String }],
351
  profile: {
352
+ nickname: { type: String, sparse: true },
353
  avatar: String,
354
  status: { type: String, default: 'Available', enum: ['Available', 'Busy', 'Open to Work'] },
355
  jobTitle: String,
 
4060
  app.set('views', './views');
4061
  app.use(express.static('public'));
4062
 
4063
+ // Privacy Policy Page
4064
+ app.get('/privacy', (req, res) => {
4065
+ res.render('privacy');
4066
+ });
4067
+
4068
+ // Terms of Service Page
4069
+ app.get('/terms', (req, res) => {
4070
+ res.render('terms');
4071
+ });
4072
+
4073
+
4074
+ // Sitemap.xml
4075
+ app.get('/sitemap.xml', (req, res) => {
4076
+ const baseUrl = process.env.BASE_URL || 'https://mgzon-server.hf.space';
4077
+ const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
4078
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
4079
+ <url>
4080
+ <loc>${baseUrl}/</loc>
4081
+ <lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
4082
+ <changefreq>daily</changefreq>
4083
+ <priority>1.0</priority>
4084
+ </url>
4085
+ <url>
4086
+ <loc>${baseUrl}/api-docs</loc>
4087
+ <lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
4088
+ <changefreq>weekly</changefreq>
4089
+ <priority>0.8</priority>
4090
+ </url>
4091
+ <url>
4092
+ <loc>${baseUrl}/privacy</loc>
4093
+ <lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
4094
+ <changefreq>monthly</changefreq>
4095
+ <priority>0.5</priority>
4096
+ </url>
4097
+ <url>
4098
+ <loc>${baseUrl}/terms</loc>
4099
+ <lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
4100
+ <changefreq>monthly</changefreq>
4101
+ <priority>0.5</priority>
4102
+ </url>
4103
+ <url>
4104
+ <loc>${baseUrl}/api/health</loc>
4105
+ <lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
4106
+ <changefreq>hourly</changefreq>
4107
+ <priority>0.3</priority>
4108
+ </url>
4109
+ </urlset>`;
4110
+ res.header('Content-Type', 'application/xml');
4111
+ res.send(sitemap);
4112
+ });
4113
+
4114
+ // Robots.txt
4115
+ app.get('/robots.txt', (req, res) => {
4116
+ const robots = `User-agent: *
4117
+ Allow: /
4118
+ Sitemap: ${process.env.BASE_URL || 'https://mgzon-server.hf.space'}/sitemap.xml`;
4119
+ res.header('Content-Type', 'text/plain');
4120
+ res.send(robots);
4121
+ });
4122
+
4123
+ // Serve static verification files (Ψ¨Ψ£Ψ³Ω…Ψ§Ψ‘ الملفاΨͺ Ψ§Ω„Ψ΅Ψ­ΩŠΨ­Ψ©)
4124
+ app.get('/google620570ce87abd87a.html', (req, res) => {
4125
+ res.sendFile(path.join(__dirname, 'public', 'google620570ce87abd87a.html'));
4126
+ });
4127
+
4128
+ app.get('/BingSiteAuth.xml', (req, res) => {
4129
+ res.sendFile(path.join(__dirname, 'public', 'BingSiteAuth.xml'));
4130
+ });
4131
+
4132
+ app.get('/yandex_b820fb59d7fe880e.html', (req, res) => {
4133
+ res.sendFile(path.join(__dirname, 'public', 'yandex_b820fb59d7fe880e.html'));
4134
+ });
4135
+
4136
+
4137
+ // Serve profile image and logos
4138
+ app.use('/images', express.static('public/images'));
4139
+
4140
 
4141
 
4142
  app.get('/', (req, res) => {
views/index.ejs CHANGED
@@ -8,29 +8,53 @@
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" content="Full-stack web developer portfolio API with authentication and AI features">
 
15
  <meta property="og:type" content="website">
16
  <meta property="og:url" content="https://mgzon-server.hf.space">
17
  <meta property="og:image" content="https://mgzon-server.hf.space/images/logo.png">
18
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  <!-- Twitter Card -->
20
  <meta name="twitter:card" content="summary_large_image">
21
  <meta name="twitter:title" content="Ibrahim Al-Asfar Portfolio API">
22
  <meta name="twitter:description" content="Full-stack web developer portfolio API">
23
  <meta name="twitter:image" content="https://mgzon-server.hf.space/images/logo.png">
24
-
25
  <!-- Favicon -->
26
  <link rel="icon" type="image/png" href="/images/logo.png">
27
  <link rel="apple-touch-icon" href="/images/logo.png">
28
-
29
- <title>Ibrahim Al-Asfar Portfolio API</title>
30
 
31
  <!-- Tailwind CSS -->
32
  <script src="https://cdn.tailwindcss.com"></script>
33
-
34
  <!-- Font Awesome -->
35
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
36
 
@@ -47,6 +71,7 @@
47
  opacity: 0;
48
  transform: translateY(30px);
49
  }
 
50
  to {
51
  opacity: 1;
52
  transform: translateY(0);
@@ -54,8 +79,15 @@
54
  }
55
 
56
  @keyframes float {
57
- 0%, 100% { transform: translateY(0px); }
58
- 50% { transform: translateY(-10px); }
 
 
 
 
 
 
 
59
  }
60
 
61
  .animate-fadeInUp {
@@ -146,98 +178,342 @@
146
  border-radius: 0.75rem;
147
  }
148
 
149
- .p-2 { padding: 0.5rem; }
150
- .p-3 { padding: 0.75rem; }
151
- .p-4 { padding: 1rem; }
152
- .p-5 { padding: 1.25rem; }
153
- .p-6 { padding: 1.5rem; }
154
- .py-8 { padding-top: 2rem; padding-bottom: 2rem; }
155
- .py-12 { padding-top: 3rem; padding-bottom: 3rem; }
156
- .py-16 { padding-top: 4rem; padding-bottom: 4rem; }
157
- .py-20 { padding-top: 5rem; padding-bottom: 5rem; }
158
- .px-4 { padding-left: 1rem; padding-right: 1rem; }
159
- .px-8 { padding-left: 2rem; padding-right: 2rem; }
160
- .text-center { text-align: center; }
161
- .text-white { color: white; }
162
- .text-gray-600 { color: #4b5563; }
163
- .text-gray-800 { color: #1f2937; }
164
- .text-xl { font-size: 1.25rem; }
165
- .text-2xl { font-size: 1.5rem; }
166
- .text-3xl { font-size: 1.875rem; }
167
- .text-5xl { font-size: 3rem; }
168
- .font-bold { font-weight: 700; }
169
- .font-semibold { font-weight: 600; }
170
- .bg-white { background-color: white; }
171
- .bg-gray-800 { background-color: #1f2937; }
172
- .bg-green-500 { background-color: #10b981; }
173
- .bg-red-500 { background-color: #ef4444; }
174
- .mb-2 { margin-bottom: 0.5rem; }
175
- .mb-4 { margin-bottom: 1rem; }
176
- .mb-6 { margin-bottom: 1.5rem; }
177
- .mb-8 { margin-bottom: 2rem; }
178
- .mb-12 { margin-bottom: 3rem; }
179
- .mt-2 { margin-top: 0.5rem; }
180
- .mr-2 { margin-right: 0.5rem; }
181
- .ml-2 { margin-left: 0.5rem; }
182
- .flex { display: flex; }
183
- .inline-flex { display: inline-flex; }
184
- .grid { display: grid; }
185
- .items-center { align-items: center; }
186
- .justify-center { justify-content: center; }
187
- .justify-between { justify-content: space-between; }
188
- .gap-4 { gap: 1rem; }
189
- .gap-6 { gap: 1.5rem; }
190
- .gap-8 { gap: 2rem; }
191
- .max-w-2xl { max-width: 42rem; }
192
- .max-w-7xl { max-width: 80rem; }
193
- .mx-auto { margin-left: auto; margin-right: auto; }
194
- .w-12 { width: 3rem; }
195
- .h-12 { height: 3rem; }
196
- .w-16 { width: 4rem; }
197
- .h-16 { height: 4rem; }
198
- .w-3 { width: 0.75rem; }
199
- .h-3 { height: 0.75rem; }
200
- .rounded-full { border-radius: 9999px; }
201
- .cursor-pointer { cursor: pointer; }
202
- .animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
203
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  @keyframes pulse {
205
- 0%, 100% { opacity: 1; }
206
- 50% { opacity: .5; }
207
- }
208
-
209
- .sticky { position: sticky; }
210
- .top-0 { top: 0; }
211
- .z-50 { z-index: 50; }
212
- .absolute { position: absolute; }
213
- .relative { position: relative; }
214
- .inset-0 { top: 0; right: 0; bottom: 0; left: 0; }
215
- .overflow-hidden { overflow: hidden; }
216
- .bg-opacity-10 { opacity: 0.1; }
217
- .transition { transition-property: all; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
218
- .hover\:shadow-xl:hover { box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); }
219
- .hover\:shadow-2xl:hover { box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); }
220
- .hover\:bg-gray-900:hover { background-color: #111827; }
221
- .hover\:text-purple-600:hover { color: #7c3aed; }
222
- .hover\:scale-105:hover { transform: scale(1.05); }
223
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  @media (min-width: 640px) {
225
- .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
 
 
 
226
  }
227
-
228
  @media (min-width: 768px) {
229
- .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
230
- .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
231
- .md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
232
- .md\:text-6xl { font-size: 3.75rem; }
 
 
 
 
 
 
 
 
 
 
 
233
  }
234
-
235
  @media (min-width: 1024px) {
236
- .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
237
- .lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
238
- .lg\:px-8 { padding-left: 2rem; padding-right: 2rem; }
 
 
 
 
 
 
 
 
 
239
  }
240
-
241
  /* Toast notification */
242
  .toast {
243
  position: fixed;
@@ -246,26 +522,129 @@
246
  background: white;
247
  border-radius: 8px;
248
  padding: 12px 20px;
249
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
250
  z-index: 1000;
251
  animation: slideIn 0.3s ease;
252
  }
253
-
254
  @keyframes slideIn {
255
  from {
256
  transform: translateX(100%);
257
  opacity: 0;
258
  }
 
259
  to {
260
  transform: translateX(0);
261
  opacity: 1;
262
  }
263
  }
264
-
265
- .toast.success { border-left: 4px solid #10b981; }
266
- .toast.error { border-left: 4px solid #ef4444; }
267
- .toast.info { border-left: 4px solid #3b82f6; }
 
 
 
 
 
 
 
 
268
  </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  </head>
270
 
271
  <body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
@@ -283,13 +662,16 @@
283
  </h1>
284
  </div>
285
  <div class="flex items-center space-x-4">
286
- <a href="/api-docs" target="_blank" class="text-gray-600 hover:text-purple-600 transition" title="API Documentation">
 
287
  <i class="fas fa-book text-lg"></i>
288
  </a>
289
- <a href="https://github.com/Mark-Lasfar" target="_blank" class="text-gray-600 hover:text-purple-600 transition" title="GitHub">
 
290
  <i class="fab fa-github text-lg"></i>
291
  </a>
292
- <a href="https://linkedin.com/in/ibrahim-elasfar" target="_blank" class="text-gray-600 hover:text-purple-600 transition" title="LinkedIn">
 
293
  <i class="fab fa-linkedin text-lg"></i>
294
  </a>
295
  </div>
@@ -310,14 +692,18 @@
310
  Welcome to My Portfolio API
311
  </h1>
312
  <p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
313
- Full-stack web developer passionate about building modern web applications with cutting-edge technologies.
 
314
  </p>
315
  <div class="flex flex-wrap justify-center gap-4">
316
- <a href="/api-docs" class="gradient-bg text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center">
 
317
  <i class="fas fa-rocket mr-2"></i>
318
  Explore API
319
  </a>
320
- <a href="#test-api" class="bg-gray-800 text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center" id="test-api-btn">
 
 
321
  <i class="fas fa-vial mr-2"></i>
322
  Test API
323
  </a>
@@ -363,45 +749,56 @@
363
  <h2 class="text-3xl font-bold text-center text-gray-800 mb-12">πŸ” Authentication Providers</h2>
364
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
365
  <!-- Google OAuth -->
366
- <a href="/auth/google" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
 
367
  <div class="w-16 h-16 mx-auto mb-4">
368
  <i class="fab fa-google text-5xl text-red-500 group-hover:scale-110 transition-transform"></i>
369
  </div>
370
  <h3 class="font-semibold text-gray-800">Sign in with Google</h3>
371
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
372
- <span class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click to test β†’</span>
 
 
373
  </a>
374
 
375
  <!-- Facebook OAuth -->
376
- <a href="/auth/facebook" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
 
377
  <div class="w-16 h-16 mx-auto mb-4">
378
  <i class="fab fa-facebook text-5xl text-blue-600 group-hover:scale-110 transition-transform"></i>
379
  </div>
380
  <h3 class="font-semibold text-gray-800">Sign in with Facebook</h3>
381
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
382
- <span class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click to test β†’</span>
 
 
383
  </a>
384
 
385
  <!-- GitHub OAuth -->
386
- <a href="/auth/github" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
 
387
  <div class="w-16 h-16 mx-auto mb-4">
388
  <i class="fab fa-github text-5xl text-gray-800 group-hover:scale-110 transition-transform"></i>
389
  </div>
390
  <h3 class="font-semibold text-gray-800">Sign in with GitHub</h3>
391
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
392
- <span class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click to test β†’</span>
 
 
393
  </a>
394
 
395
- <!-- MGZon OAuth -->
396
  <a href="/auth/mgz" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
397
  <div class="w-16 h-16 mx-auto mb-4">
398
- <img src="https://mgzon-server.hf.space/images/logo.png" alt="MGZon"
399
  class="w-16 h-16 rounded-full mx-auto group-hover:scale-110 transition-transform"
400
  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>'">
401
  </div>
402
  <h3 class="font-semibold text-gray-800">Sign in with MGZon</h3>
403
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
404
- <span class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click to test β†’</span>
 
 
405
  </a>
406
  </div>
407
  </div>
@@ -413,7 +810,8 @@
413
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
414
  <div>
415
  <label class="block text-sm font-medium text-gray-700 mb-2">Endpoint</label>
416
- <select id="api-endpoint" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
 
417
  <option value="/api/health">GET /api/health</option>
418
  <option value="/api/projects">GET /api/projects</option>
419
  <option value="/api/skills">GET /api/skills</option>
@@ -423,7 +821,9 @@
423
  </div>
424
  <div>
425
  <label class="block text-sm font-medium text-gray-700 mb-2">Request Body (JSON)</label>
426
- <textarea id="api-body" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg font-mono text-sm" placeholder='{"email": "test@example.com", "password": "12345678"}'></textarea>
 
 
427
  </div>
428
  </div>
429
  <div class="mt-4 flex justify-center">
@@ -434,7 +834,8 @@
434
  </div>
435
  <div class="mt-6">
436
  <label class="block text-sm font-medium text-gray-700 mb-2">Response</label>
437
- <pre id="api-response" 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>
 
438
  </div>
439
  </div>
440
  </div>
@@ -450,7 +851,8 @@
450
  </div>
451
  <code class="text-sm text-gray-700">/api/projects</code>
452
  <p class="text-gray-500 text-sm mt-2">Get all public projects</p>
453
- <button onclick="testEndpoint('/api/projects')" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
454
  </div>
455
 
456
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -460,7 +862,8 @@
460
  </div>
461
  <code class="text-sm text-gray-700">/api/skills</code>
462
  <p class="text-gray-500 text-sm mt-2">Get all skills</p>
463
- <button onclick="testEndpoint('/api/skills')" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
464
  </div>
465
 
466
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -470,7 +873,8 @@
470
  </div>
471
  <code class="text-sm text-gray-700">/api/health</code>
472
  <p class="text-gray-500 text-sm mt-2">Check system health</p>
473
- <button onclick="testEndpoint('/api/health')" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
474
  </div>
475
 
476
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -480,7 +884,9 @@
480
  </div>
481
  <code class="text-sm text-gray-700">/api/register</code>
482
  <p class="text-gray-500 text-sm mt-2">Create new account</p>
483
- <button onclick="testEndpoint('/api/register', 'POST', {email: 'test@example.com', password: '12345678', username: 'testuser'})" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
 
484
  </div>
485
 
486
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -490,7 +896,8 @@
490
  </div>
491
  <code class="text-sm text-gray-700">/api/login</code>
492
  <p class="text-gray-500 text-sm mt-2">Authenticate user</p>
493
- <button onclick="testEndpoint('/api/login', 'POST', {email: 'admin@elasfar.com', password: 'admin123'})" class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
 
494
  </div>
495
 
496
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
@@ -500,7 +907,8 @@
500
  </div>
501
  <code class="text-sm text-gray-700">/auth/google</code>
502
  <p class="text-gray-500 text-sm mt-2">Google OAuth Login</p>
503
- <a href="/auth/google" target="_blank" class="mt-3 text-purple-600 text-sm hover:underline inline-block">Open β†’</a>
 
504
  </div>
505
  </div>
506
  </div>
@@ -580,13 +988,13 @@
580
  async function testEndpoint(url, method = 'GET', body = null) {
581
  const responseDiv = document.getElementById('api-response');
582
  responseDiv.textContent = 'Loading...';
583
-
584
  try {
585
  const options = { method, headers: { 'Content-Type': 'application/json' } };
586
  if (body && method !== 'GET') {
587
  options.body = JSON.stringify(body);
588
  }
589
-
590
  const response = await fetch(url, options);
591
  const data = await response.json();
592
  responseDiv.textContent = JSON.stringify(data, null, 2);
@@ -606,10 +1014,10 @@
606
 
607
  document.getElementById('db-status').innerHTML = data.mongodb === 'connected' ? 'MongoDB Connected βœ…' : 'MongoDB Error ❌';
608
  document.getElementById('db-status').className = data.mongodb === 'connected' ? 'text-green-600' : 'text-red-600';
609
-
610
  document.getElementById('cloudinary-status').innerHTML = data.cloudinary === 'configured' ? 'Cloudinary Active βœ…' : 'Cloudinary Not Configured ⚠️';
611
  document.getElementById('cloudinary-status').className = data.cloudinary === 'configured' ? 'text-green-600' : 'text-yellow-600';
612
-
613
  document.getElementById('sentry-status').innerHTML = data.sentry === 'configured' ? 'Sentry Active βœ…' : 'Sentry Not Configured ⚠️';
614
  document.getElementById('sentry-status').className = data.sentry === 'configured' ? 'text-green-600' : 'text-yellow-600';
615
  } catch (error) {
@@ -625,7 +1033,7 @@
625
  const endpoint = document.getElementById('api-endpoint').value;
626
  const bodyText = document.getElementById('api-body').value;
627
  let body = null;
628
-
629
  if (bodyText && (endpoint.includes('/register') || endpoint.includes('/login'))) {
630
  try {
631
  body = JSON.parse(bodyText);
@@ -634,14 +1042,14 @@
634
  return;
635
  }
636
  }
637
-
638
  const method = endpoint.includes('/register') || endpoint.includes('/login') ? 'POST' : 'GET';
639
  await testEndpoint(endpoint, method, body);
640
  });
641
 
642
  // Load stats on page load
643
  fetchStats();
644
-
645
  // Refresh stats every 30 seconds
646
  setInterval(fetchStats, 30000);
647
 
@@ -666,7 +1074,7 @@
666
  document.body.style.opacity = '1';
667
  }, 100);
668
  });
669
-
670
  // Show welcome toast
671
  setTimeout(() => {
672
  showToast('Welcome to Portfolio API! πŸš€', 'info');
 
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
+
21
+ <!-- Open Graph / Social Media -->
22
+ <meta property="og:title" content="Ibrahim Al-Asfar Portfolio API">
23
+ <meta property="og:description"
24
+ content="Full-stack web developer portfolio backend API with authentication, project management, and AI features">
25
+ <meta property="og:type" content="website">
26
+ <meta property="og:url" content="https://mgzon-server.hf.space">
27
+ <meta property="og:image" content="https://mgzon-server.hf.space/images/profile.jpg">
28
+ <meta property="og:image:alt" content="Ibrahim Al-Asfar Profile Picture">
29
+ <meta property="og:site_name" content="Ibrahim Al-Asfar Portfolio">
30
+
31
+ <!-- Twitter Card -->
32
+ <meta name="twitter:card" content="summary_large_image">
33
+ <meta name="twitter:title" content="Ibrahim Al-Asfar Portfolio API">
34
+ <meta name="twitter:description" content="Full-stack web developer portfolio backend API">
35
+ <meta name="twitter:image" content="https://mgzon-server.hf.space/images/profile.jpg">
36
+ <meta name="twitter:image:alt" content="Ibrahim Al-Asfar Profile">
37
+ <meta name="twitter:site" content="@MarkLasfar">
38
+ <meta name="twitter:creator" content="@MarkLasfar">
39
+
40
+ <!-- Author -->
41
+ <meta name="author" content="Ibrahim Al-Asfar (Mark Elasfar)">
42
+
43
  <!-- Twitter Card -->
44
  <meta name="twitter:card" content="summary_large_image">
45
  <meta name="twitter:title" content="Ibrahim Al-Asfar Portfolio API">
46
  <meta name="twitter:description" content="Full-stack web developer portfolio API">
47
  <meta name="twitter:image" content="https://mgzon-server.hf.space/images/logo.png">
48
+
49
  <!-- Favicon -->
50
  <link rel="icon" type="image/png" href="/images/logo.png">
51
  <link rel="apple-touch-icon" href="/images/logo.png">
52
+
53
+ <title> Portfolio API</title>
54
 
55
  <!-- Tailwind CSS -->
56
  <script src="https://cdn.tailwindcss.com"></script>
57
+
58
  <!-- Font Awesome -->
59
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
60
 
 
71
  opacity: 0;
72
  transform: translateY(30px);
73
  }
74
+
75
  to {
76
  opacity: 1;
77
  transform: translateY(0);
 
79
  }
80
 
81
  @keyframes float {
82
+
83
+ 0%,
84
+ 100% {
85
+ transform: translateY(0px);
86
+ }
87
+
88
+ 50% {
89
+ transform: translateY(-10px);
90
+ }
91
  }
92
 
93
  .animate-fadeInUp {
 
178
  border-radius: 0.75rem;
179
  }
180
 
181
+ .p-2 {
182
+ padding: 0.5rem;
183
+ }
184
+
185
+ .p-3 {
186
+ padding: 0.75rem;
187
+ }
188
+
189
+ .p-4 {
190
+ padding: 1rem;
191
+ }
192
+
193
+ .p-5 {
194
+ padding: 1.25rem;
195
+ }
196
+
197
+ .p-6 {
198
+ padding: 1.5rem;
199
+ }
200
+
201
+ .py-8 {
202
+ padding-top: 2rem;
203
+ padding-bottom: 2rem;
204
+ }
205
+
206
+ .py-12 {
207
+ padding-top: 3rem;
208
+ padding-bottom: 3rem;
209
+ }
210
+
211
+ .py-16 {
212
+ padding-top: 4rem;
213
+ padding-bottom: 4rem;
214
+ }
215
+
216
+ .py-20 {
217
+ padding-top: 5rem;
218
+ padding-bottom: 5rem;
219
+ }
220
+
221
+ .px-4 {
222
+ padding-left: 1rem;
223
+ padding-right: 1rem;
224
+ }
225
+
226
+ .px-8 {
227
+ padding-left: 2rem;
228
+ padding-right: 2rem;
229
+ }
230
+
231
+ .text-center {
232
+ text-align: center;
233
+ }
234
+
235
+ .text-white {
236
+ color: white;
237
+ }
238
+
239
+ .text-gray-600 {
240
+ color: #4b5563;
241
+ }
242
+
243
+ .text-gray-800 {
244
+ color: #1f2937;
245
+ }
246
+
247
+ .text-xl {
248
+ font-size: 1.25rem;
249
+ }
250
+
251
+ .text-2xl {
252
+ font-size: 1.5rem;
253
+ }
254
+
255
+ .text-3xl {
256
+ font-size: 1.875rem;
257
+ }
258
+
259
+ .text-5xl {
260
+ font-size: 3rem;
261
+ }
262
+
263
+ .font-bold {
264
+ font-weight: 700;
265
+ }
266
+
267
+ .font-semibold {
268
+ font-weight: 600;
269
+ }
270
+
271
+ .bg-white {
272
+ background-color: white;
273
+ }
274
+
275
+ .bg-gray-800 {
276
+ background-color: #1f2937;
277
+ }
278
+
279
+ .bg-green-500 {
280
+ background-color: #10b981;
281
+ }
282
+
283
+ .bg-red-500 {
284
+ background-color: #ef4444;
285
+ }
286
+
287
+ .mb-2 {
288
+ margin-bottom: 0.5rem;
289
+ }
290
+
291
+ .mb-4 {
292
+ margin-bottom: 1rem;
293
+ }
294
+
295
+ .mb-6 {
296
+ margin-bottom: 1.5rem;
297
+ }
298
+
299
+ .mb-8 {
300
+ margin-bottom: 2rem;
301
+ }
302
+
303
+ .mb-12 {
304
+ margin-bottom: 3rem;
305
+ }
306
+
307
+ .mt-2 {
308
+ margin-top: 0.5rem;
309
+ }
310
+
311
+ .mr-2 {
312
+ margin-right: 0.5rem;
313
+ }
314
+
315
+ .ml-2 {
316
+ margin-left: 0.5rem;
317
+ }
318
+
319
+ .flex {
320
+ display: flex;
321
+ }
322
+
323
+ .inline-flex {
324
+ display: inline-flex;
325
+ }
326
+
327
+ .grid {
328
+ display: grid;
329
+ }
330
+
331
+ .items-center {
332
+ align-items: center;
333
+ }
334
+
335
+ .justify-center {
336
+ justify-content: center;
337
+ }
338
+
339
+ .justify-between {
340
+ justify-content: space-between;
341
+ }
342
+
343
+ .gap-4 {
344
+ gap: 1rem;
345
+ }
346
+
347
+ .gap-6 {
348
+ gap: 1.5rem;
349
+ }
350
+
351
+ .gap-8 {
352
+ gap: 2rem;
353
+ }
354
+
355
+ .max-w-2xl {
356
+ max-width: 42rem;
357
+ }
358
+
359
+ .max-w-7xl {
360
+ max-width: 80rem;
361
+ }
362
+
363
+ .mx-auto {
364
+ margin-left: auto;
365
+ margin-right: auto;
366
+ }
367
+
368
+ .w-12 {
369
+ width: 3rem;
370
+ }
371
+
372
+ .h-12 {
373
+ height: 3rem;
374
+ }
375
+
376
+ .w-16 {
377
+ width: 4rem;
378
+ }
379
+
380
+ .h-16 {
381
+ height: 4rem;
382
+ }
383
+
384
+ .w-3 {
385
+ width: 0.75rem;
386
+ }
387
+
388
+ .h-3 {
389
+ height: 0.75rem;
390
+ }
391
+
392
+ .rounded-full {
393
+ border-radius: 9999px;
394
+ }
395
+
396
+ .cursor-pointer {
397
+ cursor: pointer;
398
+ }
399
+
400
+ .animate-pulse {
401
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
402
+ }
403
+
404
  @keyframes pulse {
405
+
406
+ 0%,
407
+ 100% {
408
+ opacity: 1;
409
+ }
410
+
411
+ 50% {
412
+ opacity: .5;
413
+ }
414
+ }
415
+
416
+ .sticky {
417
+ position: sticky;
418
+ }
419
+
420
+ .top-0 {
421
+ top: 0;
422
+ }
423
+
424
+ .z-50 {
425
+ z-index: 50;
426
+ }
427
+
428
+ .absolute {
429
+ position: absolute;
430
+ }
431
+
432
+ .relative {
433
+ position: relative;
434
+ }
435
+
436
+ .inset-0 {
437
+ top: 0;
438
+ right: 0;
439
+ bottom: 0;
440
+ left: 0;
441
+ }
442
+
443
+ .overflow-hidden {
444
+ overflow: hidden;
445
+ }
446
+
447
+ .bg-opacity-10 {
448
+ opacity: 0.1;
449
+ }
450
+
451
+ .transition {
452
+ transition-property: all;
453
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
454
+ transition-duration: 150ms;
455
+ }
456
+
457
+ .hover\:shadow-xl:hover {
458
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
459
+ }
460
+
461
+ .hover\:shadow-2xl:hover {
462
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
463
+ }
464
+
465
+ .hover\:bg-gray-900:hover {
466
+ background-color: #111827;
467
+ }
468
+
469
+ .hover\:text-purple-600:hover {
470
+ color: #7c3aed;
471
+ }
472
+
473
+ .hover\:scale-105:hover {
474
+ transform: scale(1.05);
475
+ }
476
+
477
  @media (min-width: 640px) {
478
+ .sm\:px-6 {
479
+ padding-left: 1.5rem;
480
+ padding-right: 1.5rem;
481
+ }
482
  }
483
+
484
  @media (min-width: 768px) {
485
+ .md\:grid-cols-2 {
486
+ grid-template-columns: repeat(2, minmax(0, 1fr));
487
+ }
488
+
489
+ .md\:grid-cols-3 {
490
+ grid-template-columns: repeat(3, minmax(0, 1fr));
491
+ }
492
+
493
+ .md\:grid-cols-4 {
494
+ grid-template-columns: repeat(4, minmax(0, 1fr));
495
+ }
496
+
497
+ .md\:text-6xl {
498
+ font-size: 3.75rem;
499
+ }
500
  }
501
+
502
  @media (min-width: 1024px) {
503
+ .lg\:grid-cols-3 {
504
+ grid-template-columns: repeat(3, minmax(0, 1fr));
505
+ }
506
+
507
+ .lg\:grid-cols-4 {
508
+ grid-template-columns: repeat(4, minmax(0, 1fr));
509
+ }
510
+
511
+ .lg\:px-8 {
512
+ padding-left: 2rem;
513
+ padding-right: 2rem;
514
+ }
515
  }
516
+
517
  /* Toast notification */
518
  .toast {
519
  position: fixed;
 
522
  background: white;
523
  border-radius: 8px;
524
  padding: 12px 20px;
525
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
526
  z-index: 1000;
527
  animation: slideIn 0.3s ease;
528
  }
529
+
530
  @keyframes slideIn {
531
  from {
532
  transform: translateX(100%);
533
  opacity: 0;
534
  }
535
+
536
  to {
537
  transform: translateX(0);
538
  opacity: 1;
539
  }
540
  }
541
+
542
+ .toast.success {
543
+ border-left: 4px solid #10b981;
544
+ }
545
+
546
+ .toast.error {
547
+ border-left: 4px solid #ef4444;
548
+ }
549
+
550
+ .toast.info {
551
+ border-left: 4px solid #3b82f6;
552
+ }
553
  </style>
554
+
555
+ <!-- Structured Data / Schema.org -->
556
+ <script type="application/ld+json">
557
+ {
558
+ "@context": "https://schema.org",
559
+ "@type": "Person",
560
+ "@id": "https://mgzon-server.hf.space/#person",
561
+ "name": "Ibrahim Al-Asfar",
562
+ "alternateName": ["Mark Elasfar", "Mark Al-Asfar", "ibrahim elasfar", "ibrahim Al-asfar", "alasfar"],
563
+ "givenName": "Ibrahim",
564
+ "familyName": "Al-Asfar",
565
+ "email": "marklasfar@gmail.com",
566
+ "url": "https://mgzon-server.hf.space",
567
+ "image": "https://mgzon-server.hf.space/images/profile.jpg",
568
+ "sameAs": [
569
+ "https://github.com/Mark-Lasfar",
570
+ "https://linkedin.com/in/ibrahim-elasfar",
571
+ "https://twitter.com/MarkLasfar"
572
+ ],
573
+ "jobTitle": "Full-stack Web Developer",
574
+ "worksFor": {
575
+ "@type": "Organization",
576
+ "name": "MGZon",
577
+ "url": "https://mgzon.com",
578
+ "logo": "https://mgzon-server.hf.space/images/mgzon-logo.png"
579
+ },
580
+ "knowsAbout": ["Node.js", "Express", "MongoDB", "React", "GraphQL", "Docker", "AWS", "OAuth", "JWT", "REST API"],
581
+ "alumniOf": [
582
+ {
583
+ "@type": "CollegeOrUniversity",
584
+ "name": "Computer Science Department"
585
+ }
586
+ ],
587
+ "description": "Full-stack web developer portfolio backend API with authentication, project management, and AI features"
588
+ }
589
+ </script>
590
+
591
+ <!-- WebApplication Schema -->
592
+ <script type="application/ld+json">
593
+ {
594
+ "@context": "https://schema.org",
595
+ "@type": "WebApplication",
596
+ "name": "Portfolio API",
597
+ "description": "Full-stack web developer portfolio backend API with authentication, project management, and AI features",
598
+ "url": "https://mgzon-server.hf.space",
599
+ "applicationCategory": "DeveloperApplication",
600
+ "operatingSystem": "All",
601
+ "softwareVersion": "1.0.0",
602
+ "author": {
603
+ "@type": "Person",
604
+ "@id": "https://mgzon-server.hf.space/#person"
605
+ },
606
+ "creator": {
607
+ "@type": "Person",
608
+ "name": "Ibrahim Al-Asfar"
609
+ },
610
+ "provider": {
611
+ "@type": "Organization",
612
+ "name": "MGZon",
613
+ "url": "https://mgzon.com",
614
+ "logo": "https://mgzon-server.hf.space/images/mgzon-logo.png"
615
+ },
616
+ "offers": {
617
+ "@type": "Offer",
618
+ "price": "0",
619
+ "priceCurrency": "USD"
620
+ },
621
+ "featureList": [
622
+ "JWT Authentication",
623
+ "OAuth Integration (Google, Facebook, GitHub, MGZon)",
624
+ "Project Management",
625
+ "File Upload with Cloudinary",
626
+ "AI Chatbot",
627
+ "PDF/DOCX Resume Export",
628
+ "Real-time Analytics"
629
+ ]
630
+ }
631
+ </script>
632
+
633
+ <!-- Organization Schema -->
634
+ <script type="application/ld+json">
635
+ {
636
+ "@context": "https://schema.org",
637
+ "@type": "Organization",
638
+ "name": "MGZon",
639
+ "url": "https://mgzon.com",
640
+ "logo": "https://mgzon-server.hf.space/images/mgzon-logo.png",
641
+ "sameAs": [
642
+ "https://github.com/MGZon",
643
+ "https://twitter.com/MGZon"
644
+ ]
645
+ }
646
+ </script>
647
+
648
  </head>
649
 
650
  <body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen">
 
662
  </h1>
663
  </div>
664
  <div class="flex items-center space-x-4">
665
+ <a href="/api-docs" target="_blank" class="text-gray-600 hover:text-purple-600 transition"
666
+ title="API Documentation">
667
  <i class="fas fa-book text-lg"></i>
668
  </a>
669
+ <a href="https://github.com/Mark-Lasfar" target="_blank"
670
+ class="text-gray-600 hover:text-purple-600 transition" title="GitHub">
671
  <i class="fab fa-github text-lg"></i>
672
  </a>
673
+ <a href="https://linkedin.com/in/ibrahim-elasfar" target="_blank"
674
+ class="text-gray-600 hover:text-purple-600 transition" title="LinkedIn">
675
  <i class="fab fa-linkedin text-lg"></i>
676
  </a>
677
  </div>
 
692
  Welcome to My Portfolio API
693
  </h1>
694
  <p class="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
695
+ Full-stack web developer passionate about building modern web applications with cutting-edge
696
+ technologies.
697
  </p>
698
  <div class="flex flex-wrap justify-center gap-4">
699
+ <a href="/api-docs"
700
+ class="gradient-bg text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center">
701
  <i class="fas fa-rocket mr-2"></i>
702
  Explore API
703
  </a>
704
+ <a href="#test-api"
705
+ class="bg-gray-800 text-white px-8 py-3 rounded-lg font-semibold btn-hover inline-flex items-center"
706
+ id="test-api-btn">
707
  <i class="fas fa-vial mr-2"></i>
708
  Test API
709
  </a>
 
749
  <h2 class="text-3xl font-bold text-center text-gray-800 mb-12">πŸ” Authentication Providers</h2>
750
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
751
  <!-- Google OAuth -->
752
+ <a href="/auth/google" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
753
+ target="_blank">
754
  <div class="w-16 h-16 mx-auto mb-4">
755
  <i class="fab fa-google text-5xl text-red-500 group-hover:scale-110 transition-transform"></i>
756
  </div>
757
  <h3 class="font-semibold text-gray-800">Sign in with Google</h3>
758
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
759
+ <span
760
+ class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
761
+ to test β†’</span>
762
  </a>
763
 
764
  <!-- Facebook OAuth -->
765
+ <a href="/auth/facebook" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
766
+ target="_blank">
767
  <div class="w-16 h-16 mx-auto mb-4">
768
  <i class="fab fa-facebook text-5xl text-blue-600 group-hover:scale-110 transition-transform"></i>
769
  </div>
770
  <h3 class="font-semibold text-gray-800">Sign in with Facebook</h3>
771
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
772
+ <span
773
+ class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
774
+ to test β†’</span>
775
  </a>
776
 
777
  <!-- GitHub OAuth -->
778
+ <a href="/auth/github" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group"
779
+ target="_blank">
780
  <div class="w-16 h-16 mx-auto mb-4">
781
  <i class="fab fa-github text-5xl text-gray-800 group-hover:scale-110 transition-transform"></i>
782
  </div>
783
  <h3 class="font-semibold text-gray-800">Sign in with GitHub</h3>
784
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
785
+ <span
786
+ class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
787
+ to test β†’</span>
788
  </a>
789
 
790
+ <!-- MGZon OAuth -->
791
  <a href="/auth/mgz" class="bg-white rounded-xl shadow-lg p-6 card-hover text-center group" target="_blank">
792
  <div class="w-16 h-16 mx-auto mb-4">
793
+ <img src="https://mgzon-server.hf.space/images/logo.png" alt="MGZon"
794
  class="w-16 h-16 rounded-full mx-auto group-hover:scale-110 transition-transform"
795
  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>'">
796
  </div>
797
  <h3 class="font-semibold text-gray-800">Sign in with MGZon</h3>
798
  <p class="text-xs text-gray-500 mt-2">OAuth 2.0</p>
799
+ <span
800
+ class="inline-block mt-3 text-purple-600 text-sm opacity-0 group-hover:opacity-100 transition">Click
801
+ to test β†’</span>
802
  </a>
803
  </div>
804
  </div>
 
810
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
811
  <div>
812
  <label class="block text-sm font-medium text-gray-700 mb-2">Endpoint</label>
813
+ <select id="api-endpoint"
814
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent">
815
  <option value="/api/health">GET /api/health</option>
816
  <option value="/api/projects">GET /api/projects</option>
817
  <option value="/api/skills">GET /api/skills</option>
 
821
  </div>
822
  <div>
823
  <label class="block text-sm font-medium text-gray-700 mb-2">Request Body (JSON)</label>
824
+ <textarea id="api-body" rows="3"
825
+ class="w-full px-4 py-2 border border-gray-300 rounded-lg font-mono text-sm"
826
+ placeholder='{"email": "test@example.com", "password": "12345678"}'></textarea>
827
  </div>
828
  </div>
829
  <div class="mt-4 flex justify-center">
 
834
  </div>
835
  <div class="mt-6">
836
  <label class="block text-sm font-medium text-gray-700 mb-2">Response</label>
837
+ <pre id="api-response"
838
+ 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>
839
  </div>
840
  </div>
841
  </div>
 
851
  </div>
852
  <code class="text-sm text-gray-700">/api/projects</code>
853
  <p class="text-gray-500 text-sm mt-2">Get all public projects</p>
854
+ <button onclick="testEndpoint('/api/projects')"
855
+ class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
856
  </div>
857
 
858
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
862
  </div>
863
  <code class="text-sm text-gray-700">/api/skills</code>
864
  <p class="text-gray-500 text-sm mt-2">Get all skills</p>
865
+ <button onclick="testEndpoint('/api/skills')" class="mt-3 text-purple-600 text-sm hover:underline">Test
866
+ β†’</button>
867
  </div>
868
 
869
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
873
  </div>
874
  <code class="text-sm text-gray-700">/api/health</code>
875
  <p class="text-gray-500 text-sm mt-2">Check system health</p>
876
+ <button onclick="testEndpoint('/api/health')" class="mt-3 text-purple-600 text-sm hover:underline">Test
877
+ β†’</button>
878
  </div>
879
 
880
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
884
  </div>
885
  <code class="text-sm text-gray-700">/api/register</code>
886
  <p class="text-gray-500 text-sm mt-2">Create new account</p>
887
+ <button
888
+ onclick="testEndpoint('/api/register', 'POST', {email: 'test@example.com', password: '12345678', username: 'testuser'})"
889
+ class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
890
  </div>
891
 
892
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
896
  </div>
897
  <code class="text-sm text-gray-700">/api/login</code>
898
  <p class="text-gray-500 text-sm mt-2">Authenticate user</p>
899
+ <button onclick="testEndpoint('/api/login', 'POST', {email: 'admin@elasfar.com', password: 'admin123'})"
900
+ class="mt-3 text-purple-600 text-sm hover:underline">Test β†’</button>
901
  </div>
902
 
903
  <div class="bg-white rounded-lg shadow-md p-5 card-hover">
 
907
  </div>
908
  <code class="text-sm text-gray-700">/auth/google</code>
909
  <p class="text-gray-500 text-sm mt-2">Google OAuth Login</p>
910
+ <a href="/auth/google" target="_blank"
911
+ class="mt-3 text-purple-600 text-sm hover:underline inline-block">Open β†’</a>
912
  </div>
913
  </div>
914
  </div>
 
988
  async function testEndpoint(url, method = 'GET', body = null) {
989
  const responseDiv = document.getElementById('api-response');
990
  responseDiv.textContent = 'Loading...';
991
+
992
  try {
993
  const options = { method, headers: { 'Content-Type': 'application/json' } };
994
  if (body && method !== 'GET') {
995
  options.body = JSON.stringify(body);
996
  }
997
+
998
  const response = await fetch(url, options);
999
  const data = await response.json();
1000
  responseDiv.textContent = JSON.stringify(data, null, 2);
 
1014
 
1015
  document.getElementById('db-status').innerHTML = data.mongodb === 'connected' ? 'MongoDB Connected βœ…' : 'MongoDB Error ❌';
1016
  document.getElementById('db-status').className = data.mongodb === 'connected' ? 'text-green-600' : 'text-red-600';
1017
+
1018
  document.getElementById('cloudinary-status').innerHTML = data.cloudinary === 'configured' ? 'Cloudinary Active βœ…' : 'Cloudinary Not Configured ⚠️';
1019
  document.getElementById('cloudinary-status').className = data.cloudinary === 'configured' ? 'text-green-600' : 'text-yellow-600';
1020
+
1021
  document.getElementById('sentry-status').innerHTML = data.sentry === 'configured' ? 'Sentry Active βœ…' : 'Sentry Not Configured ⚠️';
1022
  document.getElementById('sentry-status').className = data.sentry === 'configured' ? 'text-green-600' : 'text-yellow-600';
1023
  } catch (error) {
 
1033
  const endpoint = document.getElementById('api-endpoint').value;
1034
  const bodyText = document.getElementById('api-body').value;
1035
  let body = null;
1036
+
1037
  if (bodyText && (endpoint.includes('/register') || endpoint.includes('/login'))) {
1038
  try {
1039
  body = JSON.parse(bodyText);
 
1042
  return;
1043
  }
1044
  }
1045
+
1046
  const method = endpoint.includes('/register') || endpoint.includes('/login') ? 'POST' : 'GET';
1047
  await testEndpoint(endpoint, method, body);
1048
  });
1049
 
1050
  // Load stats on page load
1051
  fetchStats();
1052
+
1053
  // Refresh stats every 30 seconds
1054
  setInterval(fetchStats, 30000);
1055
 
 
1074
  document.body.style.opacity = '1';
1075
  }, 100);
1076
  });
1077
+
1078
  // Show welcome toast
1079
  setTimeout(() => {
1080
  showToast('Welcome to Portfolio API! πŸš€', 'info');