nodebb-plugin-sso-biogrenci 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,8 @@
3
3
  "allow": [
4
4
  "Bash(tree -L 3 -I 'node_modules' /c/Users/kadir/OneDrive/Masaüstü/Projeler/nodebb-plugin-sso-biogrenci/ 2>/dev/null || find /c/Users/kadir/OneDrive/Masaüstü/Projeler/nodebb-plugin-sso-biogrenci -not -path '*/node_modules/*' -type f | sort)",
5
5
  "WebFetch(domain:raw.githubusercontent.com)",
6
- "Bash(xargs grep:*)"
6
+ "Bash(xargs grep:*)",
7
+ "WebFetch(domain:biogrenci.com)"
7
8
  ]
8
9
  }
9
10
  }
package/library.js CHANGED
@@ -7,8 +7,11 @@ const user = require.main.require('./src/user');
7
7
  const db = require.main.require('./src/database');
8
8
  const meta = require.main.require('./src/meta');
9
9
  const groups = require.main.require('./src/groups');
10
+ const winston = require.main.require('winston');
10
11
  const authenticationController = require.main.require('./src/controllers/authentication');
11
12
 
13
+ const LOG_PREFIX = '[sso-biogrenci]';
14
+
12
15
  const constants = {
13
16
  name: 'biogrenci',
14
17
  displayName: "bi'öğrenci",
@@ -25,70 +28,91 @@ const plugin = {
25
28
  clientId: '019ccf3d-0032-70e0-bf69-474e3f4dd2fa',
26
29
  clientSecret: '0bpVolqxVtNGcrZuVWtWjNfKr36EABj8zzj61e6i',
27
30
  },
31
+ ready: false,
28
32
  };
29
33
 
30
34
  plugin.init = async function (params) {
31
35
  const { router, middleware } = params;
36
+ winston.info(`${LOG_PREFIX} Initializing plugin...`);
32
37
 
33
38
  // Admin settings page
34
39
  router.get('/admin/plugins/sso-biogrenci', middleware.admin.buildHeader, renderAdmin);
35
40
  router.get('/api/admin/plugins/sso-biogrenci', renderAdmin);
41
+ winston.info(`${LOG_PREFIX} Admin routes registered`);
36
42
 
37
43
  // Firsatlar (discounts) page
38
44
  router.get('/firsatlar', middleware.buildHeader, renderFirsatlar);
39
45
  router.get('/api/firsatlar', renderFirsatlar);
46
+ winston.info(`${LOG_PREFIX} Firsatlar routes registered`);
40
47
 
41
48
  // API: fetch opportunities from bi'ogrenci
42
49
  router.get('/api/biogrenci/firsatlar', async (req, res) => {
50
+ winston.info(`${LOG_PREFIX} GET /api/biogrenci/firsatlar query=%j`, req.query);
43
51
  try {
44
52
  const data = await fetchOpportunities(req.query);
53
+ winston.info(`${LOG_PREFIX} Firsatlar response: %d items`, data.data ? data.data.length : 0);
45
54
  res.json(data);
46
55
  } catch (err) {
56
+ winston.error(`${LOG_PREFIX} Firsatlar fetch error: %s`, err.message);
47
57
  res.status(500).json({ error: err.message });
48
58
  }
49
59
  });
50
60
 
51
61
  // API: claim an opportunity
52
62
  router.post('/api/biogrenci/firsatlar/:id/claim', async (req, res) => {
63
+ winston.info(`${LOG_PREFIX} POST claim opportunity id=%s uid=%s`, req.params.id, req.uid);
53
64
  if (!req.uid) {
54
65
  return res.status(401).json({ error: 'Giriş yapmanız gerekiyor' });
55
66
  }
56
67
  try {
57
68
  const accessToken = await db.getObjectField(`biogrenci:uid:${req.uid}`, 'accessToken');
58
69
  if (!accessToken) {
70
+ winston.warn(`${LOG_PREFIX} No access token for uid=%s`, req.uid);
59
71
  return res.status(403).json({ error: "bi'öğrenci hesabınızla giriş yapmalısınız" });
60
72
  }
61
- const response = await fetch(`${constants.partnerAPIBase}/opportunities/${req.params.id}/claim`, {
73
+ const url = `${constants.partnerAPIBase}/opportunities/${req.params.id}/claim`;
74
+ winston.info(`${LOG_PREFIX} Claiming: POST %s`, url);
75
+ const response = await fetch(url, {
62
76
  method: 'POST',
63
77
  headers: { Authorization: `Bearer ${accessToken}` },
64
78
  });
65
79
  const data = await response.json();
80
+ winston.info(`${LOG_PREFIX} Claim response status=%d data=%j`, response.status, data);
66
81
  res.json(data);
67
82
  } catch (err) {
83
+ winston.error(`${LOG_PREFIX} Claim error: %s`, err.message);
68
84
  res.status(500).json({ error: err.message });
69
85
  }
70
86
  });
71
87
 
72
88
  // Profile: link/unlink bi'öğrenci account
73
89
  router.get('/api/biogrenci/status', async (req, res) => {
90
+ winston.verbose(`${LOG_PREFIX} GET /api/biogrenci/status uid=%s`, req.uid);
74
91
  if (!req.uid) {
75
92
  return res.json({ linked: false });
76
93
  }
77
- const data = await db.getObject(`biogrenci:uid:${req.uid}`);
78
- if (data && data.biogrenciId) {
79
- const education = await user.getUserField(req.uid, 'biogrenci_education');
80
- const verified = await user.getUserField(req.uid, 'biogrenci_verified');
81
- return res.json({
82
- linked: true,
83
- biogrenciId: data.biogrenciId,
84
- education: education || null,
85
- verified: !!verified,
86
- });
94
+ try {
95
+ const data = await db.getObject(`biogrenci:uid:${req.uid}`);
96
+ if (data && data.biogrenciId) {
97
+ const education = await user.getUserField(req.uid, 'biogrenci_education');
98
+ const verified = await user.getUserField(req.uid, 'biogrenci_verified');
99
+ winston.verbose(`${LOG_PREFIX} Status: linked=true verified=%s`, !!verified);
100
+ return res.json({
101
+ linked: true,
102
+ biogrenciId: data.biogrenciId,
103
+ education: education || null,
104
+ verified: !!verified,
105
+ });
106
+ }
107
+ res.json({ linked: false });
108
+ } catch (err) {
109
+ winston.error(`${LOG_PREFIX} Status error: %s`, err.message);
110
+ res.json({ linked: false });
87
111
  }
88
- res.json({ linked: false });
89
112
  });
90
113
 
91
114
  router.post('/api/biogrenci/unlink', async (req, res) => {
115
+ winston.info(`${LOG_PREFIX} POST /api/biogrenci/unlink uid=%s`, req.uid);
92
116
  if (!req.uid) {
93
117
  return res.status(401).json({ error: 'Giriş yapmanız gerekiyor' });
94
118
  }
@@ -102,20 +126,36 @@ plugin.init = async function (params) {
102
126
  await user.setUserField(req.uid, 'biogrenci_education', '');
103
127
  await user.setUserField(req.uid, 'biogrenci_verified', false);
104
128
  await groups.leave('Doğrulanmış Öğrenciler', req.uid);
129
+ winston.info(`${LOG_PREFIX} Unlinked uid=%s`, req.uid);
105
130
  res.json({ success: true });
106
131
  } catch (err) {
132
+ winston.error(`${LOG_PREFIX} Unlink error: %s`, err.message);
107
133
  res.status(500).json({ error: err.message });
108
134
  }
109
135
  });
110
136
 
111
137
  // Load settings from DB
112
- const settings = await meta.settings.get('sso-biogrenci');
113
- if (settings.clientId) plugin.settings.clientId = settings.clientId;
114
- if (settings.clientSecret) plugin.settings.clientSecret = settings.clientSecret;
138
+ try {
139
+ const settings = await meta.settings.get('sso-biogrenci');
140
+ winston.info(`${LOG_PREFIX} Loaded settings from DB: clientId=%s clientSecret=%s`,
141
+ settings.clientId ? settings.clientId.substring(0, 8) + '...' : '(empty)',
142
+ settings.clientSecret ? '***set***' : '(empty)'
143
+ );
144
+ if (settings.clientId) plugin.settings.clientId = settings.clientId;
145
+ if (settings.clientSecret) plugin.settings.clientSecret = settings.clientSecret;
146
+ } catch (err) {
147
+ winston.error(`${LOG_PREFIX} Failed to load settings: %s`, err.message);
148
+ }
149
+
150
+ plugin.ready = true;
151
+ winston.info(`${LOG_PREFIX} Plugin initialized. clientId=%s`,
152
+ plugin.settings.clientId ? plugin.settings.clientId.substring(0, 8) + '...' : '(not set)'
153
+ );
115
154
  };
116
155
 
117
156
  async function renderAdmin(req, res) {
118
157
  const callbackURL = nconf.get('url') + '/auth/biogrenci/callback';
158
+ winston.verbose(`${LOG_PREFIX} Rendering admin page, callbackURL=%s`, callbackURL);
119
159
  res.render('admin/plugins/sso-biogrenci', {
120
160
  title: "bi'öğrenci SSO",
121
161
  callbackURL: callbackURL,
@@ -123,6 +163,7 @@ async function renderAdmin(req, res) {
123
163
  }
124
164
 
125
165
  async function renderFirsatlar(req, res) {
166
+ winston.verbose(`${LOG_PREFIX} Rendering firsatlar page, uid=%s`, req.uid);
126
167
  res.render('biogrenci-firsatlar', {
127
168
  title: 'Öğrenci Fırsatları',
128
169
  uid: req.uid || 0,
@@ -139,19 +180,42 @@ async function fetchOpportunities(query) {
139
180
  params.set('page', query.page || 1);
140
181
  params.set('per_page', query.per_page || 20);
141
182
 
142
- const response = await fetch(`${constants.partnerAPIBase}/opportunities?${params.toString()}`);
183
+ const url = `${constants.partnerAPIBase}/opportunities?${params.toString()}`;
184
+ winston.info(`${LOG_PREFIX} Fetching opportunities: GET %s`, url);
185
+
186
+ const response = await fetch(url, {
187
+ headers: {
188
+ 'Accept': 'application/json',
189
+ 'X-Client-Id': plugin.settings.clientId,
190
+ },
191
+ });
192
+
193
+ winston.info(`${LOG_PREFIX} Partner API response: status=%d`, response.status);
194
+
143
195
  if (!response.ok) {
196
+ const body = await response.text();
197
+ winston.error(`${LOG_PREFIX} Partner API error: status=%d body=%s`, response.status, body);
144
198
  throw new Error(`Partner API error: ${response.status}`);
145
199
  }
146
- return response.json();
200
+
201
+ const data = await response.json();
202
+ winston.info(`${LOG_PREFIX} Partner API data keys=%j`, Object.keys(data));
203
+ return data;
147
204
  }
148
205
 
149
206
  plugin.getStrategy = async function (strategies) {
207
+ winston.info(`${LOG_PREFIX} getStrategy called. clientId=%s clientSecret=%s`,
208
+ plugin.settings.clientId ? 'set' : 'NOT SET',
209
+ plugin.settings.clientSecret ? 'set' : 'NOT SET'
210
+ );
211
+
150
212
  if (!plugin.settings.clientId || !plugin.settings.clientSecret) {
213
+ winston.warn(`${LOG_PREFIX} Missing credentials, skipping strategy registration`);
151
214
  return strategies;
152
215
  }
153
216
 
154
217
  const callbackURL = nconf.get('url') + '/auth/biogrenci/callback';
218
+ winston.info(`${LOG_PREFIX} Registering OAuth2 strategy, callbackURL=%s`, callbackURL);
155
219
 
156
220
  const strategy = new OAuth2Strategy(
157
221
  {
@@ -164,19 +228,36 @@ plugin.getStrategy = async function (strategies) {
164
228
  passReqToCallback: true,
165
229
  },
166
230
  async (req, accessToken, refreshToken, params, profile, done) => {
231
+ winston.info(`${LOG_PREFIX} OAuth2 callback received. accessToken=%s refreshToken=%s`,
232
+ accessToken ? accessToken.substring(0, 10) + '...' : 'none',
233
+ refreshToken ? 'set' : 'none'
234
+ );
167
235
  try {
168
236
  // Fetch user info from bi'ogrenci
237
+ winston.info(`${LOG_PREFIX} Fetching userinfo from %s`, constants.userInfoURL);
169
238
  const response = await fetch(constants.userInfoURL, {
170
239
  headers: { Authorization: `Bearer ${accessToken}` },
171
240
  });
172
241
 
242
+ winston.info(`${LOG_PREFIX} Userinfo response status=%d`, response.status);
243
+
173
244
  if (!response.ok) {
245
+ const body = await response.text();
246
+ winston.error(`${LOG_PREFIX} Userinfo error: %s`, body);
174
247
  return done(new Error('Kullanıcı bilgisi alınamadı'));
175
248
  }
176
249
 
177
250
  const biogrenciUser = await response.json();
251
+ winston.info(`${LOG_PREFIX} Userinfo: sub=%s email=%s name=%s verified=%s`,
252
+ biogrenciUser.sub,
253
+ biogrenciUser.email,
254
+ biogrenciUser.name || biogrenciUser.first_name,
255
+ biogrenciUser.student_verified
256
+ );
178
257
 
179
258
  const handleUser = async function (uid) {
259
+ winston.info(`${LOG_PREFIX} handleUser uid=%s biogrenciId=%s`, uid, biogrenciUser.sub);
260
+
180
261
  // Store access/refresh tokens
181
262
  await db.setObject(`biogrenci:uid:${uid}`, {
182
263
  accessToken,
@@ -191,14 +272,13 @@ plugin.getStrategy = async function (strategies) {
191
272
  .filter(Boolean)
192
273
  .join(' - ');
193
274
  await user.setUserField(uid, 'biogrenci_education', eduString);
275
+ winston.info(`${LOG_PREFIX} Saved education: %s`, eduString);
194
276
  }
195
277
 
196
278
  if (biogrenciUser.student_verified) {
197
279
  await user.setUserField(uid, 'biogrenci_verified', true);
198
- }
199
280
 
200
- // Add verified students to a group
201
- if (biogrenciUser.student_verified) {
281
+ // Add verified students to a group
202
282
  const groupExists = await groups.exists('Doğrulanmış Öğrenciler');
203
283
  if (!groupExists) {
204
284
  await groups.create({
@@ -208,8 +288,10 @@ plugin.getStrategy = async function (strategies) {
208
288
  private: 1,
209
289
  disableJoinRequests: 1,
210
290
  });
291
+ winston.info(`${LOG_PREFIX} Created "Doğrulanmış Öğrenciler" group`);
211
292
  }
212
293
  await groups.join('Doğrulanmış Öğrenciler', uid);
294
+ winston.info(`${LOG_PREFIX} User uid=%s added to verified group`, uid);
213
295
  }
214
296
 
215
297
  done(null, uid);
@@ -218,11 +300,13 @@ plugin.getStrategy = async function (strategies) {
218
300
  // Check if user already linked
219
301
  const existingUid = await plugin.getUidByBiogrenciId(biogrenciUser.sub);
220
302
  if (existingUid) {
303
+ winston.info(`${LOG_PREFIX} Found existing linked user uid=%s`, existingUid);
221
304
  return handleUser(existingUid);
222
305
  }
223
306
 
224
307
  // Check if logged-in user wants to link account
225
308
  if (req.uid) {
309
+ winston.info(`${LOG_PREFIX} Linking to logged-in user uid=%s`, req.uid);
226
310
  await plugin.linkAccount(req.uid, biogrenciUser.sub);
227
311
  return handleUser(req.uid);
228
312
  }
@@ -231,30 +315,39 @@ plugin.getStrategy = async function (strategies) {
231
315
  if (biogrenciUser.email) {
232
316
  const existingEmailUid = await user.getUidByEmail(biogrenciUser.email);
233
317
  if (existingEmailUid) {
318
+ winston.info(`${LOG_PREFIX} Found user by email uid=%s`, existingEmailUid);
234
319
  await plugin.linkAccount(existingEmailUid, biogrenciUser.sub);
235
320
  return handleUser(existingEmailUid);
236
321
  }
237
322
  }
238
323
 
239
324
  // Create new user
325
+ const username = biogrenciUser.first_name
326
+ ? `${biogrenciUser.first_name}${biogrenciUser.last_name ? '_' + biogrenciUser.last_name : ''}`
327
+ : `biogrenci_${biogrenciUser.sub}`;
328
+
329
+ winston.info(`${LOG_PREFIX} Creating new user: username=%s email=%s`, username, biogrenciUser.email);
330
+
240
331
  const newUid = await user.create({
241
- username: biogrenciUser.first_name
242
- ? `${biogrenciUser.first_name}${biogrenciUser.last_name ? '_' + biogrenciUser.last_name : ''}`
243
- : `biogrenci_${biogrenciUser.sub}`,
332
+ username: username,
244
333
  email: biogrenciUser.email || undefined,
245
334
  fullname: biogrenciUser.name || undefined,
246
335
  });
247
336
 
337
+ winston.info(`${LOG_PREFIX} Created user uid=%s`, newUid);
338
+
248
339
  await plugin.linkAccount(newUid, biogrenciUser.sub);
249
340
 
250
341
  // Set avatar if available
251
342
  if (biogrenciUser.avatar) {
252
343
  await user.setUserField(newUid, 'picture', biogrenciUser.avatar);
253
344
  await user.setUserField(newUid, 'uploadedpicture', biogrenciUser.avatar);
345
+ winston.info(`${LOG_PREFIX} Set avatar for uid=%s`, newUid);
254
346
  }
255
347
 
256
348
  handleUser(newUid);
257
349
  } catch (err) {
350
+ winston.error(`${LOG_PREFIX} OAuth2 callback error: %s\n%s`, err.message, err.stack);
258
351
  done(err);
259
352
  }
260
353
  },
@@ -265,6 +358,7 @@ plugin.getStrategy = async function (strategies) {
265
358
  };
266
359
 
267
360
  passport.use(constants.name, strategy);
361
+ winston.info(`${LOG_PREFIX} Passport strategy "%s" registered`, constants.name);
268
362
 
269
363
  strategies.push({
270
364
  name: constants.name,
@@ -276,15 +370,21 @@ plugin.getStrategy = async function (strategies) {
276
370
  scope: constants.scope,
277
371
  });
278
372
 
373
+ winston.info(`${LOG_PREFIX} Strategy added to list. Total strategies: %d`, strategies.length);
279
374
  return strategies;
280
375
  };
281
376
 
282
377
  plugin.listStrategy = async function (strategies) {
283
- strategies.push({
284
- name: constants.name,
285
- displayName: constants.displayName,
286
- icon: constants.icon,
287
- });
378
+ if (plugin.settings.clientId && plugin.settings.clientSecret) {
379
+ strategies.push({
380
+ name: constants.name,
381
+ displayName: constants.displayName,
382
+ icon: constants.icon,
383
+ });
384
+ winston.verbose(`${LOG_PREFIX} listStrategy: added to list`);
385
+ } else {
386
+ winston.warn(`${LOG_PREFIX} listStrategy: skipped, no credentials`);
387
+ }
288
388
  return strategies;
289
389
  };
290
390
 
@@ -317,6 +417,7 @@ plugin.addCustomFields = async function (data) {
317
417
  };
318
418
 
319
419
  plugin.linkAccount = async function (uid, biogrenciId) {
420
+ winston.info(`${LOG_PREFIX} Linking uid=%s to biogrenciId=%s`, uid, biogrenciId);
320
421
  await db.setObjectField('biogrenci:id', biogrenciId, uid);
321
422
  await db.setObjectField(`user:${uid}:biogrenci`, 'biogrenciId', biogrenciId);
322
423
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-sso-biogrenci",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "bi'öğrenci OAuth2 SSO login & öğrenci indirimleri for NodeBB",
5
5
  "main": "library.js",
6
6
  "keywords": [
@@ -4,23 +4,38 @@ define('admin/plugins/sso-biogrenci', ['settings'], function (Settings) {
4
4
  var ACP = {};
5
5
 
6
6
  ACP.init = function () {
7
- Settings.load('sso-biogrenci', $('.sso-biogrenci-settings'));
7
+ console.log('[sso-biogrenci] Admin page init');
8
+
9
+ var $form = $('.sso-biogrenci-settings');
10
+ console.log('[sso-biogrenci] Form element found:', $form.length > 0);
11
+ console.log('[sso-biogrenci] Form inputs:', $form.find('input').length);
12
+
13
+ Settings.load('sso-biogrenci', $form, function () {
14
+ console.log('[sso-biogrenci] Settings loaded from DB');
15
+ console.log('[sso-biogrenci] clientId value:', $('#clientId').val() ? 'set (' + $('#clientId').val().substring(0, 8) + '...)' : '(empty)');
16
+ console.log('[sso-biogrenci] clientSecret value:', $('#clientSecret').val() ? 'set (hidden)' : '(empty)');
17
+ });
8
18
 
9
19
  $('#save').on('click', function () {
10
- Settings.save('sso-biogrenci', $('.sso-biogrenci-settings'), function () {
20
+ console.log('[sso-biogrenci] Save clicked');
21
+ console.log('[sso-biogrenci] Saving clientId:', $('#clientId').val() ? 'set' : '(empty)');
22
+ console.log('[sso-biogrenci] Saving clientSecret:', $('#clientSecret').val() ? 'set' : '(empty)');
23
+
24
+ Settings.save('sso-biogrenci', $form, function () {
25
+ console.log('[sso-biogrenci] Settings saved successfully');
11
26
  app.alertSuccess('Ayarlar kaydedildi!');
12
27
  });
13
28
  });
14
29
 
15
30
  $('#toggleSecret').on('click', function () {
16
- var input = $('#clientSecret');
17
- var icon = $(this).find('i');
18
- if (input.attr('type') === 'password') {
19
- input.attr('type', 'text');
20
- icon.removeClass('fa-eye').addClass('fa-eye-slash');
31
+ var $input = $('#clientSecret');
32
+ var $icon = $(this).find('i');
33
+ if ($input.attr('type') === 'password') {
34
+ $input.attr('type', 'text');
35
+ $icon.removeClass('fa-eye').addClass('fa-eye-slash');
21
36
  } else {
22
- input.attr('type', 'password');
23
- icon.removeClass('fa-eye-slash').addClass('fa-eye');
37
+ $input.attr('type', 'password');
38
+ $icon.removeClass('fa-eye-slash').addClass('fa-eye');
24
39
  }
25
40
  });
26
41
  };
@@ -10,6 +10,7 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
10
10
  var searchTimeout = null;
11
11
 
12
12
  Page.init = function () {
13
+ console.log('[sso-biogrenci] Firsatlar page init');
13
14
  loadOpportunities();
14
15
 
15
16
  // Filter buttons
@@ -18,6 +19,7 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
18
19
  $(this).addClass('active');
19
20
  currentType = $(this).attr('data-type');
20
21
  currentPage = 1;
22
+ console.log('[sso-biogrenci] Filter changed: type=%s', currentType);
21
23
  loadOpportunities();
22
24
  });
23
25
 
@@ -25,6 +27,7 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
25
27
  $('#biogrenci-sort').on('change', function () {
26
28
  currentSort = $(this).val();
27
29
  currentPage = 1;
30
+ console.log('[sso-biogrenci] Sort changed: %s', currentSort);
28
31
  loadOpportunities();
29
32
  });
30
33
 
@@ -35,6 +38,7 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
35
38
  searchTimeout = setTimeout(function () {
36
39
  currentSearch = val;
37
40
  currentPage = 1;
41
+ console.log('[sso-biogrenci] Search: %s', currentSearch);
38
42
  loadOpportunities();
39
43
  }, 400);
40
44
  });
@@ -70,10 +74,38 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
70
74
  if (currentType) params.set('type', currentType);
71
75
  if (currentSearch) params.set('search', currentSearch);
72
76
 
73
- fetch(config.relative_path + '/api/biogrenci/firsatlar?' + params.toString())
74
- .then(function (r) { return r.json(); })
77
+ var url = config.relative_path + '/api/biogrenci/firsatlar?' + params.toString();
78
+ console.log('[sso-biogrenci] Fetching opportunities: %s', url);
79
+
80
+ fetch(url)
81
+ .then(function (r) {
82
+ console.log('[sso-biogrenci] API response status: %d', r.status);
83
+ return r.json();
84
+ })
75
85
  .then(function (res) {
76
- if (!res.data || !res.data.length) {
86
+ console.log('[sso-biogrenci] API response:', JSON.stringify(res).substring(0, 500));
87
+ console.log('[sso-biogrenci] Response keys:', Object.keys(res));
88
+
89
+ if (res.error) {
90
+ console.error('[sso-biogrenci] API error:', res.error);
91
+ $list.html(
92
+ '<div class="biogrenci-empty biogrenci-error">' +
93
+ '<i class="fa fa-exclamation-triangle fa-3x"></i>' +
94
+ '<h4>Fırsatlar yüklenirken hata oluştu</h4>' +
95
+ '<p>' + res.error + '</p>' +
96
+ '</div>'
97
+ );
98
+ return;
99
+ }
100
+
101
+ var items = res.data || res.items || res;
102
+ if (Array.isArray(res) && res.length) {
103
+ items = res;
104
+ }
105
+
106
+ console.log('[sso-biogrenci] Items count: %d, isArray: %s', items ? items.length : 0, Array.isArray(items));
107
+
108
+ if (!items || !items.length) {
77
109
  $list.html(
78
110
  '<div class="biogrenci-empty">' +
79
111
  '<i class="fa fa-search fa-3x"></i>' +
@@ -84,15 +116,17 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
84
116
  $('#biogrenci-pagination').hide();
85
117
  return;
86
118
  }
87
- renderOpportunities(res.data);
119
+
120
+ renderOpportunities(items);
88
121
  renderPagination(res.meta || {});
89
122
  })
90
- .catch(function () {
123
+ .catch(function (err) {
124
+ console.error('[sso-biogrenci] Fetch error:', err);
91
125
  $list.html(
92
126
  '<div class="biogrenci-empty biogrenci-error">' +
93
127
  '<i class="fa fa-exclamation-triangle fa-3x"></i>' +
94
128
  '<h4>Fırsatlar yüklenirken hata oluştu</h4>' +
95
- '<p>Lütfen daha sonra tekrar deneyin</p>' +
129
+ '<p>' + err.message + '</p>' +
96
130
  '</div>'
97
131
  );
98
132
  });
@@ -126,7 +160,6 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
126
160
 
127
161
  $list.html(html);
128
162
 
129
- // Bind claim buttons
130
163
  $list.find('.biogrenci-claim-btn').on('click', function () {
131
164
  claimOpportunity($(this));
132
165
  });
@@ -148,6 +181,7 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
148
181
  function claimOpportunity($btn) {
149
182
  var id = $btn.attr('data-id');
150
183
  $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Alınıyor...');
184
+ console.log('[sso-biogrenci] Claiming opportunity id=%s', id);
151
185
 
152
186
  fetch(config.relative_path + '/api/biogrenci/firsatlar/' + id + '/claim', {
153
187
  method: 'POST',
@@ -158,20 +192,23 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
158
192
  })
159
193
  .then(function (r) { return r.json(); })
160
194
  .then(function (res) {
195
+ console.log('[sso-biogrenci] Claim response:', res);
161
196
  if (res.error) {
162
197
  alerts.error(res.error);
163
198
  $btn.prop('disabled', false).html('<i class="fa fa-hand-pointer-o"></i> Fırsatı Al');
164
199
  return;
165
200
  }
166
- if (res.data && res.data.coupon_code) {
201
+
202
+ var data = res.data || res;
203
+ if (data.coupon_code) {
167
204
  $btn.replaceWith(
168
205
  '<div class="biogrenci-coupon-result">' +
169
- '<code class="biogrenci-coupon-code">' + res.data.coupon_code + '</code>' +
170
- '<button class="btn btn-sm biogrenci-copy-btn" data-code="' + res.data.coupon_code + '">' +
206
+ '<code class="biogrenci-coupon-code">' + data.coupon_code + '</code>' +
207
+ '<button class="btn btn-sm biogrenci-copy-btn" data-code="' + data.coupon_code + '">' +
171
208
  '<i class="fa fa-copy"></i>' +
172
209
  '</button>' +
173
- (res.data.redirect_url
174
- ? '<a href="' + res.data.redirect_url + '" target="_blank" rel="noopener" class="btn btn-sm biogrenci-go-btn">Siteye Git <i class="fa fa-external-link"></i></a>'
210
+ (data.redirect_url
211
+ ? '<a href="' + data.redirect_url + '" target="_blank" rel="noopener" class="btn btn-sm biogrenci-go-btn">Siteye Git <i class="fa fa-external-link"></i></a>'
175
212
  : '') +
176
213
  '</div>'
177
214
  );
@@ -179,14 +216,15 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
179
216
  navigator.clipboard.writeText($(this).attr('data-code'));
180
217
  alerts.success('Kopyalandı!');
181
218
  });
182
- } else if (res.data && res.data.qr_code) {
183
- $btn.replaceWith('<div class="biogrenci-qr-result">' + res.data.qr_code + '</div>');
184
- } else if (res.data && res.data.redirect_url) {
185
- window.open(res.data.redirect_url, '_blank');
219
+ } else if (data.qr_code) {
220
+ $btn.replaceWith('<div class="biogrenci-qr-result">' + data.qr_code + '</div>');
221
+ } else if (data.redirect_url) {
222
+ window.open(data.redirect_url, '_blank');
186
223
  $btn.html('<i class="fa fa-check"></i> Açıldı').addClass('claimed');
187
224
  }
188
225
  })
189
- .catch(function () {
226
+ .catch(function (err) {
227
+ console.error('[sso-biogrenci] Claim error:', err);
190
228
  alerts.error('Bir hata oluştu');
191
229
  $btn.prop('disabled', false).html('<i class="fa fa-hand-pointer-o"></i> Fırsatı Al');
192
230
  });