@agent-foundry/studio 1.0.0 → 1.0.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/README.md +180 -47
- package/dist/bff/client.d.ts +168 -0
- package/dist/bff/client.d.ts.map +1 -0
- package/dist/bff/client.js +288 -0
- package/dist/bff/client.js.map +1 -0
- package/dist/bff/index.d.ts +27 -0
- package/dist/bff/index.d.ts.map +1 -0
- package/dist/bff/index.js +26 -0
- package/dist/bff/index.js.map +1 -0
- package/dist/bff/types.d.ts +168 -0
- package/dist/bff/types.d.ts.map +1 -0
- package/dist/bff/types.js +8 -0
- package/dist/bff/types.js.map +1 -0
- package/dist/db/deployments.d.ts +67 -1
- package/dist/db/deployments.d.ts.map +1 -1
- package/dist/db/deployments.js +92 -1
- package/dist/db/deployments.js.map +1 -1
- package/dist/db/index.d.ts +17 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +17 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/projects.d.ts +54 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/db/projects.js +79 -1
- package/dist/db/projects.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/types/deployment.d.ts +56 -0
- package/dist/types/deployment.d.ts.map +1 -1
- package/dist/types/project.d.ts +6 -0
- package/dist/types/project.d.ts.map +1 -1
- package/package.json +14 -4
- package/src/bff/client.ts +412 -0
- package/src/bff/index.ts +32 -0
- package/src/bff/types.ts +212 -0
- package/src/db/deployments.ts +99 -1
- package/src/db/index.ts +17 -0
- package/src/db/projects.ts +86 -1
- package/src/db/schema.sql +35 -52
- package/src/index.ts +18 -2
- package/src/types/deployment.ts +75 -0
- package/src/types/project.ts +7 -0
package/src/bff/types.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BFF API Types
|
|
3
|
+
*
|
|
4
|
+
* Response types from the BFF Studio API endpoints.
|
|
5
|
+
* These match the Pydantic models in services/bff/app/models/studio.py
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
StudioProject,
|
|
10
|
+
ProjectConfig,
|
|
11
|
+
ProjectFramework,
|
|
12
|
+
} from '../types/project';
|
|
13
|
+
import type {
|
|
14
|
+
DeploymentStatus,
|
|
15
|
+
DeploymentMetadata,
|
|
16
|
+
AppManifest,
|
|
17
|
+
} from '../types/deployment';
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Project API Types
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Request body for creating a project via BFF
|
|
25
|
+
*/
|
|
26
|
+
export interface CreateProjectRequest {
|
|
27
|
+
name: string;
|
|
28
|
+
slug: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
rootPath: string;
|
|
31
|
+
framework?: ProjectFramework;
|
|
32
|
+
config?: ProjectConfig;
|
|
33
|
+
parentProjectId?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Request body for updating a project via BFF
|
|
38
|
+
*/
|
|
39
|
+
export interface UpdateProjectRequest {
|
|
40
|
+
name?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
config?: Partial<ProjectConfig>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Request body for forking a project via BFF
|
|
47
|
+
*/
|
|
48
|
+
export interface ForkProjectRequest {
|
|
49
|
+
newSlug: string;
|
|
50
|
+
newRootPath: string;
|
|
51
|
+
newName?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Project response from BFF API
|
|
56
|
+
*/
|
|
57
|
+
export interface ProjectResponse extends StudioProject {
|
|
58
|
+
latestDeployment?: DeploymentSummary;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Paginated project list response
|
|
63
|
+
*/
|
|
64
|
+
export interface ProjectListResponse {
|
|
65
|
+
projects: ProjectResponse[];
|
|
66
|
+
total: number;
|
|
67
|
+
page: number;
|
|
68
|
+
pageSize: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// Deployment API Types
|
|
73
|
+
// =============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Deployment summary (for lists)
|
|
77
|
+
*/
|
|
78
|
+
export interface DeploymentSummary {
|
|
79
|
+
id: string;
|
|
80
|
+
version: string;
|
|
81
|
+
status: DeploymentStatus;
|
|
82
|
+
ossUrl?: string;
|
|
83
|
+
bundleSizeBytes?: number;
|
|
84
|
+
createdAt: string;
|
|
85
|
+
publishedAt?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Request body for starting a deployment
|
|
90
|
+
*/
|
|
91
|
+
export interface StartDeploymentRequest {
|
|
92
|
+
projectId: string;
|
|
93
|
+
version?: string;
|
|
94
|
+
metadata?: Partial<DeploymentMetadata>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* STS credentials for OSS upload
|
|
99
|
+
*/
|
|
100
|
+
export interface STSCredentials {
|
|
101
|
+
accessKeyId: string;
|
|
102
|
+
accessKeySecret: string;
|
|
103
|
+
securityToken: string;
|
|
104
|
+
expiration: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Response from starting a deployment
|
|
109
|
+
*/
|
|
110
|
+
export interface StartDeploymentResponse {
|
|
111
|
+
deploymentId: string;
|
|
112
|
+
credentials: STSCredentials;
|
|
113
|
+
bucket: string;
|
|
114
|
+
region: string;
|
|
115
|
+
keyPrefix: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Request body for updating deployment status
|
|
120
|
+
*/
|
|
121
|
+
export interface UpdateDeploymentStatusRequest {
|
|
122
|
+
status: DeploymentStatus;
|
|
123
|
+
buildLog?: string;
|
|
124
|
+
errorMessage?: string;
|
|
125
|
+
metadata?: Partial<DeploymentMetadata>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Request body for completing a deployment
|
|
130
|
+
*/
|
|
131
|
+
export interface CompleteDeploymentRequest {
|
|
132
|
+
bucket: string;
|
|
133
|
+
keyPrefix: string;
|
|
134
|
+
ossUrl: string;
|
|
135
|
+
totalBytes: number;
|
|
136
|
+
fileCount: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Response from completing a deployment
|
|
141
|
+
*/
|
|
142
|
+
export interface CompleteDeploymentResponse {
|
|
143
|
+
deploymentId: string;
|
|
144
|
+
ossUrl: string;
|
|
145
|
+
version: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Request body for failing a deployment
|
|
150
|
+
*/
|
|
151
|
+
export interface FailDeploymentRequest {
|
|
152
|
+
errorMessage: string;
|
|
153
|
+
buildLog?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Deployment list response
|
|
158
|
+
*/
|
|
159
|
+
export interface DeploymentListResponse {
|
|
160
|
+
deployments: DeploymentSummary[];
|
|
161
|
+
total: number;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// =============================================================================
|
|
165
|
+
// Publish API Types
|
|
166
|
+
// =============================================================================
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Request body for publishing to Feed
|
|
170
|
+
*/
|
|
171
|
+
export interface PublishRequest {
|
|
172
|
+
deploymentId?: string;
|
|
173
|
+
manifest?: AppManifest;
|
|
174
|
+
status?: 'canary' | 'stable';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Response from publishing to Feed
|
|
179
|
+
*/
|
|
180
|
+
export interface PublishResponse {
|
|
181
|
+
appId: string;
|
|
182
|
+
artifactId: string;
|
|
183
|
+
deploymentId: string;
|
|
184
|
+
shareUrl: string;
|
|
185
|
+
entryUrl: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// =============================================================================
|
|
189
|
+
// Error Types
|
|
190
|
+
// =============================================================================
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* API error response
|
|
194
|
+
*/
|
|
195
|
+
export interface APIError {
|
|
196
|
+
detail: string;
|
|
197
|
+
code?: string;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* BFF client configuration
|
|
202
|
+
*/
|
|
203
|
+
export interface BFFClientConfig {
|
|
204
|
+
/** BFF base URL (e.g., "http://localhost:11001") */
|
|
205
|
+
baseUrl: string;
|
|
206
|
+
|
|
207
|
+
/** Supabase JWT token for authentication */
|
|
208
|
+
authToken: string;
|
|
209
|
+
|
|
210
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
211
|
+
timeout?: number;
|
|
212
|
+
}
|
package/src/db/deployments.ts
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Deployments Repository
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Provides access to studio_deployments table via Supabase client.
|
|
5
|
+
*
|
|
6
|
+
* ## Important: Hybrid Access Pattern
|
|
7
|
+
*
|
|
8
|
+
* Due to RLS (Row Level Security) restrictions, this repository should
|
|
9
|
+
* primarily be used for **read operations only**.
|
|
10
|
+
*
|
|
11
|
+
* **For write operations, use the BFF client or DirectUploader:**
|
|
12
|
+
*
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { DirectUploader } from '@agent-foundry/studio/oss';
|
|
15
|
+
*
|
|
16
|
+
* // DirectUploader handles the complete deployment workflow via BFF
|
|
17
|
+
* const uploader = new DirectUploader({
|
|
18
|
+
* bffBaseUrl: 'http://localhost:11001',
|
|
19
|
+
* authToken: 'your-jwt-token',
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* const result = await uploader.upload({
|
|
23
|
+
* projectId: 'project-uuid',
|
|
24
|
+
* files: distFiles,
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Or use the BFF client directly:
|
|
29
|
+
*
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { createBFFClient } from '@agent-foundry/studio/bff';
|
|
32
|
+
*
|
|
33
|
+
* const bff = createBFFClient({ ... });
|
|
34
|
+
* const deployment = await bff.deployments.start({ projectId: '...' });
|
|
35
|
+
* ```
|
|
5
36
|
*/
|
|
6
37
|
|
|
7
38
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
@@ -26,6 +57,7 @@ interface DeploymentRow {
|
|
|
26
57
|
oss_bucket: string | null;
|
|
27
58
|
oss_key: string | null;
|
|
28
59
|
oss_url: string | null;
|
|
60
|
+
artifact_id: string | null;
|
|
29
61
|
bundle_size_bytes: number | null;
|
|
30
62
|
build_log: string | null;
|
|
31
63
|
error_message: string | null;
|
|
@@ -47,6 +79,7 @@ function rowToDeployment(row: DeploymentRow): Deployment {
|
|
|
47
79
|
ossBucket: row.oss_bucket ?? undefined,
|
|
48
80
|
ossKey: row.oss_key ?? undefined,
|
|
49
81
|
ossUrl: row.oss_url ?? undefined,
|
|
82
|
+
artifactId: row.artifact_id ?? undefined,
|
|
50
83
|
bundleSizeBytes: row.bundle_size_bytes ?? undefined,
|
|
51
84
|
buildLog: row.build_log ?? undefined,
|
|
52
85
|
errorMessage: row.error_message ?? undefined,
|
|
@@ -79,6 +112,12 @@ export class DeploymentsRepository {
|
|
|
79
112
|
|
|
80
113
|
/**
|
|
81
114
|
* Create a new deployment
|
|
115
|
+
*
|
|
116
|
+
* @deprecated Use `DirectUploader.upload()` or `createBFFClient().deployments.start()` instead.
|
|
117
|
+
*
|
|
118
|
+
* This method may fail with RLS errors. The recommended workflow is:
|
|
119
|
+
* 1. Use DirectUploader which handles the complete deployment lifecycle
|
|
120
|
+
* 2. Or use BFF client's deployments.start() to get STS credentials
|
|
82
121
|
*/
|
|
83
122
|
async create(input: CreateDeploymentInput): Promise<Deployment> {
|
|
84
123
|
const { data: { user } } = await this.supabase.auth.getUser();
|
|
@@ -168,6 +207,10 @@ export class DeploymentsRepository {
|
|
|
168
207
|
|
|
169
208
|
/**
|
|
170
209
|
* Update a deployment
|
|
210
|
+
*
|
|
211
|
+
* @deprecated Use `createBFFClient().deployments.updateStatus()` instead.
|
|
212
|
+
*
|
|
213
|
+
* This method may fail with RLS errors. Use BFF client for write operations.
|
|
171
214
|
*/
|
|
172
215
|
async update(id: string, input: UpdateDeploymentInput): Promise<Deployment> {
|
|
173
216
|
const updateData: Record<string, unknown> = {};
|
|
@@ -221,6 +264,8 @@ export class DeploymentsRepository {
|
|
|
221
264
|
|
|
222
265
|
/**
|
|
223
266
|
* Mark deployment as building
|
|
267
|
+
*
|
|
268
|
+
* @deprecated Use `createBFFClient().deployments.updateStatus()` instead.
|
|
224
269
|
*/
|
|
225
270
|
async markBuilding(id: string): Promise<Deployment> {
|
|
226
271
|
return this.update(id, { status: 'building' });
|
|
@@ -228,6 +273,8 @@ export class DeploymentsRepository {
|
|
|
228
273
|
|
|
229
274
|
/**
|
|
230
275
|
* Mark deployment as uploading
|
|
276
|
+
*
|
|
277
|
+
* @deprecated Use `createBFFClient().deployments.updateStatus()` instead.
|
|
231
278
|
*/
|
|
232
279
|
async markUploading(id: string): Promise<Deployment> {
|
|
233
280
|
return this.update(id, { status: 'uploading' });
|
|
@@ -235,6 +282,8 @@ export class DeploymentsRepository {
|
|
|
235
282
|
|
|
236
283
|
/**
|
|
237
284
|
* Mark deployment as published
|
|
285
|
+
*
|
|
286
|
+
* @deprecated Use `createBFFClient().deployments.complete()` instead.
|
|
238
287
|
*/
|
|
239
288
|
async markPublished(
|
|
240
289
|
id: string,
|
|
@@ -252,6 +301,8 @@ export class DeploymentsRepository {
|
|
|
252
301
|
|
|
253
302
|
/**
|
|
254
303
|
* Mark deployment as failed
|
|
304
|
+
*
|
|
305
|
+
* @deprecated Use `createBFFClient().deployments.fail()` instead.
|
|
255
306
|
*/
|
|
256
307
|
async markFailed(id: string, errorMessage: string, buildLog?: string): Promise<Deployment> {
|
|
257
308
|
return this.update(id, {
|
|
@@ -286,6 +337,9 @@ export class DeploymentsRepository {
|
|
|
286
337
|
|
|
287
338
|
/**
|
|
288
339
|
* Delete a deployment
|
|
340
|
+
*
|
|
341
|
+
* @deprecated Deployment deletion should be handled server-side.
|
|
342
|
+
* This method may fail with RLS errors.
|
|
289
343
|
*/
|
|
290
344
|
async delete(id: string): Promise<void> {
|
|
291
345
|
const { error } = await this.supabase
|
|
@@ -300,6 +354,9 @@ export class DeploymentsRepository {
|
|
|
300
354
|
|
|
301
355
|
/**
|
|
302
356
|
* Append to build log
|
|
357
|
+
*
|
|
358
|
+
* @deprecated Use `createBFFClient().deployments.updateStatus()` with buildLog field.
|
|
359
|
+
* This method may fail with RLS errors.
|
|
303
360
|
*/
|
|
304
361
|
async appendBuildLog(id: string, logLine: string): Promise<void> {
|
|
305
362
|
const existing = await this.getById(id);
|
|
@@ -313,4 +370,45 @@ export class DeploymentsRepository {
|
|
|
313
370
|
|
|
314
371
|
await this.update(id, { buildLog: newLog });
|
|
315
372
|
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Link deployment to an artifact (called after publishing to Feed)
|
|
376
|
+
*
|
|
377
|
+
* @deprecated This is handled automatically by BFF's publish workflow.
|
|
378
|
+
* Use `createBFFClient().projects.publish()` instead.
|
|
379
|
+
*/
|
|
380
|
+
async linkToArtifact(id: string, artifactId: string): Promise<Deployment> {
|
|
381
|
+
const { data, error } = await this.supabase
|
|
382
|
+
.from(this.TABLE)
|
|
383
|
+
.update({ artifact_id: artifactId })
|
|
384
|
+
.eq('id', id)
|
|
385
|
+
.select()
|
|
386
|
+
.single();
|
|
387
|
+
|
|
388
|
+
if (error) {
|
|
389
|
+
throw new Error(`Failed to link deployment to artifact: ${error.message}`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return rowToDeployment(data as DeploymentRow);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Get deployment by artifact ID
|
|
397
|
+
*/
|
|
398
|
+
async getByArtifactId(artifactId: string): Promise<Deployment | null> {
|
|
399
|
+
const { data, error } = await this.supabase
|
|
400
|
+
.from(this.TABLE)
|
|
401
|
+
.select()
|
|
402
|
+
.eq('artifact_id', artifactId)
|
|
403
|
+
.single();
|
|
404
|
+
|
|
405
|
+
if (error) {
|
|
406
|
+
if (error.code === 'PGRST116') {
|
|
407
|
+
return null; // Not found
|
|
408
|
+
}
|
|
409
|
+
throw new Error(`Failed to get deployment: ${error.message}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return rowToDeployment(data as DeploymentRow);
|
|
413
|
+
}
|
|
316
414
|
}
|
package/src/db/index.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database module - Supabase client for Studio
|
|
3
|
+
*
|
|
4
|
+
* ## Important: Read-Only Usage Recommended
|
|
5
|
+
*
|
|
6
|
+
* Due to RLS (Row Level Security), this module should primarily be used
|
|
7
|
+
* for **read operations only**. For write operations, use the BFF client:
|
|
8
|
+
*
|
|
9
|
+
* ```typescript
|
|
10
|
+
* // Read operations - use this module
|
|
11
|
+
* import { createStudioClient } from '@agent-foundry/studio/db';
|
|
12
|
+
* const db = createStudioClient({ supabaseUrl: '...', supabaseKey: '...' });
|
|
13
|
+
* const projects = await db.projects.list();
|
|
14
|
+
*
|
|
15
|
+
* // Write operations - use BFF client
|
|
16
|
+
* import { createBFFClient } from '@agent-foundry/studio/bff';
|
|
17
|
+
* const bff = createBFFClient({ baseUrl: '...', authToken: '...' });
|
|
18
|
+
* const project = await bff.projects.create({ ... });
|
|
19
|
+
* ```
|
|
3
20
|
*/
|
|
4
21
|
|
|
5
22
|
export { createStudioClient, type StudioClientConfig } from './client';
|
package/src/db/projects.ts
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Projects Repository
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Provides access to studio_projects table via Supabase client.
|
|
5
|
+
*
|
|
6
|
+
* ## Important: Hybrid Access Pattern
|
|
7
|
+
*
|
|
8
|
+
* Due to RLS (Row Level Security) restrictions, this repository should
|
|
9
|
+
* primarily be used for **read operations only**.
|
|
10
|
+
*
|
|
11
|
+
* **For write operations (create, update, delete, fork), use the BFF client:**
|
|
12
|
+
*
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { createBFFClient } from '@agent-foundry/studio/bff';
|
|
15
|
+
*
|
|
16
|
+
* const bff = createBFFClient({
|
|
17
|
+
* baseUrl: 'http://localhost:11001',
|
|
18
|
+
* authToken: 'your-jwt-token',
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Create project via BFF (recommended)
|
|
22
|
+
* const project = await bff.projects.create({ ... });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* The BFF uses service_role credentials which bypass RLS, ensuring
|
|
26
|
+
* consistent and secure write operations.
|
|
5
27
|
*/
|
|
6
28
|
|
|
7
29
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
@@ -22,6 +44,7 @@ interface ProjectRow {
|
|
|
22
44
|
name: string;
|
|
23
45
|
slug: string;
|
|
24
46
|
description: string | null;
|
|
47
|
+
app_id: string | null;
|
|
25
48
|
root_path: string;
|
|
26
49
|
framework: string;
|
|
27
50
|
config: ProjectConfig;
|
|
@@ -40,6 +63,7 @@ function rowToProject(row: ProjectRow): StudioProject {
|
|
|
40
63
|
name: row.name,
|
|
41
64
|
slug: row.slug,
|
|
42
65
|
description: row.description ?? undefined,
|
|
66
|
+
appId: row.app_id ?? undefined,
|
|
43
67
|
rootPath: row.root_path,
|
|
44
68
|
framework: row.framework as StudioProject['framework'],
|
|
45
69
|
config: row.config,
|
|
@@ -59,6 +83,11 @@ export class ProjectsRepository {
|
|
|
59
83
|
|
|
60
84
|
/**
|
|
61
85
|
* Create a new project
|
|
86
|
+
*
|
|
87
|
+
* @deprecated Use BFF client instead: `createBFFClient().projects.create()`
|
|
88
|
+
*
|
|
89
|
+
* This method may fail with RLS errors when using anon key.
|
|
90
|
+
* The BFF client uses service_role credentials which bypass RLS.
|
|
62
91
|
*/
|
|
63
92
|
async create(input: CreateProjectInput): Promise<StudioProject> {
|
|
64
93
|
const { data: { user } } = await this.supabase.auth.getUser();
|
|
@@ -169,6 +198,11 @@ export class ProjectsRepository {
|
|
|
169
198
|
|
|
170
199
|
/**
|
|
171
200
|
* Update a project
|
|
201
|
+
*
|
|
202
|
+
* @deprecated Use BFF client instead: `createBFFClient().projects.update()`
|
|
203
|
+
*
|
|
204
|
+
* This method may fail with RLS errors when using anon key.
|
|
205
|
+
* The BFF client uses service_role credentials which bypass RLS.
|
|
172
206
|
*/
|
|
173
207
|
async update(id: string, input: UpdateProjectInput): Promise<StudioProject> {
|
|
174
208
|
const updateData: Record<string, unknown> = {};
|
|
@@ -204,6 +238,11 @@ export class ProjectsRepository {
|
|
|
204
238
|
|
|
205
239
|
/**
|
|
206
240
|
* Delete a project
|
|
241
|
+
*
|
|
242
|
+
* @deprecated Use BFF client instead: `createBFFClient().projects.delete()`
|
|
243
|
+
*
|
|
244
|
+
* This method may fail with RLS errors when using anon key.
|
|
245
|
+
* The BFF client uses service_role credentials which bypass RLS.
|
|
207
246
|
*/
|
|
208
247
|
async delete(id: string): Promise<void> {
|
|
209
248
|
const { error } = await this.supabase
|
|
@@ -218,6 +257,11 @@ export class ProjectsRepository {
|
|
|
218
257
|
|
|
219
258
|
/**
|
|
220
259
|
* Fork a project (create a copy)
|
|
260
|
+
*
|
|
261
|
+
* @deprecated Use BFF client instead: `createBFFClient().projects.fork()`
|
|
262
|
+
*
|
|
263
|
+
* This method may fail with RLS errors when using anon key.
|
|
264
|
+
* The BFF client uses service_role credentials which bypass RLS.
|
|
221
265
|
*/
|
|
222
266
|
async fork(id: string, newSlug: string, newRootPath: string): Promise<StudioProject> {
|
|
223
267
|
const original = await this.getById(id);
|
|
@@ -243,4 +287,45 @@ export class ProjectsRepository {
|
|
|
243
287
|
const existing = await this.getBySlug(slug);
|
|
244
288
|
return existing === null;
|
|
245
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Link project to a registered app (called after publishing to Feed)
|
|
293
|
+
*
|
|
294
|
+
* @deprecated This is handled automatically by BFF's publish workflow.
|
|
295
|
+
* Use `createBFFClient().projects.publish()` instead.
|
|
296
|
+
*/
|
|
297
|
+
async linkToApp(id: string, appId: string): Promise<StudioProject> {
|
|
298
|
+
const { data, error } = await this.supabase
|
|
299
|
+
.from(this.TABLE)
|
|
300
|
+
.update({ app_id: appId })
|
|
301
|
+
.eq('id', id)
|
|
302
|
+
.select()
|
|
303
|
+
.single();
|
|
304
|
+
|
|
305
|
+
if (error) {
|
|
306
|
+
throw new Error(`Failed to link project to app: ${error.message}`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return rowToProject(data as ProjectRow);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get project by app ID
|
|
314
|
+
*/
|
|
315
|
+
async getByAppId(appId: string): Promise<StudioProject | null> {
|
|
316
|
+
const { data, error } = await this.supabase
|
|
317
|
+
.from(this.TABLE)
|
|
318
|
+
.select()
|
|
319
|
+
.eq('app_id', appId)
|
|
320
|
+
.single();
|
|
321
|
+
|
|
322
|
+
if (error) {
|
|
323
|
+
if (error.code === 'PGRST116') {
|
|
324
|
+
return null; // Not found
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Failed to get project: ${error.message}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return rowToProject(data as ProjectRow);
|
|
330
|
+
}
|
|
246
331
|
}
|
package/src/db/schema.sql
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
-- =============================================================================
|
|
2
|
-
-- Agent Foundry Studio Database Schema
|
|
2
|
+
-- Agent Foundry Studio Database Schema (Reference)
|
|
3
3
|
-- =============================================================================
|
|
4
|
-
-- This
|
|
5
|
-
--
|
|
4
|
+
-- This is a REFERENCE file showing the schema design.
|
|
5
|
+
-- Actual migrations are managed via Alembic in services/bff/alembic/versions/
|
|
6
6
|
--
|
|
7
|
-
--
|
|
8
|
-
-- 1. Enable uuid-ossp extension: CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
9
|
-
-- 2. Supabase Auth must be configured (auth.users table exists)
|
|
7
|
+
-- See: 005_add_studio_tables.py for the actual migration
|
|
10
8
|
-- =============================================================================
|
|
11
9
|
|
|
12
|
-
-- Enable UUID extension if not already enabled
|
|
13
|
-
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
14
|
-
|
|
15
10
|
-- =============================================================================
|
|
16
11
|
-- Studio Projects
|
|
17
12
|
-- =============================================================================
|
|
18
13
|
-- Represents a React/Vite project created and managed in Build Studio.
|
|
19
14
|
-- Each project belongs to a user and can optionally be forked from another project.
|
|
15
|
+
-- After first publish to Feed, links to an apps entry via app_id.
|
|
20
16
|
|
|
21
17
|
CREATE TABLE IF NOT EXISTS studio_projects (
|
|
22
18
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
@@ -29,6 +25,9 @@ CREATE TABLE IF NOT EXISTS studio_projects (
|
|
|
29
25
|
slug VARCHAR(255) NOT NULL,
|
|
30
26
|
description TEXT,
|
|
31
27
|
|
|
28
|
+
-- Link to registered app (set after first publish to Feed)
|
|
29
|
+
app_id VARCHAR(255) REFERENCES apps(id) ON DELETE SET NULL,
|
|
30
|
+
|
|
32
31
|
-- Local development
|
|
33
32
|
root_path TEXT NOT NULL, -- Local filesystem path to project root
|
|
34
33
|
|
|
@@ -44,35 +43,21 @@ CREATE TABLE IF NOT EXISTS studio_projects (
|
|
|
44
43
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
45
44
|
|
|
46
45
|
-- Ensure slug is unique per user
|
|
47
|
-
CONSTRAINT
|
|
46
|
+
CONSTRAINT uq_studio_projects_user_slug UNIQUE (user_id, slug)
|
|
48
47
|
);
|
|
49
48
|
|
|
50
|
-
--
|
|
49
|
+
-- Indexes
|
|
51
50
|
CREATE INDEX IF NOT EXISTS idx_studio_projects_user_id ON studio_projects(user_id);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
CREATE INDEX IF NOT EXISTS
|
|
55
|
-
|
|
56
|
-
-- Trigger to auto-update updated_at
|
|
57
|
-
CREATE OR REPLACE FUNCTION update_studio_projects_updated_at()
|
|
58
|
-
RETURNS TRIGGER AS $$
|
|
59
|
-
BEGIN
|
|
60
|
-
NEW.updated_at = NOW();
|
|
61
|
-
RETURN NEW;
|
|
62
|
-
END;
|
|
63
|
-
$$ LANGUAGE plpgsql;
|
|
64
|
-
|
|
65
|
-
DROP TRIGGER IF EXISTS trigger_studio_projects_updated_at ON studio_projects;
|
|
66
|
-
CREATE TRIGGER trigger_studio_projects_updated_at
|
|
67
|
-
BEFORE UPDATE ON studio_projects
|
|
68
|
-
FOR EACH ROW
|
|
69
|
-
EXECUTE FUNCTION update_studio_projects_updated_at();
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_studio_projects_app_id ON studio_projects(app_id) WHERE app_id IS NOT NULL;
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_studio_projects_parent ON studio_projects(parent_project_id) WHERE parent_project_id IS NOT NULL;
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_studio_projects_created ON studio_projects(created_at DESC);
|
|
70
54
|
|
|
71
55
|
-- =============================================================================
|
|
72
56
|
-- Studio Deployments
|
|
73
57
|
-- =============================================================================
|
|
74
58
|
-- Represents a build and upload of a project to Alibaba Cloud OSS.
|
|
75
59
|
-- Tracks the full lifecycle: pending -> building -> uploading -> published/failed
|
|
60
|
+
-- When published to Feed, links to an artifact entry via artifact_id.
|
|
76
61
|
|
|
77
62
|
CREATE TABLE IF NOT EXISTS studio_deployments (
|
|
78
63
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
@@ -93,6 +78,9 @@ CREATE TABLE IF NOT EXISTS studio_deployments (
|
|
|
93
78
|
oss_key TEXT,
|
|
94
79
|
oss_url TEXT,
|
|
95
80
|
|
|
81
|
+
-- Link to artifact (set when publishing to Feed)
|
|
82
|
+
artifact_id UUID REFERENCES artifacts(id) ON DELETE SET NULL,
|
|
83
|
+
|
|
96
84
|
-- Metrics
|
|
97
85
|
bundle_size_bytes BIGINT,
|
|
98
86
|
|
|
@@ -108,49 +96,44 @@ CREATE TABLE IF NOT EXISTS studio_deployments (
|
|
|
108
96
|
published_at TIMESTAMPTZ
|
|
109
97
|
);
|
|
110
98
|
|
|
111
|
-
--
|
|
99
|
+
-- Indexes
|
|
112
100
|
CREATE INDEX IF NOT EXISTS idx_studio_deployments_project_id ON studio_deployments(project_id);
|
|
113
|
-
|
|
114
|
-
-- Index for user's deployments
|
|
115
101
|
CREATE INDEX IF NOT EXISTS idx_studio_deployments_user_id ON studio_deployments(user_id);
|
|
116
|
-
|
|
117
|
-
-- Index for filtering by status
|
|
118
102
|
CREATE INDEX IF NOT EXISTS idx_studio_deployments_status ON studio_deployments(status);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
CREATE INDEX IF NOT EXISTS idx_studio_deployments_project_created
|
|
122
|
-
ON studio_deployments(project_id, created_at DESC);
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_studio_deployments_artifact ON studio_deployments(artifact_id) WHERE artifact_id IS NOT NULL;
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_studio_deployments_project_created ON studio_deployments(project_id, created_at DESC);
|
|
123
105
|
|
|
124
106
|
-- =============================================================================
|
|
125
107
|
-- Row Level Security (RLS) Policies
|
|
126
108
|
-- =============================================================================
|
|
127
|
-
-- Enable RLS to ensure users can only access their own data.
|
|
128
109
|
|
|
129
|
-
-- Enable RLS on both tables
|
|
130
110
|
ALTER TABLE studio_projects ENABLE ROW LEVEL SECURITY;
|
|
131
111
|
ALTER TABLE studio_deployments ENABLE ROW LEVEL SECURITY;
|
|
132
112
|
|
|
133
113
|
-- Projects: Users can only see/modify their own projects
|
|
134
|
-
|
|
135
|
-
CREATE POLICY studio_projects_user_policy ON studio_projects
|
|
114
|
+
CREATE POLICY studio_projects_owner_all ON studio_projects
|
|
136
115
|
FOR ALL
|
|
137
116
|
USING (auth.uid() = user_id)
|
|
138
117
|
WITH CHECK (auth.uid() = user_id);
|
|
139
118
|
|
|
140
119
|
-- Deployments: Users can only see/modify their own deployments
|
|
141
|
-
|
|
142
|
-
CREATE POLICY studio_deployments_user_policy ON studio_deployments
|
|
120
|
+
CREATE POLICY studio_deployments_owner_all ON studio_deployments
|
|
143
121
|
FOR ALL
|
|
144
122
|
USING (auth.uid() = user_id)
|
|
145
123
|
WITH CHECK (auth.uid() = user_id);
|
|
146
124
|
|
|
147
125
|
-- =============================================================================
|
|
148
|
-
--
|
|
126
|
+
-- Integration with Existing Tables
|
|
127
|
+
-- =============================================================================
|
|
128
|
+
--
|
|
129
|
+
-- When a project is published to Feed:
|
|
130
|
+
-- 1. Create/update entry in `apps` table
|
|
131
|
+
-- 2. Create `app_versions` record
|
|
132
|
+
-- 3. Create `artifacts` record with:
|
|
133
|
+
-- - type = 'bundle'
|
|
134
|
+
-- - bucket = 'external'
|
|
135
|
+
-- - object_path = OSS URL
|
|
136
|
+
-- 4. Link deployment to artifact via artifact_id
|
|
137
|
+
-- 5. Link project to app via app_id
|
|
138
|
+
--
|
|
149
139
|
-- =============================================================================
|
|
150
|
-
|
|
151
|
-
COMMENT ON TABLE studio_projects IS 'React/Vite projects created in Agent Foundry Build Studio';
|
|
152
|
-
COMMENT ON COLUMN studio_projects.root_path IS 'Local filesystem path - only valid on the machine where project was created';
|
|
153
|
-
COMMENT ON COLUMN studio_projects.config IS 'JSON config: buildCommand, outputDir, envVars, baseUrl, viteConfig';
|
|
154
|
-
|
|
155
|
-
COMMENT ON TABLE studio_deployments IS 'Build and deployment history to Alibaba Cloud OSS';
|
|
156
|
-
COMMENT ON COLUMN studio_deployments.metadata IS 'JSON metadata: commitHash, branch, buildDurationMs, uploadDurationMs, fileCount';
|