mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat(den-web): persist user workers and remove manual worker-id recovery
This commit is contained in:
@@ -404,6 +404,57 @@ body {
|
||||
gap: 0.58rem;
|
||||
}
|
||||
|
||||
.ow-worker-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 0.46rem;
|
||||
}
|
||||
|
||||
.ow-worker-item {
|
||||
border: 1px solid #dbe3f4;
|
||||
border-radius: 0.76rem;
|
||||
background: #fff;
|
||||
padding: 0.56rem;
|
||||
display: grid;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.ow-worker-item.is-active {
|
||||
border-color: #9fb2eb;
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
.ow-worker-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.ow-worker-meta {
|
||||
margin: 0;
|
||||
font-size: 0.72rem;
|
||||
color: #475569;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.ow-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #b8c8ec;
|
||||
background: #edf3ff;
|
||||
color: #1e3a8a;
|
||||
font-size: 0.64rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
padding: 0.2rem 0.42rem;
|
||||
}
|
||||
|
||||
.ow-inline-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -25,6 +25,9 @@ type WorkerSummary = {
|
||||
workerId: string;
|
||||
workerName: string;
|
||||
status: string;
|
||||
instanceUrl: string | null;
|
||||
provider: string | null;
|
||||
isMine: boolean;
|
||||
};
|
||||
|
||||
type WorkerTokens = {
|
||||
@@ -32,6 +35,16 @@ type WorkerTokens = {
|
||||
hostToken: string | null;
|
||||
};
|
||||
|
||||
type WorkerListItem = {
|
||||
workerId: string;
|
||||
workerName: string;
|
||||
status: string;
|
||||
instanceUrl: string | null;
|
||||
provider: string | null;
|
||||
isMine: boolean;
|
||||
createdAt: string | null;
|
||||
};
|
||||
|
||||
type EventLevel = "info" | "success" | "warning" | "error";
|
||||
|
||||
type LaunchEvent = {
|
||||
@@ -150,10 +163,15 @@ function getWorkerSummary(payload: unknown): WorkerSummary | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = isRecord(payload.instance) ? payload.instance : null;
|
||||
|
||||
return {
|
||||
workerId: worker.id,
|
||||
workerName: worker.name,
|
||||
status: typeof worker.status === "string" ? worker.status : "unknown"
|
||||
status: typeof worker.status === "string" ? worker.status : "unknown",
|
||||
instanceUrl: instance && typeof instance.url === "string" ? instance.url : null,
|
||||
provider: instance && typeof instance.provider === "string" ? instance.provider : null,
|
||||
isMine: worker.isMine === true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -173,6 +191,47 @@ function getWorkerTokens(payload: unknown): WorkerTokens | null {
|
||||
return { clientToken, hostToken };
|
||||
}
|
||||
|
||||
function parseWorkerListItem(value: unknown): WorkerListItem | null {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const workerId = value.id;
|
||||
const workerName = value.name;
|
||||
if (typeof workerId !== "string" || typeof workerName !== "string") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const instance = isRecord(value.instance) ? value.instance : null;
|
||||
const createdAt = typeof value.createdAt === "string" ? value.createdAt : null;
|
||||
|
||||
return {
|
||||
workerId,
|
||||
workerName,
|
||||
status: typeof value.status === "string" ? value.status : "unknown",
|
||||
instanceUrl: instance && typeof instance.url === "string" ? instance.url : null,
|
||||
provider: instance && typeof instance.provider === "string" ? instance.provider : null,
|
||||
isMine: value.isMine === true,
|
||||
createdAt
|
||||
};
|
||||
}
|
||||
|
||||
function getWorkersList(payload: unknown): WorkerListItem[] {
|
||||
if (!isRecord(payload) || !Array.isArray(payload.workers)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rows: WorkerListItem[] = [];
|
||||
for (const item of payload.workers) {
|
||||
const parsed = parseWorkerListItem(item);
|
||||
if (parsed) {
|
||||
rows.push(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function isWorkerLaunch(value: unknown): value is WorkerLaunch {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
@@ -189,6 +248,18 @@ function isWorkerLaunch(value: unknown): value is WorkerLaunch {
|
||||
);
|
||||
}
|
||||
|
||||
function listItemToWorker(item: WorkerListItem, current: WorkerLaunch | null = null): WorkerLaunch {
|
||||
return {
|
||||
workerId: item.workerId,
|
||||
workerName: item.workerName,
|
||||
status: item.status,
|
||||
provider: item.provider,
|
||||
instanceUrl: item.instanceUrl,
|
||||
clientToken: current?.workerId === item.workerId ? current.clientToken : null,
|
||||
hostToken: current?.workerId === item.workerId ? current.hostToken : null
|
||||
};
|
||||
}
|
||||
|
||||
async function requestJson(path: string, init: RequestInit = {}, timeoutMs = 30000) {
|
||||
const headers = new Headers(init.headers);
|
||||
headers.set("Accept", "application/json");
|
||||
@@ -277,6 +348,9 @@ export function CloudControlPanel() {
|
||||
const [workerName, setWorkerName] = useState("Founder Ops Pilot");
|
||||
const [worker, setWorker] = useState<WorkerLaunch | null>(null);
|
||||
const [workerLookupId, setWorkerLookupId] = useState("");
|
||||
const [workers, setWorkers] = useState<WorkerListItem[]>([]);
|
||||
const [workersBusy, setWorkersBusy] = useState(false);
|
||||
const [workersError, setWorkersError] = useState<string | null>(null);
|
||||
const [launchBusy, setLaunchBusy] = useState(false);
|
||||
const [actionBusy, setActionBusy] = useState<"status" | "token" | null>(null);
|
||||
const [launchStatus, setLaunchStatus] = useState("Name your worker and click launch.");
|
||||
@@ -287,6 +361,8 @@ export function CloudControlPanel() {
|
||||
const [events, setEvents] = useState<LaunchEvent[]>([]);
|
||||
const [copiedField, setCopiedField] = useState<string | null>(null);
|
||||
|
||||
const selectedWorker = workers.find((item) => item.workerId === workerLookupId) ?? null;
|
||||
|
||||
const progressWidth = step === 1 ? "33.333%" : step === 2 ? "66.666%" : "100%";
|
||||
|
||||
function appendEvent(level: EventLevel, label: string, detail: string) {
|
||||
@@ -306,6 +382,53 @@ export function CloudControlPanel() {
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshWorkers(options: { keepSelection?: boolean } = {}) {
|
||||
if (!user) {
|
||||
setWorkers([]);
|
||||
setWorkersError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkersBusy(true);
|
||||
setWorkersError(null);
|
||||
|
||||
try {
|
||||
const { response, payload } = await requestJson("/v1/workers?limit=20", {
|
||||
method: "GET",
|
||||
headers: authToken ? { Authorization: `Bearer ${authToken}` } : undefined
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = getErrorMessage(payload, `Failed to load workers (${response.status}).`);
|
||||
setWorkersError(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const nextWorkers = getWorkersList(payload);
|
||||
setWorkers(nextWorkers);
|
||||
|
||||
const currentSelection = options.keepSelection ? workerLookupId : "";
|
||||
const nextSelectedId =
|
||||
currentSelection && nextWorkers.some((item) => item.workerId === currentSelection)
|
||||
? currentSelection
|
||||
: nextWorkers[0]?.workerId ?? "";
|
||||
|
||||
setWorkerLookupId(nextSelectedId);
|
||||
|
||||
if (nextSelectedId && worker && worker.workerId === nextSelectedId) {
|
||||
const selected = nextWorkers.find((item) => item.workerId === nextSelectedId) ?? null;
|
||||
if (selected) {
|
||||
setWorker((current) => listItemToWorker(selected, current));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown network error";
|
||||
setWorkersError(message);
|
||||
} finally {
|
||||
setWorkersBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function copyToClipboard(field: string, value: string | null) {
|
||||
if (!value) {
|
||||
return;
|
||||
@@ -351,6 +474,16 @@ export function CloudControlPanel() {
|
||||
void refreshSession(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
setWorkers([]);
|
||||
setWorkersError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
void refreshWorkers();
|
||||
}, [user?.id, authToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
@@ -550,7 +683,7 @@ export function CloudControlPanel() {
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof DOMException && error.name === "AbortError"
|
||||
? "Launch request timed out after 45s. Retry launch or come back later with your worker ID."
|
||||
? "Launch request timed out after 45s. Refresh the worker list below to continue without manual IDs."
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: "Unknown network error";
|
||||
@@ -560,6 +693,7 @@ export function CloudControlPanel() {
|
||||
appendEvent("error", "Launch failed", message);
|
||||
} finally {
|
||||
setLaunchBusy(false);
|
||||
void refreshWorkers({ keepSelection: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,12 +703,14 @@ export function CloudControlPanel() {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = workerLookupId.trim();
|
||||
const id = workerLookupId.trim() || worker?.workerId || workers[0]?.workerId || "";
|
||||
if (!id) {
|
||||
setLaunchError("Enter a worker ID first.");
|
||||
setLaunchError("No worker selected yet. Launch one first, then use this panel.");
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkerLookupId(id);
|
||||
|
||||
setActionBusy("status");
|
||||
setLaunchError(null);
|
||||
|
||||
@@ -603,7 +739,9 @@ export function CloudControlPanel() {
|
||||
return {
|
||||
...previous,
|
||||
workerName: summary.workerName,
|
||||
status: summary.status
|
||||
status: summary.status,
|
||||
provider: summary.provider,
|
||||
instanceUrl: summary.instanceUrl
|
||||
};
|
||||
}
|
||||
|
||||
@@ -611,8 +749,8 @@ export function CloudControlPanel() {
|
||||
workerId: summary.workerId,
|
||||
workerName: summary.workerName,
|
||||
status: summary.status,
|
||||
provider: null,
|
||||
instanceUrl: null,
|
||||
provider: summary.provider,
|
||||
instanceUrl: summary.instanceUrl,
|
||||
clientToken: null,
|
||||
hostToken: null
|
||||
};
|
||||
@@ -621,6 +759,7 @@ export function CloudControlPanel() {
|
||||
setWorkerLookupId(summary.workerId);
|
||||
setLaunchStatus(`Worker ${summary.workerName} is currently ${summary.status}.`);
|
||||
appendEvent("info", "Status refreshed", `${summary.workerName}: ${summary.status}`);
|
||||
void refreshWorkers({ keepSelection: true });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown network error";
|
||||
setLaunchError(message);
|
||||
@@ -636,12 +775,14 @@ export function CloudControlPanel() {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = workerLookupId.trim();
|
||||
const id = workerLookupId.trim() || worker?.workerId || workers[0]?.workerId || "";
|
||||
if (!id) {
|
||||
setLaunchError("Enter a worker ID before generating an API key.");
|
||||
setLaunchError("No worker selected yet. Launch one first, then generate a key.");
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkerLookupId(id);
|
||||
|
||||
setActionBusy("token");
|
||||
setLaunchError(null);
|
||||
|
||||
@@ -688,6 +829,7 @@ export function CloudControlPanel() {
|
||||
|
||||
setLaunchStatus("Generated a fresh worker API key.");
|
||||
appendEvent("success", "Generated new worker API key", `Worker ID ${id}`);
|
||||
void refreshWorkers({ keepSelection: true });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unknown network error";
|
||||
setLaunchError(message);
|
||||
@@ -848,19 +990,60 @@ export function CloudControlPanel() {
|
||||
) : null}
|
||||
|
||||
<div className="ow-lookup-box">
|
||||
<p className="ow-section-title">Come back later</p>
|
||||
<p className="ow-section-title">Your workers</p>
|
||||
<p className="ow-caption">No Worker ID guessing. Pick from your recent workers and continue.</p>
|
||||
|
||||
{workersBusy ? <p className="ow-caption">Loading workers...</p> : null}
|
||||
{workersError ? <p className="ow-error-text">{workersError}</p> : null}
|
||||
|
||||
{workers.length > 0 ? (
|
||||
<ul className="ow-worker-list">
|
||||
{workers.map((item) => (
|
||||
<li
|
||||
key={item.workerId}
|
||||
className={`ow-worker-item ${workerLookupId === item.workerId ? "is-active" : ""}`}
|
||||
>
|
||||
<div className="ow-worker-head">
|
||||
<div>
|
||||
<p className="ow-step-title">{item.workerName}</p>
|
||||
<p className="ow-step-detail">{item.status}</p>
|
||||
</div>
|
||||
{item.isMine ? <span className="ow-badge">Yours</span> : null}
|
||||
</div>
|
||||
<p className="ow-worker-meta ow-mono">{item.instanceUrl ?? "URL pending provisioning"}</p>
|
||||
<button
|
||||
type="button"
|
||||
className="ow-btn-secondary"
|
||||
onClick={() => {
|
||||
setWorkerLookupId(item.workerId);
|
||||
setWorker((current) => listItemToWorker(item, current));
|
||||
}}
|
||||
>
|
||||
{workerLookupId === item.workerId ? "Selected" : "Select"}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
|
||||
{workers.length === 0 && !workersBusy ? (
|
||||
<p className="ow-caption">No workers yet. Launch one and it will appear here automatically.</p>
|
||||
) : null}
|
||||
|
||||
<div className="ow-inline-actions">
|
||||
<input
|
||||
className="ow-input ow-mono"
|
||||
value={workerLookupId}
|
||||
onChange={(event) => setWorkerLookupId(event.target.value)}
|
||||
placeholder="Worker ID"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="ow-btn-secondary"
|
||||
onClick={() => void refreshWorkers({ keepSelection: true })}
|
||||
disabled={workersBusy}
|
||||
>
|
||||
Refresh list
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ow-btn-secondary"
|
||||
onClick={handleCheckStatus}
|
||||
disabled={actionBusy !== null}
|
||||
disabled={actionBusy !== null || !selectedWorker}
|
||||
>
|
||||
{actionBusy === "status" ? "Checking..." : "Check status"}
|
||||
</button>
|
||||
@@ -868,7 +1051,7 @@ export function CloudControlPanel() {
|
||||
type="button"
|
||||
className="ow-btn-secondary"
|
||||
onClick={handleGenerateKey}
|
||||
disabled={actionBusy !== null}
|
||||
disabled={actionBusy !== null || !selectedWorker}
|
||||
>
|
||||
{actionBusy === "token" ? "Generating..." : "New API key"}
|
||||
</button>
|
||||
|
||||
@@ -60,9 +60,11 @@ pnpm db:migrate
|
||||
- `GET /health`
|
||||
- `GET /` demo web app (sign-up + auth + worker launch)
|
||||
- `GET /v1/me`
|
||||
- `GET /v1/workers` (list recent workers for signed-in user/org)
|
||||
- `POST /v1/workers`
|
||||
- Returns `402 payment_required` with Polar checkout URL when paywall is enabled and entitlement is missing.
|
||||
- `GET /v1/workers/:id`
|
||||
- Includes latest instance metadata when available.
|
||||
- `POST /v1/workers/:id/tokens`
|
||||
|
||||
## CI deployment (dev == prod)
|
||||
|
||||
3
services/den/drizzle/0002_worker_created_by.sql
Normal file
3
services/den/drizzle/0002_worker_created_by.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `worker` ADD `created_by_user_id` varchar(64);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `worker_created_by_user_id` ON `worker` (`created_by_user_id`);
|
||||
@@ -15,6 +15,13 @@
|
||||
"when": 1771639607782,
|
||||
"tag": "0001_auth_columns_fix",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "5",
|
||||
"when": 1771741800000,
|
||||
"tag": "0002_worker_created_by",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ export const WorkerTable = mysqlTable(
|
||||
{
|
||||
id: id().primaryKey(),
|
||||
org_id: varchar("org_id", { length: 64 }).notNull(),
|
||||
created_by_user_id: varchar("created_by_user_id", { length: 64 }),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
description: varchar("description", { length: 1024 }),
|
||||
destination: mysqlEnum("destination", WorkerDestination).notNull(),
|
||||
@@ -141,7 +142,11 @@ export const WorkerTable = mysqlTable(
|
||||
sandbox_backend: varchar("sandbox_backend", { length: 64 }),
|
||||
...timestamps,
|
||||
},
|
||||
(table) => [index("worker_org_id").on(table.org_id), index("worker_status").on(table.status)],
|
||||
(table) => [
|
||||
index("worker_org_id").on(table.org_id),
|
||||
index("worker_created_by_user_id").on(table.created_by_user_id),
|
||||
index("worker_status").on(table.status),
|
||||
],
|
||||
)
|
||||
|
||||
export const WorkerInstanceTable = mysqlTable(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { randomBytes, randomUUID } from "crypto"
|
||||
import express from "express"
|
||||
import { fromNodeHeaders } from "better-auth/node"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { and, desc, eq } from "drizzle-orm"
|
||||
import { z } from "zod"
|
||||
import { auth } from "../auth.js"
|
||||
import { requireCloudWorkerAccess } from "../billing/polar.js"
|
||||
@@ -20,8 +20,15 @@ const createSchema = z.object({
|
||||
imageVersion: z.string().optional(),
|
||||
})
|
||||
|
||||
const listSchema = z.object({
|
||||
limit: z.coerce.number().int().min(1).max(50).default(20),
|
||||
})
|
||||
|
||||
const token = () => randomBytes(32).toString("hex")
|
||||
|
||||
type WorkerRow = typeof WorkerTable.$inferSelect
|
||||
type WorkerInstanceRow = typeof WorkerInstanceTable.$inferSelect
|
||||
|
||||
async function requireSession(req: express.Request, res: express.Response) {
|
||||
const session = await auth.api.getSession({
|
||||
headers: fromNodeHeaders(req.headers),
|
||||
@@ -45,8 +52,88 @@ async function getOrgId(userId: string) {
|
||||
return membership[0].org_id
|
||||
}
|
||||
|
||||
async function getLatestWorkerInstance(workerId: string) {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(WorkerInstanceTable)
|
||||
.where(eq(WorkerInstanceTable.worker_id, workerId))
|
||||
.orderBy(desc(WorkerInstanceTable.created_at))
|
||||
.limit(1)
|
||||
|
||||
return rows[0] ?? null
|
||||
}
|
||||
|
||||
function toInstanceResponse(instance: WorkerInstanceRow | null) {
|
||||
if (!instance) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
provider: instance.provider,
|
||||
region: instance.region,
|
||||
url: instance.url,
|
||||
status: instance.status,
|
||||
createdAt: instance.created_at,
|
||||
updatedAt: instance.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
function toWorkerResponse(row: WorkerRow, userId: string) {
|
||||
return {
|
||||
id: row.id,
|
||||
orgId: row.org_id,
|
||||
createdByUserId: row.created_by_user_id,
|
||||
isMine: row.created_by_user_id === userId,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
destination: row.destination,
|
||||
status: row.status,
|
||||
imageVersion: row.image_version,
|
||||
workspacePath: row.workspace_path,
|
||||
sandboxBackend: row.sandbox_backend,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
export const workersRouter = express.Router()
|
||||
|
||||
workersRouter.get("/", async (req, res) => {
|
||||
const session = await requireSession(req, res)
|
||||
if (!session) return
|
||||
|
||||
const orgId = await getOrgId(session.user.id)
|
||||
if (!orgId) {
|
||||
res.json({ workers: [] })
|
||||
return
|
||||
}
|
||||
|
||||
const parsed = listSchema.safeParse({ limit: req.query.limit })
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ error: "invalid_request", details: parsed.error.flatten() })
|
||||
return
|
||||
}
|
||||
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(WorkerTable)
|
||||
.where(eq(WorkerTable.org_id, orgId))
|
||||
.orderBy(desc(WorkerTable.created_at))
|
||||
.limit(parsed.data.limit)
|
||||
|
||||
const workers = await Promise.all(
|
||||
rows.map(async (row) => {
|
||||
const instance = await getLatestWorkerInstance(row.id)
|
||||
return {
|
||||
...toWorkerResponse(row, session.user.id),
|
||||
instance: toInstanceResponse(instance),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
res.json({ workers })
|
||||
})
|
||||
|
||||
workersRouter.post("/", async (req, res) => {
|
||||
const session = await requireSession(req, res)
|
||||
if (!session) return
|
||||
@@ -86,12 +173,12 @@ workersRouter.post("/", async (req, res) => {
|
||||
const orgId =
|
||||
(await getOrgId(session.user.id)) ?? (await ensureDefaultOrg(session.user.id, session.user.name ?? session.user.email ?? "Personal"))
|
||||
const workerId = randomUUID()
|
||||
let workerStatus: "provisioning" | "healthy" | "failed" | "stopped" =
|
||||
parsed.data.destination === "cloud" ? "provisioning" : "healthy"
|
||||
let workerStatus: WorkerRow["status"] = parsed.data.destination === "cloud" ? "provisioning" : "healthy"
|
||||
|
||||
await db.insert(WorkerTable).values({
|
||||
id: workerId,
|
||||
org_id: orgId,
|
||||
created_by_user_id: session.user.id,
|
||||
name: parsed.data.name,
|
||||
description: parsed.data.description,
|
||||
destination: parsed.data.destination,
|
||||
@@ -156,17 +243,23 @@ workersRouter.post("/", async (req, res) => {
|
||||
}
|
||||
|
||||
res.status(201).json({
|
||||
worker: {
|
||||
id: workerId,
|
||||
orgId,
|
||||
name: parsed.data.name,
|
||||
description: parsed.data.description ?? null,
|
||||
destination: parsed.data.destination,
|
||||
status: workerStatus,
|
||||
imageVersion: parsed.data.imageVersion ?? null,
|
||||
workspacePath: parsed.data.workspacePath ?? null,
|
||||
sandboxBackend: parsed.data.sandboxBackend ?? null,
|
||||
},
|
||||
worker: toWorkerResponse(
|
||||
{
|
||||
id: workerId,
|
||||
org_id: orgId,
|
||||
created_by_user_id: session.user.id,
|
||||
name: parsed.data.name,
|
||||
description: parsed.data.description ?? null,
|
||||
destination: parsed.data.destination,
|
||||
status: workerStatus,
|
||||
image_version: parsed.data.imageVersion ?? null,
|
||||
workspace_path: parsed.data.workspacePath ?? null,
|
||||
sandbox_backend: parsed.data.sandboxBackend ?? null,
|
||||
created_at: new Date(),
|
||||
updated_at: new Date(),
|
||||
},
|
||||
session.user.id,
|
||||
),
|
||||
tokens: {
|
||||
host: hostToken,
|
||||
client: clientToken,
|
||||
@@ -188,28 +281,19 @@ workersRouter.get("/:id", async (req, res) => {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(WorkerTable)
|
||||
.where(eq(WorkerTable.id, req.params.id))
|
||||
.where(and(eq(WorkerTable.id, req.params.id), eq(WorkerTable.org_id, orgId)))
|
||||
.limit(1)
|
||||
|
||||
if (rows.length === 0 || rows[0].org_id !== orgId) {
|
||||
if (rows.length === 0) {
|
||||
res.status(404).json({ error: "worker_not_found" })
|
||||
return
|
||||
}
|
||||
|
||||
const instance = await getLatestWorkerInstance(rows[0].id)
|
||||
|
||||
res.json({
|
||||
worker: {
|
||||
id: rows[0].id,
|
||||
orgId: rows[0].org_id,
|
||||
name: rows[0].name,
|
||||
description: rows[0].description,
|
||||
destination: rows[0].destination,
|
||||
status: rows[0].status,
|
||||
imageVersion: rows[0].image_version,
|
||||
workspacePath: rows[0].workspace_path,
|
||||
sandboxBackend: rows[0].sandbox_backend,
|
||||
createdAt: rows[0].created_at,
|
||||
updatedAt: rows[0].updated_at,
|
||||
},
|
||||
worker: toWorkerResponse(rows[0], session.user.id),
|
||||
instance: toInstanceResponse(instance),
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user