the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add sandbox-scoped listing endpoints and schemas

- Implement handlers for files, secrets, variables and volumes with DB
queries, auth checks, retries and presentation transforms
- Add sandboxId query param to lexicons, pkl types and generated TS
types; support offset/limit pagination
- Add fileView and extra volume fields (path, readOnly, createdAt,
updatedAt) to schemas and types
- Add DB migrations and schema updates (created_at/updated_at, sandbox
volume name) and adjust drizzle schemas
- Update web hooks, types and pages to pass sandboxId/offset/limit and
add pagination and small UI tweaks

+3286 -144
+23
apps/api/lexicons/file/defs.json
··· 23 23 } 24 24 } 25 25 }, 26 + "fileView": { 27 + "type": "object", 28 + "properties": { 29 + "id": { 30 + "type": "string", 31 + "description": "Unique identifier of the file." 32 + }, 33 + "path": { 34 + "type": "string", 35 + "description": "The file path within the sandbox, e.g. '/app/config.json', '/home/user/.ssh/id_rsa', etc." 36 + }, 37 + "createdAt": { 38 + "type": "string", 39 + "description": "The timestamp when the file was created.", 40 + "format": "datetime" 41 + }, 42 + "updatedAt": { 43 + "type": "string", 44 + "description": "The timestamp when the file was last updated.", 45 + "format": "datetime" 46 + } 47 + } 48 + }, 26 49 "files": { 27 50 "type": "array", 28 51 "items": {
+4
apps/api/lexicons/file/getFiles.json
··· 7 7 "parameters": { 8 8 "type": "params", 9 9 "properties": { 10 + "sandboxId": { 11 + "type": "string", 12 + "description": "The ID of the sandbox for which to retrieve files." 13 + }, 10 14 "limit": { 11 15 "type": "integer", 12 16 "description": "The maximum number of files to return.",
+4
apps/api/lexicons/secret/getSecrets.json
··· 7 7 "parameters": { 8 8 "type": "params", 9 9 "properties": { 10 + "sandboxId": { 11 + "type": "string", 12 + "description": "The ID of the sandbox for which to retrieve secrets." 13 + }, 10 14 "limit": { 11 15 "type": "integer", 12 16 "description": "The maximum number of secrets to return.",
+4
apps/api/lexicons/variable/getVariables.json
··· 7 7 "parameters": { 8 8 "type": "params", 9 9 "properties": { 10 + "sandboxId": { 11 + "type": "string", 12 + "description": "The ID of the sandbox for which to retrieve variables." 13 + }, 10 14 "limit": { 11 15 "type": "integer", 12 16 "description": "The maximum number of variables to return.",
+18
apps/api/lexicons/volume/defs.json
··· 12 12 "name": { 13 13 "type": "string", 14 14 "description": "Name of the volume, e.g. 'data-volume', 'logs', etc." 15 + }, 16 + "path": { 17 + "type": "string", 18 + "description": "The path within the sandbox where the volume will be mounted, e.g. '/data', '/logs', etc." 19 + }, 20 + "readOnly": { 21 + "type": "boolean", 22 + "description": "Whether the volume should be mounted as read-only within the sandbox. Defaults to false (read-write)." 23 + }, 24 + "createdAt": { 25 + "type": "string", 26 + "description": "The timestamp when the volume was created.", 27 + "format": "datetime" 28 + }, 29 + "updatedAt": { 30 + "type": "string", 31 + "description": "The timestamp when the volume was last updated.", 32 + "format": "datetime" 15 33 } 16 34 } 17 35 },
+4
apps/api/lexicons/volume/getVolumes.json
··· 7 7 "parameters": { 8 8 "type": "params", 9 9 "properties": { 10 + "sandboxId": { 11 + "type": "string", 12 + "description": "The ID of the sandbox for which to retrieve volumes." 13 + }, 10 14 "limit": { 11 15 "type": "integer", 12 16 "description": "The maximum number of volumes to return.",
+24 -1
apps/api/pkl/defs/file/defs.pkl
··· 3 3 lexicon = 1 4 4 id = "io.pocketenv.file.defs" 5 5 defs = new Mapping<String, ObjectType | StringType | Array> { 6 - ["file"] = new ObjectType { 6 + ["file"] = new ObjectType { 7 7 type = "object" 8 8 required = List("path", "content") 9 9 properties { ··· 20 20 type = "string" 21 21 description = 22 22 "The content of the file. This will be written to the specified path within the sandbox. The content should be base64 encoded if it's binary data." 23 + } 24 + } 25 + } 26 + ["fileView"] = new ObjectType { 27 + type = "object" 28 + properties { 29 + ["id"] = new StringType { 30 + type = "string" 31 + description = "Unique identifier of the file." 32 + } 33 + ["path"] = new StringType { 34 + type = "string" 35 + description = "The file path within the sandbox, e.g. '/app/config.json', '/home/user/.ssh/id_rsa', etc." 36 + } 37 + ["createdAt"] = new StringType { 38 + type = "string" 39 + description = "The timestamp when the file was created." 40 + format = "datetime" 41 + } 42 + ["updatedAt"] = new StringType { 43 + type = "string" 44 + description = "The timestamp when the file was last updated." 45 + format = "datetime" 23 46 } 24 47 } 25 48 }
+4
apps/api/pkl/defs/file/getFiles.pkl
··· 8 8 parameters { 9 9 type = "params" 10 10 properties { 11 + ["sandboxId"] = new StringType { 12 + type = "string" 13 + description = "The ID of the sandbox for which to retrieve files." 14 + } 11 15 ["limit"] = new IntegerType { 12 16 type = "integer" 13 17 description = "The maximum number of files to return."
+4
apps/api/pkl/defs/secret/getSecrets.pkl
··· 8 8 parameters { 9 9 type = "params" 10 10 properties { 11 + ["sandboxId"] = new StringType { 12 + type = "string" 13 + description = "The ID of the sandbox for which to retrieve secrets." 14 + } 11 15 ["limit"] = new IntegerType { 12 16 type = "integer" 13 17 description = "The maximum number of secrets to return."
+4
apps/api/pkl/defs/variable/getVariables.pkl
··· 8 8 parameters { 9 9 type = "params" 10 10 properties { 11 + ["sandboxId"] = new StringType { 12 + type = "string" 13 + description = "The ID of the sandbox for which to retrieve variables." 14 + } 11 15 ["limit"] = new IntegerType { 12 16 type = "integer" 13 17 description = "The maximum number of variables to return."
+20
apps/api/pkl/defs/volume/defs.pkl
··· 14 14 type = "string" 15 15 description = "Name of the volume, e.g. 'data-volume', 'logs', etc." 16 16 } 17 + ["path"] = new StringType { 18 + type = "string" 19 + description = 20 + "The path within the sandbox where the volume will be mounted, e.g. '/data', '/logs', etc." 21 + } 22 + ["readOnly"] = new BooleanType { 23 + type = "boolean" 24 + description = 25 + "Whether the volume should be mounted as read-only within the sandbox. Defaults to false (read-write)." 26 + } 27 + ["createdAt"] = new StringType { 28 + type = "string" 29 + description = "The timestamp when the volume was created." 30 + format = "datetime" 31 + } 32 + ["updatedAt"] = new StringType { 33 + type = "string" 34 + description = "The timestamp when the volume was last updated." 35 + format = "datetime" 36 + } 17 37 } 18 38 } 19 39 ["volumes"] = new Array {
+4
apps/api/pkl/defs/volume/getVolumes.pkl
··· 8 8 parameters { 9 9 type = "params" 10 10 properties { 11 + ["sandboxId"] = new StringType { 12 + type = "string" 13 + description = "The ID of the sandbox for which to retrieve volumes." 14 + } 11 15 ["limit"] = new IntegerType { 12 16 type = "integer" 13 17 description = "The maximum number of volumes to return."
+63
apps/api/src/lexicon/lexicons.ts
··· 254 254 }, 255 255 }, 256 256 }, 257 + fileView: { 258 + type: "object", 259 + properties: { 260 + id: { 261 + type: "string", 262 + description: "Unique identifier of the file.", 263 + }, 264 + path: { 265 + type: "string", 266 + description: 267 + "The file path within the sandbox, e.g. '/app/config.json', '/home/user/.ssh/id_rsa', etc.", 268 + }, 269 + createdAt: { 270 + type: "string", 271 + description: "The timestamp when the file was created.", 272 + format: "datetime", 273 + }, 274 + updatedAt: { 275 + type: "string", 276 + description: "The timestamp when the file was last updated.", 277 + format: "datetime", 278 + }, 279 + }, 280 + }, 257 281 files: { 258 282 type: "array", 259 283 items: { ··· 292 316 parameters: { 293 317 type: "params", 294 318 properties: { 319 + sandboxId: { 320 + type: "string", 321 + description: "The ID of the sandbox for which to retrieve files.", 322 + }, 295 323 limit: { 296 324 type: "integer", 297 325 description: "The maximum number of files to return.", ··· 1294 1322 parameters: { 1295 1323 type: "params", 1296 1324 properties: { 1325 + sandboxId: { 1326 + type: "string", 1327 + description: 1328 + "The ID of the sandbox for which to retrieve secrets.", 1329 + }, 1297 1330 limit: { 1298 1331 type: "integer", 1299 1332 description: "The maximum number of secrets to return.", ··· 1434 1467 parameters: { 1435 1468 type: "params", 1436 1469 properties: { 1470 + sandboxId: { 1471 + type: "string", 1472 + description: 1473 + "The ID of the sandbox for which to retrieve variables.", 1474 + }, 1437 1475 limit: { 1438 1476 type: "integer", 1439 1477 description: "The maximum number of variables to return.", ··· 1507 1545 type: "string", 1508 1546 description: "Name of the volume, e.g. 'data-volume', 'logs', etc.", 1509 1547 }, 1548 + path: { 1549 + type: "string", 1550 + description: 1551 + "The path within the sandbox where the volume will be mounted, e.g. '/data', '/logs', etc.", 1552 + }, 1553 + readOnly: { 1554 + type: "boolean", 1555 + description: 1556 + "Whether the volume should be mounted as read-only within the sandbox. Defaults to false (read-write).", 1557 + }, 1558 + createdAt: { 1559 + type: "string", 1560 + description: "The timestamp when the volume was created.", 1561 + format: "datetime", 1562 + }, 1563 + updatedAt: { 1564 + type: "string", 1565 + description: "The timestamp when the volume was last updated.", 1566 + format: "datetime", 1567 + }, 1510 1568 }, 1511 1569 }, 1512 1570 volumes: { ··· 1572 1630 parameters: { 1573 1631 type: "params", 1574 1632 properties: { 1633 + sandboxId: { 1634 + type: "string", 1635 + description: 1636 + "The ID of the sandbox for which to retrieve volumes.", 1637 + }, 1575 1638 limit: { 1576 1639 type: "integer", 1577 1640 description: "The maximum number of volumes to return.",
+24
apps/api/src/lexicon/types/io/pocketenv/file/defs.ts
··· 26 26 return lexicons.validate("io.pocketenv.file.defs#file", v); 27 27 } 28 28 29 + export interface FileView { 30 + /** Unique identifier of the file. */ 31 + id?: string; 32 + /** The file path within the sandbox, e.g. '/app/config.json', '/home/user/.ssh/id_rsa', etc. */ 33 + path?: string; 34 + /** The timestamp when the file was created. */ 35 + createdAt?: string; 36 + /** The timestamp when the file was last updated. */ 37 + updatedAt?: string; 38 + [k: string]: unknown; 39 + } 40 + 41 + export function isFileView(v: unknown): v is FileView { 42 + return ( 43 + isObj(v) && 44 + hasProp(v, "$type") && 45 + v.$type === "io.pocketenv.file.defs#fileView" 46 + ); 47 + } 48 + 49 + export function validateFileView(v: unknown): ValidationResult { 50 + return lexicons.validate("io.pocketenv.file.defs#fileView", v); 51 + } 52 + 29 53 export type Files = File[];
+2
apps/api/src/lexicon/types/io/pocketenv/file/getFiles.ts
··· 10 10 import type * as IoPocketenvFileDefs from "./defs"; 11 11 12 12 export interface QueryParams { 13 + /** The ID of the sandbox for which to retrieve files. */ 14 + sandboxId?: string; 13 15 /** The maximum number of files to return. */ 14 16 limit?: number; 15 17 /** The number of files to skip before starting to collect the result set. */
+2
apps/api/src/lexicon/types/io/pocketenv/secret/getSecrets.ts
··· 10 10 import type * as IoPocketenvSecretDefs from "./defs"; 11 11 12 12 export interface QueryParams { 13 + /** The ID of the sandbox for which to retrieve secrets. */ 14 + sandboxId?: string; 13 15 /** The maximum number of secrets to return. */ 14 16 limit?: number; 15 17 /** The number of secrets to skip before starting to collect the result set. */
+2
apps/api/src/lexicon/types/io/pocketenv/variable/getVariables.ts
··· 10 10 import type * as IoPocketenvVariableDefs from "./defs"; 11 11 12 12 export interface QueryParams { 13 + /** The ID of the sandbox for which to retrieve variables. */ 14 + sandboxId?: string; 13 15 /** The maximum number of variables to return. */ 14 16 limit?: number; 15 17 /** The number of variables to skip before starting to collect the result set. */
+8
apps/api/src/lexicon/types/io/pocketenv/volume/defs.ts
··· 11 11 id?: string; 12 12 /** Name of the volume, e.g. 'data-volume', 'logs', etc. */ 13 13 name?: string; 14 + /** The path within the sandbox where the volume will be mounted, e.g. '/data', '/logs', etc. */ 15 + path?: string; 16 + /** Whether the volume should be mounted as read-only within the sandbox. Defaults to false (read-write). */ 17 + readOnly?: boolean; 18 + /** The timestamp when the volume was created. */ 19 + createdAt?: string; 20 + /** The timestamp when the volume was last updated. */ 21 + updatedAt?: string; 14 22 [k: string]: unknown; 15 23 } 16 24
+2
apps/api/src/lexicon/types/io/pocketenv/volume/getVolumes.ts
··· 10 10 import type * as IoPocketenvVolumeDefs from "./defs"; 11 11 12 12 export interface QueryParams { 13 + /** The ID of the sandbox for which to retrieve volumes. */ 14 + sandboxId?: string; 13 15 /** The maximum number of volumes to return. */ 14 16 limit?: number; 15 17 /** The number of volumes to skip before starting to collect the result set. */
+1 -3
apps/api/src/schema/authorized-keys.ts
··· 3 3 import sandboxes from "./sandboxes"; 4 4 5 5 const authorizedKeys = pgTable("authorized_keys", { 6 - id: text("id") 7 - .primaryKey() 8 - .default(sql`xata_id()`), 6 + id: text("id").primaryKey().default(sql`xata_id()`), 9 7 sandboxId: text("sandbox_id").references(() => sandboxes.id), 10 8 publicKey: text("public_key").notNull(), 11 9 createdAt: timestamp("created_at").defaultNow().notNull(),
+1 -3
apps/api/src/schema/files.ts
··· 2 2 import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 3 4 4 const files = pgTable("files", { 5 - id: text("id") 6 - .primaryKey() 7 - .default(sql`variable_id()`), 5 + id: text("id").primaryKey().default(sql`variable_id()`), 8 6 content: text("content").notNull(), 9 7 createdAt: timestamp("created_at").defaultNow().notNull(), 10 8 updatedAt: timestamp("updated_at").defaultNow().notNull(),
+4 -4
apps/api/src/schema/sandbox-files.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes"; 4 4 import files from "./files"; 5 5 6 6 const sandboxFiles = pgTable( 7 7 "sandbox_files", 8 8 { 9 - id: text("id") 10 - .primaryKey() 11 - .default(sql`xata_id()`), 9 + id: text("id").primaryKey().default(sql`xata_id()`), 12 10 sandboxId: text("sandbox_id") 13 11 .notNull() 14 12 .references(() => sandboxes.id), ··· 16 14 .notNull() 17 15 .references(() => files.id), 18 16 path: text("path").notNull(), 17 + createdAt: timestamp("created_at").defaultNow().notNull(), 18 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 19 }, 20 20 (t) => [ 21 21 uniqueIndex("unique_sandbox_file").on(t.sandboxId, t.fileId),
+4 -4
apps/api/src/schema/sandbox-secrets.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes"; 4 4 import secrets from "./secrets"; 5 5 6 6 const sandboxSecrets = pgTable( 7 7 "sandbox_secrets", 8 8 { 9 - id: text("id") 10 - .primaryKey() 11 - .default(sql`xata_id()`), 9 + id: text("id").primaryKey().default(sql`xata_id()`), 12 10 sandboxId: text("sandbox_id") 13 11 .notNull() 14 12 .references(() => sandboxes.id), ··· 16 14 .notNull() 17 15 .references(() => secrets.id), 18 16 name: text("name"), 17 + createdAt: timestamp("created_at").defaultNow().notNull(), 18 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 19 }, 20 20 (t) => [ 21 21 uniqueIndex("unique_sandbox_secret").on(t.sandboxId, t.secretId),
+4 -4
apps/api/src/schema/sandbox-variables.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes"; 4 4 import variables from "./variables"; 5 5 6 6 const sandboxVariables = pgTable( 7 7 "sandbox_variables", 8 8 { 9 - id: text("id") 10 - .primaryKey() 11 - .default(sql`xata_id()`), 9 + id: text("id").primaryKey().default(sql`xata_id()`), 12 10 sandboxId: text("sandbox_id") 13 11 .notNull() 14 12 .references(() => sandboxes.id), ··· 16 14 .notNull() 17 15 .references(() => variables.id), 18 16 name: text("name"), 17 + createdAt: timestamp("created_at").defaultNow().notNull(), 18 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 19 }, 20 20 (t) => [ 21 21 uniqueIndex("unique_sandbox_variables").on(t.sandboxId, t.variableId),
+5 -4
apps/api/src/schema/sandbox-volumes.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes"; 4 4 import volumes from "./volumes"; 5 5 6 6 const sandboxVolumes = pgTable( 7 7 "sandbox_volumes", 8 8 { 9 - id: text("id") 10 - .primaryKey() 11 - .default(sql`xata_id()`), 9 + id: text("id").primaryKey().default(sql`xata_id()`), 12 10 sandboxId: text("sandbox_id") 13 11 .notNull() 14 12 .references(() => sandboxes.id), 15 13 volumeId: text("volume_id") 16 14 .notNull() 17 15 .references(() => volumes.id), 16 + name: text("name"), 18 17 path: text("path").notNull(), 18 + createdAt: timestamp("created_at").defaultNow().notNull(), 19 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 20 }, 20 21 (t) => [ 21 22 uniqueIndex("unique_sandbox_volume").on(t.sandboxId, t.volumeId),
+2
apps/api/src/xrpc/index.ts
··· 22 22 import putPreferences from "./io/pocketenv/sandbox/putPreferences"; 23 23 import getPreferences from "./io/pocketenv/sandbox/getPreferences"; 24 24 import addVariable from "./io/pocketenv/variable/addVariable"; 25 + import getVariables from "./io/pocketenv/variable/getVariables"; 25 26 26 27 export default function (server: Server, ctx: Context) { 27 28 // io.pocketenv ··· 42 43 getFiles(server, ctx); 43 44 addSecret(server, ctx); 44 45 addVariable(server, ctx); 46 + getVariables(server, ctx); 45 47 deleteSecret(server, ctx); 46 48 getSecrets(server, ctx); 47 49 addVolume(server, ctx);
+7 -8
apps/api/src/xrpc/io/pocketenv/actor/getActorSandboxes.ts
··· 1 - import type { HandlerAuth } from "@atproto/xrpc-server"; 1 + import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import type { Context } from "context"; 3 3 import type { Server } from "lexicon"; 4 4 import { Effect, pipe } from "effect"; ··· 19 19 Effect.flatMap(presentation), 20 20 Effect.retry({ times: 3 }), 21 21 Effect.timeout("10 seconds"), 22 - Effect.catchAll((err) => { 23 - consola.error("Error retrieving sandboxes:", err); 24 - return Effect.succeed({ sandboxes: [] }); 25 - }), 26 22 ); 27 23 server.io.pocketenv.actor.getActorSandboxes({ 28 24 auth: ctx.authVerifier, ··· 74 70 .execute() 75 71 .then((result) => result[0]?.count ?? 0), 76 72 ]), 77 - catch: (error) => 78 - new Error( 73 + catch: (error) => { 74 + consola.error("Error retrieving sandboxes:", error); 75 + return new XRPCError( 76 + 500, 79 77 `Failed to retrieve sandboxes: ${error instanceof Error ? error.message : String(error)}`, 80 - ), 78 + ); 79 + }, 81 80 }); 82 81 }; 83 82
+107 -4
apps/api/src/xrpc/io/pocketenv/file/getFiles.ts
··· 1 1 import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import type { Context } from "context"; 3 + import { eq, and, count } from "drizzle-orm"; 3 4 import type { Server } from "lexicon"; 4 - import type { QueryParams } from "lexicon/types/io/pocketenv/file/getFiles"; 5 + import type { 6 + QueryParams, 7 + OutputSchema, 8 + } from "lexicon/types/io/pocketenv/file/getFiles"; 9 + import files from "schema/files"; 10 + import sandboxFiles, { type SelectSandboxFile } from "schema/sandbox-files"; 11 + import sandboxes from "schema/sandboxes"; 12 + import users from "schema/users"; 13 + import { pipe, Effect } from "effect"; 14 + import { consola } from "consola"; 5 15 6 16 export default function (server: Server, ctx: Context) { 7 - const getFiles = async (params: QueryParams, auth: HandlerAuth) => { 8 - return {}; 17 + const getFiles = (params: QueryParams, auth: HandlerAuth) => { 18 + if (!auth?.credentials?.did) { 19 + throw new XRPCError(401, "Unauthorized"); 20 + } 21 + 22 + return pipe( 23 + { params, ctx, auth }, 24 + retrieve, 25 + Effect.flatMap(presentation), 26 + Effect.retry({ times: 3 }), 27 + Effect.timeout("10 seconds"), 28 + Effect.catchAll((err) => { 29 + consola.error("Error retrieving files:", err); 30 + return Effect.succeed({ sandboxes: [] }); 31 + }), 32 + ); 9 33 }; 34 + 10 35 server.io.pocketenv.file.getFiles({ 11 36 auth: ctx.authVerifier, 12 37 handler: async ({ params, auth }) => { 13 - const result = await getFiles(params, auth); 38 + const result = await Effect.runPromise(getFiles(params, auth)); 14 39 return { 15 40 encoding: "application/json", 16 41 body: result, ··· 18 43 }, 19 44 }); 20 45 } 46 + 47 + const retrieve = ({ 48 + params, 49 + ctx, 50 + auth, 51 + }: { 52 + params: QueryParams; 53 + ctx: Context; 54 + auth: HandlerAuth; 55 + }): Effect.Effect<[SelectSandboxFile[], number], Error> => { 56 + return Effect.tryPromise({ 57 + try: async () => 58 + Promise.all([ 59 + ctx.db 60 + .select() 61 + .from(sandboxFiles) 62 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxFiles.sandboxId)) 63 + .leftJoin(files, eq(files.id, sandboxFiles.fileId)) 64 + .leftJoin(users, eq(users.id, sandboxes.userId)) 65 + .where( 66 + params.sandboxId 67 + ? and( 68 + eq(users.did, auth.credentials.did), 69 + eq(sandboxFiles.sandboxId, params.sandboxId), 70 + ) 71 + : eq(users.did, auth.credentials.did), 72 + ) 73 + .offset(params.offset ?? 0) 74 + .limit(params.limit ?? 20) 75 + .execute() 76 + .then((rows) => 77 + rows 78 + .map((row) => row.sandbox_files) 79 + .filter((file) => file !== null), 80 + ), 81 + ctx.db 82 + .select({ 83 + count: count(), 84 + }) 85 + .from(sandboxFiles) 86 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxFiles.sandboxId)) 87 + .leftJoin(files, eq(files.id, sandboxFiles.fileId)) 88 + .leftJoin(users, eq(users.id, sandboxes.userId)) 89 + .where( 90 + params.sandboxId 91 + ? and( 92 + eq(users.did, auth.credentials.did), 93 + eq(sandboxFiles.sandboxId, params.sandboxId), 94 + ) 95 + : eq(users.did, auth.credentials.did), 96 + ) 97 + .execute() 98 + .then((result) => result[0]?.count ?? 0), 99 + ]), 100 + catch: (error) => { 101 + consola.error("Error retrieving files:", error); 102 + throw new XRPCError( 103 + 500, 104 + `Failed to retrieve files: ${error instanceof Error ? error.message : String(error)}`, 105 + ); 106 + }, 107 + }); 108 + }; 109 + 110 + const presentation = ([files, total]: [ 111 + SelectSandboxFile[], 112 + number, 113 + ]): Effect.Effect<OutputSchema, never> => { 114 + return Effect.sync(() => ({ 115 + files: files.map((file) => ({ 116 + id: file.id, 117 + path: file.path, 118 + createdAt: file.createdAt.toISOString(), 119 + updatedAt: file.updatedAt.toISOString(), 120 + })), 121 + total, 122 + })); 123 + };
+106 -5
apps/api/src/xrpc/io/pocketenv/secret/getSecrets.ts
··· 1 1 import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import type { Context } from "context"; 3 + import { eq, and, count } from "drizzle-orm"; 3 4 import type { Server } from "lexicon"; 4 - import type { QueryParams } from "lexicon/types/io/pocketenv/secret/getSecrets"; 5 + import type { 6 + QueryParams, 7 + OutputSchema, 8 + } from "lexicon/types/io/pocketenv/secret/getSecrets"; 9 + import sandboxes from "schema/sandboxes"; 10 + import users from "schema/users"; 11 + import { pipe, Effect } from "effect"; 12 + import { consola } from "consola"; 13 + import secrets, { type SelectSecret } from "schema/secrets"; 14 + import sandboxSecrets from "schema/sandbox-secrets"; 5 15 6 16 export default function (server: Server, ctx: Context) { 7 - const getSecrets = async (params: QueryParams, auth: HandlerAuth) => { 8 - return {}; 17 + const getSecrets = (params: QueryParams, auth: HandlerAuth) => { 18 + if (!auth?.credentials?.did) { 19 + throw new XRPCError(401, "Unauthorized"); 20 + } 21 + 22 + return pipe( 23 + { params, ctx, auth }, 24 + retrieve, 25 + Effect.flatMap(presentation), 26 + Effect.retry({ times: 3 }), 27 + Effect.timeout("10 seconds"), 28 + Effect.catchAll((err) => { 29 + consola.error("Error retrieving secrets:", err); 30 + return Effect.succeed({ sandboxes: [] }); 31 + }), 32 + ); 9 33 }; 34 + 10 35 server.io.pocketenv.secret.getSecrets({ 11 36 auth: ctx.authVerifier, 12 37 handler: async ({ params, auth }) => { 13 - const result = await getSecrets(params, auth); 38 + const result = await Effect.runPromise(getSecrets(params, auth)); 14 39 return { 15 40 encoding: "application/json", 16 - body: result as any, // TODO: Implement getSecrets 41 + body: result, 17 42 }; 18 43 }, 19 44 }); 20 45 } 46 + 47 + const retrieve = ({ 48 + params, 49 + ctx, 50 + auth, 51 + }: { 52 + params: QueryParams; 53 + ctx: Context; 54 + auth: HandlerAuth; 55 + }): Effect.Effect<[SelectSecret[], number], Error> => { 56 + return Effect.tryPromise({ 57 + try: async () => 58 + Promise.all([ 59 + ctx.db 60 + .select() 61 + .from(sandboxSecrets) 62 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxSecrets.sandboxId)) 63 + .leftJoin(secrets, eq(secrets.id, sandboxSecrets.secretId)) 64 + .leftJoin(users, eq(users.id, sandboxes.userId)) 65 + .where( 66 + params.sandboxId 67 + ? and( 68 + eq(users.did, auth.credentials.did), 69 + eq(sandboxSecrets.sandboxId, params.sandboxId), 70 + ) 71 + : eq(users.did, auth.credentials.did), 72 + ) 73 + .offset(params.offset ?? 0) 74 + .limit(params.limit ?? 20) 75 + .execute() 76 + .then((rows) => 77 + rows 78 + .map((row) => row.secrets) 79 + .filter((variable) => variable !== null), 80 + ), 81 + ctx.db 82 + .select({ 83 + count: count(), 84 + }) 85 + .from(sandboxSecrets) 86 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxSecrets.sandboxId)) 87 + .leftJoin(users, eq(users.id, sandboxes.userId)) 88 + .where( 89 + params.sandboxId 90 + ? and( 91 + eq(users.did, auth.credentials.did), 92 + eq(sandboxSecrets.sandboxId, params.sandboxId), 93 + ) 94 + : eq(users.did, auth.credentials.did), 95 + ) 96 + .execute() 97 + .then((result) => result[0]?.count ?? 0), 98 + ]), 99 + catch: (error) => { 100 + consola.error("Error retrieving secrets:", error); 101 + throw new XRPCError( 102 + 500, 103 + `Failed to retrieve secrets: ${error instanceof Error ? error.message : String(error)}`, 104 + ); 105 + }, 106 + }); 107 + }; 108 + 109 + const presentation = ([secrets, total]: [ 110 + SelectSecret[], 111 + number, 112 + ]): Effect.Effect<OutputSchema, never> => { 113 + return Effect.sync(() => ({ 114 + secrets: secrets.map((secret) => ({ 115 + id: secret.id, 116 + name: secret.name, 117 + createdAt: secret.createdAt.toISOString(), 118 + })), 119 + total, 120 + })); 121 + };
+111 -5
apps/api/src/xrpc/io/pocketenv/variable/getVariables.ts
··· 1 1 import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import type { Context } from "context"; 3 + import { eq, and, count } from "drizzle-orm"; 3 4 import type { Server } from "lexicon"; 4 - import type { QueryParams } from "lexicon/types/io/pocketenv/variable/getVariables"; 5 + import type { 6 + QueryParams, 7 + OutputSchema, 8 + } from "lexicon/types/io/pocketenv/variable/getVariables"; 9 + import sandboxes from "schema/sandboxes"; 10 + import users from "schema/users"; 11 + import { pipe, Effect } from "effect"; 12 + import { consola } from "consola"; 13 + import variables, { type SelectVariable } from "schema/variables"; 14 + import sandboxVariables from "schema/sandbox-variables"; 5 15 6 16 export default function (server: Server, ctx: Context) { 7 - const getVariables = async (params: QueryParams, auth: HandlerAuth) => { 8 - return {}; 17 + const getVariables = (params: QueryParams, auth: HandlerAuth) => { 18 + if (!auth?.credentials?.did) { 19 + throw new XRPCError(401, "Unauthorized"); 20 + } 21 + 22 + return pipe( 23 + { params, ctx, auth }, 24 + retrieve, 25 + Effect.flatMap(presentation), 26 + Effect.retry({ times: 3 }), 27 + Effect.timeout("10 seconds"), 28 + Effect.catchAll((err) => { 29 + consola.error("Error retrieving variables:", err); 30 + return Effect.succeed({ sandboxes: [] }); 31 + }), 32 + ); 9 33 }; 34 + 10 35 server.io.pocketenv.variable.getVariables({ 11 36 auth: ctx.authVerifier, 12 37 handler: async ({ params, auth }) => { 13 - const result = await getVariables(params, auth); 14 - return result as any; // TODO: Implement getVariables handler 38 + const result = await Effect.runPromise(getVariables(params, auth)); 39 + return { 40 + encoding: "application/json", 41 + body: result, 42 + }; 15 43 }, 16 44 }); 17 45 } 46 + 47 + const retrieve = ({ 48 + params, 49 + ctx, 50 + auth, 51 + }: { 52 + params: QueryParams; 53 + ctx: Context; 54 + auth: HandlerAuth; 55 + }): Effect.Effect<[SelectVariable[], number], Error> => { 56 + return Effect.tryPromise({ 57 + try: async () => 58 + Promise.all([ 59 + ctx.db 60 + .select() 61 + .from(sandboxVariables) 62 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxVariables.sandboxId)) 63 + .leftJoin(variables, eq(variables.id, sandboxVariables.variableId)) 64 + .leftJoin(users, eq(users.id, sandboxes.userId)) 65 + .where( 66 + params.sandboxId 67 + ? and( 68 + eq(users.did, auth.credentials.did), 69 + eq(sandboxVariables.sandboxId, params.sandboxId), 70 + ) 71 + : eq(users.did, auth.credentials.did), 72 + ) 73 + .offset(params.offset ?? 0) 74 + .limit(params.limit ?? 20) 75 + .execute() 76 + .then((rows) => 77 + rows 78 + .map((row) => row.variables) 79 + .filter((variable) => variable !== null), 80 + ), 81 + ctx.db 82 + .select({ 83 + count: count(), 84 + }) 85 + .from(sandboxVariables) 86 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxVariables.sandboxId)) 87 + .leftJoin(users, eq(users.id, sandboxes.userId)) 88 + .where( 89 + params.sandboxId 90 + ? and( 91 + eq(users.did, auth.credentials.did), 92 + eq(sandboxVariables.sandboxId, params.sandboxId), 93 + ) 94 + : eq(users.did, auth.credentials.did), 95 + ) 96 + .execute() 97 + .then((result) => result[0]?.count ?? 0), 98 + ]), 99 + catch: (error) => { 100 + consola.error("Error retrieving variables:", error); 101 + throw new XRPCError( 102 + 500, 103 + `Failed to retrieve variables: ${error instanceof Error ? error.message : String(error)}`, 104 + ); 105 + }, 106 + }); 107 + }; 108 + 109 + const presentation = ([variables, total]: [ 110 + SelectVariable[], 111 + number, 112 + ]): Effect.Effect<OutputSchema, never> => { 113 + return Effect.sync(() => ({ 114 + variables: variables.map((variable) => ({ 115 + id: variable.id, 116 + name: variable.name, 117 + value: variable.value, 118 + createdAt: variable.createdAt.toISOString(), 119 + updatedAt: variable.updatedAt.toISOString(), 120 + })), 121 + total, 122 + })); 123 + };
+138 -5
apps/api/src/xrpc/io/pocketenv/volume/getVolumes.ts
··· 1 1 import { XRPCError, type HandlerAuth } from "@atproto/xrpc-server"; 2 2 import type { Context } from "context"; 3 + import { eq, and, count } from "drizzle-orm"; 3 4 import type { Server } from "lexicon"; 4 - import type { QueryParams } from "lexicon/types/io/pocketenv/volume/getVolumes"; 5 + import type { 6 + QueryParams, 7 + OutputSchema, 8 + } from "lexicon/types/io/pocketenv/volume/getVolumes"; 9 + import volumes, { type SelectVolume } from "schema/volumes"; 10 + import sandboxVolumes, { 11 + type SelectSandboxVolume, 12 + } from "schema/sandbox-volumes"; 13 + import sandboxes from "schema/sandboxes"; 14 + import users from "schema/users"; 15 + import { pipe, Effect } from "effect"; 16 + import { consola } from "consola"; 5 17 6 18 export default function (server: Server, ctx: Context) { 7 - const getVolumes = async (params: QueryParams, auth: HandlerAuth) => { 8 - return {}; 19 + const getVolumes = (params: QueryParams, auth: HandlerAuth) => { 20 + if (!auth?.credentials?.did) { 21 + throw new XRPCError(401, "Unauthorized"); 22 + } 23 + 24 + return pipe( 25 + { params, ctx, auth }, 26 + retrieve, 27 + Effect.flatMap(presentation), 28 + Effect.retry({ times: 3 }), 29 + Effect.timeout("10 seconds"), 30 + Effect.catchAll((err) => { 31 + consola.error("Error retrieving files:", err); 32 + return Effect.succeed({ sandboxes: [] }); 33 + }), 34 + ); 9 35 }; 36 + 10 37 server.io.pocketenv.volume.getVolumes({ 11 38 auth: ctx.authVerifier, 12 39 handler: async ({ params, auth }) => { 13 - const result = await getVolumes(params, auth); 14 - return result as any; // TODO: Implement getVolumes handler 40 + const result = await Effect.runPromise(getVolumes(params, auth)); 41 + return { 42 + encoding: "application/json", 43 + body: result, 44 + }; 15 45 }, 16 46 }); 17 47 } 48 + 49 + const retrieve = ({ 50 + params, 51 + ctx, 52 + auth, 53 + }: { 54 + params: QueryParams; 55 + ctx: Context; 56 + auth: HandlerAuth; 57 + }): Effect.Effect< 58 + [SelectSandboxVolume[], (SelectVolume | null)[], number], 59 + Error 60 + > => { 61 + return Effect.tryPromise({ 62 + try: async () => 63 + Promise.all([ 64 + ctx.db 65 + .select() 66 + .from(sandboxVolumes) 67 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxVolumes.sandboxId)) 68 + .leftJoin(volumes, eq(volumes.id, sandboxVolumes.volumeId)) 69 + .leftJoin(users, eq(users.id, sandboxes.userId)) 70 + .where( 71 + params.sandboxId 72 + ? and( 73 + eq(users.did, auth.credentials.did), 74 + eq(sandboxVolumes.sandboxId, params.sandboxId), 75 + ) 76 + : eq(users.did, auth.credentials.did), 77 + ) 78 + .offset(params.offset ?? 0) 79 + .limit(params.limit ?? 20) 80 + .execute() 81 + .then((rows) => 82 + rows 83 + .map((row) => row.sandbox_volumes) 84 + .filter((volume) => volume !== null), 85 + ), 86 + ctx.db 87 + .select() 88 + .from(sandboxVolumes) 89 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxVolumes.sandboxId)) 90 + .leftJoin(volumes, eq(volumes.id, sandboxVolumes.volumeId)) 91 + .leftJoin(users, eq(users.id, sandboxes.userId)) 92 + .where( 93 + params.sandboxId 94 + ? and( 95 + eq(users.did, auth.credentials.did), 96 + eq(sandboxVolumes.sandboxId, params.sandboxId), 97 + ) 98 + : eq(users.did, auth.credentials.did), 99 + ) 100 + .offset(params.offset ?? 0) 101 + .limit(params.limit ?? 20) 102 + .execute() 103 + .then((rows) => 104 + rows.map((row) => row.volumes).filter((volume) => volume), 105 + ), 106 + ctx.db 107 + .select({ 108 + count: count(), 109 + }) 110 + .from(sandboxVolumes) 111 + .leftJoin(sandboxes, eq(sandboxes.id, sandboxVolumes.sandboxId)) 112 + .leftJoin(volumes, eq(volumes.id, sandboxVolumes.volumeId)) 113 + .leftJoin(users, eq(users.id, sandboxes.userId)) 114 + .where( 115 + params.sandboxId 116 + ? and( 117 + eq(users.did, auth.credentials.did), 118 + eq(sandboxVolumes.sandboxId, params.sandboxId), 119 + ) 120 + : eq(users.did, auth.credentials.did), 121 + ) 122 + .execute() 123 + .then((result) => result[0]?.count ?? 0), 124 + ]), 125 + catch: (error) => { 126 + consola.error("Error retrieving files:", error); 127 + throw new XRPCError( 128 + 500, 129 + `Failed to retrieve files: ${error instanceof Error ? error.message : String(error)}`, 130 + ); 131 + }, 132 + }); 133 + }; 134 + 135 + const presentation = ([volumes, metadata, total]: [ 136 + SelectSandboxVolume[], 137 + (SelectVolume | null)[], 138 + number, 139 + ]): Effect.Effect<OutputSchema, never> => { 140 + return Effect.sync(() => ({ 141 + volumes: volumes.map((volume, index) => ({ 142 + id: volume.id, 143 + name: metadata[index]?.slug, 144 + path: volume.path, 145 + createdAt: volume.createdAt.toISOString(), 146 + updatedAt: volume.updatedAt.toISOString(), 147 + })), 148 + total, 149 + })); 150 + };
+8
apps/cf-sandbox/drizzle/0017_pale_nehzno.sql
··· 1 + ALTER TABLE "sandbox_files" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint 2 + ALTER TABLE "sandbox_files" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint 3 + ALTER TABLE "sandbox_secrets" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint 4 + ALTER TABLE "sandbox_secrets" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint 5 + ALTER TABLE "sandbox_variables" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint 6 + ALTER TABLE "sandbox_variables" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint 7 + ALTER TABLE "sandbox_volumes" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint 8 + ALTER TABLE "sandbox_volumes" ADD COLUMN "updated_at" timestamp DEFAULT now() NOT NULL;
+1
apps/cf-sandbox/drizzle/0018_awesome_sally_floyd.sql
··· 1 + ALTER TABLE "sandbox_volumes" ADD COLUMN "name" text;
+1064
apps/cf-sandbox/drizzle/meta/0017_snapshot.json
··· 1 + { 2 + "id": "17ad78ac-906e-40f2-a2c9-29b9b18331f3", 3 + "prevId": "4ed922ed-167d-4b31-858e-db2bf8e6787a", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.authorized_keys": { 8 + "name": "authorized_keys", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "sandbox_id": { 19 + "name": "sandbox_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": false 23 + }, 24 + "public_key": { 25 + "name": "public_key", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + }, 30 + "created_at": { 31 + "name": "created_at", 32 + "type": "timestamp", 33 + "primaryKey": false, 34 + "notNull": true, 35 + "default": "now()" 36 + } 37 + }, 38 + "indexes": {}, 39 + "foreignKeys": { 40 + "authorized_keys_sandbox_id_sandboxes_id_fk": { 41 + "name": "authorized_keys_sandbox_id_sandboxes_id_fk", 42 + "tableFrom": "authorized_keys", 43 + "tableTo": "sandboxes", 44 + "columnsFrom": [ 45 + "sandbox_id" 46 + ], 47 + "columnsTo": [ 48 + "id" 49 + ], 50 + "onDelete": "no action", 51 + "onUpdate": "no action" 52 + } 53 + }, 54 + "compositePrimaryKeys": {}, 55 + "uniqueConstraints": {}, 56 + "policies": {}, 57 + "checkConstraints": {}, 58 + "isRLSEnabled": false 59 + }, 60 + "public.files": { 61 + "name": "files", 62 + "schema": "", 63 + "columns": { 64 + "id": { 65 + "name": "id", 66 + "type": "text", 67 + "primaryKey": true, 68 + "notNull": true, 69 + "default": "variable_id()" 70 + }, 71 + "content": { 72 + "name": "content", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": true 76 + }, 77 + "created_at": { 78 + "name": "created_at", 79 + "type": "timestamp", 80 + "primaryKey": false, 81 + "notNull": true, 82 + "default": "now()" 83 + }, 84 + "updated_at": { 85 + "name": "updated_at", 86 + "type": "timestamp", 87 + "primaryKey": false, 88 + "notNull": true, 89 + "default": "now()" 90 + } 91 + }, 92 + "indexes": {}, 93 + "foreignKeys": {}, 94 + "compositePrimaryKeys": {}, 95 + "uniqueConstraints": {}, 96 + "policies": {}, 97 + "checkConstraints": {}, 98 + "isRLSEnabled": false 99 + }, 100 + "public.sandbox_files": { 101 + "name": "sandbox_files", 102 + "schema": "", 103 + "columns": { 104 + "id": { 105 + "name": "id", 106 + "type": "text", 107 + "primaryKey": true, 108 + "notNull": true, 109 + "default": "xata_id()" 110 + }, 111 + "sandbox_id": { 112 + "name": "sandbox_id", 113 + "type": "text", 114 + "primaryKey": false, 115 + "notNull": true 116 + }, 117 + "file_id": { 118 + "name": "file_id", 119 + "type": "text", 120 + "primaryKey": false, 121 + "notNull": true 122 + }, 123 + "path": { 124 + "name": "path", 125 + "type": "text", 126 + "primaryKey": false, 127 + "notNull": true 128 + }, 129 + "created_at": { 130 + "name": "created_at", 131 + "type": "timestamp", 132 + "primaryKey": false, 133 + "notNull": true, 134 + "default": "now()" 135 + }, 136 + "updated_at": { 137 + "name": "updated_at", 138 + "type": "timestamp", 139 + "primaryKey": false, 140 + "notNull": true, 141 + "default": "now()" 142 + } 143 + }, 144 + "indexes": { 145 + "unique_sandbox_file": { 146 + "name": "unique_sandbox_file", 147 + "columns": [ 148 + { 149 + "expression": "sandbox_id", 150 + "isExpression": false, 151 + "asc": true, 152 + "nulls": "last" 153 + }, 154 + { 155 + "expression": "file_id", 156 + "isExpression": false, 157 + "asc": true, 158 + "nulls": "last" 159 + } 160 + ], 161 + "isUnique": true, 162 + "concurrently": false, 163 + "method": "btree", 164 + "with": {} 165 + }, 166 + "unique_sandbox_file_path": { 167 + "name": "unique_sandbox_file_path", 168 + "columns": [ 169 + { 170 + "expression": "sandbox_id", 171 + "isExpression": false, 172 + "asc": true, 173 + "nulls": "last" 174 + }, 175 + { 176 + "expression": "path", 177 + "isExpression": false, 178 + "asc": true, 179 + "nulls": "last" 180 + } 181 + ], 182 + "isUnique": true, 183 + "concurrently": false, 184 + "method": "btree", 185 + "with": {} 186 + } 187 + }, 188 + "foreignKeys": { 189 + "sandbox_files_sandbox_id_sandboxes_id_fk": { 190 + "name": "sandbox_files_sandbox_id_sandboxes_id_fk", 191 + "tableFrom": "sandbox_files", 192 + "tableTo": "sandboxes", 193 + "columnsFrom": [ 194 + "sandbox_id" 195 + ], 196 + "columnsTo": [ 197 + "id" 198 + ], 199 + "onDelete": "no action", 200 + "onUpdate": "no action" 201 + }, 202 + "sandbox_files_file_id_files_id_fk": { 203 + "name": "sandbox_files_file_id_files_id_fk", 204 + "tableFrom": "sandbox_files", 205 + "tableTo": "files", 206 + "columnsFrom": [ 207 + "file_id" 208 + ], 209 + "columnsTo": [ 210 + "id" 211 + ], 212 + "onDelete": "no action", 213 + "onUpdate": "no action" 214 + } 215 + }, 216 + "compositePrimaryKeys": {}, 217 + "uniqueConstraints": {}, 218 + "policies": {}, 219 + "checkConstraints": {}, 220 + "isRLSEnabled": false 221 + }, 222 + "public.sandbox_secrets": { 223 + "name": "sandbox_secrets", 224 + "schema": "", 225 + "columns": { 226 + "id": { 227 + "name": "id", 228 + "type": "text", 229 + "primaryKey": true, 230 + "notNull": true, 231 + "default": "xata_id()" 232 + }, 233 + "sandbox_id": { 234 + "name": "sandbox_id", 235 + "type": "text", 236 + "primaryKey": false, 237 + "notNull": true 238 + }, 239 + "secret_id": { 240 + "name": "secret_id", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": true 244 + }, 245 + "name": { 246 + "name": "name", 247 + "type": "text", 248 + "primaryKey": false, 249 + "notNull": false 250 + }, 251 + "created_at": { 252 + "name": "created_at", 253 + "type": "timestamp", 254 + "primaryKey": false, 255 + "notNull": true, 256 + "default": "now()" 257 + }, 258 + "updated_at": { 259 + "name": "updated_at", 260 + "type": "timestamp", 261 + "primaryKey": false, 262 + "notNull": true, 263 + "default": "now()" 264 + } 265 + }, 266 + "indexes": { 267 + "unique_sandbox_secret": { 268 + "name": "unique_sandbox_secret", 269 + "columns": [ 270 + { 271 + "expression": "sandbox_id", 272 + "isExpression": false, 273 + "asc": true, 274 + "nulls": "last" 275 + }, 276 + { 277 + "expression": "secret_id", 278 + "isExpression": false, 279 + "asc": true, 280 + "nulls": "last" 281 + } 282 + ], 283 + "isUnique": true, 284 + "concurrently": false, 285 + "method": "btree", 286 + "with": {} 287 + }, 288 + "unique_sandbox_secret_by_name": { 289 + "name": "unique_sandbox_secret_by_name", 290 + "columns": [ 291 + { 292 + "expression": "sandbox_id", 293 + "isExpression": false, 294 + "asc": true, 295 + "nulls": "last" 296 + }, 297 + { 298 + "expression": "name", 299 + "isExpression": false, 300 + "asc": true, 301 + "nulls": "last" 302 + } 303 + ], 304 + "isUnique": true, 305 + "concurrently": false, 306 + "method": "btree", 307 + "with": {} 308 + } 309 + }, 310 + "foreignKeys": { 311 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 312 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 313 + "tableFrom": "sandbox_secrets", 314 + "tableTo": "sandboxes", 315 + "columnsFrom": [ 316 + "sandbox_id" 317 + ], 318 + "columnsTo": [ 319 + "id" 320 + ], 321 + "onDelete": "no action", 322 + "onUpdate": "no action" 323 + }, 324 + "sandbox_secrets_secret_id_secrets_id_fk": { 325 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 326 + "tableFrom": "sandbox_secrets", 327 + "tableTo": "secrets", 328 + "columnsFrom": [ 329 + "secret_id" 330 + ], 331 + "columnsTo": [ 332 + "id" 333 + ], 334 + "onDelete": "no action", 335 + "onUpdate": "no action" 336 + } 337 + }, 338 + "compositePrimaryKeys": {}, 339 + "uniqueConstraints": {}, 340 + "policies": {}, 341 + "checkConstraints": {}, 342 + "isRLSEnabled": false 343 + }, 344 + "public.sandbox_variables": { 345 + "name": "sandbox_variables", 346 + "schema": "", 347 + "columns": { 348 + "id": { 349 + "name": "id", 350 + "type": "text", 351 + "primaryKey": true, 352 + "notNull": true, 353 + "default": "xata_id()" 354 + }, 355 + "sandbox_id": { 356 + "name": "sandbox_id", 357 + "type": "text", 358 + "primaryKey": false, 359 + "notNull": true 360 + }, 361 + "variable_id": { 362 + "name": "variable_id", 363 + "type": "text", 364 + "primaryKey": false, 365 + "notNull": true 366 + }, 367 + "name": { 368 + "name": "name", 369 + "type": "text", 370 + "primaryKey": false, 371 + "notNull": false 372 + }, 373 + "created_at": { 374 + "name": "created_at", 375 + "type": "timestamp", 376 + "primaryKey": false, 377 + "notNull": true, 378 + "default": "now()" 379 + }, 380 + "updated_at": { 381 + "name": "updated_at", 382 + "type": "timestamp", 383 + "primaryKey": false, 384 + "notNull": true, 385 + "default": "now()" 386 + } 387 + }, 388 + "indexes": { 389 + "unique_sandbox_variables": { 390 + "name": "unique_sandbox_variables", 391 + "columns": [ 392 + { 393 + "expression": "sandbox_id", 394 + "isExpression": false, 395 + "asc": true, 396 + "nulls": "last" 397 + }, 398 + { 399 + "expression": "variable_id", 400 + "isExpression": false, 401 + "asc": true, 402 + "nulls": "last" 403 + } 404 + ], 405 + "isUnique": true, 406 + "concurrently": false, 407 + "method": "btree", 408 + "with": {} 409 + }, 410 + "unique_sandbox_variables_by_name": { 411 + "name": "unique_sandbox_variables_by_name", 412 + "columns": [ 413 + { 414 + "expression": "sandbox_id", 415 + "isExpression": false, 416 + "asc": true, 417 + "nulls": "last" 418 + }, 419 + { 420 + "expression": "name", 421 + "isExpression": false, 422 + "asc": true, 423 + "nulls": "last" 424 + } 425 + ], 426 + "isUnique": true, 427 + "concurrently": false, 428 + "method": "btree", 429 + "with": {} 430 + } 431 + }, 432 + "foreignKeys": { 433 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 434 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 435 + "tableFrom": "sandbox_variables", 436 + "tableTo": "sandboxes", 437 + "columnsFrom": [ 438 + "sandbox_id" 439 + ], 440 + "columnsTo": [ 441 + "id" 442 + ], 443 + "onDelete": "no action", 444 + "onUpdate": "no action" 445 + }, 446 + "sandbox_variables_variable_id_variables_id_fk": { 447 + "name": "sandbox_variables_variable_id_variables_id_fk", 448 + "tableFrom": "sandbox_variables", 449 + "tableTo": "variables", 450 + "columnsFrom": [ 451 + "variable_id" 452 + ], 453 + "columnsTo": [ 454 + "id" 455 + ], 456 + "onDelete": "no action", 457 + "onUpdate": "no action" 458 + } 459 + }, 460 + "compositePrimaryKeys": {}, 461 + "uniqueConstraints": {}, 462 + "policies": {}, 463 + "checkConstraints": {}, 464 + "isRLSEnabled": false 465 + }, 466 + "public.sandbox_volumes": { 467 + "name": "sandbox_volumes", 468 + "schema": "", 469 + "columns": { 470 + "id": { 471 + "name": "id", 472 + "type": "text", 473 + "primaryKey": true, 474 + "notNull": true, 475 + "default": "xata_id()" 476 + }, 477 + "sandbox_id": { 478 + "name": "sandbox_id", 479 + "type": "text", 480 + "primaryKey": false, 481 + "notNull": true 482 + }, 483 + "volume_id": { 484 + "name": "volume_id", 485 + "type": "text", 486 + "primaryKey": false, 487 + "notNull": true 488 + }, 489 + "path": { 490 + "name": "path", 491 + "type": "text", 492 + "primaryKey": false, 493 + "notNull": true 494 + }, 495 + "created_at": { 496 + "name": "created_at", 497 + "type": "timestamp", 498 + "primaryKey": false, 499 + "notNull": true, 500 + "default": "now()" 501 + }, 502 + "updated_at": { 503 + "name": "updated_at", 504 + "type": "timestamp", 505 + "primaryKey": false, 506 + "notNull": true, 507 + "default": "now()" 508 + } 509 + }, 510 + "indexes": { 511 + "unique_sandbox_volume": { 512 + "name": "unique_sandbox_volume", 513 + "columns": [ 514 + { 515 + "expression": "sandbox_id", 516 + "isExpression": false, 517 + "asc": true, 518 + "nulls": "last" 519 + }, 520 + { 521 + "expression": "volume_id", 522 + "isExpression": false, 523 + "asc": true, 524 + "nulls": "last" 525 + } 526 + ], 527 + "isUnique": true, 528 + "concurrently": false, 529 + "method": "btree", 530 + "with": {} 531 + }, 532 + "unique_sandbox_volume_path": { 533 + "name": "unique_sandbox_volume_path", 534 + "columns": [ 535 + { 536 + "expression": "sandbox_id", 537 + "isExpression": false, 538 + "asc": true, 539 + "nulls": "last" 540 + }, 541 + { 542 + "expression": "path", 543 + "isExpression": false, 544 + "asc": true, 545 + "nulls": "last" 546 + } 547 + ], 548 + "isUnique": true, 549 + "concurrently": false, 550 + "method": "btree", 551 + "with": {} 552 + } 553 + }, 554 + "foreignKeys": { 555 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 556 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 557 + "tableFrom": "sandbox_volumes", 558 + "tableTo": "sandboxes", 559 + "columnsFrom": [ 560 + "sandbox_id" 561 + ], 562 + "columnsTo": [ 563 + "id" 564 + ], 565 + "onDelete": "no action", 566 + "onUpdate": "no action" 567 + }, 568 + "sandbox_volumes_volume_id_volumes_id_fk": { 569 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 570 + "tableFrom": "sandbox_volumes", 571 + "tableTo": "volumes", 572 + "columnsFrom": [ 573 + "volume_id" 574 + ], 575 + "columnsTo": [ 576 + "id" 577 + ], 578 + "onDelete": "no action", 579 + "onUpdate": "no action" 580 + } 581 + }, 582 + "compositePrimaryKeys": {}, 583 + "uniqueConstraints": {}, 584 + "policies": {}, 585 + "checkConstraints": {}, 586 + "isRLSEnabled": false 587 + }, 588 + "public.sandboxes": { 589 + "name": "sandboxes", 590 + "schema": "", 591 + "columns": { 592 + "id": { 593 + "name": "id", 594 + "type": "text", 595 + "primaryKey": true, 596 + "notNull": true, 597 + "default": "sandbox_id()" 598 + }, 599 + "base": { 600 + "name": "base", 601 + "type": "text", 602 + "primaryKey": false, 603 + "notNull": false 604 + }, 605 + "name": { 606 + "name": "name", 607 + "type": "text", 608 + "primaryKey": false, 609 + "notNull": true 610 + }, 611 + "display_name": { 612 + "name": "display_name", 613 + "type": "text", 614 + "primaryKey": false, 615 + "notNull": false 616 + }, 617 + "uri": { 618 + "name": "uri", 619 + "type": "text", 620 + "primaryKey": false, 621 + "notNull": false 622 + }, 623 + "cid": { 624 + "name": "cid", 625 + "type": "text", 626 + "primaryKey": false, 627 + "notNull": false 628 + }, 629 + "repo": { 630 + "name": "repo", 631 + "type": "text", 632 + "primaryKey": false, 633 + "notNull": false 634 + }, 635 + "provider": { 636 + "name": "provider", 637 + "type": "text", 638 + "primaryKey": false, 639 + "notNull": true, 640 + "default": "'cloudflare'" 641 + }, 642 + "description": { 643 + "name": "description", 644 + "type": "text", 645 + "primaryKey": false, 646 + "notNull": false 647 + }, 648 + "logo": { 649 + "name": "logo", 650 + "type": "text", 651 + "primaryKey": false, 652 + "notNull": false 653 + }, 654 + "readme": { 655 + "name": "readme", 656 + "type": "text", 657 + "primaryKey": false, 658 + "notNull": false 659 + }, 660 + "public_key": { 661 + "name": "public_key", 662 + "type": "text", 663 + "primaryKey": false, 664 + "notNull": true 665 + }, 666 + "user_id": { 667 + "name": "user_id", 668 + "type": "text", 669 + "primaryKey": false, 670 + "notNull": false 671 + }, 672 + "instance_type": { 673 + "name": "instance_type", 674 + "type": "text", 675 + "primaryKey": false, 676 + "notNull": false 677 + }, 678 + "vcpus": { 679 + "name": "vcpus", 680 + "type": "integer", 681 + "primaryKey": false, 682 + "notNull": false 683 + }, 684 + "memory": { 685 + "name": "memory", 686 + "type": "integer", 687 + "primaryKey": false, 688 + "notNull": false 689 + }, 690 + "disk": { 691 + "name": "disk", 692 + "type": "integer", 693 + "primaryKey": false, 694 + "notNull": false 695 + }, 696 + "status": { 697 + "name": "status", 698 + "type": "text", 699 + "primaryKey": false, 700 + "notNull": true 701 + }, 702 + "keep_alive": { 703 + "name": "keep_alive", 704 + "type": "boolean", 705 + "primaryKey": false, 706 + "notNull": true, 707 + "default": false 708 + }, 709 + "sleep_after": { 710 + "name": "sleep_after", 711 + "type": "text", 712 + "primaryKey": false, 713 + "notNull": false 714 + }, 715 + "sandbox_id": { 716 + "name": "sandbox_id", 717 + "type": "text", 718 + "primaryKey": false, 719 + "notNull": false 720 + }, 721 + "installs": { 722 + "name": "installs", 723 + "type": "integer", 724 + "primaryKey": false, 725 + "notNull": true, 726 + "default": 0 727 + }, 728 + "started_at": { 729 + "name": "started_at", 730 + "type": "timestamp", 731 + "primaryKey": false, 732 + "notNull": false 733 + }, 734 + "created_at": { 735 + "name": "created_at", 736 + "type": "timestamp", 737 + "primaryKey": false, 738 + "notNull": true, 739 + "default": "now()" 740 + }, 741 + "updated_at": { 742 + "name": "updated_at", 743 + "type": "timestamp", 744 + "primaryKey": false, 745 + "notNull": true, 746 + "default": "now()" 747 + } 748 + }, 749 + "indexes": {}, 750 + "foreignKeys": { 751 + "sandboxes_user_id_users_id_fk": { 752 + "name": "sandboxes_user_id_users_id_fk", 753 + "tableFrom": "sandboxes", 754 + "tableTo": "users", 755 + "columnsFrom": [ 756 + "user_id" 757 + ], 758 + "columnsTo": [ 759 + "id" 760 + ], 761 + "onDelete": "no action", 762 + "onUpdate": "no action" 763 + } 764 + }, 765 + "compositePrimaryKeys": {}, 766 + "uniqueConstraints": { 767 + "sandboxes_name_unique": { 768 + "name": "sandboxes_name_unique", 769 + "nullsNotDistinct": false, 770 + "columns": [ 771 + "name" 772 + ] 773 + }, 774 + "sandboxes_uri_unique": { 775 + "name": "sandboxes_uri_unique", 776 + "nullsNotDistinct": false, 777 + "columns": [ 778 + "uri" 779 + ] 780 + }, 781 + "sandboxes_cid_unique": { 782 + "name": "sandboxes_cid_unique", 783 + "nullsNotDistinct": false, 784 + "columns": [ 785 + "cid" 786 + ] 787 + } 788 + }, 789 + "policies": {}, 790 + "checkConstraints": {}, 791 + "isRLSEnabled": false 792 + }, 793 + "public.secrets": { 794 + "name": "secrets", 795 + "schema": "", 796 + "columns": { 797 + "id": { 798 + "name": "id", 799 + "type": "text", 800 + "primaryKey": true, 801 + "notNull": true, 802 + "default": "secret_id()" 803 + }, 804 + "name": { 805 + "name": "name", 806 + "type": "text", 807 + "primaryKey": false, 808 + "notNull": true 809 + }, 810 + "value": { 811 + "name": "value", 812 + "type": "text", 813 + "primaryKey": false, 814 + "notNull": true 815 + }, 816 + "created_at": { 817 + "name": "created_at", 818 + "type": "timestamp", 819 + "primaryKey": false, 820 + "notNull": true, 821 + "default": "now()" 822 + } 823 + }, 824 + "indexes": {}, 825 + "foreignKeys": {}, 826 + "compositePrimaryKeys": {}, 827 + "uniqueConstraints": {}, 828 + "policies": {}, 829 + "checkConstraints": {}, 830 + "isRLSEnabled": false 831 + }, 832 + "public.snapshots": { 833 + "name": "snapshots", 834 + "schema": "", 835 + "columns": { 836 + "id": { 837 + "name": "id", 838 + "type": "text", 839 + "primaryKey": true, 840 + "notNull": true, 841 + "default": "snapshot_id()" 842 + }, 843 + "slug": { 844 + "name": "slug", 845 + "type": "text", 846 + "primaryKey": false, 847 + "notNull": true 848 + }, 849 + "created_at": { 850 + "name": "created_at", 851 + "type": "timestamp", 852 + "primaryKey": false, 853 + "notNull": true, 854 + "default": "now()" 855 + } 856 + }, 857 + "indexes": {}, 858 + "foreignKeys": {}, 859 + "compositePrimaryKeys": {}, 860 + "uniqueConstraints": { 861 + "snapshots_slug_unique": { 862 + "name": "snapshots_slug_unique", 863 + "nullsNotDistinct": false, 864 + "columns": [ 865 + "slug" 866 + ] 867 + } 868 + }, 869 + "policies": {}, 870 + "checkConstraints": {}, 871 + "isRLSEnabled": false 872 + }, 873 + "public.users": { 874 + "name": "users", 875 + "schema": "", 876 + "columns": { 877 + "id": { 878 + "name": "id", 879 + "type": "text", 880 + "primaryKey": true, 881 + "notNull": true, 882 + "default": "xata_id()" 883 + }, 884 + "did": { 885 + "name": "did", 886 + "type": "text", 887 + "primaryKey": false, 888 + "notNull": true 889 + }, 890 + "display_name": { 891 + "name": "display_name", 892 + "type": "text", 893 + "primaryKey": false, 894 + "notNull": false 895 + }, 896 + "handle": { 897 + "name": "handle", 898 + "type": "text", 899 + "primaryKey": false, 900 + "notNull": true 901 + }, 902 + "avatar": { 903 + "name": "avatar", 904 + "type": "text", 905 + "primaryKey": false, 906 + "notNull": false 907 + }, 908 + "created_at": { 909 + "name": "created_at", 910 + "type": "timestamp", 911 + "primaryKey": false, 912 + "notNull": true, 913 + "default": "now()" 914 + }, 915 + "updated_at": { 916 + "name": "updated_at", 917 + "type": "timestamp", 918 + "primaryKey": false, 919 + "notNull": true, 920 + "default": "now()" 921 + } 922 + }, 923 + "indexes": {}, 924 + "foreignKeys": {}, 925 + "compositePrimaryKeys": {}, 926 + "uniqueConstraints": { 927 + "users_did_unique": { 928 + "name": "users_did_unique", 929 + "nullsNotDistinct": false, 930 + "columns": [ 931 + "did" 932 + ] 933 + }, 934 + "users_handle_unique": { 935 + "name": "users_handle_unique", 936 + "nullsNotDistinct": false, 937 + "columns": [ 938 + "handle" 939 + ] 940 + } 941 + }, 942 + "policies": {}, 943 + "checkConstraints": {}, 944 + "isRLSEnabled": false 945 + }, 946 + "public.variables": { 947 + "name": "variables", 948 + "schema": "", 949 + "columns": { 950 + "id": { 951 + "name": "id", 952 + "type": "text", 953 + "primaryKey": true, 954 + "notNull": true, 955 + "default": "variable_id()" 956 + }, 957 + "name": { 958 + "name": "name", 959 + "type": "text", 960 + "primaryKey": false, 961 + "notNull": true 962 + }, 963 + "value": { 964 + "name": "value", 965 + "type": "text", 966 + "primaryKey": false, 967 + "notNull": true 968 + }, 969 + "created_at": { 970 + "name": "created_at", 971 + "type": "timestamp", 972 + "primaryKey": false, 973 + "notNull": true, 974 + "default": "now()" 975 + }, 976 + "updated_at": { 977 + "name": "updated_at", 978 + "type": "timestamp", 979 + "primaryKey": false, 980 + "notNull": true, 981 + "default": "now()" 982 + } 983 + }, 984 + "indexes": {}, 985 + "foreignKeys": {}, 986 + "compositePrimaryKeys": {}, 987 + "uniqueConstraints": {}, 988 + "policies": {}, 989 + "checkConstraints": {}, 990 + "isRLSEnabled": false 991 + }, 992 + "public.volumes": { 993 + "name": "volumes", 994 + "schema": "", 995 + "columns": { 996 + "id": { 997 + "name": "id", 998 + "type": "text", 999 + "primaryKey": true, 1000 + "notNull": true, 1001 + "default": "volume_id()" 1002 + }, 1003 + "slug": { 1004 + "name": "slug", 1005 + "type": "text", 1006 + "primaryKey": false, 1007 + "notNull": true 1008 + }, 1009 + "size": { 1010 + "name": "size", 1011 + "type": "integer", 1012 + "primaryKey": false, 1013 + "notNull": true 1014 + }, 1015 + "size_unit": { 1016 + "name": "size_unit", 1017 + "type": "text", 1018 + "primaryKey": false, 1019 + "notNull": true 1020 + }, 1021 + "created_at": { 1022 + "name": "created_at", 1023 + "type": "timestamp", 1024 + "primaryKey": false, 1025 + "notNull": true, 1026 + "default": "now()" 1027 + }, 1028 + "updated_at": { 1029 + "name": "updated_at", 1030 + "type": "timestamp", 1031 + "primaryKey": false, 1032 + "notNull": true, 1033 + "default": "now()" 1034 + } 1035 + }, 1036 + "indexes": {}, 1037 + "foreignKeys": {}, 1038 + "compositePrimaryKeys": {}, 1039 + "uniqueConstraints": { 1040 + "volumes_slug_unique": { 1041 + "name": "volumes_slug_unique", 1042 + "nullsNotDistinct": false, 1043 + "columns": [ 1044 + "slug" 1045 + ] 1046 + } 1047 + }, 1048 + "policies": {}, 1049 + "checkConstraints": {}, 1050 + "isRLSEnabled": false 1051 + } 1052 + }, 1053 + "enums": {}, 1054 + "schemas": {}, 1055 + "sequences": {}, 1056 + "roles": {}, 1057 + "policies": {}, 1058 + "views": {}, 1059 + "_meta": { 1060 + "columns": {}, 1061 + "schemas": {}, 1062 + "tables": {} 1063 + } 1064 + }
+1070
apps/cf-sandbox/drizzle/meta/0018_snapshot.json
··· 1 + { 2 + "id": "0a58b7af-2443-495b-b5ec-bfdacb42409a", 3 + "prevId": "17ad78ac-906e-40f2-a2c9-29b9b18331f3", 4 + "version": "7", 5 + "dialect": "postgresql", 6 + "tables": { 7 + "public.authorized_keys": { 8 + "name": "authorized_keys", 9 + "schema": "", 10 + "columns": { 11 + "id": { 12 + "name": "id", 13 + "type": "text", 14 + "primaryKey": true, 15 + "notNull": true, 16 + "default": "xata_id()" 17 + }, 18 + "sandbox_id": { 19 + "name": "sandbox_id", 20 + "type": "text", 21 + "primaryKey": false, 22 + "notNull": false 23 + }, 24 + "public_key": { 25 + "name": "public_key", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true 29 + }, 30 + "created_at": { 31 + "name": "created_at", 32 + "type": "timestamp", 33 + "primaryKey": false, 34 + "notNull": true, 35 + "default": "now()" 36 + } 37 + }, 38 + "indexes": {}, 39 + "foreignKeys": { 40 + "authorized_keys_sandbox_id_sandboxes_id_fk": { 41 + "name": "authorized_keys_sandbox_id_sandboxes_id_fk", 42 + "tableFrom": "authorized_keys", 43 + "tableTo": "sandboxes", 44 + "columnsFrom": [ 45 + "sandbox_id" 46 + ], 47 + "columnsTo": [ 48 + "id" 49 + ], 50 + "onDelete": "no action", 51 + "onUpdate": "no action" 52 + } 53 + }, 54 + "compositePrimaryKeys": {}, 55 + "uniqueConstraints": {}, 56 + "policies": {}, 57 + "checkConstraints": {}, 58 + "isRLSEnabled": false 59 + }, 60 + "public.files": { 61 + "name": "files", 62 + "schema": "", 63 + "columns": { 64 + "id": { 65 + "name": "id", 66 + "type": "text", 67 + "primaryKey": true, 68 + "notNull": true, 69 + "default": "variable_id()" 70 + }, 71 + "content": { 72 + "name": "content", 73 + "type": "text", 74 + "primaryKey": false, 75 + "notNull": true 76 + }, 77 + "created_at": { 78 + "name": "created_at", 79 + "type": "timestamp", 80 + "primaryKey": false, 81 + "notNull": true, 82 + "default": "now()" 83 + }, 84 + "updated_at": { 85 + "name": "updated_at", 86 + "type": "timestamp", 87 + "primaryKey": false, 88 + "notNull": true, 89 + "default": "now()" 90 + } 91 + }, 92 + "indexes": {}, 93 + "foreignKeys": {}, 94 + "compositePrimaryKeys": {}, 95 + "uniqueConstraints": {}, 96 + "policies": {}, 97 + "checkConstraints": {}, 98 + "isRLSEnabled": false 99 + }, 100 + "public.sandbox_files": { 101 + "name": "sandbox_files", 102 + "schema": "", 103 + "columns": { 104 + "id": { 105 + "name": "id", 106 + "type": "text", 107 + "primaryKey": true, 108 + "notNull": true, 109 + "default": "xata_id()" 110 + }, 111 + "sandbox_id": { 112 + "name": "sandbox_id", 113 + "type": "text", 114 + "primaryKey": false, 115 + "notNull": true 116 + }, 117 + "file_id": { 118 + "name": "file_id", 119 + "type": "text", 120 + "primaryKey": false, 121 + "notNull": true 122 + }, 123 + "path": { 124 + "name": "path", 125 + "type": "text", 126 + "primaryKey": false, 127 + "notNull": true 128 + }, 129 + "created_at": { 130 + "name": "created_at", 131 + "type": "timestamp", 132 + "primaryKey": false, 133 + "notNull": true, 134 + "default": "now()" 135 + }, 136 + "updated_at": { 137 + "name": "updated_at", 138 + "type": "timestamp", 139 + "primaryKey": false, 140 + "notNull": true, 141 + "default": "now()" 142 + } 143 + }, 144 + "indexes": { 145 + "unique_sandbox_file": { 146 + "name": "unique_sandbox_file", 147 + "columns": [ 148 + { 149 + "expression": "sandbox_id", 150 + "isExpression": false, 151 + "asc": true, 152 + "nulls": "last" 153 + }, 154 + { 155 + "expression": "file_id", 156 + "isExpression": false, 157 + "asc": true, 158 + "nulls": "last" 159 + } 160 + ], 161 + "isUnique": true, 162 + "concurrently": false, 163 + "method": "btree", 164 + "with": {} 165 + }, 166 + "unique_sandbox_file_path": { 167 + "name": "unique_sandbox_file_path", 168 + "columns": [ 169 + { 170 + "expression": "sandbox_id", 171 + "isExpression": false, 172 + "asc": true, 173 + "nulls": "last" 174 + }, 175 + { 176 + "expression": "path", 177 + "isExpression": false, 178 + "asc": true, 179 + "nulls": "last" 180 + } 181 + ], 182 + "isUnique": true, 183 + "concurrently": false, 184 + "method": "btree", 185 + "with": {} 186 + } 187 + }, 188 + "foreignKeys": { 189 + "sandbox_files_sandbox_id_sandboxes_id_fk": { 190 + "name": "sandbox_files_sandbox_id_sandboxes_id_fk", 191 + "tableFrom": "sandbox_files", 192 + "tableTo": "sandboxes", 193 + "columnsFrom": [ 194 + "sandbox_id" 195 + ], 196 + "columnsTo": [ 197 + "id" 198 + ], 199 + "onDelete": "no action", 200 + "onUpdate": "no action" 201 + }, 202 + "sandbox_files_file_id_files_id_fk": { 203 + "name": "sandbox_files_file_id_files_id_fk", 204 + "tableFrom": "sandbox_files", 205 + "tableTo": "files", 206 + "columnsFrom": [ 207 + "file_id" 208 + ], 209 + "columnsTo": [ 210 + "id" 211 + ], 212 + "onDelete": "no action", 213 + "onUpdate": "no action" 214 + } 215 + }, 216 + "compositePrimaryKeys": {}, 217 + "uniqueConstraints": {}, 218 + "policies": {}, 219 + "checkConstraints": {}, 220 + "isRLSEnabled": false 221 + }, 222 + "public.sandbox_secrets": { 223 + "name": "sandbox_secrets", 224 + "schema": "", 225 + "columns": { 226 + "id": { 227 + "name": "id", 228 + "type": "text", 229 + "primaryKey": true, 230 + "notNull": true, 231 + "default": "xata_id()" 232 + }, 233 + "sandbox_id": { 234 + "name": "sandbox_id", 235 + "type": "text", 236 + "primaryKey": false, 237 + "notNull": true 238 + }, 239 + "secret_id": { 240 + "name": "secret_id", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": true 244 + }, 245 + "name": { 246 + "name": "name", 247 + "type": "text", 248 + "primaryKey": false, 249 + "notNull": false 250 + }, 251 + "created_at": { 252 + "name": "created_at", 253 + "type": "timestamp", 254 + "primaryKey": false, 255 + "notNull": true, 256 + "default": "now()" 257 + }, 258 + "updated_at": { 259 + "name": "updated_at", 260 + "type": "timestamp", 261 + "primaryKey": false, 262 + "notNull": true, 263 + "default": "now()" 264 + } 265 + }, 266 + "indexes": { 267 + "unique_sandbox_secret": { 268 + "name": "unique_sandbox_secret", 269 + "columns": [ 270 + { 271 + "expression": "sandbox_id", 272 + "isExpression": false, 273 + "asc": true, 274 + "nulls": "last" 275 + }, 276 + { 277 + "expression": "secret_id", 278 + "isExpression": false, 279 + "asc": true, 280 + "nulls": "last" 281 + } 282 + ], 283 + "isUnique": true, 284 + "concurrently": false, 285 + "method": "btree", 286 + "with": {} 287 + }, 288 + "unique_sandbox_secret_by_name": { 289 + "name": "unique_sandbox_secret_by_name", 290 + "columns": [ 291 + { 292 + "expression": "sandbox_id", 293 + "isExpression": false, 294 + "asc": true, 295 + "nulls": "last" 296 + }, 297 + { 298 + "expression": "name", 299 + "isExpression": false, 300 + "asc": true, 301 + "nulls": "last" 302 + } 303 + ], 304 + "isUnique": true, 305 + "concurrently": false, 306 + "method": "btree", 307 + "with": {} 308 + } 309 + }, 310 + "foreignKeys": { 311 + "sandbox_secrets_sandbox_id_sandboxes_id_fk": { 312 + "name": "sandbox_secrets_sandbox_id_sandboxes_id_fk", 313 + "tableFrom": "sandbox_secrets", 314 + "tableTo": "sandboxes", 315 + "columnsFrom": [ 316 + "sandbox_id" 317 + ], 318 + "columnsTo": [ 319 + "id" 320 + ], 321 + "onDelete": "no action", 322 + "onUpdate": "no action" 323 + }, 324 + "sandbox_secrets_secret_id_secrets_id_fk": { 325 + "name": "sandbox_secrets_secret_id_secrets_id_fk", 326 + "tableFrom": "sandbox_secrets", 327 + "tableTo": "secrets", 328 + "columnsFrom": [ 329 + "secret_id" 330 + ], 331 + "columnsTo": [ 332 + "id" 333 + ], 334 + "onDelete": "no action", 335 + "onUpdate": "no action" 336 + } 337 + }, 338 + "compositePrimaryKeys": {}, 339 + "uniqueConstraints": {}, 340 + "policies": {}, 341 + "checkConstraints": {}, 342 + "isRLSEnabled": false 343 + }, 344 + "public.sandbox_variables": { 345 + "name": "sandbox_variables", 346 + "schema": "", 347 + "columns": { 348 + "id": { 349 + "name": "id", 350 + "type": "text", 351 + "primaryKey": true, 352 + "notNull": true, 353 + "default": "xata_id()" 354 + }, 355 + "sandbox_id": { 356 + "name": "sandbox_id", 357 + "type": "text", 358 + "primaryKey": false, 359 + "notNull": true 360 + }, 361 + "variable_id": { 362 + "name": "variable_id", 363 + "type": "text", 364 + "primaryKey": false, 365 + "notNull": true 366 + }, 367 + "name": { 368 + "name": "name", 369 + "type": "text", 370 + "primaryKey": false, 371 + "notNull": false 372 + }, 373 + "created_at": { 374 + "name": "created_at", 375 + "type": "timestamp", 376 + "primaryKey": false, 377 + "notNull": true, 378 + "default": "now()" 379 + }, 380 + "updated_at": { 381 + "name": "updated_at", 382 + "type": "timestamp", 383 + "primaryKey": false, 384 + "notNull": true, 385 + "default": "now()" 386 + } 387 + }, 388 + "indexes": { 389 + "unique_sandbox_variables": { 390 + "name": "unique_sandbox_variables", 391 + "columns": [ 392 + { 393 + "expression": "sandbox_id", 394 + "isExpression": false, 395 + "asc": true, 396 + "nulls": "last" 397 + }, 398 + { 399 + "expression": "variable_id", 400 + "isExpression": false, 401 + "asc": true, 402 + "nulls": "last" 403 + } 404 + ], 405 + "isUnique": true, 406 + "concurrently": false, 407 + "method": "btree", 408 + "with": {} 409 + }, 410 + "unique_sandbox_variables_by_name": { 411 + "name": "unique_sandbox_variables_by_name", 412 + "columns": [ 413 + { 414 + "expression": "sandbox_id", 415 + "isExpression": false, 416 + "asc": true, 417 + "nulls": "last" 418 + }, 419 + { 420 + "expression": "name", 421 + "isExpression": false, 422 + "asc": true, 423 + "nulls": "last" 424 + } 425 + ], 426 + "isUnique": true, 427 + "concurrently": false, 428 + "method": "btree", 429 + "with": {} 430 + } 431 + }, 432 + "foreignKeys": { 433 + "sandbox_variables_sandbox_id_sandboxes_id_fk": { 434 + "name": "sandbox_variables_sandbox_id_sandboxes_id_fk", 435 + "tableFrom": "sandbox_variables", 436 + "tableTo": "sandboxes", 437 + "columnsFrom": [ 438 + "sandbox_id" 439 + ], 440 + "columnsTo": [ 441 + "id" 442 + ], 443 + "onDelete": "no action", 444 + "onUpdate": "no action" 445 + }, 446 + "sandbox_variables_variable_id_variables_id_fk": { 447 + "name": "sandbox_variables_variable_id_variables_id_fk", 448 + "tableFrom": "sandbox_variables", 449 + "tableTo": "variables", 450 + "columnsFrom": [ 451 + "variable_id" 452 + ], 453 + "columnsTo": [ 454 + "id" 455 + ], 456 + "onDelete": "no action", 457 + "onUpdate": "no action" 458 + } 459 + }, 460 + "compositePrimaryKeys": {}, 461 + "uniqueConstraints": {}, 462 + "policies": {}, 463 + "checkConstraints": {}, 464 + "isRLSEnabled": false 465 + }, 466 + "public.sandbox_volumes": { 467 + "name": "sandbox_volumes", 468 + "schema": "", 469 + "columns": { 470 + "id": { 471 + "name": "id", 472 + "type": "text", 473 + "primaryKey": true, 474 + "notNull": true, 475 + "default": "xata_id()" 476 + }, 477 + "sandbox_id": { 478 + "name": "sandbox_id", 479 + "type": "text", 480 + "primaryKey": false, 481 + "notNull": true 482 + }, 483 + "volume_id": { 484 + "name": "volume_id", 485 + "type": "text", 486 + "primaryKey": false, 487 + "notNull": true 488 + }, 489 + "name": { 490 + "name": "name", 491 + "type": "text", 492 + "primaryKey": false, 493 + "notNull": false 494 + }, 495 + "path": { 496 + "name": "path", 497 + "type": "text", 498 + "primaryKey": false, 499 + "notNull": true 500 + }, 501 + "created_at": { 502 + "name": "created_at", 503 + "type": "timestamp", 504 + "primaryKey": false, 505 + "notNull": true, 506 + "default": "now()" 507 + }, 508 + "updated_at": { 509 + "name": "updated_at", 510 + "type": "timestamp", 511 + "primaryKey": false, 512 + "notNull": true, 513 + "default": "now()" 514 + } 515 + }, 516 + "indexes": { 517 + "unique_sandbox_volume": { 518 + "name": "unique_sandbox_volume", 519 + "columns": [ 520 + { 521 + "expression": "sandbox_id", 522 + "isExpression": false, 523 + "asc": true, 524 + "nulls": "last" 525 + }, 526 + { 527 + "expression": "volume_id", 528 + "isExpression": false, 529 + "asc": true, 530 + "nulls": "last" 531 + } 532 + ], 533 + "isUnique": true, 534 + "concurrently": false, 535 + "method": "btree", 536 + "with": {} 537 + }, 538 + "unique_sandbox_volume_path": { 539 + "name": "unique_sandbox_volume_path", 540 + "columns": [ 541 + { 542 + "expression": "sandbox_id", 543 + "isExpression": false, 544 + "asc": true, 545 + "nulls": "last" 546 + }, 547 + { 548 + "expression": "path", 549 + "isExpression": false, 550 + "asc": true, 551 + "nulls": "last" 552 + } 553 + ], 554 + "isUnique": true, 555 + "concurrently": false, 556 + "method": "btree", 557 + "with": {} 558 + } 559 + }, 560 + "foreignKeys": { 561 + "sandbox_volumes_sandbox_id_sandboxes_id_fk": { 562 + "name": "sandbox_volumes_sandbox_id_sandboxes_id_fk", 563 + "tableFrom": "sandbox_volumes", 564 + "tableTo": "sandboxes", 565 + "columnsFrom": [ 566 + "sandbox_id" 567 + ], 568 + "columnsTo": [ 569 + "id" 570 + ], 571 + "onDelete": "no action", 572 + "onUpdate": "no action" 573 + }, 574 + "sandbox_volumes_volume_id_volumes_id_fk": { 575 + "name": "sandbox_volumes_volume_id_volumes_id_fk", 576 + "tableFrom": "sandbox_volumes", 577 + "tableTo": "volumes", 578 + "columnsFrom": [ 579 + "volume_id" 580 + ], 581 + "columnsTo": [ 582 + "id" 583 + ], 584 + "onDelete": "no action", 585 + "onUpdate": "no action" 586 + } 587 + }, 588 + "compositePrimaryKeys": {}, 589 + "uniqueConstraints": {}, 590 + "policies": {}, 591 + "checkConstraints": {}, 592 + "isRLSEnabled": false 593 + }, 594 + "public.sandboxes": { 595 + "name": "sandboxes", 596 + "schema": "", 597 + "columns": { 598 + "id": { 599 + "name": "id", 600 + "type": "text", 601 + "primaryKey": true, 602 + "notNull": true, 603 + "default": "sandbox_id()" 604 + }, 605 + "base": { 606 + "name": "base", 607 + "type": "text", 608 + "primaryKey": false, 609 + "notNull": false 610 + }, 611 + "name": { 612 + "name": "name", 613 + "type": "text", 614 + "primaryKey": false, 615 + "notNull": true 616 + }, 617 + "display_name": { 618 + "name": "display_name", 619 + "type": "text", 620 + "primaryKey": false, 621 + "notNull": false 622 + }, 623 + "uri": { 624 + "name": "uri", 625 + "type": "text", 626 + "primaryKey": false, 627 + "notNull": false 628 + }, 629 + "cid": { 630 + "name": "cid", 631 + "type": "text", 632 + "primaryKey": false, 633 + "notNull": false 634 + }, 635 + "repo": { 636 + "name": "repo", 637 + "type": "text", 638 + "primaryKey": false, 639 + "notNull": false 640 + }, 641 + "provider": { 642 + "name": "provider", 643 + "type": "text", 644 + "primaryKey": false, 645 + "notNull": true, 646 + "default": "'cloudflare'" 647 + }, 648 + "description": { 649 + "name": "description", 650 + "type": "text", 651 + "primaryKey": false, 652 + "notNull": false 653 + }, 654 + "logo": { 655 + "name": "logo", 656 + "type": "text", 657 + "primaryKey": false, 658 + "notNull": false 659 + }, 660 + "readme": { 661 + "name": "readme", 662 + "type": "text", 663 + "primaryKey": false, 664 + "notNull": false 665 + }, 666 + "public_key": { 667 + "name": "public_key", 668 + "type": "text", 669 + "primaryKey": false, 670 + "notNull": true 671 + }, 672 + "user_id": { 673 + "name": "user_id", 674 + "type": "text", 675 + "primaryKey": false, 676 + "notNull": false 677 + }, 678 + "instance_type": { 679 + "name": "instance_type", 680 + "type": "text", 681 + "primaryKey": false, 682 + "notNull": false 683 + }, 684 + "vcpus": { 685 + "name": "vcpus", 686 + "type": "integer", 687 + "primaryKey": false, 688 + "notNull": false 689 + }, 690 + "memory": { 691 + "name": "memory", 692 + "type": "integer", 693 + "primaryKey": false, 694 + "notNull": false 695 + }, 696 + "disk": { 697 + "name": "disk", 698 + "type": "integer", 699 + "primaryKey": false, 700 + "notNull": false 701 + }, 702 + "status": { 703 + "name": "status", 704 + "type": "text", 705 + "primaryKey": false, 706 + "notNull": true 707 + }, 708 + "keep_alive": { 709 + "name": "keep_alive", 710 + "type": "boolean", 711 + "primaryKey": false, 712 + "notNull": true, 713 + "default": false 714 + }, 715 + "sleep_after": { 716 + "name": "sleep_after", 717 + "type": "text", 718 + "primaryKey": false, 719 + "notNull": false 720 + }, 721 + "sandbox_id": { 722 + "name": "sandbox_id", 723 + "type": "text", 724 + "primaryKey": false, 725 + "notNull": false 726 + }, 727 + "installs": { 728 + "name": "installs", 729 + "type": "integer", 730 + "primaryKey": false, 731 + "notNull": true, 732 + "default": 0 733 + }, 734 + "started_at": { 735 + "name": "started_at", 736 + "type": "timestamp", 737 + "primaryKey": false, 738 + "notNull": false 739 + }, 740 + "created_at": { 741 + "name": "created_at", 742 + "type": "timestamp", 743 + "primaryKey": false, 744 + "notNull": true, 745 + "default": "now()" 746 + }, 747 + "updated_at": { 748 + "name": "updated_at", 749 + "type": "timestamp", 750 + "primaryKey": false, 751 + "notNull": true, 752 + "default": "now()" 753 + } 754 + }, 755 + "indexes": {}, 756 + "foreignKeys": { 757 + "sandboxes_user_id_users_id_fk": { 758 + "name": "sandboxes_user_id_users_id_fk", 759 + "tableFrom": "sandboxes", 760 + "tableTo": "users", 761 + "columnsFrom": [ 762 + "user_id" 763 + ], 764 + "columnsTo": [ 765 + "id" 766 + ], 767 + "onDelete": "no action", 768 + "onUpdate": "no action" 769 + } 770 + }, 771 + "compositePrimaryKeys": {}, 772 + "uniqueConstraints": { 773 + "sandboxes_name_unique": { 774 + "name": "sandboxes_name_unique", 775 + "nullsNotDistinct": false, 776 + "columns": [ 777 + "name" 778 + ] 779 + }, 780 + "sandboxes_uri_unique": { 781 + "name": "sandboxes_uri_unique", 782 + "nullsNotDistinct": false, 783 + "columns": [ 784 + "uri" 785 + ] 786 + }, 787 + "sandboxes_cid_unique": { 788 + "name": "sandboxes_cid_unique", 789 + "nullsNotDistinct": false, 790 + "columns": [ 791 + "cid" 792 + ] 793 + } 794 + }, 795 + "policies": {}, 796 + "checkConstraints": {}, 797 + "isRLSEnabled": false 798 + }, 799 + "public.secrets": { 800 + "name": "secrets", 801 + "schema": "", 802 + "columns": { 803 + "id": { 804 + "name": "id", 805 + "type": "text", 806 + "primaryKey": true, 807 + "notNull": true, 808 + "default": "secret_id()" 809 + }, 810 + "name": { 811 + "name": "name", 812 + "type": "text", 813 + "primaryKey": false, 814 + "notNull": true 815 + }, 816 + "value": { 817 + "name": "value", 818 + "type": "text", 819 + "primaryKey": false, 820 + "notNull": true 821 + }, 822 + "created_at": { 823 + "name": "created_at", 824 + "type": "timestamp", 825 + "primaryKey": false, 826 + "notNull": true, 827 + "default": "now()" 828 + } 829 + }, 830 + "indexes": {}, 831 + "foreignKeys": {}, 832 + "compositePrimaryKeys": {}, 833 + "uniqueConstraints": {}, 834 + "policies": {}, 835 + "checkConstraints": {}, 836 + "isRLSEnabled": false 837 + }, 838 + "public.snapshots": { 839 + "name": "snapshots", 840 + "schema": "", 841 + "columns": { 842 + "id": { 843 + "name": "id", 844 + "type": "text", 845 + "primaryKey": true, 846 + "notNull": true, 847 + "default": "snapshot_id()" 848 + }, 849 + "slug": { 850 + "name": "slug", 851 + "type": "text", 852 + "primaryKey": false, 853 + "notNull": true 854 + }, 855 + "created_at": { 856 + "name": "created_at", 857 + "type": "timestamp", 858 + "primaryKey": false, 859 + "notNull": true, 860 + "default": "now()" 861 + } 862 + }, 863 + "indexes": {}, 864 + "foreignKeys": {}, 865 + "compositePrimaryKeys": {}, 866 + "uniqueConstraints": { 867 + "snapshots_slug_unique": { 868 + "name": "snapshots_slug_unique", 869 + "nullsNotDistinct": false, 870 + "columns": [ 871 + "slug" 872 + ] 873 + } 874 + }, 875 + "policies": {}, 876 + "checkConstraints": {}, 877 + "isRLSEnabled": false 878 + }, 879 + "public.users": { 880 + "name": "users", 881 + "schema": "", 882 + "columns": { 883 + "id": { 884 + "name": "id", 885 + "type": "text", 886 + "primaryKey": true, 887 + "notNull": true, 888 + "default": "xata_id()" 889 + }, 890 + "did": { 891 + "name": "did", 892 + "type": "text", 893 + "primaryKey": false, 894 + "notNull": true 895 + }, 896 + "display_name": { 897 + "name": "display_name", 898 + "type": "text", 899 + "primaryKey": false, 900 + "notNull": false 901 + }, 902 + "handle": { 903 + "name": "handle", 904 + "type": "text", 905 + "primaryKey": false, 906 + "notNull": true 907 + }, 908 + "avatar": { 909 + "name": "avatar", 910 + "type": "text", 911 + "primaryKey": false, 912 + "notNull": false 913 + }, 914 + "created_at": { 915 + "name": "created_at", 916 + "type": "timestamp", 917 + "primaryKey": false, 918 + "notNull": true, 919 + "default": "now()" 920 + }, 921 + "updated_at": { 922 + "name": "updated_at", 923 + "type": "timestamp", 924 + "primaryKey": false, 925 + "notNull": true, 926 + "default": "now()" 927 + } 928 + }, 929 + "indexes": {}, 930 + "foreignKeys": {}, 931 + "compositePrimaryKeys": {}, 932 + "uniqueConstraints": { 933 + "users_did_unique": { 934 + "name": "users_did_unique", 935 + "nullsNotDistinct": false, 936 + "columns": [ 937 + "did" 938 + ] 939 + }, 940 + "users_handle_unique": { 941 + "name": "users_handle_unique", 942 + "nullsNotDistinct": false, 943 + "columns": [ 944 + "handle" 945 + ] 946 + } 947 + }, 948 + "policies": {}, 949 + "checkConstraints": {}, 950 + "isRLSEnabled": false 951 + }, 952 + "public.variables": { 953 + "name": "variables", 954 + "schema": "", 955 + "columns": { 956 + "id": { 957 + "name": "id", 958 + "type": "text", 959 + "primaryKey": true, 960 + "notNull": true, 961 + "default": "variable_id()" 962 + }, 963 + "name": { 964 + "name": "name", 965 + "type": "text", 966 + "primaryKey": false, 967 + "notNull": true 968 + }, 969 + "value": { 970 + "name": "value", 971 + "type": "text", 972 + "primaryKey": false, 973 + "notNull": true 974 + }, 975 + "created_at": { 976 + "name": "created_at", 977 + "type": "timestamp", 978 + "primaryKey": false, 979 + "notNull": true, 980 + "default": "now()" 981 + }, 982 + "updated_at": { 983 + "name": "updated_at", 984 + "type": "timestamp", 985 + "primaryKey": false, 986 + "notNull": true, 987 + "default": "now()" 988 + } 989 + }, 990 + "indexes": {}, 991 + "foreignKeys": {}, 992 + "compositePrimaryKeys": {}, 993 + "uniqueConstraints": {}, 994 + "policies": {}, 995 + "checkConstraints": {}, 996 + "isRLSEnabled": false 997 + }, 998 + "public.volumes": { 999 + "name": "volumes", 1000 + "schema": "", 1001 + "columns": { 1002 + "id": { 1003 + "name": "id", 1004 + "type": "text", 1005 + "primaryKey": true, 1006 + "notNull": true, 1007 + "default": "volume_id()" 1008 + }, 1009 + "slug": { 1010 + "name": "slug", 1011 + "type": "text", 1012 + "primaryKey": false, 1013 + "notNull": true 1014 + }, 1015 + "size": { 1016 + "name": "size", 1017 + "type": "integer", 1018 + "primaryKey": false, 1019 + "notNull": true 1020 + }, 1021 + "size_unit": { 1022 + "name": "size_unit", 1023 + "type": "text", 1024 + "primaryKey": false, 1025 + "notNull": true 1026 + }, 1027 + "created_at": { 1028 + "name": "created_at", 1029 + "type": "timestamp", 1030 + "primaryKey": false, 1031 + "notNull": true, 1032 + "default": "now()" 1033 + }, 1034 + "updated_at": { 1035 + "name": "updated_at", 1036 + "type": "timestamp", 1037 + "primaryKey": false, 1038 + "notNull": true, 1039 + "default": "now()" 1040 + } 1041 + }, 1042 + "indexes": {}, 1043 + "foreignKeys": {}, 1044 + "compositePrimaryKeys": {}, 1045 + "uniqueConstraints": { 1046 + "volumes_slug_unique": { 1047 + "name": "volumes_slug_unique", 1048 + "nullsNotDistinct": false, 1049 + "columns": [ 1050 + "slug" 1051 + ] 1052 + } 1053 + }, 1054 + "policies": {}, 1055 + "checkConstraints": {}, 1056 + "isRLSEnabled": false 1057 + } 1058 + }, 1059 + "enums": {}, 1060 + "schemas": {}, 1061 + "sequences": {}, 1062 + "roles": {}, 1063 + "policies": {}, 1064 + "views": {}, 1065 + "_meta": { 1066 + "columns": {}, 1067 + "schemas": {}, 1068 + "tables": {} 1069 + } 1070 + }
+14
apps/cf-sandbox/drizzle/meta/_journal.json
··· 120 120 "when": 1772858827451, 121 121 "tag": "0016_milky_invaders", 122 122 "breakpoints": true 123 + }, 124 + { 125 + "idx": 17, 126 + "version": "7", 127 + "when": 1772993431535, 128 + "tag": "0017_pale_nehzno", 129 + "breakpoints": true 130 + }, 131 + { 132 + "idx": 18, 133 + "version": "7", 134 + "when": 1772997231610, 135 + "tag": "0018_awesome_sally_floyd", 136 + "breakpoints": true 123 137 } 124 138 ] 125 139 }
+3 -1
apps/cf-sandbox/src/schema/sandbox-files.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes"; 4 4 import files from "./files"; 5 5 ··· 16 16 .notNull() 17 17 .references(() => files.id), 18 18 path: text("path").notNull(), 19 + createdAt: timestamp("created_at").defaultNow().notNull(), 20 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 21 }, 20 22 (t) => [ 21 23 uniqueIndex("unique_sandbox_file").on(t.sandboxId, t.fileId),
+3 -1
apps/cf-sandbox/src/schema/sandbox-secrets.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes"; 4 4 import secrets from "./secrets"; 5 5 ··· 16 16 .notNull() 17 17 .references(() => secrets.id), 18 18 name: text("name"), 19 + createdAt: timestamp("created_at").defaultNow().notNull(), 20 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 21 }, 20 22 (t) => [ 21 23 uniqueIndex("unique_sandbox_secret").on(t.sandboxId, t.secretId),
+3 -1
apps/cf-sandbox/src/schema/sandbox-variables.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes"; 4 4 import variables from "./variables"; 5 5 ··· 16 16 .notNull() 17 17 .references(() => variables.id), 18 18 name: text("name"), 19 + createdAt: timestamp("created_at").defaultNow().notNull(), 20 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 21 }, 20 22 (t) => [ 21 23 uniqueIndex("unique_sandbox_variables").on(t.sandboxId, t.variableId),
+4 -1
apps/cf-sandbox/src/schema/sandbox-volumes.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, uniqueIndex, timestamp } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes"; 4 4 import volumes from "./volumes"; 5 5 ··· 15 15 volumeId: text("volume_id") 16 16 .notNull() 17 17 .references(() => volumes.id), 18 + name: text("name"), 18 19 path: text("path").notNull(), 20 + createdAt: timestamp("created_at").defaultNow().notNull(), 21 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 22 }, 20 23 (t) => [ 21 24 uniqueIndex("unique_sandbox_volume").on(t.sandboxId, t.volumeId),
+3 -1
apps/sandbox/src/schema/sandbox-files.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes.ts"; 4 4 import files from "./files.ts"; 5 5 ··· 16 16 .notNull() 17 17 .references(() => files.id), 18 18 path: text("path").notNull(), 19 + createdAt: timestamp("created_at").defaultNow().notNull(), 20 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 21 }, 20 22 (t) => [ 21 23 uniqueIndex("unique_sandbox_file").on(t.sandboxId, t.fileId),
+3 -1
apps/sandbox/src/schema/sandbox-secrets.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes.ts"; 4 4 import secrets from "./secrets.ts"; 5 5 ··· 16 16 .notNull() 17 17 .references(() => secrets.id), 18 18 name: text("name"), 19 + createdAt: timestamp("created_at").defaultNow().notNull(), 20 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 21 }, 20 22 (t) => [ 21 23 uniqueIndex("unique_sandbox_secret").on(t.sandboxId, t.secretId),
+3 -1
apps/sandbox/src/schema/sandbox-variables.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes.ts"; 4 4 import variables from "./variables.ts"; 5 5 ··· 16 16 .notNull() 17 17 .references(() => variables.id), 18 18 name: text("name"), 19 + createdAt: timestamp("created_at").defaultNow().notNull(), 20 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 21 }, 20 22 (t) => [ 21 23 uniqueIndex("unique_sandbox_variables").on(t.sandboxId, t.variableId),
+4 -1
apps/sandbox/src/schema/sandbox-volumes.ts
··· 1 1 import { type InferInsertModel, type InferSelectModel, sql } from "drizzle-orm"; 2 - import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core"; 2 + import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; 3 3 import sandboxes from "./sandboxes.ts"; 4 4 import volumes from "./volumes.ts"; 5 5 ··· 15 15 volumeId: text("volume_id") 16 16 .notNull() 17 17 .references(() => volumes.id), 18 + name: text("name"), 18 19 path: text("path").notNull(), 20 + createdAt: timestamp("created_at").defaultNow().notNull(), 21 + updatedAt: timestamp("updated_at").defaultNow().notNull(), 19 22 }, 20 23 (t) => [ 21 24 uniqueIndex("unique_sandbox_volume").on(t.sandboxId, t.volumeId),
+9 -5
apps/web/src/api/file.ts
··· 1 1 import { client } from "."; 2 + import type { File } from "../types/file"; 2 3 3 4 export const addFile = (sandboxId: string, path: string, content: string) => 4 5 client.post( ··· 24 25 }, 25 26 }); 26 27 27 - export const getFiles = () => 28 - client.get("/xrpc/io.pocketenv.file.getFiles", { 29 - headers: { 30 - Authorization: `Bearer ${localStorage.getItem("token")}`, 28 + export const getFiles = (sandboxId?: string, offset?: number, limit?: number) => 29 + client.get<{ files: File[]; total: number }>( 30 + `/xrpc/io.pocketenv.file.getFiles${sandboxId ? `?sandboxId=${sandboxId}` : ""}&offset=${offset ?? 0}&limit=${limit ?? 30}`, 31 + { 32 + headers: { 33 + Authorization: `Bearer ${localStorage.getItem("token")}`, 34 + }, 31 35 }, 32 - }); 36 + );
+13 -5
apps/web/src/api/secret.ts
··· 1 1 import { client } from "."; 2 + import type { Secret } from "../types/secret"; 2 3 3 4 export const addSecret = (sandboxId: string, name: string, value: string) => 4 5 client.post( ··· 24 25 }, 25 26 }); 26 27 27 - export const getSecrets = () => 28 - client.get("/xrpc/io.pocketenv.secret.getSecrets", { 29 - headers: { 30 - Authorization: `Bearer ${localStorage.getItem("token")}`, 28 + export const getSecrets = ( 29 + sandboxId?: string, 30 + offset?: number, 31 + limit?: number, 32 + ) => 33 + client.get<{ secrets: Secret[]; total: number }>( 34 + `/xrpc/io.pocketenv.secret.getSecrets?sandboxId=${sandboxId}&offset=${offset ?? 0}&limit=${limit ?? 30}`, 35 + { 36 + headers: { 37 + Authorization: `Bearer ${localStorage.getItem("token")}`, 38 + }, 31 39 }, 32 - }); 40 + );
+13 -5
apps/web/src/api/variable.ts
··· 1 1 import { client } from "."; 2 + import type { Variable } from "../types/variable"; 2 3 3 4 export const addVariable = (sandboxId: string, name: string, value: string) => 4 5 client.post( ··· 28 29 }, 29 30 ); 30 31 31 - export const getVariables = () => 32 - client.get("/xrpc/io.pocketenv.variable.getVariables", { 33 - headers: { 34 - Authorization: `Bearer ${localStorage.getItem("token")}`, 32 + export const getVariables = ( 33 + sandboxId?: string, 34 + offset?: number, 35 + limit?: number, 36 + ) => 37 + client.get<{ variables: Variable[]; total: number }>( 38 + `/xrpc/io.pocketenv.variable.getVariables${sandboxId ? `?sandboxId=${sandboxId}` : ""}&offset=${offset ?? 0}&limit=${limit ?? 30}`, 39 + { 40 + headers: { 41 + Authorization: `Bearer ${localStorage.getItem("token")}`, 42 + }, 35 43 }, 36 - }); 44 + );
+13 -5
apps/web/src/api/volume.ts
··· 1 1 import { client } from "."; 2 + import type { Volume } from "../types/volume"; 2 3 3 4 export const addVolume = (sandboxId: string, name: string, path: string) => 4 5 client.post( ··· 24 25 }, 25 26 }); 26 27 27 - export const getVolumes = () => 28 - client.get("/xrpc/io.pocketenv.volume.getVolumes", { 29 - headers: { 30 - Authorization: `Bearer ${localStorage.getItem("token")}`, 28 + export const getVolumes = ( 29 + sandboxId?: string, 30 + offset?: number, 31 + limit?: number, 32 + ) => 33 + client.get<{ volumes: Volume[]; total: number }>( 34 + `/xrpc/io.pocketenv.volume.getVolumes${sandboxId ? `?sandboxId=${sandboxId}` : ""}&offset=${offset ?? 0}&limit=${limit ?? 30}`, 35 + { 36 + headers: { 37 + Authorization: `Bearer ${localStorage.getItem("token")}`, 38 + }, 31 39 }, 32 - }); 40 + );
apps/web/src/components/contextmenu/AddEnvironmentVariableModal/AddEnvironmentVariableModal.tsx apps/web/src/components/contextmenu/AddVariableModal/AddVariableModal.tsx
-3
apps/web/src/components/contextmenu/AddEnvironmentVariableModal/index.tsx
··· 1 - import AddEnvironmentVariableModal from "./AddEnvironmentVariableModal"; 2 - 3 - export default AddEnvironmentVariableModal;
+3
apps/web/src/components/contextmenu/AddVariableModal/index.tsx
··· 1 + import AddVariableModal from "./AddVariableModal"; 2 + 3 + export default AddVariableModal;
+2 -2
apps/web/src/components/contextmenu/ContextMenu.tsx
··· 1 1 import { useEffect, useRef, useState } from "react"; 2 - import AddEnvironmentVariableModal from "./AddEnvironmentVariableModal"; 2 + import AddVariableModal from "./AddVariableModal"; 3 3 import AddFileModal from "./AddFileModal"; 4 4 import AddVolumeModal from "./AddVolumeModal"; 5 5 import DeleteSandboxModal from "./DeleteSandboxModal"; ··· 139 139 </li> 140 140 </ul> 141 141 </div> 142 - <AddEnvironmentVariableModal 142 + <AddVariableModal 143 143 isOpen={isAddEnvironmentVariableModalOpen} 144 144 onClose={() => setIsAddEnvironmentVariableModalOpen(false)} 145 145 sandboxId={sandboxId}
+7 -3
apps/web/src/hooks/useFile.ts
··· 31 31 }); 32 32 }; 33 33 34 - export const useFilesQuery = () => 34 + export const useFilesQuery = ( 35 + sandboxId?: string, 36 + offset?: number, 37 + limit?: number, 38 + ) => 35 39 useQuery({ 36 - queryKey: ["files"], 37 - queryFn: () => getFiles(), 40 + queryKey: ["files", sandboxId, offset, limit], 41 + queryFn: () => getFiles(sandboxId, offset, limit), 38 42 select: (response) => response.data, 39 43 });
+7 -3
apps/web/src/hooks/useSecret.ts
··· 31 31 }); 32 32 }; 33 33 34 - export const useSecretsQuery = () => 34 + export const useSecretsQuery = ( 35 + sandboxId?: string, 36 + offset?: number, 37 + limit?: number, 38 + ) => 35 39 useQuery({ 36 - queryKey: ["secrets"], 37 - queryFn: () => getSecrets(), 40 + queryKey: ["secrets", sandboxId, offset, limit], 41 + queryFn: () => getSecrets(sandboxId, offset, limit), 38 42 select: (response) => response.data, 39 43 });
+7 -3
apps/web/src/hooks/useVariable.ts
··· 31 31 }); 32 32 }; 33 33 34 - export const useVariablesQuery = () => 34 + export const useVariablesQuery = ( 35 + sandboxId?: string, 36 + offset?: number, 37 + limit?: number, 38 + ) => 35 39 useQuery({ 36 - queryKey: ["variables"], 37 - queryFn: async () => getVariables(), 40 + queryKey: ["variables", sandboxId, offset, limit], 41 + queryFn: async () => getVariables(sandboxId, offset, limit), 38 42 select: (response) => response.data, 39 43 });
+7 -3
apps/web/src/hooks/useVolume.ts
··· 29 29 }); 30 30 }; 31 31 32 - export const useVolumesQuery = () => 32 + export const useVolumesQuery = ( 33 + sandboxId?: string, 34 + offset?: number, 35 + limit?: number, 36 + ) => 33 37 useQuery({ 34 - queryKey: ["volumes"], 35 - queryFn: async () => getVolumes(), 38 + queryKey: ["volumes", sandboxId, offset, limit], 39 + queryFn: async () => getVolumes(sandboxId, offset, limit), 36 40 select: (response) => response.data, 37 41 });
+66 -11
apps/web/src/pages/settings/files/Files.tsx
··· 2 2 import { useSandboxQuery } from "../../../hooks/useSandbox"; 3 3 import Main from "../../../layouts/Main"; 4 4 import Sidebar from "../sidebar/Sidebar"; 5 + import AddFileModal from "../../../components/contextmenu/AddFileModal"; 6 + import { useState } from "react"; 7 + import { useFilesQuery } from "../../../hooks/useFile"; 8 + import dayjs from "dayjs"; 9 + import Pagination from "../../../components/pagination"; 10 + 11 + const PAGE_SIZE = 12; 5 12 6 13 function Files() { 14 + const [isOpen, setIsOpen] = useState(false); 15 + const [currentPage, setCurrentPage] = useState(1); 16 + const offset = (currentPage - 1) * PAGE_SIZE; 17 + 7 18 const routerState = useRouterState(); 8 19 const pathname = routerState.location.pathname; 9 20 const { data } = useSandboxQuery( 10 21 `at:/${pathname.replace("/files", "").replace("sandbox", "io.pocketenv.sandbox")}`, 11 22 ); 23 + const { data: files } = useFilesQuery(data?.sandbox?.id, offset, PAGE_SIZE); 24 + 25 + const totalPages = files?.total ? Math.ceil(files.total / PAGE_SIZE) : 1; 26 + 27 + const onPageChange = (page: number) => { 28 + setCurrentPage(page); 29 + }; 12 30 13 31 return ( 14 32 <Main ··· 17 35 rootLink={pathname.replace("/files", "")} 18 36 > 19 37 <> 20 - <div className="w-[95%] m-auto"> 38 + <div 39 + className="w-[95%] m-auto relative" 40 + style={{ height: "calc(100vh - 80px)" }} 41 + > 21 42 <div className="flex flex-row items-center"> 22 43 <h1 className="mb-2 text-xl flex-1">Files</h1> 23 - <button className="btn btn-primary font-semibold">New File</button> 44 + <button 45 + className="btn btn-primary font-semibold" 46 + onClick={() => setIsOpen(true)} 47 + > 48 + New File 49 + </button> 24 50 </div> 25 51 <p className="opacity-60 mb-5"> 26 52 Files (encrypted) that are automatically injected into the sandbox 27 53 filesystem. 28 54 </p> 29 - <table className="table mb-20"> 30 - <thead> 31 - <tr> 32 - <th className="normal-case text-[14px]">Path</th> 33 - <th className="normal-case text-[14px]"></th> 34 - </tr> 35 - </thead> 36 - <tbody></tbody> 37 - </table> 55 + <div className="w-full overflow-x-auto"> 56 + <table className="table mb-20"> 57 + <thead> 58 + <tr> 59 + <th className="normal-case text-[14px]">Path</th> 60 + <th className="normal-case text-[14px]">Created At</th> 61 + <th className="normal-case text-[14px]"></th> 62 + </tr> 63 + </thead> 64 + <tbody> 65 + {files?.files?.map((file) => ( 66 + <tr key={file.id}> 67 + <td className="normal-case text-[14px] font-medium"> 68 + {file.path} 69 + </td> 70 + <td className="normal-case text-[14px] font-medium"> 71 + {dayjs(file.createdAt).format("M/D/YYYY, h:mm:ss A")} 72 + </td> 73 + <td className="normal-case text-[14px]"></td> 74 + </tr> 75 + ))} 76 + </tbody> 77 + </table> 78 + </div> 79 + <div className="absolute bottom-3.75 w-full"> 80 + <div className="flex justify-center align-center"> 81 + <Pagination 82 + currentPage={currentPage} 83 + totalPages={totalPages} 84 + onPageChange={onPageChange} 85 + /> 86 + </div> 87 + </div> 38 88 </div> 89 + <AddFileModal 90 + isOpen={isOpen} 91 + onClose={() => setIsOpen(false)} 92 + sandboxId={data?.sandbox?.id ?? ""} 93 + /> 39 94 </> 40 95 </Main> 41 96 );
+5 -2
apps/web/src/pages/settings/integrations/Integrations.tsx
··· 2 2 import { useSandboxQuery } from "../../../hooks/useSandbox"; 3 3 import Main from "../../../layouts/Main"; 4 4 import Sidebar from "../sidebar/Sidebar"; 5 + import NewWebhook from "./NewWebhook"; 5 6 6 7 function Integrations() { 7 8 const routerState = useRouterState(); ··· 28 29 Integrations are third-party services that you can connect to your 29 30 sandbox by adding a webhook. 30 31 </p> 31 - <table className="table mb-20"> 32 + {/* 33 + <table className="table mb-20"> 32 34 <thead> 33 35 <tr> 34 36 <th className="normal-case text-[14px]">Name</th> ··· 36 38 </tr> 37 39 </thead> 38 40 <tbody></tbody> 39 - </table> 41 + </table>*/} 40 42 </div> 43 + <NewWebhook /> 41 44 </> 42 45 </Main> 43 46 );
+5
apps/web/src/pages/settings/integrations/NewWebhook/NewWebhook.tsx
··· 1 + function NewWebhook() { 2 + return <></>; 3 + } 4 + 5 + export default NewWebhook;
+3
apps/web/src/pages/settings/integrations/NewWebhook/index.tsx
··· 1 + import NewWebhook from "./NewWebhook"; 2 + 3 + export default NewWebhook;
+68 -11
apps/web/src/pages/settings/secrets/Secrets.tsx
··· 2 2 import { useSandboxQuery } from "../../../hooks/useSandbox"; 3 3 import Main from "../../../layouts/Main"; 4 4 import Sidebar from "../sidebar/Sidebar"; 5 + import AddSecretModal from "../../../components/contextmenu/AddSecretModal"; 6 + import { useState } from "react"; 7 + import { useSecretsQuery } from "../../../hooks/useSecret"; 8 + import dayjs from "dayjs"; 9 + import Pagination from "../../../components/pagination"; 10 + 11 + const PAGE_SIZE = 12; 5 12 6 13 function Secrets() { 14 + const [isOpen, setIsOpen] = useState(false); 15 + const [currentPage, setCurrentPage] = useState(1); 16 + const offset = (currentPage - 1) * PAGE_SIZE; 17 + 7 18 const routerState = useRouterState(); 8 19 const pathname = routerState.location.pathname; 9 20 const { data } = useSandboxQuery( 10 21 `at:/${pathname.replace("/secrets", "").replace("sandbox", "io.pocketenv.sandbox")}`, 11 22 ); 23 + const { data: secrets } = useSecretsQuery( 24 + data?.sandbox?.id, 25 + offset, 26 + PAGE_SIZE, 27 + ); 28 + 29 + const totalPages = secrets?.total ? Math.ceil(secrets.total / PAGE_SIZE) : 1; 30 + 31 + const onPageChange = (page: number) => { 32 + setCurrentPage(page); 33 + }; 12 34 13 35 return ( 14 36 <Main ··· 17 39 rootLink={pathname.replace("/secrets", "")} 18 40 > 19 41 <> 20 - <div className="w-[95%] m-auto"> 42 + <div 43 + className="w-[95%] m-auto relative" 44 + style={{ height: "calc(100vh - 80px)" }} 45 + > 21 46 <div className="flex flex-row items-center"> 22 47 <h1 className="mb-2 text-xl flex-1">Secrets</h1> 23 - <button className="btn btn-primary font-semibold"> 48 + <button 49 + className="btn btn-primary font-semibold" 50 + onClick={() => setIsOpen(true)} 51 + > 24 52 New Secret 25 53 </button> 26 54 </div> ··· 28 56 Sensitive environment variables (API keys, tokens, credentials) 29 57 stored securely. 30 58 </p> 31 - <table className="table mb-20"> 32 - <thead> 33 - <tr> 34 - <th className="normal-case text-[14px]">Name</th> 35 - <th className="normal-case text-[14px]"></th> 36 - </tr> 37 - </thead> 38 - <tbody></tbody> 39 - </table> 59 + <div className="w-full overflow-x-auto"> 60 + <table className="table mb-20"> 61 + <thead> 62 + <tr> 63 + <th className="normal-case text-[14px]">Name</th> 64 + <th className="normal-case text-[14px]">Created At</th> 65 + <th className="normal-case text-[14px]"></th> 66 + </tr> 67 + </thead> 68 + <tbody> 69 + {secrets?.secrets?.map((secret) => ( 70 + <tr key={secret.id}> 71 + <td className="normal-case text-[14px] font-medium"> 72 + {secret.name} 73 + </td> 74 + <td className="normal-case text-[14px] font-medium"> 75 + {dayjs(secret.createdAt).format("M/D/YYYY, h:mm:ss A")} 76 + </td> 77 + <td className="normal-case text-[14px]"></td> 78 + </tr> 79 + ))} 80 + </tbody> 81 + </table> 82 + </div> 83 + <div className="absolute bottom-3.75 w-full"> 84 + <div className="flex justify-center align-center"> 85 + <Pagination 86 + currentPage={currentPage} 87 + totalPages={totalPages} 88 + onPageChange={onPageChange} 89 + /> 90 + </div> 91 + </div> 40 92 </div> 93 + <AddSecretModal 94 + isOpen={isOpen} 95 + onClose={() => setIsOpen(false)} 96 + sandboxId={data?.sandbox?.id ?? ""} 97 + /> 41 98 </> 42 99 </Main> 43 100 );
+1 -1
apps/web/src/pages/settings/tailscale/Tailscale.tsx
··· 27 27 </p> 28 28 <div className="input input-bordered w-xl input-lg text-[15px] font-semibold bg-transparent"> 29 29 <input 30 - type="password" 30 + type="text" 31 31 className={`grow`} 32 32 placeholder="Enter your Tailscale Token" 33 33 autoComplete="off"
+74 -12
apps/web/src/pages/settings/variables/Variables.tsx
··· 2 2 import { useSandboxQuery } from "../../../hooks/useSandbox"; 3 3 import Main from "../../../layouts/Main"; 4 4 import Sidebar from "../sidebar/Sidebar"; 5 + import AddVariableModal from "../../../components/contextmenu/AddVariableModal"; 6 + import { useState } from "react"; 7 + import { useVariablesQuery } from "../../../hooks/useVariable"; 8 + import dayjs from "dayjs"; 9 + import Pagination from "../../../components/pagination"; 10 + 11 + const PAGE_SIZE = 10; 5 12 6 13 function Variables() { 14 + const [isOpen, setIsOpen] = useState(false); 15 + const [currentPage, setCurrentPage] = useState(1); 16 + const offset = (currentPage - 1) * PAGE_SIZE; 17 + 7 18 const routerState = useRouterState(); 8 19 const pathname = routerState.location.pathname; 9 20 const { data } = useSandboxQuery( 10 21 `at:/${pathname.replace("/variables", "").replace("sandbox", "io.pocketenv.sandbox")}`, 11 22 ); 23 + const { data: variables } = useVariablesQuery( 24 + data?.sandbox?.id, 25 + offset, 26 + PAGE_SIZE, 27 + ); 28 + 29 + const totalPages = variables?.total 30 + ? Math.ceil(variables.total / PAGE_SIZE) 31 + : 1; 32 + 33 + const onPageChange = (page: number) => { 34 + setCurrentPage(page); 35 + }; 12 36 13 37 return ( 14 38 <Main ··· 17 41 rootLink={pathname.replace("/variables", "")} 18 42 > 19 43 <> 20 - <div className="w-[95%] m-auto"> 44 + <div 45 + className="w-[95%] m-auto relative" 46 + style={{ height: "calc(100vh - 80px)" }} 47 + > 21 48 <div className="flex flex-row items-center"> 22 49 <h1 className="mb-2 text-xl flex-1">Variables</h1> 23 - <button className="btn btn-primary font-semibold"> 50 + <button 51 + className="btn btn-primary font-semibold" 52 + onClick={() => setIsOpen(true)} 53 + > 24 54 New Variable 25 55 </button> 26 56 </div> 27 57 <p className="opacity-60 mb-5"> 28 58 Environment variables available to your sandbox during execution. 29 59 </p> 30 - <table className="table mb-20"> 31 - <thead> 32 - <tr> 33 - <th className="normal-case text-[14px]">Name</th> 34 - <th className="normal-case text-[14px]">Value</th> 35 - <th className="normal-case text-[14px]"></th> 36 - </tr> 37 - </thead> 38 - <tbody></tbody> 39 - </table> 60 + <div className="w-full overflow-x-auto"> 61 + <table className="table mb-20"> 62 + <thead> 63 + <tr> 64 + <th className="normal-case text-[14px]">Name</th> 65 + <th className="normal-case text-[14px]">Value</th> 66 + <th className="normal-case text-[14px]">Created At</th> 67 + <th className="normal-case text-[14px]"></th> 68 + </tr> 69 + </thead> 70 + <tbody> 71 + {variables?.variables?.map((variable) => ( 72 + <tr key={variable.id}> 73 + <td className="normal-case text-[14px] font-medium"> 74 + {variable.name} 75 + </td> 76 + <td className="normal-case text-[14px] font-medium"> 77 + {variable.value} 78 + </td> 79 + <td className="normal-case text-[14px] font-medium"> 80 + {dayjs(variable.createdAt).format("M/D/YYYY, h:mm:ss A")} 81 + </td> 82 + <td className="normal-case text-[14px] font-medium"></td> 83 + </tr> 84 + ))} 85 + </tbody> 86 + </table> 87 + </div> 88 + <div className="absolute bottom-3.75 w-full"> 89 + <div className="flex justify-center align-center"> 90 + <Pagination 91 + currentPage={currentPage} 92 + totalPages={totalPages} 93 + onPageChange={onPageChange} 94 + /> 95 + </div> 96 + </div> 40 97 </div> 98 + <AddVariableModal 99 + isOpen={isOpen} 100 + onClose={() => setIsOpen(false)} 101 + sandboxId={data?.sandbox?.id ?? ""} 102 + /> 41 103 </> 42 104 </Main> 43 105 );
+68 -12
apps/web/src/pages/settings/volumes/Volumes.tsx
··· 2 2 import { useSandboxQuery } from "../../../hooks/useSandbox"; 3 3 import Main from "../../../layouts/Main"; 4 4 import Sidebar from "../sidebar/Sidebar"; 5 + import AddVolumeModal from "../../../components/contextmenu/AddVolumeModal"; 6 + import { useState } from "react"; 7 + import { useVolumesQuery } from "../../../hooks/useVolume"; 8 + import dayjs from "dayjs"; 9 + import Pagination from "../../../components/pagination"; 10 + 11 + const PAGE_SIZE = 12; 5 12 6 13 function Volumes() { 14 + const [isOpen, setIsOpen] = useState(false); 15 + const [currentPage, setCurrentPage] = useState(1); 16 + const offset = (currentPage - 1) * PAGE_SIZE; 17 + 7 18 const routerState = useRouterState(); 8 19 const pathname = routerState.location.pathname; 9 20 const { data } = useSandboxQuery( 10 21 `at:/${pathname.replace("/volumes", "").replace("sandbox", "io.pocketenv.sandbox")}`, 11 22 ); 23 + const { data: volumes } = useVolumesQuery( 24 + data?.sandbox?.id, 25 + offset, 26 + PAGE_SIZE, 27 + ); 28 + 29 + const totalPages = volumes?.total ? Math.ceil(volumes.total / PAGE_SIZE) : 1; 30 + 31 + const onPageChange = (page: number) => { 32 + setCurrentPage(page); 33 + }; 12 34 13 35 return ( 14 36 <Main ··· 17 39 rootLink={pathname.replace("/files", "")} 18 40 > 19 41 <> 20 - <div className="w-[95%] m-auto"> 42 + <div 43 + className="w-[95%] m-auto relative" 44 + style={{ height: "calc(100vh - 80px)" }} 45 + > 21 46 <div className="flex flex-row items-center"> 22 47 <h1 className="mb-2 text-xl flex-1">Volumes</h1> 23 - <button className="btn btn-primary font-semibold"> 48 + <button 49 + className="btn btn-primary font-semibold" 50 + onClick={() => setIsOpen(true)} 51 + > 24 52 New Volume 25 53 </button> 26 54 </div> ··· 28 56 Persistent storage mounted into the sandbox to keep data between 29 57 sessions. 30 58 </p> 31 - <table className="table mb-20"> 32 - <thead> 33 - <tr> 34 - <th className="normal-case text-[14px]">Name</th> 35 - <th className="normal-case text-[14px]">Mount Path</th> 36 - <th className="normal-case text-[14px]"></th> 37 - </tr> 38 - </thead> 39 - <tbody></tbody> 40 - </table> 59 + <div className="w-full overflow-x-auto"> 60 + <table className="table mb-20"> 61 + <thead> 62 + <tr> 63 + <th className="normal-case text-[14px]">Name</th> 64 + <th className="normal-case text-[14px]">Mount Path</th> 65 + <th className="normal-case text-[14px]">Created At</th> 66 + <th className="normal-case text-[14px]"></th> 67 + </tr> 68 + </thead> 69 + <tbody> 70 + {volumes?.volumes?.map((volume) => ( 71 + <tr key={volume.id}> 72 + <td>{volume.name}</td> 73 + <td>{volume.path}</td> 74 + <td> 75 + {dayjs(volume.createdAt).format("M/D/YYYY, h:mm:ss A")} 76 + </td> 77 + <td></td> 78 + </tr> 79 + ))} 80 + </tbody> 81 + </table> 82 + </div> 83 + <div className="absolute bottom-3.75 w-full"> 84 + <div className="flex justify-center align-center"> 85 + <Pagination 86 + currentPage={currentPage} 87 + totalPages={totalPages} 88 + onPageChange={onPageChange} 89 + /> 90 + </div> 91 + </div> 41 92 </div> 93 + <AddVolumeModal 94 + isOpen={isOpen} 95 + onClose={() => setIsOpen(false)} 96 + sandboxId={data?.sandbox?.id ?? ""} 97 + /> 42 98 </> 43 99 </Main> 44 100 );
+5
apps/web/src/types/file.ts
··· 1 + export type File = { 2 + id: string; 3 + path: string; 4 + createdAt: string; 5 + };
+5
apps/web/src/types/secret.ts
··· 1 + export type Secret = { 2 + id: string; 3 + name: string; 4 + createdAt: string; 5 + };
+6
apps/web/src/types/variable.ts
··· 1 + export type Variable = { 2 + id: string; 3 + name: string; 4 + value: string; 5 + createdAt: string; 6 + };
+6
apps/web/src/types/volume.ts
··· 1 + export type Volume = { 2 + id: string; 3 + name: string; 4 + path: string; 5 + createdAt: string; 6 + };