@voyant-travel/apps 0.1.0
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/dist/access-boundary.d.ts +29 -0
- package/dist/access-boundary.js +33 -0
- package/dist/api-runtime.d.ts +10 -0
- package/dist/api-runtime.js +8 -0
- package/dist/app-api-contracts.d.ts +51 -0
- package/dist/app-api-contracts.js +50 -0
- package/dist/app-api-routes.d.ts +26 -0
- package/dist/app-api-routes.js +133 -0
- package/dist/app-api-service.d.ts +1263 -0
- package/dist/app-api-service.js +231 -0
- package/dist/app-api-service.test.d.ts +1 -0
- package/dist/app-api-service.test.js +105 -0
- package/dist/compiler.d.ts +38 -0
- package/dist/compiler.js +118 -0
- package/dist/compiler.test.d.ts +1 -0
- package/dist/compiler.test.js +99 -0
- package/dist/consent.d.ts +15 -0
- package/dist/consent.js +50 -0
- package/dist/consent.test.d.ts +1 -0
- package/dist/consent.test.js +80 -0
- package/dist/contracts.d.ts +263 -0
- package/dist/contracts.js +273 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/ingestion.d.ts +15 -0
- package/dist/ingestion.js +232 -0
- package/dist/ingestion.test.d.ts +1 -0
- package/dist/ingestion.test.js +47 -0
- package/dist/installation-reconciliation.d.ts +18 -0
- package/dist/installation-reconciliation.js +254 -0
- package/dist/installation-service.d.ts +351 -0
- package/dist/installation-service.js +328 -0
- package/dist/installation-state.d.ts +15 -0
- package/dist/installation-state.js +17 -0
- package/dist/installation-state.test.d.ts +1 -0
- package/dist/installation-state.test.js +38 -0
- package/dist/oauth-crypto.d.ts +8 -0
- package/dist/oauth-crypto.js +23 -0
- package/dist/oauth-crypto.test.d.ts +1 -0
- package/dist/oauth-crypto.test.js +11 -0
- package/dist/oauth-service.d.ts +65 -0
- package/dist/oauth-service.js +381 -0
- package/dist/oauth-service.test.d.ts +1 -0
- package/dist/oauth-service.test.js +8 -0
- package/dist/routes.d.ts +17 -0
- package/dist/routes.js +131 -0
- package/dist/schema.d.ts +2937 -0
- package/dist/schema.js +342 -0
- package/dist/service.d.ts +95 -0
- package/dist/service.js +172 -0
- package/dist/service.test.d.ts +1 -0
- package/dist/service.test.js +178 -0
- package/dist/test-fixtures.d.ts +80 -0
- package/dist/test-fixtures.js +78 -0
- package/dist/voyant.d.ts +2 -0
- package/dist/voyant.js +115 -0
- package/dist/webhook-delivery.d.ts +69 -0
- package/dist/webhook-delivery.js +262 -0
- package/migrations/20260717000100_app_registry_foundation.sql +87 -0
- package/migrations/20260717111938_breezy_karma.sql +136 -0
- package/migrations/20260717123000_app_webhook_delivery_health.sql +4 -0
- package/migrations/20260717143000_app_oauth_authorization.sql +47 -0
- package/migrations/meta/_journal.json +34 -0
- package/package.json +159 -0
package/dist/routes.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { parseJsonBody, parseQuery, RequestValidationError } from "@voyant-travel/hono";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { appCredentialRevocationSchema, appListQuerySchema, appOAuthAuthorizeQuerySchema, appOAuthTokenSchema, appWebhookReplaySchema, createCustomAppRegistrationSchema, releaseManifestFetchSchema, releaseManifestUploadSchema, } from "./contracts.js";
|
|
5
|
+
import { createAppOAuthService } from "./oauth-service.js";
|
|
6
|
+
import { createAppsService } from "./service.js";
|
|
7
|
+
import { listAppWebhookHealth, replayAppWebhookDelivery } from "./webhook-delivery.js";
|
|
8
|
+
const appIdParamSchema = z.object({ appId: z.string().min(1) });
|
|
9
|
+
const installationIdParamSchema = z.object({ installationId: z.string().min(1) });
|
|
10
|
+
export function createAppsAdminRoutes(options = {}) {
|
|
11
|
+
const routes = new Hono();
|
|
12
|
+
const service = createAppsService(options);
|
|
13
|
+
const oauth = options.oauth ? createAppOAuthService(options.oauth) : null;
|
|
14
|
+
routes.get("/", async (c) => {
|
|
15
|
+
const query = parseQuery(c, appListQuerySchema);
|
|
16
|
+
return c.json(await service.list(c.get("db"), query), 200);
|
|
17
|
+
});
|
|
18
|
+
routes.post("/", async (c) => {
|
|
19
|
+
const body = await parseJsonBody(c, createCustomAppRegistrationSchema);
|
|
20
|
+
const app = await service.createCustomApp(c.get("db"), body);
|
|
21
|
+
return c.json({ data: app }, 201);
|
|
22
|
+
});
|
|
23
|
+
routes.get("/oauth/authorize", async (c) => {
|
|
24
|
+
if (!oauth)
|
|
25
|
+
return c.json({ error: "App OAuth is not configured" }, 501);
|
|
26
|
+
const query = parseQuery(c, appOAuthAuthorizeQuerySchema);
|
|
27
|
+
const result = await oauth.authorize(c.get("db"), {
|
|
28
|
+
appId: query.client_id,
|
|
29
|
+
releaseId: query.release_id,
|
|
30
|
+
redirectUri: query.redirect_uri,
|
|
31
|
+
state: query.state,
|
|
32
|
+
codeChallenge: query.code_challenge,
|
|
33
|
+
codeChallengeMethod: query.code_challenge_method,
|
|
34
|
+
actorId: query.actor_id,
|
|
35
|
+
operatorGrantedScopes: splitScopes(query.operator_scopes),
|
|
36
|
+
grantedOptionalScopes: splitScopes(query.optional_scopes),
|
|
37
|
+
});
|
|
38
|
+
const redirectUrl = new URL(result.redirectUri);
|
|
39
|
+
redirectUrl.searchParams.set("code", result.code);
|
|
40
|
+
redirectUrl.searchParams.set("state", result.state);
|
|
41
|
+
return c.redirect(redirectUrl.toString(), 302);
|
|
42
|
+
});
|
|
43
|
+
routes.post("/oauth/token", async (c) => {
|
|
44
|
+
if (!oauth)
|
|
45
|
+
return c.json({ error: "App OAuth is not configured" }, 501);
|
|
46
|
+
const body = await parseJsonBody(c, appOAuthTokenSchema);
|
|
47
|
+
const token = body.grant_type === "authorization_code"
|
|
48
|
+
? await oauth.token(c.get("db"), {
|
|
49
|
+
grantType: "authorization_code",
|
|
50
|
+
code: body.code,
|
|
51
|
+
redirectUri: body.redirect_uri,
|
|
52
|
+
codeVerifier: body.code_verifier,
|
|
53
|
+
clientId: body.client_id,
|
|
54
|
+
clientSecret: body.client_secret,
|
|
55
|
+
})
|
|
56
|
+
: body.grant_type === "refresh_token"
|
|
57
|
+
? await oauth.token(c.get("db"), {
|
|
58
|
+
grantType: "refresh_token",
|
|
59
|
+
refreshToken: body.refresh_token,
|
|
60
|
+
clientId: body.client_id,
|
|
61
|
+
clientSecret: body.client_secret,
|
|
62
|
+
})
|
|
63
|
+
: await oauth.token(c.get("db"), {
|
|
64
|
+
grantType: "urn:voyant:params:oauth:grant-type:actor-token-exchange",
|
|
65
|
+
installationId: body.installation_id,
|
|
66
|
+
viewerId: body.viewer_id,
|
|
67
|
+
viewerScopes: body.viewer_scopes,
|
|
68
|
+
contextualScopes: body.contextual_scopes,
|
|
69
|
+
clientId: body.client_id,
|
|
70
|
+
clientSecret: body.client_secret,
|
|
71
|
+
});
|
|
72
|
+
return c.json(token, 200);
|
|
73
|
+
});
|
|
74
|
+
routes.post("/oauth/revoke-installation", async (c) => {
|
|
75
|
+
if (!oauth)
|
|
76
|
+
return c.json({ error: "App OAuth is not configured" }, 501);
|
|
77
|
+
const body = await parseJsonBody(c, appCredentialRevocationSchema);
|
|
78
|
+
const result = await oauth.revokeInstallationCredentials(c.get("db"), body.installationId, body.actorId);
|
|
79
|
+
return c.json(result, 200);
|
|
80
|
+
});
|
|
81
|
+
routes.get("/:appId", async (c) => {
|
|
82
|
+
const { appId } = parseParams(c.req.param());
|
|
83
|
+
const app = await service.get(c.get("db"), appId);
|
|
84
|
+
return app ? c.json({ data: app }, 200) : c.json({ error: "App not found" }, 404);
|
|
85
|
+
});
|
|
86
|
+
routes.post("/:appId/releases", async (c) => {
|
|
87
|
+
const { appId } = parseParams(c.req.param());
|
|
88
|
+
const body = await parseJsonBody(c, releaseManifestUploadSchema);
|
|
89
|
+
const result = await service.releaseFromUpload(c.get("db"), appId, body);
|
|
90
|
+
return c.json({ data: result.release, digest: result.digest, created: result.created }, 201);
|
|
91
|
+
});
|
|
92
|
+
routes.post("/:appId/releases/fetch", async (c) => {
|
|
93
|
+
const { appId } = parseParams(c.req.param());
|
|
94
|
+
const body = await parseJsonBody(c, releaseManifestFetchSchema);
|
|
95
|
+
const result = await service.releaseFromFetch(c.get("db"), appId, body);
|
|
96
|
+
return c.json({ data: result.release, digest: result.digest, created: result.created }, 201);
|
|
97
|
+
});
|
|
98
|
+
routes.get("/installations/:installationId/webhooks", async (c) => {
|
|
99
|
+
const { installationId } = parseInstallationParams(c.req.param());
|
|
100
|
+
return c.json(await listAppWebhookHealth(c.get("db"), installationId), 200);
|
|
101
|
+
});
|
|
102
|
+
routes.post("/installations/:installationId/webhooks/replay", async (c) => {
|
|
103
|
+
parseInstallationParams(c.req.param());
|
|
104
|
+
const body = await parseJsonBody(c, appWebhookReplaySchema);
|
|
105
|
+
const delivery = await replayAppWebhookDelivery(c.get("db"), {
|
|
106
|
+
deliveryId: body.deliveryId,
|
|
107
|
+
actorId: body.actorId,
|
|
108
|
+
signingKey: { id: body.signingKeyId, secret: body.signingSecret },
|
|
109
|
+
});
|
|
110
|
+
return c.json({ data: delivery }, 202);
|
|
111
|
+
});
|
|
112
|
+
return routes;
|
|
113
|
+
}
|
|
114
|
+
function splitScopes(value) {
|
|
115
|
+
return value
|
|
116
|
+
.split(/[,\s]+/)
|
|
117
|
+
.map((scope) => scope.trim())
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
}
|
|
120
|
+
function parseParams(input) {
|
|
121
|
+
const parsed = appIdParamSchema.safeParse(input);
|
|
122
|
+
if (!parsed.success)
|
|
123
|
+
throw new RequestValidationError("Invalid route parameters");
|
|
124
|
+
return parsed.data;
|
|
125
|
+
}
|
|
126
|
+
function parseInstallationParams(input) {
|
|
127
|
+
const parsed = installationIdParamSchema.safeParse(input);
|
|
128
|
+
if (!parsed.success)
|
|
129
|
+
throw new RequestValidationError("Invalid route parameters");
|
|
130
|
+
return parsed.data;
|
|
131
|
+
}
|