@messenger-box/platform-server 10.0.3-alpha.7 → 10.0.3-alpha.74

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 (202) hide show
  1. package/lib/config/env-config.d.ts +7 -0
  2. package/lib/config/env-config.js +20 -0
  3. package/lib/config/env-config.js.map +1 -1
  4. package/lib/containers/containers.js +9 -1
  5. package/lib/containers/containers.js.map +1 -1
  6. package/lib/containers/context-services-from-container.d.ts +1 -1
  7. package/lib/containers/context-services-from-container.js +4 -2
  8. package/lib/containers/context-services-from-container.js.map +1 -1
  9. package/lib/graphql/resolvers/ai-fragment.d.ts +3 -0
  10. package/lib/graphql/resolvers/ai-fragment.js +276 -0
  11. package/lib/graphql/resolvers/ai-fragment.js.map +1 -0
  12. package/lib/graphql/resolvers/channel-member.d.ts +3 -2
  13. package/lib/graphql/resolvers/channel-member.js +30 -5
  14. package/lib/graphql/resolvers/channel-member.js.map +1 -1
  15. package/lib/graphql/resolvers/channel.d.ts +3 -2
  16. package/lib/graphql/resolvers/channel.js +308 -53
  17. package/lib/graphql/resolvers/channel.js.map +1 -1
  18. package/lib/graphql/resolvers/extended-token-account.d.ts +3 -2
  19. package/lib/graphql/resolvers/extended-token-account.js +90 -23
  20. package/lib/graphql/resolvers/extended-token-account.js.map +1 -1
  21. package/lib/graphql/resolvers/index.d.ts +1 -1
  22. package/lib/graphql/resolvers/index.js +1 -1
  23. package/lib/graphql/resolvers/index.js.map +1 -1
  24. package/lib/graphql/resolvers/post-thread.d.ts +1 -1
  25. package/lib/graphql/resolvers/post-thread.js +294 -132
  26. package/lib/graphql/resolvers/post-thread.js.map +1 -1
  27. package/lib/graphql/resolvers/post.d.ts +2 -3
  28. package/lib/graphql/resolvers/post.js +874 -239
  29. package/lib/graphql/resolvers/post.js.map +1 -1
  30. package/lib/graphql/resolvers/reaction.d.ts +3 -2
  31. package/lib/graphql/resolvers/reaction.js +96 -14
  32. package/lib/graphql/resolvers/reaction.js.map +1 -1
  33. package/lib/graphql/schema/ai-fragment.graphql +311 -0
  34. package/lib/graphql/schema/ai-fragment.graphql.js +1 -0
  35. package/lib/graphql/schema/ai-fragment.graphql.js.map +1 -0
  36. package/lib/graphql/schema/channel-member.graphql +110 -21
  37. package/lib/graphql/schema/channel-member.graphql.js +1 -1
  38. package/lib/graphql/schema/channel.graphql +356 -38
  39. package/lib/graphql/schema/channel.graphql.js +1 -1
  40. package/lib/graphql/schema/index.js +2 -2
  41. package/lib/graphql/schema/index.js.map +1 -1
  42. package/lib/graphql/schema/post-thread.graphql +167 -21
  43. package/lib/graphql/schema/post-thread.graphql.js +1 -1
  44. package/lib/graphql/schema/post.graphql +360 -40
  45. package/lib/graphql/schema/post.graphql.js +1 -1
  46. package/lib/graphql/schema/reaction.graphql +71 -13
  47. package/lib/graphql/schema/reaction.graphql.js +1 -1
  48. package/lib/graphql/schema/services.graphql +21 -0
  49. package/lib/graphql/schema/users.graphql +76 -13
  50. package/lib/graphql/schema/users.graphql.js +1 -1
  51. package/lib/index.js +1 -1
  52. package/lib/index.js.map +1 -1
  53. package/lib/inngest/factory.d.ts +20 -0
  54. package/lib/inngest/factory.js +4 -0
  55. package/lib/inngest/factory.js.map +1 -0
  56. package/lib/inngest/functions.d.ts +235 -0
  57. package/lib/inngest/functions.js +1385 -0
  58. package/lib/inngest/functions.js.map +1 -0
  59. package/lib/inngest/index.d.ts +3 -0
  60. package/lib/inngest/prompt.d.ts +6 -0
  61. package/lib/inngest/prompt.js +871 -0
  62. package/lib/inngest/prompt.js.map +1 -0
  63. package/lib/inngest/utils.d.ts +5 -0
  64. package/lib/inngest/utils.js +32 -0
  65. package/lib/inngest/utils.js.map +1 -0
  66. package/lib/interfaces/index.d.ts +0 -1
  67. package/lib/interfaces/services.d.ts +1 -1
  68. package/lib/migrations/dbMigrations/AddPostsConfigurationsMigration.d.ts +17 -0
  69. package/lib/migrations/dbMigrations/AddPostsConfigurationsMigration.js +44 -0
  70. package/lib/migrations/dbMigrations/AddPostsConfigurationsMigration.js.map +1 -0
  71. package/lib/migrations/dbMigrations/index.d.ts +1 -0
  72. package/lib/migrations/index.d.ts +1 -0
  73. package/lib/migrations/mail-template-migration.js +1 -1
  74. package/lib/migrations/message-notification-template-migration.d.ts +1 -1
  75. package/lib/migrations/message-notification-template-migration.js +1 -1
  76. package/lib/module.js +10 -3
  77. package/lib/module.js.map +1 -1
  78. package/lib/plugins/ai-fragment-moleculer-service.d.ts +29 -0
  79. package/lib/plugins/ai-fragment-moleculer-service.js +516 -0
  80. package/lib/plugins/ai-fragment-moleculer-service.js.map +1 -0
  81. package/lib/plugins/channel-moleculer-service.d.ts +21 -1
  82. package/lib/plugins/channel-moleculer-service.js +426 -115
  83. package/lib/plugins/channel-moleculer-service.js.map +1 -1
  84. package/lib/plugins/extended-token-account-moleculer-service.d.ts +25 -1
  85. package/lib/plugins/extended-token-account-moleculer-service.js +348 -22
  86. package/lib/plugins/extended-token-account-moleculer-service.js.map +1 -1
  87. package/lib/plugins/index.d.ts +1 -0
  88. package/lib/plugins/messenger-notification-moleculer-service.d.ts +27 -3
  89. package/lib/plugins/messenger-notification-moleculer-service.js +404 -58
  90. package/lib/plugins/messenger-notification-moleculer-service.js.map +1 -1
  91. package/lib/plugins/post-moleculer-service.d.ts +85 -21
  92. package/lib/plugins/post-moleculer-service.js +1102 -256
  93. package/lib/plugins/post-moleculer-service.js.map +1 -1
  94. package/lib/plugins/post-thread-moleculer-service.d.ts +33 -1
  95. package/lib/plugins/post-thread-moleculer-service.js +326 -8
  96. package/lib/plugins/post-thread-moleculer-service.js.map +1 -1
  97. package/lib/plugins/reaction-moleculer-service.js +1 -1
  98. package/lib/plugins/reaction-moleculer-service.js.map +1 -1
  99. package/lib/preferences/settings/post-settings.d.ts +2 -0
  100. package/lib/preferences/settings/post-settings.js +47 -9
  101. package/lib/preferences/settings/post-settings.js.map +1 -1
  102. package/lib/services/ai-fragment-service.d.ts +195 -0
  103. package/lib/services/ai-fragment-service.js +631 -0
  104. package/lib/services/ai-fragment-service.js.map +1 -0
  105. package/lib/services/channel-service.d.ts +181 -33
  106. package/lib/services/channel-service.js +842 -273
  107. package/lib/services/channel-service.js.map +1 -1
  108. package/lib/services/extended-token-account-service.d.ts +130 -14
  109. package/lib/services/extended-token-account-service.js +462 -52
  110. package/lib/services/extended-token-account-service.js.map +1 -1
  111. package/lib/services/index.d.ts +3 -0
  112. package/lib/services/messenger-notification-service.d.ts +106 -13
  113. package/lib/services/messenger-notification-service.js +824 -442
  114. package/lib/services/messenger-notification-service.js.map +1 -1
  115. package/lib/services/post-service.d.ts +189 -16
  116. package/lib/services/post-service.js +949 -113
  117. package/lib/services/post-service.js.map +1 -1
  118. package/lib/services/post-thread-service.d.ts +114 -5
  119. package/lib/services/post-thread-service.js +400 -13
  120. package/lib/services/post-thread-service.js.map +1 -1
  121. package/lib/services/proxy-services/ai-fragment-microservice.d.ts +23 -0
  122. package/lib/services/proxy-services/ai-fragment-microservice.js +78 -0
  123. package/lib/services/proxy-services/ai-fragment-microservice.js.map +1 -0
  124. package/lib/services/proxy-services/channel-microservice.d.ts +6 -3
  125. package/lib/services/proxy-services/channel-microservice.js +25 -10
  126. package/lib/services/proxy-services/channel-microservice.js.map +1 -1
  127. package/lib/services/proxy-services/index.d.ts +1 -0
  128. package/lib/services/proxy-services/messenger-notification-microservice.d.ts +128 -8
  129. package/lib/services/proxy-services/messenger-notification-microservice.js +324 -29
  130. package/lib/services/proxy-services/messenger-notification-microservice.js.map +1 -1
  131. package/lib/services/proxy-services/post-microservice.d.ts +207 -12
  132. package/lib/services/proxy-services/post-microservice.js +623 -54
  133. package/lib/services/proxy-services/post-microservice.js.map +1 -1
  134. package/lib/services/proxy-services/post-thread-microservice.d.ts +134 -3
  135. package/lib/services/proxy-services/post-thread-microservice.js +388 -6
  136. package/lib/services/proxy-services/post-thread-microservice.js.map +1 -1
  137. package/lib/services/proxy-services/reaction-microservice.d.ts +161 -3
  138. package/lib/services/proxy-services/reaction-microservice.js +474 -2
  139. package/lib/services/proxy-services/reaction-microservice.js.map +1 -1
  140. package/lib/services/reaction-service.d.ts +124 -4
  141. package/lib/services/reaction-service.js +415 -3
  142. package/lib/services/reaction-service.js.map +1 -1
  143. package/lib/services/redis-cache-manager.d.ts +18 -0
  144. package/lib/services/redis-cache-manager.js +83 -0
  145. package/lib/services/redis-cache-manager.js.map +1 -0
  146. package/lib/services/sandbox-error-service.d.ts +23 -0
  147. package/lib/services/sandbox-error-service.js +422 -0
  148. package/lib/services/sandbox-error-service.js.map +1 -0
  149. package/lib/store/models/account-token-store.d.ts +1 -1
  150. package/lib/store/models/account-token-store.js.map +1 -1
  151. package/lib/store/models/ai-fragment.d.ts +4 -0
  152. package/lib/store/models/ai-fragment.js +125 -0
  153. package/lib/store/models/ai-fragment.js.map +1 -0
  154. package/lib/store/models/channel.d.ts +2 -3
  155. package/lib/store/models/channel.js +185 -71
  156. package/lib/store/models/channel.js.map +1 -1
  157. package/lib/store/models/index.d.ts +1 -0
  158. package/lib/store/models/post-thread.d.ts +3 -3
  159. package/lib/store/models/post-thread.js +96 -14
  160. package/lib/store/models/post-thread.js.map +1 -1
  161. package/lib/store/models/post.d.ts +2 -3
  162. package/lib/store/models/post.js +143 -23
  163. package/lib/store/models/post.js.map +1 -1
  164. package/lib/store/models/reaction.d.ts +2 -3
  165. package/lib/store/models/reaction.js +67 -8
  166. package/lib/store/models/reaction.js.map +1 -1
  167. package/lib/store/repositories/__tests__/__fixtures__/team-repository.d.ts +3 -3
  168. package/lib/store/repositories/ai-fragment-repository.d.ts +15 -0
  169. package/lib/store/repositories/ai-fragment-repository.js +69 -0
  170. package/lib/store/repositories/ai-fragment-repository.js.map +1 -0
  171. package/lib/store/repositories/channel-repository.d.ts +6 -6
  172. package/lib/store/repositories/channel-repository.js +5 -2
  173. package/lib/store/repositories/channel-repository.js.map +1 -1
  174. package/lib/store/repositories/index.d.ts +1 -0
  175. package/lib/store/repositories/post-repository.d.ts +6 -6
  176. package/lib/store/repositories/post-repository.js +5 -2
  177. package/lib/store/repositories/post-repository.js.map +1 -1
  178. package/lib/store/repositories/post-thread-repository.d.ts +6 -6
  179. package/lib/store/repositories/post-thread-repository.js +5 -2
  180. package/lib/store/repositories/post-thread-repository.js.map +1 -1
  181. package/lib/store/repositories/reaction-repository.d.ts +6 -6
  182. package/lib/store/repositories/reaction-repository.js +5 -2
  183. package/lib/store/repositories/reaction-repository.js.map +1 -1
  184. package/lib/templates/constants/SERVER_TYPES.ts.template +4 -4
  185. package/lib/templates/repositories/AiFragmentRepository.ts.template +4 -0
  186. package/lib/templates/repositories/ChannelRepository.ts.template +3 -3
  187. package/lib/templates/repositories/PostRepository.ts.template +3 -3
  188. package/lib/templates/repositories/PostThreadRepository.ts.template +3 -3
  189. package/lib/templates/repositories/ReactionRepository.ts.template +3 -4
  190. package/lib/templates/services/AiFragmentService.ts.template +123 -0
  191. package/lib/templates/services/ChannelService.ts.template +290 -39
  192. package/lib/templates/services/ExtendedTokenAccountService.ts.template +104 -9
  193. package/lib/templates/services/MessengerNotificationService.ts.template +94 -19
  194. package/lib/templates/services/PostService.ts.template +265 -20
  195. package/lib/templates/services/PostThreadService.ts.template +151 -6
  196. package/lib/templates/services/ReactionService.ts.template +129 -3
  197. package/lib/templates/services/RedisCacheManager.ts.template +22 -0
  198. package/lib/templates/services/SandboxErrorService.ts.template +125 -0
  199. package/package.json +14 -7
  200. package/lib/interfaces/context.d.ts +0 -14
  201. package/lib/store/models/common-options.js +0 -20
  202. package/lib/store/models/common-options.js.map +0 -1
@@ -1,55 +1,230 @@
1
- import {__decorate,__param,__metadata}from'tslib';import'@cdm-logger/core';import {inject}from'inversify';import {set}from'lodash-es';import {SERVER_TYPES,FileRefType}from'common';import {BaseService}from'@common-stack/store-mongo';import {PubSubEngine}from'graphql-subscriptions';import {CommonType}from'@common-stack/core';import {ServiceBroker}from'moleculer';import {ChannelRepository}from'../store/repositories/channel-repository.js';import {PostRepository}from'../store/repositories/post-repository.js';import'../store/repositories/post-thread-repository.js';import'../store/repositories/reaction-repository.js';import {PostTypes}from'../preferences/settings/post-settings.js';import'../preferences/permissions/inbox-permission-contribution.js';import'../preferences/permissions/inbox-roles-permission-overwrite.js';import {config}from'../config/env-config.js';var PostService_1;
2
- let PostService = PostService_1 = class PostService extends BaseService {
1
+ import {__decorate,__param,__metadata}from'tslib';import'@cdm-logger/core';import {inject}from'inversify';import {set}from'lodash-es';import {SERVER_TYPES,FileRefType,AiAgentMessageRole,AiAgentMessageType}from'common/server';import {BaseService2}from'@common-stack/store-mongo';import {PubSubEngine}from'graphql-subscriptions';import {CommonType}from'@common-stack/core';import {ServiceBroker}from'moleculer';import {DisposableCollection}from'@adminide-stack/core';import {ChannelRepository}from'../store/repositories/channel-repository.js';import {PostRepository}from'../store/repositories/post-repository.js';import'../store/repositories/post-thread-repository.js';import'../store/repositories/reaction-repository.js';import'../store/repositories/ai-fragment-repository.js';import {PostTypes}from'../preferences/settings/post-settings.js';import'../preferences/permissions/inbox-permission-contribution.js';import'../preferences/permissions/inbox-roles-permission-overwrite.js';import {config}from'../config/env-config.js';import {getSandbox}from'../inngest/utils.js';var PostService_1;
2
+ /**
3
+ * Post Service Implementation
4
+ *
5
+ * This service handles comprehensive post management within the messenger platform,
6
+ * providing operations for creating, updating, managing posts and their associated
7
+ * content including file attachments, notifications, threading, and real-time
8
+ * message delivery across channels and organizations.
9
+ *
10
+ * Key capabilities:
11
+ * - Post lifecycle management (creation, updates, deletion)
12
+ * - File attachment handling and upload management
13
+ * - Real-time message delivery and notifications
14
+ * - Post threading and conversation management
15
+ * - Message status tracking (read, delivered)
16
+ * - Channel integration and message counting
17
+ * - Expo push notification integration
18
+ * - File upload link generation and management
19
+ */
20
+ // @ts-ignore - Type compatibility issue between BaseService2 and IPostService
21
+ let PostService = class PostService extends BaseService2 {
22
+ static {
23
+ PostService_1 = this;
24
+ }
3
25
  channelRepo;
4
26
  fileInfoService;
5
27
  messengerNotificationService;
28
+ aiFragmentService;
6
29
  broker;
7
30
  pubsub;
31
+ redisCacheManager;
32
+ redisClient;
33
+ toDispose = new DisposableCollection();
8
34
  logger;
9
- constructor(repository, channelRepo, fileInfoService, messengerNotificationService, broker, pubsub, logger) {
35
+ // Redis cache configuration for posts
36
+ static CACHE_TTL = {
37
+ maxAge: 5 * 60,
38
+ scope: 'PUBLIC'
39
+ };
40
+ constructor(repository, channelRepo, fileInfoService, messengerNotificationService, aiFragmentService, broker, pubsub, redisCacheManager, redisClient, logger) {
10
41
  super(repository);
11
42
  this.channelRepo = channelRepo;
12
43
  this.fileInfoService = fileInfoService;
13
44
  this.messengerNotificationService = messengerNotificationService;
45
+ this.aiFragmentService = aiFragmentService;
14
46
  this.broker = broker;
15
47
  this.pubsub = pubsub;
48
+ this.redisCacheManager = redisCacheManager;
49
+ this.redisClient = redisClient;
16
50
  this.logger = logger?.child({
17
51
  className: PostService_1.name
18
52
  });
19
53
  }
20
- createFileUploadLink(postId, filename, userId) {
21
- return this.fileInfoService.uploadByUrl({
22
- filename,
23
- refType: FileRefType.Post,
24
- ref: postId,
25
- userId
26
- });
54
+ /**
55
+ * Disposes of resources used by the service
56
+ */
57
+ dispose() {
58
+ this.toDispose.dispose();
59
+ }
60
+ // Override base service methods to handle errors internally
61
+ async create(data) {
62
+ try {
63
+ this.logger.debug('Creating post', {
64
+ data
65
+ });
66
+ // Validate required fields
67
+ if (!data) {
68
+ throw new Error('Post data is required');
69
+ }
70
+ if (!data.channel) {
71
+ throw new Error('Channel is required for post creation');
72
+ }
73
+ if (!data.author) {
74
+ throw new Error('Author is required for post creation');
75
+ }
76
+ // Add timestamps and default type
77
+ const postData = {
78
+ ...data,
79
+ type: data.type || 'Simple',
80
+ // Default to Simple type for regular messages
81
+ createdAt: new Date(),
82
+ updatedAt: new Date()
83
+ };
84
+ // Create the post using repository
85
+ // const result = await this.repository.create(postData);
86
+ const result = await super.create(postData);
87
+ if (!result) {
88
+ throw new Error('Failed to create post');
89
+ }
90
+ // Update channel statistics
91
+ try {
92
+ await this.channelRepo.model.updateOne({
93
+ _id: data.channel
94
+ }, {
95
+ lastPostAt: result.createdAt,
96
+ $inc: {
97
+ totalMsgCount: 1,
98
+ totalMsgCountRoot: 1,
99
+ 'members.$[elem].msgCountRoot': 1,
100
+ 'members.$[elem].msgCount': 1
101
+ }
102
+ }, {
103
+ arrayFilters: [{
104
+ 'elem.user': data.author
105
+ }]
106
+ });
107
+ } catch (channelError) {
108
+ this.logger.error('Error updating channel stats:', channelError);
109
+ // Don't fail the post creation if stats update fails
110
+ }
111
+ console.log('result...', JSON.stringify(result, null, 2));
112
+ if (result.props.sendNotificationWithProjectId) {
113
+ await this.pubsub.publish(`POST_CREATED.${result.props.projectId}`, result);
114
+ }
115
+ await this.pubsub.publish(`POST_CREATED.${data.channel}`, result);
116
+ await this.sendExpoNotification(result);
117
+ // Invalidate caches related to this channel
118
+ await this.invalidatePostCachesByChannel(String(data.channel));
119
+ return result;
120
+ } catch (error) {
121
+ this.logger.error('Error creating post:', error);
122
+ throw error;
123
+ }
124
+ }
125
+ async update(id, data) {
126
+ try {
127
+ const result = await super.update(id, data);
128
+ if (!result || typeof result === 'object' && 'message' in result) {
129
+ this.logger.error('Failed to update post', {
130
+ error: result
131
+ });
132
+ throw new Error('Failed to update post');
133
+ }
134
+ try {
135
+ // Invalidate caches related to this post's channel
136
+ const channelId = result?.channel?.toString?.() || data?.channel;
137
+ if (channelId && !result?.props?.projectId) {
138
+ await this.invalidatePostCachesByChannel(String(channelId));
139
+ }
140
+ } catch (cacheError) {
141
+ this.logger.warn('Cache invalidation error after post update: %o', cacheError);
142
+ }
143
+ return result;
144
+ } catch (error) {
145
+ this.logger.error('Error updating post', {
146
+ error
147
+ });
148
+ throw error;
149
+ }
27
150
  }
151
+ async delete(criteria) {
152
+ try {
153
+ const result = await super.delete(criteria);
154
+ if (typeof result !== 'boolean') {
155
+ this.logger.error('Failed to delete post', {
156
+ error: result
157
+ });
158
+ throw new Error('Failed to delete post');
159
+ }
160
+ try {
161
+ // Attempt to invalidate caches if criteria is id
162
+ const id = typeof criteria === 'string' ? criteria : criteria?.id;
163
+ if (id) {
164
+ const existing = await this.get(id).catch(() => null);
165
+ const channelId = existing?.channel?.toString?.();
166
+ if (channelId) {
167
+ await this.invalidatePostCachesByChannel(String(channelId));
168
+ }
169
+ }
170
+ } catch (cacheError) {
171
+ this.logger.warn('Cache invalidation error after post delete: %o', cacheError);
172
+ }
173
+ return result;
174
+ } catch (error) {
175
+ this.logger.error('Error deleting post', {
176
+ error
177
+ });
178
+ throw error;
179
+ }
180
+ }
181
+ /**
182
+ * Creates a file upload link for a post
183
+ *
184
+ * @description Generates a secure upload URL for attaching files to posts
185
+ *
186
+ * @param {string} postId - The post identifier
187
+ * @param {string} filename - The name of the file to upload
188
+ * @param {string} userId - The user performing the upload
189
+ * @returns {Promise<string | Error>} - Upload URL or error
190
+ */
191
+ async createFileUploadLink(postId, filename, userId) {
192
+ try {
193
+ if (!postId || !filename || !userId) {
194
+ return new Error('Post ID, filename, and user ID are required');
195
+ }
196
+ const uploadUrl = await this.fileInfoService.uploadByUrl({
197
+ filename,
198
+ refType: FileRefType.Post,
199
+ ref: postId,
200
+ userId
201
+ });
202
+ this.logger.debug('File upload link created', {
203
+ postId,
204
+ filename,
205
+ userId
206
+ });
207
+ return uploadUrl;
208
+ } catch (error) {
209
+ this.logger.error('Error creating file upload link: %o', error);
210
+ return error instanceof Error ? error : new Error('Unknown error occurred while creating upload link');
211
+ }
212
+ }
213
+ /**
214
+ * Attaches an uploaded file to a post
215
+ *
216
+ * @description Processes and attaches a completed file upload to a post
217
+ *
218
+ * @param {string} postId - The post identifier
219
+ * @param {IUploadedFileInput} file - The uploaded file information
220
+ * @param {string} createdBy - The user who uploaded the file
221
+ * @returns {Promise<IFileInfo | Error>} - File information or error
222
+ */
28
223
  async attachUploadedFile(postId, file, createdBy) {
29
- const {
30
- id
31
- } = await this.fileInfoService.createUploadedFile({
32
- ...file,
33
- refType: FileRefType.Post,
34
- ref: postId,
35
- createdBy
36
- });
37
- const fileResponse = await this.fileInfoService.get(id);
38
- return {
39
- ...fileResponse,
40
- url: this.fileInfoService.getDefaultImage(fileResponse.url)
41
- };
42
- }
43
- createFilesUploadLink(postId, filenames, userId) {
44
- return Promise.all(filenames.map(filename => this.fileInfoService.uploadByUrl({
45
- filename,
46
- refType: FileRefType.Post,
47
- ref: postId,
48
- userId
49
- })));
50
- }
51
- attachUploadedFiles(postId, files, createdBy) {
52
- return Promise.all(files.map(async file => {
224
+ try {
225
+ if (!postId || !file || !createdBy) {
226
+ return new Error('Post ID, file data, and creator ID are required');
227
+ }
53
228
  const {
54
229
  id
55
230
  } = await this.fileInfoService.createUploadedFile({
@@ -59,103 +234,764 @@ let PostService = PostService_1 = class PostService extends BaseService {
59
234
  createdBy
60
235
  });
61
236
  const fileResponse = await this.fileInfoService.get(id);
62
- return {
237
+ const result = {
63
238
  ...fileResponse,
64
239
  url: this.fileInfoService.getDefaultImage(fileResponse.url)
65
240
  };
66
- }));
241
+ this.logger.debug('File attached to post', {
242
+ postId,
243
+ fileId: id,
244
+ createdBy
245
+ });
246
+ return result;
247
+ } catch (error) {
248
+ this.logger.error('Error attaching uploaded file: %o', error);
249
+ return error instanceof Error ? error : new Error('Unknown error occurred while attaching file');
250
+ }
67
251
  }
68
- async create(data) {
69
- const post = await super.create(data);
70
- const {
71
- channel
72
- } = data;
73
- // Accessing private property
74
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
75
- // @ts-ignore
76
- await this.channelRepo.model.updateOne({
77
- _id: channel
78
- }, {
79
- lastPostAt: post.createdAt,
80
- $inc: {
81
- totalMsgCount: 1,
82
- totalMsgCountRoot: 1,
83
- 'members.$[elem].msgCountRoot': 1,
84
- 'members.$[elem].msgCount': 1
85
- }
86
- }, {
87
- arrayFilters: [{
88
- 'elem.user': data.author
89
- }]
90
- });
91
- await this.pubsub.publish(`POST_CREATED.${channel}`, post);
92
- await this.sendExpoNotification(post);
93
- // if (post?.props?.notificationParams) await this.messengerNotificationService.sendExpoNotificationOnPost(post);
94
- this.logger.trace('Post created');
95
- return post;
252
+ /**
253
+ * Creates upload links for multiple files
254
+ *
255
+ * @description Generates secure upload URLs for multiple files to be attached to a post
256
+ *
257
+ * @param {string} postId - The post identifier
258
+ * @param {string[]} filenames - Array of file names
259
+ * @param {string} userId - The user performing the uploads
260
+ * @returns {Promise<string[] | Error>} - Array of upload URLs or error
261
+ */
262
+ async createFilesUploadLink(postId, filenames, userId) {
263
+ try {
264
+ if (!postId || !filenames || !Array.isArray(filenames) || !userId) {
265
+ return new Error('Post ID, filenames array, and user ID are required');
266
+ }
267
+ if (filenames.length === 0) {
268
+ return new Error('At least one filename is required');
269
+ }
270
+ const uploadUrls = await Promise.all(filenames.map(filename => this.fileInfoService.uploadByUrl({
271
+ filename,
272
+ refType: FileRefType.Post,
273
+ ref: postId,
274
+ userId
275
+ })));
276
+ this.logger.debug('Multiple file upload links created', {
277
+ postId,
278
+ fileCount: filenames.length,
279
+ userId
280
+ });
281
+ return uploadUrls;
282
+ } catch (error) {
283
+ this.logger.error('Error creating multiple file upload links: %o', error);
284
+ return error instanceof Error ? error : new Error('Unknown error occurred while creating upload links');
285
+ }
96
286
  }
287
+ /**
288
+ * Attaches multiple uploaded files to a post
289
+ *
290
+ * @description Processes and attaches multiple completed file uploads to a post
291
+ *
292
+ * @param {string} postId - The post identifier
293
+ * @param {IUploadedFileInput[]} files - Array of uploaded file information
294
+ * @param {string} createdBy - The user who uploaded the files
295
+ * @returns {Promise<IFileInfo[] | Error>} - Array of file information or error
296
+ */
297
+ async attachUploadedFiles(postId, files, createdBy) {
298
+ try {
299
+ if (!postId || !files || !Array.isArray(files) || !createdBy) {
300
+ return new Error('Post ID, files array, and creator ID are required');
301
+ }
302
+ if (files.length === 0) {
303
+ return new Error('At least one file is required');
304
+ }
305
+ const fileResults = await Promise.all(files.map(async file => {
306
+ const {
307
+ id
308
+ } = await this.fileInfoService.createUploadedFile({
309
+ ...file,
310
+ refType: FileRefType.Post,
311
+ ref: postId,
312
+ createdBy
313
+ });
314
+ const fileResponse = await this.fileInfoService.get(id);
315
+ return {
316
+ ...fileResponse,
317
+ url: this.fileInfoService.getDefaultImage(fileResponse.url)
318
+ };
319
+ }));
320
+ this.logger.debug('Multiple files attached to post', {
321
+ postId,
322
+ fileCount: files.length,
323
+ createdBy
324
+ });
325
+ return fileResults;
326
+ } catch (error) {
327
+ this.logger.error('Error attaching multiple uploaded files: %o', error);
328
+ return error instanceof Error ? error : new Error('Unknown error occurred while attaching files');
329
+ }
330
+ }
331
+ /**
332
+ * Creates a new post with full integration
333
+ *
334
+ * @description Creates a post with channel updates, notifications, and real-time publishing
335
+ *
336
+ * @param {IPostServiceInput} data - Post creation data
337
+ * @returns {Promise<AsDomainType<IPostModel> | Error>} - Created post or error
338
+ */
339
+ // @ts-ignore - Type compatibility issue between interface and implementation
97
340
  async createWithoutSubscription(data) {
98
- const post = await super.create(data);
99
- const {
100
- channel
101
- } = data;
102
- await this.channelRepo.model.updateOne({
103
- _id: channel
104
- }, {
105
- lastPostAt: post.createdAt,
106
- $inc: {
107
- totalMsgCount: 1,
108
- totalMsgCountRoot: 1,
109
- 'members.$[elem].msgCountRoot': 1,
110
- 'members.$[elem].msgCount': 1
111
- }
112
- }, {
113
- arrayFilters: [{
114
- 'elem.user': data.author
115
- }]
116
- });
117
- return post;
341
+ try {
342
+ if (!data) {
343
+ return new Error('Post data is required');
344
+ }
345
+ if (!data.channel) {
346
+ return new Error('Channel is required for post creation');
347
+ }
348
+ if (!data.author) {
349
+ return new Error('Author is required for post creation');
350
+ }
351
+ // Create the post
352
+ const postData = {
353
+ ...data,
354
+ createdAt: new Date(),
355
+ updatedAt: new Date()
356
+ }; // Type assertion for compatibility
357
+ const post = await this.repository.create(postData);
358
+ if (!post) {
359
+ return new Error('Failed to create post');
360
+ }
361
+ const {
362
+ channel
363
+ } = data;
364
+ // Update channel statistics without real-time publishing
365
+ // @ts-ignore - Accessing private property for channel updates
366
+ await this.channelRepo.model.updateOne({
367
+ _id: channel
368
+ }, {
369
+ lastPostAt: post.createdAt,
370
+ $inc: {
371
+ totalMsgCount: 1,
372
+ totalMsgCountRoot: 1,
373
+ 'members.$[elem].msgCountRoot': 1,
374
+ 'members.$[elem].msgCount': 1
375
+ }
376
+ }, {
377
+ arrayFilters: [{
378
+ 'elem.user': data.author
379
+ }]
380
+ });
381
+ this.logger.debug('Post created without subscription', {
382
+ postId: post.id,
383
+ channelId: channel,
384
+ authorId: data.author
385
+ });
386
+ // Invalidate caches related to this channel
387
+ await this.invalidatePostCachesByChannel(String(channel));
388
+ return post;
389
+ } catch (error) {
390
+ this.logger.error('Error creating post without subscription: %o', error);
391
+ return error instanceof Error ? error : new Error('Unknown error occurred while creating post');
392
+ }
118
393
  }
119
- deleteFile(url) {
120
- return this.fileInfoService.deleteByUrl(url);
394
+ /**
395
+ * Deletes a file by URL
396
+ *
397
+ * @description Removes a file from storage using its URL
398
+ *
399
+ * @param {string} url - The file URL to delete
400
+ * @returns {Promise<boolean | Error>} - Success status or error
401
+ */
402
+ async deleteFile(url) {
403
+ try {
404
+ if (!url) {
405
+ return new Error('File URL is required');
406
+ }
407
+ const success = await this.fileInfoService.deleteByUrl(url);
408
+ this.logger.debug('File deleted', {
409
+ url,
410
+ success
411
+ });
412
+ return success;
413
+ } catch (error) {
414
+ this.logger.error('Error deleting file: %o', error);
415
+ return error instanceof Error ? error : new Error('Unknown error occurred while deleting file');
416
+ }
121
417
  }
418
+ /**
419
+ * Marks a message as read by a user
420
+ *
421
+ * @description Updates message status to indicate it has been read by a specific user
422
+ *
423
+ * @param {IMessageIdentifier} messageId - Message identifier
424
+ * @param {string} user - User ID who read the message
425
+ * @returns {Promise<boolean | Error>} - Success status or error
426
+ */
122
427
  async readMessage(messageId, user) {
123
- await this.update(messageId.messageId, {
124
- props: {
125
- [`[${user}]`]: set({}, PostTypes.isRead, true)
428
+ try {
429
+ if (!messageId?.messageId || !user) {
430
+ return new Error('Message ID and user ID are required');
126
431
  }
127
- });
128
- return true;
432
+ await this.update(messageId.messageId, {
433
+ props: {
434
+ [`[${user}]`]: set({}, PostTypes.isRead, true)
435
+ }
436
+ });
437
+ this.logger.debug('Message marked as read', {
438
+ messageId: messageId.messageId,
439
+ userId: user
440
+ });
441
+ return true;
442
+ } catch (error) {
443
+ this.logger.error('Error marking message as read: %o', error);
444
+ return error instanceof Error ? error : new Error('Unknown error occurred while marking message as read');
445
+ }
129
446
  }
447
+ /**
448
+ * Marks a message as delivered to a user
449
+ *
450
+ * @description Updates message status to indicate it has been delivered to a specific user
451
+ *
452
+ * @param {IMessageIdentifier} messageId - Message identifier
453
+ * @param {string} user - User ID who received the message
454
+ * @returns {Promise<boolean | Error>} - Success status or error
455
+ */
130
456
  async deliverMessage(messageId, user) {
131
- await this.update(messageId.messageId, {
132
- props: {
133
- [`[${user}]`]: set({}, PostTypes.isDelivered, true)
457
+ try {
458
+ if (!messageId?.messageId || !user) {
459
+ return new Error('Message ID and user ID are required');
134
460
  }
135
- });
136
- return true;
461
+ await this.update(messageId.messageId, {
462
+ props: {
463
+ [`[${user}]`]: set({}, PostTypes.isDelivered, true)
464
+ }
465
+ });
466
+ this.logger.debug('Message marked as delivered', {
467
+ messageId: messageId.messageId,
468
+ userId: user
469
+ });
470
+ return true;
471
+ } catch (error) {
472
+ this.logger.error('Error marking message as delivered: %o', error);
473
+ return error instanceof Error ? error : new Error('Unknown error occurred while marking message as delivered');
474
+ }
475
+ }
476
+ /**
477
+ * Creates a post with associated post thread
478
+ *
479
+ * @description Creates both a post and its associated thread for threaded conversations
480
+ *
481
+ * @param {CreatePostThreadOptions} data - Post and thread creation options
482
+ * @returns {Promise<{post: AsDomainType<IPostModel>; postThread: IPostThread} | Error>} - Created post and thread or error
483
+ */
484
+ // @ts-ignore - Type compatibility issue between interface and implementation
485
+ async createPostWithPostThread(data) {
486
+ try {
487
+ if (!data) {
488
+ return new Error('Post thread creation data is required');
489
+ }
490
+ // This would require integration with PostThreadService
491
+ // For now, return an error indicating it needs implementation
492
+ return new Error('Post thread creation is not yet implemented');
493
+ } catch (error) {
494
+ this.logger.error('Error creating post with thread: %o', error);
495
+ return error instanceof Error ? error : new Error('Unknown error occurred while creating post with thread');
496
+ }
497
+ }
498
+ /**
499
+ * Retrieves the last message in a channel
500
+ *
501
+ * @description Gets the most recent post/message from a specific channel
502
+ *
503
+ * @param {string} channelId - The channel identifier
504
+ * @returns {Promise<AsDomainType<IPostModel> | Error>} - Last message or error
505
+ */
506
+ // @ts-ignore - Type compatibility issue between interface and implementation
507
+ async getLastMessage(channelId) {
508
+ try {
509
+ if (!channelId) {
510
+ return new Error('Channel ID is required');
511
+ }
512
+ // Try Redis cache first
513
+ if (this.redisCacheManager) {
514
+ try {
515
+ const cacheKey = `${config.APP_NAME}:getLastPostByChannel`;
516
+ const variables = {
517
+ channelId
518
+ };
519
+ const cacheCtx = {
520
+ userId: 'system'
521
+ };
522
+ const cached = await this.redisCacheManager.get(cacheKey, variables, cacheCtx);
523
+ if (cached) {
524
+ this.logger.debug(`Cache hit for last post by channel ${channelId}`);
525
+ return cached;
526
+ }
527
+ } catch (cacheError) {
528
+ this.logger.warn(`Redis cache error for getLastMessage(${channelId}): ${cacheError?.message}`);
529
+ }
530
+ }
531
+ // @ts-ignore - Accessing private property for direct query
532
+ const post = await this.repository.model.findOne({
533
+ channel: channelId
534
+ }).sort({
535
+ createdAt: -1
536
+ });
537
+ if (!post) {
538
+ return new Error('No messages found in channel');
539
+ }
540
+ this.logger.debug('Last message retrieved', {
541
+ channelId,
542
+ postId: post._id
543
+ });
544
+ const result = post;
545
+ // Cache the result
546
+ if (this.redisCacheManager) {
547
+ try {
548
+ const cacheKey = `${config.APP_NAME}:getLastPostByChannel`;
549
+ const variables = {
550
+ channelId
551
+ };
552
+ const cacheCtx = {
553
+ userId: 'system'
554
+ };
555
+ await this.redisCacheManager.set(cacheKey, variables, result, cacheCtx, PostService_1.CACHE_TTL);
556
+ } catch (cacheError) {
557
+ this.logger.warn(`Failed to cache last post by channel: ${cacheError?.message}`);
558
+ }
559
+ }
560
+ return result;
561
+ } catch (error) {
562
+ this.logger.error('Error getting last message: %o', error);
563
+ return error instanceof Error ? error : new Error('Unknown error occurred while getting last message');
564
+ }
565
+ }
566
+ /**
567
+ * Gets posts by channel with pagination
568
+ *
569
+ * @description Retrieves posts from a channel with pagination support
570
+ *
571
+ * @param {string} channelId - The channel identifier
572
+ * @param {number} limit - Maximum number of posts to return
573
+ * @param {number} offset - Number of posts to skip
574
+ * @returns {Promise<Array<AsDomainType<IPostModel>> | Error>} - Array of posts or error
575
+ */
576
+ async getPostsByChannel(channelId, limit = 50, offset = 0) {
577
+ try {
578
+ if (!channelId) {
579
+ return new Error('Channel ID is required');
580
+ }
581
+ // Try Redis cache first
582
+ if (this.redisCacheManager) {
583
+ try {
584
+ const cacheKey = `${config.APP_NAME}:getPostsByChannel`;
585
+ const variables = {
586
+ channelId,
587
+ limit,
588
+ offset
589
+ };
590
+ const cacheCtx = {
591
+ userId: 'system'
592
+ };
593
+ const cached = await this.redisCacheManager.get(cacheKey, variables, cacheCtx);
594
+ if (cached) {
595
+ this.logger.debug(`Cache hit for posts by channel ${channelId}`);
596
+ return cached;
597
+ }
598
+ } catch (cacheError) {
599
+ this.logger.warn(`Redis cache error for getPostsByChannel(${channelId}): ${cacheError?.message}`);
600
+ }
601
+ }
602
+ const posts = await this.getAll({
603
+ criteria: {
604
+ channel: channelId
605
+ }
606
+ });
607
+ // Apply manual pagination
608
+ const paginatedPosts = posts.slice(offset, offset + limit);
609
+ this.logger.debug('Posts retrieved by channel', {
610
+ channelId,
611
+ count: paginatedPosts.length,
612
+ limit,
613
+ offset
614
+ });
615
+ const result = paginatedPosts;
616
+ // Cache the result
617
+ if (this.redisCacheManager) {
618
+ try {
619
+ const cacheKey = `${config.APP_NAME}:getPostsByChannel`;
620
+ const variables = {
621
+ channelId,
622
+ limit,
623
+ offset
624
+ };
625
+ const cacheCtx = {
626
+ userId: 'system'
627
+ };
628
+ await this.redisCacheManager.set(cacheKey, variables, result, cacheCtx, PostService_1.CACHE_TTL);
629
+ } catch (cacheError) {
630
+ this.logger.warn(`Failed to cache posts by channel: ${cacheError?.message}`);
631
+ }
632
+ }
633
+ return result;
634
+ } catch (error) {
635
+ this.logger.error('Error getting posts by channel: %o', error);
636
+ return error instanceof Error ? error : new Error('Unknown error occurred while getting posts by channel');
637
+ }
638
+ }
639
+ /**
640
+ * Updates a post
641
+ *
642
+ * @description Updates post content and metadata
643
+ *
644
+ * @param {string} postId - The post identifier
645
+ * @param {Partial<IPostServiceInput>} updates - Update data
646
+ * @returns {Promise<AsDomainType<IPostModel> | Error>} - Updated post or error
647
+ */
648
+ async updatePost(postId, updates) {
649
+ try {
650
+ if (!postId) {
651
+ return new Error('Post ID is required');
652
+ }
653
+ if (!updates || Object.keys(updates).length === 0) {
654
+ return new Error('Updates are required');
655
+ }
656
+ // Get existing post
657
+ const existingPost = await this.get(postId);
658
+ if (!existingPost) {
659
+ return new Error('Post not found');
660
+ }
661
+ // Prepare update data
662
+ const updateData = {
663
+ ...updates,
664
+ updatedAt: new Date()
665
+ }; // Type assertion for compatibility
666
+ // Update the post
667
+ const updatedPost = await this.update(postId, updateData);
668
+ if (!updatedPost) {
669
+ return new Error('Failed to update post');
670
+ }
671
+ this.logger.debug('Post updated successfully', {
672
+ postId,
673
+ updates: Object.keys(updates)
674
+ });
675
+ return updatedPost;
676
+ } catch (error) {
677
+ this.logger.error('Error updating post: %o', error);
678
+ return error instanceof Error ? error : new Error('Unknown error occurred while updating post');
679
+ }
680
+ }
681
+ /**
682
+ * Deletes a post
683
+ *
684
+ * @description Soft deletes a post and its associated data
685
+ *
686
+ * @param {string} postId - The post identifier
687
+ * @returns {Promise<boolean | Error>} - Success status or error
688
+ */
689
+ async deletePost(postId) {
690
+ try {
691
+ if (!postId) {
692
+ return new Error('Post ID is required');
693
+ }
694
+ // Get post to verify it exists
695
+ const post = await this.get(postId);
696
+ if (!post) {
697
+ return new Error('Post not found');
698
+ }
699
+ // Soft delete the post
700
+ const success = await this.repository.delete({
701
+ id: postId
702
+ });
703
+ if (success) {
704
+ this.logger.debug('Post deleted successfully', {
705
+ postId
706
+ });
707
+ try {
708
+ const channelId = post?.channel?.toString?.();
709
+ if (channelId) {
710
+ await this.invalidatePostCachesByChannel(String(channelId));
711
+ }
712
+ } catch (cacheError) {
713
+ this.logger.warn('Cache invalidation error after deletePost: %o', cacheError);
714
+ }
715
+ }
716
+ return success;
717
+ } catch (error) {
718
+ this.logger.error('Error deleting post: %o', error);
719
+ return error instanceof Error ? error : new Error('Unknown error occurred while deleting post');
720
+ }
137
721
  }
722
+ /**
723
+ * Sends Expo push notification for a post
724
+ *
725
+ * @description Handles push notification logic for new posts
726
+ *
727
+ * @param {any} post - The post data
728
+ * @private
729
+ */
138
730
  async sendExpoNotification(post) {
139
- if (post?.props?.notificationParams && (!post?.type || post?.type !== 'ALERT')) {
140
- await this.messengerNotificationService.sendExpoNotificationOnPost(post);
141
- } else if (!post?.type || post?.type !== 'ALERT') {
142
- const notificationData = {
143
- url: config.INBOX_MESSEGE_PATH,
144
- params: {
145
- channelId: post?.channel,
146
- hideTabBar: true
147
- },
148
- screen: 'DialogMessages',
149
- other: {
150
- sound: 'default'
731
+ try {
732
+ if (post?.props?.notificationParams && Object.keys(post?.props?.notificationParams).length > 0 && (!post?.type || post?.type !== 'ALERT')) {
733
+ await this.messengerNotificationService.sendExpoNotificationOnPost(post);
734
+ } else if (!post?.type || post?.type !== 'ALERT') {
735
+ const notificationData = {
736
+ url: config.INBOX_MESSEGE_PATH,
737
+ params: {
738
+ channelId: post?.channel,
739
+ hideTabBar: true
740
+ },
741
+ screen: 'DialogMessages',
742
+ other: {
743
+ sound: 'default'
744
+ }
745
+ };
746
+ await this.messengerNotificationService.sendExpoNotificationOnPost(post, notificationData);
747
+ }
748
+ this.logger.debug('Expo notification sent', {
749
+ postId: post?.id
750
+ });
751
+ } catch (error) {
752
+ this.logger.error('Error sending Expo notification: %o', error);
753
+ // Don't throw error as notification failure shouldn't break post creation
754
+ }
755
+ }
756
+ /**
757
+ * Invalidates cache entries related to a channel's posts
758
+ * @param channelId - Channel ID
759
+ */
760
+ async invalidatePostCachesByChannel(channelId) {
761
+ if (!this.redisCacheManager || !channelId) {
762
+ return;
763
+ }
764
+ try {
765
+ // Invalidate last post by channel
766
+ await this.redisCacheManager.del(`${config.APP_NAME}:getLastPostByChannel`, {
767
+ channelId
768
+ }, {
769
+ userId: 'system'
770
+ }, true);
771
+ // Invalidate posts by channel with any pagination (wildcard delete)
772
+ await this.redisCacheManager.del(`${config.APP_NAME}:getPostsByChannel`, {
773
+ channelId
774
+ }, {
775
+ userId: 'system'
776
+ }, true);
777
+ this.logger.debug(`Invalidated post caches for channel ${channelId}`);
778
+ } catch (error) {
779
+ this.logger.warn(`Error invalidating post caches: ${error?.message}`);
780
+ }
781
+ }
782
+ async getPreviousMessagesByProjectId(projectId, limit) {
783
+ // Get total count first, then calculate skip to get the latest messages
784
+ const totalCount = await this.repository.count({
785
+ props: {
786
+ projectId: projectId?.toString?.()
787
+ }
788
+ });
789
+ const skip = Math.max(0, totalCount - limit);
790
+ const messages = await this.repository.getAll({
791
+ criteria: {
792
+ props: {
793
+ projectId: projectId?.toString?.()
151
794
  }
795
+ },
796
+ limit,
797
+ skip
798
+ });
799
+ const formattedMessages = [];
800
+ for (const message of messages) {
801
+ const messageData = {
802
+ type: 'text',
803
+ role: message.props.role === AiAgentMessageRole.ASSISTANT ? 'assistant' : 'user',
804
+ content: message.message
152
805
  };
153
- await this.messengerNotificationService.sendExpoNotificationOnPost(post, notificationData);
806
+ formattedMessages.push(messageData);
807
+ }
808
+ // Return in reverse chronological order (newest to oldest)
809
+ return formattedMessages;
810
+ }
811
+ async saveCodeAgentResult(request, sandboxUrl, fragmentTitle, responseContent, files, summary, isError, canvasLayers) {
812
+ const {
813
+ projectId,
814
+ owner,
815
+ orgName,
816
+ modelConfig
817
+ } = request;
818
+ // Validate required fields for fragment creation
819
+ // if (!owner || !orgName) {
820
+ // this.logger.error('Missing required fields for fragment creation', {
821
+ // owner: owner || 'undefined',
822
+ // orgName: orgName || 'undefined',
823
+ // projectId,
824
+ // });
825
+ // throw new Error('Owner and orgName are required for fragment creation');
826
+ // }
827
+ if (isError) {
828
+ try {
829
+ console.log('Creating error message');
830
+ const errorMessage = await this.repository.create({
831
+ message: 'Something went wrong. Please try again.',
832
+ type: AiAgentMessageType.ERROR,
833
+ editedBy: owner,
834
+ author: owner,
835
+ props: {
836
+ role: AiAgentMessageRole.ASSISTANT,
837
+ projectId: projectId?.toString?.(),
838
+ template: modelConfig?.template || 'nextjs',
839
+ fragment: {
840
+ sandboxUrl,
841
+ title: fragmentTitle || 'Something went wrong. Please try again.',
842
+ files,
843
+ owner,
844
+ orgName,
845
+ canvasLayers,
846
+ summary
847
+ }
848
+ }
849
+ });
850
+ console.log('Error message created successfully:', errorMessage.id);
851
+ // this.pubsub.publish(`POST_CREATED.${projectId}`, errorMessage);
852
+ return errorMessage;
853
+ } catch (errorCreationFailed) {
854
+ console.error('Error message creation failed:', errorCreationFailed);
855
+ throw errorCreationFailed;
856
+ }
857
+ }
858
+ // Create assistant message with fragment
859
+ let messageDoc;
860
+ try {
861
+ console.log('Creating assistant message');
862
+ messageDoc = await this.repository.create({
863
+ message: responseContent || 'Code generated successfully',
864
+ type: AiAgentMessageType.RESULT,
865
+ editedBy: owner,
866
+ author: owner,
867
+ props: {
868
+ role: AiAgentMessageRole.ASSISTANT,
869
+ projectId: projectId?.toString?.(),
870
+ template: modelConfig?.template || 'nextjs',
871
+ fragment: {
872
+ sandboxUrl,
873
+ title: fragmentTitle || 'Generated Page',
874
+ files,
875
+ owner,
876
+ orgName,
877
+ canvasLayers,
878
+ summary
879
+ }
880
+ }
881
+ });
882
+ } catch (messageError) {
883
+ console.error('Assistant message creation failed:', messageError);
884
+ throw messageError;
154
885
  }
886
+ return messageDoc;
155
887
  }
156
- // eslint-disable-next-line class-methods-use-this
157
- createPostWithPostThread(data) {
158
- throw new Error('Not implemented');
888
+ extractSandboxId(sandboxUrl) {
889
+ try {
890
+ // Handle format: https://3000-{sandboxId}.yarntra.ai
891
+ const url = new URL(sandboxUrl);
892
+ const hostname = url.hostname;
893
+ // Extract sandbox ID from hostname format: 3000-{sandboxId}.yarntra.ai
894
+ if (hostname.includes('yarntra.ai')) {
895
+ const match = hostname.match(/^3000-(.+)\.yarntra\.ai$/);
896
+ return match ? match[1] : null;
897
+ }
898
+ // Fallback: try extracting from path (for other URL formats)
899
+ const pathParts = url.pathname.split('/');
900
+ return pathParts[pathParts.length - 1] || null;
901
+ } catch (error) {
902
+ console.error('Error extracting sandbox ID from URL:', sandboxUrl, error);
903
+ return null;
904
+ }
905
+ }
906
+ async updateSandboxFile(projectId, messageId, filePath, content) {
907
+ try {
908
+ const post = await this.get(messageId);
909
+ if (!post) {
910
+ throw new Error('Message not found');
911
+ }
912
+ if (!post) {
913
+ return {
914
+ success: false,
915
+ message: `Post not found with ID: ${messageId}`,
916
+ filePath,
917
+ timestamp: new Date().toISOString(),
918
+ syncStatus: 'error'
919
+ };
920
+ }
921
+ // Extract sandbox ID from URL (assuming format like e2b.dev/sandbox/{sandboxId})
922
+ const sandboxId = this.extractSandboxId(post?.props?.fragment?.sandboxUrl);
923
+ if (!sandboxId) {
924
+ return {
925
+ success: false,
926
+ message: 'Invalid sandbox URL',
927
+ filePath,
928
+ timestamp: new Date().toISOString(),
929
+ syncStatus: 'error'
930
+ };
931
+ }
932
+ // Connect to E2B sandbox and update file
933
+ const sandbox = await getSandbox(sandboxId);
934
+ await sandbox.files.write(filePath, content);
935
+ // Update fragment with new file content and version
936
+ const updatedFiles = {
937
+ ...post?.props?.fragment?.files,
938
+ [filePath]: content
939
+ };
940
+ const fileVersions = {
941
+ ...(post?.props?.fragment?.fileVersions ?? {}),
942
+ [filePath]: Date.now()
943
+ };
944
+ await this.update(messageId, {
945
+ props: {
946
+ fragment: {
947
+ files: updatedFiles,
948
+ fileVersions,
949
+ syncStatus: 'synced'
950
+ }
951
+ }
952
+ });
953
+ const timestamp = new Date().toISOString();
954
+ // Publish file update event via GraphQL subscription
955
+ await this.pubsub.publish('FILE_UPDATED', {
956
+ fileUpdated: {
957
+ projectId,
958
+ messageId,
959
+ // Use the original fragmentId (messageId from frontend)
960
+ filePath,
961
+ content,
962
+ operation: 'update',
963
+ timestamp,
964
+ syncStatus: 'synced'
965
+ }
966
+ });
967
+ return {
968
+ success: true,
969
+ message: 'File updated successfully',
970
+ filePath,
971
+ timestamp,
972
+ syncStatus: 'synced'
973
+ };
974
+ } catch (error) {
975
+ console.error('Error updating sandbox file:', error);
976
+ // Update sync status to error
977
+ const post = await this.get(messageId);
978
+ if (post) {
979
+ await this.update(messageId, {
980
+ props: {
981
+ fragment: {
982
+ syncStatus: 'error'
983
+ }
984
+ }
985
+ });
986
+ }
987
+ return {
988
+ success: false,
989
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
990
+ filePath,
991
+ timestamp: new Date().toISOString(),
992
+ syncStatus: 'error'
993
+ };
994
+ }
159
995
  }
160
996
  };
161
- PostService = PostService_1 = __decorate([__param(0, inject(SERVER_TYPES.PostRepository)), __param(1, inject(SERVER_TYPES.ChannelRepository)), __param(2, inject(SERVER_TYPES.FileInfoService)), __param(3, inject(SERVER_TYPES.MessengerNotificationService)), __param(4, inject(CommonType.MOLECULER_BROKER)), __param(5, inject('PubSub')), __param(6, inject('Logger')), __metadata("design:paramtypes", [PostRepository, ChannelRepository, Object, Object, ServiceBroker, PubSubEngine, Object])], PostService);export{PostService};//# sourceMappingURL=post-service.js.map
997
+ PostService = PostService_1 = __decorate([__param(0, inject(SERVER_TYPES.PostRepository)), __param(1, inject(SERVER_TYPES.ChannelRepository)), __param(2, inject(SERVER_TYPES.FileInfoService)), __param(3, inject(SERVER_TYPES.MessengerNotificationService)), __param(4, inject(SERVER_TYPES.AiFragmentService)), __param(5, inject(CommonType.MOLECULER_BROKER)), __param(6, inject('PubSub')), __param(7, inject(SERVER_TYPES.IRedisCacheManager)), __param(8, inject(CommonType.REDIS_CLIENT)), __param(9, inject('Logger')), __metadata("design:paramtypes", [PostRepository, ChannelRepository, Object, Object, Object, ServiceBroker, PubSubEngine, Object, Function, Object])], PostService);export{PostService};//# sourceMappingURL=post-service.js.map