@hed-hog/lms 0.0.358 → 0.0.361

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.
@@ -1 +1 @@
1
- {"version":3,"file":"course.module.d.ts","sourceRoot":"","sources":["../../src/course/course.module.ts"],"names":[],"mappings":"AAiBA,qBAsBa,YAAY;CAAG"}
1
+ {"version":3,"file":"course.module.d.ts","sourceRoot":"","sources":["../../src/course/course.module.ts"],"names":[],"mappings":"AAmBA,qBA8Ba,YAAY;CAAG"}
@@ -13,16 +13,18 @@ const queue_1 = require("@hed-hog/queue");
13
13
  const common_1 = require("@nestjs/common");
14
14
  const instructor_module_1 = require("../instructor/instructor.module");
15
15
  const course_lesson_controller_1 = require("./course-lesson.controller");
16
- const course_operations_controller_1 = require("./course-operations.controller");
17
16
  const course_operations_integration_service_1 = require("./course-operations-integration.service");
17
+ const course_operations_controller_1 = require("./course-operations.controller");
18
18
  const course_structure_controller_1 = require("./course-structure.controller");
19
- const lms_setting_controller_1 = require("./lms-setting.controller");
20
19
  const course_structure_service_1 = require("./course-structure.service");
21
20
  const course_video_conversion_service_1 = require("./course-video-conversion.service");
22
21
  const course_controller_1 = require("./course.controller");
23
22
  const course_mcp_tools_1 = require("./course.mcp-tools");
24
23
  const course_service_1 = require("./course.service");
24
+ const lms_bulk_upload_controller_1 = require("./lms-bulk-upload.controller");
25
+ const lms_bulk_upload_service_1 = require("./lms-bulk-upload.service");
25
26
  const lms_operations_task_subscriber_1 = require("./lms-operations-task.subscriber");
27
+ const lms_setting_controller_1 = require("./lms-setting.controller");
26
28
  let CourseModule = class CourseModule {
27
29
  };
28
30
  exports.CourseModule = CourseModule;
@@ -34,12 +36,20 @@ exports.CourseModule = CourseModule = __decorate([
34
36
  (0, common_1.forwardRef)(() => core_1.CoreModule),
35
37
  (0, common_1.forwardRef)(() => queue_1.QueueModule),
36
38
  ],
37
- controllers: [course_controller_1.CourseController, course_structure_controller_1.CourseStructureController, course_lesson_controller_1.CourseLessonController, lms_setting_controller_1.LmsSettingController, course_operations_controller_1.CourseOperationsController],
39
+ controllers: [
40
+ course_controller_1.CourseController,
41
+ course_structure_controller_1.CourseStructureController,
42
+ course_lesson_controller_1.CourseLessonController,
43
+ lms_setting_controller_1.LmsSettingController,
44
+ course_operations_controller_1.CourseOperationsController,
45
+ lms_bulk_upload_controller_1.LmsBulkUploadController,
46
+ ],
38
47
  providers: [
39
48
  course_operations_integration_service_1.CourseOperationsIntegrationService,
40
49
  course_service_1.CourseService,
41
50
  course_structure_service_1.CourseStructureService,
42
51
  course_video_conversion_service_1.CourseVideoConversionService,
52
+ lms_bulk_upload_service_1.LmsBulkUploadService,
43
53
  course_mcp_tools_1.LmsCoursesMcpTools,
44
54
  lms_operations_task_subscriber_1.LmsOperationsTaskSubscriber,
45
55
  ],
@@ -1 +1 @@
1
- {"version":3,"file":"course.module.js","sourceRoot":"","sources":["../../src/course/course.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAmD;AACnD,wCAA2C;AAC3C,0CAA6C;AAC7C,2CAAoD;AACpD,uEAAmE;AACnE,yEAAoE;AACpE,iFAA4E;AAC5E,mGAA6F;AAC7F,+EAA0E;AAC1E,qEAAgE;AAChE,yEAAoE;AACpE,uFAAiF;AACjF,2DAAuD;AACvD,yDAAwD;AACxD,qDAAiD;AACjD,qFAA+E;AAwBxE,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IAtBxB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,yBAAY,CAAC;YAC9B,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,oCAAgB,CAAC;YAClC,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,iBAAU,CAAC;YAC5B,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,mBAAW,CAAC;SAC9B;QACD,WAAW,EAAE,CAAC,oCAAgB,EAAE,uDAAyB,EAAE,iDAAsB,EAAE,6CAAoB,EAAE,yDAA0B,CAAC;QACpI,SAAS,EAAE;YACT,0EAAkC;YAClC,8BAAa;YACb,iDAAsB;YACtB,8DAA4B;YAC5B,qCAAkB;YAClB,4DAA2B;SAC5B;QACD,OAAO,EAAE;YACP,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,8BAAa,CAAC;YAC/B,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,iDAAsB,CAAC;YACxC,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,8DAA4B,CAAC;SAC/C;KACF,CAAC;GACW,YAAY,CAAG"}
1
+ {"version":3,"file":"course.module.js","sourceRoot":"","sources":["../../src/course/course.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,oDAAmD;AACnD,wCAA2C;AAC3C,0CAA6C;AAC7C,2CAAoD;AACpD,uEAAmE;AACnE,yEAAoE;AACpE,mGAA6F;AAC7F,iFAA4E;AAC5E,+EAA0E;AAC1E,yEAAoE;AACpE,uFAAiF;AACjF,2DAAuD;AACvD,yDAAwD;AACxD,qDAAiD;AACjD,6EAAuE;AACvE,uEAAiE;AACjE,qFAA+E;AAC/E,qEAAgE;AAgCzD,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IA9BxB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,yBAAY,CAAC;YAC9B,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,oCAAgB,CAAC;YAClC,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,iBAAU,CAAC;YAC5B,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,mBAAW,CAAC;SAC9B;QACD,WAAW,EAAE;YACX,oCAAgB;YAChB,uDAAyB;YACzB,iDAAsB;YACtB,6CAAoB;YACpB,yDAA0B;YAC1B,oDAAuB;SACxB;QACD,SAAS,EAAE;YACT,0EAAkC;YAClC,8BAAa;YACb,iDAAsB;YACtB,8DAA4B;YAC5B,8CAAoB;YACpB,qCAAkB;YAClB,4DAA2B;SAC5B;QACD,OAAO,EAAE;YACP,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,8BAAa,CAAC;YAC/B,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,iDAAsB,CAAC;YACxC,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,8DAA4B,CAAC;SAC/C;KACF,CAAC;GACW,YAAY,CAAG"}
@@ -0,0 +1,37 @@
1
+ import { LmsBulkUploadService } from './lms-bulk-upload.service';
2
+ export declare class LmsBulkUploadController {
3
+ private readonly bulkUploadService;
4
+ constructor(bulkUploadService: LmsBulkUploadService);
5
+ getSettings(): Promise<{
6
+ storageProfileId: number;
7
+ bucketName: string;
8
+ sessionDurationSeconds: number;
9
+ }>;
10
+ getTemporaryCredentials(userId: number): Promise<{
11
+ provider: string;
12
+ bucket: string;
13
+ region: string;
14
+ keyPrefix: string;
15
+ credentials: {
16
+ accessKeyId: string;
17
+ secretAccessKey: string;
18
+ sessionToken: string;
19
+ expiresAt: string;
20
+ };
21
+ }>;
22
+ verifyFile(userId: number, payload: {
23
+ key?: string;
24
+ fileName?: string;
25
+ }): Promise<{
26
+ isValid: boolean;
27
+ key: string;
28
+ sizeBytes: number;
29
+ etag: string;
30
+ } | {
31
+ isValid: boolean;
32
+ key: string;
33
+ sizeBytes?: undefined;
34
+ etag?: undefined;
35
+ }>;
36
+ }
37
+ //# sourceMappingURL=lms-bulk-upload.controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lms-bulk-upload.controller.d.ts","sourceRoot":"","sources":["../../src/course/lms-bulk-upload.controller.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,qBAEa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,iBAAiB;gBAAjB,iBAAiB,EAAE,oBAAoB;IAGpE,WAAW;;;;;IAKX,uBAAuB,CAAa,MAAM,EAAE,MAAM;;;;;;;;;;;;IAKlD,UAAU,CACI,MAAM,EAAE,MAAM,EAClB,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;CAIvD"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.LmsBulkUploadController = void 0;
16
+ const api_1 = require("@hed-hog/api");
17
+ const common_1 = require("@nestjs/common");
18
+ const lms_bulk_upload_service_1 = require("./lms-bulk-upload.service");
19
+ let LmsBulkUploadController = class LmsBulkUploadController {
20
+ constructor(bulkUploadService) {
21
+ this.bulkUploadService = bulkUploadService;
22
+ }
23
+ getSettings() {
24
+ return this.bulkUploadService.getBulkUploadSettings();
25
+ }
26
+ getTemporaryCredentials(userId) {
27
+ return this.bulkUploadService.getTemporaryCredentials(userId);
28
+ }
29
+ verifyFile(userId, payload) {
30
+ return this.bulkUploadService.verifyFileOnS3(userId, payload !== null && payload !== void 0 ? payload : {});
31
+ }
32
+ };
33
+ exports.LmsBulkUploadController = LmsBulkUploadController;
34
+ __decorate([
35
+ (0, common_1.Get)('settings'),
36
+ __metadata("design:type", Function),
37
+ __metadata("design:paramtypes", []),
38
+ __metadata("design:returntype", void 0)
39
+ ], LmsBulkUploadController.prototype, "getSettings", null);
40
+ __decorate([
41
+ (0, common_1.Post)('temporary-credentials'),
42
+ __param(0, (0, api_1.User)('id')),
43
+ __metadata("design:type", Function),
44
+ __metadata("design:paramtypes", [Number]),
45
+ __metadata("design:returntype", void 0)
46
+ ], LmsBulkUploadController.prototype, "getTemporaryCredentials", null);
47
+ __decorate([
48
+ (0, common_1.Post)('verify'),
49
+ __param(0, (0, api_1.User)('id')),
50
+ __param(1, (0, common_1.Body)()),
51
+ __metadata("design:type", Function),
52
+ __metadata("design:paramtypes", [Number, Object]),
53
+ __metadata("design:returntype", void 0)
54
+ ], LmsBulkUploadController.prototype, "verifyFile", null);
55
+ exports.LmsBulkUploadController = LmsBulkUploadController = __decorate([
56
+ (0, api_1.Role)(),
57
+ (0, common_1.Controller)('lms/bulk-upload'),
58
+ __metadata("design:paramtypes", [lms_bulk_upload_service_1.LmsBulkUploadService])
59
+ ], LmsBulkUploadController);
60
+ //# sourceMappingURL=lms-bulk-upload.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lms-bulk-upload.controller.js","sourceRoot":"","sources":["../../src/course/lms-bulk-upload.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,sCAA0C;AAC1C,2CAA6D;AAC7D,uEAAiE;AAI1D,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAClC,YAA6B,iBAAuC;QAAvC,sBAAiB,GAAjB,iBAAiB,CAAsB;IAAG,CAAC;IAGxE,WAAW;QACT,OAAO,IAAI,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,CAAC;IACxD,CAAC;IAGD,uBAAuB,CAAa,MAAc;QAChD,OAAO,IAAI,CAAC,iBAAiB,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChE,CAAC;IAGD,UAAU,CACI,MAAc,EAClB,OAA4C;QAEpD,OAAO,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,EAAE,CAAC,CAAC;IACtE,CAAC;CACF,CAAA;AApBY,0DAAuB;AAIlC;IADC,IAAA,YAAG,EAAC,UAAU,CAAC;;;;0DAGf;AAGD;IADC,IAAA,aAAI,EAAC,uBAAuB,CAAC;IACL,WAAA,IAAA,UAAI,EAAC,IAAI,CAAC,CAAA;;;;sEAElC;AAGD;IADC,IAAA,aAAI,EAAC,QAAQ,CAAC;IAEZ,WAAA,IAAA,UAAI,EAAC,IAAI,CAAC,CAAA;IACV,WAAA,IAAA,aAAI,GAAE,CAAA;;;;yDAGR;kCAnBU,uBAAuB;IAFnC,IAAA,UAAI,GAAE;IACN,IAAA,mBAAU,EAAC,iBAAiB,CAAC;qCAEoB,8CAAoB;GADzD,uBAAuB,CAoBnC"}
@@ -0,0 +1,42 @@
1
+ import { FileService } from '@hed-hog/core';
2
+ import { PrismaService } from '@hed-hog/api-prisma';
3
+ import { SettingService } from '@hed-hog/core';
4
+ export declare class LmsBulkUploadService {
5
+ private readonly settingService;
6
+ private readonly prismaService;
7
+ private readonly fileService;
8
+ constructor(settingService: SettingService, prismaService: PrismaService, fileService: FileService);
9
+ getBulkUploadSettings(): Promise<{
10
+ storageProfileId: number;
11
+ bucketName: string;
12
+ sessionDurationSeconds: number;
13
+ }>;
14
+ getTemporaryCredentials(userId: number): Promise<{
15
+ provider: string;
16
+ bucket: string;
17
+ region: string;
18
+ keyPrefix: string;
19
+ credentials: {
20
+ accessKeyId: string;
21
+ secretAccessKey: string;
22
+ sessionToken: string;
23
+ expiresAt: string;
24
+ };
25
+ }>;
26
+ verifyFileOnS3(userId: number, payload: {
27
+ key?: string;
28
+ fileName?: string;
29
+ }): Promise<{
30
+ isValid: boolean;
31
+ key: string;
32
+ sizeBytes: number;
33
+ etag: string;
34
+ } | {
35
+ isValid: boolean;
36
+ key: string;
37
+ sizeBytes?: undefined;
38
+ etag?: undefined;
39
+ }>;
40
+ private resolveStorageProfileAndBucket;
41
+ }
42
+ //# sourceMappingURL=lms-bulk-upload.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lms-bulk-upload.service.d.ts","sourceRoot":"","sources":["../../src/course/lms-bulk-upload.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAmB/C,qBACa,oBAAoB;IAG7B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAE/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAJX,cAAc,EAAE,cAAc,EAE9B,aAAa,EAAE,aAAa,EAE5B,WAAW,EAAE,WAAW;IAGrC,qBAAqB;;;;;IAkBrB,uBAAuB,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;IA2CtC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;YA2CnE,8BAA8B;CAmE7C"}
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.LmsBulkUploadService = void 0;
16
+ const core_1 = require("@hed-hog/core");
17
+ const api_prisma_1 = require("@hed-hog/api-prisma");
18
+ const core_2 = require("@hed-hog/core");
19
+ const common_1 = require("@nestjs/common");
20
+ let LmsBulkUploadService = class LmsBulkUploadService {
21
+ constructor(settingService, prismaService, fileService) {
22
+ this.settingService = settingService;
23
+ this.prismaService = prismaService;
24
+ this.fileService = fileService;
25
+ }
26
+ async getBulkUploadSettings() {
27
+ const settings = await this.settingService.getSettingValues([
28
+ 'lms-bulk-upload-storage-profile-id',
29
+ 'lms-bulk-upload-s3-bucket',
30
+ 'lms-bulk-upload-sts-duration-seconds',
31
+ ]);
32
+ return {
33
+ storageProfileId: Number(settings['lms-bulk-upload-storage-profile-id'] || 0) || null,
34
+ bucketName: typeof settings['lms-bulk-upload-s3-bucket'] === 'string'
35
+ ? settings['lms-bulk-upload-s3-bucket'].trim()
36
+ : '',
37
+ sessionDurationSeconds: Number(settings['lms-bulk-upload-sts-duration-seconds'] || 3600) || 3600,
38
+ };
39
+ }
40
+ async getTemporaryCredentials(userId) {
41
+ var _a, _b, _c, _d, _e, _f, _g;
42
+ const { profile, region, bucket, durationSeconds, prefix } = await this.resolveStorageProfileAndBucket(userId);
43
+ const credentials = profile.config;
44
+ const accessKeyId = String((_a = credentials.access_key_id) !== null && _a !== void 0 ? _a : '').trim();
45
+ const secretAccessKey = String((_b = credentials.secret_access_key) !== null && _b !== void 0 ? _b : '').trim();
46
+ const sessionToken = String((_c = credentials.session_token) !== null && _c !== void 0 ? _c : '').trim() || undefined;
47
+ if (!accessKeyId || !secretAccessKey) {
48
+ throw new common_1.BadRequestException('Storage profile does not contain AWS credentials (access_key_id/secret_access_key).');
49
+ }
50
+ const roleArn = String((_d = credentials.role_arn) !== null && _d !== void 0 ? _d : '').trim();
51
+ const externalId = String((_e = credentials.external_id) !== null && _e !== void 0 ? _e : '').trim();
52
+ const tempCredentials = await this.fileService.getTemporaryCredentials({
53
+ accessKeyId,
54
+ secretAccessKey,
55
+ sessionToken,
56
+ region,
57
+ roleArn: roleArn || undefined,
58
+ externalId: externalId || undefined,
59
+ durationSeconds,
60
+ sessionName: `hedhog-desktop-u${userId}-${Date.now()}`,
61
+ });
62
+ return {
63
+ provider: 'aws-s3',
64
+ bucket,
65
+ region,
66
+ keyPrefix: prefix,
67
+ credentials: {
68
+ accessKeyId: tempCredentials.AccessKeyId,
69
+ secretAccessKey: tempCredentials.SecretAccessKey,
70
+ sessionToken: tempCredentials.SessionToken,
71
+ expiresAt: (_g = (_f = tempCredentials.Expiration) === null || _f === void 0 ? void 0 : _f.toISOString()) !== null && _g !== void 0 ? _g : null,
72
+ },
73
+ };
74
+ }
75
+ async verifyFileOnS3(userId, payload) {
76
+ var _a, _b, _c, _d, _e, _f, _g;
77
+ const { profile, region, bucket, prefix } = await this.resolveStorageProfileAndBucket(userId);
78
+ const credentials = profile.config;
79
+ const accessKeyId = String((_a = credentials.access_key_id) !== null && _a !== void 0 ? _a : '').trim();
80
+ const secretAccessKey = String((_b = credentials.secret_access_key) !== null && _b !== void 0 ? _b : '').trim();
81
+ const sessionToken = String((_c = credentials.session_token) !== null && _c !== void 0 ? _c : '').trim() || undefined;
82
+ if (!accessKeyId || !secretAccessKey) {
83
+ throw new common_1.BadRequestException('Storage profile does not contain AWS credentials (access_key_id/secret_access_key).');
84
+ }
85
+ const objectKey = String((_d = payload.key) !== null && _d !== void 0 ? _d : '').trim() || `${prefix}${String((_e = payload.fileName) !== null && _e !== void 0 ? _e : '').trim()}`;
86
+ if (!objectKey) {
87
+ throw new common_1.BadRequestException('Provide file key or fileName to verify on S3.');
88
+ }
89
+ try {
90
+ const result = await this.fileService.headS3Object({
91
+ accessKeyId,
92
+ secretAccessKey,
93
+ sessionToken,
94
+ region,
95
+ bucket,
96
+ key: objectKey,
97
+ });
98
+ return {
99
+ isValid: true,
100
+ key: objectKey,
101
+ sizeBytes: (_f = result.ContentLength) !== null && _f !== void 0 ? _f : null,
102
+ etag: (_g = result.ETag) !== null && _g !== void 0 ? _g : null,
103
+ };
104
+ }
105
+ catch (_h) {
106
+ return {
107
+ isValid: false,
108
+ key: objectKey,
109
+ };
110
+ }
111
+ }
112
+ async resolveStorageProfileAndBucket(userId) {
113
+ var _a, _b, _c, _d, _e;
114
+ const settings = await this.settingService.getSettingValues([
115
+ 'lms-bulk-upload-storage-profile-id',
116
+ 'lms-bulk-upload-s3-bucket',
117
+ 'lms-bulk-upload-sts-duration-seconds',
118
+ ]);
119
+ const profileId = Number(settings['lms-bulk-upload-storage-profile-id'] || 0);
120
+ if (!Number.isFinite(profileId) || profileId <= 0) {
121
+ throw new common_1.BadRequestException('LMS bulk upload storage profile is not configured (lms-bulk-upload-storage-profile-id).');
122
+ }
123
+ const profile = await this.prismaService.integration_profile.findUnique({
124
+ where: { id: profileId },
125
+ include: {
126
+ integration_type: { select: { slug: true } },
127
+ integration_provider: { select: { slug: true } },
128
+ },
129
+ });
130
+ if (!profile) {
131
+ throw new common_1.NotFoundException(`Storage integration profile ${profileId} was not found.`);
132
+ }
133
+ if (profile.integration_type.slug !== 'storage') {
134
+ throw new common_1.BadRequestException(`Integration profile ${profileId} is not a storage profile.`);
135
+ }
136
+ const providerSlug = String((_a = profile.integration_provider.slug) !== null && _a !== void 0 ? _a : '').toLowerCase();
137
+ if (providerSlug !== 's3') {
138
+ throw new common_1.BadRequestException(`Integration provider ${providerSlug} is not supported for desktop bulk upload. Use AWS S3 profile.`);
139
+ }
140
+ const config = (_b = profile.config) !== null && _b !== void 0 ? _b : {};
141
+ const region = String((_c = config.region) !== null && _c !== void 0 ? _c : '').trim() || 'us-east-1';
142
+ const bucketFromSettings = String((_d = settings['lms-bulk-upload-s3-bucket']) !== null && _d !== void 0 ? _d : '').trim();
143
+ const bucketFromProfile = String((_e = config.bucket) !== null && _e !== void 0 ? _e : '').trim();
144
+ const bucket = bucketFromSettings || bucketFromProfile;
145
+ if (!bucket) {
146
+ throw new common_1.BadRequestException('LMS bulk upload bucket is not configured. Set lms-bulk-upload-s3-bucket or profile bucket.');
147
+ }
148
+ const durationSeconds = Math.max(900, Math.min(43200, Number(settings['lms-bulk-upload-sts-duration-seconds'] || 3600) || 3600));
149
+ const prefix = `desktop/lms/u${userId}/`;
150
+ return {
151
+ profile,
152
+ region,
153
+ bucket,
154
+ durationSeconds,
155
+ prefix,
156
+ };
157
+ }
158
+ };
159
+ exports.LmsBulkUploadService = LmsBulkUploadService;
160
+ exports.LmsBulkUploadService = LmsBulkUploadService = __decorate([
161
+ (0, common_1.Injectable)(),
162
+ __param(0, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_2.SettingService))),
163
+ __param(1, (0, common_1.Inject)((0, common_1.forwardRef)(() => api_prisma_1.PrismaService))),
164
+ __param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.FileService))),
165
+ __metadata("design:paramtypes", [core_2.SettingService,
166
+ api_prisma_1.PrismaService,
167
+ core_1.FileService])
168
+ ], LmsBulkUploadService);
169
+ //# sourceMappingURL=lms-bulk-upload.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lms-bulk-upload.service.js","sourceRoot":"","sources":["../../src/course/lms-bulk-upload.service.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,9 @@ export declare class LmsSettingController {
8
8
  transcriptionEnabled: boolean;
9
9
  youtubeEnabled: boolean;
10
10
  vimeoEnabled: boolean;
11
+ bulkUploadStorageProfileId: number;
12
+ bulkUploadBucketName: string;
13
+ bulkUploadStsDurationSeconds: number;
11
14
  }>;
12
15
  }
13
16
  //# sourceMappingURL=lms-setting.controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lms-setting.controller.d.ts","sourceRoot":"","sources":["../../src/course/lms-setting.controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAI/C,qBAEa,oBAAoB;IAG7B,OAAO,CAAC,QAAQ,CAAC,cAAc;gBAAd,cAAc,EAAE,cAAc;IAI3C,eAAe;;;;;;;CAgBtB"}
1
+ {"version":3,"file":"lms-setting.controller.d.ts","sourceRoot":"","sources":["../../src/course/lms-setting.controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,qBAEa,oBAAoB;IAG7B,OAAO,CAAC,QAAQ,CAAC,cAAc;gBAAd,cAAc,EAAE,cAAc;IAI3C,eAAe;;;;;;;;;;CA2BtB"}
@@ -13,9 +13,9 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.LmsSettingController = void 0;
16
+ const api_1 = require("@hed-hog/api");
16
17
  const core_1 = require("@hed-hog/core");
17
18
  const common_1 = require("@nestjs/common");
18
- const api_1 = require("@hed-hog/api");
19
19
  let LmsSettingController = class LmsSettingController {
20
20
  constructor(settingService) {
21
21
  this.settingService = settingService;
@@ -27,6 +27,9 @@ let LmsSettingController = class LmsSettingController {
27
27
  'lms-audio-transcription-enabled',
28
28
  'lms-youtube-provider-enabled',
29
29
  'lms-vimeo-provider-enabled',
30
+ 'lms-bulk-upload-storage-profile-id',
31
+ 'lms-bulk-upload-s3-bucket',
32
+ 'lms-bulk-upload-sts-duration-seconds',
30
33
  ]);
31
34
  return {
32
35
  videoConversionEnabled: v['lms-video-conversion-enabled'] !== false,
@@ -34,6 +37,11 @@ let LmsSettingController = class LmsSettingController {
34
37
  transcriptionEnabled: v['lms-audio-transcription-enabled'] !== false,
35
38
  youtubeEnabled: v['lms-youtube-provider-enabled'] !== false,
36
39
  vimeoEnabled: v['lms-vimeo-provider-enabled'] !== false,
40
+ bulkUploadStorageProfileId: Number(v['lms-bulk-upload-storage-profile-id'] || 0) || null,
41
+ bulkUploadBucketName: typeof v['lms-bulk-upload-s3-bucket'] === 'string'
42
+ ? v['lms-bulk-upload-s3-bucket']
43
+ : '',
44
+ bulkUploadStsDurationSeconds: Number(v['lms-bulk-upload-sts-duration-seconds'] || 3600) || 3600,
37
45
  };
38
46
  }
39
47
  };
@@ -1 +1 @@
1
- {"version":3,"file":"lms-setting.controller.js","sourceRoot":"","sources":["../../src/course/lms-setting.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,wCAA+C;AAC/C,2CAAqE;AACrE,sCAAoC;AAI7B,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAC/B,YAEmB,cAA8B;QAA9B,mBAAc,GAAd,cAAc,CAAgB;IAC9C,CAAC;IAGE,AAAN,KAAK,CAAC,eAAe;QACnB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC;YACnD,8BAA8B;YAC9B,8BAA8B;YAC9B,iCAAiC;YACjC,8BAA8B;YAC9B,4BAA4B;SAC7B,CAAC,CAAC;QACH,OAAO;YACL,sBAAsB,EAAE,CAAC,CAAC,8BAA8B,CAAC,KAAK,KAAK;YACnE,sBAAsB,EAAE,CAAC,CAAC,8BAA8B,CAAC,KAAK,KAAK;YACnE,oBAAoB,EAAE,CAAC,CAAC,iCAAiC,CAAC,KAAK,KAAK;YACpE,cAAc,EAAE,CAAC,CAAC,8BAA8B,CAAC,KAAK,KAAK;YAC3D,YAAY,EAAE,CAAC,CAAC,4BAA4B,CAAC,KAAK,KAAK;SACxD,CAAC;IACJ,CAAC;CACF,CAAA;AAvBY,oDAAoB;AAOzB;IADL,IAAA,YAAG,GAAE;;;;2DAgBL;+BAtBU,oBAAoB;IAFhC,IAAA,UAAI,GAAE;IACN,IAAA,mBAAU,EAAC,cAAc,CAAC;IAGtB,WAAA,IAAA,eAAM,EAAC,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,qBAAc,CAAC,CAAC,CAAA;qCACR,qBAAc;GAHtC,oBAAoB,CAuBhC"}
1
+ {"version":3,"file":"lms-setting.controller.js","sourceRoot":"","sources":["../../src/course/lms-setting.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,sCAAoC;AACpC,wCAA+C;AAC/C,2CAAqE;AAI9D,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAC/B,YAEmB,cAA8B;QAA9B,mBAAc,GAAd,cAAc,CAAgB;IAC9C,CAAC;IAGE,AAAN,KAAK,CAAC,eAAe;QACnB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC;YACnD,8BAA8B;YAC9B,8BAA8B;YAC9B,iCAAiC;YACjC,8BAA8B;YAC9B,4BAA4B;YAC5B,oCAAoC;YACpC,2BAA2B;YAC3B,sCAAsC;SACvC,CAAC,CAAC;QACH,OAAO;YACL,sBAAsB,EAAE,CAAC,CAAC,8BAA8B,CAAC,KAAK,KAAK;YACnE,sBAAsB,EAAE,CAAC,CAAC,8BAA8B,CAAC,KAAK,KAAK;YACnE,oBAAoB,EAAE,CAAC,CAAC,iCAAiC,CAAC,KAAK,KAAK;YACpE,cAAc,EAAE,CAAC,CAAC,8BAA8B,CAAC,KAAK,KAAK;YAC3D,YAAY,EAAE,CAAC,CAAC,4BAA4B,CAAC,KAAK,KAAK;YACvD,0BAA0B,EACxB,MAAM,CAAC,CAAC,CAAC,oCAAoC,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI;YAC9D,oBAAoB,EAClB,OAAO,CAAC,CAAC,2BAA2B,CAAC,KAAK,QAAQ;gBAChD,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC;gBAChC,CAAC,CAAC,EAAE;YACR,4BAA4B,EAC1B,MAAM,CAAC,CAAC,CAAC,sCAAsC,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI;SACpE,CAAC;IACJ,CAAC;CACF,CAAA;AAlCY,oDAAoB;AAOzB;IADL,IAAA,YAAG,GAAE;;;;2DA2BL;+BAjCU,oBAAoB;IAFhC,IAAA,UAAI,GAAE;IACN,IAAA,mBAAU,EAAC,cAAc,CAAC;IAGtB,WAAA,IAAA,eAAM,EAAC,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,qBAAc,CAAC,CAAC,CAAA;qCACR,qBAAc;GAHtC,oBAAoB,CAkChC"}
@@ -242,22 +242,22 @@ export declare class PlataformaController {
242
242
  }>;
243
243
  markAllNotificationsRead(userId: number): Promise<import("@prisma/client").Prisma.BatchPayload>;
244
244
  markNotificationRead(userId: number, id: number): Promise<{
245
- type: import("@prisma/client").$Enums.notification_type_enum;
246
- progress: number | null;
247
245
  id: number;
248
- body: string | null;
249
- title: string;
250
- status: import("@prisma/client").$Enums.notification_status_enum;
251
- icon: string | null;
252
246
  user_id: number;
253
247
  created_at: Date;
254
248
  updated_at: Date;
255
- started_at: Date | null;
256
- finished_at: Date | null;
257
- action_url: string | null;
249
+ type: import("@prisma/client").$Enums.notification_type_enum;
250
+ body: string | null;
251
+ status: import("@prisma/client").$Enums.notification_status_enum;
252
+ icon: string | null;
253
+ title: string;
254
+ progress: number | null;
258
255
  action_type: import("@prisma/client").$Enums.notification_action_type_enum | null;
259
256
  action_data: import("@prisma/client/runtime/library").JsonValue | null;
257
+ action_url: string | null;
260
258
  auto_remove: boolean;
259
+ started_at: Date | null;
260
+ finished_at: Date | null;
261
261
  }>;
262
262
  getStreak(userId: number, days?: number): Promise<{
263
263
  currentStreak: number;
@@ -6,6 +6,14 @@
6
6
  en: Administrator with full access to LMS management.
7
7
  pt: Administrador com acesso total à gestão de LMS.
8
8
 
9
+ - slug: lms-bulk-upload
10
+ name:
11
+ en: LMS Bulk Upload
12
+ pt: Upload em Massa LMS
13
+ description:
14
+ en: Allows access to temporary AWS credentials and desktop bulk video upload workflows.
15
+ pt: Permite acesso às credenciais temporárias da AWS e aos fluxos de upload em massa no desktop.
16
+
9
17
  - slug: lms-student
10
18
  name:
11
19
  en: LMS Student
@@ -6,6 +6,41 @@
6
6
  slug: admin
7
7
  - where:
8
8
  slug: admin-lms
9
+ - where:
10
+ slug: lms-bulk-upload
11
+
12
+ - url: /lms/bulk-upload/settings
13
+ method: GET
14
+ relations:
15
+ role:
16
+ - where:
17
+ slug: admin
18
+ - where:
19
+ slug: admin-lms
20
+ - where:
21
+ slug: lms-bulk-upload
22
+
23
+ - url: /lms/bulk-upload/temporary-credentials
24
+ method: POST
25
+ relations:
26
+ role:
27
+ - where:
28
+ slug: admin
29
+ - where:
30
+ slug: admin-lms
31
+ - where:
32
+ slug: lms-bulk-upload
33
+
34
+ - url: /lms/bulk-upload/verify
35
+ method: POST
36
+ relations:
37
+ role:
38
+ - where:
39
+ slug: admin
40
+ - where:
41
+ slug: admin-lms
42
+ - where:
43
+ slug: lms-bulk-upload
9
44
 
10
45
  - url: /lms/evaluations
11
46
  method: GET
@@ -74,3 +74,36 @@
74
74
  pt: Quando habilitado, instrutores podem usar links do Vimeo como fonte de video nas aulas.
75
75
  value: true
76
76
  user_override: false
77
+ - slug: lms-bulk-upload-storage-profile-id
78
+ type: number
79
+ component: input-number
80
+ name:
81
+ en: Bulk Upload Storage Profile ID
82
+ pt: ID do perfil de armazenamento para Upload em Massa
83
+ description:
84
+ en: Integration profile id (storage/S3) used to generate temporary credentials for desktop bulk upload.
85
+ pt: ID do perfil de integração (armazenamento/S3) usado para gerar credenciais temporárias no upload em massa do desktop.
86
+ value: 0
87
+ user_override: false
88
+ - slug: lms-bulk-upload-s3-bucket
89
+ type: string
90
+ component: input-text
91
+ name:
92
+ en: Bulk Upload Bucket Name
93
+ pt: Nome do bucket de Upload em Massa
94
+ description:
95
+ en: Target S3 bucket for desktop bulk upload. If empty, uses bucket from the selected profile.
96
+ pt: Bucket S3 de destino para upload em massa no desktop. Se vazio, usa o bucket do perfil selecionado.
97
+ value: ''
98
+ user_override: false
99
+ - slug: lms-bulk-upload-sts-duration-seconds
100
+ type: number
101
+ component: input-number
102
+ name:
103
+ en: Temporary Credential Duration (seconds)
104
+ pt: Duracao da credencial temporaria (segundos)
105
+ description:
106
+ en: Expiration time in seconds for temporary AWS credentials used by desktop bulk upload.
107
+ pt: Tempo de expiracao em segundos para as credenciais temporarias da AWS usadas no upload em massa do desktop.
108
+ value: 3600
109
+ user_override: false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/lms",
3
- "version": "0.0.358",
3
+ "version": "0.0.361",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -9,16 +9,16 @@
9
9
  "@nestjs/core": "^11",
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
- "@hed-hog/api": "0.0.8",
12
+ "@hed-hog/api-types": "0.0.1",
13
13
  "@hed-hog/api-prisma": "0.0.6",
14
+ "@hed-hog/api": "0.0.8",
14
15
  "@hed-hog/api-locale": "0.0.14",
15
- "@hed-hog/api-types": "0.0.1",
16
+ "@hed-hog/category": "0.0.361",
16
17
  "@hed-hog/api-pagination": "0.0.7",
17
- "@hed-hog/crm": "0.0.358",
18
- "@hed-hog/category": "0.0.358",
19
- "@hed-hog/queue": "0.0.358",
20
- "@hed-hog/finance": "0.0.358",
21
- "@hed-hog/core": "0.0.358"
18
+ "@hed-hog/crm": "0.0.361",
19
+ "@hed-hog/queue": "0.0.361",
20
+ "@hed-hog/core": "0.0.361",
21
+ "@hed-hog/finance": "0.0.361"
22
22
  },
23
23
  "exports": {
24
24
  ".": {
@@ -4,16 +4,18 @@ import { QueueModule } from '@hed-hog/queue';
4
4
  import { forwardRef, Module } from '@nestjs/common';
5
5
  import { InstructorModule } from '../instructor/instructor.module';
6
6
  import { CourseLessonController } from './course-lesson.controller';
7
- import { CourseOperationsController } from './course-operations.controller';
8
7
  import { CourseOperationsIntegrationService } from './course-operations-integration.service';
8
+ import { CourseOperationsController } from './course-operations.controller';
9
9
  import { CourseStructureController } from './course-structure.controller';
10
- import { LmsSettingController } from './lms-setting.controller';
11
10
  import { CourseStructureService } from './course-structure.service';
12
11
  import { CourseVideoConversionService } from './course-video-conversion.service';
13
12
  import { CourseController } from './course.controller';
14
13
  import { LmsCoursesMcpTools } from './course.mcp-tools';
15
14
  import { CourseService } from './course.service';
15
+ import { LmsBulkUploadController } from './lms-bulk-upload.controller';
16
+ import { LmsBulkUploadService } from './lms-bulk-upload.service';
16
17
  import { LmsOperationsTaskSubscriber } from './lms-operations-task.subscriber';
18
+ import { LmsSettingController } from './lms-setting.controller';
17
19
 
18
20
  @Module({
19
21
  imports: [
@@ -22,12 +24,20 @@ import { LmsOperationsTaskSubscriber } from './lms-operations-task.subscriber';
22
24
  forwardRef(() => CoreModule),
23
25
  forwardRef(() => QueueModule),
24
26
  ],
25
- controllers: [CourseController, CourseStructureController, CourseLessonController, LmsSettingController, CourseOperationsController],
27
+ controllers: [
28
+ CourseController,
29
+ CourseStructureController,
30
+ CourseLessonController,
31
+ LmsSettingController,
32
+ CourseOperationsController,
33
+ LmsBulkUploadController,
34
+ ],
26
35
  providers: [
27
36
  CourseOperationsIntegrationService,
28
37
  CourseService,
29
38
  CourseStructureService,
30
39
  CourseVideoConversionService,
40
+ LmsBulkUploadService,
31
41
  LmsCoursesMcpTools,
32
42
  LmsOperationsTaskSubscriber,
33
43
  ],
@@ -0,0 +1,27 @@
1
+ import { Role, User } from '@hed-hog/api';
2
+ import { Body, Controller, Get, Post } from '@nestjs/common';
3
+ import { LmsBulkUploadService } from './lms-bulk-upload.service';
4
+
5
+ @Role()
6
+ @Controller('lms/bulk-upload')
7
+ export class LmsBulkUploadController {
8
+ constructor(private readonly bulkUploadService: LmsBulkUploadService) {}
9
+
10
+ @Get('settings')
11
+ getSettings() {
12
+ return this.bulkUploadService.getBulkUploadSettings();
13
+ }
14
+
15
+ @Post('temporary-credentials')
16
+ getTemporaryCredentials(@User('id') userId: number) {
17
+ return this.bulkUploadService.getTemporaryCredentials(userId);
18
+ }
19
+
20
+ @Post('verify')
21
+ verifyFile(
22
+ @User('id') userId: number,
23
+ @Body() payload: { key?: string; fileName?: string },
24
+ ) {
25
+ return this.bulkUploadService.verifyFileOnS3(userId, payload ?? {});
26
+ }
27
+ }
@@ -0,0 +1,204 @@
1
+ import { FileService } from '@hed-hog/core';
2
+ import { PrismaService } from '@hed-hog/api-prisma';
3
+ import { SettingService } from '@hed-hog/core';
4
+ import {
5
+ BadRequestException,
6
+ Inject,
7
+ Injectable,
8
+ NotFoundException,
9
+ forwardRef,
10
+ } from '@nestjs/common';
11
+
12
+ type IntegrationProfileConfig = {
13
+ access_key_id?: string;
14
+ secret_access_key?: string;
15
+ session_token?: string;
16
+ region?: string;
17
+ bucket?: string;
18
+ role_arn?: string;
19
+ external_id?: string;
20
+ };
21
+
22
+ @Injectable()
23
+ export class LmsBulkUploadService {
24
+ constructor(
25
+ @Inject(forwardRef(() => SettingService))
26
+ private readonly settingService: SettingService,
27
+ @Inject(forwardRef(() => PrismaService))
28
+ private readonly prismaService: PrismaService,
29
+ @Inject(forwardRef(() => FileService))
30
+ private readonly fileService: FileService,
31
+ ) {}
32
+
33
+ async getBulkUploadSettings() {
34
+ const settings = await this.settingService.getSettingValues([
35
+ 'lms-bulk-upload-storage-profile-id',
36
+ 'lms-bulk-upload-s3-bucket',
37
+ 'lms-bulk-upload-sts-duration-seconds',
38
+ ]);
39
+
40
+ return {
41
+ storageProfileId: Number(settings['lms-bulk-upload-storage-profile-id'] || 0) || null,
42
+ bucketName:
43
+ typeof settings['lms-bulk-upload-s3-bucket'] === 'string'
44
+ ? settings['lms-bulk-upload-s3-bucket'].trim()
45
+ : '',
46
+ sessionDurationSeconds:
47
+ Number(settings['lms-bulk-upload-sts-duration-seconds'] || 3600) || 3600,
48
+ };
49
+ }
50
+
51
+ async getTemporaryCredentials(userId: number) {
52
+ const { profile, region, bucket, durationSeconds, prefix } =
53
+ await this.resolveStorageProfileAndBucket(userId);
54
+
55
+ const credentials = profile.config as IntegrationProfileConfig;
56
+ const accessKeyId = String(credentials.access_key_id ?? '').trim();
57
+ const secretAccessKey = String(credentials.secret_access_key ?? '').trim();
58
+ const sessionToken = String(credentials.session_token ?? '').trim() || undefined;
59
+
60
+ if (!accessKeyId || !secretAccessKey) {
61
+ throw new BadRequestException(
62
+ 'Storage profile does not contain AWS credentials (access_key_id/secret_access_key).',
63
+ );
64
+ }
65
+
66
+ const roleArn = String(credentials.role_arn ?? '').trim();
67
+ const externalId = String(credentials.external_id ?? '').trim();
68
+
69
+ const tempCredentials = await this.fileService.getTemporaryCredentials({
70
+ accessKeyId,
71
+ secretAccessKey,
72
+ sessionToken,
73
+ region,
74
+ roleArn: roleArn || undefined,
75
+ externalId: externalId || undefined,
76
+ durationSeconds,
77
+ sessionName: `hedhog-desktop-u${userId}-${Date.now()}`,
78
+ });
79
+
80
+ return {
81
+ provider: 'aws-s3',
82
+ bucket,
83
+ region,
84
+ keyPrefix: prefix,
85
+ credentials: {
86
+ accessKeyId: tempCredentials.AccessKeyId,
87
+ secretAccessKey: tempCredentials.SecretAccessKey,
88
+ sessionToken: tempCredentials.SessionToken,
89
+ expiresAt: tempCredentials.Expiration?.toISOString() ?? null,
90
+ },
91
+ };
92
+ }
93
+
94
+ async verifyFileOnS3(userId: number, payload: { key?: string; fileName?: string }) {
95
+ const { profile, region, bucket, prefix } = await this.resolveStorageProfileAndBucket(userId);
96
+
97
+ const credentials = profile.config as IntegrationProfileConfig;
98
+ const accessKeyId = String(credentials.access_key_id ?? '').trim();
99
+ const secretAccessKey = String(credentials.secret_access_key ?? '').trim();
100
+ const sessionToken = String(credentials.session_token ?? '').trim() || undefined;
101
+
102
+ if (!accessKeyId || !secretAccessKey) {
103
+ throw new BadRequestException(
104
+ 'Storage profile does not contain AWS credentials (access_key_id/secret_access_key).',
105
+ );
106
+ }
107
+
108
+ const objectKey = String(payload.key ?? '').trim() || `${prefix}${String(payload.fileName ?? '').trim()}`;
109
+ if (!objectKey) {
110
+ throw new BadRequestException('Provide file key or fileName to verify on S3.');
111
+ }
112
+
113
+ try {
114
+ const result = await this.fileService.headS3Object({
115
+ accessKeyId,
116
+ secretAccessKey,
117
+ sessionToken,
118
+ region,
119
+ bucket,
120
+ key: objectKey,
121
+ });
122
+
123
+ return {
124
+ isValid: true,
125
+ key: objectKey,
126
+ sizeBytes: result.ContentLength ?? null,
127
+ etag: result.ETag ?? null,
128
+ };
129
+ } catch {
130
+ return {
131
+ isValid: false,
132
+ key: objectKey,
133
+ };
134
+ }
135
+ }
136
+
137
+ private async resolveStorageProfileAndBucket(userId: number) {
138
+ const settings = await this.settingService.getSettingValues([
139
+ 'lms-bulk-upload-storage-profile-id',
140
+ 'lms-bulk-upload-s3-bucket',
141
+ 'lms-bulk-upload-sts-duration-seconds',
142
+ ]);
143
+
144
+ const profileId = Number(settings['lms-bulk-upload-storage-profile-id'] || 0);
145
+ if (!Number.isFinite(profileId) || profileId <= 0) {
146
+ throw new BadRequestException(
147
+ 'LMS bulk upload storage profile is not configured (lms-bulk-upload-storage-profile-id).',
148
+ );
149
+ }
150
+
151
+ const profile = await this.prismaService.integration_profile.findUnique({
152
+ where: { id: profileId },
153
+ include: {
154
+ integration_type: { select: { slug: true } },
155
+ integration_provider: { select: { slug: true } },
156
+ },
157
+ });
158
+
159
+ if (!profile) {
160
+ throw new NotFoundException(`Storage integration profile ${profileId} was not found.`);
161
+ }
162
+
163
+ if (profile.integration_type.slug !== 'storage') {
164
+ throw new BadRequestException(
165
+ `Integration profile ${profileId} is not a storage profile.`,
166
+ );
167
+ }
168
+
169
+ const providerSlug = String(profile.integration_provider.slug ?? '').toLowerCase();
170
+ if (providerSlug !== 's3') {
171
+ throw new BadRequestException(
172
+ `Integration provider ${providerSlug} is not supported for desktop bulk upload. Use AWS S3 profile.`,
173
+ );
174
+ }
175
+
176
+ const config = (profile.config as IntegrationProfileConfig) ?? {};
177
+ const region = String(config.region ?? '').trim() || 'us-east-1';
178
+
179
+ const bucketFromSettings = String(settings['lms-bulk-upload-s3-bucket'] ?? '').trim();
180
+ const bucketFromProfile = String(config.bucket ?? '').trim();
181
+ const bucket = bucketFromSettings || bucketFromProfile;
182
+
183
+ if (!bucket) {
184
+ throw new BadRequestException(
185
+ 'LMS bulk upload bucket is not configured. Set lms-bulk-upload-s3-bucket or profile bucket.',
186
+ );
187
+ }
188
+
189
+ const durationSeconds = Math.max(
190
+ 900,
191
+ Math.min(43200, Number(settings['lms-bulk-upload-sts-duration-seconds'] || 3600) || 3600),
192
+ );
193
+
194
+ const prefix = `desktop/lms/u${userId}/`;
195
+
196
+ return {
197
+ profile,
198
+ region,
199
+ bucket,
200
+ durationSeconds,
201
+ prefix,
202
+ };
203
+ }
204
+ }
@@ -1,6 +1,6 @@
1
+ import { Role } from '@hed-hog/api';
1
2
  import { SettingService } from '@hed-hog/core';
2
3
  import { Controller, forwardRef, Get, Inject } from '@nestjs/common';
3
- import { Role } from '@hed-hog/api';
4
4
 
5
5
  @Role()
6
6
  @Controller('lms/settings')
@@ -18,6 +18,9 @@ export class LmsSettingController {
18
18
  'lms-audio-transcription-enabled',
19
19
  'lms-youtube-provider-enabled',
20
20
  'lms-vimeo-provider-enabled',
21
+ 'lms-bulk-upload-storage-profile-id',
22
+ 'lms-bulk-upload-s3-bucket',
23
+ 'lms-bulk-upload-sts-duration-seconds',
21
24
  ]);
22
25
  return {
23
26
  videoConversionEnabled: v['lms-video-conversion-enabled'] !== false,
@@ -25,6 +28,14 @@ export class LmsSettingController {
25
28
  transcriptionEnabled: v['lms-audio-transcription-enabled'] !== false,
26
29
  youtubeEnabled: v['lms-youtube-provider-enabled'] !== false,
27
30
  vimeoEnabled: v['lms-vimeo-provider-enabled'] !== false,
31
+ bulkUploadStorageProfileId:
32
+ Number(v['lms-bulk-upload-storage-profile-id'] || 0) || null,
33
+ bulkUploadBucketName:
34
+ typeof v['lms-bulk-upload-s3-bucket'] === 'string'
35
+ ? v['lms-bulk-upload-s3-bucket']
36
+ : '',
37
+ bulkUploadStsDurationSeconds:
38
+ Number(v['lms-bulk-upload-sts-duration-seconds'] || 3600) || 3600,
28
39
  };
29
40
  }
30
41
  }