@hasna/microservices 0.0.4 → 0.0.6

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 (57) hide show
  1. package/bin/index.js +9 -1
  2. package/bin/mcp.js +9 -1
  3. package/dist/index.js +9 -1
  4. package/microservices/microservice-ads/src/cli/index.ts +198 -0
  5. package/microservices/microservice-ads/src/db/campaigns.ts +304 -0
  6. package/microservices/microservice-ads/src/mcp/index.ts +160 -0
  7. package/microservices/microservice-company/package.json +27 -0
  8. package/microservices/microservice-company/src/cli/index.ts +1126 -0
  9. package/microservices/microservice-company/src/db/company.ts +854 -0
  10. package/microservices/microservice-company/src/db/database.ts +93 -0
  11. package/microservices/microservice-company/src/db/migrations.ts +214 -0
  12. package/microservices/microservice-company/src/db/workflow-migrations.ts +44 -0
  13. package/microservices/microservice-company/src/index.ts +60 -0
  14. package/microservices/microservice-company/src/lib/audit.ts +168 -0
  15. package/microservices/microservice-company/src/lib/finance.ts +299 -0
  16. package/microservices/microservice-company/src/lib/settings.ts +85 -0
  17. package/microservices/microservice-company/src/lib/workflows.ts +698 -0
  18. package/microservices/microservice-company/src/mcp/index.ts +991 -0
  19. package/microservices/microservice-contracts/src/cli/index.ts +410 -23
  20. package/microservices/microservice-contracts/src/db/contracts.ts +430 -1
  21. package/microservices/microservice-contracts/src/db/migrations.ts +83 -0
  22. package/microservices/microservice-contracts/src/mcp/index.ts +312 -3
  23. package/microservices/microservice-domains/src/cli/index.ts +673 -0
  24. package/microservices/microservice-domains/src/db/domains.ts +613 -0
  25. package/microservices/microservice-domains/src/index.ts +21 -0
  26. package/microservices/microservice-domains/src/lib/brandsight.ts +285 -0
  27. package/microservices/microservice-domains/src/lib/godaddy.ts +328 -0
  28. package/microservices/microservice-domains/src/lib/namecheap.ts +474 -0
  29. package/microservices/microservice-domains/src/lib/registrar.ts +355 -0
  30. package/microservices/microservice-domains/src/mcp/index.ts +413 -0
  31. package/microservices/microservice-hiring/src/cli/index.ts +318 -8
  32. package/microservices/microservice-hiring/src/db/hiring.ts +503 -0
  33. package/microservices/microservice-hiring/src/db/migrations.ts +21 -0
  34. package/microservices/microservice-hiring/src/index.ts +29 -0
  35. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  36. package/microservices/microservice-hiring/src/mcp/index.ts +245 -0
  37. package/microservices/microservice-payments/src/cli/index.ts +255 -3
  38. package/microservices/microservice-payments/src/db/migrations.ts +18 -0
  39. package/microservices/microservice-payments/src/db/payments.ts +552 -0
  40. package/microservices/microservice-payments/src/mcp/index.ts +223 -0
  41. package/microservices/microservice-payroll/src/cli/index.ts +269 -0
  42. package/microservices/microservice-payroll/src/db/migrations.ts +26 -0
  43. package/microservices/microservice-payroll/src/db/payroll.ts +636 -0
  44. package/microservices/microservice-payroll/src/mcp/index.ts +246 -0
  45. package/microservices/microservice-shipping/src/cli/index.ts +211 -3
  46. package/microservices/microservice-shipping/src/db/migrations.ts +8 -0
  47. package/microservices/microservice-shipping/src/db/shipping.ts +453 -3
  48. package/microservices/microservice-shipping/src/mcp/index.ts +149 -1
  49. package/microservices/microservice-social/src/cli/index.ts +244 -2
  50. package/microservices/microservice-social/src/db/migrations.ts +33 -0
  51. package/microservices/microservice-social/src/db/social.ts +378 -4
  52. package/microservices/microservice-social/src/mcp/index.ts +221 -1
  53. package/microservices/microservice-subscriptions/src/cli/index.ts +315 -0
  54. package/microservices/microservice-subscriptions/src/db/migrations.ts +68 -0
  55. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +567 -3
  56. package/microservices/microservice-subscriptions/src/mcp/index.ts +267 -1
  57. package/package.json +1 -1
@@ -0,0 +1,474 @@
1
+ /**
2
+ * Namecheap API integration for domain management
3
+ *
4
+ * Requires environment variables:
5
+ * NAMECHEAP_API_KEY — API key from Namecheap
6
+ * NAMECHEAP_USERNAME — Namecheap account username
7
+ * NAMECHEAP_CLIENT_IP — Whitelisted client IP address
8
+ */
9
+
10
+ // ============================================================
11
+ // Types
12
+ // ============================================================
13
+
14
+ export interface NamecheapConfig {
15
+ apiKey: string;
16
+ username: string;
17
+ clientIp: string;
18
+ sandbox?: boolean;
19
+ }
20
+
21
+ export interface NamecheapDomain {
22
+ domain: string;
23
+ expiry: string;
24
+ autoRenew: boolean;
25
+ isLocked: boolean;
26
+ }
27
+
28
+ export interface NamecheapDomainInfo {
29
+ domain: string;
30
+ registrar: string;
31
+ created: string;
32
+ expires: string;
33
+ nameservers: string[];
34
+ }
35
+
36
+ export interface NamecheapDnsRecord {
37
+ hostId?: string;
38
+ type: string;
39
+ name: string;
40
+ address: string;
41
+ mxPref?: number;
42
+ ttl: number;
43
+ }
44
+
45
+ export interface NamecheapAvailability {
46
+ domain: string;
47
+ available: boolean;
48
+ }
49
+
50
+ export interface NamecheapRenewResult {
51
+ domain: string;
52
+ success: boolean;
53
+ transactionId?: string;
54
+ chargedAmount?: string;
55
+ orderId?: string;
56
+ }
57
+
58
+ export interface NamecheapSyncResult {
59
+ synced: number;
60
+ errors: string[];
61
+ domains: string[];
62
+ }
63
+
64
+ // ============================================================
65
+ // Configuration
66
+ // ============================================================
67
+
68
+ const API_BASE = "https://api.namecheap.com/xml.response";
69
+ const SANDBOX_BASE = "https://api.sandbox.namecheap.com/xml.response";
70
+
71
+ export function getConfig(): NamecheapConfig {
72
+ const apiKey = process.env["NAMECHEAP_API_KEY"];
73
+ const username = process.env["NAMECHEAP_USERNAME"];
74
+ const clientIp = process.env["NAMECHEAP_CLIENT_IP"];
75
+
76
+ if (!apiKey) throw new Error("NAMECHEAP_API_KEY environment variable is not set");
77
+ if (!username) throw new Error("NAMECHEAP_USERNAME environment variable is not set");
78
+ if (!clientIp) throw new Error("NAMECHEAP_CLIENT_IP environment variable is not set");
79
+
80
+ return {
81
+ apiKey,
82
+ username,
83
+ clientIp,
84
+ sandbox: process.env["NAMECHEAP_SANDBOX"] === "true",
85
+ };
86
+ }
87
+
88
+ // ============================================================
89
+ // XML Parsing Helpers (regex-based, no dependencies)
90
+ // ============================================================
91
+
92
+ export function extractTag(xml: string, tag: string): string | null {
93
+ const regex = new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`, "i");
94
+ const match = xml.match(regex);
95
+ return match ? match[1].trim() : null;
96
+ }
97
+
98
+ export function extractAttribute(xml: string, tag: string, attr: string): string | null {
99
+ const regex = new RegExp(`<${tag}\\s[^>]*${attr}="([^"]*)"`, "i");
100
+ const match = xml.match(regex);
101
+ return match ? match[1] : null;
102
+ }
103
+
104
+ export function extractAllTags(xml: string, tag: string): string[] {
105
+ const regex = new RegExp(`<${tag}(?:\\s[^>]*)?\\/?>(?:([^<]*)<\\/${tag}>)?`, "gi");
106
+ const results: string[] = [];
107
+ let match;
108
+ while ((match = regex.exec(xml)) !== null) {
109
+ // Only match exact tag name — skip tags like <DomainListResult> when searching for <Domain>
110
+ const fullMatch = match[0];
111
+ const tagNameCheck = new RegExp(`^<${tag}(?:\\s|\\/>|>)`, "i");
112
+ if (tagNameCheck.test(fullMatch)) {
113
+ results.push(fullMatch);
114
+ }
115
+ }
116
+ return results;
117
+ }
118
+
119
+ export function extractAttributeFromElement(element: string, attr: string): string | null {
120
+ const regex = new RegExp(`${attr}="([^"]*)"`, "i");
121
+ const match = element.match(regex);
122
+ return match ? match[1] : null;
123
+ }
124
+
125
+ export function checkApiError(xml: string): void {
126
+ const status = extractAttribute(xml, "ApiResponse", "Status");
127
+ if (status === "ERROR") {
128
+ const errorMsg = extractTag(xml, "Message") || extractTag(xml, "Err") || "Unknown Namecheap API error";
129
+ const errorNumber = extractAttribute(xml, "Error", "Number") || extractAttribute(xml, "Err", "Number");
130
+ throw new Error(`Namecheap API error${errorNumber ? ` (${errorNumber})` : ""}: ${errorMsg}`);
131
+ }
132
+ }
133
+
134
+ // ============================================================
135
+ // HTTP Layer
136
+ // ============================================================
137
+
138
+ export function buildUrl(command: string, config: NamecheapConfig, extraParams?: Record<string, string>): string {
139
+ const base = config.sandbox ? SANDBOX_BASE : API_BASE;
140
+ const params = new URLSearchParams({
141
+ ApiUser: config.username,
142
+ ApiKey: config.apiKey,
143
+ UserName: config.username,
144
+ ClientIp: config.clientIp,
145
+ Command: command,
146
+ ...extraParams,
147
+ });
148
+ return `${base}?${params.toString()}`;
149
+ }
150
+
151
+ export async function apiRequest(command: string, config: NamecheapConfig, extraParams?: Record<string, string>): Promise<string> {
152
+ const url = buildUrl(command, config, extraParams);
153
+ const response = await fetch(url, {
154
+ signal: AbortSignal.timeout(30000),
155
+ headers: { "User-Agent": "microservice-domains/0.0.1" },
156
+ });
157
+
158
+ if (!response.ok) {
159
+ throw new Error(`Namecheap API HTTP error: ${response.status} ${response.statusText}`);
160
+ }
161
+
162
+ const xml = await response.text();
163
+ checkApiError(xml);
164
+ return xml;
165
+ }
166
+
167
+ // ============================================================
168
+ // API Functions
169
+ // ============================================================
170
+
171
+ /**
172
+ * List all domains in the Namecheap account
173
+ * Command: namecheap.domains.getList
174
+ */
175
+ export async function listNamecheapDomains(config?: NamecheapConfig): Promise<NamecheapDomain[]> {
176
+ const cfg = config || getConfig();
177
+ const xml = await apiRequest("namecheap.domains.getList", cfg, {
178
+ PageSize: "100",
179
+ Page: "1",
180
+ });
181
+
182
+ const domainElements = extractAllTags(xml, "Domain");
183
+ const domains: NamecheapDomain[] = [];
184
+
185
+ for (const el of domainElements) {
186
+ const name = extractAttributeFromElement(el, "Name");
187
+ const expires = extractAttributeFromElement(el, "Expires");
188
+ const autoRenew = extractAttributeFromElement(el, "AutoRenew");
189
+ const isLocked = extractAttributeFromElement(el, "IsLocked");
190
+
191
+ if (name) {
192
+ domains.push({
193
+ domain: name,
194
+ expiry: expires || "",
195
+ autoRenew: autoRenew === "true",
196
+ isLocked: isLocked === "true",
197
+ });
198
+ }
199
+ }
200
+
201
+ return domains;
202
+ }
203
+
204
+ /**
205
+ * Get detailed info for a specific domain
206
+ * Command: namecheap.domains.getInfo
207
+ */
208
+ export async function getDomainInfo(domain: string, config?: NamecheapConfig): Promise<NamecheapDomainInfo> {
209
+ const cfg = config || getConfig();
210
+ const xml = await apiRequest("namecheap.domains.getInfo", cfg, {
211
+ DomainName: domain,
212
+ });
213
+
214
+ // Parse creation and expiry dates
215
+ const createdDate = extractTag(xml, "CreatedDate") || extractAttribute(xml, "DomainGetInfoResult", "CreatedDate") || "";
216
+ const expiresDate = extractTag(xml, "ExpiredDate") || extractAttribute(xml, "DomainGetInfoResult", "ExpiredDate") || "";
217
+
218
+ // Parse nameservers
219
+ const nsSection = xml.match(/<DnsDetails[^>]*>([\s\S]*?)<\/DnsDetails>/i);
220
+ const nameservers: string[] = [];
221
+ if (nsSection) {
222
+ const nsElements = nsSection[1].matchAll(/<Nameserver[^>]*>([^<]*)<\/Nameserver>/gi);
223
+ for (const m of nsElements) {
224
+ if (m[1]) nameservers.push(m[1].trim().toLowerCase());
225
+ }
226
+ }
227
+
228
+ return {
229
+ domain,
230
+ registrar: "Namecheap",
231
+ created: createdDate,
232
+ expires: expiresDate,
233
+ nameservers,
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Renew a domain
239
+ * Command: namecheap.domains.renew
240
+ */
241
+ export async function renewDomain(domain: string, years: number = 1, config?: NamecheapConfig): Promise<NamecheapRenewResult> {
242
+ const cfg = config || getConfig();
243
+ const xml = await apiRequest("namecheap.domains.renew", cfg, {
244
+ DomainName: domain,
245
+ Years: String(years),
246
+ });
247
+
248
+ const transactionId = extractAttribute(xml, "DomainRenewResult", "TransactionID") || undefined;
249
+ const chargedAmount = extractAttribute(xml, "DomainRenewResult", "ChargedAmount") || undefined;
250
+ const orderId = extractAttribute(xml, "DomainRenewResult", "OrderID") || undefined;
251
+
252
+ return {
253
+ domain,
254
+ success: true,
255
+ transactionId,
256
+ chargedAmount,
257
+ orderId,
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Get DNS host records for a domain
263
+ * Command: namecheap.domains.dns.getHosts
264
+ */
265
+ export async function getDnsRecords(domain: string, sld: string, tld: string, config?: NamecheapConfig): Promise<NamecheapDnsRecord[]> {
266
+ const cfg = config || getConfig();
267
+ const xml = await apiRequest("namecheap.domains.dns.getHosts", cfg, {
268
+ SLD: sld,
269
+ TLD: tld,
270
+ });
271
+
272
+ const hostElements = extractAllTags(xml, "host");
273
+ const records: NamecheapDnsRecord[] = [];
274
+
275
+ for (const el of hostElements) {
276
+ const type = extractAttributeFromElement(el, "Type");
277
+ const name = extractAttributeFromElement(el, "Name");
278
+ const address = extractAttributeFromElement(el, "Address");
279
+ const hostId = extractAttributeFromElement(el, "HostId");
280
+ const mxPref = extractAttributeFromElement(el, "MXPref");
281
+ const ttl = extractAttributeFromElement(el, "TTL");
282
+
283
+ if (type && name && address) {
284
+ records.push({
285
+ hostId: hostId || undefined,
286
+ type,
287
+ name,
288
+ address,
289
+ mxPref: mxPref ? parseInt(mxPref) : undefined,
290
+ ttl: ttl ? parseInt(ttl) : 1800,
291
+ });
292
+ }
293
+ }
294
+
295
+ return records;
296
+ }
297
+
298
+ /**
299
+ * Set DNS host records for a domain
300
+ * Command: namecheap.domains.dns.setHosts
301
+ */
302
+ export async function setDnsRecords(
303
+ domain: string,
304
+ sld: string,
305
+ tld: string,
306
+ records: NamecheapDnsRecord[],
307
+ config?: NamecheapConfig
308
+ ): Promise<boolean> {
309
+ const cfg = config || getConfig();
310
+ const params: Record<string, string> = {
311
+ SLD: sld,
312
+ TLD: tld,
313
+ };
314
+
315
+ for (let i = 0; i < records.length; i++) {
316
+ const r = records[i];
317
+ const idx = i + 1;
318
+ params[`HostName${idx}`] = r.name;
319
+ params[`RecordType${idx}`] = r.type;
320
+ params[`Address${idx}`] = r.address;
321
+ params[`TTL${idx}`] = String(r.ttl);
322
+ if (r.mxPref !== undefined) {
323
+ params[`MXPref${idx}`] = String(r.mxPref);
324
+ }
325
+ }
326
+
327
+ await apiRequest("namecheap.domains.dns.setHosts", cfg, params);
328
+ return true;
329
+ }
330
+
331
+ /**
332
+ * Check domain availability
333
+ * Command: namecheap.domains.check
334
+ */
335
+ export async function checkAvailability(domain: string, config?: NamecheapConfig): Promise<NamecheapAvailability> {
336
+ const cfg = config || getConfig();
337
+ const xml = await apiRequest("namecheap.domains.check", cfg, {
338
+ DomainList: domain,
339
+ });
340
+
341
+ const available = extractAttribute(xml, "DomainCheckResult", "Available");
342
+
343
+ return {
344
+ domain,
345
+ available: available === "true",
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Split a domain name into SLD (second-level domain) and TLD (top-level domain)
351
+ */
352
+ export function splitDomain(domain: string): { sld: string; tld: string } {
353
+ const parts = domain.split(".");
354
+ if (parts.length < 2) {
355
+ throw new Error(`Invalid domain: ${domain}`);
356
+ }
357
+ // Handle multi-part TLDs like .co.uk
358
+ if (parts.length >= 3 && ["co", "com", "org", "net", "ac", "gov"].includes(parts[parts.length - 2])) {
359
+ return {
360
+ sld: parts.slice(0, -2).join("."),
361
+ tld: parts.slice(-2).join("."),
362
+ };
363
+ }
364
+ return {
365
+ sld: parts.slice(0, -1).join("."),
366
+ tld: parts[parts.length - 1],
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Sync domains from Namecheap to local database
372
+ * Calls listDomains + getDomainInfo for each, upserts into local domains table
373
+ */
374
+ export async function syncToLocalDb(dbFunctions: {
375
+ getDomainByName: (name: string) => { id: string } | null;
376
+ createDomain: (input: {
377
+ name: string;
378
+ registrar?: string;
379
+ status?: string;
380
+ registered_at?: string;
381
+ expires_at?: string;
382
+ auto_renew?: boolean;
383
+ nameservers?: string[];
384
+ }) => { id: string; name: string };
385
+ updateDomain: (
386
+ id: string,
387
+ input: {
388
+ registrar?: string;
389
+ status?: string;
390
+ registered_at?: string;
391
+ expires_at?: string;
392
+ auto_renew?: boolean;
393
+ nameservers?: string[];
394
+ }
395
+ ) => unknown;
396
+ }, config?: NamecheapConfig): Promise<NamecheapSyncResult> {
397
+ const cfg = config || getConfig();
398
+ const result: NamecheapSyncResult = { synced: 0, errors: [], domains: [] };
399
+
400
+ let ncDomains: NamecheapDomain[];
401
+ try {
402
+ ncDomains = await listNamecheapDomains(cfg);
403
+ } catch (error) {
404
+ throw new Error(`Failed to list Namecheap domains: ${error instanceof Error ? error.message : String(error)}`);
405
+ }
406
+
407
+ for (const ncDomain of ncDomains) {
408
+ try {
409
+ // Get detailed info
410
+ let info: NamecheapDomainInfo;
411
+ try {
412
+ info = await getDomainInfo(ncDomain.domain, cfg);
413
+ } catch {
414
+ // Fall back to basic info if getInfo fails
415
+ info = {
416
+ domain: ncDomain.domain,
417
+ registrar: "Namecheap",
418
+ created: "",
419
+ expires: ncDomain.expiry,
420
+ nameservers: [],
421
+ };
422
+ }
423
+
424
+ // Normalize dates to ISO format
425
+ const expiresAt = normalizeDate(info.expires || ncDomain.expiry);
426
+ const createdAt = normalizeDate(info.created);
427
+
428
+ // Upsert into local DB
429
+ const existing = dbFunctions.getDomainByName(ncDomain.domain);
430
+ if (existing) {
431
+ dbFunctions.updateDomain(existing.id, {
432
+ registrar: "Namecheap",
433
+ status: "active",
434
+ registered_at: createdAt || undefined,
435
+ expires_at: expiresAt || undefined,
436
+ auto_renew: ncDomain.autoRenew,
437
+ nameservers: info.nameservers.length > 0 ? info.nameservers : undefined,
438
+ });
439
+ } else {
440
+ dbFunctions.createDomain({
441
+ name: ncDomain.domain,
442
+ registrar: "Namecheap",
443
+ status: "active",
444
+ registered_at: createdAt || undefined,
445
+ expires_at: expiresAt || undefined,
446
+ auto_renew: ncDomain.autoRenew,
447
+ nameservers: info.nameservers,
448
+ });
449
+ }
450
+
451
+ result.synced++;
452
+ result.domains.push(ncDomain.domain);
453
+ } catch (error) {
454
+ result.errors.push(`${ncDomain.domain}: ${error instanceof Error ? error.message : String(error)}`);
455
+ }
456
+ }
457
+
458
+ return result;
459
+ }
460
+
461
+ // ============================================================
462
+ // Helpers
463
+ // ============================================================
464
+
465
+ function normalizeDate(dateStr: string): string | null {
466
+ if (!dateStr) return null;
467
+ try {
468
+ const d = new Date(dateStr);
469
+ if (isNaN(d.getTime())) return null;
470
+ return d.toISOString();
471
+ } catch {
472
+ return null;
473
+ }
474
+ }