From 918739057d28ec440547bbe1299c1b6da9cf5485 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 28 Sep 2025 19:55:38 -0400 Subject: [PATCH] wip: zen --- .../console/app/src/routes/workspace/[id].tsx | 21 +- .../workspace/billing-section.module.css | 0 .../workspace/billing-section.tsx | 0 .../workspace/common.tsx | 0 .../workspace/key-section.module.css | 0 .../workspace/key-section.tsx | 0 .../workspace/member-section.module.css | 179 +++++ .../src/routes/workspace/member-section.tsx | 189 +++++ .../monthly-limit-section.module.css | 0 .../workspace/monthly-limit-section.tsx | 0 .../workspace/new-user-section.module.css | 0 .../workspace/new-user-section.tsx | 0 .../workspace/payment-section.module.css | 0 .../workspace/payment-section.tsx | 0 .../workspace/usage-section.module.css | 0 .../workspace/usage-section.tsx | 0 .../core/migrations/0019_dazzling_cable.sql | 1 + .../core/migrations/meta/0019_snapshot.json | 702 ++++++++++++++++++ .../core/migrations/meta/_journal.json | 7 + packages/console/core/src/schema/user.sql.ts | 2 +- 20 files changed, 1093 insertions(+), 8 deletions(-) rename packages/console/app/src/{component => routes}/workspace/billing-section.module.css (100%) rename packages/console/app/src/{component => routes}/workspace/billing-section.tsx (100%) rename packages/console/app/src/{component => routes}/workspace/common.tsx (100%) rename packages/console/app/src/{component => routes}/workspace/key-section.module.css (100%) rename packages/console/app/src/{component => routes}/workspace/key-section.tsx (100%) create mode 100644 packages/console/app/src/routes/workspace/member-section.module.css create mode 100644 packages/console/app/src/routes/workspace/member-section.tsx rename packages/console/app/src/{component => routes}/workspace/monthly-limit-section.module.css (100%) rename packages/console/app/src/{component => routes}/workspace/monthly-limit-section.tsx (100%) rename packages/console/app/src/{component => routes}/workspace/new-user-section.module.css (100%) rename packages/console/app/src/{component => routes}/workspace/new-user-section.tsx (100%) rename packages/console/app/src/{component => routes}/workspace/payment-section.module.css (100%) rename packages/console/app/src/{component => routes}/workspace/payment-section.tsx (100%) rename packages/console/app/src/{component => routes}/workspace/usage-section.module.css (100%) rename packages/console/app/src/{component => routes}/workspace/usage-section.tsx (100%) create mode 100644 packages/console/core/migrations/0019_dazzling_cable.sql create mode 100644 packages/console/core/migrations/meta/0019_snapshot.json diff --git a/packages/console/app/src/routes/workspace/[id].tsx b/packages/console/app/src/routes/workspace/[id].tsx index af8a0dd6..5257a8e9 100644 --- a/packages/console/app/src/routes/workspace/[id].tsx +++ b/packages/console/app/src/routes/workspace/[id].tsx @@ -1,12 +1,16 @@ import "./[id].css" -import { MonthlyLimitSection } from "~/component/workspace/monthly-limit-section" -import { NewUserSection } from "~/component/workspace/new-user-section" -import { BillingSection } from "~/component/workspace/billing-section" -import { PaymentSection } from "~/component/workspace/payment-section" -import { UsageSection } from "~/component/workspace/usage-section" -import { KeySection } from "~/component/workspace/key-section" +import { MonthlyLimitSection } from "./monthly-limit-section" +import { NewUserSection } from "./new-user-section" +import { BillingSection } from "./billing-section" +import { PaymentSection } from "./payment-section" +import { UsageSection } from "./usage-section" +import { KeySection } from "./key-section" +import { MemberSection } from "./member-section" +import { Show } from "solid-js" +import { useParams } from "@solidjs/router" export default function () { + const params = useParams() return (
@@ -23,6 +27,9 @@ export default function () {
+ + + @@ -36,6 +43,6 @@ export function isBeta(workspaceID: string) { return [ "wrk_01K46JDFR0E75SG2Q8K172KF3Y", // production "wrk_01K4NFRR5P7FSYWH88307B4DDS", // dev - "wrk_01K4PJRKJ2WPQZN3FFYRV4673F", // frank + "wrk_01K68M8J1KK0PJ39H59B1EGHP6", // frank ].includes(workspaceID) } diff --git a/packages/console/app/src/component/workspace/billing-section.module.css b/packages/console/app/src/routes/workspace/billing-section.module.css similarity index 100% rename from packages/console/app/src/component/workspace/billing-section.module.css rename to packages/console/app/src/routes/workspace/billing-section.module.css diff --git a/packages/console/app/src/component/workspace/billing-section.tsx b/packages/console/app/src/routes/workspace/billing-section.tsx similarity index 100% rename from packages/console/app/src/component/workspace/billing-section.tsx rename to packages/console/app/src/routes/workspace/billing-section.tsx diff --git a/packages/console/app/src/component/workspace/common.tsx b/packages/console/app/src/routes/workspace/common.tsx similarity index 100% rename from packages/console/app/src/component/workspace/common.tsx rename to packages/console/app/src/routes/workspace/common.tsx diff --git a/packages/console/app/src/component/workspace/key-section.module.css b/packages/console/app/src/routes/workspace/key-section.module.css similarity index 100% rename from packages/console/app/src/component/workspace/key-section.module.css rename to packages/console/app/src/routes/workspace/key-section.module.css diff --git a/packages/console/app/src/component/workspace/key-section.tsx b/packages/console/app/src/routes/workspace/key-section.tsx similarity index 100% rename from packages/console/app/src/component/workspace/key-section.tsx rename to packages/console/app/src/routes/workspace/key-section.tsx diff --git a/packages/console/app/src/routes/workspace/member-section.module.css b/packages/console/app/src/routes/workspace/member-section.module.css new file mode 100644 index 00000000..16b6ff8d --- /dev/null +++ b/packages/console/app/src/routes/workspace/member-section.module.css @@ -0,0 +1,179 @@ +.root { + [data-component="empty-state"] { + padding: var(--space-20) var(--space-6); + text-align: center; + border: 1px dashed var(--color-border); + border-radius: var(--border-radius-sm); + display: flex; + flex-direction: column; + gap: var(--space-2); + + p { + line-height: 1.5; + font-size: var(--font-size-sm); + color: var(--color-text-muted); + } + } + + [data-slot="create-form"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + + [data-slot="input-container"] { + display: flex; + flex-direction: column; + gap: var(--space-1); + } + + @media (max-width: 30rem) { + gap: var(--space-2); + } + + input { + flex: 1; + padding: var(--space-2) var(--space-3); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg); + color: var(--color-text); + font-size: var(--font-size-sm); + font-family: var(--font-mono); + + &:focus { + outline: none; + border-color: var(--color-accent); + } + + &::placeholder { + color: var(--color-text-disabled); + } + } + + [data-slot="form-actions"] { + display: flex; + gap: var(--space-2); + } + + [data-slot="form-error"] { + color: var(--color-danger); + font-size: var(--font-size-sm); + margin-top: var(--space-1); + line-height: 1.4; + } + } + + [data-slot="members-table"] { + overflow-x: auto; + } + + [data-slot="members-table-element"] { + width: 100%; + border-collapse: collapse; + font-size: var(--font-size-sm); + + thead { + border-bottom: 1px solid var(--color-border); + } + + th { + padding: var(--space-3) var(--space-4); + text-align: left; + font-weight: normal; + color: var(--color-text-muted); + text-transform: uppercase; + } + + td { + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--color-border-muted); + color: var(--color-text-muted); + font-family: var(--font-mono); + + &[data-slot="member-email"] { + color: var(--color-text); + font-family: var(--font-sans); + font-weight: 500; + } + + &[data-slot="member-role"] { + font-family: var(--font-mono); + + button { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + font-size: var(--font-size-sm); + font-weight: 400; + border: none; + background-color: transparent; + color: var(--color-text-muted); + font-family: var(--font-mono); + border-radius: var(--border-radius-sm); + cursor: pointer; + transition: all 0.15s ease; + text-transform: none; + + &:hover:not(:disabled) { + background-color: var(--color-bg-surface); + color: var(--color-text); + } + + &:disabled { + cursor: default; + color: var(--color-text); + } + + span { + font-family: inherit; + } + } + } + + &[data-slot="member-date"] { + color: var(--color-text); + } + + &[data-slot="member-actions"] { + font-family: var(--font-sans); + } + } + + tbody tr { + &:last-child td { + border-bottom: none; + } + } + + @media (max-width: 40rem) { + + th, + td { + padding: var(--space-2) var(--space-3); + font-size: var(--font-size-xs); + } + + th { + &:nth-child(3) + + /* Date */ + { + display: none; + } + } + + td { + &:nth-child(3) + + /* Date */ + { + display: none; + } + } + } + } +} \ No newline at end of file diff --git a/packages/console/app/src/routes/workspace/member-section.tsx b/packages/console/app/src/routes/workspace/member-section.tsx new file mode 100644 index 00000000..602c29ef --- /dev/null +++ b/packages/console/app/src/routes/workspace/member-section.tsx @@ -0,0 +1,189 @@ +import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" +import { createEffect, createSignal, For, Show } from "solid-js" +import { withActor } from "~/context/auth.withActor" +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 { Identifier } from "@opencode/console-core/identifier.js" + +const removeMember = action(async (form: FormData) => { + "use server" + const id = form.get("id")?.toString() + if (!id) return { error: "ID is required" } + const workspaceID = form.get("workspaceID")?.toString() + if (!workspaceID) return { error: "Workspace ID is required" } + return json( + await withActor( + () => + Database.use((tx) => + tx + .update(UserTable) + .set({ timeDeleted: sql`now()` }) + .where(and(eq(UserTable.id, id), eq(UserTable.workspaceID, workspaceID))), + ), + workspaceID, + ), + { revalidate: listMembers.key }, + ) +}, "member.remove") + +const inviteMember = action(async (form: FormData) => { + "use server" + const name = form.get("name")?.toString().trim() + if (!name) return { error: "Name is required" } + const workspaceID = form.get("workspaceID")?.toString() + if (!workspaceID) return { error: "Workspace ID is required" } + return json( + await withActor( + () => + Database.use((tx) => + tx + .insert(UserTable) + .values({ + id: Identifier.create("user"), + name: "", + email: name, + workspaceID, + role: "member", + timeJoined: sql`now()`, + }) + .onDuplicateKeyUpdate({ set: { timeJoined: sql`now()` } }) + .then((data) => ({ error: undefined, data })) + .catch((e) => ({ error: e.message as string })), + ), + workspaceID, + ), + { revalidate: listMembers.key }, + ) +}, "member.create") + +const listMembers = query(async (workspaceID: string) => { + "use server" + return withActor( + () => Database.use((tx) => tx.select().from(UserTable).where(eq(UserTable.workspaceID, workspaceID))), + workspaceID, + ) +}, "member.list") + +export function MemberCreateForm() { + const params = useParams() + const submission = useSubmission(inviteMember) + const [store, setStore] = createStore({ show: false }) + + let input: HTMLInputElement + + createEffect(() => { + if (!submission.pending && submission.result && !submission.result.error) { + hide() + } + }) + + function show() { + // submission.clear() does not clear the result in some cases, ie. + // 1. Create key with empty name => error shows + // 2. Put in a key name and creates the key => form hides + // 3. Click add key button again => form shows with the same error if + // submission.clear() is called only once + while (true) { + submission.clear() + if (!submission.result) break + } + setStore("show", true) + input.focus() + } + + function hide() { + setStore("show", false) + } + + return ( + show()}> + Invite Member + + } + > +
+
+ (input = r)} data-component="input" name="name" type="text" placeholder="Enter email" /> + + {(err) =>
{err()}
} +
+
+ +
+ + +
+
+
+ ) +} + +export function MemberSection() { + const params = useParams() + const members = createAsync(() => listMembers(params.id)) + + return ( +
+
+

Members

+

Manage your members for accessing opencode services.

+
+ +
+ +

Invite a member to your workspace

+
+ } + > + + + + + + + + + + + + {(member) => { + const [copied, setCopied] = createSignal(false) + // const submission = useSubmission(removeKey, ([fd]) => fd.get("id")?.toString() === key.id) + return ( + + + + + + + ) + }} + + +
EmailRoleJoined
{member.email}{member.role} + {formatDateForTable(member.timeJoined!)} + +
+ + + +
+
+ +
+
+ ) +} diff --git a/packages/console/app/src/component/workspace/monthly-limit-section.module.css b/packages/console/app/src/routes/workspace/monthly-limit-section.module.css similarity index 100% rename from packages/console/app/src/component/workspace/monthly-limit-section.module.css rename to packages/console/app/src/routes/workspace/monthly-limit-section.module.css diff --git a/packages/console/app/src/component/workspace/monthly-limit-section.tsx b/packages/console/app/src/routes/workspace/monthly-limit-section.tsx similarity index 100% rename from packages/console/app/src/component/workspace/monthly-limit-section.tsx rename to packages/console/app/src/routes/workspace/monthly-limit-section.tsx diff --git a/packages/console/app/src/component/workspace/new-user-section.module.css b/packages/console/app/src/routes/workspace/new-user-section.module.css similarity index 100% rename from packages/console/app/src/component/workspace/new-user-section.module.css rename to packages/console/app/src/routes/workspace/new-user-section.module.css diff --git a/packages/console/app/src/component/workspace/new-user-section.tsx b/packages/console/app/src/routes/workspace/new-user-section.tsx similarity index 100% rename from packages/console/app/src/component/workspace/new-user-section.tsx rename to packages/console/app/src/routes/workspace/new-user-section.tsx diff --git a/packages/console/app/src/component/workspace/payment-section.module.css b/packages/console/app/src/routes/workspace/payment-section.module.css similarity index 100% rename from packages/console/app/src/component/workspace/payment-section.module.css rename to packages/console/app/src/routes/workspace/payment-section.module.css diff --git a/packages/console/app/src/component/workspace/payment-section.tsx b/packages/console/app/src/routes/workspace/payment-section.tsx similarity index 100% rename from packages/console/app/src/component/workspace/payment-section.tsx rename to packages/console/app/src/routes/workspace/payment-section.tsx diff --git a/packages/console/app/src/component/workspace/usage-section.module.css b/packages/console/app/src/routes/workspace/usage-section.module.css similarity index 100% rename from packages/console/app/src/component/workspace/usage-section.module.css rename to packages/console/app/src/routes/workspace/usage-section.module.css diff --git a/packages/console/app/src/component/workspace/usage-section.tsx b/packages/console/app/src/routes/workspace/usage-section.tsx similarity index 100% rename from packages/console/app/src/component/workspace/usage-section.tsx rename to packages/console/app/src/routes/workspace/usage-section.tsx diff --git a/packages/console/core/migrations/0019_dazzling_cable.sql b/packages/console/core/migrations/0019_dazzling_cable.sql new file mode 100644 index 00000000..ab0a2223 --- /dev/null +++ b/packages/console/core/migrations/0019_dazzling_cable.sql @@ -0,0 +1 @@ +ALTER TABLE `user` MODIFY COLUMN `role` enum('admin','member') NOT NULL; \ No newline at end of file diff --git a/packages/console/core/migrations/meta/0019_snapshot.json b/packages/console/core/migrations/meta/0019_snapshot.json new file mode 100644 index 00000000..8f1afeb5 --- /dev/null +++ b/packages/console/core/migrations/meta/0019_snapshot.json @@ -0,0 +1,702 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "a2bb7222-561c-45f0-8939-8ef9b8e57bb3", + "prevId": "e9c91c2d-787d-4234-b98d-1620e4ce80e1", + "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 + }, + "time_joined": { + "name": "time_joined", + "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": {} + } +} \ No newline at end of file diff --git a/packages/console/core/migrations/meta/_journal.json b/packages/console/core/migrations/meta/_journal.json index 06fe047f..d19c9a11 100644 --- a/packages/console/core/migrations/meta/_journal.json +++ b/packages/console/core/migrations/meta/_journal.json @@ -134,6 +134,13 @@ "when": 1759077265040, "tag": "0018_nervous_iron_lad", "breakpoints": true + }, + { + "idx": 19, + "version": "5", + "when": 1759103696975, + "tag": "0019_dazzling_cable", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/console/core/src/schema/user.sql.ts b/packages/console/core/src/schema/user.sql.ts index 7f051a61..0c2bba94 100644 --- a/packages/console/core/src/schema/user.sql.ts +++ b/packages/console/core/src/schema/user.sql.ts @@ -15,7 +15,7 @@ export const UserTable = mysqlTable( timeSeen: utc("time_seen"), timeJoined: utc("time_joined"), color: int("color"), - role: mysqlEnum("role", ["admin", "member"]), + role: mysqlEnum("role", ["admin", "member"]).notNull(), }, (table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)], )