• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

teableio / teable / 10315240697

09 Aug 2024 07:14AM UTC coverage: 82.671% (-0.004%) from 82.675%
10315240697

Pull #807

github

web-flow
Merge 5c2c3e070 into 7701966d5
Pull Request #807: feat: new isUnrestricted parameter returned by base query interface

4399 of 4620 branches covered (95.22%)

2 of 7 new or added lines in 2 files covered. (28.57%)

1 existing line in 1 file now uncovered.

29292 of 35432 relevant lines covered (82.67%)

1242.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

50.88
/apps/nestjs-backend/src/features/base/base.service.ts
1
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
4✔
2
import type { BaseRole, SpaceRole } from '@teable/core';
4✔
3
import {
4✔
4
  ActionPrefix,
4✔
5
  RoleType,
4✔
6
  actionPrefixMap,
4✔
7
  generateBaseId,
4✔
8
  getPermissionMap,
4✔
9
  isUnrestrictedRole,
4✔
10
} from '@teable/core';
4✔
11
import { PrismaService } from '@teable/db-main-prisma';
4✔
12
import type {
4✔
13
  ICreateBaseFromTemplateRo,
4✔
14
  ICreateBaseRo,
4✔
15
  IDuplicateBaseRo,
4✔
16
  IUpdateBaseRo,
4✔
17
  IUpdateOrderRo,
4✔
18
} from '@teable/openapi';
4✔
19
import { pick } from 'lodash';
4✔
20
import { ClsService } from 'nestjs-cls';
4✔
21
import { IThresholdConfig, ThresholdConfig } from '../../configs/threshold.config';
4✔
22
import { InjectDbProvider } from '../../db-provider/db.provider';
4✔
23
import { IDbProvider } from '../../db-provider/db.provider.interface';
4✔
24
import type { IClsStore } from '../../types/cls';
4✔
25
import { updateOrder } from '../../utils/update-order';
4✔
26
import { PermissionService } from '../auth/permission.service';
4✔
27
import { CollaboratorService } from '../collaborator/collaborator.service';
4✔
28
import { BaseDuplicateService } from './base-duplicate.service';
4✔
29

4✔
30
@Injectable()
4✔
31
export class BaseService {
4✔
32
  private logger = new Logger(BaseService.name);
78✔
33

78✔
34
  constructor(
78✔
35
    private readonly prismaService: PrismaService,
78✔
36
    private readonly cls: ClsService<IClsStore>,
78✔
37
    private readonly collaboratorService: CollaboratorService,
78✔
38
    private readonly baseDuplicateService: BaseDuplicateService,
78✔
39
    private readonly permissionService: PermissionService,
78✔
40
    @InjectDbProvider() private readonly dbProvider: IDbProvider,
78✔
41
    @ThresholdConfig() private readonly thresholdConfig: IThresholdConfig
78✔
42
  ) {}
78✔
43

78✔
44
  async getBaseById(baseId: string) {
78✔
45
    const userId = this.cls.get('user.id');
×
46
    const { spaceIds, roleMap } =
×
47
      await this.collaboratorService.getCollaboratorsBaseAndSpaceArray(userId);
×
48

×
49
    const base = await this.prismaService.base.findFirst({
×
50
      select: {
×
51
        id: true,
×
52
        name: true,
×
53
        icon: true,
×
54
        spaceId: true,
×
55
      },
×
56
      where: {
×
57
        id: baseId,
×
58
        deletedTime: null,
×
59
        spaceId: {
×
60
          in: spaceIds,
×
61
        },
×
62
      },
×
63
    });
×
64
    if (!base) {
×
65
      throw new NotFoundException('Base not found');
×
66
    }
×
NEW
67
    const role = roleMap[base.id] || roleMap[base.spaceId];
×
68
    return {
×
69
      ...base,
×
NEW
70
      role: role,
×
NEW
71
      isUnrestricted: isUnrestrictedRole(role),
×
72
    };
×
73
  }
×
74

78✔
75
  async getAllBaseList() {
78✔
76
    const userId = this.cls.get('user.id');
×
77
    const { spaceIds, baseIds, roleMap } =
×
78
      await this.collaboratorService.getCollaboratorsBaseAndSpaceArray(userId);
×
79
    const baseList = await this.prismaService.base.findMany({
×
80
      select: {
×
81
        id: true,
×
82
        name: true,
×
83
        order: true,
×
84
        spaceId: true,
×
85
        icon: true,
×
86
      },
×
87
      where: {
×
88
        deletedTime: null,
×
89
        OR: [
×
90
          {
×
91
            id: {
×
92
              in: baseIds,
×
93
            },
×
94
          },
×
95
          {
×
96
            spaceId: {
×
97
              in: spaceIds,
×
98
            },
×
99
          },
×
100
        ],
×
101
      },
×
102
      orderBy: [{ spaceId: 'asc' }, { order: 'asc' }],
×
103
    });
×
104
    return baseList.map((base) => ({ ...base, role: roleMap[base.id] || roleMap[base.spaceId] }));
×
105
  }
×
106

78✔
107
  async getAccessBaseList() {
78✔
108
    const userId = this.cls.get('user.id');
×
109
    const accessTokenId = this.cls.get('accessTokenId');
×
110
    const { spaceIds, baseIds } =
×
111
      await this.collaboratorService.getCollaboratorsBaseAndSpaceArray(userId);
×
112

×
113
    if (accessTokenId) {
×
114
      const access = await this.prismaService.accessToken.findFirst({
×
115
        select: {
×
116
          baseIds: true,
×
117
          spaceIds: true,
×
118
        },
×
119
        where: {
×
120
          id: accessTokenId,
×
121
          userId,
×
122
        },
×
123
      });
×
124
      if (!access) {
×
125
        return [];
×
126
      }
×
127
      spaceIds.push(...(access.spaceIds || []));
×
128
      baseIds.push(...(access.baseIds || []));
×
129
    }
×
130

×
131
    return this.prismaService.base.findMany({
×
132
      select: {
×
133
        id: true,
×
134
        name: true,
×
135
      },
×
136
      where: {
×
137
        deletedTime: null,
×
138
        OR: [
×
139
          {
×
140
            id: {
×
141
              in: baseIds,
×
142
            },
×
143
          },
×
144
          {
×
145
            spaceId: {
×
146
              in: spaceIds,
×
147
            },
×
148
          },
×
149
        ],
×
150
      },
×
151
      orderBy: [{ spaceId: 'asc' }, { order: 'asc' }],
×
152
    });
×
153
  }
×
154

78✔
155
  private async getMaxOrder(spaceId: string) {
78✔
156
    const spaceAggregate = await this.prismaService.base.aggregate({
25✔
157
      where: { spaceId, deletedTime: null },
25✔
158
      _max: { order: true },
25✔
159
    });
25✔
160
    return spaceAggregate._max.order || 0;
25✔
161
  }
25✔
162

78✔
163
  async createBase(createBaseRo: ICreateBaseRo) {
78✔
164
    const userId = this.cls.get('user.id');
25✔
165
    const { name, spaceId } = createBaseRo;
25✔
166

25✔
167
    return this.prismaService.$transaction(async (prisma) => {
25✔
168
      const order = (await this.getMaxOrder(spaceId)) + 1;
25✔
169

25✔
170
      const base = await prisma.base.create({
25✔
171
        data: {
25✔
172
          id: generateBaseId(),
25✔
173
          name: name || 'Untitled Base',
25✔
174
          spaceId,
25✔
175
          order,
25✔
176
          createdBy: userId,
25✔
177
        },
25✔
178
        select: {
25✔
179
          id: true,
25✔
180
          name: true,
25✔
181
          icon: true,
25✔
182
          spaceId: true,
25✔
183
        },
25✔
184
      });
25✔
185

25✔
186
      const sqlList = this.dbProvider.createSchema(base.id);
25✔
187
      if (sqlList) {
25✔
188
        for (const sql of sqlList) {
15✔
189
          await prisma.$executeRawUnsafe(sql);
30✔
190
        }
30✔
191
      }
15✔
192

25✔
193
      return base;
25✔
194
    });
25✔
195
  }
25✔
196

78✔
197
  async updateBase(baseId: string, updateBaseRo: IUpdateBaseRo) {
78✔
198
    const userId = this.cls.get('user.id');
×
199

×
200
    return this.prismaService.base.update({
×
201
      data: {
×
202
        ...updateBaseRo,
×
203
        lastModifiedBy: userId,
×
204
      },
×
205
      select: {
×
206
        id: true,
×
207
        name: true,
×
208
        spaceId: true,
×
209
      },
×
210
      where: {
×
211
        id: baseId,
×
212
        deletedTime: null,
×
213
      },
×
214
    });
×
215
  }
×
216

78✔
217
  async shuffle(spaceId: string) {
78✔
218
    const bases = await this.prismaService.base.findMany({
×
219
      where: { spaceId, deletedTime: null },
×
220
      select: { id: true },
×
221
      orderBy: { order: 'asc' },
×
222
    });
×
223

×
224
    this.logger.log(`lucky base shuffle! ${spaceId}`, 'shuffle');
×
225

×
226
    await this.prismaService.$tx(async (prisma) => {
×
227
      for (let i = 0; i < bases.length; i++) {
×
228
        const base = bases[i];
×
229
        await prisma.base.update({
×
230
          data: { order: i },
×
231
          where: { id: base.id },
×
232
        });
×
233
      }
×
234
    });
×
235
  }
×
236

78✔
237
  async updateOrder(baseId: string, orderRo: IUpdateOrderRo) {
78✔
238
    const { anchorId, position } = orderRo;
8✔
239

8✔
240
    const base = await this.prismaService.base
8✔
241
      .findFirstOrThrow({
8✔
242
        select: { spaceId: true, order: true, id: true },
8✔
243
        where: { id: baseId, deletedTime: null },
8✔
244
      })
8✔
245
      .catch(() => {
8✔
246
        throw new NotFoundException(`Base ${baseId} not found`);
×
247
      });
×
248

8✔
249
    const anchorBase = await this.prismaService.base
8✔
250
      .findFirstOrThrow({
8✔
251
        select: { order: true, id: true },
8✔
252
        where: { spaceId: base.spaceId, id: anchorId, deletedTime: null },
8✔
253
      })
8✔
254
      .catch(() => {
8✔
255
        throw new NotFoundException(`Anchor ${anchorId} not found`);
×
256
      });
×
257

8✔
258
    await updateOrder({
8✔
259
      query: base.spaceId,
8✔
260
      position,
8✔
261
      item: base,
8✔
262
      anchorItem: anchorBase,
8✔
263
      getNextItem: async (whereOrder, align) => {
8✔
264
        return this.prismaService.base.findFirst({
8✔
265
          select: { order: true, id: true },
8✔
266
          where: {
8✔
267
            spaceId: base.spaceId,
8✔
268
            deletedTime: null,
8✔
269
            order: whereOrder,
8✔
270
          },
8✔
271
          orderBy: { order: align },
8✔
272
        });
8✔
273
      },
8✔
274
      update: async (_, id, data) => {
8✔
275
        await this.prismaService.base.update({
8✔
276
          data: { order: data.newOrder },
8✔
277
          where: { id },
8✔
278
        });
8✔
279
      },
8✔
280
      shuffle: this.shuffle.bind(this),
8✔
281
    });
8✔
282
  }
8✔
283

78✔
284
  async deleteBase(baseId: string) {
78✔
285
    const userId = this.cls.get('user.id');
19✔
286

19✔
287
    await this.prismaService.base.update({
19✔
288
      data: { deletedTime: new Date(), lastModifiedBy: userId },
19✔
289
      where: { id: baseId, deletedTime: null },
19✔
290
    });
19✔
291
  }
19✔
292

78✔
293
  async duplicateBase(duplicateBaseRo: IDuplicateBaseRo) {
78✔
294
    // permission check, base read permission
5✔
295
    await this.checkBaseReadPermission(duplicateBaseRo.fromBaseId);
5✔
296
    return await this.prismaService.$tx(
5✔
297
      async () => {
5✔
298
        return await this.baseDuplicateService.duplicate(duplicateBaseRo);
5✔
299
      },
5✔
300
      { timeout: this.thresholdConfig.bigTransactionTimeout }
5✔
301
    );
5✔
302
  }
5✔
303

78✔
304
  private async checkBaseReadPermission(baseId: string) {
78✔
305
    // First check if the user has the base read permission
5✔
306
    await this.permissionService.validPermissions(baseId, ['base|read']);
5✔
307

5✔
308
    // Then check the token permissions if the request was made with a token
5✔
309
    const accessTokenId = this.cls.get('accessTokenId');
5✔
310
    if (accessTokenId) {
5✔
311
      await this.permissionService.validPermissions(baseId, ['base|read'], accessTokenId);
×
312
    }
×
313
  }
5✔
314

78✔
315
  async createBaseFromTemplate(createBaseFromTemplateRo: ICreateBaseFromTemplateRo) {
78✔
316
    const { spaceId, templateId, withRecords } = createBaseFromTemplateRo;
×
317
    return await this.prismaService.$tx(async () => {
×
318
      return await this.baseDuplicateService.duplicate({
×
319
        fromBaseId: templateId,
×
320
        spaceId,
×
321
        withRecords,
×
322
      });
×
323
    });
×
324
  }
×
325

78✔
326
  async getPermission(baseId: string) {
78✔
327
    const { role } = await this.getBaseById(baseId);
×
328
    return this.getPermissionByRole(role);
×
329
  }
×
330

78✔
331
  async getPermissionByRole(role: SpaceRole | BaseRole) {
78✔
332
    const permissionMap = getPermissionMap(RoleType.Base, role);
×
333
    return pick(permissionMap, [
×
334
      ...actionPrefixMap[ActionPrefix.Table],
×
335
      ...actionPrefixMap[ActionPrefix.Base],
×
336
      ...actionPrefixMap[ActionPrefix.Automation],
×
337
      ...actionPrefixMap[ActionPrefix.RecordHistory],
×
338
    ]);
×
339
  }
×
340
}
78✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc