refactor: remove get table full (#673)

* refactor: remove the functionality of fetching table content (view, field, record) from the API

* chore: public convertProjection method in record.service

* fix: multiple socket connections and resolve readonly user role confusion after snapshot changes
This commit is contained in:
Boris 2024-06-25 18:14:17 +08:00 committed by GitHub
parent d6dc4fccdc
commit a2f48f4597
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 96 additions and 132 deletions

View File

@ -552,7 +552,7 @@ export class RecordService {
return this.prismaService.txClient().$executeRawUnsafe(updateRecordSql);
}
private convertProjection(fieldKeys?: string[]) {
convertProjection(fieldKeys?: string[]) {
return fieldKeys?.reduce<Record<string, boolean>>((acc, cur) => {
acc[cur] = true;
return acc;

View File

@ -2,8 +2,6 @@
import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common';
import type { ITableFullVo, ITableListVo, ITableVo } from '@teable/openapi';
import {
getTableQuerySchema,
IGetTableQuery,
tableRoSchema,
ICreateTableWithDefault,
dbTableNameRoSchema,
@ -46,10 +44,9 @@ export class TableController {
@Get(':tableId')
async getTable(
@Param('baseId') baseId: string,
@Param('tableId') tableId: string,
@Query(new ZodValidationPipe(getTableQuerySchema)) query: IGetTableQuery
@Param('tableId') tableId: string
): Promise<ITableVo> {
return await this.tableOpenApiService.getTable(baseId, tableId, query);
return await this.tableOpenApiService.getTable(baseId, tableId);
}
@Permissions('table|read')

View File

@ -25,7 +25,6 @@ import type {
ICreateRecordsRo,
ICreateTableRo,
ICreateTableWithDefault,
IGetTableQuery,
ITableFullVo,
ITablePermissionVo,
ITableVo,
@ -161,11 +160,7 @@ export class TableOpenApiService {
return await this.tableService.createTable(baseId, tableRo);
}
async getTable(baseId: string, tableId: string, query: IGetTableQuery): Promise<ITableVo> {
const { viewId, fieldKeyType, includeContent } = query;
if (includeContent) {
return await this.tableService.getFullTable(baseId, tableId, viewId, fieldKeyType);
}
async getTable(baseId: string, tableId: string): Promise<ITableVo> {
return await this.tableService.getTableMeta(baseId, tableId);
}

View File

@ -1,7 +1,6 @@
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import type { IOtOperation, ISnapshotBase } from '@teable/core';
import {
FieldKeyType,
generateTableId,
getRandomString,
getUniqName,
@ -10,7 +9,7 @@ import {
} from '@teable/core';
import type { Prisma } from '@teable/db-main-prisma';
import { PrismaService } from '@teable/db-main-prisma';
import type { ICreateTableRo, ITableFullVo, ITableVo } from '@teable/openapi';
import type { ICreateTableRo, ITableVo } from '@teable/openapi';
import { Knex } from 'knex';
import { InjectModel } from 'nest-knexjs';
import { ClsService } from 'nestjs-cls';
@ -22,9 +21,6 @@ import type { IClsStore } from '../../types/cls';
import { convertNameToValidCharacter } from '../../utils/name-conversion';
import { Timing } from '../../utils/timing';
import { BatchService } from '../calculation/batch.service';
import { FieldService } from '../field/field.service';
import { RecordService } from '../record/record.service';
import { ViewService } from '../view/view.service';
@Injectable()
export class TableService implements IReadonlyAdapterService {
@ -34,9 +30,6 @@ export class TableService implements IReadonlyAdapterService {
private readonly cls: ClsService<IClsStore>,
private readonly prismaService: PrismaService,
private readonly batchService: BatchService,
private readonly viewService: ViewService,
private readonly fieldService: FieldService,
private readonly recordService: RecordService,
@InjectDbProvider() private readonly dbProvider: IDbProvider,
@InjectModel('CUSTOM_KNEX') private readonly knex: Knex
) {}
@ -198,32 +191,6 @@ export class TableService implements IReadonlyAdapterService {
};
}
async getFullTable(
baseId: string,
tableId: string,
viewId?: string,
fieldKeyType: FieldKeyType = FieldKeyType.Name
): Promise<ITableFullVo> {
const tableMeta = await this.getTableMeta(baseId, tableId);
const fields = await this.fieldService.getFieldsByQuery(tableId, { viewId });
const views = await this.viewService.getViews(tableId);
const { records } = await this.recordService.getRecords(tableId, {
viewId,
skip: 0,
take: 50,
fieldKeyType,
});
return {
...tableMeta,
description: tableMeta.description ?? undefined,
icon: tableMeta.icon ?? undefined,
fields,
views,
records,
};
}
async getDefaultViewId(tableId: string) {
const viewRaw = await this.prismaService.view.findFirst({
where: { tableId, deletedTime: null },

View File

@ -102,17 +102,21 @@ export class ShareDbAdapter extends ShareDb.DB {
callback: (error: any | null, ids: string[], extra?: any) => void
) {
try {
await this.cls.runWith(this.cls.get(), async () => {
this.cls.set('cookie', options.cookie);
this.cls.set('shareViewId', options.shareId);
const [docType, collectionId] = collection.split('_');
const queryResult = await this.getReadonlyService(docType as IdPrefix).getDocIdsByQuery(
collectionId,
query
);
callback(null, queryResult.ids, queryResult.extra);
});
await this.cls.runWith(
{
...this.cls.get(),
cookie: options.cookie,
shareViewId: options.shareId,
},
async () => {
const [docType, collectionId] = collection.split('_');
const queryResult = await this.getReadonlyService(docType as IdPrefix).getDocIdsByQuery(
collectionId,
query
);
callback(null, queryResult.ids, queryResult.extra);
}
);
} catch (e) {
this.logger.error(e);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -201,18 +205,23 @@ export class ShareDbAdapter extends ShareDb.DB {
options: any,
callback: (err: unknown, data?: Snapshot) => void
) {
await this.cls.runWith(this.cls.get(), async () => {
this.cls.set('cookie', options.agentCustom.cookie);
this.cls.set('shareViewId', options.agentCustom.shareId);
this.getSnapshotBulk(collection, [id], projection, options, (err, data) => {
if (err) {
callback(err);
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
callback(null, data![id]);
}
});
});
await this.cls.runWith(
{
...this.cls.get(),
cookie: options.agentCustom.cookie,
shareViewId: options.agentCustom.shareId,
},
async () => {
this.getSnapshotBulk(collection, [id], projection, options, (err, data) => {
if (err) {
callback(err);
} else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
callback(null, data![id]);
}
});
}
);
}
// Get operations between [from, to) non-inclusively. (Ie, the range should

View File

@ -10,7 +10,6 @@ import {
notify as apiNotify,
analyzeFile as apiAnalyzeFile,
importTableFromFile as apiImportTableFromFile,
getTableById as apiGetTableById,
createBase as apiCreateBase,
createSpace as apiCreateSpace,
deleteBase as apiDeleteBase,
@ -21,7 +20,7 @@ import {
import * as XLSX from 'xlsx';
import { CsvImporter } from '../src/features/import/open-api/import.class';
import { initApp, deleteTable } from './utils/init-app';
import { initApp, deleteTable, getTable as apiGetTableById } from './utils/init-app';
enum TestFileFormat {
'CSV' = 'csv',
@ -265,17 +264,11 @@ describe('OpenAPI ImportController (e2e)', () => {
name: field.name,
}));
const res = await apiGetTableById(baseId, table.data[0].id, {
includeContent: true,
});
await apiGetTableById(baseId, table.data[0].id);
bases.push([baseId, id]);
expect(createdFields).toEqual(assertHeaders);
expect(res).toMatchObject({
status: 200,
statusText: 'OK',
});
}
);
});
@ -349,9 +342,7 @@ describe('OpenAPI ImportController (e2e)', () => {
await delay(1000);
const {
data: { records },
} = await apiGetTableById(baseId, tableId, {
const { records } = await apiGetTableById(baseId, tableId, {
includeContent: true,
});

View File

@ -25,8 +25,6 @@ import type {
IRecordsVo,
IUpdateRecordRo,
ITableFullVo,
IGetTableQuery,
ITableVo,
} from '@teable/openapi';
import {
axios,
@ -163,17 +161,29 @@ export async function deleteTable(baseId: string, tableId: string, expectStatus?
}
}
type IMakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export async function getTable(
baseId: string,
tableId: string,
query: IGetTableQuery = {}
): Promise<ITableVo> {
const result = await apiGetTableById(baseId, tableId, query);
query?: { includeContent?: boolean; viewId?: string }
): Promise<IMakeOptional<ITableFullVo, 'records' | 'views' | 'fields'>> {
const result = await apiGetTableById(baseId, tableId);
if (query?.includeContent) {
const { records } = await getRecords(tableId);
const fields = await getFields(tableId, query.viewId);
const views = await getViews(tableId);
return {
...result.data,
records,
views,
fields,
};
}
return result.data;
}
async function getCookie(email: string, password: string) {
export async function getCookie(email: string, password: string) {
const sessionResponse = await apiSignin({ email, password });
return {
access_token: sessionResponse.data,

View File

@ -2,12 +2,11 @@
import type { INestApplication } from '@nestjs/common';
import type { IColumn, IFieldRo, IFieldVo, IViewRo } from '@teable/core';
import { FieldType, Relationship, ViewType } from '@teable/core';
import type { ICreateTableRo, ITableFullVo, ITableVo } from '@teable/openapi';
import type { ICreateTableRo, ITableFullVo } from '@teable/openapi';
import {
updateViewDescription,
updateViewName,
getViewFilterLinkRecords,
getTableById,
updateViewShareMeta,
enableShareView,
} from '@teable/openapi';
@ -21,6 +20,7 @@ import {
createTable,
getViews,
getView,
getTable,
} from './utils/init-app';
const defaultViews = [
@ -140,7 +140,7 @@ describe('OpenAPI ViewController (e2e)', () => {
});
describe('/api/table/{tableId}/view/:viewId/filter-link-records (GET)', () => {
let table: ITableVo;
let table: ITableFullVo;
let linkTable1: ITableFullVo;
let linkTable2: ITableFullVo;
@ -235,8 +235,7 @@ describe('OpenAPI ViewController (e2e)', () => {
records: linkTable2RecordRo,
});
const tableData = await getTableById(baseId, fullTable.id, { includeContent: true });
table = tableData.data;
table = (await getTable(baseId, fullTable.id, { includeContent: true })) as ITableFullVo;
});
afterAll(async () => {

View File

@ -1,4 +1,4 @@
import type { IFieldVo, IRecord } from '@teable/core';
import type { IFieldVo, IGetFieldsQuery, IRecord, IViewVo } from '@teable/core';
import { FieldKeyType } from '@teable/core';
import type {
AcceptInvitationLinkRo,
@ -11,6 +11,8 @@ import type {
ShareViewGetVo,
ITableFullVo,
ITableListVo,
IRecordsVo,
ITableVo,
} from '@teable/openapi';
import {
ACCEPT_INVITATION_LINK,
@ -18,10 +20,12 @@ import {
GET_BASE_ALL,
GET_DEFAULT_VIEW_ID,
GET_FIELD_LIST,
GET_RECORDS_URL,
GET_RECORD_URL,
GET_SPACE,
GET_TABLE,
GET_TABLE_LIST,
GET_VIEW_LIST,
SHARE_VIEW_GET,
SPACE_COLLABORATE_LIST,
UPDATE_NOTIFICATION_STATUS,
@ -38,9 +42,22 @@ export class SsrApi {
this.axios = getAxios();
}
async getTable(baseId: string, tableId: string, viewId?: string) {
return this.axios
.get<ITableFullVo>(urlBuilder(GET_TABLE, { baseId, tableId }), {
async getTable(baseId: string, tableId: string, viewId?: string): Promise<ITableFullVo> {
const { records } = await this.axios
.get<IRecordsVo>(urlBuilder(GET_RECORDS_URL, { baseId, tableId }), {
params: {
viewId,
fieldKeyType: FieldKeyType.Id,
},
})
.then(({ data }) => data);
const fields = await this.getFields(tableId, { viewId });
const views = await this.axios
.get<IViewVo[]>(urlBuilder(GET_VIEW_LIST, { tableId }))
.then(({ data }) => data);
const table = await this.axios
.get<ITableVo>(urlBuilder(GET_TABLE, { baseId, tableId }), {
params: {
includeContent: true,
viewId,
@ -48,11 +65,17 @@ export class SsrApi {
},
})
.then(({ data }) => data);
return {
...table,
records,
views,
fields,
};
}
async getFields(tableId: string) {
async getFields(tableId: string, query?: IGetFieldsQuery) {
return this.axios
.get<IFieldVo[]>(urlBuilder(GET_FIELD_LIST, { tableId }))
.get<IFieldVo[]>(urlBuilder(GET_FIELD_LIST, { tableId }), { params: query })
.then(({ data }) => data);
}

View File

@ -37,7 +37,7 @@ const InplaceFieldConfigPanel = (props: IInplaceFieldConfigPanel) => {
const { data: table } = useQuery({
queryKey: ReactQueryKeys.tableInfo(base.id, tableId),
queryFn: () => apiGetTableById(base.id, tableId, {}),
queryFn: () => apiGetTableById(base.id, tableId),
});
const { data: fields } = useQuery({

View File

@ -58,7 +58,7 @@ export const tableFullVoSchema = z
export type ITableFullVo = z.infer<typeof tableFullVoSchema>;
export const tableVoSchema = tableFullVoSchema.partial({
export const tableVoSchema = tableFullVoSchema.omit({
fields: true,
views: true,
records: true,

View File

@ -1,31 +1,11 @@
import type { RouteConfig } from '@asteasolutions/zod-to-openapi';
import { IdPrefix } from '@teable/core';
import { axios } from '../axios';
import { fieldKeyTypeRoSchema } from '../record';
import { registerRoute, urlBuilder } from '../utils';
import { z } from '../zod';
import { tableListVoSchema } from './create';
export type ITableListVo = z.infer<typeof tableListVoSchema>;
export const getTableQuerySchema = z.object({
viewId: z.string().startsWith(IdPrefix.View).optional().openapi({
description: 'Which view to get the data from.',
}),
includeContent: z
.string()
.or(z.boolean())
.transform(Boolean)
.pipe(z.boolean())
.optional()
.openapi({
description: 'If true return table content. including fields, views, first 50 records.',
}),
fieldKeyType: fieldKeyTypeRoSchema,
});
export type IGetTableQuery = z.infer<typeof getTableQuerySchema>;
export const GET_TABLE_LIST = '/base/{baseId}/table';
export const GetTableListRoute: RouteConfig = registerRoute({
@ -50,6 +30,6 @@ export const GetTableListRoute: RouteConfig = registerRoute({
tags: ['table'],
});
export const getTableList = async (baseId: string, params?: IGetTableQuery) => {
return axios.get<ITableListVo>(urlBuilder(GET_TABLE_LIST, { baseId }), { params });
export const getTableList = async (baseId: string) => {
return axios.get<ITableListVo>(urlBuilder(GET_TABLE_LIST, { baseId }));
};

View File

@ -4,9 +4,6 @@ import { registerRoute, urlBuilder } from '../utils';
import { z } from '../zod';
import type { ITableVo } from './create';
import { tableVoSchema } from './create';
import type { IGetTableQuery } from './get-list';
import { getTableQuerySchema } from './get-list';
export const GET_TABLE = '/base/{baseId}/table/{tableId}';
export const GetTableRoute: RouteConfig = registerRoute({
@ -18,7 +15,6 @@ export const GetTableRoute: RouteConfig = registerRoute({
baseId: z.string(),
tableId: z.string(),
}),
query: getTableQuerySchema,
},
responses: {
200: {
@ -33,14 +29,11 @@ export const GetTableRoute: RouteConfig = registerRoute({
tags: ['table'],
});
export const getTableById = async (baseId: string, tableId: string, query: IGetTableQuery) => {
export const getTableById = async (baseId: string, tableId: string) => {
return axios.get<ITableVo>(
urlBuilder(GET_TABLE, {
baseId,
tableId,
}),
{
params: query,
}
})
);
};