@hed-hog/core 0.0.362 → 0.0.365

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 (185) hide show
  1. package/dist/ai/ai.controller.d.ts +4 -4
  2. package/dist/ai/ai.module.d.ts.map +1 -1
  3. package/dist/ai/ai.module.js +2 -0
  4. package/dist/ai/ai.module.js.map +1 -1
  5. package/dist/ai/ai.service.d.ts +7 -5
  6. package/dist/ai/ai.service.d.ts.map +1 -1
  7. package/dist/ai/ai.service.js +63 -31
  8. package/dist/ai/ai.service.js.map +1 -1
  9. package/dist/ai-instruction/ai-execution.service.d.ts +45 -0
  10. package/dist/ai-instruction/ai-execution.service.d.ts.map +1 -0
  11. package/dist/ai-instruction/ai-execution.service.js +203 -0
  12. package/dist/ai-instruction/ai-execution.service.js.map +1 -0
  13. package/dist/ai-instruction/ai-instruction.controller.d.ts +25 -0
  14. package/dist/ai-instruction/ai-instruction.controller.d.ts.map +1 -0
  15. package/dist/ai-instruction/ai-instruction.controller.js +117 -0
  16. package/dist/ai-instruction/ai-instruction.controller.js.map +1 -0
  17. package/dist/ai-instruction/ai-instruction.module.d.ts +3 -0
  18. package/dist/ai-instruction/ai-instruction.module.d.ts.map +1 -0
  19. package/dist/ai-instruction/ai-instruction.module.js +27 -0
  20. package/dist/ai-instruction/ai-instruction.module.js.map +1 -0
  21. package/dist/ai-instruction/ai-instruction.service.d.ts +38 -0
  22. package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -0
  23. package/dist/ai-instruction/ai-instruction.service.js +289 -0
  24. package/dist/ai-instruction/ai-instruction.service.js.map +1 -0
  25. package/dist/ai-instruction/ai-prompt-builder.service.d.ts +53 -0
  26. package/dist/ai-instruction/ai-prompt-builder.service.d.ts.map +1 -0
  27. package/dist/ai-instruction/ai-prompt-builder.service.js +132 -0
  28. package/dist/ai-instruction/ai-prompt-builder.service.js.map +1 -0
  29. package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts +12 -0
  30. package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts.map +1 -0
  31. package/dist/ai-instruction/dto/create-ai-instruction.dto.js +57 -0
  32. package/dist/ai-instruction/dto/create-ai-instruction.dto.js.map +1 -0
  33. package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts +10 -0
  34. package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts.map +1 -0
  35. package/dist/ai-instruction/dto/update-ai-instruction.dto.js +56 -0
  36. package/dist/ai-instruction/dto/update-ai-instruction.dto.js.map +1 -0
  37. package/dist/ai-instruction/types/ai-instruction.types.d.ts +78 -0
  38. package/dist/ai-instruction/types/ai-instruction.types.d.ts.map +1 -0
  39. package/dist/ai-instruction/types/ai-instruction.types.js +3 -0
  40. package/dist/ai-instruction/types/ai-instruction.types.js.map +1 -0
  41. package/dist/auth/auth.controller.d.ts +13 -5
  42. package/dist/auth/auth.controller.d.ts.map +1 -1
  43. package/dist/auth/auth.controller.js +39 -20
  44. package/dist/auth/auth.controller.js.map +1 -1
  45. package/dist/auth/auth.service.d.ts +18 -7
  46. package/dist/auth/auth.service.d.ts.map +1 -1
  47. package/dist/auth/auth.service.js +20 -16
  48. package/dist/auth/auth.service.js.map +1 -1
  49. package/dist/core.module.d.ts.map +1 -1
  50. package/dist/core.module.js +3 -0
  51. package/dist/core.module.js.map +1 -1
  52. package/dist/file/file.service.d.ts +37 -0
  53. package/dist/file/file.service.d.ts.map +1 -1
  54. package/dist/file/file.service.js +94 -4
  55. package/dist/file/file.service.js.map +1 -1
  56. package/dist/file/provider/abstract.provider.d.ts +6 -2
  57. package/dist/file/provider/abstract.provider.d.ts.map +1 -1
  58. package/dist/file/provider/abstract.provider.js.map +1 -1
  59. package/dist/file/provider/azure.provider.d.ts +4 -1
  60. package/dist/file/provider/azure.provider.d.ts.map +1 -1
  61. package/dist/file/provider/azure.provider.js +7 -2
  62. package/dist/file/provider/azure.provider.js.map +1 -1
  63. package/dist/file/provider/gcs.provider.d.ts +4 -1
  64. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  65. package/dist/file/provider/gcs.provider.js +7 -2
  66. package/dist/file/provider/gcs.provider.js.map +1 -1
  67. package/dist/file/provider/local.provider.d.ts +2 -2
  68. package/dist/file/provider/local.provider.d.ts.map +1 -1
  69. package/dist/file/provider/local.provider.js +7 -2
  70. package/dist/file/provider/local.provider.js.map +1 -1
  71. package/dist/file/provider/s3.provider.d.ts +4 -1
  72. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  73. package/dist/file/provider/s3.provider.js +9 -6
  74. package/dist/file/provider/s3.provider.js.map +1 -1
  75. package/dist/file/provider/spaces.provider.d.ts +4 -1
  76. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  77. package/dist/file/provider/spaces.provider.js +9 -5
  78. package/dist/file/provider/spaces.provider.js.map +1 -1
  79. package/dist/integration-profile/integration-profile.controller.d.ts +3 -3
  80. package/dist/integration-profile/integration-profile.service.d.ts +3 -3
  81. package/dist/mcp-chat/mcp-chat.controller.d.ts +2 -2
  82. package/dist/mcp-chat/mcp-chat.module.d.ts.map +1 -1
  83. package/dist/mcp-chat/mcp-chat.module.js +2 -0
  84. package/dist/mcp-chat/mcp-chat.module.js.map +1 -1
  85. package/dist/mcp-chat/mcp-chat.service.d.ts +8 -3
  86. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  87. package/dist/mcp-chat/mcp-chat.service.js +100 -20
  88. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  89. package/dist/notification/notification.service.d.ts +1 -0
  90. package/dist/notification/notification.service.d.ts.map +1 -1
  91. package/dist/notification/notification.service.js +29 -1
  92. package/dist/notification/notification.service.js.map +1 -1
  93. package/dist/oauth/oauth.service.d.ts +1 -0
  94. package/dist/oauth/oauth.service.d.ts.map +1 -1
  95. package/dist/oauth/oauth.service.js +55 -1
  96. package/dist/oauth/oauth.service.js.map +1 -1
  97. package/dist/profile/events/profile-avatar-updated.event.d.ts +7 -0
  98. package/dist/profile/events/profile-avatar-updated.event.d.ts.map +1 -0
  99. package/dist/profile/events/profile-avatar-updated.event.js +12 -0
  100. package/dist/profile/events/profile-avatar-updated.event.js.map +1 -0
  101. package/dist/profile/events/profile-email-changed.event.d.ts +7 -0
  102. package/dist/profile/events/profile-email-changed.event.d.ts.map +1 -0
  103. package/dist/profile/events/profile-email-changed.event.js +12 -0
  104. package/dist/profile/events/profile-email-changed.event.js.map +1 -0
  105. package/dist/profile/events/profile-updated.event.d.ts +7 -0
  106. package/dist/profile/events/profile-updated.event.d.ts.map +1 -0
  107. package/dist/profile/events/profile-updated.event.js +12 -0
  108. package/dist/profile/events/profile-updated.event.js.map +1 -0
  109. package/dist/profile/profile.service.d.ts +3 -1
  110. package/dist/profile/profile.service.d.ts.map +1 -1
  111. package/dist/profile/profile.service.js +28 -17
  112. package/dist/profile/profile.service.js.map +1 -1
  113. package/dist/session/session.controller.d.ts +1 -0
  114. package/dist/session/session.controller.d.ts.map +1 -1
  115. package/dist/session/session.service.d.ts +9 -4
  116. package/dist/session/session.service.d.ts.map +1 -1
  117. package/dist/session/session.service.js +61 -51
  118. package/dist/session/session.service.js.map +1 -1
  119. package/dist/setting/setting.controller.d.ts +5 -5
  120. package/dist/setting/setting.service.d.ts +5 -5
  121. package/dist/setting/setting.service.d.ts.map +1 -1
  122. package/dist/setting/setting.service.js +2 -0
  123. package/dist/setting/setting.service.js.map +1 -1
  124. package/dist/task/task.service.d.ts +8 -11
  125. package/dist/task/task.service.d.ts.map +1 -1
  126. package/dist/task/task.service.js +10 -16
  127. package/dist/task/task.service.js.map +1 -1
  128. package/dist/user/user.controller.d.ts +1 -0
  129. package/dist/user/user.controller.d.ts.map +1 -1
  130. package/dist/user/user.service.d.ts +3 -0
  131. package/dist/user/user.service.d.ts.map +1 -1
  132. package/dist/webhook-integration/webhook-command.registry.d.ts +9 -2
  133. package/dist/webhook-integration/webhook-command.registry.d.ts.map +1 -1
  134. package/dist/webhook-integration/webhook-command.registry.js.map +1 -1
  135. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  136. package/dist/webhook-integration/webhook-integration.service.js +13 -4
  137. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  138. package/hedhog/data/integration_provider.yaml +17 -0
  139. package/hedhog/data/integration_type.yaml +6 -0
  140. package/hedhog/data/route.yaml +86 -2
  141. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +109 -0
  142. package/hedhog/frontend/app/configurations/layout.tsx.ejs +6 -6
  143. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +169 -14
  144. package/hedhog/frontend/messages/en.json +11 -2
  145. package/hedhog/frontend/messages/pt.json +11 -2
  146. package/hedhog/table/ai_execution.yaml +79 -0
  147. package/hedhog/table/ai_execution_step.yaml +65 -0
  148. package/hedhog/table/ai_instruction.yaml +43 -0
  149. package/hedhog/table/ai_instruction_version.yaml +30 -0
  150. package/hedhog/table/ai_model_pricing.yaml +35 -0
  151. package/hedhog/table/user_session.yaml +2 -0
  152. package/package.json +5 -4
  153. package/src/ai/ai.module.ts +2 -0
  154. package/src/ai/ai.service.ts +64 -31
  155. package/src/ai-instruction/ai-execution.service.ts +244 -0
  156. package/src/ai-instruction/ai-instruction.controller.ts +73 -0
  157. package/src/ai-instruction/ai-instruction.module.ts +14 -0
  158. package/src/ai-instruction/ai-instruction.service.ts +343 -0
  159. package/src/ai-instruction/ai-prompt-builder.service.ts +142 -0
  160. package/src/ai-instruction/dto/create-ai-instruction.dto.ts +36 -0
  161. package/src/ai-instruction/dto/update-ai-instruction.dto.ts +35 -0
  162. package/src/ai-instruction/types/ai-instruction.types.ts +90 -0
  163. package/src/auth/auth.controller.ts +23 -5
  164. package/src/auth/auth.service.ts +26 -15
  165. package/src/core.module.ts +3 -0
  166. package/src/file/file.service.ts +136 -4
  167. package/src/file/provider/abstract.provider.ts +4 -2
  168. package/src/file/provider/azure.provider.ts +6 -3
  169. package/src/file/provider/gcs.provider.ts +5 -3
  170. package/src/file/provider/local.provider.ts +7 -3
  171. package/src/file/provider/s3.provider.ts +8 -6
  172. package/src/file/provider/spaces.provider.ts +8 -7
  173. package/src/mcp-chat/mcp-chat.module.ts +19 -17
  174. package/src/mcp-chat/mcp-chat.service.ts +95 -9
  175. package/src/notification/notification.service.ts +32 -1
  176. package/src/oauth/oauth.service.ts +74 -1
  177. package/src/profile/events/profile-avatar-updated.event.ts +8 -0
  178. package/src/profile/events/profile-email-changed.event.ts +8 -0
  179. package/src/profile/events/profile-updated.event.ts +8 -0
  180. package/src/profile/profile.service.ts +25 -16
  181. package/src/session/session.service.ts +70 -52
  182. package/src/setting/setting.service.ts +10 -8
  183. package/src/task/task.service.ts +10 -16
  184. package/src/webhook-integration/webhook-command.registry.ts +10 -2
  185. package/src/webhook-integration/webhook-integration.service.ts +31 -4
@@ -46,6 +46,17 @@ export class AuthController {
46
46
  return this.service.verifyUser(locale, id);
47
47
  }
48
48
 
49
+ @NoRole()
50
+ @Get('desktop-token')
51
+ async desktopToken(
52
+ @User() { id },
53
+ @Locale() locale: string,
54
+ @Ip() ipAddress: string,
55
+ @Headers('user-agent') userAgent: string,
56
+ ) {
57
+ return this.service.issueDesktopToken(locale, id, ipAddress, userAgent);
58
+ }
59
+
49
60
  @NoRole()
50
61
  @Get('roles')
51
62
  async roles(@UserOptional() user, @Locale() locale: string) {
@@ -61,12 +72,13 @@ export class AuthController {
61
72
  @Res({ passthrough: true }) res,
62
73
  @Body('refreshToken') refreshTokenFromBody: string,
63
74
  @Headers('user-agent') userAgent: string,
75
+ @Headers('x-browser-id') browserId: string,
64
76
  @Ip() ipAddress: string
65
77
  ) {
66
78
  const tokenSource = req.cookies['rt'] ? 'cookie' : (refreshTokenFromBody ? 'body' : 'none');
67
79
  this.logger.debug(`[AuthController] POST /auth/refresh tokenSource=${tokenSource} ip=${ipAddress}`);
68
80
  const currentRefreshToken = req.cookies['rt'] || refreshTokenFromBody;
69
- const { session, refreshToken, accessToken } = await this.service.refreshAccessToken(locale, currentRefreshToken, ipAddress, userAgent);
81
+ const { session, refreshToken, accessToken } = await this.service.refreshAccessToken(locale, currentRefreshToken, ipAddress, userAgent, browserId || null);
70
82
  await this.token.setRefreshTokenCookie(locale, res, refreshToken, session.expires_at);
71
83
 
72
84
  return {
@@ -82,9 +94,10 @@ export class AuthController {
82
94
  @Body() data: LoginDTO,
83
95
  @Ip() ipAddress: string,
84
96
  @Headers('user-agent') userAgent: string,
97
+ @Headers('x-browser-id') browserId: string,
85
98
  @Res({ passthrough: true }) res,
86
99
  ) {
87
- return this.service.loginWithEmailAndPassword(res, locale, ipAddress, userAgent, data);
100
+ return this.service.loginWithEmailAndPassword(res, locale, ipAddress, userAgent, data, browserId || null);
88
101
  }
89
102
 
90
103
  @Public()
@@ -94,9 +107,10 @@ export class AuthController {
94
107
  @Body() {token, code}: LoginEmailVerificationDTO,
95
108
  @Ip() ipAddress: string,
96
109
  @Headers('user-agent') userAgent: string,
110
+ @Headers('x-browser-id') browserId: string,
97
111
  @Res({ passthrough: true }) res,
98
112
  ) {
99
- return this.service.emailVerificationLogin(locale, token, code, ipAddress, userAgent, res);
113
+ return this.service.emailVerificationLogin(locale, token, code, ipAddress, userAgent, res, browserId || null);
100
114
  }
101
115
 
102
116
  @Public()
@@ -124,6 +138,7 @@ export class AuthController {
124
138
  @Body() { token, code, methodType }: LoginWithCodeDTO,
125
139
  @Ip() ipAddress: string,
126
140
  @Headers('user-agent') userAgent: string,
141
+ @Headers('x-browser-id') browserId: string,
127
142
  @Res({ passthrough: true }) res,
128
143
  ) {
129
144
  const { accessToken, refreshToken, session, requiresPasswordReset } = await this.service.verifyMfaCode(
@@ -132,7 +147,8 @@ export class AuthController {
132
147
  code,
133
148
  ipAddress,
134
149
  userAgent,
135
- methodType
150
+ methodType,
151
+ browserId || null,
136
152
  );
137
153
 
138
154
  await this.token.setRefreshTokenCookie(locale, res, refreshToken, session.expires_at);
@@ -146,6 +162,7 @@ export class AuthController {
146
162
  @Body() { token, code }: LoginWithRecoveryCodeDTO,
147
163
  @Ip() ipAddress: string,
148
164
  @Headers('user-agent') userAgent: string,
165
+ @Headers('x-browser-id') browserId: string,
149
166
  @Res({ passthrough: true }) res,
150
167
  ) {
151
168
  const { accessToken, refreshToken, session, requiresPasswordReset } = await this.service.verifyMfaRecoveryCode(
@@ -153,7 +170,8 @@ export class AuthController {
153
170
  token,
154
171
  code,
155
172
  ipAddress,
156
- userAgent
173
+ userAgent,
174
+ browserId || null,
157
175
  );
158
176
 
159
177
  await this.token.setRefreshTokenCookie(locale, res, refreshToken, session.expires_at);
@@ -43,8 +43,8 @@ export class AuthService {
43
43
  private readonly challenge: ChallengeService,
44
44
  ) { }
45
45
 
46
- async getAuthenticationPayload(locale: string, userId: number, ipAddress: string, userAgent: string) {
47
- const { token: refreshToken, session } = await this.session.create(locale, userId, ipAddress, userAgent);
46
+ async getAuthenticationPayload(locale: string, userId: number, ipAddress: string, userAgent: string, browserId?: string | null) {
47
+ const { token: refreshToken, session } = await this.session.create(locale, userId, ipAddress, userAgent, browserId);
48
48
  const payload = { sub: userId, sessionId: session.id };
49
49
  const accessToken = await this.token.createAccessToken(payload);
50
50
  await this.prisma.user.update({ where: { id: userId }, data: { last_login_at: new Date() } });
@@ -58,6 +58,11 @@ export class AuthService {
58
58
 
59
59
  }
60
60
 
61
+ async issueDesktopToken(locale: string, userId: number, ipAddress: string, userAgent: string) {
62
+ const { accessToken, refreshToken } = await this.getAuthenticationPayload(locale, userId, ipAddress, userAgent);
63
+ return { accessToken, refreshToken };
64
+ }
65
+
61
66
  private hasPendingPasswordReset(user: User | any) {
62
67
  return Boolean(
63
68
  user?.user_credential?.some(
@@ -264,7 +269,7 @@ export class AuthService {
264
269
  }
265
270
  }
266
271
 
267
- async emailVerificationLogin(locale: string, token: string, code: string, ipAddress: string, userAgent: string, res: any) {
272
+ async emailVerificationLogin(locale: string, token: string, code: string, ipAddress: string, userAgent: string, res: any, browserId?: string | null) {
268
273
 
269
274
  try {
270
275
  const payload = await this.token.verify(locale, token);
@@ -317,14 +322,14 @@ export class AuthService {
317
322
 
318
323
  const user = await this.user.findUserById(locale, challenge.user_identifier.user_id);
319
324
 
320
- return this.login(locale, user as unknown as User, ipAddress, userAgent, res);
325
+ return this.login(locale, user as unknown as User, ipAddress, userAgent, res, browserId);
321
326
 
322
327
  } catch (error) {
323
328
  throw error;
324
329
  }
325
330
  }
326
331
 
327
- private async login(locale: string, user: User, ipAddress: string, userAgent: string, res: any) {
332
+ private async login(locale: string, user: User, ipAddress: string, userAgent: string, res: any, browserId?: string | null) {
328
333
 
329
334
  const emails = await this.prisma.user_identifier.findMany({
330
335
  where: {
@@ -342,7 +347,8 @@ export class AuthService {
342
347
  locale,
343
348
  user.id,
344
349
  ipAddress,
345
- userAgent
350
+ userAgent,
351
+ browserId,
346
352
  );
347
353
 
348
354
  for (const emailObj of emails) {
@@ -382,6 +388,7 @@ export class AuthService {
382
388
  ipAddress: string,
383
389
  userAgent: string,
384
390
  { email, password }: LoginDTO,
391
+ browserId?: string | null,
385
392
  ) {
386
393
 
387
394
  const user = await this.user.findUserByEmail(locale, email);
@@ -446,7 +453,7 @@ export class AuthService {
446
453
 
447
454
  }
448
455
 
449
- return this.login(locale, user as unknown as User, ipAddress, userAgent, res);
456
+ return this.login(locale, user as unknown as User, ipAddress, userAgent, res, browserId);
450
457
  }
451
458
 
452
459
  async verifyRoles(_locale: string, userId: number) {
@@ -474,7 +481,7 @@ export class AuthService {
474
481
  };
475
482
  }
476
483
 
477
- async refreshAccessToken(locale: string, refreshToken: string, ipAddress: string, userAgent: string) {
484
+ async refreshAccessToken(locale: string, refreshToken: string, ipAddress: string, userAgent: string, browserId?: string | null) {
478
485
 
479
486
  if (!refreshToken) {
480
487
  this.logger.warn(`[AuthService] refreshAccessToken called without token ip=${ipAddress}`);
@@ -487,7 +494,8 @@ export class AuthService {
487
494
  locale,
488
495
  refreshToken,
489
496
  ipAddress,
490
- userAgent
497
+ userAgent,
498
+ browserId,
491
499
  );
492
500
 
493
501
  const user = await this.user.findUserById(locale, session.user_id);
@@ -573,7 +581,7 @@ export class AuthService {
573
581
  return { accessToken, refreshToken, session }
574
582
  }
575
583
 
576
- async verifyMfaCode(locale: string, mfaToken: string, code: string, ipAddress: string, userAgent: string, methodType?: 'totp' | 'email' | 'recovery') {
584
+ async verifyMfaCode(locale: string, mfaToken: string, code: string, ipAddress: string, userAgent: string, methodType?: 'totp' | 'email' | 'recovery', browserId?: string | null) {
577
585
  const payload = await this.token.verifyMfaChallengeToken(mfaToken);
578
586
  const user = await this.prisma.user.findUnique({
579
587
  where: { id: payload.userId },
@@ -673,7 +681,8 @@ export class AuthService {
673
681
  locale,
674
682
  user.id,
675
683
  ipAddress,
676
- userAgent
684
+ userAgent,
685
+ browserId,
677
686
  );
678
687
 
679
688
  this.mail.sendTemplatedMail(
@@ -697,7 +706,7 @@ export class AuthService {
697
706
  return { accessToken, refreshToken, session, requiresPasswordReset };
698
707
  }
699
708
 
700
- async verifyMfaRecoveryCode(locale: string, mfaToken: string, recoveryCode: string, ipAddress: string, userAgent: string) {
709
+ async verifyMfaRecoveryCode(locale: string, mfaToken: string, recoveryCode: string, ipAddress: string, userAgent: string, browserId?: string | null) {
701
710
  const payload = await this.token.verifyMfaChallengeToken(mfaToken);
702
711
  const user = await this.prisma.user.findUnique({
703
712
  where: { id: payload.userId },
@@ -729,7 +738,8 @@ export class AuthService {
729
738
  locale,
730
739
  user.id,
731
740
  ipAddress,
732
- userAgent
741
+ userAgent,
742
+ browserId,
733
743
  );
734
744
 
735
745
  await this.user.registerUserActivity(user.id, "login");
@@ -815,7 +825,7 @@ export class AuthService {
815
825
  return options;
816
826
  }
817
827
 
818
- async verifyWebAuthnAuthentication(locale: string, mfaToken: string, assertionResponse: any, ipAddress: string, userAgent: string) {
828
+ async verifyWebAuthnAuthentication(locale: string, mfaToken: string, assertionResponse: any, ipAddress: string, userAgent: string, browserId?: string | null) {
819
829
  const { verifyAuthenticationResponse } = await import('@simplewebauthn/server');
820
830
  const payload = await this.token.verifyMfaChallengeToken(mfaToken);
821
831
  const userId = payload.userId;
@@ -880,7 +890,8 @@ export class AuthService {
880
890
  locale,
881
891
  userId,
882
892
  ipAddress,
883
- userAgent
893
+ userAgent,
894
+ browserId,
884
895
  );
885
896
 
886
897
  const user = await this.prisma.user.findFirst({ where: { id: userId } });
@@ -41,6 +41,7 @@ import { IsEmailWithSettingsConstraint } from './validators/is-email-with-settin
41
41
  import { IsPinCodeWithSettingConstraint } from './validators/is-pin-code-with-setting.validator';
42
42
  import { IsStrongPasswordWithSettingsConstraint } from './validators/is-strong-password-with-settings.validator';
43
43
  import { ValidatorServiceLocator } from './validators/service-locator';
44
+ import { AiInstructionModule } from './ai-instruction/ai-instruction.module';
44
45
  import { IntegrationProfileModule } from './integration-profile/integration-profile.module';
45
46
  import { WebhookIntegrationModule } from './webhook-integration/webhook-integration.module';
46
47
 
@@ -76,6 +77,7 @@ import { WebhookIntegrationModule } from './webhook-integration/webhook-integrat
76
77
  forwardRef(() => OAuthModule),
77
78
  forwardRef(() => IntegrationModule),
78
79
  forwardRef(() => WebhookIntegrationModule),
80
+ forwardRef(() => AiInstructionModule),
79
81
  forwardRef(() => McpModule),
80
82
  forwardRef(() => McpChatModule),
81
83
  forwardRef(() => PdfModule),
@@ -97,6 +99,7 @@ import { WebhookIntegrationModule } from './webhook-integration/webhook-integrat
97
99
  FileModule,
98
100
  SessionModule,
99
101
  AiModule,
102
+ AiInstructionModule,
100
103
  IntegrationModule,
101
104
  WebhookIntegrationModule,
102
105
  IsStrongPasswordWithSettingsConstraint,
@@ -10,6 +10,7 @@ import {
10
10
  CreateFunctionCommand,
11
11
  GetFunctionConfigurationCommand,
12
12
  LambdaClient,
13
+ UpdateFunctionCodeCommand,
13
14
  UpdateFunctionConfigurationCommand,
14
15
  } from '@aws-sdk/client-lambda';
15
16
  import {
@@ -291,6 +292,43 @@ export class FileService implements OnModuleInit {
291
292
  }
292
293
  }
293
294
 
295
+ async openReadStreamWithRange(fileId: number, start?: number, end?: number) {
296
+ if (!fileId) throw new BadRequestException('No file ID provided');
297
+
298
+ const file = await this.prismaService.file.findUnique({
299
+ where: { id: fileId },
300
+ include: { file_mimetype: true },
301
+ });
302
+
303
+ if (!file) throw new NotFoundException(`File not found: ${fileId}`);
304
+
305
+ const provider = await this.getProvider();
306
+ const providerPath = this.resolveProviderPath(file);
307
+ const totalSize: number = file.size ?? 0;
308
+
309
+ const resolvedStart = start ?? 0;
310
+ const resolvedEnd = end !== undefined ? end : totalSize > 0 ? totalSize - 1 : undefined;
311
+ const hasRange = start !== undefined;
312
+
313
+ const stream = await provider.readStream(
314
+ providerPath,
315
+ hasRange ? { start: resolvedStart, end: resolvedEnd } : undefined,
316
+ );
317
+
318
+ const contentLength =
319
+ resolvedEnd !== undefined ? resolvedEnd - resolvedStart + 1 : totalSize;
320
+
321
+ return {
322
+ file,
323
+ stream,
324
+ totalSize,
325
+ start: resolvedStart,
326
+ end: resolvedEnd,
327
+ contentLength,
328
+ mimetype: file.file_mimetype?.name ?? 'application/octet-stream',
329
+ };
330
+ }
331
+
294
332
  async downloadToPath(
295
333
  fileId: number,
296
334
  destinationPath: string,
@@ -834,10 +872,40 @@ export class FileService implements OnModuleInit {
834
872
  }),
835
873
  );
836
874
 
837
- const merged = {
838
- ...(current.Environment?.Variables ?? {}),
839
- ...params.environmentVariables,
840
- };
875
+ this.logger.log(
876
+ `updateLambdaEnvironmentVariables: fn=${params.functionName} region=${params.region} ` +
877
+ `currentStatus=${current.LastUpdateStatus} ` +
878
+ `incomingKeys=[${Object.keys(params.environmentVariables).join(',')}]`,
879
+ );
880
+
881
+ const LAMBDA_RESERVED_KEYS = new Set([
882
+ 'AWS_REGION',
883
+ 'AWS_DEFAULT_REGION',
884
+ 'AWS_LAMBDA_FUNCTION_NAME',
885
+ 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE',
886
+ 'AWS_LAMBDA_FUNCTION_VERSION',
887
+ 'AWS_LAMBDA_LOG_GROUP_NAME',
888
+ 'AWS_LAMBDA_LOG_STREAM_NAME',
889
+ 'AWS_LAMBDA_RUNTIME_API',
890
+ 'AWS_EXECUTION_ENV',
891
+ 'LAMBDA_TASK_ROOT',
892
+ 'LAMBDA_RUNTIME_DIR',
893
+ 'TZ',
894
+ '_HANDLER',
895
+ ]);
896
+
897
+ const merged = Object.fromEntries(
898
+ Object.entries({
899
+ ...(current.Environment?.Variables ?? {}),
900
+ ...params.environmentVariables,
901
+ }).filter(([key]) => !LAMBDA_RESERVED_KEYS.has(key)),
902
+ );
903
+
904
+ this.logger.log(
905
+ `updateLambdaEnvironmentVariables: sending merged keys=[${Object.keys(merged).join(',')}] ` +
906
+ `WEBHOOK_URL=${merged['WEBHOOK_URL'] ?? '(not set)'} ` +
907
+ `WEBHOOK_SECRET_LEN=${merged['WEBHOOK_SECRET']?.length ?? 0}`,
908
+ );
841
909
 
842
910
  await lambda.send(
843
911
  new UpdateFunctionConfigurationCommand({
@@ -848,12 +916,76 @@ export class FileService implements OnModuleInit {
848
916
  }),
849
917
  );
850
918
 
919
+ this.logger.log(`updateLambdaEnvironmentVariables: UpdateFunctionConfiguration sent, polling for completion`);
920
+
921
+ // Wait for configuration update to leave InProgress before returning.
922
+ const cfgDeadline = Date.now() + 60_000;
923
+ let finalStatus = 'unknown';
924
+ let confirmedVars: Record<string, string> = {};
925
+ while (Date.now() < cfgDeadline) {
926
+ const poll = await lambda.send(
927
+ new GetFunctionConfigurationCommand({ FunctionName: params.functionName }),
928
+ );
929
+ finalStatus = poll.LastUpdateStatus ?? 'unknown';
930
+ confirmedVars = (poll.Environment?.Variables ?? {}) as Record<string, string>;
931
+ if (finalStatus !== 'InProgress') break;
932
+ await new Promise((resolve) => setTimeout(resolve, 2000));
933
+ }
934
+
935
+ this.logger.log(
936
+ `updateLambdaEnvironmentVariables: done, finalStatus=${finalStatus} ` +
937
+ `confirmedKeys=[${Object.keys(confirmedVars).join(',')}] ` +
938
+ `confirmedWEBHOOK_URL=${confirmedVars['WEBHOOK_URL'] ?? '(missing)'} ` +
939
+ `confirmedWEBHOOK_SECRET_LEN=${confirmedVars['WEBHOOK_SECRET']?.length ?? 0}`,
940
+ );
941
+
851
942
  return {
852
943
  functionName: current.FunctionName ?? params.functionName,
853
944
  functionArn: current.FunctionArn ?? null,
854
945
  };
855
946
  }
856
947
 
948
+ async updateLambdaFunctionCode(params: {
949
+ functionName: string;
950
+ region: string;
951
+ accessKeyId: string;
952
+ secretAccessKey: string;
953
+ sessionToken?: string;
954
+ zipFile: Uint8Array;
955
+ }) {
956
+ const lambda = new LambdaClient({
957
+ region: params.region,
958
+ credentials: {
959
+ accessKeyId: params.accessKeyId,
960
+ secretAccessKey: params.secretAccessKey,
961
+ ...(params.sessionToken ? { sessionToken: params.sessionToken } : {}),
962
+ },
963
+ });
964
+
965
+ const result = await lambda.send(
966
+ new UpdateFunctionCodeCommand({
967
+ FunctionName: params.functionName,
968
+ ZipFile: params.zipFile,
969
+ }),
970
+ );
971
+
972
+ // Wait until the function leaves InProgress state before returning,
973
+ // so subsequent UpdateFunctionConfiguration calls don't get ResourceConflictException.
974
+ const deadline = Date.now() + 60_000;
975
+ while (Date.now() < deadline) {
976
+ const config = await lambda.send(
977
+ new GetFunctionConfigurationCommand({ FunctionName: params.functionName }),
978
+ );
979
+ if (config.LastUpdateStatus !== 'InProgress') break;
980
+ await new Promise((resolve) => setTimeout(resolve, 2000));
981
+ }
982
+
983
+ return {
984
+ functionName: result.FunctionName ?? params.functionName,
985
+ functionArn: result.FunctionArn ?? null,
986
+ };
987
+ }
988
+
857
989
  async createLambdaFunctionIfMissing(params: {
858
990
  functionName: string;
859
991
  region: string;
@@ -1,6 +1,8 @@
1
1
  import { v4 } from 'uuid';
2
2
  import { MulterFile } from '../../types/multer';
3
3
 
4
+ export type ReadStreamOptions = { start?: number; end?: number };
5
+
4
6
  interface IProvider {
5
7
  uploadFromString(
6
8
  destination: string,
@@ -14,7 +16,7 @@ interface IProvider {
14
16
  filePath: string,
15
17
  options?: { filename?: string; mimetype?: string },
16
18
  ): Promise<any>;
17
- readStream(filepath: string): Promise<any>;
19
+ readStream(filepath: string, options?: ReadStreamOptions): Promise<any>;
18
20
  delete(filepath: string): Promise<any>;
19
21
  metaData(filepath: string): Promise<any>;
20
22
  buffer(filepath: string): Promise<any>;
@@ -35,7 +37,7 @@ export abstract class AbstractProvider implements IProvider {
35
37
  filePath: string,
36
38
  options?: { filename?: string; mimetype?: string },
37
39
  ): Promise<any>;
38
- abstract readStream(filepath: string): Promise<any>;
40
+ abstract readStream(filepath: string, options?: ReadStreamOptions): Promise<any>;
39
41
  abstract delete(filepath: string): Promise<any>;
40
42
  abstract metaData(filepath: string): Promise<any>;
41
43
  abstract buffer(filepath: string): Promise<any>;
@@ -120,14 +120,17 @@ export class AzureProvider extends AbstractProvider {
120
120
  return await blobClient.delete();
121
121
  }
122
122
 
123
- async readStream(filepath: string): Promise<any> {
123
+ async readStream(filepath: string, options?: { start?: number; end?: number }): Promise<any> {
124
124
  const blobServiceClient = await this.getClient();
125
125
  const containerClient = blobServiceClient.getContainerClient(
126
126
  this.setting['storage-abs-container'],
127
127
  );
128
128
  const blobClient = containerClient.getBlockBlobClient(filepath);
129
-
130
- const downloadBlockBlobResponse = await blobClient.download();
129
+ const offset = options?.start ?? 0;
130
+ const count = options?.start !== undefined && options?.end !== undefined
131
+ ? options.end - options.start + 1
132
+ : undefined;
133
+ const downloadBlockBlobResponse = await blobClient.download(offset, count);
131
134
  return downloadBlockBlobResponse.readableStreamBody;
132
135
  }
133
136
 
@@ -87,12 +87,14 @@ export class GCSProvider extends AbstractProvider {
87
87
 
88
88
  return objectPath;
89
89
  }
90
- async readStream(filepath: string): Promise<any> {
90
+ async readStream(filepath: string, options?: { start?: number; end?: number }): Promise<any> {
91
91
  const storage = await this.getClient();
92
92
  const bucket = storage.bucket(this.setting['storage-gcs-bucket']);
93
93
  const file = bucket.file(filepath);
94
-
95
- return file.createReadStream();
94
+ const rangeOpts: any = {};
95
+ if (options?.start !== undefined) rangeOpts.start = options.start;
96
+ if (options?.end !== undefined) rangeOpts.end = options.end;
97
+ return file.createReadStream(Object.keys(rangeOpts).length > 0 ? rangeOpts : undefined);
96
98
  }
97
99
  async delete(filepath: string): Promise<any> {
98
100
  const storage = await this.getClient();
@@ -5,7 +5,7 @@ import { copyFile, mkdir, stat, unlink, writeFile } from 'fs/promises';
5
5
  import * as path from 'path';
6
6
  import { basename, join, sep } from 'path';
7
7
  import { Stream } from 'stream';
8
- import { AbstractProvider } from './abstract.provider';
8
+ import { AbstractProvider, ReadStreamOptions } from './abstract.provider';
9
9
 
10
10
  export class LocalProvider extends AbstractProvider {
11
11
  constructor(
@@ -127,14 +127,18 @@ export class LocalProvider extends AbstractProvider {
127
127
  return unlink(fullPath);
128
128
  }
129
129
 
130
- async readStream(filepath: string): Promise<Stream> {
130
+ async readStream(filepath: string, options?: ReadStreamOptions): Promise<Stream> {
131
131
  const fullPath = this.resolveFilepath(filepath);
132
132
 
133
133
  if (!existsSync(fullPath)) {
134
134
  throw new BadRequestException(`File not found: ${fullPath}`);
135
135
  }
136
136
 
137
- return createReadStream(fullPath);
137
+ const rangeOpts: { start?: number; end?: number } = {};
138
+ if (options?.start !== undefined) rangeOpts.start = options.start;
139
+ if (options?.end !== undefined) rangeOpts.end = options.end;
140
+
141
+ return createReadStream(fullPath, Object.keys(rangeOpts).length > 0 ? rangeOpts : undefined);
138
142
  }
139
143
 
140
144
  async metaData(filepath: string): Promise<any> {
@@ -184,14 +184,16 @@ export class S3Provider extends AbstractProvider {
184
184
  return true;
185
185
  }
186
186
 
187
- async readStream(filepath: string): Promise<Readable> {
187
+ async readStream(filepath: string, options?: { start?: number; end?: number }): Promise<Readable> {
188
188
  const s3 = await this.getClient();
189
189
  const key = this.resolveS3Key(filepath);
190
- const command = new GetObjectCommand({
191
- Bucket: this.setting['storage-s3-bucket'],
192
- Key: key,
193
- });
194
- const result = await s3.send(command);
190
+ const params: any = { Bucket: this.setting['storage-s3-bucket'], Key: key };
191
+ if (options?.start !== undefined || options?.end !== undefined) {
192
+ const start = options?.start ?? 0;
193
+ const end = options?.end !== undefined ? options.end : '';
194
+ params.Range = `bytes=${start}-${end}`;
195
+ }
196
+ const result = await s3.send(new GetObjectCommand(params));
195
197
  // @ts-ignore
196
198
  return result.Body as Readable;
197
199
  }
@@ -209,15 +209,16 @@ export class SpacesProvider extends AbstractProvider {
209
209
  return true;
210
210
  }
211
211
 
212
- async readStream(filepath: string): Promise<Readable> {
212
+ async readStream(filepath: string, options?: { start?: number; end?: number }): Promise<Readable> {
213
213
  const s3 = await this.getClient();
214
214
  const key = this.resolveKey(filepath);
215
- const result = await s3.send(
216
- new GetObjectCommand({
217
- Bucket: this.setting['storage-spaces-bucket'],
218
- Key: key,
219
- }),
220
- );
215
+ const params: any = { Bucket: this.setting['storage-spaces-bucket'], Key: key };
216
+ if (options?.start !== undefined || options?.end !== undefined) {
217
+ const start = options?.start ?? 0;
218
+ const end = options?.end !== undefined ? options.end : '';
219
+ params.Range = `bytes=${start}-${end}`;
220
+ }
221
+ const result = await s3.send(new GetObjectCommand(params));
221
222
  // @ts-ignore
222
223
  return result.Body as Readable;
223
224
  }
@@ -1,17 +1,19 @@
1
- import { PrismaModule } from '@hed-hog/api-prisma';
2
- import { Module, forwardRef } from '@nestjs/common';
3
- import { McpModule } from '../mcp/mcp.module';
4
- import { SettingModule } from '../setting/setting.module';
5
- import { McpChatController } from './mcp-chat.controller';
6
- import { McpChatService } from './mcp-chat.service';
7
-
8
- @Module({
9
- imports: [
10
- forwardRef(() => PrismaModule),
11
- forwardRef(() => SettingModule),
12
- forwardRef(() => McpModule),
13
- ],
14
- controllers: [McpChatController],
15
- providers: [McpChatService],
16
- })
17
- export class McpChatModule {}
1
+ import { PrismaModule } from '@hed-hog/api-prisma';
2
+ import { Module, forwardRef } from '@nestjs/common';
3
+ import { AiInstructionModule } from '../ai-instruction/ai-instruction.module';
4
+ import { McpModule } from '../mcp/mcp.module';
5
+ import { SettingModule } from '../setting/setting.module';
6
+ import { McpChatController } from './mcp-chat.controller';
7
+ import { McpChatService } from './mcp-chat.service';
8
+
9
+ @Module({
10
+ imports: [
11
+ forwardRef(() => PrismaModule),
12
+ forwardRef(() => SettingModule),
13
+ forwardRef(() => McpModule),
14
+ forwardRef(() => AiInstructionModule),
15
+ ],
16
+ controllers: [McpChatController],
17
+ providers: [McpChatService],
18
+ })
19
+ export class McpChatModule {}