academe-kit 0.9.0 → 0.9.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.
@@ -221,6 +221,221 @@ export interface paths {
221
221
  };
222
222
  trace?: never;
223
223
  };
224
+ "/auth/forgot-password": {
225
+ parameters: {
226
+ query?: never;
227
+ header?: never;
228
+ path?: never;
229
+ cookie?: never;
230
+ };
231
+ get?: never;
232
+ put?: never;
233
+ /**
234
+ * Request password reset email
235
+ * @description Sends a password reset email to the specified address if a user with that email exists.
236
+ *
237
+ * **Security Note:**
238
+ * For security reasons, this endpoint always returns a success response regardless of whether
239
+ * the email exists in the system. This prevents email enumeration attacks.
240
+ *
241
+ * **Flow:**
242
+ * 1. Client sends email address
243
+ * 2. Backend searches for user by email in Keycloak
244
+ * 3. If user exists, Keycloak sends password reset email with UPDATE_PASSWORD action
245
+ * 4. User receives email with link valid for 1 hour
246
+ * 5. Endpoint returns success (regardless of whether email was found)
247
+ *
248
+ * **Email Template:**
249
+ * The email sent uses the Keycloak realm's configured email template for password reset.
250
+ */
251
+ post: {
252
+ parameters: {
253
+ query?: never;
254
+ header?: never;
255
+ path?: never;
256
+ cookie?: never;
257
+ };
258
+ requestBody: {
259
+ content: {
260
+ "application/json": {
261
+ /**
262
+ * Format: email
263
+ * @description User's email address
264
+ * @example user@example.com
265
+ */
266
+ email: string;
267
+ };
268
+ };
269
+ };
270
+ responses: {
271
+ /** @description Request processed successfully (always returned for security) */
272
+ 200: {
273
+ headers: {
274
+ [name: string]: unknown;
275
+ };
276
+ content: {
277
+ "application/json": {
278
+ /** @example success */
279
+ status?: string;
280
+ /** @example If this email is registered, you will receive password reset instructions. */
281
+ message?: string;
282
+ };
283
+ };
284
+ };
285
+ /** @description Bad Request - Email not provided */
286
+ 400: {
287
+ headers: {
288
+ [name: string]: unknown;
289
+ };
290
+ content: {
291
+ "application/json": {
292
+ /** @example error */
293
+ status?: string;
294
+ /** @example Email is required */
295
+ message?: string;
296
+ };
297
+ };
298
+ };
299
+ /** @description Internal Server Error */
300
+ 500: {
301
+ headers: {
302
+ [name: string]: unknown;
303
+ };
304
+ content: {
305
+ "application/json": {
306
+ /** @example error */
307
+ status?: string;
308
+ /** @example Internal server error */
309
+ message?: string;
310
+ };
311
+ };
312
+ };
313
+ };
314
+ };
315
+ delete?: never;
316
+ options?: never;
317
+ head?: never;
318
+ patch?: never;
319
+ trace?: never;
320
+ };
321
+ "/auth/google-exchange": {
322
+ parameters: {
323
+ query?: never;
324
+ header?: never;
325
+ path?: never;
326
+ cookie?: never;
327
+ };
328
+ get?: never;
329
+ put?: never;
330
+ /**
331
+ * Exchange Google access token for Keycloak tokens
332
+ * @description Exchanges a Google access token for Keycloak tokens.
333
+ *
334
+ * **Flow:**
335
+ * 1. Client sends Google access token
336
+ * 2. Backend fetches user email from Google Userinfo API
337
+ * 3. Backend verifies the email exists in the local database
338
+ * 4. If user exists, performs Keycloak token exchange
339
+ * 5. Returns Keycloak tokens (access_token, refresh_token, etc.)
340
+ *
341
+ * **Error Cases:**
342
+ * - 400: Invalid or expired Google token
343
+ * - 404: User email not found in database
344
+ */
345
+ post: {
346
+ parameters: {
347
+ query?: never;
348
+ header?: never;
349
+ path?: never;
350
+ cookie?: never;
351
+ };
352
+ requestBody: {
353
+ content: {
354
+ "application/json": {
355
+ /**
356
+ * @description Google OAuth2 access token
357
+ * @example ya29.a0AfH6SM...
358
+ */
359
+ googleAccessToken: string;
360
+ };
361
+ };
362
+ };
363
+ responses: {
364
+ /** @description Token exchange successful */
365
+ 200: {
366
+ headers: {
367
+ [name: string]: unknown;
368
+ };
369
+ content: {
370
+ "application/json": {
371
+ /** @example success */
372
+ status?: string;
373
+ data?: {
374
+ /** @description Keycloak access token */
375
+ access_token?: string;
376
+ /** @description Keycloak refresh token */
377
+ refresh_token?: string;
378
+ /** @description Token expiration time in seconds */
379
+ expires_in?: number;
380
+ /** @description Refresh token expiration time in seconds */
381
+ refresh_expires_in?: number;
382
+ /** @example Bearer */
383
+ token_type?: string;
384
+ scope?: string;
385
+ };
386
+ };
387
+ };
388
+ };
389
+ /** @description Bad Request - Invalid Google token or missing googleAccessToken */
390
+ 400: {
391
+ headers: {
392
+ [name: string]: unknown;
393
+ };
394
+ content: {
395
+ "application/json": {
396
+ /** @example error */
397
+ status?: string;
398
+ /** @example Token do Google inválido ou expirado */
399
+ message?: string;
400
+ };
401
+ };
402
+ };
403
+ /** @description Not Found - User email not registered in database */
404
+ 404: {
405
+ headers: {
406
+ [name: string]: unknown;
407
+ };
408
+ content: {
409
+ "application/json": {
410
+ /** @example error */
411
+ status?: string;
412
+ /** @example Usuário não encontrado */
413
+ message?: string;
414
+ };
415
+ };
416
+ };
417
+ /** @description Internal Server Error */
418
+ 500: {
419
+ headers: {
420
+ [name: string]: unknown;
421
+ };
422
+ content: {
423
+ "application/json": {
424
+ /** @example error */
425
+ status?: string;
426
+ /** @example Internal server error */
427
+ message?: string;
428
+ };
429
+ };
430
+ };
431
+ };
432
+ };
433
+ delete?: never;
434
+ options?: never;
435
+ head?: never;
436
+ patch?: never;
437
+ trace?: never;
438
+ };
224
439
  "/auth/token": {
225
440
  parameters: {
226
441
  query?: never;
@@ -823,6 +1038,68 @@ export interface paths {
823
1038
  patch?: never;
824
1039
  trace?: never;
825
1040
  };
1041
+ "/certificates/{userId}/download-all": {
1042
+ parameters: {
1043
+ query?: never;
1044
+ header?: never;
1045
+ path?: never;
1046
+ cookie?: never;
1047
+ };
1048
+ /**
1049
+ * Download all certificates of a user as a single PDF
1050
+ * @description Downloads all certificates of the given user merged into a single PDF file.
1051
+ * Each certificate occupies one page in the resulting file.
1052
+ * PDFs are downloaded from S3 in parallel and merged using pdf-lib.
1053
+ */
1054
+ get: {
1055
+ parameters: {
1056
+ query?: never;
1057
+ header?: never;
1058
+ path: {
1059
+ /** @description ID of the user whose certificates will be downloaded */
1060
+ userId: string;
1061
+ };
1062
+ cookie?: never;
1063
+ };
1064
+ requestBody?: never;
1065
+ responses: {
1066
+ /** @description Merged PDF file containing all user certificates */
1067
+ 200: {
1068
+ headers: {
1069
+ /** @description attachment; filename="Meus-Certificados.pdf" */
1070
+ "Content-Disposition"?: string;
1071
+ [name: string]: unknown;
1072
+ };
1073
+ content: {
1074
+ "application/pdf": string;
1075
+ };
1076
+ };
1077
+ 401: components["responses"]["Unauthorized"];
1078
+ /** @description No certificates found for the user */
1079
+ 404: {
1080
+ headers: {
1081
+ [name: string]: unknown;
1082
+ };
1083
+ content: {
1084
+ "application/json": {
1085
+ /** @example error */
1086
+ status?: string;
1087
+ /** @example No certificates found for this user */
1088
+ message?: string;
1089
+ };
1090
+ };
1091
+ };
1092
+ 500: components["responses"]["ServerError"];
1093
+ };
1094
+ };
1095
+ put?: never;
1096
+ post?: never;
1097
+ delete?: never;
1098
+ options?: never;
1099
+ head?: never;
1100
+ patch?: never;
1101
+ trace?: never;
1102
+ };
826
1103
  "/certificates": {
827
1104
  parameters: {
828
1105
  query?: never;
@@ -832,13 +1109,24 @@ export interface paths {
832
1109
  };
833
1110
  /**
834
1111
  * List all certificates
835
- * @description Retrieve a list of all certificates in the system.
1112
+ * @description Retrieve a paginated list of all certificates in the system.
836
1113
  * Each certificate includes course information (id, title, description) when available.
837
1114
  * Course data is extracted from the related Quiz through QuizAttempt.
838
1115
  */
839
1116
  get: {
840
1117
  parameters: {
841
- query?: never;
1118
+ query?: {
1119
+ /** @description Filter certificates by user ID */
1120
+ userId?: string;
1121
+ /** @description Search term */
1122
+ search?: components["parameters"]["search"];
1123
+ /** @description Page number */
1124
+ page?: components["parameters"]["page"];
1125
+ /** @description Items per page */
1126
+ limit?: components["parameters"]["limit"];
1127
+ /** @description Whether to include course data in the response (default true). Pass false to skip course lookup. */
1128
+ includeCourse?: boolean;
1129
+ };
842
1130
  header?: never;
843
1131
  path?: never;
844
1132
  cookie?: never;
@@ -855,6 +1143,16 @@ export interface paths {
855
1143
  /** @example success */
856
1144
  status?: string;
857
1145
  data?: components["schemas"]["Certificate"][];
1146
+ meta?: {
1147
+ /** @example 10 */
1148
+ total?: number;
1149
+ /** @example 1 */
1150
+ page?: number;
1151
+ /** @example 10 */
1152
+ limit?: number;
1153
+ /** @example 1 */
1154
+ totalPages?: number;
1155
+ };
858
1156
  };
859
1157
  };
860
1158
  };
@@ -890,8 +1188,6 @@ export interface paths {
890
1188
  /** @example success */
891
1189
  status?: string;
892
1190
  data?: components["schemas"]["Certificate"];
893
- /** @example Certificate created successfully */
894
- message?: string;
895
1191
  };
896
1192
  };
897
1193
  };
@@ -969,18 +1265,11 @@ export interface paths {
969
1265
  requestBody?: never;
970
1266
  responses: {
971
1267
  /** @description Certificate deleted successfully */
972
- 200: {
1268
+ 204: {
973
1269
  headers: {
974
1270
  [name: string]: unknown;
975
1271
  };
976
- content: {
977
- "application/json": {
978
- /** @example success */
979
- status?: string;
980
- /** @example Certificate deleted successfully */
981
- message?: string;
982
- };
983
- };
1272
+ content?: never;
984
1273
  };
985
1274
  400: components["responses"]["BadRequest"];
986
1275
  401: components["responses"]["Unauthorized"];
@@ -1020,8 +1309,6 @@ export interface paths {
1020
1309
  /** @example success */
1021
1310
  status?: string;
1022
1311
  data?: components["schemas"]["Certificate"];
1023
- /** @example Certificate updated successfully */
1024
- message?: string;
1025
1312
  };
1026
1313
  };
1027
1314
  };
@@ -1033,6 +1320,109 @@ export interface paths {
1033
1320
  };
1034
1321
  trace?: never;
1035
1322
  };
1323
+ "/certificates/{id}/download": {
1324
+ parameters: {
1325
+ query?: never;
1326
+ header?: never;
1327
+ path?: never;
1328
+ cookie?: never;
1329
+ };
1330
+ /**
1331
+ * Download certificate PDF
1332
+ * @description Download the PDF of a specific certificate.
1333
+ * Acts as a proxy to download from S3, avoiding CORS issues.
1334
+ * Only the certificate owner can download their certificate.
1335
+ */
1336
+ get: {
1337
+ parameters: {
1338
+ query?: never;
1339
+ header?: never;
1340
+ path: {
1341
+ /** @description Resource ID */
1342
+ id: components["parameters"]["id"];
1343
+ };
1344
+ cookie?: never;
1345
+ };
1346
+ requestBody?: never;
1347
+ responses: {
1348
+ /** @description Certificate PDF file */
1349
+ 200: {
1350
+ headers: {
1351
+ [name: string]: unknown;
1352
+ };
1353
+ content: {
1354
+ "application/pdf": string;
1355
+ };
1356
+ };
1357
+ 401: components["responses"]["Unauthorized"];
1358
+ 403: components["responses"]["Forbidden"];
1359
+ 404: components["responses"]["NotFound"];
1360
+ 500: components["responses"]["ServerError"];
1361
+ };
1362
+ };
1363
+ put?: never;
1364
+ post?: never;
1365
+ delete?: never;
1366
+ options?: never;
1367
+ head?: never;
1368
+ patch?: never;
1369
+ trace?: never;
1370
+ };
1371
+ "/certificates/generate": {
1372
+ parameters: {
1373
+ query?: never;
1374
+ header?: never;
1375
+ path?: never;
1376
+ cookie?: never;
1377
+ };
1378
+ get?: never;
1379
+ put?: never;
1380
+ /**
1381
+ * Generate a certificate PDF server-side
1382
+ * @description Generates a certificate PDF on the server and creates the certificate record.
1383
+ * This eliminates CORS issues by downloading the template image on the server.
1384
+ */
1385
+ post: {
1386
+ parameters: {
1387
+ query?: never;
1388
+ header?: never;
1389
+ path?: never;
1390
+ cookie?: never;
1391
+ };
1392
+ requestBody: {
1393
+ content: {
1394
+ "application/json": components["schemas"]["GenerateCertificateDto"];
1395
+ };
1396
+ };
1397
+ responses: {
1398
+ /** @description Certificate generated and created successfully */
1399
+ 201: {
1400
+ headers: {
1401
+ [name: string]: unknown;
1402
+ };
1403
+ content: {
1404
+ "application/json": {
1405
+ /** @example success */
1406
+ status?: string;
1407
+ data?: {
1408
+ certificate?: components["schemas"]["Certificate"];
1409
+ /** @example https://s3.amazonaws.com/bucket/certificate.pdf */
1410
+ pdfUrl?: string;
1411
+ };
1412
+ };
1413
+ };
1414
+ };
1415
+ 400: components["responses"]["BadRequest"];
1416
+ 401: components["responses"]["Unauthorized"];
1417
+ 500: components["responses"]["ServerError"];
1418
+ };
1419
+ };
1420
+ delete?: never;
1421
+ options?: never;
1422
+ head?: never;
1423
+ patch?: never;
1424
+ trace?: never;
1425
+ };
1036
1426
  "/classrooms": {
1037
1427
  parameters: {
1038
1428
  query?: never;
@@ -9540,6 +9930,8 @@ export interface paths {
9540
9930
  requestBody: {
9541
9931
  content: {
9542
9932
  "application/json": {
9933
+ /** @example sa@academe.com.br */
9934
+ email?: string;
9543
9935
  /** @example John */
9544
9936
  firstName?: string;
9545
9937
  /** @example Doe */
@@ -9939,7 +10331,19 @@ export interface paths {
9939
10331
  head?: never;
9940
10332
  /**
9941
10333
  * Update user
9942
- * @description Update user information
10334
+ * @description Update user information.
10335
+ *
10336
+ * **Profile and seat code behavior:**
10337
+ * - Send `groupId` to change the user's profile/group.
10338
+ * - When `groupId` changes, the system will:
10339
+ * 1. Release the previous seat code used by the user (`isReserved = false`)
10340
+ * 2. Allocate a seat code for the new group in the same institution registration
10341
+ * 3. Create a new seat code automatically if none is available for that group
10342
+ *
10343
+ * **Important:**
10344
+ * - The target group must exist.
10345
+ * - The institution must have a seat configured for the target group.
10346
+ * - If any institution registration cannot be migrated to the new group, the operation fails.
9943
10347
  */
9944
10348
  patch: {
9945
10349
  parameters: {
@@ -10255,6 +10659,165 @@ export interface paths {
10255
10659
  patch?: never;
10256
10660
  trace?: never;
10257
10661
  };
10662
+ "/users/register-hotmart": {
10663
+ parameters: {
10664
+ query?: never;
10665
+ header?: never;
10666
+ path?: never;
10667
+ cookie?: never;
10668
+ };
10669
+ get?: never;
10670
+ put?: never;
10671
+ /**
10672
+ * Hotmart webhook for user registration and lifecycle
10673
+ * @description Public endpoint that receives webhook events from Hotmart.
10674
+ * Handles user creation and deactivation based on purchase events.
10675
+ *
10676
+ * **Supported Events:**
10677
+ *
10678
+ * | Event | Action |
10679
+ * |-------|--------|
10680
+ * | `PURCHASE_APPROVED` | Enqueues user creation with buyer data |
10681
+ * | `PURCHASE_CANCELED` | Deactivates (soft deletes) the user |
10682
+ * | `PURCHASE_REFUNDED` | Deactivates (soft deletes) the user |
10683
+ * | `PURCHASE_CHARGEBACK` | Deactivates (soft deletes) the user |
10684
+ *
10685
+ * **User Creation Flow (PURCHASE_APPROVED):**
10686
+ * 1. Extracts buyer information from the Hotmart payload
10687
+ * 2. Determines institution based on offer code
10688
+ * 3. Enqueues user creation request for async processing
10689
+ * 4. User is created in Keycloak and database by the worker
10690
+ *
10691
+ * **Offer-based Institution Assignment:**
10692
+ * - Offer code `d9to7y3w` → Institution `580268b1-458d-4f4c-901d-281eccf3b40d`
10693
+ *
10694
+ * **User Deactivation Flow:**
10695
+ * 1. Looks up the user by buyer email
10696
+ * 2. Soft deletes the user (marks as inactive)
10697
+ * 3. Disables the user in Keycloak
10698
+ *
10699
+ * All events return HTTP 200 to acknowledge receipt.
10700
+ * Unrecognized events are logged and ignored.
10701
+ */
10702
+ post: {
10703
+ parameters: {
10704
+ query?: never;
10705
+ header?: never;
10706
+ path?: never;
10707
+ cookie?: never;
10708
+ };
10709
+ requestBody: {
10710
+ content: {
10711
+ "application/json": {
10712
+ /**
10713
+ * Format: uuid
10714
+ * @description Unique webhook event ID
10715
+ * @example d96fafd0-75ed-4ca7-8fa8-c6a57f48e384
10716
+ */
10717
+ id: string;
10718
+ /**
10719
+ * @description Hotmart event type
10720
+ * @example PURCHASE_APPROVED
10721
+ * @enum {string}
10722
+ */
10723
+ event: "PURCHASE_APPROVED" | "PURCHASE_CANCELED" | "PURCHASE_REFUNDED" | "PURCHASE_CHARGEBACK" | "PURCHASE_COMPLETE" | "PURCHASE_PROTEST" | "PURCHASE_DELAYED";
10724
+ /**
10725
+ * @description Event creation timestamp (Unix milliseconds)
10726
+ * @example 1773161133350
10727
+ */
10728
+ creation_date: number;
10729
+ /**
10730
+ * @description Webhook payload version
10731
+ * @example 2.0.0
10732
+ */
10733
+ version: string;
10734
+ data: {
10735
+ buyer: {
10736
+ /**
10737
+ * Format: email
10738
+ * @example buyer@example.com
10739
+ */
10740
+ email: string;
10741
+ /** @example Teste */
10742
+ first_name: string;
10743
+ /** @example Comprador */
10744
+ last_name: string;
10745
+ /** @example Teste Comprador */
10746
+ name?: string;
10747
+ /** @example 69526128664 */
10748
+ document?: string;
10749
+ /** @example CPF */
10750
+ document_type?: string;
10751
+ /** @example 99999999900 */
10752
+ checkout_phone?: string;
10753
+ };
10754
+ purchase: {
10755
+ /** @example HP16015479281022 */
10756
+ transaction: string;
10757
+ /** @example APPROVED */
10758
+ status: string;
10759
+ offer?: {
10760
+ /**
10761
+ * @description Offer code used to determine institution assignment
10762
+ * @example d9to7y3w
10763
+ */
10764
+ code?: string;
10765
+ };
10766
+ };
10767
+ product: {
10768
+ id?: number;
10769
+ name?: string;
10770
+ /** Format: uuid */
10771
+ ucode?: string;
10772
+ };
10773
+ subscription?: {
10774
+ subscriber?: {
10775
+ code?: string;
10776
+ };
10777
+ plan?: {
10778
+ name?: string;
10779
+ id?: number;
10780
+ };
10781
+ status?: string;
10782
+ };
10783
+ };
10784
+ };
10785
+ };
10786
+ };
10787
+ responses: {
10788
+ /** @description Webhook event processed successfully */
10789
+ 200: {
10790
+ headers: {
10791
+ [name: string]: unknown;
10792
+ };
10793
+ content: {
10794
+ "application/json": {
10795
+ /** @example success */
10796
+ status?: string;
10797
+ /**
10798
+ * @description Action taken for the event
10799
+ * @example user_creation_enqueued
10800
+ * @enum {string}
10801
+ */
10802
+ action?: "user_creation_enqueued" | "user_deactivated" | "user_not_found" | "event_ignored";
10803
+ /**
10804
+ * Format: uuid
10805
+ * @description Request ID for tracking (only on user_creation_enqueued)
10806
+ * @example 550e8400-e29b-41d4-a716-446655440000
10807
+ */
10808
+ requestId?: string;
10809
+ };
10810
+ };
10811
+ };
10812
+ 500: components["responses"]["ServerError"];
10813
+ };
10814
+ };
10815
+ delete?: never;
10816
+ options?: never;
10817
+ head?: never;
10818
+ patch?: never;
10819
+ trace?: never;
10820
+ };
10258
10821
  }
10259
10822
  export type webhooks = Record<string, never>;
10260
10823
  export interface components {
@@ -11500,6 +12063,20 @@ export interface components {
11500
12063
  };
11501
12064
  };
11502
12065
  };
12066
+ ForgotPasswordRequest: {
12067
+ /**
12068
+ * Format: email
12069
+ * @description User's email address for password reset
12070
+ * @example user@example.com
12071
+ */
12072
+ email: string;
12073
+ };
12074
+ ForgotPasswordResponse: {
12075
+ /** @example success */
12076
+ status?: string;
12077
+ /** @example If this email is registered, you will receive password reset instructions. */
12078
+ message?: string;
12079
+ };
11503
12080
  CreateInstitutionClassroomDto: {
11504
12081
  /**
11505
12082
  * Format: uuid
@@ -11680,10 +12257,14 @@ export interface components {
11680
12257
  * @description Product ID associated with this seat
11681
12258
  */
11682
12259
  productId?: string;
12260
+ /** @description Total number of seat codes for this seat */
12261
+ total?: number;
11683
12262
  /** @description Available quantity for this group in the institution */
11684
12263
  availableQuantity?: number;
11685
12264
  /** @description Number of users currently assigned to this group in the institution */
11686
12265
  usedQuantity?: number;
12266
+ /** @description Number of reserved seat codes without active registration */
12267
+ reservedQuantity?: number;
11687
12268
  /** Format: date-time */
11688
12269
  createdAt?: string;
11689
12270
  /** Format: date-time */
@@ -12172,6 +12753,27 @@ export interface components {
12172
12753
  /** Format: uri */
12173
12754
  url?: string;
12174
12755
  };
12756
+ GenerateCertificateDto: {
12757
+ /** Format: uuid */
12758
+ userId: string;
12759
+ /** Format: uuid */
12760
+ quizAttemptId?: string;
12761
+ /** Format: uuid */
12762
+ certificateTemplateId: string;
12763
+ /** @example João Silva */
12764
+ studentName: string;
12765
+ /** @example Introdução ao JavaScript */
12766
+ quizTitle: string;
12767
+ /** @example 85 */
12768
+ score?: number;
12769
+ /** @example CERT-2024-000001 */
12770
+ certificateNumber?: string;
12771
+ /**
12772
+ * Format: date-time
12773
+ * @example 2024-01-01T00:00:00.000Z
12774
+ */
12775
+ issuedAt?: string;
12776
+ };
12175
12777
  CreateGuardianDto: {
12176
12778
  /**
12177
12779
  * Format: email