Frank 6 месяцев назад
Родитель
Сommit
7447460b5a

+ 1 - 1
github/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 1 - 0
infra/app.ts

@@ -2,6 +2,7 @@ import { domain } from "./stage"
 
 const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
 const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
+export const EMAILOCTOPUS_API_KEY = new sst.Secret("EMAILOCTOPUS_API_KEY")
 const bucket = new sst.cloudflare.Bucket("Bucket")
 
 export const api = new sst.cloudflare.Worker("Api", {

+ 2 - 1
infra/console.ts

@@ -1,4 +1,5 @@
 import { domain } from "./stage"
+import { EMAILOCTOPUS_API_KEY } from "./app"
 
 ////////////////
 // DATABASE
@@ -121,7 +122,7 @@ if ($app.stage === "production" || $app.stage === "frank") {
 new sst.cloudflare.x.SolidStart("Console", {
   domain,
   path: "packages/console/app",
-  link: [database, AUTH_API_URL, STRIPE_WEBHOOK_SECRET, STRIPE_SECRET_KEY, ZEN_MODELS],
+  link: [database, AUTH_API_URL, STRIPE_WEBHOOK_SECRET, STRIPE_SECRET_KEY, ZEN_MODELS, EMAILOCTOPUS_API_KEY],
   environment: {
     //VITE_DOCS_URL: web.url.apply((url) => url!),
     //VITE_API_URL: gateway.url.apply((url) => url!),

+ 4 - 2
packages/app/src/sst-env.d.ts

@@ -2,7 +2,9 @@
 /* tslint:disable */
 /* eslint-disable */
 /// <reference types="vite/client" />
-interface ImportMetaEnv {}
+interface ImportMetaEnv {
+
+}
 interface ImportMeta {
   readonly env: ImportMetaEnv
-}
+}

+ 1 - 1
packages/app/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 29 - 13
packages/console/app/src/routes/workspace/member-section.tsx

@@ -5,7 +5,7 @@ import { createStore } from "solid-js/store"
 import { formatDateUTC, formatDateForTable } from "./common"
 import styles from "./member-section.module.css"
 import { and, Database, eq, sql } from "@opencode/console-core/drizzle/index.js"
-import { UserTable } from "@opencode/console-core/schema/user.sql.js"
+import { UserTable, UserRole } from "@opencode/console-core/schema/user.sql.js"
 import { Identifier } from "@opencode/console-core/identifier.js"
 
 const removeMember = action(async (form: FormData) => {
@@ -31,10 +31,12 @@ const removeMember = action(async (form: FormData) => {
 
 const inviteMember = action(async (form: FormData) => {
   "use server"
-  const name = form.get("name")?.toString().trim()
-  if (!name) return { error: "Name is required" }
+  const email = form.get("email")?.toString().trim()
+  if (!email) return { error: "Email is required" }
   const workspaceID = form.get("workspaceID")?.toString()
   if (!workspaceID) return { error: "Workspace ID is required" }
+  const role = form.get("role")?.toString() as (typeof UserRole)[number]
+  if (!role) return { error: "Role is required" }
   return json(
     await withActor(
       () =>
@@ -44,12 +46,10 @@ const inviteMember = action(async (form: FormData) => {
             .values({
               id: Identifier.create("user"),
               name: "",
-              email: name,
+              email,
               workspaceID,
-              role: "member",
-              timeJoined: sql`now()`,
+              role,
             })
-            .onDuplicateKeyUpdate({ set: { timeJoined: sql`now()` } })
             .then((data) => ({ error: undefined, data }))
             .catch((e) => ({ error: e.message as string })),
         ),
@@ -109,7 +109,23 @@ export function MemberCreateForm() {
     >
       <form action={inviteMember} method="post" data-slot="create-form">
         <div data-slot="input-container">
-          <input ref={(r) => (input = r)} data-component="input" name="name" type="text" placeholder="Enter email" />
+          <input ref={(r) => (input = r)} data-component="input" name="email" type="text" placeholder="Enter email" />
+          <div data-slot="role-selector">
+            <label>
+              <input type="radio" name="role" value="admin" checked />
+              <div>
+                <strong>Admin</strong>
+                <p>Can manage models, members, and billing</p>
+              </div>
+            </label>
+            <label>
+              <input type="radio" name="role" value="member" />
+              <div>
+                <strong>Member</strong>
+                <p>Can only generate API keys for themselves</p>
+              </div>
+            </label>
+          </div>
           <Show when={submission.result && submission.result.error}>
             {(err) => <div data-slot="form-error">{err()}</div>}
           </Show>
@@ -160,15 +176,15 @@ export function MemberSection() {
             <tbody>
               <For each={members()!}>
                 {(member) => {
-                  const [copied, setCopied] = createSignal(false)
-                  // const submission = useSubmission(removeKey, ([fd]) => fd.get("id")?.toString() === key.id)
                   return (
                     <tr>
                       <td data-slot="member-email">{member.email}</td>
                       <td data-slot="member-role">{member.role}</td>
-                      <td data-slot="member-joined" title={formatDateUTC(member.timeJoined!)}>
-                        {formatDateForTable(member.timeJoined!)}
-                      </td>
+                      <Show when={member.timeSeen} fallback={<td data-slot="member-joined">invited</td>}>
+                        <td data-slot="member-joined" title={formatDateUTC(member.timeSeen!)}>
+                          {formatDateForTable(member.timeSeen!)}
+                        </td>
+                      </Show>
                       <td data-slot="member-actions">
                         <form action={removeMember} method="post">
                           <input type="hidden" name="id" value={member.id} />

+ 1 - 1
packages/console/app/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 1 - 0
packages/console/core/migrations/0020_supreme_jack_power.sql

@@ -0,0 +1 @@
+ALTER TABLE `user` DROP COLUMN `time_joined`;

+ 695 - 0
packages/console/core/migrations/meta/0020_snapshot.json

@@ -0,0 +1,695 @@
+{
+  "version": "5",
+  "dialect": "mysql",
+  "id": "908437f9-54ed-4c83-b555-614926e326f8",
+  "prevId": "a2bb7222-561c-45f0-8939-8ef9b8e57bb3",
+  "tables": {
+    "account": {
+      "name": "account",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "email": {
+          "name": "email",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "email": {
+          "name": "email",
+          "columns": [
+            "email"
+          ],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {},
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "billing": {
+      "name": "billing",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "customer_id": {
+          "name": "customer_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_method_id": {
+          "name": "payment_method_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_method_last4": {
+          "name": "payment_method_last4",
+          "type": "varchar(4)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "balance": {
+          "name": "balance",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "monthly_limit": {
+          "name": "monthly_limit",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "monthly_usage": {
+          "name": "monthly_usage",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_monthly_usage_updated": {
+          "name": "time_monthly_usage_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload": {
+          "name": "reload",
+          "type": "boolean",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "reload_error": {
+          "name": "reload_error",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_reload_error": {
+          "name": "time_reload_error",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_reload_locked_till": {
+          "name": "time_reload_locked_till",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "global_customer_id": {
+          "name": "global_customer_id",
+          "columns": [
+            "customer_id"
+          ],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "billing_workspace_id_id_pk": {
+          "name": "billing_workspace_id_id_pk",
+          "columns": [
+            "workspace_id",
+            "id"
+          ]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "payment": {
+      "name": "payment",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "customer_id": {
+          "name": "customer_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "invoice_id": {
+          "name": "invoice_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "payment_id": {
+          "name": "payment_id",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "amount": {
+          "name": "amount",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_refunded": {
+          "name": "time_refunded",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "payment_workspace_id_id_pk": {
+          "name": "payment_workspace_id_id_pk",
+          "columns": [
+            "workspace_id",
+            "id"
+          ]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "usage": {
+      "name": "usage",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "model": {
+          "name": "model",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "provider": {
+          "name": "provider",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "input_tokens": {
+          "name": "input_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "output_tokens": {
+          "name": "output_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "reasoning_tokens": {
+          "name": "reasoning_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_read_tokens": {
+          "name": "cache_read_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_write_5m_tokens": {
+          "name": "cache_write_5m_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cache_write_1h_tokens": {
+          "name": "cache_write_1h_tokens",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "cost": {
+          "name": "cost",
+          "type": "bigint",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {},
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "usage_workspace_id_id_pk": {
+          "name": "usage_workspace_id_id_pk",
+          "columns": [
+            "workspace_id",
+            "id"
+          ]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "key": {
+      "name": "key",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "actor": {
+          "name": "actor",
+          "type": "json",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "old_name": {
+          "name": "old_name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "key": {
+          "name": "key",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_used": {
+          "name": "time_used",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "global_key": {
+          "name": "global_key",
+          "columns": [
+            "key"
+          ],
+          "isUnique": true
+        },
+        "name": {
+          "name": "name",
+          "columns": [
+            "workspace_id",
+            "name"
+          ],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "key_workspace_id_id_pk": {
+          "name": "key_workspace_id_id_pk",
+          "columns": [
+            "workspace_id",
+            "id"
+          ]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "user": {
+      "name": "user",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "workspace_id": {
+          "name": "workspace_id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "email": {
+          "name": "email",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "time_seen": {
+          "name": "time_seen",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "color": {
+          "name": "color",
+          "type": "int",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "role": {
+          "name": "role",
+          "type": "enum('admin','member')",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "user_email": {
+          "name": "user_email",
+          "columns": [
+            "workspace_id",
+            "email"
+          ],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "user_workspace_id_id_pk": {
+          "name": "user_workspace_id_id_pk",
+          "columns": [
+            "workspace_id",
+            "id"
+          ]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    },
+    "workspace": {
+      "name": "workspace",
+      "columns": {
+        "id": {
+          "name": "id",
+          "type": "varchar(30)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false
+        },
+        "slug": {
+          "name": "slug",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "name": {
+          "name": "name",
+          "type": "varchar(255)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        },
+        "time_created": {
+          "name": "time_created",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "(now())"
+        },
+        "time_updated": {
+          "name": "time_updated",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": true,
+          "autoincrement": false,
+          "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+        },
+        "time_deleted": {
+          "name": "time_deleted",
+          "type": "timestamp(3)",
+          "primaryKey": false,
+          "notNull": false,
+          "autoincrement": false
+        }
+      },
+      "indexes": {
+        "slug": {
+          "name": "slug",
+          "columns": [
+            "slug"
+          ],
+          "isUnique": true
+        }
+      },
+      "foreignKeys": {},
+      "compositePrimaryKeys": {
+        "workspace_id": {
+          "name": "workspace_id",
+          "columns": [
+            "id"
+          ]
+        }
+      },
+      "uniqueConstraints": {},
+      "checkConstraint": {}
+    }
+  },
+  "views": {},
+  "_meta": {
+    "schemas": {},
+    "tables": {},
+    "columns": {}
+  },
+  "internal": {
+    "tables": {},
+    "indexes": {}
+  }
+}

+ 7 - 0
packages/console/core/migrations/meta/_journal.json

@@ -141,6 +141,13 @@
       "when": 1759103696975,
       "tag": "0019_dazzling_cable",
       "breakpoints": true
+    },
+    {
+      "idx": 20,
+      "version": "5",
+      "when": 1759169697658,
+      "tag": "0020_supreme_jack_power",
+      "breakpoints": true
     }
   ]
 }

+ 1 - 1
packages/console/core/src/actor.ts

@@ -21,7 +21,7 @@ export namespace Actor {
     properties: {
       userID: string
       workspaceID: string
-      role: UserRole
+      role: (typeof UserRole)[number]
     }
   }
 

+ 2 - 4
packages/console/core/src/schema/user.sql.ts

@@ -2,8 +2,7 @@ import { mysqlTable, uniqueIndex, varchar, int, mysqlEnum } from "drizzle-orm/my
 import { timestamps, utc, workspaceColumns } from "../drizzle/types"
 import { workspaceIndexes } from "./workspace.sql"
 
-const UserRole = ["admin", "member"] as const
-export type UserRole = (typeof UserRole)[number]
+export const UserRole = ["admin", "member"] as const
 
 export const UserTable = mysqlTable(
   "user",
@@ -13,9 +12,8 @@ export const UserTable = mysqlTable(
     email: varchar("email", { length: 255 }).notNull(),
     name: varchar("name", { length: 255 }).notNull(),
     timeSeen: utc("time_seen"),
-    timeJoined: utc("time_joined"),
     color: int("color"),
-    role: mysqlEnum("role", ["admin", "member"]).notNull(),
+    role: mysqlEnum("role", UserRole).notNull(),
   },
   (table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
 )

+ 1 - 1
packages/console/core/src/workspace.ts

@@ -21,8 +21,8 @@ export namespace Workspace {
         id: Identifier.create("user"),
         email: account.properties.email,
         name: "",
+        timeSeen: sql`now()`,
         role: "admin",
-        timeJoined: sql`now()`,
       })
       await tx.insert(BillingTable).values({
         workspaceID,

+ 1 - 1
packages/console/core/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 59 - 51
packages/console/function/sst-env.d.ts

@@ -6,75 +6,83 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
     }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
     }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
     }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
     }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    ZEN_MODELS: {
-      type: "sst.sst.Secret"
-      value: string
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
-// cloudflare
-import * as cloudflare from "@cloudflare/workers-types"
+// cloudflare 
+import * as cloudflare from "@cloudflare/workers-types";
 declare module "sst" {
   export interface Resource {
-    Api: cloudflare.Service
-    AuthApi: cloudflare.Service
-    AuthStorage: cloudflare.KVNamespace
-    Bucket: cloudflare.R2Bucket
-    LogProcessor: cloudflare.Service
+    "Api": cloudflare.Service
+    "AuthApi": cloudflare.Service
+    "AuthStorage": cloudflare.KVNamespace
+    "Bucket": cloudflare.R2Bucket
+    "LogProcessor": cloudflare.Service
   }
 }
 
 import "sst"
-export {}
+export {}

+ 59 - 51
packages/console/resource/sst-env.d.ts

@@ -6,75 +6,83 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
     }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
     }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
     }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
     }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    ZEN_MODELS: {
-      type: "sst.sst.Secret"
-      value: string
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
-// cloudflare
-import * as cloudflare from "@cloudflare/workers-types"
+// cloudflare 
+import * as cloudflare from "@cloudflare/workers-types";
 declare module "sst" {
   export interface Resource {
-    Api: cloudflare.Service
-    AuthApi: cloudflare.Service
-    AuthStorage: cloudflare.KVNamespace
-    Bucket: cloudflare.R2Bucket
-    LogProcessor: cloudflare.Service
+    "Api": cloudflare.Service
+    "AuthApi": cloudflare.Service
+    "AuthStorage": cloudflare.KVNamespace
+    "Bucket": cloudflare.R2Bucket
+    "LogProcessor": cloudflare.Service
   }
 }
 
 import "sst"
-export {}
+export {}

+ 1 - 1
packages/console/scripts/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 59 - 51
packages/function/sst-env.d.ts

@@ -6,75 +6,83 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
     }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
     }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
     }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
     }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    ZEN_MODELS: {
-      type: "sst.sst.Secret"
-      value: string
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
-// cloudflare
-import * as cloudflare from "@cloudflare/workers-types"
+// cloudflare 
+import * as cloudflare from "@cloudflare/workers-types";
 declare module "sst" {
   export interface Resource {
-    Api: cloudflare.Service
-    AuthApi: cloudflare.Service
-    AuthStorage: cloudflare.KVNamespace
-    Bucket: cloudflare.R2Bucket
-    LogProcessor: cloudflare.Service
+    "Api": cloudflare.Service
+    "AuthApi": cloudflare.Service
+    "AuthStorage": cloudflare.KVNamespace
+    "Bucket": cloudflare.R2Bucket
+    "LogProcessor": cloudflare.Service
   }
 }
 
 import "sst"
-export {}
+export {}

+ 1 - 1
packages/opencode/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 1 - 1
packages/plugin/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 1 - 1
packages/sdk/js/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 1 - 1
packages/web/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 1 - 1
sdks/vscode/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 65 - 57
sst-env.d.ts

@@ -5,83 +5,91 @@
 
 declare module "sst" {
   export interface Resource {
-    AUTH_API_URL: {
-      type: "sst.sst.Linkable"
-      value: string
+    "AUTH_API_URL": {
+      "type": "sst.sst.Linkable"
+      "value": string
     }
-    Api: {
-      type: "sst.cloudflare.Worker"
-      url: string
+    "Api": {
+      "type": "sst.cloudflare.Worker"
+      "url": string
     }
-    AuthApi: {
-      type: "sst.cloudflare.Worker"
-      url: string
+    "AuthApi": {
+      "type": "sst.cloudflare.Worker"
+      "url": string
     }
-    AuthStorage: {
-      type: "sst.cloudflare.Kv"
+    "AuthStorage": {
+      "type": "sst.cloudflare.Kv"
     }
-    Bucket: {
-      name: string
-      type: "sst.cloudflare.Bucket"
+    "Bucket": {
+      "name": string
+      "type": "sst.cloudflare.Bucket"
     }
-    Console: {
-      type: "sst.cloudflare.SolidStart"
-      url: string
+    "Console": {
+      "type": "sst.cloudflare.SolidStart"
+      "url": string
     }
-    Database: {
-      database: string
-      host: string
-      password: string
-      port: number
-      type: "sst.sst.Linkable"
-      username: string
+    "Database": {
+      "database": string
+      "host": string
+      "password": string
+      "port": number
+      "type": "sst.sst.Linkable"
+      "username": string
     }
-    GITHUB_APP_ID: {
-      type: "sst.sst.Secret"
-      value: string
+    "Desktop": {
+      "type": "sst.cloudflare.StaticSite"
+      "url": string
     }
-    GITHUB_APP_PRIVATE_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "EMAILOCTOPUS_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GITHUB_CLIENT_ID_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_APP_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GITHUB_CLIENT_SECRET_CONSOLE: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_APP_PRIVATE_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    GOOGLE_CLIENT_ID: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_CLIENT_ID_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    HONEYCOMB_API_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "GITHUB_CLIENT_SECRET_CONSOLE": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    LogProcessor: {
-      type: "sst.cloudflare.Worker"
+    "GOOGLE_CLIENT_ID": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    STRIPE_SECRET_KEY: {
-      type: "sst.sst.Secret"
-      value: string
+    "HONEYCOMB_API_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    STRIPE_WEBHOOK_SECRET: {
-      type: "sst.sst.Linkable"
-      value: string
+    "LogProcessor": {
+      "type": "sst.cloudflare.Worker"
     }
-    Web: {
-      type: "sst.cloudflare.Astro"
-      url: string
+    "STRIPE_SECRET_KEY": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
-    ZEN_MODELS: {
-      type: "sst.sst.Secret"
-      value: string
+    "STRIPE_WEBHOOK_SECRET": {
+      "type": "sst.sst.Linkable"
+      "value": string
+    }
+    "Web": {
+      "type": "sst.cloudflare.Astro"
+      "url": string
+    }
+    "ZEN_MODELS": {
+      "type": "sst.sst.Secret"
+      "value": string
     }
   }
 }
 /// <reference path="sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}