mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
refactor(API): Migrate GET data-tables columns endpoint to PublicApiController (no-changelog) (#39772)
This commit is contained in:
parent
80a1596a3d
commit
bd2d361afc
|
|
@ -62,3 +62,9 @@ export class CreateDataTablePublicDto extends Z.class({
|
|||
export class UpdateDataTablePublicDto extends Z.class({
|
||||
name: dataTableNameSchema.openapi(updateDataTableFieldDocs.name),
|
||||
}) {}
|
||||
|
||||
const dataTableListColumnPublicSchema = dataTableColumnPublicSchema.extend({
|
||||
dataTableId: z.string().openapi(dataTableColumnFieldDocs.dataTableId),
|
||||
});
|
||||
|
||||
export class DataTableColumnListPublicDto extends Z.array(dataTableListColumnPublicSchema) {}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export const dataTableColumnFieldDocs = {
|
|||
index: { type: 'integer', description: 'Column position' },
|
||||
createdAt: { description: 'Timestamp when the column was created' },
|
||||
updatedAt: { description: 'Timestamp when the column was last updated' },
|
||||
dataTableId: { description: 'ID of the data table this column belongs to' },
|
||||
} as const satisfies Record<string, ZodOpenAPIMetadata>;
|
||||
|
||||
export const dataTableListFieldDocs = {
|
||||
|
|
|
|||
|
|
@ -422,6 +422,7 @@ export { CreateDataTableDto } from './data-table/create-data-table.dto';
|
|||
export { UpdateDataTableDto } from './data-table/update-data-table.dto';
|
||||
export {
|
||||
CreateDataTablePublicDto,
|
||||
DataTableColumnListPublicDto,
|
||||
DataTableListPublicDto,
|
||||
DataTablePublicDto,
|
||||
UpdateDataTablePublicDto,
|
||||
|
|
|
|||
|
|
@ -159,8 +159,6 @@ export declare namespace DataTableRequest {
|
|||
}
|
||||
>;
|
||||
|
||||
type ListColumns = AuthenticatedRequest<{ dataTableId: string }, {}, {}, {}>;
|
||||
|
||||
type CreateColumn = AuthenticatedRequest<{ dataTableId: string }, {}, AddDataTableColumnDto, {}>;
|
||||
|
||||
type DeleteColumn = AuthenticatedRequest<{ dataTableId: string; columnId: string }, {}, {}, {}>;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
CreateDataTablePublicDto,
|
||||
DataTableColumnListPublicDto,
|
||||
DataTableListPublicDto,
|
||||
DataTablePublicDto,
|
||||
PublicApiListDataTableQueryDto,
|
||||
|
|
@ -31,6 +32,7 @@ import { ConflictError } from '@/errors/response-errors/conflict.error';
|
|||
import { ForbiddenError } from '@/errors/response-errors/forbidden.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
import { DataTableAggregateService } from '@/modules/data-table/data-table-aggregate.service';
|
||||
import type { DataTableColumn } from '@/modules/data-table/data-table-column.entity';
|
||||
import type { DataTable } from '@/modules/data-table/data-table.entity';
|
||||
import { DataTableService } from '@/modules/data-table/data-table.service';
|
||||
import { DataTableAccessDeniedError } from '@/modules/data-table/errors/data-table-access-denied.error';
|
||||
|
|
@ -83,6 +85,18 @@ const toDataTablePublicDto = (dataTable: DataTable, sizeBytes: number): DataTabl
|
|||
sizeBytes,
|
||||
});
|
||||
|
||||
const toDataTableColumnPublicDto = (
|
||||
column: DataTableColumn,
|
||||
): DataTableColumnListPublicDto[number] => ({
|
||||
id: column.id,
|
||||
name: column.name,
|
||||
dataTableId: column.dataTableId,
|
||||
type: column.type,
|
||||
index: column.index,
|
||||
createdAt: column.createdAt.toISOString(),
|
||||
updatedAt: column.updatedAt.toISOString(),
|
||||
});
|
||||
|
||||
@PublicApiController('/data-tables')
|
||||
export class DataTablesPublicController {
|
||||
constructor(
|
||||
|
|
@ -232,6 +246,25 @@ export class DataTablesPublicController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:dataTableId/columns')
|
||||
@ApiKeyScope('dataTableColumn:read')
|
||||
@ProjectScope('dataTable:readColumn')
|
||||
@ApiSummary('List columns of a data table')
|
||||
@ApiDescription('Retrieve all columns for a specific data table.')
|
||||
@ApiTags(tags)
|
||||
@ApiResponse(200, DataTableColumnListPublicDto)
|
||||
@ApiErrorResponse(404)
|
||||
async listDataTableColumns(
|
||||
_req: AuthenticatedRequest,
|
||||
_res: Response,
|
||||
@Param('dataTableId', dataTableIdParamSchema) dataTableId: string,
|
||||
): Promise<DataTableColumnListPublicDto> {
|
||||
const projectId = await this.dataTableService.getProjectIdForDataTable(dataTableId);
|
||||
const columns = await this.dataTableService.getColumns(dataTableId, projectId);
|
||||
|
||||
return columns.map(toDataTableColumnPublicDto);
|
||||
}
|
||||
|
||||
private async withSize(dataTable: DataTable): Promise<DataTablePublicDto> {
|
||||
const sizes = await this.dataTableService.getCachedSizeBytesByIds([dataTable.id]);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,23 +23,12 @@ const handleError = (error: unknown) => {
|
|||
};
|
||||
|
||||
type DataTableColumnsHandlers = {
|
||||
listDataTableColumns: PublicAPIEndpoint<DataTableRequest.ListColumns>;
|
||||
createDataTableColumn: PublicAPIEndpoint<DataTableRequest.CreateColumn>;
|
||||
deleteDataTableColumn: PublicAPIEndpoint<DataTableRequest.DeleteColumn>;
|
||||
updateDataTableColumn: PublicAPIEndpoint<DataTableRequest.UpdateColumn>;
|
||||
};
|
||||
|
||||
const dataTableColumnsHandlers: DataTableColumnsHandlers = {
|
||||
listDataTableColumns: [
|
||||
publicApiScope('dataTableColumn:read'),
|
||||
projectScope('dataTable:readColumn', 'dataTable'),
|
||||
async (req, res) => {
|
||||
const { dataTableId } = req.params;
|
||||
const projectId = await Container.get(DataTableService).getProjectIdForDataTable(dataTableId);
|
||||
return res.json(await Container.get(DataTableService).getColumns(dataTableId, projectId));
|
||||
},
|
||||
],
|
||||
|
||||
createDataTableColumn: [
|
||||
publicApiScope('dataTableColumn:create'),
|
||||
projectScope('dataTable:writeColumn', 'dataTable'),
|
||||
|
|
|
|||
|
|
@ -1,30 +1,3 @@
|
|||
get:
|
||||
x-eov-operation-id: listDataTableColumns
|
||||
x-required-scope: dataTableColumn:read
|
||||
x-eov-operation-handler: v1/handlers/data-tables/data-tables.columns.handler
|
||||
tags:
|
||||
- DataTable
|
||||
summary: List columns of a data table
|
||||
description: Retrieve all columns for a specific data table.
|
||||
operationId: list-data-table-columns
|
||||
parameters:
|
||||
- $ref: '../schemas/parameters/dataTableId.yml'
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully retrieved columns
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '../schemas/dataTableColumn.yml'
|
||||
'401':
|
||||
$ref: '../../../../shared/spec/responses/unauthorized.yml'
|
||||
'404':
|
||||
$ref: '../../../../shared/spec/responses/notFound.yml'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
|
||||
post:
|
||||
x-eov-operation-id: createDataTableColumn
|
||||
x-required-scope: dataTableColumn:create
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
operationId: listDataTableColumns
|
||||
tags:
|
||||
- DataTable
|
||||
summary: List columns of a data table
|
||||
description: Retrieve all columns for a specific data table.
|
||||
x-required-scope: dataTableColumn:read
|
||||
x-eov-operation-id: unreachable
|
||||
x-eov-operation-handler: v1/handlers/decorator-routed.handler
|
||||
x-decorator-routed: true
|
||||
parameters:
|
||||
- schema:
|
||||
type: string
|
||||
format: nanoid
|
||||
pattern: ^[A-Za-z0-9]{16}$
|
||||
required: true
|
||||
description: The ID of the data table
|
||||
name: dataTableId
|
||||
in: path
|
||||
responses:
|
||||
'200':
|
||||
description: Operation successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: Column ID
|
||||
name:
|
||||
type: string
|
||||
description: Column name
|
||||
type:
|
||||
type: string
|
||||
description: Column data type
|
||||
enum:
|
||||
- string
|
||||
- number
|
||||
- boolean
|
||||
- date
|
||||
index:
|
||||
type: integer
|
||||
description: Column position
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when the column was created
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp when the column was last updated
|
||||
dataTableId:
|
||||
type: string
|
||||
description: ID of the data table this column belongs to
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- type
|
||||
- index
|
||||
- createdAt
|
||||
- updatedAt
|
||||
- dataTableId
|
||||
'401':
|
||||
$ref: ../../../../shared/spec/responses/unauthorized.yml
|
||||
'403':
|
||||
$ref: ../../../../shared/spec/responses/forbidden.yml
|
||||
'404':
|
||||
$ref: ../../../../shared/spec/responses/notFound.yml
|
||||
|
|
@ -74,6 +74,9 @@ paths:
|
|||
$ref: ./handlers/data-tables/spec/paths/updateDataTable.generated.yml
|
||||
delete:
|
||||
$ref: ./handlers/data-tables/spec/paths/deleteDataTable.generated.yml
|
||||
/data-tables/{dataTableId}/columns:
|
||||
get:
|
||||
$ref: ./handlers/data-tables/spec/paths/listDataTableColumns.generated.yml
|
||||
/discover:
|
||||
get:
|
||||
$ref: ./handlers/discover/spec/paths/getDiscover.generated.yml
|
||||
|
|
|
|||
|
|
@ -2079,6 +2079,25 @@ describe('GET /data-tables/:dataTableId/columns', () => {
|
|||
testWithAPIKey('get', '/data-tables/123/columns', 'abcXYZ'),
|
||||
);
|
||||
|
||||
test('should reject listing without dataTableColumn:read', async () => {
|
||||
const dataTable = await createDataTable(ownerPersonalProject, {
|
||||
name: 'scope-columns-table',
|
||||
columns: [{ name: 'col1', type: 'string' }],
|
||||
});
|
||||
const unscopedAgent = await createUnscopedAgent();
|
||||
|
||||
const response = await unscopedAgent.get(`/data-tables/${dataTable.id}/columns`);
|
||||
|
||||
expect(response.statusCode).toBe(403);
|
||||
});
|
||||
|
||||
test('should reject a malformed data table id', async () => {
|
||||
const response = await authOwnerAgent.get('/data-tables/not-a-nanoid/columns');
|
||||
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.body).toHaveProperty('message');
|
||||
});
|
||||
|
||||
test('should list columns for a data table', async () => {
|
||||
const dataTable = await createDataTable(ownerPersonalProject, {
|
||||
name: 'columns-test',
|
||||
|
|
@ -2086,19 +2105,54 @@ describe('GET /data-tables/:dataTableId/columns', () => {
|
|||
{ name: 'name', type: 'string' },
|
||||
{ name: 'age', type: 'number' },
|
||||
{ name: 'active', type: 'boolean' },
|
||||
{ name: 'signedUpAt', type: 'date' },
|
||||
],
|
||||
});
|
||||
|
||||
const response = await authOwnerAgent.get(`/data-tables/${dataTable.id}/columns`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
expect(response.body).toHaveLength(3);
|
||||
expect(response.body[0]).toHaveProperty('id');
|
||||
expect(response.body[0]).toHaveProperty('name');
|
||||
expect(response.body[0]).toHaveProperty('type');
|
||||
expect(response.body[0]).toHaveProperty('index');
|
||||
expect(response.body[0]).toHaveProperty('dataTableId', dataTable.id);
|
||||
|
||||
// Columns aren't guaranteed to come back in index order, so sort before comparing.
|
||||
const columns = [...response.body].sort((a, b) => a.index - b.index);
|
||||
expect(columns).toStrictEqual([
|
||||
{
|
||||
id: expect.any(String),
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
index: 0,
|
||||
dataTableId: dataTable.id,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
{
|
||||
id: expect.any(String),
|
||||
name: 'age',
|
||||
type: 'number',
|
||||
index: 1,
|
||||
dataTableId: dataTable.id,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
{
|
||||
id: expect.any(String),
|
||||
name: 'active',
|
||||
type: 'boolean',
|
||||
index: 2,
|
||||
dataTableId: dataTable.id,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
{
|
||||
id: expect.any(String),
|
||||
name: 'signedUpAt',
|
||||
type: 'date',
|
||||
index: 3,
|
||||
dataTableId: dataTable.id,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return 404 for non-existing data table', async () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user