@hasna/microservices 0.0.3 → 0.0.4
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.
- package/bin/index.js +63 -0
- package/bin/mcp.js +63 -0
- package/dist/index.js +63 -0
- package/microservices/microservice-ads/package.json +27 -0
- package/microservices/microservice-ads/src/cli/index.ts +407 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +493 -0
- package/microservices/microservice-ads/src/db/database.ts +93 -0
- package/microservices/microservice-ads/src/db/migrations.ts +60 -0
- package/microservices/microservice-ads/src/index.ts +39 -0
- package/microservices/microservice-ads/src/mcp/index.ts +320 -0
- package/microservices/microservice-contracts/package.json +27 -0
- package/microservices/microservice-contracts/src/cli/index.ts +383 -0
- package/microservices/microservice-contracts/src/db/contracts.ts +496 -0
- package/microservices/microservice-contracts/src/db/database.ts +93 -0
- package/microservices/microservice-contracts/src/db/migrations.ts +58 -0
- package/microservices/microservice-contracts/src/index.ts +43 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +308 -0
- package/microservices/microservice-domains/package.json +27 -0
- package/microservices/microservice-domains/src/cli/index.ts +438 -0
- package/microservices/microservice-domains/src/db/database.ts +93 -0
- package/microservices/microservice-domains/src/db/domains.ts +551 -0
- package/microservices/microservice-domains/src/db/migrations.ts +60 -0
- package/microservices/microservice-domains/src/index.ts +44 -0
- package/microservices/microservice-domains/src/mcp/index.ts +368 -0
- package/microservices/microservice-hiring/package.json +27 -0
- package/microservices/microservice-hiring/src/cli/index.ts +431 -0
- package/microservices/microservice-hiring/src/db/database.ts +93 -0
- package/microservices/microservice-hiring/src/db/hiring.ts +582 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +68 -0
- package/microservices/microservice-hiring/src/index.ts +51 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +464 -0
- package/microservices/microservice-payments/package.json +27 -0
- package/microservices/microservice-payments/src/cli/index.ts +357 -0
- package/microservices/microservice-payments/src/db/database.ts +93 -0
- package/microservices/microservice-payments/src/db/migrations.ts +63 -0
- package/microservices/microservice-payments/src/db/payments.ts +652 -0
- package/microservices/microservice-payments/src/index.ts +51 -0
- package/microservices/microservice-payments/src/mcp/index.ts +460 -0
- package/microservices/microservice-payroll/package.json +27 -0
- package/microservices/microservice-payroll/src/cli/index.ts +374 -0
- package/microservices/microservice-payroll/src/db/database.ts +93 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +69 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +741 -0
- package/microservices/microservice-payroll/src/index.ts +48 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +420 -0
- package/microservices/microservice-shipping/package.json +27 -0
- package/microservices/microservice-shipping/src/cli/index.ts +398 -0
- package/microservices/microservice-shipping/src/db/database.ts +93 -0
- package/microservices/microservice-shipping/src/db/migrations.ts +61 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +643 -0
- package/microservices/microservice-shipping/src/index.ts +53 -0
- package/microservices/microservice-shipping/src/mcp/index.ts +385 -0
- package/microservices/microservice-social/package.json +27 -0
- package/microservices/microservice-social/src/cli/index.ts +447 -0
- package/microservices/microservice-social/src/db/database.ts +93 -0
- package/microservices/microservice-social/src/db/migrations.ts +55 -0
- package/microservices/microservice-social/src/db/social.ts +672 -0
- package/microservices/microservice-social/src/index.ts +46 -0
- package/microservices/microservice-social/src/mcp/index.ts +435 -0
- package/microservices/microservice-subscriptions/package.json +27 -0
- package/microservices/microservice-subscriptions/src/cli/index.ts +400 -0
- package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +57 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +692 -0
- package/microservices/microservice-subscriptions/src/index.ts +41 -0
- package/microservices/microservice-subscriptions/src/mcp/index.ts +365 -0
- package/package.json +1 -1
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain, DNS record, and alert CRUD operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getDatabase } from "./database.js";
|
|
6
|
+
|
|
7
|
+
// --- Domain types ---
|
|
8
|
+
|
|
9
|
+
export interface Domain {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
registrar: string | null;
|
|
13
|
+
status: "active" | "expired" | "transferring" | "redemption";
|
|
14
|
+
registered_at: string | null;
|
|
15
|
+
expires_at: string | null;
|
|
16
|
+
auto_renew: boolean;
|
|
17
|
+
nameservers: string[];
|
|
18
|
+
whois: Record<string, unknown>;
|
|
19
|
+
ssl_expires_at: string | null;
|
|
20
|
+
ssl_issuer: string | null;
|
|
21
|
+
notes: string | null;
|
|
22
|
+
metadata: Record<string, unknown>;
|
|
23
|
+
created_at: string;
|
|
24
|
+
updated_at: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface DomainRow {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
registrar: string | null;
|
|
31
|
+
status: string;
|
|
32
|
+
registered_at: string | null;
|
|
33
|
+
expires_at: string | null;
|
|
34
|
+
auto_renew: number;
|
|
35
|
+
nameservers: string;
|
|
36
|
+
whois: string;
|
|
37
|
+
ssl_expires_at: string | null;
|
|
38
|
+
ssl_issuer: string | null;
|
|
39
|
+
notes: string | null;
|
|
40
|
+
metadata: string;
|
|
41
|
+
created_at: string;
|
|
42
|
+
updated_at: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function rowToDomain(row: DomainRow): Domain {
|
|
46
|
+
return {
|
|
47
|
+
...row,
|
|
48
|
+
status: row.status as Domain["status"],
|
|
49
|
+
auto_renew: row.auto_renew === 1,
|
|
50
|
+
nameservers: JSON.parse(row.nameservers || "[]"),
|
|
51
|
+
whois: JSON.parse(row.whois || "{}"),
|
|
52
|
+
metadata: JSON.parse(row.metadata || "{}"),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --- DNS Record types ---
|
|
57
|
+
|
|
58
|
+
export interface DnsRecord {
|
|
59
|
+
id: string;
|
|
60
|
+
domain_id: string;
|
|
61
|
+
type: "A" | "AAAA" | "CNAME" | "MX" | "TXT" | "NS" | "SRV";
|
|
62
|
+
name: string;
|
|
63
|
+
value: string;
|
|
64
|
+
ttl: number;
|
|
65
|
+
priority: number | null;
|
|
66
|
+
created_at: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface DnsRecordRow {
|
|
70
|
+
id: string;
|
|
71
|
+
domain_id: string;
|
|
72
|
+
type: string;
|
|
73
|
+
name: string;
|
|
74
|
+
value: string;
|
|
75
|
+
ttl: number;
|
|
76
|
+
priority: number | null;
|
|
77
|
+
created_at: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function rowToDnsRecord(row: DnsRecordRow): DnsRecord {
|
|
81
|
+
return {
|
|
82
|
+
...row,
|
|
83
|
+
type: row.type as DnsRecord["type"],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// --- Alert types ---
|
|
88
|
+
|
|
89
|
+
export interface Alert {
|
|
90
|
+
id: string;
|
|
91
|
+
domain_id: string;
|
|
92
|
+
type: "expiry" | "ssl_expiry" | "dns_change";
|
|
93
|
+
trigger_days_before: number | null;
|
|
94
|
+
sent_at: string | null;
|
|
95
|
+
created_at: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface AlertRow {
|
|
99
|
+
id: string;
|
|
100
|
+
domain_id: string;
|
|
101
|
+
type: string;
|
|
102
|
+
trigger_days_before: number | null;
|
|
103
|
+
sent_at: string | null;
|
|
104
|
+
created_at: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function rowToAlert(row: AlertRow): Alert {
|
|
108
|
+
return {
|
|
109
|
+
...row,
|
|
110
|
+
type: row.type as Alert["type"],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================
|
|
115
|
+
// Domain CRUD
|
|
116
|
+
// ============================================================
|
|
117
|
+
|
|
118
|
+
export interface CreateDomainInput {
|
|
119
|
+
name: string;
|
|
120
|
+
registrar?: string;
|
|
121
|
+
status?: Domain["status"];
|
|
122
|
+
registered_at?: string;
|
|
123
|
+
expires_at?: string;
|
|
124
|
+
auto_renew?: boolean;
|
|
125
|
+
nameservers?: string[];
|
|
126
|
+
whois?: Record<string, unknown>;
|
|
127
|
+
ssl_expires_at?: string;
|
|
128
|
+
ssl_issuer?: string;
|
|
129
|
+
notes?: string;
|
|
130
|
+
metadata?: Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function createDomain(input: CreateDomainInput): Domain {
|
|
134
|
+
const db = getDatabase();
|
|
135
|
+
const id = crypto.randomUUID();
|
|
136
|
+
const nameservers = JSON.stringify(input.nameservers || []);
|
|
137
|
+
const whois = JSON.stringify(input.whois || {});
|
|
138
|
+
const metadata = JSON.stringify(input.metadata || {});
|
|
139
|
+
|
|
140
|
+
db.prepare(
|
|
141
|
+
`INSERT INTO domains (id, name, registrar, status, registered_at, expires_at, auto_renew, nameservers, whois, ssl_expires_at, ssl_issuer, notes, metadata)
|
|
142
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
143
|
+
).run(
|
|
144
|
+
id,
|
|
145
|
+
input.name,
|
|
146
|
+
input.registrar || null,
|
|
147
|
+
input.status || "active",
|
|
148
|
+
input.registered_at || null,
|
|
149
|
+
input.expires_at || null,
|
|
150
|
+
input.auto_renew !== undefined ? (input.auto_renew ? 1 : 0) : 1,
|
|
151
|
+
nameservers,
|
|
152
|
+
whois,
|
|
153
|
+
input.ssl_expires_at || null,
|
|
154
|
+
input.ssl_issuer || null,
|
|
155
|
+
input.notes || null,
|
|
156
|
+
metadata
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
return getDomain(id)!;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getDomain(id: string): Domain | null {
|
|
163
|
+
const db = getDatabase();
|
|
164
|
+
const row = db.prepare("SELECT * FROM domains WHERE id = ?").get(id) as DomainRow | null;
|
|
165
|
+
return row ? rowToDomain(row) : null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ListDomainsOptions {
|
|
169
|
+
search?: string;
|
|
170
|
+
status?: Domain["status"];
|
|
171
|
+
registrar?: string;
|
|
172
|
+
limit?: number;
|
|
173
|
+
offset?: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function listDomains(options: ListDomainsOptions = {}): Domain[] {
|
|
177
|
+
const db = getDatabase();
|
|
178
|
+
const conditions: string[] = [];
|
|
179
|
+
const params: unknown[] = [];
|
|
180
|
+
|
|
181
|
+
if (options.search) {
|
|
182
|
+
conditions.push("(name LIKE ? OR registrar LIKE ? OR notes LIKE ?)");
|
|
183
|
+
const q = `%${options.search}%`;
|
|
184
|
+
params.push(q, q, q);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (options.status) {
|
|
188
|
+
conditions.push("status = ?");
|
|
189
|
+
params.push(options.status);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (options.registrar) {
|
|
193
|
+
conditions.push("registrar = ?");
|
|
194
|
+
params.push(options.registrar);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let sql = "SELECT * FROM domains";
|
|
198
|
+
if (conditions.length > 0) {
|
|
199
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
200
|
+
}
|
|
201
|
+
sql += " ORDER BY name";
|
|
202
|
+
|
|
203
|
+
if (options.limit) {
|
|
204
|
+
sql += " LIMIT ?";
|
|
205
|
+
params.push(options.limit);
|
|
206
|
+
}
|
|
207
|
+
if (options.offset) {
|
|
208
|
+
sql += " OFFSET ?";
|
|
209
|
+
params.push(options.offset);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const rows = db.prepare(sql).all(...params) as DomainRow[];
|
|
213
|
+
return rows.map(rowToDomain);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface UpdateDomainInput {
|
|
217
|
+
name?: string;
|
|
218
|
+
registrar?: string;
|
|
219
|
+
status?: Domain["status"];
|
|
220
|
+
registered_at?: string;
|
|
221
|
+
expires_at?: string;
|
|
222
|
+
auto_renew?: boolean;
|
|
223
|
+
nameservers?: string[];
|
|
224
|
+
whois?: Record<string, unknown>;
|
|
225
|
+
ssl_expires_at?: string;
|
|
226
|
+
ssl_issuer?: string;
|
|
227
|
+
notes?: string;
|
|
228
|
+
metadata?: Record<string, unknown>;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function updateDomain(id: string, input: UpdateDomainInput): Domain | null {
|
|
232
|
+
const db = getDatabase();
|
|
233
|
+
const existing = getDomain(id);
|
|
234
|
+
if (!existing) return null;
|
|
235
|
+
|
|
236
|
+
const sets: string[] = [];
|
|
237
|
+
const params: unknown[] = [];
|
|
238
|
+
|
|
239
|
+
if (input.name !== undefined) {
|
|
240
|
+
sets.push("name = ?");
|
|
241
|
+
params.push(input.name);
|
|
242
|
+
}
|
|
243
|
+
if (input.registrar !== undefined) {
|
|
244
|
+
sets.push("registrar = ?");
|
|
245
|
+
params.push(input.registrar);
|
|
246
|
+
}
|
|
247
|
+
if (input.status !== undefined) {
|
|
248
|
+
sets.push("status = ?");
|
|
249
|
+
params.push(input.status);
|
|
250
|
+
}
|
|
251
|
+
if (input.registered_at !== undefined) {
|
|
252
|
+
sets.push("registered_at = ?");
|
|
253
|
+
params.push(input.registered_at);
|
|
254
|
+
}
|
|
255
|
+
if (input.expires_at !== undefined) {
|
|
256
|
+
sets.push("expires_at = ?");
|
|
257
|
+
params.push(input.expires_at);
|
|
258
|
+
}
|
|
259
|
+
if (input.auto_renew !== undefined) {
|
|
260
|
+
sets.push("auto_renew = ?");
|
|
261
|
+
params.push(input.auto_renew ? 1 : 0);
|
|
262
|
+
}
|
|
263
|
+
if (input.nameservers !== undefined) {
|
|
264
|
+
sets.push("nameservers = ?");
|
|
265
|
+
params.push(JSON.stringify(input.nameservers));
|
|
266
|
+
}
|
|
267
|
+
if (input.whois !== undefined) {
|
|
268
|
+
sets.push("whois = ?");
|
|
269
|
+
params.push(JSON.stringify(input.whois));
|
|
270
|
+
}
|
|
271
|
+
if (input.ssl_expires_at !== undefined) {
|
|
272
|
+
sets.push("ssl_expires_at = ?");
|
|
273
|
+
params.push(input.ssl_expires_at);
|
|
274
|
+
}
|
|
275
|
+
if (input.ssl_issuer !== undefined) {
|
|
276
|
+
sets.push("ssl_issuer = ?");
|
|
277
|
+
params.push(input.ssl_issuer);
|
|
278
|
+
}
|
|
279
|
+
if (input.notes !== undefined) {
|
|
280
|
+
sets.push("notes = ?");
|
|
281
|
+
params.push(input.notes);
|
|
282
|
+
}
|
|
283
|
+
if (input.metadata !== undefined) {
|
|
284
|
+
sets.push("metadata = ?");
|
|
285
|
+
params.push(JSON.stringify(input.metadata));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (sets.length === 0) return existing;
|
|
289
|
+
|
|
290
|
+
sets.push("updated_at = datetime('now')");
|
|
291
|
+
params.push(id);
|
|
292
|
+
|
|
293
|
+
db.prepare(
|
|
294
|
+
`UPDATE domains SET ${sets.join(", ")} WHERE id = ?`
|
|
295
|
+
).run(...params);
|
|
296
|
+
|
|
297
|
+
return getDomain(id);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function deleteDomain(id: string): boolean {
|
|
301
|
+
const db = getDatabase();
|
|
302
|
+
const result = db.prepare("DELETE FROM domains WHERE id = ?").run(id);
|
|
303
|
+
return result.changes > 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function countDomains(): number {
|
|
307
|
+
const db = getDatabase();
|
|
308
|
+
const row = db.prepare("SELECT COUNT(*) as count FROM domains").get() as { count: number };
|
|
309
|
+
return row.count;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function searchDomains(query: string): Domain[] {
|
|
313
|
+
return listDomains({ search: query });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function getByRegistrar(registrar: string): Domain[] {
|
|
317
|
+
return listDomains({ registrar });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function listExpiring(days: number): Domain[] {
|
|
321
|
+
const db = getDatabase();
|
|
322
|
+
const rows = db
|
|
323
|
+
.prepare(
|
|
324
|
+
`SELECT * FROM domains
|
|
325
|
+
WHERE expires_at IS NOT NULL
|
|
326
|
+
AND expires_at <= datetime('now', '+' || ? || ' days')
|
|
327
|
+
AND expires_at >= datetime('now')
|
|
328
|
+
AND status = 'active'
|
|
329
|
+
ORDER BY expires_at`
|
|
330
|
+
)
|
|
331
|
+
.all(days) as DomainRow[];
|
|
332
|
+
return rows.map(rowToDomain);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function listSslExpiring(days: number): Domain[] {
|
|
336
|
+
const db = getDatabase();
|
|
337
|
+
const rows = db
|
|
338
|
+
.prepare(
|
|
339
|
+
`SELECT * FROM domains
|
|
340
|
+
WHERE ssl_expires_at IS NOT NULL
|
|
341
|
+
AND ssl_expires_at <= datetime('now', '+' || ? || ' days')
|
|
342
|
+
AND ssl_expires_at >= datetime('now')
|
|
343
|
+
ORDER BY ssl_expires_at`
|
|
344
|
+
)
|
|
345
|
+
.all(days) as DomainRow[];
|
|
346
|
+
return rows.map(rowToDomain);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export interface DomainStats {
|
|
350
|
+
total: number;
|
|
351
|
+
active: number;
|
|
352
|
+
expired: number;
|
|
353
|
+
transferring: number;
|
|
354
|
+
redemption: number;
|
|
355
|
+
auto_renew_enabled: number;
|
|
356
|
+
expiring_30_days: number;
|
|
357
|
+
ssl_expiring_30_days: number;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function getDomainStats(): DomainStats {
|
|
361
|
+
const db = getDatabase();
|
|
362
|
+
|
|
363
|
+
const total = (
|
|
364
|
+
db.prepare("SELECT COUNT(*) as count FROM domains").get() as { count: number }
|
|
365
|
+
).count;
|
|
366
|
+
|
|
367
|
+
const active = (
|
|
368
|
+
db.prepare("SELECT COUNT(*) as count FROM domains WHERE status = 'active'").get() as { count: number }
|
|
369
|
+
).count;
|
|
370
|
+
|
|
371
|
+
const expired = (
|
|
372
|
+
db.prepare("SELECT COUNT(*) as count FROM domains WHERE status = 'expired'").get() as { count: number }
|
|
373
|
+
).count;
|
|
374
|
+
|
|
375
|
+
const transferring = (
|
|
376
|
+
db.prepare("SELECT COUNT(*) as count FROM domains WHERE status = 'transferring'").get() as { count: number }
|
|
377
|
+
).count;
|
|
378
|
+
|
|
379
|
+
const redemption = (
|
|
380
|
+
db.prepare("SELECT COUNT(*) as count FROM domains WHERE status = 'redemption'").get() as { count: number }
|
|
381
|
+
).count;
|
|
382
|
+
|
|
383
|
+
const auto_renew_enabled = (
|
|
384
|
+
db.prepare("SELECT COUNT(*) as count FROM domains WHERE auto_renew = 1").get() as { count: number }
|
|
385
|
+
).count;
|
|
386
|
+
|
|
387
|
+
const expiring_30_days = listExpiring(30).length;
|
|
388
|
+
const ssl_expiring_30_days = listSslExpiring(30).length;
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
total,
|
|
392
|
+
active,
|
|
393
|
+
expired,
|
|
394
|
+
transferring,
|
|
395
|
+
redemption,
|
|
396
|
+
auto_renew_enabled,
|
|
397
|
+
expiring_30_days,
|
|
398
|
+
ssl_expiring_30_days,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ============================================================
|
|
403
|
+
// DNS Record CRUD
|
|
404
|
+
// ============================================================
|
|
405
|
+
|
|
406
|
+
export interface CreateDnsRecordInput {
|
|
407
|
+
domain_id: string;
|
|
408
|
+
type: DnsRecord["type"];
|
|
409
|
+
name: string;
|
|
410
|
+
value: string;
|
|
411
|
+
ttl?: number;
|
|
412
|
+
priority?: number;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function createDnsRecord(input: CreateDnsRecordInput): DnsRecord {
|
|
416
|
+
const db = getDatabase();
|
|
417
|
+
const id = crypto.randomUUID();
|
|
418
|
+
|
|
419
|
+
db.prepare(
|
|
420
|
+
`INSERT INTO dns_records (id, domain_id, type, name, value, ttl, priority)
|
|
421
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
422
|
+
).run(
|
|
423
|
+
id,
|
|
424
|
+
input.domain_id,
|
|
425
|
+
input.type,
|
|
426
|
+
input.name,
|
|
427
|
+
input.value,
|
|
428
|
+
input.ttl ?? 3600,
|
|
429
|
+
input.priority ?? null
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
return getDnsRecord(id)!;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function getDnsRecord(id: string): DnsRecord | null {
|
|
436
|
+
const db = getDatabase();
|
|
437
|
+
const row = db.prepare("SELECT * FROM dns_records WHERE id = ?").get(id) as DnsRecordRow | null;
|
|
438
|
+
return row ? rowToDnsRecord(row) : null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function listDnsRecords(domainId: string, type?: DnsRecord["type"]): DnsRecord[] {
|
|
442
|
+
const db = getDatabase();
|
|
443
|
+
let sql = "SELECT * FROM dns_records WHERE domain_id = ?";
|
|
444
|
+
const params: unknown[] = [domainId];
|
|
445
|
+
|
|
446
|
+
if (type) {
|
|
447
|
+
sql += " AND type = ?";
|
|
448
|
+
params.push(type);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
sql += " ORDER BY type, name";
|
|
452
|
+
|
|
453
|
+
const rows = db.prepare(sql).all(...params) as DnsRecordRow[];
|
|
454
|
+
return rows.map(rowToDnsRecord);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export interface UpdateDnsRecordInput {
|
|
458
|
+
type?: DnsRecord["type"];
|
|
459
|
+
name?: string;
|
|
460
|
+
value?: string;
|
|
461
|
+
ttl?: number;
|
|
462
|
+
priority?: number;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export function updateDnsRecord(id: string, input: UpdateDnsRecordInput): DnsRecord | null {
|
|
466
|
+
const db = getDatabase();
|
|
467
|
+
const existing = getDnsRecord(id);
|
|
468
|
+
if (!existing) return null;
|
|
469
|
+
|
|
470
|
+
const sets: string[] = [];
|
|
471
|
+
const params: unknown[] = [];
|
|
472
|
+
|
|
473
|
+
if (input.type !== undefined) {
|
|
474
|
+
sets.push("type = ?");
|
|
475
|
+
params.push(input.type);
|
|
476
|
+
}
|
|
477
|
+
if (input.name !== undefined) {
|
|
478
|
+
sets.push("name = ?");
|
|
479
|
+
params.push(input.name);
|
|
480
|
+
}
|
|
481
|
+
if (input.value !== undefined) {
|
|
482
|
+
sets.push("value = ?");
|
|
483
|
+
params.push(input.value);
|
|
484
|
+
}
|
|
485
|
+
if (input.ttl !== undefined) {
|
|
486
|
+
sets.push("ttl = ?");
|
|
487
|
+
params.push(input.ttl);
|
|
488
|
+
}
|
|
489
|
+
if (input.priority !== undefined) {
|
|
490
|
+
sets.push("priority = ?");
|
|
491
|
+
params.push(input.priority);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (sets.length === 0) return existing;
|
|
495
|
+
|
|
496
|
+
params.push(id);
|
|
497
|
+
|
|
498
|
+
db.prepare(
|
|
499
|
+
`UPDATE dns_records SET ${sets.join(", ")} WHERE id = ?`
|
|
500
|
+
).run(...params);
|
|
501
|
+
|
|
502
|
+
return getDnsRecord(id);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export function deleteDnsRecord(id: string): boolean {
|
|
506
|
+
const db = getDatabase();
|
|
507
|
+
const result = db.prepare("DELETE FROM dns_records WHERE id = ?").run(id);
|
|
508
|
+
return result.changes > 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ============================================================
|
|
512
|
+
// Alert CRUD
|
|
513
|
+
// ============================================================
|
|
514
|
+
|
|
515
|
+
export interface CreateAlertInput {
|
|
516
|
+
domain_id: string;
|
|
517
|
+
type: Alert["type"];
|
|
518
|
+
trigger_days_before?: number;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export function createAlert(input: CreateAlertInput): Alert {
|
|
522
|
+
const db = getDatabase();
|
|
523
|
+
const id = crypto.randomUUID();
|
|
524
|
+
|
|
525
|
+
db.prepare(
|
|
526
|
+
`INSERT INTO alerts (id, domain_id, type, trigger_days_before)
|
|
527
|
+
VALUES (?, ?, ?, ?)`
|
|
528
|
+
).run(id, input.domain_id, input.type, input.trigger_days_before ?? null);
|
|
529
|
+
|
|
530
|
+
return getAlert(id)!;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export function getAlert(id: string): Alert | null {
|
|
534
|
+
const db = getDatabase();
|
|
535
|
+
const row = db.prepare("SELECT * FROM alerts WHERE id = ?").get(id) as AlertRow | null;
|
|
536
|
+
return row ? rowToAlert(row) : null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export function listAlerts(domainId: string): Alert[] {
|
|
540
|
+
const db = getDatabase();
|
|
541
|
+
const rows = db
|
|
542
|
+
.prepare("SELECT * FROM alerts WHERE domain_id = ? ORDER BY type, trigger_days_before")
|
|
543
|
+
.all(domainId) as AlertRow[];
|
|
544
|
+
return rows.map(rowToAlert);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export function deleteAlert(id: string): boolean {
|
|
548
|
+
const db = getDatabase();
|
|
549
|
+
const result = db.prepare("DELETE FROM alerts WHERE id = ?").run(id);
|
|
550
|
+
return result.changes > 0;
|
|
551
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface MigrationEntry {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
sql: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const MIGRATIONS: MigrationEntry[] = [
|
|
8
|
+
{
|
|
9
|
+
id: 1,
|
|
10
|
+
name: "initial_schema",
|
|
11
|
+
sql: `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS domains (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
name TEXT NOT NULL UNIQUE,
|
|
15
|
+
registrar TEXT,
|
|
16
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'expired', 'transferring', 'redemption')),
|
|
17
|
+
registered_at TEXT,
|
|
18
|
+
expires_at TEXT,
|
|
19
|
+
auto_renew INTEGER NOT NULL DEFAULT 1,
|
|
20
|
+
nameservers TEXT NOT NULL DEFAULT '[]',
|
|
21
|
+
whois TEXT NOT NULL DEFAULT '{}',
|
|
22
|
+
ssl_expires_at TEXT,
|
|
23
|
+
ssl_issuer TEXT,
|
|
24
|
+
notes TEXT,
|
|
25
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
26
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
27
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS dns_records (
|
|
31
|
+
id TEXT PRIMARY KEY,
|
|
32
|
+
domain_id TEXT NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
|
|
33
|
+
type TEXT NOT NULL CHECK (type IN ('A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SRV')),
|
|
34
|
+
name TEXT NOT NULL,
|
|
35
|
+
value TEXT NOT NULL,
|
|
36
|
+
ttl INTEGER NOT NULL DEFAULT 3600,
|
|
37
|
+
priority INTEGER,
|
|
38
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS alerts (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
domain_id TEXT NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
|
|
44
|
+
type TEXT NOT NULL CHECK (type IN ('expiry', 'ssl_expiry', 'dns_change')),
|
|
45
|
+
trigger_days_before INTEGER,
|
|
46
|
+
sent_at TEXT,
|
|
47
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_domains_name ON domains(name);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_domains_registrar ON domains(registrar);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_domains_status ON domains(status);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_domains_expires_at ON domains(expires_at);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_dns_records_domain ON dns_records(domain_id);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_dns_records_type ON dns_records(type);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_alerts_domain ON alerts(domain_id);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_alerts_type ON alerts(type);
|
|
58
|
+
`,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* microservice-domains — Domain portfolio and DNS management microservice
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
createDomain,
|
|
7
|
+
getDomain,
|
|
8
|
+
listDomains,
|
|
9
|
+
updateDomain,
|
|
10
|
+
deleteDomain,
|
|
11
|
+
countDomains,
|
|
12
|
+
searchDomains,
|
|
13
|
+
getByRegistrar,
|
|
14
|
+
listExpiring,
|
|
15
|
+
listSslExpiring,
|
|
16
|
+
getDomainStats,
|
|
17
|
+
type Domain,
|
|
18
|
+
type CreateDomainInput,
|
|
19
|
+
type UpdateDomainInput,
|
|
20
|
+
type ListDomainsOptions,
|
|
21
|
+
type DomainStats,
|
|
22
|
+
} from "./db/domains.js";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
createDnsRecord,
|
|
26
|
+
getDnsRecord,
|
|
27
|
+
listDnsRecords,
|
|
28
|
+
updateDnsRecord,
|
|
29
|
+
deleteDnsRecord,
|
|
30
|
+
type DnsRecord,
|
|
31
|
+
type CreateDnsRecordInput,
|
|
32
|
+
type UpdateDnsRecordInput,
|
|
33
|
+
} from "./db/domains.js";
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
createAlert,
|
|
37
|
+
getAlert,
|
|
38
|
+
listAlerts,
|
|
39
|
+
deleteAlert,
|
|
40
|
+
type Alert,
|
|
41
|
+
type CreateAlertInput,
|
|
42
|
+
} from "./db/domains.js";
|
|
43
|
+
|
|
44
|
+
export { getDatabase, closeDatabase } from "./db/database.js";
|