Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
6386e6b
updates
lakeesiv Jan 13, 2026
5dddb03
required
lakeesiv Jan 13, 2026
e80660f
trashy table viewer
lakeesiv Jan 13, 2026
4316f45
updates
lakeesiv Jan 13, 2026
7e4fc32
updates
lakeesiv Jan 13, 2026
0872314
filtering ui
lakeesiv Jan 14, 2026
9a3d563
updates
lakeesiv Jan 14, 2026
48ecb19
updates
lakeesiv Jan 14, 2026
ed807be
updates
lakeesiv Jan 14, 2026
8a8589e
one input mode
lakeesiv Jan 14, 2026
a919816
format
lakeesiv Jan 14, 2026
6605c88
fix lints
lakeesiv Jan 14, 2026
c1eef30
improved errors
lakeesiv Jan 14, 2026
a537ca7
updates
lakeesiv Jan 14, 2026
fc6dbcf
updates
lakeesiv Jan 14, 2026
48250f5
chages
lakeesiv Jan 14, 2026
c155d8a
doc strings
lakeesiv Jan 14, 2026
4d176c0
breaking down file
lakeesiv Jan 14, 2026
e287388
update comments with ai
lakeesiv Jan 14, 2026
dfa018f
updates
lakeesiv Jan 14, 2026
22f89cf
comments
lakeesiv Jan 14, 2026
8f9cf93
changes
lakeesiv Jan 14, 2026
4422a69
revert
lakeesiv Jan 15, 2026
15bef48
updates
lakeesiv Jan 15, 2026
cfbc8d7
dedupe
lakeesiv Jan 15, 2026
b3ca0c9
updates
lakeesiv Jan 15, 2026
df3e869
updates
lakeesiv Jan 15, 2026
96a3fe5
updates
lakeesiv Jan 15, 2026
cbb93c6
refactoring
lakeesiv Jan 15, 2026
c9373c7
renames & refactors
lakeesiv Jan 15, 2026
b08ce03
refactoring
lakeesiv Jan 15, 2026
ffad20e
updates
lakeesiv Jan 15, 2026
793c888
undo
lakeesiv Jan 15, 2026
c3afbae
update db
lakeesiv Jan 15, 2026
5a69d16
wand
lakeesiv Jan 15, 2026
fdc3af9
updates
lakeesiv Jan 15, 2026
80270ce
fix comments
lakeesiv Jan 15, 2026
57fbd2a
fixes
lakeesiv Jan 15, 2026
7f894ec
simplify comments
lakeesiv Jan 15, 2026
ed543a7
u[dates
lakeesiv Jan 15, 2026
e503408
renames
lakeesiv Jan 15, 2026
0a6312d
better comments
lakeesiv Jan 16, 2026
4665595
validation
lakeesiv Jan 16, 2026
d00997c
updates
lakeesiv Jan 16, 2026
cfffd05
updates
lakeesiv Jan 16, 2026
1a13762
updates
lakeesiv Jan 16, 2026
4490996
fix sorting
lakeesiv Jan 16, 2026
fef2d2c
fix appearnce
lakeesiv Jan 16, 2026
c94bb5a
updating prompt to make it user sort
lakeesiv Jan 16, 2026
e695007
rm
lakeesiv Jan 16, 2026
a940dd6
updates
lakeesiv Jan 16, 2026
271375d
rename
lakeesiv Jan 16, 2026
26d9662
comments
lakeesiv Jan 16, 2026
5173320
clean comments
lakeesiv Jan 16, 2026
ea72ab5
simplicifcaiton
lakeesiv Jan 16, 2026
42aa794
updates
lakeesiv Jan 16, 2026
897891e
updates
lakeesiv Jan 16, 2026
7093209
refactor
lakeesiv Jan 16, 2026
2e624c2
reduced type confusion
lakeesiv Jan 16, 2026
f90c9c7
undo
lakeesiv Jan 16, 2026
abb671e
rename
lakeesiv Jan 16, 2026
448b8f0
undo changes
lakeesiv Jan 16, 2026
e4dd14d
undo
lakeesiv Jan 16, 2026
cca1772
simplify
lakeesiv Jan 16, 2026
86c5e1b
updates
lakeesiv Jan 16, 2026
94c6795
updates
lakeesiv Jan 16, 2026
3d81c1c
revert
lakeesiv Jan 16, 2026
51d1b95
updates
lakeesiv Jan 16, 2026
c662a31
db updates
lakeesiv Jan 16, 2026
e7f4516
type fix
lakeesiv Jan 16, 2026
4357230
fix
lakeesiv Jan 17, 2026
0e1133f
fix error handling
lakeesiv Jan 17, 2026
8955915
updates
lakeesiv Jan 17, 2026
ea77790
docs
lakeesiv Jan 17, 2026
292cd39
docs
lakeesiv Jan 17, 2026
118e4f6
updates
lakeesiv Jan 17, 2026
7ca628d
rename
lakeesiv Jan 17, 2026
37b50cb
dedupe
lakeesiv Jan 17, 2026
4b6de03
revert
lakeesiv Jan 17, 2026
e79e9e7
Merge origin/main into lakees/db
lakeesiv Jan 17, 2026
8574d66
uncook
lakeesiv Jan 17, 2026
d0c3c6a
updates
lakeesiv Jan 17, 2026
6e8dc77
fix
lakeesiv Jan 17, 2026
87f8fcd
fix
lakeesiv Jan 17, 2026
f05f5bb
fix
lakeesiv Jan 17, 2026
a8e413a
fix
lakeesiv Jan 17, 2026
107679b
prepare merge
icecrasher321 Jan 22, 2026
8d43947
Merge staging into lakees/db
icecrasher321 Jan 22, 2026
1f682eb
readd migrations
icecrasher321 Jan 22, 2026
2d49de7
add back missed code
icecrasher321 Jan 22, 2026
2818b74
migrate enrichment logic to general abstraction
icecrasher321 Jan 22, 2026
1938818
address bugbot concerns
icecrasher321 Jan 22, 2026
be757a4
adhere to size limits for tables
icecrasher321 Jan 22, 2026
d9dbe93
remove conflicting migration
icecrasher321 Feb 11, 2026
f0a8d82
Merge remote-tracking branch 'origin/staging' into lakees/db
icecrasher321 Feb 11, 2026
0ffcce8
add back migrations
icecrasher321 Feb 11, 2026
ca70280
fix tables auth
icecrasher321 Feb 11, 2026
8bf3370
fix permissive auth
icecrasher321 Feb 11, 2026
c4986a9
fix lint
icecrasher321 Feb 11, 2026
c18fbcf
Merge branch 'staging' into lakees/db
waleedlatif1 Feb 19, 2026
b476b8d
reran migrations
waleedlatif1 Feb 19, 2026
592dd46
migrate to use tanstack query for all server state
waleedlatif1 Feb 19, 2026
f1938f0
update table-selector
waleedlatif1 Feb 19, 2026
7b0e030
update names
waleedlatif1 Feb 19, 2026
407f9c7
added tables to permission groups, updated subblock types
waleedlatif1 Feb 19, 2026
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
Prev Previous commit
Next Next commit
doc strings
  • Loading branch information
lakeesiv committed Jan 17, 2026
commit c155d8ac6ca7b1af62b38045698af269590ab0aa
85 changes: 66 additions & 19 deletions apps/sim/app/api/table/[tableId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,69 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import type { TableColumnData, TableSchemaData } from '../utils'
import { checkTableAccess, checkTableWriteAccess, verifyTableWorkspace } from '../utils'

const logger = createLogger('TableDetailAPI')

/**
* Schema for getting a table by ID
* Zod schema for validating get table requests.
*
* The workspaceId is optional for backward compatibility but
* is validated via table access checks when provided.
*/
const GetTableSchema = z.object({
workspaceId: z.string().min(1, 'Workspace ID is required').optional(), // Optional for backward compatibility, validated via table access
workspaceId: z.string().min(1, 'Workspace ID is required').optional(),
})

/**
* Route params for table detail endpoints.
*/
interface TableRouteParams {
params: Promise<{ tableId: string }>
}

/**
* Normalizes a column definition ensuring all optional fields have explicit values.
*
* @param col - The column data to normalize
* @returns Normalized column with explicit required and unique values
*/
function normalizeColumn(col: TableColumnData): TableColumnData {
return {
name: col.name,
type: col.type,
required: col.required ?? false,
unique: col.unique ?? false,
}
}

/**
* GET /api/table/[tableId]?workspaceId=xxx
* Get table details
*
* Retrieves details for a specific table.
*
* @param request - The incoming HTTP request
* @param context - Route context containing tableId param
* @returns JSON response with table details or error
*
* @example Response:
* ```json
* {
* "success": true,
* "data": {
* "table": {
* "id": "tbl_abc123",
* "name": "customers",
* "schema": { "columns": [...] },
* "rowCount": 150,
* "maxRows": 10000
* }
* }
* }
* ```
*/
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ tableId: string }> }
) {
export async function GET(request: NextRequest, { params }: TableRouteParams) {
const requestId = generateRequestId()
const { tableId } = await params

Expand Down Expand Up @@ -78,6 +122,8 @@ export async function GET(

logger.info(`[${requestId}] Retrieved table ${tableId} for user ${authResult.userId}`)

const schemaData = table.schema as TableSchemaData

return NextResponse.json({
success: true,
data: {
Expand All @@ -86,12 +132,7 @@ export async function GET(
name: table.name,
description: table.description,
schema: {
columns: (table.schema as any).columns.map((col: any) => ({
name: col.name,
type: col.type,
required: col.required ?? false,
unique: col.unique ?? false,
})),
columns: schemaData.columns.map(normalizeColumn),
},
rowCount: table.rowCount,
maxRows: table.maxRows,
Expand All @@ -115,17 +156,23 @@ export async function GET(

/**
* DELETE /api/table/[tableId]?workspaceId=xxx
* Delete a table (hard delete)
*
* Permanently deletes a table and all its rows.
*
* @param request - The incoming HTTP request
* @param context - Route context containing tableId param
* @returns JSON response confirming deletion or error
*
* @remarks
* This performs a hard delete, removing all data permanently.
* The operation requires write access to the table.
*/
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ tableId: string }> }
) {
export async function DELETE(request: NextRequest, { params }: TableRouteParams) {
const requestId = generateRequestId()
const { tableId } = await params

try {
const authResult = await checkHybridAuth(_request)
const authResult = await checkHybridAuth(request)
if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized table delete attempt`)
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
Expand Down
115 changes: 86 additions & 29 deletions apps/sim/app/api/table/[tableId]/rows/[rowId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,73 @@ import {
validateRowSize,
validateUniqueConstraints,
} from '@/lib/table'
import { checkTableAccess, checkTableWriteAccess, verifyTableWorkspace } from '../../utils'
import { checkTableAccess, checkTableWriteAccess, verifyTableWorkspace } from '../../../utils'

const logger = createLogger('TableRowAPI')

/**
* Schema for getting a single row by ID
* Type for dynamic row data stored in tables.
* Keys are column names, values can be any JSON-serializable type.
*/
type RowData = Record<string, unknown>

/**
* Zod schema for validating get row requests.
*
* The workspaceId is optional for backward compatibility but
* is validated via table access checks when provided.
*/
const GetRowSchema = z.object({
workspaceId: z.string().min(1, 'Workspace ID is required').optional(), // Optional for backward compatibility, validated via table access
workspaceId: z.string().min(1, 'Workspace ID is required').optional(),
})

/**
* Schema for updating a single row
* Zod schema for validating update row requests.
*/
const UpdateRowSchema = z.object({
workspaceId: z.string().min(1, 'Workspace ID is required').optional(), // Optional for backward compatibility, validated via table access
data: z.record(z.any(), { required_error: 'Row data is required' }),
workspaceId: z.string().min(1, 'Workspace ID is required').optional(),
data: z.record(z.unknown(), { required_error: 'Row data is required' }),
})

/**
* Schema for deleting a single row
* Zod schema for validating delete row requests.
*/
const DeleteRowSchema = z.object({
workspaceId: z.string().min(1, 'Workspace ID is required').optional(), // Optional for backward compatibility, validated via table access
workspaceId: z.string().min(1, 'Workspace ID is required').optional(),
})

/**
* Route params for single row endpoints.
*/
interface RowRouteParams {
params: Promise<{ tableId: string; rowId: string }>
}

/**
* GET /api/table/[tableId]/rows/[rowId]?workspaceId=xxx
* Get a single row by ID
*
* Retrieves a single row by its ID.
*
* @param request - The incoming HTTP request
* @param context - Route context containing tableId and rowId params
* @returns JSON response with row data or error
*
* @example Response:
* ```json
* {
* "success": true,
* "data": {
* "row": {
* "id": "row_abc123",
* "data": { "name": "John", "email": "john@example.com" },
* "createdAt": "2024-01-01T00:00:00.000Z",
* "updatedAt": "2024-01-01T00:00:00.000Z"
* }
* }
* }
* ```
*/
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ tableId: string; rowId: string }> }
) {
export async function GET(request: NextRequest, { params }: RowRouteParams) {
const requestId = generateRequestId()
const { tableId, rowId } = await params

Expand Down Expand Up @@ -137,12 +170,25 @@ export async function GET(

/**
* PATCH /api/table/[tableId]/rows/[rowId]
* Update an existing row
*
* Updates an existing row with new data.
*
* @param request - The incoming HTTP request with update data
* @param context - Route context containing tableId and rowId params
* @returns JSON response with updated row or error
*
* @remarks
* The entire row data must be provided; this is a full replacement,
* not a partial update.
*
* @example Request body:
* ```json
* {
* "data": { "name": "Jane", "email": "jane@example.com" }
* }
* ```
*/
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ tableId: string; rowId: string }> }
) {
export async function PATCH(request: NextRequest, { params }: RowRouteParams) {
const requestId = generateRequestId()
const { tableId, rowId } = await params

Expand All @@ -152,7 +198,7 @@ export async function PATCH(
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}

const body = await request.json()
const body: unknown = await request.json()
const validated = UpdateRowSchema.parse(body)

// Check table write access (centralized access control)
Expand Down Expand Up @@ -192,8 +238,10 @@ export async function PATCH(
return NextResponse.json({ error: 'Table not found' }, { status: 404 })
}

const rowData = validated.data as RowData

// Validate row size
const sizeValidation = validateRowSize(validated.data)
const sizeValidation = validateRowSize(rowData)
if (!sizeValidation.valid) {
return NextResponse.json(
{ error: 'Invalid row data', details: sizeValidation.errors },
Expand All @@ -202,7 +250,7 @@ export async function PATCH(
}

// Validate row against schema
const rowValidation = validateRowAgainstSchema(validated.data, table.schema as TableSchema)
const rowValidation = validateRowAgainstSchema(rowData, table.schema as TableSchema)
if (!rowValidation.valid) {
return NextResponse.json(
{ error: 'Row data does not match schema', details: rowValidation.errors },
Expand All @@ -223,9 +271,9 @@ export async function PATCH(
.where(eq(userTableRows.tableId, tableId))

const uniqueValidation = validateUniqueConstraints(
validated.data,
rowData,
table.schema as TableSchema,
existingRows,
existingRows.map((r) => ({ id: r.id, data: r.data as RowData })),
rowId // Exclude the current row being updated
)

Expand Down Expand Up @@ -288,12 +336,21 @@ export async function PATCH(

/**
* DELETE /api/table/[tableId]/rows/[rowId]
* Delete a row
*
* Permanently deletes a single row.
*
* @param request - The incoming HTTP request
* @param context - Route context containing tableId and rowId params
* @returns JSON response confirming deletion or error
*
* @example Request body:
* ```json
* {
* "workspaceId": "ws_123"
* }
* ```
*/
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ tableId: string; rowId: string }> }
) {
export async function DELETE(request: NextRequest, { params }: RowRouteParams) {
const requestId = generateRequestId()
const { tableId, rowId } = await params

Expand All @@ -303,7 +360,7 @@ export async function DELETE(
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}

const body = await request.json()
const body: unknown = await request.json()
const validated = DeleteRowSchema.parse(body)

// Check table write access (centralized access control)
Expand Down
Loading