@voyant-travel/relationships 0.119.2
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/LICENSE +201 -0
- package/README.md +36 -0
- package/dist/action-ledger-capabilities.d.ts +20 -0
- package/dist/action-ledger-capabilities.d.ts.map +1 -0
- package/dist/action-ledger-capabilities.js +16 -0
- package/dist/events.d.ts +23 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +9 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/route-runtime.d.ts +21 -0
- package/dist/route-runtime.d.ts.map +1 -0
- package/dist/route-runtime.js +28 -0
- package/dist/routes/accounts.d.ts +1460 -0
- package/dist/routes/accounts.d.ts.map +1 -0
- package/dist/routes/accounts.js +274 -0
- package/dist/routes/activities.d.ts +299 -0
- package/dist/routes/activities.d.ts.map +1 -0
- package/dist/routes/activities.js +64 -0
- package/dist/routes/custom-fields.d.ts +256 -0
- package/dist/routes/custom-fields.d.ts.map +1 -0
- package/dist/routes/custom-fields.js +47 -0
- package/dist/routes/customer-signals.d.ts +281 -0
- package/dist/routes/customer-signals.d.ts.map +1 -0
- package/dist/routes/customer-signals.js +45 -0
- package/dist/routes/index.d.ts +2945 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +14 -0
- package/dist/routes/person-documents.d.ts +519 -0
- package/dist/routes/person-documents.d.ts.map +1 -0
- package/dist/routes/person-documents.js +240 -0
- package/dist/routes/person-relationships.d.ts +189 -0
- package/dist/routes/person-relationships.d.ts.map +1 -0
- package/dist/routes/person-relationships.js +36 -0
- package/dist/schema-accounts.d.ts +2099 -0
- package/dist/schema-accounts.d.ts.map +1 -0
- package/dist/schema-accounts.js +312 -0
- package/dist/schema-activities.d.ts +821 -0
- package/dist/schema-activities.d.ts.map +1 -0
- package/dist/schema-activities.js +92 -0
- package/dist/schema-relations.d.ts +47 -0
- package/dist/schema-relations.d.ts.map +1 -0
- package/dist/schema-relations.js +70 -0
- package/dist/schema-shared.d.ts +10 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +36 -0
- package/dist/schema-signals.d.ts +324 -0
- package/dist/schema-signals.d.ts.map +1 -0
- package/dist/schema-signals.js +80 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +5 -0
- package/dist/service/accounts-merge.d.ts +63 -0
- package/dist/service/accounts-merge.d.ts.map +1 -0
- package/dist/service/accounts-merge.js +382 -0
- package/dist/service/accounts-organizations.d.ts +97 -0
- package/dist/service/accounts-organizations.d.ts.map +1 -0
- package/dist/service/accounts-organizations.js +70 -0
- package/dist/service/accounts-people.d.ts +1315 -0
- package/dist/service/accounts-people.d.ts.map +1 -0
- package/dist/service/accounts-people.js +409 -0
- package/dist/service/accounts-resolve.d.ts +76 -0
- package/dist/service/accounts-resolve.d.ts.map +1 -0
- package/dist/service/accounts-resolve.js +103 -0
- package/dist/service/accounts-shared.d.ts +68 -0
- package/dist/service/accounts-shared.d.ts.map +1 -0
- package/dist/service/accounts-shared.js +149 -0
- package/dist/service/accounts.d.ts +1465 -0
- package/dist/service/accounts.d.ts.map +1 -0
- package/dist/service/accounts.js +13 -0
- package/dist/service/activities.d.ts +486 -0
- package/dist/service/activities.d.ts.map +1 -0
- package/dist/service/activities.js +114 -0
- package/dist/service/custom-fields.d.ts +118 -0
- package/dist/service/custom-fields.d.ts.map +1 -0
- package/dist/service/custom-fields.js +88 -0
- package/dist/service/customer-signals.d.ts +733 -0
- package/dist/service/customer-signals.d.ts.map +1 -0
- package/dist/service/customer-signals.js +112 -0
- package/dist/service/helpers.d.ts +22 -0
- package/dist/service/helpers.d.ts.map +1 -0
- package/dist/service/helpers.js +39 -0
- package/dist/service/index.d.ts +4434 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/service/index.js +17 -0
- package/dist/service/person-documents.d.ts +1201 -0
- package/dist/service/person-documents.d.ts.map +1 -0
- package/dist/service/person-documents.js +240 -0
- package/dist/service/person-relationships.d.ts +502 -0
- package/dist/service/person-relationships.d.ts.map +1 -0
- package/dist/service/person-relationships.js +121 -0
- package/dist/validation.d.ts +3 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { evaluateActionLedgerCapabilityAccess, ledgerSensitiveRead, } from "@voyant-travel/action-ledger";
|
|
2
|
+
import { parseJsonBody, parseQuery } from "@voyant-travel/hono";
|
|
3
|
+
import { encryptOptionalJsonEnvelope } from "@voyant-travel/utils";
|
|
4
|
+
import { eq } from "drizzle-orm";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import { PERSON_DOCUMENT_REVEAL_ACTION_NAME, PERSON_DOCUMENT_REVEAL_ACTION_VERSION, PERSON_DOCUMENT_REVEAL_AUTHORIZATION_SOURCE, PERSON_DOCUMENT_REVEAL_CAPABILITY, PERSON_DOCUMENT_REVEAL_DECISION_POLICY, } from "../action-ledger-capabilities.js";
|
|
7
|
+
import { RELATIONSHIPS_ROUTE_RUNTIME_CONTAINER_KEY, } from "../route-runtime.js";
|
|
8
|
+
import { people } from "../schema.js";
|
|
9
|
+
import { relationshipsService } from "../service/index.js";
|
|
10
|
+
import { insertPersonDocumentFromPlaintextSchema, insertPersonDocumentSchema, personDocumentListQuerySchema, updatePersonDocumentFromPlaintextSchema, updatePersonDocumentSchema, updatePersonProfilePiiSchema, } from "../validation.js";
|
|
11
|
+
function getRelationshipsActionLedgerContext(c) {
|
|
12
|
+
return {
|
|
13
|
+
userId: c.get("userId") ?? null,
|
|
14
|
+
agentId: null,
|
|
15
|
+
workflowPrincipalId: null,
|
|
16
|
+
principalSubtype: null,
|
|
17
|
+
sessionId: c.get("sessionId") ?? null,
|
|
18
|
+
apiTokenId: c.get("apiTokenId") ?? c.get("apiKeyId") ?? null,
|
|
19
|
+
callerType: c.get("callerType") ?? null,
|
|
20
|
+
actor: c.get("actor") ?? null,
|
|
21
|
+
isInternalRequest: c.get("isInternalRequest") ?? false,
|
|
22
|
+
organizationId: c.get("organizationId") ?? null,
|
|
23
|
+
workflowRunId: null,
|
|
24
|
+
workflowStepId: null,
|
|
25
|
+
correlationId: c.req.header("x-correlation-id") ?? c.req.header("x-request-id") ?? null,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const peopleKeyRef = { keyType: "people" };
|
|
29
|
+
async function getRelationshipsKms(c) {
|
|
30
|
+
const runtime = c.var.container?.resolve(RELATIONSHIPS_ROUTE_RUNTIME_CONTAINER_KEY);
|
|
31
|
+
if (!runtime)
|
|
32
|
+
return null;
|
|
33
|
+
return runtime.getKmsProvider();
|
|
34
|
+
}
|
|
35
|
+
function kmsRequired(c) {
|
|
36
|
+
return c.json({ error: "KMS provider not configured — admin PII routes require a wired KMS key" }, 503);
|
|
37
|
+
}
|
|
38
|
+
export const personDocumentRoutes = new Hono()
|
|
39
|
+
.get("/people/:id/documents", async (c) => {
|
|
40
|
+
const query = parseQuery(c, personDocumentListQuerySchema);
|
|
41
|
+
return c.json({
|
|
42
|
+
data: await relationshipsService.listPersonDocuments(c.get("db"), c.req.param("id"), query),
|
|
43
|
+
});
|
|
44
|
+
})
|
|
45
|
+
.post("/people/:id/documents", async (c) => {
|
|
46
|
+
const row = await relationshipsService.createPersonDocument(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertPersonDocumentSchema));
|
|
47
|
+
if (!row)
|
|
48
|
+
return c.json({ error: "Person not found" }, 404);
|
|
49
|
+
return c.json({ data: row }, 201);
|
|
50
|
+
})
|
|
51
|
+
.get("/person-documents/:id", async (c) => {
|
|
52
|
+
const row = await relationshipsService.getPersonDocument(c.get("db"), c.req.param("id"));
|
|
53
|
+
if (!row)
|
|
54
|
+
return c.json({ error: "Document not found" }, 404);
|
|
55
|
+
return c.json({ data: row });
|
|
56
|
+
})
|
|
57
|
+
.patch("/person-documents/:id", async (c) => {
|
|
58
|
+
const row = await relationshipsService.updatePersonDocument(c.get("db"), c.req.param("id"), await parseJsonBody(c, updatePersonDocumentSchema));
|
|
59
|
+
if (!row)
|
|
60
|
+
return c.json({ error: "Document not found" }, 404);
|
|
61
|
+
return c.json({ data: row });
|
|
62
|
+
})
|
|
63
|
+
.delete("/person-documents/:id", async (c) => {
|
|
64
|
+
const row = await relationshipsService.deletePersonDocument(c.get("db"), c.req.param("id"));
|
|
65
|
+
if (!row)
|
|
66
|
+
return c.json({ error: "Document not found" }, 404);
|
|
67
|
+
return c.json({ success: true });
|
|
68
|
+
})
|
|
69
|
+
.post("/person-documents/:id/set-primary", async (c) => {
|
|
70
|
+
const row = await relationshipsService.setPrimaryPersonDocument(c.get("db"), c.req.param("id"));
|
|
71
|
+
if (!row)
|
|
72
|
+
return c.json({ error: "Document not found" }, 404);
|
|
73
|
+
return c.json({ data: row });
|
|
74
|
+
})
|
|
75
|
+
// ── Admin PII conveniences (server-side encrypt/decrypt) ──────────────
|
|
76
|
+
// Operator UIs (booking-traveler dialog) need to read a person's
|
|
77
|
+
// primary passport + free-text PII in plaintext to pre-fill forms,
|
|
78
|
+
// and write changes back without round-tripping ciphertext through
|
|
79
|
+
// the browser. Endpoints below use the request-scoped Relationships runtime
|
|
80
|
+
// to access the people KMS key.
|
|
81
|
+
/**
|
|
82
|
+
* Decrypted snapshot of a person's primary passport + dietary +
|
|
83
|
+
* accessibility values. Used by the booking-traveler dialog to
|
|
84
|
+
* pre-fill snapshot fields when an operator picks an existing
|
|
85
|
+
* person. Returns 404 when person missing, 503 when KMS unwired.
|
|
86
|
+
*/
|
|
87
|
+
.get("/people/:id/travel-snapshot", async (c) => {
|
|
88
|
+
const kms = await getRelationshipsKms(c);
|
|
89
|
+
if (!kms)
|
|
90
|
+
return kmsRequired(c);
|
|
91
|
+
const snapshot = await relationshipsService.loadPersonTravelSnapshot(c.get("db"), c.req.param("id"), {
|
|
92
|
+
kms,
|
|
93
|
+
});
|
|
94
|
+
if (!snapshot)
|
|
95
|
+
return c.json({ error: "Person not found" }, 404);
|
|
96
|
+
return c.json({ data: snapshot });
|
|
97
|
+
})
|
|
98
|
+
/**
|
|
99
|
+
* Plaintext PATCH for the four free-text PII slots on
|
|
100
|
+
* `relationships.people`. The route encrypts each provided value server-side
|
|
101
|
+
* with the people KMS key. `null` clears a slot.
|
|
102
|
+
*/
|
|
103
|
+
.patch("/people/:id/profile-pii", async (c) => {
|
|
104
|
+
const kms = await getRelationshipsKms(c);
|
|
105
|
+
if (!kms)
|
|
106
|
+
return kmsRequired(c);
|
|
107
|
+
const body = await parseJsonBody(c, updatePersonProfilePiiSchema);
|
|
108
|
+
const updates = {};
|
|
109
|
+
for (const [key, column] of [
|
|
110
|
+
["accessibility", "accessibilityEncrypted"],
|
|
111
|
+
["dietary", "dietaryEncrypted"],
|
|
112
|
+
["loyalty", "loyaltyEncrypted"],
|
|
113
|
+
["insurance", "insuranceEncrypted"],
|
|
114
|
+
]) {
|
|
115
|
+
const value = body[key];
|
|
116
|
+
if (value === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
if (value === null) {
|
|
119
|
+
updates[column] = null;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
updates[column] = await encryptOptionalJsonEnvelope(kms, peopleKeyRef, { text: value });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (Object.keys(updates).length === 0) {
|
|
126
|
+
return c.json({ error: "Nothing to update" }, 400);
|
|
127
|
+
}
|
|
128
|
+
const [row] = await c
|
|
129
|
+
.get("db")
|
|
130
|
+
.update(people)
|
|
131
|
+
.set({ ...updates, updatedAt: new Date() })
|
|
132
|
+
.where(eq(people.id, c.req.param("id")))
|
|
133
|
+
.returning({ id: people.id });
|
|
134
|
+
if (!row)
|
|
135
|
+
return c.json({ error: "Person not found" }, 404);
|
|
136
|
+
return c.json({ success: true });
|
|
137
|
+
})
|
|
138
|
+
/**
|
|
139
|
+
* Plaintext document create — accepts `number` as cleartext, the
|
|
140
|
+
* route encrypts via the people KMS key. Mirrors the existing
|
|
141
|
+
* `POST /people/:id/documents` shape but spares clients from
|
|
142
|
+
* holding KMS material.
|
|
143
|
+
*/
|
|
144
|
+
.post("/people/:id/documents/from-plaintext", async (c) => {
|
|
145
|
+
const kms = await getRelationshipsKms(c);
|
|
146
|
+
if (!kms)
|
|
147
|
+
return kmsRequired(c);
|
|
148
|
+
const body = await parseJsonBody(c, insertPersonDocumentFromPlaintextSchema);
|
|
149
|
+
const { number, ...rest } = body;
|
|
150
|
+
const numberEncrypted = number == null ? null : await encryptOptionalJsonEnvelope(kms, peopleKeyRef, { number });
|
|
151
|
+
const row = await relationshipsService.createPersonDocument(c.get("db"), c.req.param("id"), {
|
|
152
|
+
...rest,
|
|
153
|
+
...(numberEncrypted !== undefined ? { numberEncrypted } : {}),
|
|
154
|
+
});
|
|
155
|
+
if (!row)
|
|
156
|
+
return c.json({ error: "Person not found" }, 404);
|
|
157
|
+
return c.json({ data: row }, 201);
|
|
158
|
+
})
|
|
159
|
+
/**
|
|
160
|
+
* Plaintext document update — same encryption convention as the
|
|
161
|
+
* create variant. Only fields explicitly provided are written;
|
|
162
|
+
* `number: null` clears the encrypted slot.
|
|
163
|
+
*/
|
|
164
|
+
.patch("/person-documents/:id/from-plaintext", async (c) => {
|
|
165
|
+
const kms = await getRelationshipsKms(c);
|
|
166
|
+
if (!kms)
|
|
167
|
+
return kmsRequired(c);
|
|
168
|
+
const body = await parseJsonBody(c, updatePersonDocumentFromPlaintextSchema);
|
|
169
|
+
const { number, ...rest } = body;
|
|
170
|
+
const updateInput = { ...rest };
|
|
171
|
+
if (number !== undefined) {
|
|
172
|
+
updateInput.numberEncrypted =
|
|
173
|
+
number === null ? null : await encryptOptionalJsonEnvelope(kms, peopleKeyRef, { number });
|
|
174
|
+
}
|
|
175
|
+
const row = await relationshipsService.updatePersonDocument(c.get("db"), c.req.param("id"), updateInput);
|
|
176
|
+
if (!row)
|
|
177
|
+
return c.json({ error: "Document not found" }, 404);
|
|
178
|
+
return c.json({ data: row });
|
|
179
|
+
})
|
|
180
|
+
/**
|
|
181
|
+
* Decrypts and returns the plaintext document number for a single
|
|
182
|
+
* person document. Gated by the `relationships-pii:read` action-ledger
|
|
183
|
+
* capability; every successful reveal writes an action-ledger row
|
|
184
|
+
* tagged `relationships.person_document.reveal` so disclosures are auditable.
|
|
185
|
+
* Returns 403 when the caller lacks the grant, 404 when the document
|
|
186
|
+
* doesn't exist, 503 when KMS isn't wired.
|
|
187
|
+
*/
|
|
188
|
+
.get("/person-documents/:id/reveal", async (c) => {
|
|
189
|
+
const documentId = c.req.param("id");
|
|
190
|
+
const access = evaluateActionLedgerCapabilityAccess({
|
|
191
|
+
definition: PERSON_DOCUMENT_REVEAL_CAPABILITY,
|
|
192
|
+
actor: c.get("actor") ?? null,
|
|
193
|
+
callerType: c.get("callerType") ?? null,
|
|
194
|
+
scopes: c.get("scopes") ?? null,
|
|
195
|
+
isInternalRequest: c.get("isInternalRequest") ?? false,
|
|
196
|
+
});
|
|
197
|
+
if (!access.allowed) {
|
|
198
|
+
return c.json({ error: "Forbidden", reason: access.reason }, 403);
|
|
199
|
+
}
|
|
200
|
+
const kms = await getRelationshipsKms(c);
|
|
201
|
+
if (!kms)
|
|
202
|
+
return kmsRequired(c);
|
|
203
|
+
const existing = await relationshipsService.getPersonDocument(c.get("db"), documentId);
|
|
204
|
+
if (!existing)
|
|
205
|
+
return c.json({ error: "Document not found" }, 404);
|
|
206
|
+
let revealed;
|
|
207
|
+
try {
|
|
208
|
+
revealed = await ledgerSensitiveRead(c.get("db"), {
|
|
209
|
+
context: getRelationshipsActionLedgerContext(c),
|
|
210
|
+
actionName: PERSON_DOCUMENT_REVEAL_ACTION_NAME,
|
|
211
|
+
actionVersion: PERSON_DOCUMENT_REVEAL_ACTION_VERSION,
|
|
212
|
+
status: "succeeded",
|
|
213
|
+
evaluatedRisk: access.evaluatedRisk ?? "high",
|
|
214
|
+
targetType: "person_document",
|
|
215
|
+
targetId: documentId,
|
|
216
|
+
routeOrToolName: "relationships.person-documents.reveal",
|
|
217
|
+
capabilityId: PERSON_DOCUMENT_REVEAL_CAPABILITY.id,
|
|
218
|
+
capabilityVersion: PERSON_DOCUMENT_REVEAL_CAPABILITY.version,
|
|
219
|
+
authorizationSource: access.authorizationSource ?? PERSON_DOCUMENT_REVEAL_AUTHORIZATION_SOURCE,
|
|
220
|
+
reasonCode: "person_document_reveal",
|
|
221
|
+
disclosedFieldSet: ["number"],
|
|
222
|
+
disclosureSummary: "Person document number reveal",
|
|
223
|
+
decisionPolicy: PERSON_DOCUMENT_REVEAL_DECISION_POLICY,
|
|
224
|
+
}, async () => {
|
|
225
|
+
const result = await relationshipsService.revealPersonDocumentNumber(c.get("db"), documentId, {
|
|
226
|
+
kms,
|
|
227
|
+
});
|
|
228
|
+
if (!result)
|
|
229
|
+
throw new Error("Document not found");
|
|
230
|
+
return result;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
if (error instanceof Error && error.message === "Document not found") {
|
|
235
|
+
return c.json({ error: "Document not found" }, 404);
|
|
236
|
+
}
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
return c.json({ data: revealed });
|
|
240
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
type Env = {
|
|
3
|
+
Variables: {
|
|
4
|
+
db: PostgresJsDatabase;
|
|
5
|
+
userId?: string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export declare const personRelationshipRoutes: import("hono/hono-base").HonoBase<Env, {
|
|
9
|
+
"/people/:id/relationships": {
|
|
10
|
+
$get: {
|
|
11
|
+
input: {
|
|
12
|
+
param: {
|
|
13
|
+
id: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
output: {
|
|
17
|
+
data: {
|
|
18
|
+
id: string;
|
|
19
|
+
fromPersonId: string;
|
|
20
|
+
toPersonId: string;
|
|
21
|
+
kind: "partner" | "other" | "spouse" | "parent" | "child" | "sibling" | "guardian" | "ward" | "emergency_contact" | "friend" | "travel_companion";
|
|
22
|
+
inverseKind: "partner" | "other" | "spouse" | "parent" | "child" | "sibling" | "guardian" | "ward" | "emergency_contact" | "friend" | "travel_companion" | null;
|
|
23
|
+
startDate: string | null;
|
|
24
|
+
endDate: string | null;
|
|
25
|
+
isPrimary: boolean;
|
|
26
|
+
notes: string | null;
|
|
27
|
+
metadata: {
|
|
28
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
29
|
+
} | null;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
outputFormat: "json";
|
|
35
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
} & {
|
|
39
|
+
"/people/:id/relationships": {
|
|
40
|
+
$post: {
|
|
41
|
+
input: {
|
|
42
|
+
param: {
|
|
43
|
+
id: string;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
output: {
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
outputFormat: "json";
|
|
50
|
+
status: 400;
|
|
51
|
+
} | {
|
|
52
|
+
input: {
|
|
53
|
+
param: {
|
|
54
|
+
id: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
output: {
|
|
58
|
+
data: {
|
|
59
|
+
id: string;
|
|
60
|
+
createdAt: string;
|
|
61
|
+
updatedAt: string;
|
|
62
|
+
notes: string | null;
|
|
63
|
+
kind: "partner" | "other" | "spouse" | "parent" | "child" | "sibling" | "guardian" | "ward" | "emergency_contact" | "friend" | "travel_companion";
|
|
64
|
+
isPrimary: boolean;
|
|
65
|
+
metadata: {
|
|
66
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
67
|
+
} | null;
|
|
68
|
+
toPersonId: string;
|
|
69
|
+
inverseKind: "partner" | "other" | "spouse" | "parent" | "child" | "sibling" | "guardian" | "ward" | "emergency_contact" | "friend" | "travel_companion" | null;
|
|
70
|
+
startDate: string | null;
|
|
71
|
+
endDate: string | null;
|
|
72
|
+
fromPersonId: string;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
outputFormat: "json";
|
|
76
|
+
status: 201;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
} & {
|
|
80
|
+
"/person-relationships/:id": {
|
|
81
|
+
$get: {
|
|
82
|
+
input: {
|
|
83
|
+
param: {
|
|
84
|
+
id: string;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
output: {
|
|
88
|
+
error: string;
|
|
89
|
+
};
|
|
90
|
+
outputFormat: "json";
|
|
91
|
+
status: 404;
|
|
92
|
+
} | {
|
|
93
|
+
input: {
|
|
94
|
+
param: {
|
|
95
|
+
id: string;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
output: {
|
|
99
|
+
data: {
|
|
100
|
+
id: string;
|
|
101
|
+
fromPersonId: string;
|
|
102
|
+
toPersonId: string;
|
|
103
|
+
kind: "partner" | "other" | "spouse" | "parent" | "child" | "sibling" | "guardian" | "ward" | "emergency_contact" | "friend" | "travel_companion";
|
|
104
|
+
inverseKind: "partner" | "other" | "spouse" | "parent" | "child" | "sibling" | "guardian" | "ward" | "emergency_contact" | "friend" | "travel_companion" | null;
|
|
105
|
+
startDate: string | null;
|
|
106
|
+
endDate: string | null;
|
|
107
|
+
isPrimary: boolean;
|
|
108
|
+
notes: string | null;
|
|
109
|
+
metadata: {
|
|
110
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
111
|
+
} | null;
|
|
112
|
+
createdAt: string;
|
|
113
|
+
updatedAt: string;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
outputFormat: "json";
|
|
117
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
} & {
|
|
121
|
+
"/person-relationships/:id": {
|
|
122
|
+
$patch: {
|
|
123
|
+
input: {
|
|
124
|
+
param: {
|
|
125
|
+
id: string;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
output: {
|
|
129
|
+
error: string;
|
|
130
|
+
};
|
|
131
|
+
outputFormat: "json";
|
|
132
|
+
status: 404;
|
|
133
|
+
} | {
|
|
134
|
+
input: {
|
|
135
|
+
param: {
|
|
136
|
+
id: string;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
output: {
|
|
140
|
+
data: {
|
|
141
|
+
id: string;
|
|
142
|
+
fromPersonId: string;
|
|
143
|
+
toPersonId: string;
|
|
144
|
+
kind: "partner" | "other" | "spouse" | "parent" | "child" | "sibling" | "guardian" | "ward" | "emergency_contact" | "friend" | "travel_companion";
|
|
145
|
+
inverseKind: "partner" | "other" | "spouse" | "parent" | "child" | "sibling" | "guardian" | "ward" | "emergency_contact" | "friend" | "travel_companion" | null;
|
|
146
|
+
startDate: string | null;
|
|
147
|
+
endDate: string | null;
|
|
148
|
+
isPrimary: boolean;
|
|
149
|
+
notes: string | null;
|
|
150
|
+
metadata: {
|
|
151
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
152
|
+
} | null;
|
|
153
|
+
createdAt: string;
|
|
154
|
+
updatedAt: string;
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
outputFormat: "json";
|
|
158
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
} & {
|
|
162
|
+
"/person-relationships/:id": {
|
|
163
|
+
$delete: {
|
|
164
|
+
input: {
|
|
165
|
+
param: {
|
|
166
|
+
id: string;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
output: {
|
|
170
|
+
error: string;
|
|
171
|
+
};
|
|
172
|
+
outputFormat: "json";
|
|
173
|
+
status: 404;
|
|
174
|
+
} | {
|
|
175
|
+
input: {
|
|
176
|
+
param: {
|
|
177
|
+
id: string;
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
output: {
|
|
181
|
+
success: true;
|
|
182
|
+
};
|
|
183
|
+
outputFormat: "json";
|
|
184
|
+
status: import("hono/utils/http-status").ContentfulStatusCode;
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
}, "/", "/person-relationships/:id">;
|
|
188
|
+
export {};
|
|
189
|
+
//# sourceMappingURL=person-relationships.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"person-relationships.d.ts","sourceRoot":"","sources":["../../src/routes/person-relationships.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAUjE,KAAK,GAAG,GAAG;IACT,SAAS,EAAE;QACT,EAAE,EAAE,kBAAkB,CAAA;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAwCjC,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { parseJsonBody, parseQuery } from "@voyant-travel/hono";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { relationshipsService } from "../service/index.js";
|
|
4
|
+
import { insertPersonRelationshipSchema, personRelationshipListQuerySchema, updatePersonRelationshipSchema, } from "../validation.js";
|
|
5
|
+
export const personRelationshipRoutes = new Hono()
|
|
6
|
+
.get("/people/:id/relationships", async (c) => {
|
|
7
|
+
const query = parseQuery(c, personRelationshipListQuerySchema);
|
|
8
|
+
return c.json({
|
|
9
|
+
data: await relationshipsService.listPersonRelationships(c.get("db"), c.req.param("id"), query),
|
|
10
|
+
});
|
|
11
|
+
})
|
|
12
|
+
.post("/people/:id/relationships", async (c) => {
|
|
13
|
+
const row = await relationshipsService.createPersonRelationship(c.get("db"), c.req.param("id"), await parseJsonBody(c, insertPersonRelationshipSchema));
|
|
14
|
+
if (!row) {
|
|
15
|
+
return c.json({ error: "Person not found or self-relationship rejected" }, 400);
|
|
16
|
+
}
|
|
17
|
+
return c.json({ data: row }, 201);
|
|
18
|
+
})
|
|
19
|
+
.get("/person-relationships/:id", async (c) => {
|
|
20
|
+
const row = await relationshipsService.getPersonRelationship(c.get("db"), c.req.param("id"));
|
|
21
|
+
if (!row)
|
|
22
|
+
return c.json({ error: "Relationship not found" }, 404);
|
|
23
|
+
return c.json({ data: row });
|
|
24
|
+
})
|
|
25
|
+
.patch("/person-relationships/:id", async (c) => {
|
|
26
|
+
const row = await relationshipsService.updatePersonRelationship(c.get("db"), c.req.param("id"), await parseJsonBody(c, updatePersonRelationshipSchema));
|
|
27
|
+
if (!row)
|
|
28
|
+
return c.json({ error: "Relationship not found" }, 404);
|
|
29
|
+
return c.json({ data: row });
|
|
30
|
+
})
|
|
31
|
+
.delete("/person-relationships/:id", async (c) => {
|
|
32
|
+
const row = await relationshipsService.deletePersonRelationship(c.get("db"), c.req.param("id"));
|
|
33
|
+
if (!row)
|
|
34
|
+
return c.json({ error: "Relationship not found" }, 404);
|
|
35
|
+
return c.json({ success: true });
|
|
36
|
+
});
|