@hed-hog/lms 0.0.350 → 0.0.351

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.
Files changed (160) hide show
  1. package/dist/certificate/certificate.controller.d.ts +2 -2
  2. package/dist/certificate/certificate.controller.d.ts.map +1 -1
  3. package/dist/certificate/certificate.controller.js +8 -6
  4. package/dist/certificate/certificate.controller.js.map +1 -1
  5. package/dist/certificate/certificate.service.d.ts +5 -2
  6. package/dist/certificate/certificate.service.d.ts.map +1 -1
  7. package/dist/certificate/certificate.service.js +70 -6
  8. package/dist/certificate/certificate.service.js.map +1 -1
  9. package/dist/course/course-structure.controller.d.ts +24 -10
  10. package/dist/course/course-structure.controller.d.ts.map +1 -1
  11. package/dist/course/course-structure.controller.js +23 -2
  12. package/dist/course/course-structure.controller.js.map +1 -1
  13. package/dist/course/course-structure.service.d.ts +16 -8
  14. package/dist/course/course-structure.service.d.ts.map +1 -1
  15. package/dist/course/course-structure.service.js +61 -30
  16. package/dist/course/course-structure.service.js.map +1 -1
  17. package/dist/course/course-video-conversion.service.d.ts +37 -0
  18. package/dist/course/course-video-conversion.service.d.ts.map +1 -0
  19. package/dist/course/course-video-conversion.service.js +308 -0
  20. package/dist/course/course-video-conversion.service.js.map +1 -0
  21. package/dist/course/course.controller.d.ts +17 -0
  22. package/dist/course/course.controller.d.ts.map +1 -1
  23. package/dist/course/course.controller.js +23 -0
  24. package/dist/course/course.controller.js.map +1 -1
  25. package/dist/course/course.module.d.ts.map +1 -1
  26. package/dist/course/course.module.js +15 -2
  27. package/dist/course/course.module.js.map +1 -1
  28. package/dist/course/course.service.d.ts +15 -0
  29. package/dist/course/course.service.d.ts.map +1 -1
  30. package/dist/course/course.service.js +103 -49
  31. package/dist/course/course.service.js.map +1 -1
  32. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +5 -1
  33. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  34. package/dist/course/dto/create-course-structure-lesson.dto.js +16 -2
  35. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  36. package/dist/course/dto/create-course.dto.d.ts +1 -0
  37. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  38. package/dist/course/dto/create-course.dto.js +9 -0
  39. package/dist/course/dto/create-course.dto.js.map +1 -1
  40. package/dist/enterprise/enterprise.controller.d.ts +3 -3
  41. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  42. package/dist/enterprise/enterprise.controller.js +0 -1
  43. package/dist/enterprise/enterprise.controller.js.map +1 -1
  44. package/dist/enterprise/enterprise.service.d.ts +3 -3
  45. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  46. package/dist/evaluation/evaluation.service.js +9 -2
  47. package/dist/evaluation/evaluation.service.js.map +1 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/lms.module.d.ts.map +1 -1
  53. package/dist/lms.module.js +3 -0
  54. package/dist/lms.module.js.map +1 -1
  55. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.d.ts +6 -0
  56. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.d.ts.map +1 -0
  57. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.js +33 -0
  58. package/dist/video-resolution-profile/dto/create-video-resolution-profile.dto.js.map +1 -0
  59. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.d.ts +6 -0
  60. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.d.ts.map +1 -0
  61. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.js +33 -0
  62. package/dist/video-resolution-profile/dto/update-video-resolution-profile.dto.js.map +1 -0
  63. package/dist/video-resolution-profile/video-resolution-profile.controller.d.ts +38 -0
  64. package/dist/video-resolution-profile/video-resolution-profile.controller.d.ts.map +1 -0
  65. package/dist/video-resolution-profile/video-resolution-profile.controller.js +89 -0
  66. package/dist/video-resolution-profile/video-resolution-profile.controller.js.map +1 -0
  67. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.d.ts +26 -0
  68. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.d.ts.map +1 -0
  69. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.js +160 -0
  70. package/dist/video-resolution-profile/video-resolution-profile.mcp-tools.js.map +1 -0
  71. package/dist/video-resolution-profile/video-resolution-profile.module.d.ts +3 -0
  72. package/dist/video-resolution-profile/video-resolution-profile.module.d.ts.map +1 -0
  73. package/dist/video-resolution-profile/video-resolution-profile.module.js +26 -0
  74. package/dist/video-resolution-profile/video-resolution-profile.module.js.map +1 -0
  75. package/dist/video-resolution-profile/video-resolution-profile.service.d.ts +45 -0
  76. package/dist/video-resolution-profile/video-resolution-profile.service.d.ts.map +1 -0
  77. package/dist/video-resolution-profile/video-resolution-profile.service.js +117 -0
  78. package/dist/video-resolution-profile/video-resolution-profile.service.js.map +1 -0
  79. package/hedhog/data/menu.yaml +17 -0
  80. package/hedhog/data/route.yaml +133 -0
  81. package/hedhog/data/video_resolution_profile.yaml +7 -0
  82. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +269 -324
  83. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +124 -70
  84. package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +7 -4
  85. package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +2 -2
  86. package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +2 -2
  87. package/hedhog/frontend/app/_lib/editor/templateSerializer.ts.ejs +34 -4
  88. package/hedhog/frontend/app/_lib/editor/types.ts.ejs +28 -3
  89. package/hedhog/frontend/app/achievements/page.tsx.ejs +9 -3
  90. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +9 -3
  91. package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +7 -3
  92. package/hedhog/frontend/app/certificates/models/CanvasStage.tsx.ejs +29 -8
  93. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +14 -0
  94. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +194 -9
  95. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +15 -5
  96. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +9 -5
  97. package/hedhog/frontend/app/classes/page.tsx.ejs +73 -47
  98. package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +19 -9
  99. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +24 -1
  100. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +1 -1
  101. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +1 -1
  102. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +28 -16
  103. package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +11 -6
  104. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +7 -4
  105. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -0
  106. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +24 -87
  107. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +892 -411
  108. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1004 -293
  109. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +11 -11
  110. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +62 -52
  111. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +2 -0
  112. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +19 -6
  113. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +86 -1
  114. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +3 -0
  115. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +1 -0
  116. package/hedhog/frontend/app/courses/page.tsx.ejs +112 -89
  117. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +1 -1
  118. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +10 -3
  119. package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +8 -4
  120. package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +2 -2
  121. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +10 -4
  122. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +10 -3
  123. package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +10 -3
  124. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +10 -3
  125. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +23 -9
  126. package/hedhog/frontend/app/exams/page.tsx.ejs +14 -6
  127. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +9 -3
  128. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +190 -17
  129. package/hedhog/frontend/app/layout.tsx.ejs +5 -1
  130. package/hedhog/frontend/app/paths/page.tsx.ejs +13 -5
  131. package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +10 -10
  132. package/hedhog/frontend/app/training/page.tsx.ejs +13 -5
  133. package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +607 -0
  134. package/hedhog/frontend/messages/en.json +250 -9
  135. package/hedhog/frontend/messages/pt.json +250 -9
  136. package/hedhog/table/course.yaml +4 -0
  137. package/hedhog/table/course_lesson_file.yaml +8 -0
  138. package/hedhog/table/course_video_resolution_profile.yaml +22 -0
  139. package/hedhog/table/video_resolution_profile.yaml +18 -0
  140. package/package.json +7 -6
  141. package/src/certificate/certificate.controller.ts +19 -14
  142. package/src/certificate/certificate.service.ts +106 -11
  143. package/src/course/course-structure.controller.ts +24 -2
  144. package/src/course/course-structure.service.ts +21 -4
  145. package/src/course/course-video-conversion.service.ts +415 -0
  146. package/src/course/course.controller.ts +18 -0
  147. package/src/course/course.module.ts +15 -2
  148. package/src/course/course.service.ts +72 -2
  149. package/src/course/dto/create-course-structure-lesson.dto.ts +13 -2
  150. package/src/course/dto/create-course.dto.ts +8 -0
  151. package/src/enterprise/enterprise.controller.ts +0 -1
  152. package/src/evaluation/evaluation.service.ts +9 -2
  153. package/src/index.ts +1 -0
  154. package/src/lms.module.ts +3 -0
  155. package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +16 -0
  156. package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +16 -0
  157. package/src/video-resolution-profile/video-resolution-profile.controller.ts +62 -0
  158. package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +128 -0
  159. package/src/video-resolution-profile/video-resolution-profile.module.ts +13 -0
  160. package/src/video-resolution-profile/video-resolution-profile.service.ts +117 -0
@@ -242,13 +242,19 @@
242
242
  "certification": "Certificação",
243
243
  "flags": "Flags"
244
244
  },
245
+ "sectionDescriptions": {
246
+ "basicInfo": "Os identificadores e a comunicação principal do curso ficam aqui.",
247
+ "relations": "Agrupe os vínculos que ajudam a encontrar, operar e ensinar este curso.",
248
+ "certification": "Escolha o modelo usado na emissão e deixe o impacto desse vínculo explícito."
249
+ },
245
250
  "title": "Dados do Curso",
246
251
  "description": "Edite as informações do curso abaixo. Os campos com * são obrigatórios.",
247
252
  "fields": {
248
253
  "code": {
249
- "label": "Código *",
250
- "placeholder": "Ex: REACT-ADV",
251
- "description": "Identificador único (letras, números e hífens)"
254
+ "label": "Código de Edição",
255
+ "placeholder": "Ex: REACT2024",
256
+ "description": "Somente letras maiúsculas e números, sem espaços (mínimo 2)",
257
+ "sectionTitle": "Código de Edição"
252
258
  },
253
259
  "internalName": {
254
260
  "label": "Nome Interno *",
@@ -291,9 +297,18 @@
291
297
  "selectPlaceholder": "Selecione uma categoria",
292
298
  "searchPlaceholder": "Digite para buscar categoria...",
293
299
  "noResults": "Nenhuma categoria encontrada",
300
+ "emptyHint": "Nenhuma categoria vinculada ainda.",
294
301
  "selectedCount": "categoria(s) selecionada(s)",
295
302
  "removeAction": "Remover categoria",
296
- "createAction": "Criar categoria"
303
+ "createAction": "Criar categoria",
304
+ "createTitle": "Criar categoria",
305
+ "createDescription": "Cadastre uma categoria rapidamente sem sair da edição do curso.",
306
+ "createFields": {
307
+ "name": {
308
+ "label": "Nome da categoria",
309
+ "placeholder": "Ex: Desenvolvimento Web"
310
+ }
311
+ }
297
312
  },
298
313
  "instructors": {
299
314
  "label": "Instrutores",
@@ -302,7 +317,16 @@
302
317
  "selectPlaceholder": "Selecione um instrutor",
303
318
  "searchPlaceholder": "Digite para buscar instrutor...",
304
319
  "noResults": "Nenhum instrutor encontrado",
305
- "removeAction": "Remover instrutor"
320
+ "emptyHint": "Nenhum instrutor vinculado ainda.",
321
+ "removeAction": "Remover instrutor",
322
+ "createAction": "Cadastrar instrutor"
323
+ },
324
+ "operationsProject": {
325
+ "label": "Projeto vinculado",
326
+ "placeholder": "Selecione um projeto do Operations",
327
+ "searchPlaceholder": "Buscar projeto...",
328
+ "entityLabel": "projeto",
329
+ "noResults": "Nenhum projeto encontrado"
306
330
  },
307
331
  "prerequisites": {
308
332
  "label": "Pré-requisitos",
@@ -328,7 +352,21 @@
328
352
  "certificateModel": {
329
353
  "label": "Modelo de Certificado",
330
354
  "description": "Selecione um único modelo para emissão do certificado",
331
- "placeholder": "Selecione um modelo"
355
+ "placeholder": "Selecione um modelo",
356
+ "entityLabel": "modelo de certificado",
357
+ "createAction": "Criar modelo",
358
+ "createTitle": "Criar modelo de certificado",
359
+ "createDescription": "Crie um modelo base e já o vincule ao curso sem sair desta página.",
360
+ "createFields": {
361
+ "name": {
362
+ "label": "Nome do modelo",
363
+ "placeholder": "Ex: Certificado institucional"
364
+ },
365
+ "description": {
366
+ "label": "Descrição",
367
+ "placeholder": "Resumo interno para identificar o template"
368
+ }
369
+ }
332
370
  }
333
371
  },
334
372
  "media": {
@@ -389,7 +427,76 @@
389
427
  "media": "Mídia",
390
428
  "resources": "Recursos",
391
429
  "extra": "Extra",
392
- "publish": "Publicação"
430
+ "publish": "Publicação",
431
+ "videoProfiles": "Vídeos"
432
+ },
433
+ "structureTab": {
434
+ "summary": {
435
+ "title": "Resumo do conteúdo",
436
+ "stats": {
437
+ "sessions": "Sessões",
438
+ "lessons": "Aulas",
439
+ "duration": "Duração",
440
+ "published": "Publicadas"
441
+ }
442
+ },
443
+ "mainInfo": {
444
+ "title": "Dados principais",
445
+ "fields": {
446
+ "title": "Título",
447
+ "internalName": "Nome Interno",
448
+ "slug": "Slug"
449
+ }
450
+ },
451
+ "description": {
452
+ "title": "Descrição"
453
+ }
454
+ },
455
+ "resources": {
456
+ "title": "Recursos para download do curso",
457
+ "dropzoneAriaLabel": "Soltar arquivo ou clicar para selecionar",
458
+ "uploading": "Enviando arquivos...",
459
+ "saving": "Salvando recursos...",
460
+ "idle": "Arraste arquivos para adicionar",
461
+ "helper": "Clique para selecionar arquivos",
462
+ "empty": "Nenhum recurso de curso cadastrado ainda.",
463
+ "attachedFile": "Arquivo anexado",
464
+ "downloadAria": "Baixar {name}",
465
+ "removeAria": "Remover {name}"
466
+ },
467
+ "videoProfiles": {
468
+ "title": "Perfis de Resolução de Vídeo",
469
+ "placeholder": "Selecionar perfil de vídeo...",
470
+ "createTitle": "Novo perfil de vídeo",
471
+ "createFields": {
472
+ "name": {
473
+ "label": "Nome",
474
+ "placeholder": "Ex: 1080p Full HD"
475
+ },
476
+ "ffmpegParams": {
477
+ "label": "Parâmetros FFmpeg",
478
+ "placeholder": "-vf \"scale=1920:1080\" -c:v libx264 ..."
479
+ }
480
+ },
481
+ "createError": "Não foi possível criar o perfil.",
482
+ "empty": "Nenhum perfil de resolução adicionado.",
483
+ "fallbackName": "Perfil #{id}",
484
+ "editAria": "Editar perfil",
485
+ "removeAria": "Remover perfil",
486
+ "sheet": {
487
+ "title": "Editar perfil de vídeo",
488
+ "description": "Atualize o nome e os parâmetros FFmpeg deste perfil de resolução.",
489
+ "actions": {
490
+ "cancel": "Cancelar",
491
+ "save": "Salvar",
492
+ "saving": "Salvando..."
493
+ }
494
+ }
495
+ },
496
+ "footer": {
497
+ "cancel": "Cancelar",
498
+ "newSession": "Nova sessão",
499
+ "save": "Salvar"
393
500
  },
394
501
  "publishChecklist": {
395
502
  "title": "Checklist de publicação",
@@ -441,7 +548,8 @@
441
548
  "statusRequired": "Selecione um status",
442
549
  "offeringTypeRequired": "Selecione o formato do curso",
443
550
  "categoryRequired": "Selecione pelo menos uma categoria",
444
- "colorInvalid": "Informe uma cor hexadecimal válida (ex: #107f46)"
551
+ "colorInvalid": "Informe uma cor hexadecimal válida (ex: #107f46)",
552
+ "editionCodeFormat": "Somente letras maiúsculas e números, sem espaços (mínimo 2)"
445
553
  }
446
554
  },
447
555
  "CoursesPage": {
@@ -616,6 +724,10 @@
616
724
  "replace": "Substituir logo",
617
725
  "remove": "Remover",
618
726
  "description": "Imagem opcional para identificar o curso"
727
+ },
728
+ "code": {
729
+ "label": "Código de Edição",
730
+ "placeholder": "Ex: REACT2024"
619
731
  }
620
732
  },
621
733
  "flags": {
@@ -649,7 +761,8 @@
649
761
  "levelRequired": "Selecione um nível",
650
762
  "statusRequired": "Selecione um status",
651
763
  "categoriesRequired": "Selecione pelo menos uma categoria",
652
- "colorInvalid": "Informe uma cor HEX válida"
764
+ "colorInvalid": "Informe uma cor HEX válida",
765
+ "editionCodePattern": "Somente letras maiúsculas e números, mínimo 2 caracteres"
653
766
  }
654
767
  },
655
768
  "deleteDialog": {
@@ -936,6 +1049,7 @@
936
1049
  "update": "Atualizar",
937
1050
  "save": "Salvar aula",
938
1051
  "tabData": "Dados",
1052
+ "tabVideos": "Vídeos",
939
1053
  "tabTranscription": "Transcrição",
940
1054
  "tabResources": "Recursos",
941
1055
  "videoProvider": "Provedor de vídeo",
@@ -959,6 +1073,70 @@
959
1073
  "videoResolutionPlaceholder": "Ex.: 1080p",
960
1074
  "uploadVideoForResolution": "Enviar vídeo para esta resolução",
961
1075
  "noResolutionVideosYet": "Nenhum vídeo por resolução enviado ainda.",
1076
+ "loadingVideoProfiles": "Carregando perfis de vídeo do curso...",
1077
+ "videoProfilesLoadError": "Não foi possível carregar os perfis de vídeo do curso.",
1078
+ "retryLoadVideoProfiles": "Tentar carregar novamente",
1079
+ "noVideoProfilesConfigured": "Configure os perfis de vídeo na aba Vídeos do curso antes de anexar vídeos File Storage.",
1080
+ "originalVideoTitle": "Vídeo original em alta resolução",
1081
+ "originalVideoHint": "Envie um original para gerar automaticamente os vídeos dos perfis do curso.",
1082
+ "originalVideoPurpose": "Este arquivo original é usado apenas para gerar as versões de vídeo do curso.",
1083
+ "fileStorageVideoHint": "Use o vídeo original abaixo para gerar automaticamente as versões por perfil do curso.",
1084
+ "uploadOriginalForConversion": "Enviar original para conversão",
1085
+ "videoUploadBlockedWhileProcessing": "O upload de original fica bloqueado enquanto a conversão atual estiver em andamento.",
1086
+ "videoUploadMaxSizeError": "O vídeo excede o limite de {size}.",
1087
+ "videoConversionQueued": "Conversão de vídeo enviada para a fila #{id}.",
1088
+ "videoConversionFailed": "Não foi possível enviar o vídeo para conversão.",
1089
+ "videoConversionJob": "Conversão na fila #{id}",
1090
+ "videoJobFeedbackTitle": "Acompanhamento da conversão",
1091
+ "videoJobLoading": "Carregando andamento da conversão...",
1092
+ "videoJobPendingLoad": "Preparando acompanhamento do job de conversão...",
1093
+ "videoJobLoadError": "Não foi possível carregar o andamento da conversão.",
1094
+ "retryLoadVideoJob": "Tentar novamente",
1095
+ "videoJobIdLabel": "Job",
1096
+ "videoJobAttemptsLabel": "Tentativas",
1097
+ "videoJobAttemptsValue": "{current} de {total}",
1098
+ "videoJobCreatedAt": "Enviado em",
1099
+ "videoJobStartedAt": "Iniciado em",
1100
+ "videoJobFinishedAt": "Finalizado em",
1101
+ "videoJobLatestAttempt": "Última tentativa",
1102
+ "videoJobAttemptValue": "Tentativa {count}",
1103
+ "videoJobLastError": "Último erro",
1104
+ "videoJobRecentEvents": "Eventos recentes",
1105
+ "videoJobNoEvents": "Nenhum evento registrado ainda.",
1106
+ "videoProfilesLockedWhileProcessing": "Os vídeos por perfil ficam bloqueados enquanto a conversão do original está ativa.",
1107
+ "videoJobStatuses": {
1108
+ "pending": "Na fila",
1109
+ "scheduled": "Agendado",
1110
+ "processing": "Processando",
1111
+ "completed": "Concluído",
1112
+ "failed": "Falhou",
1113
+ "retrying": "Tentando novamente",
1114
+ "canceled": "Cancelado",
1115
+ "dead_letter": "Falha definitiva"
1116
+ },
1117
+ "videoAttemptStatuses": {
1118
+ "processing": "Em execução",
1119
+ "completed": "Concluída",
1120
+ "failed": "Falhou",
1121
+ "canceled": "Cancelada"
1122
+ },
1123
+ "videoJobEvents": {
1124
+ "created": "Criado",
1125
+ "scheduled": "Agendado",
1126
+ "locked": "Reservado para worker",
1127
+ "started": "Iniciado",
1128
+ "completed": "Concluído",
1129
+ "failed": "Falhou",
1130
+ "retry_scheduled": "Nova tentativa agendada",
1131
+ "canceled": "Cancelado",
1132
+ "moved_to_dead_letter": "Movido para falha definitiva",
1133
+ "requeued": "Reenfileirado",
1134
+ "unlocked": "Liberado"
1135
+ },
1136
+ "videoProfileMissing": "Nenhum vídeo enviado para este perfil.",
1137
+ "playVideoAria": "Reproduzir vídeo {name}",
1138
+ "videoPreviewTitle": "Pré-visualização do vídeo: {name}",
1139
+ "replaceVideo": "Substituir",
962
1140
  "openVideoAria": "Abrir vídeo {name}",
963
1141
  "downloadVideoAria": "Baixar vídeo {name}",
964
1142
  "removeVideoAria": "Remover vídeo {name}",
@@ -3866,6 +4044,8 @@
3866
4044
  }
3867
4045
  },
3868
4046
  "evaluations": {
4047
+ "emptyTitle": "Avaliações — funcionalidade em breve",
4048
+ "emptyDescription": "Em breve você poderá visualizar as avaliações recebidas pelos alunos.",
3869
4049
  "comingSoonTitle": "Avaliações — funcionalidade em breve",
3870
4050
  "comingSoonDescription": "Em breve você poderá visualizar as avaliações recebidas pelos alunos."
3871
4051
  },
@@ -4418,5 +4598,66 @@
4418
4598
  "deleteError": "Erro ao excluir itens"
4419
4599
  }
4420
4600
  }
4601
+ },
4602
+ "VideoResolutionProfilesPage": {
4603
+ "title": "Perfis de Vídeo",
4604
+ "description": "Gerencie perfis de codificação FFmpeg para transcodificação de vídeos.",
4605
+ "breadcrumbs": {
4606
+ "home": "Início",
4607
+ "lms": "LMS",
4608
+ "current": "Perfis de Vídeo"
4609
+ },
4610
+ "table": {
4611
+ "name": "Nome",
4612
+ "ffmpegParams": "Parâmetros FFmpeg",
4613
+ "status": "Status"
4614
+ },
4615
+ "form": {
4616
+ "name": "Nome",
4617
+ "namePlaceholder": "Ex: 1080p Full HD",
4618
+ "ffmpegParams": "Parâmetros FFmpeg",
4619
+ "ffmpegParamsPlaceholder": "-vf \"scale=1920:1080\" -c:v libx264 ...",
4620
+ "status": "Status",
4621
+ "statusPlaceholder": "Selecione o status",
4622
+ "validation": {
4623
+ "required": "Este campo é obrigatório.",
4624
+ "max100": "Máximo de 100 caracteres."
4625
+ }
4626
+ },
4627
+ "sheet": {
4628
+ "createTitle": "Novo Perfil de Vídeo",
4629
+ "editTitle": "Editar Perfil de Vídeo"
4630
+ },
4631
+ "status": {
4632
+ "active": "Ativo",
4633
+ "inactive": "Inativo"
4634
+ },
4635
+ "actions": {
4636
+ "create": "Novo Perfil",
4637
+ "edit": "Editar",
4638
+ "delete": "Excluir",
4639
+ "save": "Salvar",
4640
+ "saving": "Salvando...",
4641
+ "cancel": "Cancelar",
4642
+ "deleting": "Excluindo..."
4643
+ },
4644
+ "filters": {
4645
+ "searchPlaceholder": "Buscar perfis..."
4646
+ },
4647
+ "empty": {
4648
+ "title": "Nenhum perfil de vídeo",
4649
+ "description": "Crie o primeiro perfil de resolução de vídeo para iniciar a transcodificação."
4650
+ },
4651
+ "messages": {
4652
+ "createSuccess": "Perfil criado com sucesso.",
4653
+ "updateSuccess": "Perfil atualizado com sucesso.",
4654
+ "deleteSuccess": "Perfil excluído com sucesso.",
4655
+ "saveError": "Erro ao salvar o perfil.",
4656
+ "deleteError": "Erro ao excluir o perfil."
4657
+ },
4658
+ "deleteDialog": {
4659
+ "title": "Excluir perfil",
4660
+ "description": "Tem certeza que deseja excluir o perfil \"{name}\"? Esta ação não pode ser desfeita."
4661
+ }
4421
4662
  }
4422
4663
  }
@@ -79,6 +79,10 @@ columns:
79
79
  - name: operations_project_id
80
80
  type: int
81
81
  isNullable: true
82
+ - name: code
83
+ type: varchar
84
+ length: 32
85
+ isNullable: true
82
86
  - type: created_at
83
87
  - type: updated_at
84
88
 
@@ -16,8 +16,16 @@ columns:
16
16
  - name: title
17
17
  type: varchar
18
18
  length: 255
19
+ - name: tipo
20
+ type: varchar
21
+ length: 80
22
+ isNullable: true
23
+ - name: publico
24
+ type: boolean
25
+ default: true
19
26
  - type: created_at
20
27
  - type: updated_at
21
28
 
22
29
  indices:
23
30
  - columns: [course_lesson_id]
31
+ - columns: [course_lesson_id, tipo]
@@ -0,0 +1,22 @@
1
+ columns:
2
+ - type: pk
3
+ - name: course_id
4
+ type: fk
5
+ references:
6
+ table: course
7
+ column: id
8
+ onDelete: CASCADE
9
+ - name: video_resolution_profile_id
10
+ type: fk
11
+ references:
12
+ table: video_resolution_profile
13
+ column: id
14
+ onDelete: CASCADE
15
+ - type: created_at
16
+ - type: updated_at
17
+
18
+ indices:
19
+ - columns: [course_id, video_resolution_profile_id]
20
+ isUnique: true
21
+ - columns: [course_id]
22
+ - columns: [video_resolution_profile_id]
@@ -0,0 +1,18 @@
1
+ columns:
2
+ - type: pk
3
+ - name: name
4
+ type: varchar
5
+ length: 100
6
+ - name: ffmpeg_params
7
+ type: text
8
+ - name: status
9
+ type: enum
10
+ values: [active, inactive]
11
+ default: active
12
+ - type: created_at
13
+ - type: updated_at
14
+
15
+ indices:
16
+ - columns: [name]
17
+ isUnique: true
18
+ - columns: [status]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/lms",
3
- "version": "0.0.350",
3
+ "version": "0.0.351",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,15 +9,16 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api-locale": "0.0.14",
13
12
  "@hed-hog/api-prisma": "0.0.6",
13
+ "@hed-hog/api-locale": "0.0.14",
14
14
  "@hed-hog/api-pagination": "0.0.7",
15
15
  "@hed-hog/api-types": "0.0.1",
16
16
  "@hed-hog/api": "0.0.8",
17
- "@hed-hog/core": "0.0.350",
18
- "@hed-hog/category": "0.0.350",
19
- "@hed-hog/finance": "0.0.350",
20
- "@hed-hog/contact": "0.0.350"
17
+ "@hed-hog/core": "0.0.351",
18
+ "@hed-hog/category": "0.0.351",
19
+ "@hed-hog/queue": "0.0.351",
20
+ "@hed-hog/finance": "0.0.351",
21
+ "@hed-hog/crm": "0.0.351"
21
22
  },
22
23
  "exports": {
23
24
  ".": {
@@ -1,16 +1,17 @@
1
1
  import { Role } from '@hed-hog/api';
2
2
  import {
3
- Body,
4
- Controller,
5
- Delete,
6
- Get,
7
- Param,
8
- ParseIntPipe,
9
- Patch,
10
- Post,
11
- Query,
12
- UploadedFile,
13
- UseInterceptors,
3
+ Body,
4
+ Controller,
5
+ Delete,
6
+ Get,
7
+ Headers,
8
+ Param,
9
+ ParseIntPipe,
10
+ Patch,
11
+ Post,
12
+ Query,
13
+ UploadedFile,
14
+ UseInterceptors,
14
15
  } from '@nestjs/common';
15
16
  import { FileInterceptor } from '@nestjs/platform-express';
16
17
  import { LmsCertificateService } from './certificate.service';
@@ -52,8 +53,9 @@ export class LmsCertificateController {
52
53
  updateTemplate(
53
54
  @Param('id', ParseIntPipe) id: number,
54
55
  @Body() dto: UpdateCertificateTemplateDto,
56
+ @Headers('accept-language') locale?: string,
55
57
  ) {
56
- return this.certificateService.updateTemplate(id, dto);
58
+ return this.certificateService.updateTemplate(id, dto, locale);
57
59
  }
58
60
 
59
61
  @Post('templates/background-image')
@@ -63,8 +65,11 @@ export class LmsCertificateController {
63
65
  }
64
66
 
65
67
  @Delete('templates/:id')
66
- deleteTemplate(@Param('id', ParseIntPipe) id: number) {
67
- return this.certificateService.deleteTemplate(id);
68
+ deleteTemplate(
69
+ @Param('id', ParseIntPipe) id: number,
70
+ @Headers('accept-language') locale?: string,
71
+ ) {
72
+ return this.certificateService.deleteTemplate(id, locale);
68
73
  }
69
74
 
70
75
  @Get('issued')
@@ -1,11 +1,11 @@
1
1
  import { PrismaService } from '@hed-hog/api-prisma';
2
2
  import { FileService, IntegrationDeveloperApiService } from '@hed-hog/core';
3
3
  import {
4
- BadRequestException,
5
- Inject,
6
- Injectable,
7
- NotFoundException,
8
- forwardRef,
4
+ BadRequestException,
5
+ Inject,
6
+ Injectable,
7
+ NotFoundException,
8
+ forwardRef,
9
9
  } from '@nestjs/common';
10
10
  import { CreateCertificateTemplateDto } from './dto/create-certificate-template.dto';
11
11
  import { UpdateCertificateTemplateDto } from './dto/update-certificate-template.dto';
@@ -27,6 +27,13 @@ type ListCertificateTemplatesParams = {
27
27
  status?: string;
28
28
  };
29
29
 
30
+ type TemplateLike = {
31
+ objects?: Array<{
32
+ type?: string;
33
+ image?: { file_id?: number | null };
34
+ }>;
35
+ };
36
+
30
37
  @Injectable()
31
38
  export class LmsCertificateService {
32
39
  constructor(
@@ -134,16 +141,33 @@ export class LmsCertificateService {
134
141
  return this.mapTemplate(template);
135
142
  }
136
143
 
137
- async updateTemplate(id: number, dto: UpdateCertificateTemplateDto) {
138
- await this.ensureTemplateExists(id);
144
+ async updateTemplate(
145
+ id: number,
146
+ dto: UpdateCertificateTemplateDto,
147
+ locale?: string,
148
+ ) {
149
+ const existingTemplate = await this.ensureTemplateExists(id);
139
150
 
140
151
  const payload = this.prepareTemplatePayload(dto, true);
141
152
 
153
+ const previousFileIds = this.extractTemplateImageFileIds(
154
+ existingTemplate.template_content,
155
+ );
156
+ const nextFileIds =
157
+ payload.template_content !== undefined
158
+ ? this.extractTemplateImageFileIds(payload.template_content)
159
+ : previousFileIds;
160
+ const removedFileIds = previousFileIds.filter(
161
+ (fileId) => !nextFileIds.includes(fileId),
162
+ );
163
+
142
164
  const template = await this.prisma.certificate_template.update({
143
165
  where: { id },
144
166
  data: payload,
145
167
  });
146
168
 
169
+ await this.deleteFilesIfExist(removedFileIds, locale);
170
+
147
171
  await this.integrationApi.publishEvent({
148
172
  eventName: 'lms.certificate_template.updated',
149
173
  sourceModule: 'lms',
@@ -155,8 +179,11 @@ export class LmsCertificateService {
155
179
  return this.mapTemplate(template);
156
180
  }
157
181
 
158
- async deleteTemplate(id: number) {
159
- await this.ensureTemplateExists(id);
182
+ async deleteTemplate(id: number, locale?: string) {
183
+ const existingTemplate = await this.ensureTemplateExists(id);
184
+ const fileIdsToDelete = this.extractTemplateImageFileIds(
185
+ existingTemplate.template_content,
186
+ );
160
187
 
161
188
  try {
162
189
  await this.prisma.certificate_template.delete({
@@ -166,6 +193,8 @@ export class LmsCertificateService {
166
193
  throw new BadRequestException('Certificate template cannot be removed');
167
194
  }
168
195
 
196
+ await this.deleteFilesIfExist(fileIdsToDelete, locale);
197
+
169
198
  await this.integrationApi.publishEvent({
170
199
  eventName: 'lms.certificate_template.deleted',
171
200
  sourceModule: 'lms',
@@ -462,14 +491,80 @@ export class LmsCertificateService {
462
491
  return normalized.slice(0, 255);
463
492
  }
464
493
 
494
+ private extractTemplateImageFileIds(templateContent: string): number[] {
495
+ try {
496
+ const parsed = JSON.parse(templateContent) as TemplateLike;
497
+ if (!Array.isArray(parsed?.objects)) {
498
+ return [];
499
+ }
500
+
501
+ const unique = new Set<number>();
502
+ for (const obj of parsed.objects) {
503
+ if (!obj || obj.type !== 'image') {
504
+ continue;
505
+ }
506
+
507
+ const fileId = obj.image?.file_id;
508
+ if (
509
+ typeof fileId === 'number' &&
510
+ Number.isInteger(fileId) &&
511
+ fileId > 0
512
+ ) {
513
+ unique.add(fileId);
514
+ }
515
+ }
516
+
517
+ return [...unique];
518
+ } catch {
519
+ return [];
520
+ }
521
+ }
522
+
523
+ private normalizeLocale(locale?: string) {
524
+ const value = String(locale || '').trim();
525
+ if (!value) {
526
+ return 'en';
527
+ }
528
+
529
+ return value.split(',')[0]?.trim() || 'en';
530
+ }
531
+
532
+ private async deleteFilesIfExist(fileIds: number[], locale?: string) {
533
+ if (!Array.isArray(fileIds) || fileIds.length === 0) {
534
+ return;
535
+ }
536
+
537
+ const existingFiles = await this.prisma.file.findMany({
538
+ where: {
539
+ id: { in: fileIds },
540
+ },
541
+ select: { id: true },
542
+ });
543
+
544
+ const existingIds = existingFiles.map((file) => file.id);
545
+ if (existingIds.length === 0) {
546
+ return;
547
+ }
548
+
549
+ await this.fileService.delete(this.normalizeLocale(locale), {
550
+ ids: existingIds,
551
+ });
552
+ }
553
+
465
554
  private async ensureTemplateExists(id: number) {
466
- const count = await this.prisma.certificate_template.count({
555
+ const template = await this.prisma.certificate_template.findUnique({
467
556
  where: { id },
557
+ select: {
558
+ id: true,
559
+ template_content: true,
560
+ },
468
561
  });
469
562
 
470
- if (!count) {
563
+ if (!template) {
471
564
  throw new NotFoundException('Certificate template not found');
472
565
  }
566
+
567
+ return template;
473
568
  }
474
569
 
475
570
  private async ensureCertificateExists(id: number) {