Dax Raad 5 месяцев назад
Родитель
Сommit
48a79b1173

+ 3 - 7
cloud/app/src/context/auth.ts

@@ -14,17 +14,14 @@ export const AuthClient = createClient({
   issuer: import.meta.env.VITE_AUTH_URL,
 })
 
-export const getActor = async (): Promise<Actor.Info> => {
+export const getActor = async (workspace?: string): Promise<Actor.Info> => {
   "use server"
   const evt = getRequestEvent()
   if (!evt) throw new Error("No request event")
   if (evt.locals.actor) return evt.locals.actor
   evt.locals.actor = (async () => {
-    console.log("getActor")
-    const url = new URL(evt.request.headers.has("x-server-id") ? evt.request.headers.get("referer")! : evt.request.url)
     const auth = await useAuthSession()
-    const splits = url.pathname.split("/").filter(Boolean)
-    if (splits[0] !== "workspace") {
+    if (!workspace) {
       const account = auth.data.account ?? {}
       const current = account[auth.data.current ?? ""]
       if (current) {
@@ -55,7 +52,6 @@ export const getActor = async (): Promise<Actor.Info> => {
         properties: {},
       }
     }
-    const workspaceHint = splits[1]
     const accounts = Object.keys(auth.data.account ?? {})
     if (accounts.length) {
       const result = await Database.transaction(async (tx) => {
@@ -66,7 +62,7 @@ export const getActor = async (): Promise<Actor.Info> => {
           .from(AccountTable)
           .innerJoin(UserTable, and(eq(UserTable.email, AccountTable.email)))
           .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, UserTable.workspaceID))
-          .where(and(inArray(AccountTable.id, accounts), eq(WorkspaceTable.id, workspaceHint)))
+          .where(and(inArray(AccountTable.id, accounts), eq(WorkspaceTable.id, workspace)))
           .limit(1)
           .execute()
           .then((x) => x[0])

+ 2 - 3
cloud/app/src/context/auth.withActor.ts

@@ -1,8 +1,7 @@
 import { Actor } from "@opencode/cloud-core/actor.js"
 import { getActor } from "./auth"
-import { getRequestEvent } from "solid-js/web"
 
-export async function withActor<T>(fn: () => T) {
-  const actor = await getActor()
+export async function withActor<T>(fn: () => T, workspace?: string) {
+  const actor = await getActor(workspace)
   return Actor.provide(actor.type, actor.properties, fn)
 }

+ 7 - 10
cloud/app/src/routes/index.tsx

@@ -7,7 +7,7 @@ import IMG_SPLASH from "../asset/lander/screenshot-splash.png"
 import IMG_VSCODE from "../asset/lander/screenshot-vscode.png"
 import IMG_GITHUB from "../asset/lander/screenshot-github.png"
 import { IconCopy, IconCheck } from "../component/icon"
-import { createAsync, query, redirect } from "@solidjs/router"
+import { createAsync, query, redirect, A } from "@solidjs/router"
 import { getActor } from "~/context/auth"
 import { withActor } from "~/context/auth.withActor"
 import { Account } from "@opencode/cloud-core/account.js"
@@ -21,20 +21,17 @@ function CopyStatus() {
   )
 }
 
-const isLoggedIn = query(async () => {
+const defaultWorkspace = query(async () => {
   "use server"
   const actor = await getActor()
   if (actor.type === "account") {
     const workspaces = await withActor(() => Account.workspaces())
     return workspaces[0].id
-    // throw redirect(`/workspace/${workspaces[0].id}`)
   }
-}, "isLoggedIn")
+}, "defaultWorkspace")
 
 export default function Home() {
-  const auth = createAsync(() => isLoggedIn(), {
-    deferStream: true,
-  })
+  const workspace = createAsync(() => defaultWorkspace())
   onMount(() => {
     const commands = document.querySelectorAll("[data-copy]")
     for (const button of commands) {
@@ -90,9 +87,9 @@ export default function Home() {
           </a>
           <span data-slot="description">, a curated list of models provided by opencode</span>
           <span data-slot="divider">&nbsp;/&nbsp;</span>
-          <a target="_self" data-slot="cta" href="/auth">
-            {auth() ? "Dashboard" : "Sign in"}
-          </a>
+          <A href={workspace() ? "/workspace/" + workspace() : "/auth/authorize"}>
+            {workspace() ? "Dashboard" : "Sign in"}
+          </A>
         </section>
 
         <section data-component="features">

+ 7 - 8
cloud/app/src/routes/workspace.tsx

@@ -3,17 +3,17 @@ import { useAuthSession } from "~/context/auth.session"
 import { IconLogo } from "../component/icon"
 import { withActor } from "~/context/auth.withActor"
 import "./workspace.css"
-import { query, action, redirect, createAsync, RouteSectionProps } from "@solidjs/router"
+import { query, action, redirect, createAsync, RouteSectionProps, Navigate, useNavigate, useParams, A } from "@solidjs/router"
 import { User } from "@opencode/cloud-core/user.js"
 import { Actor } from "@opencode/cloud-core/actor.js"
 
-const getUserInfo = query(async () => {
+const getUserInfo = query(async (workspaceID: string) => {
   "use server"
   return withActor(async () => {
     const actor = Actor.assert("user")
     const user = await User.fromID(actor.properties.userID)
     return { user }
-  })
+  }, workspaceID)
 }, "userInfo")
 
 const logout = action(async () => {
@@ -30,16 +30,15 @@ const logout = action(async () => {
 })
 
 export default function WorkspaceLayout(props: RouteSectionProps) {
-  const userInfo = createAsync(() => getUserInfo(), {
-    deferStream: true,
-  })
+  const params = useParams()
+  const userInfo = createAsync(() => getUserInfo(params.id))
   return (
     <main data-page="workspace">
       <header data-component="workspace-header">
         <div data-slot="header-brand">
-          <a href="/" data-component="site-title">
+          <A href="/" data-component="site-title">
             <IconLogo />
-          </a>
+          </A>
         </div>
         <div data-slot="header-actions">
           <span>{userInfo()?.user.email}</span>

+ 19 - 20
cloud/app/src/routes/workspace/[id].tsx

@@ -1,7 +1,7 @@
 import "./[id].css"
 import { Billing } from "@opencode/cloud-core/billing.js"
 import { Key } from "@opencode/cloud-core/key.js"
-import { action, createAsync, query, useAction, useSubmission, json } from "@solidjs/router"
+import { action, createAsync, query, useAction, useSubmission, json, useParams } from "@solidjs/router"
 import { createSignal, For, Show } from "solid-js"
 import { withActor } from "~/context/auth.withActor"
 import { IconCopy, IconCheck } from "~/component/icon"
@@ -13,23 +13,23 @@ import { Actor } from "@opencode/cloud-core/actor.js"
 /////////////////////////////////////
 
 
-const listKeys = query(async () => {
+const listKeys = query(async (workspaceID: string) => {
   "use server"
-  return withActor(() => Key.list())
+  return withActor(() => Key.list(), workspaceID)
 }, "key.list")
 
-const createKey = action(async (name: string) => {
+const createKey = action(async (workspaceID: string, name: string) => {
   "use server"
   return json(
-    withActor(() => Key.create({ name })),
+    withActor(() => Key.create({ name }), workspaceID),
     { revalidate: listKeys.key },
   )
 }, "key.create")
 
-const removeKey = action(async (id: string) => {
+const removeKey = action(async (workspaceID: string, id: string) => {
   "use server"
   return json(
-    withActor(() => Key.remove({ id })),
+    withActor(() => Key.remove({ id }), workspaceID),
     { revalidate: listKeys.key },
   )
 }, "key.remove")
@@ -38,7 +38,7 @@ const removeKey = action(async (id: string) => {
 // Billing related queries and actions
 /////////////////////////////////////
 
-const getBillingInfo = query(async () => {
+const getBillingInfo = query(async (workspaceID: string) => {
   "use server"
   return withActor(async () => {
     const actor = Actor.assert("user")
@@ -51,25 +51,26 @@ const getBillingInfo = query(async () => {
     ])
     console.log("duration", Date.now() - now)
     return { user, billing, payments, usage }
-  })
+  }, workspaceID)
 }, "billingInfo")
 
-const createCheckoutUrl = action(async (successUrl: string, cancelUrl: string) => {
+const createCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
   "use server"
-  return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }))
+  return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
 }, "checkoutUrl")
 
-const createPortalUrl = action(async (returnUrl: string) => {
+const createPortalUrl = action(async (workspaceID: string, returnUrl: string) => {
   "use server"
-  return withActor(() => Billing.generatePortalUrl({ returnUrl }))
+  return withActor(() => Billing.generatePortalUrl({ returnUrl }), workspaceID)
 }, "portalUrl")
 
 export default function () {
+  const params = useParams()
 
   /////////////////
   // Keys section
   /////////////////
-  const keys = createAsync(() => listKeys())
+  const keys = createAsync(() => listKeys(params.id))
   const createKeyAction = useAction(createKey)
   const removeKeyAction = useAction(removeKey)
   const createKeySubmission = useSubmission(createKey)
@@ -134,7 +135,7 @@ export default function () {
     if (!keyName().trim()) return
 
     try {
-      await createKeyAction(keyName().trim())
+      await createKeyAction(params.id, keyName().trim())
       setKeyName("")
       setShowCreateForm(false)
     } catch (error) {
@@ -148,7 +149,7 @@ export default function () {
     }
 
     try {
-      await removeKeyAction(keyId)
+      await removeKeyAction(params.id, keyId)
     } catch (error) {
       console.error("Failed to delete API key:", error)
     }
@@ -157,16 +158,14 @@ export default function () {
   /////////////////
   // Billing section
   /////////////////
-  const billingInfo = createAsync(() => getBillingInfo(), {
-    deferStream: true,
-  })
+  const billingInfo = createAsync(() => getBillingInfo(params.id))
   const createCheckoutUrlAction = useAction(createCheckoutUrl)
   const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
 
   const handleBuyCredits = async () => {
     try {
       const baseUrl = window.location.href
-      const checkoutUrl = await createCheckoutUrlAction(baseUrl, baseUrl)
+      const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
       if (checkoutUrl) {
         window.location.href = checkoutUrl
       }

+ 0 - 0
cloud/app/src/routes/workspace/index.tsx