@voyant-travel/apps 0.5.0 → 0.6.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.
- package/dist/api-runtime.d.ts +5 -2
- package/dist/app-api-routes.js +1 -1
- package/dist/app-api-service.js +1 -1
- package/dist/contracts.d.ts +43 -0
- package/dist/contracts.js +41 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/installation-read-model.d.ts +64 -0
- package/dist/installation-read-model.js +148 -0
- package/dist/installation-routes.test.d.ts +1 -0
- package/dist/installation-routes.test.js +161 -0
- package/dist/routes-openapi.d.ts +859 -0
- package/dist/routes-openapi.js +195 -0
- package/dist/routes.d.ts +18 -2
- package/dist/routes.js +109 -16
- package/dist/runtime-contributor.d.ts +1 -0
- package/dist/runtime-contributor.js +3 -0
- package/dist/voyant.js +145 -11
- package/openapi/admin/apps.json +72 -0
- package/package.json +12 -5
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { createRoute, z } from "@hono/zod-openapi";
|
|
2
|
+
import { activateInstallationBodySchema, appCredentialRevocationSchema, appInstallationAuditQuerySchema, appInstallationListQuerySchema, appListQuerySchema, appOAuthAuthorizeQuerySchema, appOAuthTokenSchema, appSessionTokenExchangeSchema, appSessionTokenIssueSchema, appWebhookReplaySchema, createCustomAppRegistrationSchema, installAppSchema, lifecycleActionBodySchema, releaseManifestFetchSchema, releaseManifestUploadSchema, } from "./contracts.js";
|
|
3
|
+
const appIdParamSchema = z.object({ appId: z.string().min(1) });
|
|
4
|
+
const installationIdParamSchema = z.object({ installationId: z.string().min(1) });
|
|
5
|
+
const errorSchema = z.object({ error: z.string() });
|
|
6
|
+
const dataEnvelopeSchema = z.object({ data: z.unknown() });
|
|
7
|
+
const listEnvelopeSchema = z.object({
|
|
8
|
+
data: z.array(z.unknown()),
|
|
9
|
+
total: z.number(),
|
|
10
|
+
limit: z.number(),
|
|
11
|
+
offset: z.number(),
|
|
12
|
+
});
|
|
13
|
+
const releaseCreateResponseSchema = z.object({
|
|
14
|
+
data: z.unknown(),
|
|
15
|
+
digest: z.string(),
|
|
16
|
+
created: z.boolean(),
|
|
17
|
+
});
|
|
18
|
+
const oauthRedirectResponseSchema = z.object({
|
|
19
|
+
data: z.object({ redirectUrl: z.string().url(), state: z.string() }),
|
|
20
|
+
});
|
|
21
|
+
const tokenResponseSchema = z.record(z.string(), z.unknown());
|
|
22
|
+
const credentialRevocationResponseSchema = z.object({
|
|
23
|
+
installationId: z.string(),
|
|
24
|
+
generation: z.number(),
|
|
25
|
+
});
|
|
26
|
+
const jsonContent = (schema, description) => ({
|
|
27
|
+
description,
|
|
28
|
+
content: { "application/json": { schema } },
|
|
29
|
+
});
|
|
30
|
+
const requiredJsonBody = (schema) => ({
|
|
31
|
+
required: true,
|
|
32
|
+
content: { "application/json": { schema } },
|
|
33
|
+
});
|
|
34
|
+
export const listAppsRoute = createRoute({
|
|
35
|
+
method: "get",
|
|
36
|
+
path: "/",
|
|
37
|
+
request: { query: appListQuerySchema },
|
|
38
|
+
responses: { 200: jsonContent(listEnvelopeSchema, "Paginated app registrations") },
|
|
39
|
+
});
|
|
40
|
+
export const createCustomAppRoute = createRoute({
|
|
41
|
+
method: "post",
|
|
42
|
+
path: "/",
|
|
43
|
+
request: { body: requiredJsonBody(createCustomAppRegistrationSchema) },
|
|
44
|
+
responses: {
|
|
45
|
+
201: jsonContent(dataEnvelopeSchema, "Created custom app registration"),
|
|
46
|
+
409: jsonContent(errorSchema, "Duplicate app registration"),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
export const authorizeAppOAuthRoute = createRoute({
|
|
50
|
+
method: "post",
|
|
51
|
+
path: "/oauth/authorize",
|
|
52
|
+
request: { body: requiredJsonBody(appOAuthAuthorizeQuerySchema) },
|
|
53
|
+
responses: {
|
|
54
|
+
200: jsonContent(oauthRedirectResponseSchema, "OAuth authorization redirect target"),
|
|
55
|
+
501: jsonContent(errorSchema, "App OAuth is not configured"),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
export const issueAppOAuthTokenRoute = createRoute({
|
|
59
|
+
method: "post",
|
|
60
|
+
path: "/oauth/token",
|
|
61
|
+
request: { body: requiredJsonBody(appOAuthTokenSchema) },
|
|
62
|
+
responses: {
|
|
63
|
+
200: jsonContent(tokenResponseSchema, "OAuth token response"),
|
|
64
|
+
501: jsonContent(errorSchema, "App OAuth is not configured"),
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
export const revokeAppInstallationCredentialsRoute = createRoute({
|
|
68
|
+
method: "post",
|
|
69
|
+
path: "/oauth/revoke-installation",
|
|
70
|
+
request: { body: requiredJsonBody(appCredentialRevocationSchema) },
|
|
71
|
+
responses: {
|
|
72
|
+
200: jsonContent(credentialRevocationResponseSchema, "Revoked installation credential generation"),
|
|
73
|
+
501: jsonContent(errorSchema, "App OAuth is not configured"),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
export const issueAppSessionTokenRoute = createRoute({
|
|
77
|
+
method: "post",
|
|
78
|
+
path: "/installations/{installationId}/session-token",
|
|
79
|
+
request: {
|
|
80
|
+
params: installationIdParamSchema,
|
|
81
|
+
body: requiredJsonBody(appSessionTokenIssueSchema),
|
|
82
|
+
},
|
|
83
|
+
responses: {
|
|
84
|
+
201: jsonContent(dataEnvelopeSchema, "Issued app session token"),
|
|
85
|
+
501: jsonContent(errorSchema, "App session tokens are not configured"),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
export const exchangeAppSessionTokenRoute = createRoute({
|
|
89
|
+
method: "post",
|
|
90
|
+
path: "/oauth/session-token/exchange",
|
|
91
|
+
request: { body: requiredJsonBody(appSessionTokenExchangeSchema) },
|
|
92
|
+
responses: {
|
|
93
|
+
200: jsonContent(tokenResponseSchema, "Online app token response"),
|
|
94
|
+
501: jsonContent(errorSchema, "App session tokens are not configured"),
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
export const installAppRoute = createRoute({
|
|
98
|
+
method: "post",
|
|
99
|
+
path: "/install",
|
|
100
|
+
request: { body: requiredJsonBody(installAppSchema) },
|
|
101
|
+
responses: { 201: jsonContent(dataEnvelopeSchema, "Installed app") },
|
|
102
|
+
});
|
|
103
|
+
export const listAppInstallationsRoute = createRoute({
|
|
104
|
+
method: "get",
|
|
105
|
+
path: "/installations",
|
|
106
|
+
request: { query: appInstallationListQuerySchema },
|
|
107
|
+
responses: { 200: jsonContent(listEnvelopeSchema, "Paginated app installations") },
|
|
108
|
+
});
|
|
109
|
+
export const getAppInstallationRoute = createRoute({
|
|
110
|
+
method: "get",
|
|
111
|
+
path: "/installations/{installationId}",
|
|
112
|
+
request: { params: installationIdParamSchema },
|
|
113
|
+
responses: {
|
|
114
|
+
200: jsonContent(dataEnvelopeSchema, "App installation detail"),
|
|
115
|
+
404: jsonContent(errorSchema, "App installation not found"),
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
export const listAppInstallationAuditRoute = createRoute({
|
|
119
|
+
method: "get",
|
|
120
|
+
path: "/installations/{installationId}/audit",
|
|
121
|
+
request: { params: installationIdParamSchema, query: appInstallationAuditQuerySchema },
|
|
122
|
+
responses: { 200: jsonContent(dataEnvelopeSchema, "App installation audit events") },
|
|
123
|
+
});
|
|
124
|
+
export const pauseAppInstallationRoute = createRoute({
|
|
125
|
+
method: "post",
|
|
126
|
+
path: "/installations/{installationId}/pause",
|
|
127
|
+
request: { params: installationIdParamSchema, body: requiredJsonBody(lifecycleActionBodySchema) },
|
|
128
|
+
responses: { 200: jsonContent(dataEnvelopeSchema, "Paused app installation") },
|
|
129
|
+
});
|
|
130
|
+
export const resumeAppInstallationRoute = createRoute({
|
|
131
|
+
method: "post",
|
|
132
|
+
path: "/installations/{installationId}/resume",
|
|
133
|
+
request: { params: installationIdParamSchema, body: requiredJsonBody(lifecycleActionBodySchema) },
|
|
134
|
+
responses: { 200: jsonContent(dataEnvelopeSchema, "Resumed app installation") },
|
|
135
|
+
});
|
|
136
|
+
export const uninstallAppInstallationRoute = createRoute({
|
|
137
|
+
method: "post",
|
|
138
|
+
path: "/installations/{installationId}/uninstall",
|
|
139
|
+
request: { params: installationIdParamSchema, body: requiredJsonBody(lifecycleActionBodySchema) },
|
|
140
|
+
responses: { 200: jsonContent(dataEnvelopeSchema, "Uninstalled app installation") },
|
|
141
|
+
});
|
|
142
|
+
export const activateAppInstallationRoute = createRoute({
|
|
143
|
+
method: "post",
|
|
144
|
+
path: "/installations/{installationId}/activate",
|
|
145
|
+
request: {
|
|
146
|
+
params: installationIdParamSchema,
|
|
147
|
+
body: requiredJsonBody(activateInstallationBodySchema),
|
|
148
|
+
},
|
|
149
|
+
responses: { 200: jsonContent(dataEnvelopeSchema, "Activated app installation release") },
|
|
150
|
+
});
|
|
151
|
+
export const previewAppInstallationPurgeRoute = createRoute({
|
|
152
|
+
method: "post",
|
|
153
|
+
path: "/installations/{installationId}/purge-preview",
|
|
154
|
+
request: { params: installationIdParamSchema, body: requiredJsonBody(lifecycleActionBodySchema) },
|
|
155
|
+
responses: { 200: jsonContent(dataEnvelopeSchema, "App installation purge preview") },
|
|
156
|
+
});
|
|
157
|
+
export const getAppRoute = createRoute({
|
|
158
|
+
method: "get",
|
|
159
|
+
path: "/{appId}",
|
|
160
|
+
request: { params: appIdParamSchema },
|
|
161
|
+
responses: {
|
|
162
|
+
200: jsonContent(dataEnvelopeSchema, "App registration"),
|
|
163
|
+
404: jsonContent(errorSchema, "App not found"),
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
export const listAppReleasesRoute = createRoute({
|
|
167
|
+
method: "get",
|
|
168
|
+
path: "/{appId}/releases",
|
|
169
|
+
request: { params: appIdParamSchema },
|
|
170
|
+
responses: { 200: jsonContent(dataEnvelopeSchema, "App releases") },
|
|
171
|
+
});
|
|
172
|
+
export const createAppReleaseRoute = createRoute({
|
|
173
|
+
method: "post",
|
|
174
|
+
path: "/{appId}/releases",
|
|
175
|
+
request: { params: appIdParamSchema, body: requiredJsonBody(releaseManifestUploadSchema) },
|
|
176
|
+
responses: { 201: jsonContent(releaseCreateResponseSchema, "Created app release") },
|
|
177
|
+
});
|
|
178
|
+
export const fetchAppReleaseRoute = createRoute({
|
|
179
|
+
method: "post",
|
|
180
|
+
path: "/{appId}/releases/fetch",
|
|
181
|
+
request: { params: appIdParamSchema, body: requiredJsonBody(releaseManifestFetchSchema) },
|
|
182
|
+
responses: { 201: jsonContent(releaseCreateResponseSchema, "Fetched app release") },
|
|
183
|
+
});
|
|
184
|
+
export const listAppWebhooksRoute = createRoute({
|
|
185
|
+
method: "get",
|
|
186
|
+
path: "/installations/{installationId}/webhooks",
|
|
187
|
+
request: { params: installationIdParamSchema },
|
|
188
|
+
responses: { 200: jsonContent(dataEnvelopeSchema, "App webhook delivery health") },
|
|
189
|
+
});
|
|
190
|
+
export const replayAppWebhookRoute = createRoute({
|
|
191
|
+
method: "post",
|
|
192
|
+
path: "/installations/{installationId}/webhooks/replay",
|
|
193
|
+
request: { params: installationIdParamSchema, body: requiredJsonBody(appWebhookReplaySchema) },
|
|
194
|
+
responses: { 202: jsonContent(dataEnvelopeSchema, "Replayed app webhook delivery") },
|
|
195
|
+
});
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
|
+
import type { EventBus } from "@voyant-travel/core/events";
|
|
3
|
+
import type { createCustomFieldsService } from "@voyant-travel/custom-fields";
|
|
1
4
|
import type { AccessCatalog } from "@voyant-travel/types/api-keys";
|
|
2
5
|
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
-
import { Hono } from "hono";
|
|
4
6
|
import { type AppsServiceOptions } from "./service.js";
|
|
7
|
+
type CustomFieldsService = ReturnType<typeof createCustomFieldsService>;
|
|
5
8
|
type Env = {
|
|
9
|
+
Bindings: {
|
|
10
|
+
/** Deployment identity; the install lifecycle is scoped to it. */
|
|
11
|
+
VOYANT_CLOUD_DEPLOYMENT_ID?: string;
|
|
12
|
+
};
|
|
6
13
|
Variables: {
|
|
7
14
|
db: PostgresJsDatabase;
|
|
8
15
|
};
|
|
@@ -21,6 +28,15 @@ export interface AppsAdminRouteOptions extends AppsServiceOptions {
|
|
|
21
28
|
secret: string;
|
|
22
29
|
ttlSeconds?: number;
|
|
23
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Deployment identity used when installing over HTTP. Falls back to
|
|
33
|
+
* {@link oauth}'s deployment id when omitted.
|
|
34
|
+
*/
|
|
35
|
+
deploymentId?: string;
|
|
36
|
+
/** Platform API version used to gate release compatibility. */
|
|
37
|
+
platformApiVersion?: string;
|
|
38
|
+
eventBus?: EventBus;
|
|
39
|
+
customFields?: CustomFieldsService;
|
|
24
40
|
}
|
|
25
|
-
export declare function createAppsAdminRoutes(options?: AppsAdminRouteOptions):
|
|
41
|
+
export declare function createAppsAdminRoutes(options?: AppsAdminRouteOptions): OpenAPIHono<Env, {}, "/">;
|
|
26
42
|
export {};
|
package/dist/routes.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
|
+
import { openApiValidationHook, parseJsonBody, parseQuery, RequestValidationError, requireUserId, } from "@voyant-travel/hono";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { appCredentialRevocationSchema, appListQuerySchema, appOAuthAuthorizeQuerySchema, appOAuthTokenSchema, appSessionTokenExchangeSchema, appSessionTokenIssueSchema, appWebhookReplaySchema, createCustomAppRegistrationSchema, releaseManifestFetchSchema, releaseManifestUploadSchema, } from "./contracts.js";
|
|
4
|
+
import { activateInstallationBodySchema, appCredentialRevocationSchema, appInstallationAuditQuerySchema, appInstallationListQuerySchema, appListQuerySchema, appOAuthAuthorizeQuerySchema, appOAuthTokenSchema, appSessionTokenExchangeSchema, appSessionTokenIssueSchema, appWebhookReplaySchema, createCustomAppRegistrationSchema, installAppSchema, lifecycleActionBodySchema, releaseManifestFetchSchema, releaseManifestUploadSchema, } from "./contracts.js";
|
|
5
|
+
import { listAppReleases, listInstallationAudit, listInstallationSummaries, loadInstallationDetail, } from "./installation-read-model.js";
|
|
6
|
+
import { createAppInstallationService } from "./installation-service.js";
|
|
5
7
|
import { createAppOAuthService } from "./oauth-service.js";
|
|
8
|
+
import { activateAppInstallationRoute, authorizeAppOAuthRoute, createAppReleaseRoute, createCustomAppRoute, exchangeAppSessionTokenRoute, fetchAppReleaseRoute, getAppInstallationRoute, getAppRoute, installAppRoute, issueAppOAuthTokenRoute, issueAppSessionTokenRoute, listAppInstallationAuditRoute, listAppInstallationsRoute, listAppReleasesRoute, listAppsRoute, listAppWebhooksRoute, pauseAppInstallationRoute, previewAppInstallationPurgeRoute, replayAppWebhookRoute, resumeAppInstallationRoute, revokeAppInstallationCredentialsRoute, uninstallAppInstallationRoute, } from "./routes-openapi.js";
|
|
6
9
|
import { createAppsService } from "./service.js";
|
|
7
10
|
import { createAppSessionTokenService } from "./session-token-service.js";
|
|
8
11
|
import { listAppWebhookHealth, replayAppWebhookDelivery } from "./webhook-delivery.js";
|
|
9
12
|
const appIdParamSchema = z.object({ appId: z.string().min(1) });
|
|
10
13
|
const installationIdParamSchema = z.object({ installationId: z.string().min(1) });
|
|
11
14
|
export function createAppsAdminRoutes(options = {}) {
|
|
12
|
-
const routes = new
|
|
15
|
+
const routes = new OpenAPIHono({ defaultHook: openApiValidationHook });
|
|
13
16
|
const service = createAppsService(options);
|
|
17
|
+
const installations = createAppInstallationService({
|
|
18
|
+
deploymentId: options.oauth?.deploymentId ?? options.deploymentId,
|
|
19
|
+
platformApiVersion: options.platformApiVersion,
|
|
20
|
+
eventBus: options.eventBus,
|
|
21
|
+
customFields: options.customFields,
|
|
22
|
+
});
|
|
14
23
|
const oauth = options.oauth ? createAppOAuthService(options.oauth) : null;
|
|
15
24
|
const sessionTokens = oauth && options.oauth && options.sessionToken
|
|
16
25
|
? createAppSessionTokenService({
|
|
@@ -20,11 +29,11 @@ export function createAppsAdminRoutes(options = {}) {
|
|
|
20
29
|
oauth,
|
|
21
30
|
})
|
|
22
31
|
: null;
|
|
23
|
-
routes.
|
|
32
|
+
routes.openapi(listAppsRoute, async (c) => {
|
|
24
33
|
const query = parseQuery(c, appListQuerySchema);
|
|
25
34
|
return c.json(await service.list(c.get("db"), query), 200);
|
|
26
35
|
});
|
|
27
|
-
routes.
|
|
36
|
+
routes.openapi(createCustomAppRoute, async (c) => {
|
|
28
37
|
const body = await parseJsonBody(c, createCustomAppRegistrationSchema);
|
|
29
38
|
const app = await service.createCustomApp(c.get("db"), body);
|
|
30
39
|
return c.json({ data: app }, 201);
|
|
@@ -32,7 +41,7 @@ export function createAppsAdminRoutes(options = {}) {
|
|
|
32
41
|
// Consent approval mutates state (installation, grants, authorization code),
|
|
33
42
|
// so it must never be reachable through read-scoped GET requests. The admin
|
|
34
43
|
// consent UI submits the approval and performs the redirect itself.
|
|
35
|
-
routes.
|
|
44
|
+
routes.openapi(authorizeAppOAuthRoute, async (c) => {
|
|
36
45
|
if (!oauth)
|
|
37
46
|
return c.json({ error: "App OAuth is not configured" }, 501);
|
|
38
47
|
const body = await parseJsonBody(c, appOAuthAuthorizeQuerySchema);
|
|
@@ -52,7 +61,7 @@ export function createAppsAdminRoutes(options = {}) {
|
|
|
52
61
|
redirectUrl.searchParams.set("state", result.state);
|
|
53
62
|
return c.json({ data: { redirectUrl: redirectUrl.toString(), state: result.state } }, 200);
|
|
54
63
|
});
|
|
55
|
-
routes.
|
|
64
|
+
routes.openapi(issueAppOAuthTokenRoute, async (c) => {
|
|
56
65
|
if (!oauth)
|
|
57
66
|
return c.json({ error: "App OAuth is not configured" }, 501);
|
|
58
67
|
const body = await parseJsonBody(c, appOAuthTokenSchema);
|
|
@@ -83,7 +92,7 @@ export function createAppsAdminRoutes(options = {}) {
|
|
|
83
92
|
});
|
|
84
93
|
return c.json(token, 200);
|
|
85
94
|
});
|
|
86
|
-
routes.
|
|
95
|
+
routes.openapi(revokeAppInstallationCredentialsRoute, async (c) => {
|
|
87
96
|
if (!oauth)
|
|
88
97
|
return c.json({ error: "App OAuth is not configured" }, 501);
|
|
89
98
|
const body = await parseJsonBody(c, appCredentialRevocationSchema);
|
|
@@ -93,7 +102,7 @@ export function createAppsAdminRoutes(options = {}) {
|
|
|
93
102
|
// Staff-authenticated: the admin host requests a short-lived session token for
|
|
94
103
|
// the current viewer + entity/slot context. The viewer is taken from the
|
|
95
104
|
// authenticated session, never from the frame.
|
|
96
|
-
routes.
|
|
105
|
+
routes.openapi(issueAppSessionTokenRoute, async (c) => {
|
|
97
106
|
if (!sessionTokens)
|
|
98
107
|
return c.json({ error: "App session tokens are not configured" }, 501);
|
|
99
108
|
const { installationId } = parseInstallationParams(c.req.param());
|
|
@@ -109,7 +118,7 @@ export function createAppsAdminRoutes(options = {}) {
|
|
|
109
118
|
});
|
|
110
119
|
// App-backend-facing: exchange a presented session token for online actor
|
|
111
120
|
// access. Client-authenticated; bounded by viewer ∩ app grants.
|
|
112
|
-
routes.
|
|
121
|
+
routes.openapi(exchangeAppSessionTokenRoute, async (c) => {
|
|
113
122
|
if (!sessionTokens)
|
|
114
123
|
return c.json({ error: "App session tokens are not configured" }, 501);
|
|
115
124
|
const body = await parseJsonBody(c, appSessionTokenExchangeSchema);
|
|
@@ -122,28 +131,112 @@ export function createAppsAdminRoutes(options = {}) {
|
|
|
122
131
|
});
|
|
123
132
|
return c.json(token, 200);
|
|
124
133
|
});
|
|
125
|
-
routes.
|
|
134
|
+
routes.openapi(installAppRoute, async (c) => {
|
|
135
|
+
const body = await parseJsonBody(c, installAppSchema);
|
|
136
|
+
// The deployment id is a runtime value (not known at graph-composition
|
|
137
|
+
// time), so resolve it per request: explicit body → runtime env →
|
|
138
|
+
// construction option. Without this the standard runtime mounts these
|
|
139
|
+
// routes with no deployment id and every install 400s (app_deployment_required).
|
|
140
|
+
const deploymentId = body.deploymentId ?? c.env?.VOYANT_CLOUD_DEPLOYMENT_ID?.trim() ?? options.deploymentId;
|
|
141
|
+
const result = await installations.install(c.get("db"), {
|
|
142
|
+
appId: body.appId,
|
|
143
|
+
releaseId: body.releaseId,
|
|
144
|
+
actorId: body.actorId,
|
|
145
|
+
grantedOptionalScopes: body.grantedOptionalScopes,
|
|
146
|
+
updatePolicy: body.updatePolicy,
|
|
147
|
+
deploymentId,
|
|
148
|
+
});
|
|
149
|
+
return c.json({ data: result }, 201);
|
|
150
|
+
});
|
|
151
|
+
routes.openapi(listAppInstallationsRoute, async (c) => {
|
|
152
|
+
const query = parseQuery(c, appInstallationListQuerySchema);
|
|
153
|
+
return c.json(await listInstallationSummaries(c.get("db"), query), 200);
|
|
154
|
+
});
|
|
155
|
+
routes.openapi(getAppInstallationRoute, async (c) => {
|
|
156
|
+
const { installationId } = parseInstallationParams(c.req.param());
|
|
157
|
+
const detail = await loadInstallationDetail(c.get("db"), installationId, {
|
|
158
|
+
platformApiVersion: options.platformApiVersion,
|
|
159
|
+
});
|
|
160
|
+
return detail
|
|
161
|
+
? c.json({ data: detail }, 200)
|
|
162
|
+
: c.json({ error: "App installation not found" }, 404);
|
|
163
|
+
});
|
|
164
|
+
routes.openapi(listAppInstallationAuditRoute, async (c) => {
|
|
165
|
+
const { installationId } = parseInstallationParams(c.req.param());
|
|
166
|
+
const query = parseQuery(c, appInstallationAuditQuerySchema);
|
|
167
|
+
const data = await listInstallationAudit(c.get("db"), installationId, query.limit);
|
|
168
|
+
return c.json({ data }, 200);
|
|
169
|
+
});
|
|
170
|
+
routes.openapi(pauseAppInstallationRoute, async (c) => {
|
|
171
|
+
const { installationId } = parseInstallationParams(c.req.param());
|
|
172
|
+
const body = await parseJsonBody(c, lifecycleActionBodySchema);
|
|
173
|
+
const result = await installations.pause(c.get("db"), { installationId, actorId: body.actorId });
|
|
174
|
+
return c.json({ data: result }, 200);
|
|
175
|
+
});
|
|
176
|
+
routes.openapi(resumeAppInstallationRoute, async (c) => {
|
|
177
|
+
const { installationId } = parseInstallationParams(c.req.param());
|
|
178
|
+
const body = await parseJsonBody(c, lifecycleActionBodySchema);
|
|
179
|
+
const result = await installations.resume(c.get("db"), {
|
|
180
|
+
installationId,
|
|
181
|
+
actorId: body.actorId,
|
|
182
|
+
});
|
|
183
|
+
return c.json({ data: result }, 200);
|
|
184
|
+
});
|
|
185
|
+
routes.openapi(uninstallAppInstallationRoute, async (c) => {
|
|
186
|
+
const { installationId } = parseInstallationParams(c.req.param());
|
|
187
|
+
const body = await parseJsonBody(c, lifecycleActionBodySchema);
|
|
188
|
+
const result = await installations.uninstall(c.get("db"), {
|
|
189
|
+
installationId,
|
|
190
|
+
actorId: body.actorId,
|
|
191
|
+
});
|
|
192
|
+
return c.json({ data: result }, 200);
|
|
193
|
+
});
|
|
194
|
+
routes.openapi(activateAppInstallationRoute, async (c) => {
|
|
195
|
+
const { installationId } = parseInstallationParams(c.req.param());
|
|
196
|
+
const body = await parseJsonBody(c, activateInstallationBodySchema);
|
|
197
|
+
const result = await installations.upgrade(c.get("db"), {
|
|
198
|
+
installationId,
|
|
199
|
+
releaseId: body.releaseId,
|
|
200
|
+
actorId: body.actorId,
|
|
201
|
+
});
|
|
202
|
+
return c.json({ data: result }, 200);
|
|
203
|
+
});
|
|
204
|
+
routes.openapi(previewAppInstallationPurgeRoute, async (c) => {
|
|
205
|
+
const { installationId } = parseInstallationParams(c.req.param());
|
|
206
|
+
const body = await parseJsonBody(c, lifecycleActionBodySchema);
|
|
207
|
+
const result = await installations.purgePreview(c.get("db"), {
|
|
208
|
+
installationId,
|
|
209
|
+
actorId: body.actorId,
|
|
210
|
+
});
|
|
211
|
+
return c.json({ data: result }, 200);
|
|
212
|
+
});
|
|
213
|
+
routes.openapi(getAppRoute, async (c) => {
|
|
126
214
|
const { appId } = parseParams(c.req.param());
|
|
127
215
|
const app = await service.get(c.get("db"), appId);
|
|
128
216
|
return app ? c.json({ data: app }, 200) : c.json({ error: "App not found" }, 404);
|
|
129
217
|
});
|
|
130
|
-
routes.
|
|
218
|
+
routes.openapi(listAppReleasesRoute, async (c) => {
|
|
219
|
+
const { appId } = parseParams(c.req.param());
|
|
220
|
+
const data = await listAppReleases(c.get("db"), appId);
|
|
221
|
+
return c.json({ data }, 200);
|
|
222
|
+
});
|
|
223
|
+
routes.openapi(createAppReleaseRoute, async (c) => {
|
|
131
224
|
const { appId } = parseParams(c.req.param());
|
|
132
225
|
const body = await parseJsonBody(c, releaseManifestUploadSchema);
|
|
133
226
|
const result = await service.releaseFromUpload(c.get("db"), appId, body);
|
|
134
227
|
return c.json({ data: result.release, digest: result.digest, created: result.created }, 201);
|
|
135
228
|
});
|
|
136
|
-
routes.
|
|
229
|
+
routes.openapi(fetchAppReleaseRoute, async (c) => {
|
|
137
230
|
const { appId } = parseParams(c.req.param());
|
|
138
231
|
const body = await parseJsonBody(c, releaseManifestFetchSchema);
|
|
139
232
|
const result = await service.releaseFromFetch(c.get("db"), appId, body);
|
|
140
233
|
return c.json({ data: result.release, digest: result.digest, created: result.created }, 201);
|
|
141
234
|
});
|
|
142
|
-
routes.
|
|
235
|
+
routes.openapi(listAppWebhooksRoute, async (c) => {
|
|
143
236
|
const { installationId } = parseInstallationParams(c.req.param());
|
|
144
237
|
return c.json(await listAppWebhookHealth(c.get("db"), installationId), 200);
|
|
145
238
|
});
|
|
146
|
-
routes.
|
|
239
|
+
routes.openapi(replayAppWebhookRoute, async (c) => {
|
|
147
240
|
parseInstallationParams(c.req.param());
|
|
148
241
|
const body = await parseJsonBody(c, appWebhookReplaySchema);
|
|
149
242
|
const delivery = await replayAppWebhookDelivery(c.get("db"), {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createAppsRuntimePortContribution(): Readonly<Record<string, unknown>>;
|
package/dist/voyant.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { defineModule, requirePort } from "@voyant-travel/core/project";
|
|
2
2
|
import { customFieldValueLifecycleRuntimePort, customFieldValueOperationsRuntimePort, } from "@voyant-travel/core/runtime-port";
|
|
3
|
+
const appsAdminRuntime = {
|
|
4
|
+
entry: "@voyant-travel/apps-react/admin",
|
|
5
|
+
export: "createSelectedAppsAdminExtension",
|
|
6
|
+
};
|
|
3
7
|
const appInstallationLifecyclePayloadSchema = {
|
|
4
8
|
type: "object",
|
|
5
9
|
additionalProperties: false,
|
|
@@ -29,6 +33,7 @@ export const appsVoyantModule = defineModule({
|
|
|
29
33
|
id: "@voyant-travel/apps#api.admin",
|
|
30
34
|
surface: "admin",
|
|
31
35
|
mount: "apps",
|
|
36
|
+
openapi: { document: "apps" },
|
|
32
37
|
resource: "apps",
|
|
33
38
|
transactional: true,
|
|
34
39
|
runtime: {
|
|
@@ -107,7 +112,18 @@ export const appsVoyantModule = defineModule({
|
|
|
107
112
|
label: "Own custom-field definitions",
|
|
108
113
|
description: "Read and write custom-field definitions owned by the app.",
|
|
109
114
|
remoteSafe: true,
|
|
110
|
-
actions: [
|
|
115
|
+
actions: [
|
|
116
|
+
{
|
|
117
|
+
action: "read",
|
|
118
|
+
label: "Read custom-field definitions",
|
|
119
|
+
description: "Read definitions owned by the app.",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
action: "write",
|
|
123
|
+
label: "Write custom-field definitions",
|
|
124
|
+
description: "Create and update definitions owned by the app.",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
111
127
|
wildcard: "explicit-resource",
|
|
112
128
|
},
|
|
113
129
|
{
|
|
@@ -116,17 +132,55 @@ export const appsVoyantModule = defineModule({
|
|
|
116
132
|
label: "Own custom-field values",
|
|
117
133
|
description: "Read and write app-owned custom-field values by target.",
|
|
118
134
|
remoteSafe: true,
|
|
119
|
-
actions: [
|
|
135
|
+
actions: [
|
|
136
|
+
{
|
|
137
|
+
action: "read",
|
|
138
|
+
label: "Read custom-field values",
|
|
139
|
+
description: "Read custom-field values owned by the app.",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
action: "write",
|
|
143
|
+
label: "Write custom-field values",
|
|
144
|
+
description: "Write custom-field values owned by the app.",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
action: "booking",
|
|
148
|
+
label: "Booking custom fields",
|
|
149
|
+
description: "Access app-owned custom-field values on bookings.",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
action: "person",
|
|
153
|
+
label: "Person custom fields",
|
|
154
|
+
description: "Access app-owned custom-field values on people.",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
action: "organization",
|
|
158
|
+
label: "Organization custom fields",
|
|
159
|
+
description: "Access app-owned custom-field values on organizations.",
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
action: "invoice",
|
|
163
|
+
label: "Invoice custom fields",
|
|
164
|
+
description: "Access app-owned custom-field values on invoices.",
|
|
165
|
+
},
|
|
166
|
+
],
|
|
120
167
|
wildcard: "explicit-resource",
|
|
121
168
|
},
|
|
122
169
|
{
|
|
123
|
-
id: "@voyant-travel/apps#access.webhooks",
|
|
124
|
-
|
|
170
|
+
id: "@voyant-travel/apps#access.app-webhooks",
|
|
171
|
+
// Namespaced `app-webhooks` (not `webhooks`) to avoid a duplicate
|
|
172
|
+
// access-resource authority with @voyant-travel/workflow-runs, which
|
|
173
|
+
// owns the deployment `webhooks` resource.
|
|
174
|
+
resource: "app-webhooks",
|
|
125
175
|
label: "App webhooks",
|
|
126
176
|
description: "Read webhook health and request replay.",
|
|
127
177
|
remoteSafe: true,
|
|
128
178
|
actions: [
|
|
129
|
-
|
|
179
|
+
{
|
|
180
|
+
action: "read",
|
|
181
|
+
label: "Read app webhooks",
|
|
182
|
+
description: "Read app webhook subscription and delivery health.",
|
|
183
|
+
},
|
|
130
184
|
{ action: "replay", label: "Replay webhooks", description: "Replay deliveries." },
|
|
131
185
|
],
|
|
132
186
|
},
|
|
@@ -136,7 +190,13 @@ export const appsVoyantModule = defineModule({
|
|
|
136
190
|
label: "App audit history",
|
|
137
191
|
description: "Read audit history owned by the app.",
|
|
138
192
|
remoteSafe: true,
|
|
139
|
-
actions: [
|
|
193
|
+
actions: [
|
|
194
|
+
{
|
|
195
|
+
action: "read",
|
|
196
|
+
label: "Read app audit history",
|
|
197
|
+
description: "Read audit events owned by the app.",
|
|
198
|
+
},
|
|
199
|
+
],
|
|
140
200
|
},
|
|
141
201
|
{
|
|
142
202
|
id: "@voyant-travel/apps#access.online-token",
|
|
@@ -144,7 +204,14 @@ export const appsVoyantModule = defineModule({
|
|
|
144
204
|
label: "Online token exchange",
|
|
145
205
|
description: "Exchange admin session context for online app access.",
|
|
146
206
|
remoteSafe: true,
|
|
147
|
-
actions: [
|
|
207
|
+
actions: [
|
|
208
|
+
{
|
|
209
|
+
action: "exchange",
|
|
210
|
+
label: "Exchange online token",
|
|
211
|
+
description: "Exchange admin session context for online app access.",
|
|
212
|
+
wildcard: "explicit",
|
|
213
|
+
},
|
|
214
|
+
],
|
|
148
215
|
},
|
|
149
216
|
{
|
|
150
217
|
id: "@voyant-travel/apps#access.finance-documents",
|
|
@@ -152,7 +219,13 @@ export const appsVoyantModule = defineModule({
|
|
|
152
219
|
label: "Finance documents",
|
|
153
220
|
description: "Read finance documents visible to remote accounting apps.",
|
|
154
221
|
remoteSafe: true,
|
|
155
|
-
actions: [
|
|
222
|
+
actions: [
|
|
223
|
+
{
|
|
224
|
+
action: "read",
|
|
225
|
+
label: "Read finance documents",
|
|
226
|
+
description: "Read finance documents visible to the app.",
|
|
227
|
+
},
|
|
228
|
+
],
|
|
156
229
|
wildcard: "explicit-resource",
|
|
157
230
|
},
|
|
158
231
|
{
|
|
@@ -162,9 +235,27 @@ export const appsVoyantModule = defineModule({
|
|
|
162
235
|
description: "Request approved finance document actions.",
|
|
163
236
|
remoteSafe: true,
|
|
164
237
|
actions: [
|
|
165
|
-
{
|
|
166
|
-
|
|
167
|
-
|
|
238
|
+
{
|
|
239
|
+
action: "issue",
|
|
240
|
+
label: "Issue finance documents",
|
|
241
|
+
description: "Request approved finance document issuance.",
|
|
242
|
+
sensitive: true,
|
|
243
|
+
wildcard: "explicit",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
action: "retry",
|
|
247
|
+
label: "Retry finance actions",
|
|
248
|
+
description: "Request approved finance action retry.",
|
|
249
|
+
sensitive: true,
|
|
250
|
+
wildcard: "explicit",
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
action: "reconcile",
|
|
254
|
+
label: "Reconcile finance documents",
|
|
255
|
+
description: "Request approved finance document reconciliation.",
|
|
256
|
+
sensitive: true,
|
|
257
|
+
wildcard: "explicit",
|
|
258
|
+
},
|
|
168
259
|
],
|
|
169
260
|
wildcard: "explicit-resource",
|
|
170
261
|
},
|
|
@@ -189,6 +280,49 @@ export const appsVoyantModule = defineModule({
|
|
|
189
280
|
},
|
|
190
281
|
],
|
|
191
282
|
},
|
|
283
|
+
admin: {
|
|
284
|
+
compositionOrder: 170,
|
|
285
|
+
runtime: appsAdminRuntime,
|
|
286
|
+
copy: [
|
|
287
|
+
{
|
|
288
|
+
id: "@voyant-travel/apps#admin.copy",
|
|
289
|
+
namespace: "apps.admin",
|
|
290
|
+
fallbackLocale: "en",
|
|
291
|
+
runtime: {
|
|
292
|
+
entry: "@voyant-travel/apps-react/i18n",
|
|
293
|
+
export: "appsUiMessageDefinitions",
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
routes: [
|
|
298
|
+
{
|
|
299
|
+
id: "@voyant-travel/apps#admin.route.installed",
|
|
300
|
+
path: "/apps",
|
|
301
|
+
requiredScopes: ["apps:read"],
|
|
302
|
+
runtime: appsAdminRuntime,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: "@voyant-travel/apps#admin.route.developer",
|
|
306
|
+
path: "/apps/developer",
|
|
307
|
+
requiredScopes: ["apps:write"],
|
|
308
|
+
runtime: appsAdminRuntime,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
nav: [
|
|
312
|
+
{
|
|
313
|
+
id: "@voyant-travel/apps#admin.nav.installed",
|
|
314
|
+
routeId: "@voyant-travel/apps#admin.route.installed",
|
|
315
|
+
label: { namespace: "apps.admin", key: "navigation.title" },
|
|
316
|
+
order: 170,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "@voyant-travel/apps#admin.nav.developer",
|
|
320
|
+
routeId: "@voyant-travel/apps#admin.route.developer",
|
|
321
|
+
label: { namespace: "apps.admin", key: "navigation.developerTitle" },
|
|
322
|
+
order: 171,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
192
326
|
lifecycle: { uninstall: { default: "retain-data", purge: "not-supported" } },
|
|
193
327
|
meta: {
|
|
194
328
|
ownership: "package",
|