This commit is contained in:
Lakee Sivaraya
2026-01-13 18:07:26 -08:00
parent 9a3d5631f2
commit 48ecb19af7
8 changed files with 40 additions and 47 deletions

View File

@@ -135,7 +135,7 @@ export async function GET(
/**
* DELETE /api/table/[tableId]?workspaceId=xxx
* Delete a table (soft delete)
* Delete a table (hard delete)
*/
export async function DELETE(
request: NextRequest,
@@ -165,18 +165,16 @@ export async function DELETE(
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
}
// Soft delete table
// Delete all rows first
await db.delete(userTableRows).where(eq(userTableRows.tableId, tableId))
// Hard delete table
const [deletedTable] = await db
.update(userTableDefinitions)
.set({
deletedAt: new Date(),
updatedAt: new Date(),
})
.delete(userTableDefinitions)
.where(
and(
eq(userTableDefinitions.id, tableId),
eq(userTableDefinitions.workspaceId, validated.workspaceId),
isNull(userTableDefinitions.deletedAt)
eq(userTableDefinitions.workspaceId, validated.workspaceId)
)
)
.returning()
@@ -185,9 +183,6 @@ export async function DELETE(
return NextResponse.json({ error: 'Table not found' }, { status: 404 })
}
// Delete all rows
await db.delete(userTableRows).where(eq(userTableRows.tableId, tableId))
logger.info(`[${requestId}] Deleted table ${tableId}`)
return NextResponse.json({

View File

@@ -61,8 +61,7 @@ export function AddRowModal({ isOpen, onClose, table, onSuccess }: AddRowModalPr
const cleanData: Record<string, any> = {}
columns.forEach((col) => {
const value = rowData[col.name]
const isRequired = !col.optional
if (isRequired || (value !== '' && value !== null && value !== undefined)) {
if (col.required || (value !== '' && value !== null && value !== undefined)) {
if (col.type === 'number') {
cleanData[col.name] = value === '' ? null : Number(value)
} else if (col.type === 'json') {
@@ -132,7 +131,7 @@ export function AddRowModal({ isOpen, onClose, table, onSuccess }: AddRowModalPr
<div key={column.name} className='flex flex-col gap-[8px]'>
<Label htmlFor={column.name} className='font-medium text-[13px]'>
{column.name}
{!column.optional && <span className='text-[var(--text-error)]'> *</span>}
{column.required && <span className='text-[var(--text-error)]'> *</span>}
{column.unique && (
<span className='ml-[6px] font-normal text-[11px] text-[var(--text-tertiary)]'>
(unique)
@@ -166,7 +165,7 @@ export function AddRowModal({ isOpen, onClose, table, onSuccess }: AddRowModalPr
placeholder='{"key": "value"}'
rows={4}
className='font-mono text-[12px]'
required={!column.optional}
required={column.required}
/>
) : (
<Input
@@ -180,13 +179,13 @@ export function AddRowModal({ isOpen, onClose, table, onSuccess }: AddRowModalPr
}
placeholder={`Enter ${column.name}`}
className='h-[38px]'
required={!column.optional}
required={column.required}
/>
)}
<div className='text-[12px] text-[var(--text-tertiary)]'>
Type: {column.type}
{column.optional && ' (optional)'}
{!column.required && ' (optional)'}
</div>
</div>
))}

View File

@@ -142,7 +142,7 @@ export function EditRowModal({ isOpen, onClose, table, row, onSuccess }: EditRow
<div key={column.name} className='flex flex-col gap-[8px]'>
<Label htmlFor={column.name} className='font-medium text-[13px]'>
{column.name}
{!column.optional && <span className='text-[var(--text-error)]'> *</span>}
{column.required && <span className='text-[var(--text-error)]'> *</span>}
{column.unique && (
<span className='ml-[6px] font-normal text-[11px] text-[var(--text-tertiary)]'>
(unique)
@@ -176,7 +176,7 @@ export function EditRowModal({ isOpen, onClose, table, row, onSuccess }: EditRow
placeholder='{"key": "value"}'
rows={4}
className='font-mono text-[12px]'
required={!column.optional}
required={column.required}
/>
) : (
<Input
@@ -190,13 +190,13 @@ export function EditRowModal({ isOpen, onClose, table, row, onSuccess }: EditRow
}
placeholder={`Enter ${column.name}`}
className='h-[38px]'
required={!column.optional}
required={column.required}
/>
)}
<div className='text-[12px] text-[var(--text-tertiary)]'>
Type: {column.type}
{column.optional && ' (optional)'}
{!column.required && ' (optional)'}
</div>
</div>
))}

View File

@@ -435,7 +435,7 @@ export function TableDataViewer() {
<Badge variant='outline' size='sm'>
{column.type}
</Badge>
{!column.optional && (
{column.required && (
<span className='text-[10px] text-[var(--text-error)]'>*</span>
)}
</div>
@@ -675,9 +675,9 @@ export function TableDataViewer() {
</TableCell>
<TableCell className='text-[12px]'>
<div className='flex gap-[6px]'>
{column.optional && (
<Badge variant='gray' size='sm'>
optional
{column.required && (
<Badge variant='red' size='sm'>
required
</Badge>
)}
{column.unique && (
@@ -685,7 +685,7 @@ export function TableDataViewer() {
unique
</Badge>
)}
{!column.optional && !column.unique && (
{!column.required && !column.unique && (
<span className='text-[var(--text-muted)]'></span>
)}
</div>

View File

@@ -24,7 +24,7 @@ const logger = createLogger('CreateTableModal')
interface ColumnDefinition {
name: string
type: 'string' | 'number' | 'boolean' | 'date' | 'json'
optional: boolean
required: boolean
unique: boolean
}
@@ -48,14 +48,14 @@ export function CreateTableModal({ isOpen, onClose }: CreateTableModalProps) {
const [tableName, setTableName] = useState('')
const [description, setDescription] = useState('')
const [columns, setColumns] = useState<ColumnDefinition[]>([
{ name: '', type: 'string', optional: false, unique: false },
{ name: '', type: 'string', required: true, unique: false },
])
const [error, setError] = useState<string | null>(null)
const createTable = useCreateTable(workspaceId)
const handleAddColumn = () => {
setColumns([...columns, { name: '', type: 'string', optional: false, unique: false }])
setColumns([...columns, { name: '', type: 'string', required: true, unique: false }])
}
const handleRemoveColumn = (index: number) => {
@@ -110,7 +110,7 @@ export function CreateTableModal({ isOpen, onClose }: CreateTableModalProps) {
// Reset form
setTableName('')
setDescription('')
setColumns([{ name: '', type: 'string', optional: false, unique: false }])
setColumns([{ name: '', type: 'string', required: true, unique: false }])
setError(null)
onClose()
} catch (err) {
@@ -123,7 +123,7 @@ export function CreateTableModal({ isOpen, onClose }: CreateTableModalProps) {
// Reset form on close
setTableName('')
setDescription('')
setColumns([{ name: '', type: 'string', optional: false, unique: false }])
setColumns([{ name: '', type: 'string', required: true, unique: false }])
setError(null)
onClose()
}
@@ -202,7 +202,7 @@ export function CreateTableModal({ isOpen, onClose }: CreateTableModalProps) {
<div className='flex items-center gap-[10px] rounded-[6px] bg-[var(--bg-secondary)] px-[12px] py-[8px] text-[11px] font-semibold text-[var(--text-tertiary)]'>
<div className='flex-1'>Column Name</div>
<div className='w-[110px]'>Type</div>
<div className='w-[70px] text-center'>Optional</div>
<div className='w-[70px] text-center'>Required</div>
<div className='w-[70px] text-center'>Unique</div>
<div className='w-[36px]' />
</div>
@@ -239,12 +239,12 @@ export function CreateTableModal({ isOpen, onClose }: CreateTableModalProps) {
/>
</div>
{/* Optional Checkbox */}
{/* Required Checkbox */}
<div className='flex w-[70px] items-center justify-center'>
<Checkbox
checked={column.optional}
checked={column.required}
onCheckedChange={(checked) =>
handleColumnChange(index, 'optional', checked === true)
handleColumnChange(index, 'required', checked === true)
}
/>
</div>
@@ -277,9 +277,8 @@ export function CreateTableModal({ isOpen, onClose }: CreateTableModalProps) {
</div>
<p className='text-[12px] text-[var(--text-tertiary)]'>
Columns are <span className='font-medium'>required</span> by default. Check{' '}
<span className='font-medium'>optional</span> for nullable fields, or{' '}
<span className='font-medium'>unique</span> to prevent duplicates.
Mark columns as <span className='font-medium'>unique</span> to prevent duplicate
values (e.g., id, email)
</p>
</div>
</form>

View File

@@ -256,9 +256,9 @@ export function TableCard({ table, workspaceId }: TableCardProps) {
</TableCell>
<TableCell className='text-[12px]'>
<div className='flex gap-[6px]'>
{column.optional && (
<Badge variant='gray' size='sm'>
optional
{column.required && (
<Badge variant='red' size='sm'>
required
</Badge>
)}
{column.unique && (
@@ -266,7 +266,7 @@ export function TableCard({ table, workspaceId }: TableCardProps) {
unique
</Badge>
)}
{!column.optional && !column.unique && (
{!column.required && !column.unique && (
<span className='text-[var(--text-muted)]'></span>
)}
</div>

View File

@@ -4,7 +4,7 @@ import { COLUMN_TYPES, NAME_PATTERN, TABLE_LIMITS } from './constants'
export interface ColumnDefinition {
name: string
type: ColumnType
optional?: boolean
required?: boolean
unique?: boolean
}
@@ -150,8 +150,8 @@ export function validateRowAgainstSchema(
for (const column of schema.columns) {
const value = data[column.name]
// Check required fields (columns are required by default unless marked optional)
if (!column.optional && (value === undefined || value === null)) {
// Check required fields
if (column.required && (value === undefined || value === null)) {
errors.push(`Missing required field: ${column.name}`)
continue
}

View File

@@ -22,7 +22,7 @@ export type ColumnType = 'string' | 'number' | 'boolean' | 'date' | 'json'
export interface ColumnDefinition {
name: string
type: ColumnType
optional?: boolean
required?: boolean
unique?: boolean
}