Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ If you prefer not to use Docker. **All commands run from the repository root unl
cd packages/db && bun run db:migrate && cd ../..
```

For ad-hoc schema iteration during development you can also use `bun run db:push` from `packages/db`, but `db:migrate` is the canonical command for both local and CI/CD setups.
For ad-hoc schema iteration during development you can also use `bun run db:push` from `packages/db`, but `db:migrate` is the canonical command for staging and production. `db:push` reconciles directly to the current schema without running versioned migration guards or backfills. For disposable local/dev databases, `bun run db:push --force` accepts Drizzle's data-loss prompts, including column drops.

4. **Run the Development Servers:**

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ jobs:

if [ "${ENVIRONMENT}" = "dev" ]; then
echo "Dev environment — pushing schema directly (db:push)"
# Dev deliberately forces direct schema reconciliation; staging and
# production use guarded versioned migrations in the other branch.
# drizzle-kit push needs a TTY to resolve ambiguous renames (--force only
# covers data-loss). In CI it throws "Interactive prompts require a TTY
# terminal" but still exits 0, so the job goes green without applying the
Expand All @@ -81,7 +83,6 @@ jobs:
echo "ERROR: db:push needs an interactive rename decision; land it as a versioned migration instead of relying on push." >&2
exit 1
fi
bun run ./scripts/apply-dev-workspace-file-size-cutover.ts
Comment thread
icecrasher321 marked this conversation as resolved.
else
Comment thread
icecrasher321 marked this conversation as resolved.
echo "Applying versioned migrations (db:migrate)"
bun run ./scripts/migrate.ts
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ jobs:
working-directory: packages/db
run: bun run db:migrate

- name: Verify retired-column contract migration in PostgreSQL
working-directory: packages/db
env:
RETIRED_COLUMNS_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim
run: bunx vitest run scripts/retired-columns.postgres.test.ts

- name: Verify OAuth lifecycle and SCIM membership guards in PostgreSQL
working-directory: apps/sim
# These suites share a schema and install triggers; parallel files can deadlock DDL against cleanup.
Expand Down
9 changes: 4 additions & 5 deletions apps/sim/app/api/files/uploads/finalizers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import { type Principal, resolvePrincipalAuditAttribution } from '@sim/auth/principal'
import { db } from '@sim/db'
import { withInsertColumns } from '@sim/db/insert-columns'
import { type WorkspaceFileRow, workspaceFileColumns, workspaceFiles } from '@sim/db/schema'
import { type WorkspaceFileRow, workspaceFiles } from '@sim/db/schema'
import { generateId } from '@sim/utils/id'
import { eq, sql } from 'drizzle-orm'
import type { V2File } from '@/lib/api/contracts/v2/files'
Expand Down Expand Up @@ -367,7 +366,7 @@ async function insertOrLoadFileMetadata(

const now = new Date()
const [inserted] = await db
.insert(withInsertColumns(workspaceFiles, workspaceFileColumns))
.insert(workspaceFiles)
.values({
id: generateId(),
key: input.key,
Expand All @@ -384,7 +383,7 @@ async function insertOrLoadFileMetadata(
contentUpdatedAt: now,
})
.onConflictDoNothing()
.returning(workspaceFileColumns)
.returning()

if (inserted) return { file: inserted, created: true }

Expand All @@ -399,7 +398,7 @@ async function insertOrLoadFileMetadata(

async function findFileMetadataByKey(key: string): Promise<FileMetadataRecord | undefined> {
const [file] = await db
.select(workspaceFileColumns)
.select()
.from(workspaceFiles)
.where(eq(workspaceFiles.key, key))
.orderBy(sql`${workspaceFiles.deletedAt} IS NULL DESC`)
Expand Down
8 changes: 4 additions & 4 deletions apps/sim/app/api/organizations/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import { db } from '@sim/db'
import { member, organization, organizationColumns } from '@sim/db/schema'
import { member, organization } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { isOrgAdminRole } from '@sim/platform-authz/workspace'
import { and, eq, ne } from 'drizzle-orm'
Expand Down Expand Up @@ -64,7 +64,7 @@ export const GET = withRouteHandler(
}

const organizationEntry = await db
.select(organizationColumns)
.select()
.from(organization)
.where(eq(organization.id, organizationId))
.limit(1)
Expand Down Expand Up @@ -156,7 +156,7 @@ export const PUT = withRouteHandler(
if (name !== undefined || slug !== undefined || logo !== undefined) {
if (slug !== undefined) {
const existingSlug = await db
.select(organizationColumns)
.select()
.from(organization)
.where(and(eq(organization.slug, slug), ne(organization.id, organizationId)))
.limit(1)
Expand All @@ -180,7 +180,7 @@ export const PUT = withRouteHandler(
.update(organization)
.set(updateData)
.where(eq(organization.id, organizationId))
.returning(organizationColumns)
.returning()

if (updatedOrg.length === 0) {
return NextResponse.json({ error: 'Organization not found' }, { status: 404 })
Expand Down
5 changes: 2 additions & 3 deletions apps/sim/app/api/v1/admin/credits/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@

import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import { db } from '@sim/db'
import { withInsertColumns } from '@sim/db/insert-columns'
import { organization, subscription, user, userStats, userStatsColumns } from '@sim/db/schema'
import { organization, subscription, user, userStats } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { generateShortId } from '@sim/utils/id'
import { normalizeEmail } from '@sim/utils/string'
Expand Down Expand Up @@ -156,7 +155,7 @@ export const POST = withRouteHandler(
.limit(1)

if (!existingStats) {
await db.insert(withInsertColumns(userStats, userStatsColumns)).values({
await db.insert(userStats).values({
id: generateShortId(),
userId: entityId,
})
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { db, dbReplica } from '@sim/db'
import { member, organization, organizationColumns } from '@sim/db/schema'
import { member, organization } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { count, eq } from 'drizzle-orm'
import {
Expand Down Expand Up @@ -155,7 +155,7 @@ export const PATCH = withRouteHandler(
if (!parsed.success) return parsed.response

const [orgData] = await db
.select(organizationColumns)
.select()
.from(organization)
.where(eq(organization.id, organizationId))
.limit(1)
Expand Down
8 changes: 4 additions & 4 deletions apps/sim/app/api/v1/admin/organizations/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
recordAuditBatch,
} from '@sim/audit'
import { db } from '@sim/db'
import { member, organization, organizationColumns, subscription } from '@sim/db/schema'
import { member, organization, subscription } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, count, eq, inArray, isNull, not, or } from 'drizzle-orm'
import {
Expand Down Expand Up @@ -93,7 +93,7 @@ export const GET = withRouteHandler(

try {
const [orgData] = await db
.select(organizationColumns)
.select()
.from(organization)
.where(eq(organization.id, organizationId))
.limit(1)
Expand Down Expand Up @@ -144,7 +144,7 @@ export const PATCH = withRouteHandler(

try {
const [existing] = await db
.select(organizationColumns)
.select()
.from(organization)
.where(eq(organization.id, organizationId))
.limit(1)
Expand Down Expand Up @@ -183,7 +183,7 @@ export const PATCH = withRouteHandler(
.update(organization)
.set(updateData)
.where(eq(organization.id, organizationId))
.returning(organizationColumns)
.returning()

const updatedFields = auditUpdatedFields(updateData)
logger.info(`Admin API: Updated organization ${organizationId}`, { updatedFields })
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/v1/admin/organizations/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import { db, dbReplica } from '@sim/db'
import { member, organization, organizationColumns, user } from '@sim/db/schema'
import { member, organization, user } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { slugify } from '@sim/utils/string'
import { count, eq } from 'drizzle-orm'
Expand Down Expand Up @@ -155,7 +155,7 @@ export const POST = withRouteHandler(
})

const [createdOrg] = await db
.select(organizationColumns)
.select()
.from(organization)
.where(eq(organization.id, organizationId))
.limit(1)
Expand Down
20 changes: 4 additions & 16 deletions apps/sim/app/api/v1/admin/users/[id]/billing/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,7 @@
*/

import { db } from '@sim/db'
import { withInsertColumns } from '@sim/db/insert-columns'
import {
member,
organization,
subscription,
user,
userStats,
userStatsColumns,
} from '@sim/db/schema'
import { member, organization, subscription, user, userStats } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { generateShortId } from '@sim/utils/id'
import { eq, or } from 'drizzle-orm'
Expand Down Expand Up @@ -86,11 +78,7 @@ export const GET = withRouteHandler(
return notFoundResponse('User')
}

const [stats] = await db
.select(userStatsColumns)
.from(userStats)
.where(eq(userStats.userId, userId))
.limit(1)
const [stats] = await db.select().from(userStats).where(eq(userStats.userId, userId)).limit(1)

// Canonical current-period usage (attributed usage_log, refresh-adjusted)
// comes from the same helper users see.
Expand Down Expand Up @@ -180,7 +168,7 @@ export const PATCH = withRouteHandler(
}

const [existingStats] = await db
.select(userStatsColumns)
.select()
.from(userStats)
.where(eq(userStats.userId, userId))
.limit(1)
Expand Down Expand Up @@ -248,7 +236,7 @@ export const PATCH = withRouteHandler(
if (existingStats) {
await db.update(userStats).set(updateData).where(eq(userStats.userId, userId))
} else {
await db.insert(withInsertColumns(userStats, userStatsColumns)).values({
await db.insert(userStats).values({
id: generateShortId(),
userId,
...updateData,
Expand Down
7 changes: 3 additions & 4 deletions apps/sim/ee/workspace-forking/lib/copy/copy-files.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { db } from '@sim/db'
import { withInsertColumns } from '@sim/db/insert-columns'
import { workspaceFileColumns, workspaceFiles } from '@sim/db/schema'
import { workspaceFiles } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { generateId } from '@sim/utils/id'
Expand Down Expand Up @@ -197,7 +196,7 @@ export async function planForkFileCopies(params: {
selectors.length === 0
? []
: await tx
.select(workspaceFileColumns)
.select()
.from(workspaceFiles)
.where(
and(
Expand Down Expand Up @@ -363,7 +362,7 @@ export async function executeForkFileBlobCopies(
await db.transaction(async (tx) => {
assertForkCopyActive(control)
const [inserted] = await tx
.insert(withInsertColumns(workspaceFiles, workspaceFileColumns))
.insert(workspaceFiles)
.values({
id: task.targetFileId,
key: task.targetKey,
Expand Down
15 changes: 4 additions & 11 deletions apps/sim/lib/admin/dashboard.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import { db } from '@sim/db'
import { withInsertColumns } from '@sim/db/insert-columns'
import {
member,
organization,
organizationColumns,
organizationMemberUsageLimit,
outboxEvent,
permissions,
subscription,
usageLog,
user,
userStats,
userStatsColumns,
workspace,
} from '@sim/db/schema'
import { generateId } from '@sim/utils/id'
Expand Down Expand Up @@ -654,11 +651,7 @@ export async function listDashboardUsers({ search, limit, offset }: PaginationIn
async function getDashboardOrganizationSummary(organizationId: string) {
const [[org], [memberCountRow], [externalCountRow], latestSubscription, provisionings] =
await Promise.all([
db
.select(organizationColumns)
.from(organization)
.where(eq(organization.id, organizationId))
.limit(1),
db.select().from(organization).where(eq(organization.id, organizationId)).limit(1),
db.select({ value: count() }).from(member).where(eq(member.organizationId, organizationId)),
db
.select({ value: countDistinct(permissions.userId) })
Expand Down Expand Up @@ -1284,7 +1277,7 @@ export async function updateDashboardOrganizationLimits(
const providerBacked = await db.transaction(async (tx) => {
await acquireOrganizationMutationLock(tx, organizationId)
const [org] = await tx
.select(organizationColumns)
.select()
.from(organization)
.where(eq(organization.id, organizationId))
.for('update')
Expand Down Expand Up @@ -1418,7 +1411,7 @@ export async function grantDashboardOrganizationBalance(
}),
operation: async () => {
const [org] = await tx
.select(organizationColumns)
.select()
.from(organization)
.where(eq(organization.id, organizationId))
.for('update')
Expand Down Expand Up @@ -1526,7 +1519,7 @@ export async function grantDashboardUserBalance(
? null
: getPerUserMinimumLimit(initialSubscription).toString()
await tx
.insert(withInsertColumns(userStats, userStatsColumns))
.insert(userStats)
.values({
id: generateId(),
userId,
Expand Down
3 changes: 1 addition & 2 deletions apps/sim/lib/auth/anonymous.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { db } from '@sim/db'
import { withInsertColumns } from '@sim/db/insert-columns'
import * as schema from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { generateId } from '@sim/utils/id'
Expand Down Expand Up @@ -38,7 +37,7 @@ export async function ensureAnonymousUserExists(): Promise<void> {
})

if (!existingStats) {
await db.insert(withInsertColumns(schema.userStats, schema.userStatsColumns)).values({
await db.insert(schema.userStats).values({
id: generateId(),
userId: ANONYMOUS_USER_ID,
currentUsageLimit: '10000000000',
Expand Down
7 changes: 1 addition & 6 deletions apps/sim/lib/auth/sim-auth-adapter.postgres.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as schema from '@sim/db/schema'
import { withUtcTimestamps } from '@sim/db/timestamps'
import { generateId } from '@sim/utils/id'
import type { BetterAuthOptions } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { organization } from 'better-auth/plugins'
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
Expand Down Expand Up @@ -62,14 +61,10 @@ describe.skipIf(!databaseUrl)('Better Auth across the organization column drop',
? adapter.transaction((tx) => exerciseOrganization(tx))
: exerciseOrganization(adapter)

await client`ALTER TABLE pg_temp.organization ADD COLUMN departed_member_usage numeric NOT NULL DEFAULT 0`
await exercise()
await client`ALTER TABLE pg_temp.organization DROP COLUMN departed_member_usage`

const unprojected = drizzleAdapter(database, { provider: 'pg', schema })(OPTIONS)
await expect(
unprojected.findOne({ model: 'organization', where: [{ field: 'id', value: 'missing' }] })
).rejects.toMatchObject({ cause: { code: '42703' } })

await exercise()
} finally {
await client.end()
Expand Down
11 changes: 0 additions & 11 deletions apps/sim/lib/auth/sim-auth-adapter.sql.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/**
* @vitest-environment node
*/
import * as schema from '@sim/db/schema'
import type { BetterAuthOptions } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { organization } from 'better-auth/plugins'
import { drizzle } from 'drizzle-orm/pg-proxy'
import { describe, expect, it, vi } from 'vitest'
Expand Down Expand Up @@ -70,13 +68,4 @@ describe('Better Auth organization SQL', () => {
expect(query, operation.name).not.toContain('"departed_member_usage"')
}
})

it('retains the full migration schema while the unprojected adapter remains incompatible', async () => {
const execute = vi.fn(async (_query: string) => ({ rows: [] }))
const adapter = drizzleAdapter(drizzle(execute), { provider: 'pg', schema })(OPTIONS)

await adapter.findOne({ model: 'organization', where: WHERE })

expect(execute.mock.calls[0][0]).toContain('"departed_member_usage"')
})
})
Loading
Loading