This commit is contained in:
Frank
2025-09-28 19:55:38 -04:00
parent e3a7096e44
commit 918739057d
20 changed files with 1093 additions and 8 deletions

View File

@@ -1,12 +1,16 @@
import "./[id].css" import "./[id].css"
import { MonthlyLimitSection } from "~/component/workspace/monthly-limit-section" import { MonthlyLimitSection } from "./monthly-limit-section"
import { NewUserSection } from "~/component/workspace/new-user-section" import { NewUserSection } from "./new-user-section"
import { BillingSection } from "~/component/workspace/billing-section" import { BillingSection } from "./billing-section"
import { PaymentSection } from "~/component/workspace/payment-section" import { PaymentSection } from "./payment-section"
import { UsageSection } from "~/component/workspace/usage-section" import { UsageSection } from "./usage-section"
import { KeySection } from "~/component/workspace/key-section" import { KeySection } from "./key-section"
import { MemberSection } from "./member-section"
import { Show } from "solid-js"
import { useParams } from "@solidjs/router"
export default function () { export default function () {
const params = useParams()
return ( return (
<div data-page="workspace-[id]"> <div data-page="workspace-[id]">
<section data-component="title-section"> <section data-component="title-section">
@@ -23,6 +27,9 @@ export default function () {
<div data-slot="sections"> <div data-slot="sections">
<NewUserSection /> <NewUserSection />
<KeySection /> <KeySection />
<Show when={isBeta(params.id)}>
<MemberSection />
</Show>
<BillingSection /> <BillingSection />
<MonthlyLimitSection /> <MonthlyLimitSection />
<UsageSection /> <UsageSection />
@@ -36,6 +43,6 @@ export function isBeta(workspaceID: string) {
return [ return [
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // production "wrk_01K46JDFR0E75SG2Q8K172KF3Y", // production
"wrk_01K4NFRR5P7FSYWH88307B4DDS", // dev "wrk_01K4NFRR5P7FSYWH88307B4DDS", // dev
"wrk_01K4PJRKJ2WPQZN3FFYRV4673F", // frank "wrk_01K68M8J1KK0PJ39H59B1EGHP6", // frank
].includes(workspaceID) ].includes(workspaceID)
} }

View File

@@ -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;
}
}
}
}
}

View File

@@ -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
when={store.show}
fallback={
<button data-color="primary" onClick={() => show()}>
Invite Member
</button>
}
>
<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" />
<Show when={submission.result && submission.result.error}>
{(err) => <div data-slot="form-error">{err()}</div>}
</Show>
</div>
<input type="hidden" name="workspaceID" value={params.id} />
<div data-slot="form-actions">
<button type="reset" data-color="ghost" onClick={() => hide()}>
Cancel
</button>
<button type="submit" data-color="primary" disabled={submission.pending}>
{submission.pending ? "Inviting..." : "Invite"}
</button>
</div>
</form>
</Show>
)
}
export function MemberSection() {
const params = useParams()
const members = createAsync(() => listMembers(params.id))
return (
<section class={styles.root}>
<div data-slot="section-title">
<h2>Members</h2>
<p>Manage your members for accessing opencode services.</p>
</div>
<MemberCreateForm />
<div data-slot="members-table">
<Show
when={members()?.length}
fallback={
<div data-component="empty-state">
<p>Invite a member to your workspace</p>
</div>
}
>
<table data-slot="members-table-element">
<thead>
<tr>
<th>Email</th>
<th>Role</th>
<th>Joined</th>
<th></th>
</tr>
</thead>
<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>
<td data-slot="member-actions">
<form action={removeMember} method="post">
<input type="hidden" name="id" value={member.id} />
<input type="hidden" name="workspaceID" value={params.id} />
<button data-color="ghost">Delete</button>
</form>
</td>
</tr>
)
}}
</For>
</tbody>
</table>
</Show>
</div>
</section>
)
}

View File

@@ -0,0 +1 @@
ALTER TABLE `user` MODIFY COLUMN `role` enum('admin','member') NOT NULL;

View File

@@ -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": {}
}
}

View File

@@ -134,6 +134,13 @@
"when": 1759077265040, "when": 1759077265040,
"tag": "0018_nervous_iron_lad", "tag": "0018_nervous_iron_lad",
"breakpoints": true "breakpoints": true
},
{
"idx": 19,
"version": "5",
"when": 1759103696975,
"tag": "0019_dazzling_cable",
"breakpoints": true
} }
] ]
} }

View File

@@ -15,7 +15,7 @@ export const UserTable = mysqlTable(
timeSeen: utc("time_seen"), timeSeen: utc("time_seen"),
timeJoined: utc("time_joined"), timeJoined: utc("time_joined"),
color: int("color"), 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)], (table) => [...workspaceIndexes(table), uniqueIndex("user_email").on(table.workspaceID, table.email)],
) )