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

teableio / teable / 8356130399

20 Mar 2024 08:52AM UTC coverage: 28.231% (+0.06%) from 28.17%
8356130399

push

github

web-flow
refactor: row order (#473)

* refactor: row order

* fix: sqlite test

2122 of 3238 branches covered (65.53%)

194 of 588 new or added lines in 38 files covered. (32.99%)

2 existing lines in 2 files now uncovered.

25811 of 91428 relevant lines covered (28.23%)

5.58 hits per line

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

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

1✔
21
@Injectable()
1✔
22
export class BaseService {
1✔
23
  private logger = new Logger(BaseService.name);
3✔
24

3✔
25
  constructor(
3✔
26
    private readonly prismaService: PrismaService,
3✔
27
    private readonly cls: ClsService<IClsStore>,
3✔
28
    private readonly collaboratorService: CollaboratorService,
3✔
29
    private readonly baseDuplicateService: BaseDuplicateService,
3✔
30
    private readonly permissionService: PermissionService,
3✔
31
    @InjectDbProvider() private readonly dbProvider: IDbProvider,
3✔
32
    @ThresholdConfig() private readonly thresholdConfig: IThresholdConfig
3✔
33
  ) {}
3✔
34

3✔
35
  async getBaseById(baseId: string) {
3✔
36
    const userId = this.cls.get('user.id');
×
37
    const { spaceIds, roleMap } =
×
38
      await this.collaboratorService.getCollaboratorsBaseAndSpaceArray(userId);
×
39

×
40
    const base = await this.prismaService.base.findFirst({
×
41
      select: {
×
42
        id: true,
×
43
        name: true,
×
44
        icon: true,
×
45
        spaceId: true,
×
46
      },
×
47
      where: {
×
48
        id: baseId,
×
49
        deletedTime: null,
×
50
        spaceId: {
×
51
          in: spaceIds,
×
52
        },
×
53
      },
×
54
    });
×
55
    if (!base) {
×
56
      throw new NotFoundException('Base not found');
×
57
    }
×
58
    return {
×
59
      ...base,
×
60
      role: roleMap[base.id] || roleMap[base.spaceId],
×
61
    };
×
62
  }
×
63

3✔
64
  async getAllBaseList() {
3✔
65
    const userId = this.cls.get('user.id');
×
66
    const { spaceIds, baseIds, roleMap } =
×
67
      await this.collaboratorService.getCollaboratorsBaseAndSpaceArray(userId);
×
68
    const baseList = await this.prismaService.base.findMany({
×
69
      select: {
×
70
        id: true,
×
71
        name: true,
×
72
        order: true,
×
73
        spaceId: true,
×
74
        icon: true,
×
75
      },
×
76
      where: {
×
77
        deletedTime: null,
×
78
        OR: [
×
79
          {
×
80
            id: {
×
81
              in: baseIds,
×
82
            },
×
83
          },
×
84
          {
×
85
            spaceId: {
×
86
              in: spaceIds,
×
87
            },
×
88
          },
×
89
        ],
×
90
      },
×
91
      orderBy: [{ spaceId: 'asc' }, { order: 'asc' }],
×
92
    });
×
93
    return baseList.map((base) => ({ ...base, role: roleMap[base.id] || roleMap[base.spaceId] }));
×
94
  }
×
95

3✔
96
  private async getMaxOrder(spaceId: string) {
3✔
97
    const spaceAggregate = await this.prismaService.base.aggregate({
×
98
      where: { spaceId, deletedTime: null },
×
99
      _max: { order: true },
×
100
    });
×
101
    return spaceAggregate._max.order || 0;
×
102
  }
×
103

3✔
104
  async createBase(createBaseRo: ICreateBaseRo) {
3✔
105
    const userId = this.cls.get('user.id');
×
106
    const { name, spaceId } = createBaseRo;
×
107

×
108
    return this.prismaService.$transaction(async (prisma) => {
×
109
      const order = (await this.getMaxOrder(spaceId)) + 1;
×
110

×
111
      const base = await prisma.base.create({
×
112
        data: {
×
113
          id: generateBaseId(),
×
114
          name: name || 'Untitled Base',
×
115
          spaceId,
×
116
          order,
×
117
          createdBy: userId,
×
118
        },
×
119
        select: {
×
120
          id: true,
×
121
          name: true,
×
122
          icon: true,
×
123
          spaceId: true,
×
124
        },
×
125
      });
×
126

×
127
      const sqlList = this.dbProvider.createSchema(base.id);
×
128
      if (sqlList) {
×
129
        for (const sql of sqlList) {
×
130
          await prisma.$executeRawUnsafe(sql);
×
131
        }
×
132
      }
×
133

×
134
      return base;
×
135
    });
×
136
  }
×
137

3✔
138
  async updateBase(baseId: string, updateBaseRo: IUpdateBaseRo) {
3✔
139
    const userId = this.cls.get('user.id');
×
140

×
141
    return this.prismaService.base.update({
×
142
      data: {
×
143
        ...updateBaseRo,
×
144
        lastModifiedBy: userId,
×
145
      },
×
146
      select: {
×
147
        id: true,
×
148
        name: true,
×
149
        spaceId: true,
×
150
      },
×
151
      where: {
×
152
        id: baseId,
×
153
        deletedTime: null,
×
154
      },
×
155
    });
×
156
  }
×
157

3✔
158
  async shuffle(spaceId: string) {
3✔
159
    const bases = await this.prismaService.base.findMany({
×
160
      where: { spaceId, deletedTime: null },
×
161
      select: { id: true },
×
162
      orderBy: { order: 'asc' },
×
163
    });
×
164

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

×
167
    await this.prismaService.$tx(async (prisma) => {
×
168
      for (let i = 0; i < bases.length; i++) {
×
169
        const base = bases[i];
×
170
        await prisma.base.update({
×
171
          data: { order: i },
×
172
          where: { id: base.id },
×
173
        });
×
174
      }
×
175
    });
×
176
  }
×
177

3✔
178
  async updateOrder(baseId: string, orderRo: IUpdateOrderRo) {
3✔
179
    const { anchorId, position } = orderRo;
×
180

×
181
    const base = await this.prismaService.base
×
182
      .findFirstOrThrow({
×
183
        select: { spaceId: true, order: true, id: true },
×
184
        where: { id: baseId, deletedTime: null },
×
185
      })
×
186
      .catch(() => {
×
187
        throw new NotFoundException(`Base ${baseId} not found`);
×
188
      });
×
189

×
190
    const anchorBase = await this.prismaService.base
×
191
      .findFirstOrThrow({
×
192
        select: { order: true, id: true },
×
193
        where: { spaceId: base.spaceId, id: anchorId, deletedTime: null },
×
194
      })
×
195
      .catch(() => {
×
196
        throw new NotFoundException(`Anchor ${anchorId} not found`);
×
197
      });
×
198

×
199
    await updateOrder({
×
200
      parentId: base.spaceId,
×
201
      position,
×
202
      item: base,
×
203
      anchorItem: anchorBase,
×
204
      getNextItem: async (whereOrder, align) => {
×
205
        return this.prismaService.base.findFirst({
×
206
          select: { order: true, id: true },
×
207
          where: {
×
208
            spaceId: base.spaceId,
×
209
            deletedTime: null,
×
210
            order: whereOrder,
×
211
          },
×
212
          orderBy: { order: align },
×
213
        });
×
214
      },
×
NEW
215
      update: async (_, id, data) => {
×
216
        await this.prismaService.base.update({
×
217
          data: { order: data.newOrder },
×
218
          where: { id },
×
219
        });
×
220
      },
×
221
      shuffle: this.shuffle.bind(this),
×
222
    });
×
223
  }
×
224

3✔
225
  async deleteBase(baseId: string) {
3✔
226
    const userId = this.cls.get('user.id');
×
227

×
228
    await this.prismaService.base.update({
×
229
      data: { deletedTime: new Date(), lastModifiedBy: userId },
×
230
      where: { id: baseId, deletedTime: null },
×
231
    });
×
232
  }
×
233

3✔
234
  async duplicateBase(duplicateBaseRo: IDuplicateBaseRo) {
3✔
235
    // permission check, base read permission
×
236
    await this.checkBaseReadPermission(duplicateBaseRo.fromBaseId);
×
237
    return await this.prismaService.$tx(
×
238
      async () => {
×
239
        return await this.baseDuplicateService.duplicate(duplicateBaseRo);
×
240
      },
×
241
      { timeout: this.thresholdConfig.bigTransactionTimeout }
×
242
    );
×
243
  }
×
244

3✔
245
  private async checkBaseReadPermission(baseId: string) {
3✔
246
    // First check if the user has the base read permission
×
247
    await this.permissionService.checkPermissionByBaseId(baseId, ['base|read']);
×
248

×
249
    // Then check the token permissions if the request was made with a token
×
250
    const accessTokenId = this.cls.get('accessTokenId');
×
251
    if (accessTokenId) {
×
252
      await this.permissionService.checkPermissionByAccessToken(baseId, accessTokenId, [
×
253
        'base|read',
×
254
      ]);
×
255
    }
×
256
  }
×
257

3✔
258
  async createBaseFromTemplate(createBaseFromTemplateRo: ICreateBaseFromTemplateRo) {
3✔
259
    const { spaceId, templateId, withRecords } = createBaseFromTemplateRo;
×
260
    return await this.prismaService.$tx(async () => {
×
261
      return await this.baseDuplicateService.duplicate({
×
262
        fromBaseId: templateId,
×
263
        spaceId,
×
264
        withRecords,
×
265
      });
×
266
    });
×
267
  }
×
268
}
3✔
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