@stacksjs/ts-cloud-core 0.1.1

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 (251) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +321 -0
  3. package/package.json +31 -0
  4. package/src/advanced-features.test.ts +465 -0
  5. package/src/aws/cloudformation.ts +421 -0
  6. package/src/aws/cloudfront.ts +158 -0
  7. package/src/aws/credentials.test.ts +132 -0
  8. package/src/aws/credentials.ts +545 -0
  9. package/src/aws/index.ts +87 -0
  10. package/src/aws/s3.test.ts +188 -0
  11. package/src/aws/s3.ts +1088 -0
  12. package/src/aws/signature.test.ts +670 -0
  13. package/src/aws/signature.ts +1155 -0
  14. package/src/backup/disaster-recovery.test.ts +726 -0
  15. package/src/backup/disaster-recovery.ts +500 -0
  16. package/src/backup/index.ts +34 -0
  17. package/src/backup/manager.test.ts +498 -0
  18. package/src/backup/manager.ts +432 -0
  19. package/src/cicd/circleci.ts +430 -0
  20. package/src/cicd/github-actions.ts +424 -0
  21. package/src/cicd/gitlab-ci.ts +255 -0
  22. package/src/cicd/index.ts +8 -0
  23. package/src/cli/history.ts +396 -0
  24. package/src/cli/index.ts +10 -0
  25. package/src/cli/progress.ts +458 -0
  26. package/src/cli/repl.ts +454 -0
  27. package/src/cli/suggestions.ts +327 -0
  28. package/src/cli/table.test.ts +319 -0
  29. package/src/cli/table.ts +332 -0
  30. package/src/cloudformation/builder.test.ts +327 -0
  31. package/src/cloudformation/builder.ts +378 -0
  32. package/src/cloudformation/builders/api-gateway.ts +449 -0
  33. package/src/cloudformation/builders/cache.ts +334 -0
  34. package/src/cloudformation/builders/cdn.ts +278 -0
  35. package/src/cloudformation/builders/compute.ts +485 -0
  36. package/src/cloudformation/builders/database.ts +392 -0
  37. package/src/cloudformation/builders/functions.ts +343 -0
  38. package/src/cloudformation/builders/messaging.ts +140 -0
  39. package/src/cloudformation/builders/monitoring.ts +300 -0
  40. package/src/cloudformation/builders/network.ts +264 -0
  41. package/src/cloudformation/builders/queue.ts +147 -0
  42. package/src/cloudformation/builders/security.ts +399 -0
  43. package/src/cloudformation/builders/storage.ts +285 -0
  44. package/src/cloudformation/index.ts +30 -0
  45. package/src/cloudformation/types.ts +173 -0
  46. package/src/compliance/aws-config.ts +543 -0
  47. package/src/compliance/cloudtrail.ts +376 -0
  48. package/src/compliance/compliance.test.ts +423 -0
  49. package/src/compliance/guardduty.ts +446 -0
  50. package/src/compliance/index.ts +66 -0
  51. package/src/compliance/security-hub.ts +456 -0
  52. package/src/containers/build-optimization.ts +416 -0
  53. package/src/containers/containers.test.ts +508 -0
  54. package/src/containers/image-scanning.ts +360 -0
  55. package/src/containers/index.ts +9 -0
  56. package/src/containers/registry.ts +293 -0
  57. package/src/containers/service-mesh.ts +520 -0
  58. package/src/database/database.test.ts +762 -0
  59. package/src/database/index.ts +9 -0
  60. package/src/database/migrations.ts +444 -0
  61. package/src/database/performance.ts +528 -0
  62. package/src/database/replicas.ts +534 -0
  63. package/src/database/users.ts +494 -0
  64. package/src/dependency-graph.ts +143 -0
  65. package/src/deployment/ab-testing.ts +582 -0
  66. package/src/deployment/blue-green.ts +452 -0
  67. package/src/deployment/canary.ts +500 -0
  68. package/src/deployment/deployment.test.ts +526 -0
  69. package/src/deployment/index.ts +61 -0
  70. package/src/deployment/progressive.ts +62 -0
  71. package/src/dns/dns.test.ts +641 -0
  72. package/src/dns/dnssec.ts +315 -0
  73. package/src/dns/index.ts +8 -0
  74. package/src/dns/resolver.ts +496 -0
  75. package/src/dns/routing.ts +593 -0
  76. package/src/email/advanced/analytics.ts +445 -0
  77. package/src/email/advanced/index.ts +11 -0
  78. package/src/email/advanced/rules.ts +465 -0
  79. package/src/email/advanced/scheduling.ts +352 -0
  80. package/src/email/advanced/search.ts +412 -0
  81. package/src/email/advanced/shared-mailboxes.ts +404 -0
  82. package/src/email/advanced/templates.ts +455 -0
  83. package/src/email/advanced/threading.ts +281 -0
  84. package/src/email/analytics.ts +467 -0
  85. package/src/email/bounce-handling.ts +425 -0
  86. package/src/email/email.test.ts +431 -0
  87. package/src/email/handlers/__tests__/inbound.test.ts +38 -0
  88. package/src/email/handlers/__tests__/outbound.test.ts +37 -0
  89. package/src/email/handlers/converter.ts +227 -0
  90. package/src/email/handlers/feedback.ts +228 -0
  91. package/src/email/handlers/inbound.ts +169 -0
  92. package/src/email/handlers/outbound.ts +178 -0
  93. package/src/email/index.ts +15 -0
  94. package/src/email/reputation.ts +303 -0
  95. package/src/email/templates.ts +352 -0
  96. package/src/errors/index.test.ts +434 -0
  97. package/src/errors/index.ts +416 -0
  98. package/src/health-checks/index.ts +40 -0
  99. package/src/index.ts +360 -0
  100. package/src/intrinsic-functions.ts +118 -0
  101. package/src/lambda/concurrency.ts +330 -0
  102. package/src/lambda/destinations.ts +345 -0
  103. package/src/lambda/dlq.ts +425 -0
  104. package/src/lambda/index.ts +11 -0
  105. package/src/lambda/lambda.test.ts +840 -0
  106. package/src/lambda/layers.ts +263 -0
  107. package/src/lambda/versions.ts +376 -0
  108. package/src/lambda/vpc.ts +399 -0
  109. package/src/local/config.ts +114 -0
  110. package/src/local/index.ts +6 -0
  111. package/src/local/mock-aws.ts +351 -0
  112. package/src/modules/ai.ts +340 -0
  113. package/src/modules/api.ts +478 -0
  114. package/src/modules/auth.ts +805 -0
  115. package/src/modules/cache.ts +417 -0
  116. package/src/modules/cdn.ts +1062 -0
  117. package/src/modules/communication.ts +1094 -0
  118. package/src/modules/compute.ts +3348 -0
  119. package/src/modules/database.ts +554 -0
  120. package/src/modules/deployment.ts +1079 -0
  121. package/src/modules/dns.ts +337 -0
  122. package/src/modules/email.ts +1538 -0
  123. package/src/modules/filesystem.ts +515 -0
  124. package/src/modules/index.ts +32 -0
  125. package/src/modules/messaging.ts +486 -0
  126. package/src/modules/monitoring.ts +2086 -0
  127. package/src/modules/network.ts +664 -0
  128. package/src/modules/parameter-store.ts +325 -0
  129. package/src/modules/permissions.ts +1081 -0
  130. package/src/modules/phone.ts +494 -0
  131. package/src/modules/queue.ts +1260 -0
  132. package/src/modules/redirects.ts +464 -0
  133. package/src/modules/registry.ts +699 -0
  134. package/src/modules/search.ts +401 -0
  135. package/src/modules/secrets.ts +416 -0
  136. package/src/modules/security.ts +731 -0
  137. package/src/modules/sms.ts +389 -0
  138. package/src/modules/storage.ts +1120 -0
  139. package/src/modules/workflow.ts +680 -0
  140. package/src/multi-account/config.ts +521 -0
  141. package/src/multi-account/index.ts +7 -0
  142. package/src/multi-account/manager.ts +427 -0
  143. package/src/multi-region/cross-region.ts +410 -0
  144. package/src/multi-region/index.ts +8 -0
  145. package/src/multi-region/manager.ts +483 -0
  146. package/src/multi-region/regions.ts +435 -0
  147. package/src/network-security/index.ts +48 -0
  148. package/src/observability/index.ts +9 -0
  149. package/src/observability/logs.ts +522 -0
  150. package/src/observability/metrics.ts +460 -0
  151. package/src/observability/observability.test.ts +782 -0
  152. package/src/observability/synthetics.ts +568 -0
  153. package/src/observability/xray.ts +358 -0
  154. package/src/phone/advanced/analytics.ts +349 -0
  155. package/src/phone/advanced/callbacks.ts +428 -0
  156. package/src/phone/advanced/index.ts +8 -0
  157. package/src/phone/advanced/ivr-builder.ts +504 -0
  158. package/src/phone/advanced/recording.ts +310 -0
  159. package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
  160. package/src/phone/handlers/incoming-call.ts +117 -0
  161. package/src/phone/handlers/missed-call.ts +116 -0
  162. package/src/phone/handlers/voicemail.ts +179 -0
  163. package/src/phone/index.ts +9 -0
  164. package/src/presets/api-backend.ts +134 -0
  165. package/src/presets/data-pipeline.ts +204 -0
  166. package/src/presets/extend.test.ts +295 -0
  167. package/src/presets/extend.ts +297 -0
  168. package/src/presets/fullstack-app.ts +144 -0
  169. package/src/presets/index.ts +27 -0
  170. package/src/presets/jamstack.ts +135 -0
  171. package/src/presets/microservices.ts +167 -0
  172. package/src/presets/ml-api.ts +208 -0
  173. package/src/presets/nodejs-server.ts +104 -0
  174. package/src/presets/nodejs-serverless.ts +114 -0
  175. package/src/presets/realtime-app.ts +184 -0
  176. package/src/presets/static-site.ts +64 -0
  177. package/src/presets/traditional-web-app.ts +339 -0
  178. package/src/presets/wordpress.ts +138 -0
  179. package/src/preview/github.test.ts +249 -0
  180. package/src/preview/github.ts +297 -0
  181. package/src/preview/index.ts +37 -0
  182. package/src/preview/manager.test.ts +440 -0
  183. package/src/preview/manager.ts +326 -0
  184. package/src/preview/notifications.test.ts +582 -0
  185. package/src/preview/notifications.ts +341 -0
  186. package/src/queue/batch-processing.ts +402 -0
  187. package/src/queue/dlq-monitoring.ts +402 -0
  188. package/src/queue/fifo.ts +342 -0
  189. package/src/queue/index.ts +9 -0
  190. package/src/queue/management.ts +428 -0
  191. package/src/queue/queue.test.ts +429 -0
  192. package/src/resource-mgmt/index.ts +39 -0
  193. package/src/resource-naming.ts +62 -0
  194. package/src/s3/index.ts +523 -0
  195. package/src/schema/cloud-config.schema.json +554 -0
  196. package/src/schema/index.ts +68 -0
  197. package/src/security/certificate-manager.ts +492 -0
  198. package/src/security/index.ts +9 -0
  199. package/src/security/scanning.ts +545 -0
  200. package/src/security/secrets-manager.ts +476 -0
  201. package/src/security/secrets-rotation.ts +456 -0
  202. package/src/security/security.test.ts +738 -0
  203. package/src/sms/advanced/ab-testing.ts +389 -0
  204. package/src/sms/advanced/analytics.ts +336 -0
  205. package/src/sms/advanced/campaigns.ts +523 -0
  206. package/src/sms/advanced/chatbot.ts +224 -0
  207. package/src/sms/advanced/index.ts +10 -0
  208. package/src/sms/advanced/link-tracking.ts +248 -0
  209. package/src/sms/advanced/mms.ts +308 -0
  210. package/src/sms/handlers/__tests__/send.test.ts +40 -0
  211. package/src/sms/handlers/delivery-status.ts +133 -0
  212. package/src/sms/handlers/receive.ts +162 -0
  213. package/src/sms/handlers/send.ts +174 -0
  214. package/src/sms/index.ts +9 -0
  215. package/src/stack-diff.ts +389 -0
  216. package/src/static-site/index.ts +85 -0
  217. package/src/template-builder.ts +110 -0
  218. package/src/template-validator.ts +574 -0
  219. package/src/utils/cache.ts +291 -0
  220. package/src/utils/diff.ts +269 -0
  221. package/src/utils/hash.ts +227 -0
  222. package/src/utils/index.ts +8 -0
  223. package/src/utils/parallel.ts +294 -0
  224. package/src/validators/credentials.test.ts +274 -0
  225. package/src/validators/credentials.ts +233 -0
  226. package/src/validators/quotas.test.ts +434 -0
  227. package/src/validators/quotas.ts +217 -0
  228. package/test/ai.test.ts +327 -0
  229. package/test/api.test.ts +511 -0
  230. package/test/auth.test.ts +632 -0
  231. package/test/cache.test.ts +406 -0
  232. package/test/cdn.test.ts +247 -0
  233. package/test/compute.test.ts +861 -0
  234. package/test/database.test.ts +523 -0
  235. package/test/deployment.test.ts +499 -0
  236. package/test/dns.test.ts +270 -0
  237. package/test/email.test.ts +439 -0
  238. package/test/filesystem.test.ts +382 -0
  239. package/test/integration.test.ts +350 -0
  240. package/test/messaging.test.ts +514 -0
  241. package/test/monitoring.test.ts +634 -0
  242. package/test/network.test.ts +425 -0
  243. package/test/permissions.test.ts +488 -0
  244. package/test/queue.test.ts +484 -0
  245. package/test/registry.test.ts +306 -0
  246. package/test/security.test.ts +462 -0
  247. package/test/storage.test.ts +463 -0
  248. package/test/template-validator.test.ts +559 -0
  249. package/test/workflow.test.ts +592 -0
  250. package/tsconfig.json +16 -0
  251. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,224 @@
1
+ /**
2
+ * SMS Chatbot Integration
3
+ *
4
+ * Provides conversational SMS with AI/rule-based responses
5
+ */
6
+
7
+ export interface ChatbotConfig {
8
+ enabled: boolean
9
+ provider: 'rules' | 'bedrock' | 'openai' | 'custom'
10
+ welcomeMessage?: string
11
+ fallbackMessage?: string
12
+ sessionTimeout?: number
13
+ maxTurns?: number
14
+ }
15
+
16
+ export interface ChatSession {
17
+ sessionId: string
18
+ phoneNumber: string
19
+ startedAt: string
20
+ lastMessageAt: string
21
+ turnCount: number
22
+ context: Record<string, any>
23
+ status: 'active' | 'ended' | 'timeout'
24
+ }
25
+
26
+ export interface ChatRule {
27
+ id: string
28
+ priority: number
29
+ patterns: string[]
30
+ response: string
31
+ action?: 'respond' | 'transfer' | 'end' | 'webhook'
32
+ actionParams?: Record<string, any>
33
+ }
34
+
35
+ /**
36
+ * SMS Chatbot Module
37
+ */
38
+ export class SmsChatbot {
39
+ /**
40
+ * Lambda code for chatbot processing
41
+ */
42
+ static ChatbotProcessorCode: string = [
43
+ `const { DynamoDBClient, GetItemCommand, PutItemCommand, UpdateItemCommand, ScanCommand } = require('@aws-sdk/client-dynamodb');`,
44
+ "const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime');",
45
+ "const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');",
46
+ "",
47
+ "const dynamodb = new DynamoDBClient({});",
48
+ "const bedrock = new BedrockRuntimeClient({});",
49
+ "const sns = new SNSClient({});",
50
+ "",
51
+ "const SESSIONS_TABLE = process.env.SESSIONS_TABLE;",
52
+ "const RULES_TABLE = process.env.RULES_TABLE;",
53
+ "const CHATBOT_CONFIG = JSON.parse(process.env.CHATBOT_CONFIG || '{}');",
54
+ "",
55
+ "exports.handler = async (event) => {",
56
+ " console.log('Chatbot event:', JSON.stringify(event, null, 2));",
57
+ " for (const record of event.Records) {",
58
+ " try {",
59
+ " const message = JSON.parse(record.Sns?.Message || record.body || '{}');",
60
+ " const { originationNumber, messageBody } = message;",
61
+ " if (!originationNumber || !messageBody) continue;",
62
+ " const response = await processMessage(originationNumber, messageBody.trim());",
63
+ " await sendResponse(originationNumber, response);",
64
+ " } catch (error) {",
65
+ " console.error('Error:', error);",
66
+ " }",
67
+ " }",
68
+ " return { statusCode: 200 };",
69
+ "};",
70
+ "",
71
+ "async function processMessage(phoneNumber, message) {",
72
+ " const rules = await getRules();",
73
+ " const lowerMessage = message.toLowerCase();",
74
+ " for (const rule of rules) {",
75
+ " for (const pattern of rule.patterns) {",
76
+ " if (new RegExp(pattern, 'i').test(lowerMessage)) {",
77
+ " return rule.response;",
78
+ " }",
79
+ " }",
80
+ " }",
81
+ " return CHATBOT_CONFIG.fallbackMessage || 'Sorry, I did not understand. Please try again.';",
82
+ "}",
83
+ "",
84
+ "async function getRules() {",
85
+ " const result = await dynamodb.send(new ScanCommand({ TableName: RULES_TABLE }));",
86
+ " return (result.Items || []).map(item => ({",
87
+ " patterns: JSON.parse(item.patterns?.S || '[]'),",
88
+ " response: item.response?.S || '',",
89
+ " })).sort((a, b) => (a.priority || 0) - (b.priority || 0));",
90
+ "}",
91
+ "",
92
+ "async function sendResponse(phoneNumber, message) {",
93
+ " await sns.send(new PublishCommand({",
94
+ " PhoneNumber: phoneNumber,",
95
+ " Message: message.substring(0, 1600),",
96
+ " MessageAttributes: {",
97
+ " 'AWS.SNS.SMS.SMSType': { DataType: 'String', StringValue: 'Transactional' },",
98
+ " },",
99
+ " }));",
100
+ "}",
101
+ ].join('\n')
102
+
103
+ /**
104
+ * Create sessions DynamoDB table
105
+ */
106
+ static createSessionsTable(config: { slug: string }): Record<string, any> {
107
+ return {
108
+ [`${config.slug}ChatbotSessionsTable`]: {
109
+ Type: 'AWS::DynamoDB::Table',
110
+ Properties: {
111
+ TableName: `${config.slug}-chatbot-sessions`,
112
+ BillingMode: 'PAY_PER_REQUEST',
113
+ AttributeDefinitions: [
114
+ { AttributeName: 'phoneNumber', AttributeType: 'S' },
115
+ ],
116
+ KeySchema: [
117
+ { AttributeName: 'phoneNumber', KeyType: 'HASH' },
118
+ ],
119
+ TimeToLiveSpecification: {
120
+ AttributeName: 'ttl',
121
+ Enabled: true,
122
+ },
123
+ },
124
+ },
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Create chatbot rules table
130
+ */
131
+ static createRulesTable(config: { slug: string }): Record<string, any> {
132
+ return {
133
+ [`${config.slug}ChatbotRulesTable`]: {
134
+ Type: 'AWS::DynamoDB::Table',
135
+ Properties: {
136
+ TableName: `${config.slug}-chatbot-rules`,
137
+ BillingMode: 'PAY_PER_REQUEST',
138
+ AttributeDefinitions: [
139
+ { AttributeName: 'id', AttributeType: 'S' },
140
+ ],
141
+ KeySchema: [
142
+ { AttributeName: 'id', KeyType: 'HASH' },
143
+ ],
144
+ },
145
+ },
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Create chatbot processor Lambda
151
+ */
152
+ static createChatbotProcessorLambda(config: {
153
+ slug: string
154
+ roleArn: string
155
+ sessionsTable: string
156
+ rulesTable: string
157
+ chatbotConfig: ChatbotConfig
158
+ originationNumber?: string
159
+ }): Record<string, any> {
160
+ return {
161
+ [`${config.slug}ChatbotProcessorLambda`]: {
162
+ Type: 'AWS::Lambda::Function',
163
+ Properties: {
164
+ FunctionName: `${config.slug}-chatbot-processor`,
165
+ Runtime: 'nodejs20.x',
166
+ Handler: 'index.handler',
167
+ Role: config.roleArn,
168
+ Timeout: 30,
169
+ MemorySize: 256,
170
+ Code: {
171
+ ZipFile: SmsChatbot.ChatbotProcessorCode,
172
+ },
173
+ Environment: {
174
+ Variables: {
175
+ SESSIONS_TABLE: config.sessionsTable,
176
+ RULES_TABLE: config.rulesTable,
177
+ CHATBOT_CONFIG: JSON.stringify(config.chatbotConfig),
178
+ ORIGINATION_NUMBER: config.originationNumber || '',
179
+ },
180
+ },
181
+ },
182
+ },
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Built-in chatbot rules
188
+ */
189
+ static readonly DefaultRules: ChatRule[] = [
190
+ {
191
+ id: 'greeting',
192
+ priority: 1,
193
+ patterns: ['^(hi|hello|hey|howdy)$', '^good (morning|afternoon|evening)'],
194
+ response: 'Hello! How can I help you today?',
195
+ },
196
+ {
197
+ id: 'help',
198
+ priority: 2,
199
+ patterns: ['^help$', 'what can you do', 'how does this work'],
200
+ response: 'I can help with: 1) Account info 2) Support 3) Hours. Reply with a number.',
201
+ },
202
+ {
203
+ id: 'hours',
204
+ priority: 3,
205
+ patterns: ['hours', 'open', 'when are you'],
206
+ response: 'We are open Mon-Fri 9am-5pm EST. Closed weekends.',
207
+ },
208
+ {
209
+ id: 'thanks',
210
+ priority: 4,
211
+ patterns: ['^(thanks|thank you|thx)'],
212
+ response: "You're welcome! Is there anything else I can help with?",
213
+ },
214
+ {
215
+ id: 'bye',
216
+ priority: 5,
217
+ patterns: ['^(bye|goodbye|quit|exit|stop)$'],
218
+ response: 'Goodbye! Text us anytime if you need help.',
219
+ action: 'end',
220
+ },
221
+ ]
222
+ }
223
+
224
+ export default SmsChatbot
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SMS Advanced Features
3
+ */
4
+
5
+ export * from './campaigns'
6
+ export * from './analytics'
7
+ export * from './mms'
8
+ export * from './chatbot'
9
+ export * from './link-tracking'
10
+ export * from './ab-testing'
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Link Shortening and Tracking
3
+ *
4
+ * Provides URL shortening and click tracking for SMS
5
+ */
6
+
7
+ export interface ShortenedLink {
8
+ id: string
9
+ originalUrl: string
10
+ shortUrl: string
11
+ campaignId?: string
12
+ messageId?: string
13
+ clicks: number
14
+ uniqueClicks: number
15
+ createdAt: string
16
+ expiresAt?: string
17
+ }
18
+
19
+ export interface LinkClick {
20
+ linkId: string
21
+ clickedAt: string
22
+ userAgent?: string
23
+ ipAddress?: string
24
+ country?: string
25
+ device?: string
26
+ }
27
+
28
+ /**
29
+ * Link Tracking Module
30
+ */
31
+ export class LinkTracking {
32
+ /**
33
+ * Lambda code for link shortening
34
+ */
35
+ static LinkShortenerCode = `
36
+ const { DynamoDBClient, PutItemCommand, GetItemCommand, UpdateItemCommand } = require('@aws-sdk/client-dynamodb');
37
+ const crypto = require('crypto');
38
+
39
+ const dynamodb = new DynamoDBClient({});
40
+ const LINKS_TABLE = process.env.LINKS_TABLE;
41
+ const SHORT_DOMAIN = process.env.SHORT_DOMAIN;
42
+
43
+ exports.handler = async (event) => {
44
+ console.log('Link shortener event:', JSON.stringify(event, null, 2));
45
+
46
+ const { httpMethod, body, pathParameters } = event;
47
+
48
+ try {
49
+ if (httpMethod === 'POST') {
50
+ // Create short link
51
+ const data = JSON.parse(body || '{}');
52
+ return await createShortLink(data);
53
+ } else if (httpMethod === 'GET' && pathParameters?.id) {
54
+ // Redirect to original URL
55
+ return await handleRedirect(pathParameters.id, event);
56
+ }
57
+
58
+ return { statusCode: 405, body: 'Method not allowed' };
59
+ } catch (error) {
60
+ console.error('Error:', error);
61
+ return { statusCode: 500, body: JSON.stringify({ error: error.message }) };
62
+ }
63
+ };
64
+
65
+ async function createShortLink(data) {
66
+ const { url, campaignId, messageId, expiresIn } = data;
67
+
68
+ if (!url) {
69
+ return { statusCode: 400, body: JSON.stringify({ error: 'URL is required' }) };
70
+ }
71
+
72
+ // Generate short ID
73
+ const id = generateShortId();
74
+ const now = new Date().toISOString();
75
+ const expiresAt = expiresIn
76
+ ? new Date(Date.now() + expiresIn * 1000).toISOString()
77
+ : null;
78
+
79
+ await dynamodb.send(new PutItemCommand({
80
+ TableName: LINKS_TABLE,
81
+ Item: {
82
+ id: { S: id },
83
+ originalUrl: { S: url },
84
+ campaignId: { S: campaignId || '' },
85
+ messageId: { S: messageId || '' },
86
+ clicks: { N: '0' },
87
+ uniqueClicks: { N: '0' },
88
+ visitors: { SS: ['_placeholder'] }, // DynamoDB requires non-empty set
89
+ createdAt: { S: now },
90
+ ...(expiresAt && { expiresAt: { S: expiresAt } }),
91
+ ttl: { N: String(Math.floor(Date.now() / 1000) + 90 * 24 * 60 * 60) },
92
+ },
93
+ }));
94
+
95
+ const shortUrl = SHORT_DOMAIN ? 'https://' + SHORT_DOMAIN + '/l/' + id : '/l/' + id;
96
+
97
+ return {
98
+ statusCode: 201,
99
+ headers: { 'Content-Type': 'application/json' },
100
+ body: JSON.stringify({
101
+ id,
102
+ shortUrl,
103
+ originalUrl: url,
104
+ createdAt: now,
105
+ }),
106
+ };
107
+ }
108
+
109
+ async function handleRedirect(id, event) {
110
+ const result = await dynamodb.send(new GetItemCommand({
111
+ TableName: LINKS_TABLE,
112
+ Key: { id: { S: id } },
113
+ }));
114
+
115
+ if (!result.Item) {
116
+ return { statusCode: 404, body: 'Link not found' };
117
+ }
118
+
119
+ const link = result.Item;
120
+
121
+ // Check expiration
122
+ if (link.expiresAt?.S && new Date(link.expiresAt.S) < new Date()) {
123
+ return { statusCode: 410, body: 'Link has expired' };
124
+ }
125
+
126
+ const originalUrl = link.originalUrl.S;
127
+
128
+ // Track click
129
+ const ipAddress = event.requestContext?.identity?.sourceIp || 'unknown';
130
+ const userAgent = event.headers?.['user-agent'] || '';
131
+ const visitorId = crypto.createHash('md5').update(ipAddress + userAgent).digest('hex').substring(0, 8);
132
+
133
+ try {
134
+ await dynamodb.send(new UpdateItemCommand({
135
+ TableName: LINKS_TABLE,
136
+ Key: { id: { S: id } },
137
+ UpdateExpression: 'SET clicks = clicks + :one, visitors = list_append(if_not_exists(visitors, :empty), :visitor)',
138
+ ExpressionAttributeValues: {
139
+ ':one': { N: '1' },
140
+ ':empty': { L: [] },
141
+ ':visitor': { L: [{ S: visitorId }] },
142
+ },
143
+ }));
144
+ } catch (e) {
145
+ console.error('Error tracking click:', e);
146
+ }
147
+
148
+ return {
149
+ statusCode: 302,
150
+ headers: {
151
+ 'Location': originalUrl,
152
+ 'Cache-Control': 'no-store',
153
+ },
154
+ body: '',
155
+ };
156
+ }
157
+
158
+ function generateShortId() {
159
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
160
+ let id = '';
161
+ for (let i = 0; i < 6; i++) {
162
+ id += chars.charAt(Math.floor(Math.random() * chars.length));
163
+ }
164
+ return id;
165
+ }
166
+ `
167
+
168
+ /**
169
+ * Create links DynamoDB table
170
+ */
171
+ static createLinksTable(config: { slug: string }): Record<string, any> {
172
+ return {
173
+ [`${config.slug}ShortLinksTable`]: {
174
+ Type: 'AWS::DynamoDB::Table',
175
+ Properties: {
176
+ TableName: `${config.slug}-short-links`,
177
+ BillingMode: 'PAY_PER_REQUEST',
178
+ AttributeDefinitions: [
179
+ { AttributeName: 'id', AttributeType: 'S' },
180
+ ],
181
+ KeySchema: [
182
+ { AttributeName: 'id', KeyType: 'HASH' },
183
+ ],
184
+ TimeToLiveSpecification: {
185
+ AttributeName: 'ttl',
186
+ Enabled: true,
187
+ },
188
+ },
189
+ },
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Create link shortener Lambda
195
+ */
196
+ static createLinkShortenerLambda(config: {
197
+ slug: string
198
+ roleArn: string
199
+ linksTable: string
200
+ shortDomain?: string
201
+ }): Record<string, any> {
202
+ return {
203
+ [`${config.slug}LinkShortenerLambda`]: {
204
+ Type: 'AWS::Lambda::Function',
205
+ Properties: {
206
+ FunctionName: `${config.slug}-link-shortener`,
207
+ Runtime: 'nodejs20.x',
208
+ Handler: 'index.handler',
209
+ Role: config.roleArn,
210
+ Timeout: 10,
211
+ MemorySize: 128,
212
+ Code: {
213
+ ZipFile: LinkTracking.LinkShortenerCode,
214
+ },
215
+ Environment: {
216
+ Variables: {
217
+ LINKS_TABLE: config.linksTable,
218
+ SHORT_DOMAIN: config.shortDomain || '',
219
+ },
220
+ },
221
+ },
222
+ },
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Shorten URLs in message text
228
+ */
229
+ static shortenUrlsInMessage(message: string, shortDomain: string, linkIdPrefix: string): {
230
+ message: string
231
+ links: Array<{ original: string; short: string; id: string }>
232
+ } {
233
+ const urlRegex = /(https?:\/\/[^\s]+)/g
234
+ const links: Array<{ original: string; short: string; id: string }> = []
235
+ let index = 0
236
+
237
+ const shortenedMessage = message.replace(urlRegex, (url) => {
238
+ const id = `${linkIdPrefix}-${index++}`
239
+ const shortUrl = `https://${shortDomain}/l/${id}`
240
+ links.push({ original: url, short: shortUrl, id })
241
+ return shortUrl
242
+ })
243
+
244
+ return { message: shortenedMessage, links }
245
+ }
246
+ }
247
+
248
+ export default LinkTracking