@insignia-education/api-sdk-js 0.15.62 → 0.15.65

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insignia-education/api-sdk-js",
3
- "version": "0.15.62",
3
+ "version": "0.15.65",
4
4
  "description": "JavaScript SDK for the Insignia Education API",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -45,6 +45,8 @@ export default class Courses {
45
45
  syncTelegramToUsers: (id) => this.#client.post(`${base}/${id}/sync-user-courses`),
46
46
  /** Reconciles this date's sessions with its current teacher/config set: creates missing ones, reassigns the teacher on existing ones, deletes untouched ones that no longer match the schedule. Safe to re-run any time the config, date range or teacher changes. */
47
47
  syncSessions: (id) => this.#client.post(`${base}/${id}/sync-sessions`),
48
+ /** Reconciles per-student attendance rows for this date's generated sessions against who's currently enrolled (UserCourse). Safe to re-run any time the roster changes. */
49
+ syncAttendances: (id) => this.#client.post(`${base}/${id}/sync-attendances`),
48
50
  /** Sessions already generated for this date. */
49
51
  sessions: (id) => this.#client.get(`${base}/${id}/sessions`),
50
52
  /** Create a Zoom meeting for one of this date's generated sessions. */
@@ -10,4 +10,10 @@ export default class Telegram {
10
10
 
11
11
  /** Admin-only: starts (or continues) a QR-code login. */
12
12
  qrLogin() { return this.#client.post('/telegram/qr-login'); }
13
+
14
+ /** Admin-only: current webhook registration for the main bot (account linking + notifications). */
15
+ webhookStatus() { return this.#client.get('/telegram/webhook'); }
16
+
17
+ /** Admin-only: (re)registers this API's /webhooks/telegram endpoint with Telegram. Idempotent. */
18
+ setWebhook() { return this.#client.post('/telegram/webhook'); }
13
19
  }
@@ -48,15 +48,19 @@ export default class Users {
48
48
  }
49
49
  courseNotes(userId) { return this.#nested(userId, 'course-notes'); }
50
50
 
51
- /** get() accepts an optional { courseId } filter to narrow to a single course. */
51
+ /** get() accepts optional { courseId, withTrashed } filters withTrashed also returns soft-deleted attempts. */
52
52
  quizzes(userId) {
53
53
  const base = `/users/${userId}/quizzes`;
54
54
  const client = this.#client;
55
55
  return {
56
- get: (id = null, { courseId } = {}) => id ? client.get(`${base}/${id}`) : client.get(courseId ? `${base}?course_id=${courseId}` : base),
56
+ get: (id = null, { courseId, withTrashed } = {}) => id
57
+ ? client.get(`${base}/${id}`)
58
+ : client.get(base, { course_id: courseId, with_trashed: withTrashed ? 1 : undefined }),
57
59
  create: (data) => client.put(base, data),
58
60
  edit: (id, data) => client.patch(`${base}/${id}`, data),
59
61
  delete: (id) => client.del(`${base}/${id}`),
62
+ /** Puts a soft-deleted attempt back. */
63
+ restore: (id) => client.post(`${base}/${id}/restore`),
60
64
  /**
61
65
  * Upload the file a student attaches as their answer to a
62
66
  * document_upload (PDF) or audio_answer (recording) question
@@ -78,6 +82,12 @@ export default class Users {
78
82
  create: (data) => client.put(base, data),
79
83
  edit: (id, data) => client.patch(`${base}/${id}`, data),
80
84
  delete: (id) => client.del(`${base}/${id}`),
85
+ /**
86
+ * Individual sessions only: clears teacher/course/date/time/meeting info
87
+ * so the slot can be rebooked. The owner may reset up to 1h before the
88
+ * session starts; a seller-and-above may reset any time.
89
+ */
90
+ reset: (id) => client.post(`${base}/${id}/reset`),
81
91
  };
82
92
  }
83
93
 
@@ -202,4 +212,16 @@ export default class Users {
202
212
  reject: () => client.post(`${base}/reject`),
203
213
  };
204
214
  }
215
+
216
+ /** Verified Telegram account linking for this user. Owner or staff only. */
217
+ telegram(userId) {
218
+ const base = `/users/${userId}/telegram`;
219
+ const client = this.#client;
220
+ return {
221
+ /** Mints a { hash, link, expires_at } deep link — tapping it in Telegram links this user's chat_id via the bot webhook. */
222
+ linkToken: () => client.post(`${base}/link-token`),
223
+ /** Fallback flow: completes a link the bot proposed after an organic (no-payload) /start, given the { chat_id, username, expires_at, signature } it replied with. */
224
+ connect: (data) => client.post(`${base}/connect`, data),
225
+ };
226
+ }
205
227
  }
@@ -0,0 +1,33 @@
1
+ import {
2
+ api,
3
+ loginAdmin,
4
+ loginCustomer,
5
+ } from '../../../helpers.js';
6
+
7
+ describe('api/v1/telegram', () => {
8
+ test('webhookStatus | requires authentication', async () => {
9
+ await expect(api.telegram.webhookStatus()).rejects.toMatchObject({ status: 401 });
10
+ });
11
+
12
+ test('webhookStatus | forbidden below admin', async () => {
13
+ await loginCustomer();
14
+ await expect(api.telegram.webhookStatus()).rejects.toMatchObject({ status: 403 });
15
+ });
16
+
17
+ test('webhookStatus | reachable by admin', async () => {
18
+ await loginAdmin();
19
+ await api.telegram.webhookStatus()
20
+ .then(response => {
21
+ expect(typeof response).toBe('object');
22
+ });
23
+ });
24
+
25
+ test('setWebhook | requires authentication', async () => {
26
+ await expect(api.telegram.setWebhook()).rejects.toMatchObject({ status: 401 });
27
+ });
28
+
29
+ test('setWebhook | forbidden below admin', async () => {
30
+ await loginCustomer();
31
+ await expect(api.telegram.setWebhook()).rejects.toMatchObject({ status: 403 });
32
+ });
33
+ });
@@ -0,0 +1,36 @@
1
+ import {
2
+ api,
3
+ loginCustomer,
4
+ } from '../../../../helpers.js';
5
+
6
+ describe('api/v1/users/{id}/telegram', () => {
7
+ test('linkToken | authenticated owner returns a deep link', async () => {
8
+ await loginCustomer();
9
+ const me = await api.users.get();
10
+ await api.users.telegram(me["id"]).linkToken()
11
+ .then(response => {
12
+ expect(response["hash"]).toBeDefined();
13
+ expect(response["link"]).toContain(response["hash"]);
14
+ expect(response["link"]).toMatch(/^https:\/\/t\.me\//);
15
+ expect(response["expires_at"]).toBeDefined();
16
+ });
17
+ });
18
+
19
+ test('connect | rejects an invalid signature', async () => {
20
+ await loginCustomer();
21
+ const me = await api.users.get();
22
+ await expect(api.users.telegram(me["id"]).connect({
23
+ chat_id: '12345',
24
+ username: 'someuser',
25
+ expires_at: Math.floor(Date.now() / 1000) + 600,
26
+ signature: 'not-a-real-signature',
27
+ })).rejects.toMatchObject({ status: 422 });
28
+ });
29
+
30
+ test('connect | missing params returns a validation error', async () => {
31
+ await loginCustomer();
32
+ const me = await api.users.get();
33
+ await expect(api.users.telegram(me["id"]).connect({}))
34
+ .rejects.toMatchObject({ status: 422 });
35
+ });
36
+ });