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

GrottoCenter / grottocenter-api / 10996210778

23 Sep 2024 02:17PM UTC coverage: 46.158% (-2.8%) from 48.952%
10996210778

Pull #1306

github

vmarseguerra
feat(entities): adds delete / restore for document
Pull Request #1306: Adds delete / restore for entrance sub entities

740 of 2203 branches covered (33.59%)

Branch coverage included in aggregate %.

526 of 1293 new or added lines in 114 files covered. (40.68%)

23 existing lines in 18 files now uncovered.

2462 of 4734 relevant lines covered (52.01%)

4.5 hits per line

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

57.43
/api/services/CaveService.js
1
const CommonService = require('./CommonService');
3✔
2
const DocumentService = require('./DocumentService');
3✔
3
const DescriptionService = require('./DescriptionService');
3✔
4
const NameService = require('./NameService');
3✔
5
const ElasticsearchService = require('./ElasticsearchService');
3✔
6
const NotificationService = require('./NotificationService');
3✔
7
const RecentChangeService = require('./RecentChangeService');
3✔
8

9
const GET_CUMULATED_LENGTH = `
3✔
10
  SELECT SUM(c.length) as sum_length, COUNT(c.length) as nb_data
11
  FROM t_entrance e
12
  JOIN t_cave c ON e.id_cave = c.id
13
  WHERE c.length IS NOT NULL
14
  AND c.is_deleted = false
15
  AND e.is_deleted = false
16
`;
17

18
module.exports = {
3✔
19
  /**
20
   * @param {Array<Object>} caves caves to set
21
   *
22
   * @returns {Promise} the caves with their attribute "entrances" completed
23
   */
24
  setEntrances: async (caves) => {
25
    const entrances = await TEntrance.find()
8✔
26
      .where({ cave: { in: caves.map((c) => c.id) } })
5✔
27
      .populate('names');
28
    for (const cave of caves) {
8✔
29
      cave.entrances = entrances.filter((e) => e.cave === cave.id);
12✔
30
    }
31
  },
32

33
  /**
34
   *
35
   * @param {Object} req
36
   * @param {Object} cleanedData cave-only related data
37
   * @param {Object} nameData name data (should contain an author, text and language attributes)
38
   * @param {Array[Object]} [descriptionsData] descriptions data (for each description,
39
   *  should contain an author, title, text and language attributes)
40
   * @throws Sails ORM errors (see https://sailsjs.com/documentation/concepts/models-and-orm/errors)
41
   *
42
   * @returns {Promise} the created cave
43
   */
44
  createCave: async (req, caveData, nameData, descriptionsData) => {
45
    const res = await sails.getDatastore().transaction(async (db) => {
2✔
46
      // Create cave
47
      const createdCave = await TCave.create({ ...caveData })
2✔
48
        .fetch()
49
        .usingConnection(db);
50

51
      // Format & create name
52
      await TName.create({
2✔
53
        ...nameData,
54
        cave: createdCave.id,
55
        dateInscription: new Date(),
56
        isMain: true,
57
      }).usingConnection(db);
58

59
      // Format & create descriptions
60
      if (descriptionsData) {
2!
61
        descriptionsData.map(async (d) => {
2✔
62
          const desc = await TDescription.create({
5✔
63
            ...d,
64
            cave: createdCave.id,
65
            dateInscription: new Date(),
66
          }).usingConnection(db);
67
          return desc;
5✔
68
        });
69
      }
70

71
      return createdCave;
2✔
72
    });
73

74
    const populatedCave = await module.exports.getPopulatedCave(res.id);
2✔
75
    module.exports.createESCave(populatedCave).catch(() => {});
2✔
76

77
    await RecentChangeService.setNameCreate(
2✔
78
      'cave',
79
      res.id,
80
      req.token.id,
81
      nameData.name
82
    );
83

84
    await NotificationService.notifySubscribers(
2✔
85
      req,
86
      populatedCave,
87
      req.token.id,
88
      NotificationService.NOTIFICATION_TYPES.CREATE,
89
      NotificationService.NOTIFICATION_ENTITIES.CAVE
90
    );
91

92
    return populatedCave;
2✔
93
  },
94

95
  // Extract everything from the request body except id and dateInscription
96
  getConvertedDataFromClient: (req) => ({
1✔
97
    // The TCave.create() function doesn't work with TCave field alias. See TCave.js Model
98
    depth: req.param('depth'),
99
    documents: req.param('documents'),
100
    isDiving: req.param('isDiving'),
101
    latitude: req.param('latitude'),
102
    longitude: req.param('longitude'),
103
    caveLength: req.param('length'),
104
    massif: req.param('massif'),
105
    temperature: req.param('temperature'),
106
  }),
107

108
  /**
109
   * Get the massifs in which the cave is contained.
110
   * If there is none, return an empty array.
111
   * @param {*} caveId
112
   * @returns [Massif]
113
   */
114
  // TODO Change to use the cave entrances location instead
115
  getMassifs: async (caveId) => {
116
    try {
38✔
117
      const WGS84_SRID = 4326; // GPS
38✔
118
      const query = `
38✔
119
      SELECT m.*
120
      FROM t_massif as m
121
      JOIN  t_cave AS c
122
      ON ST_Contains(ST_SetSRID(m.geog_polygon::geometry, ${WGS84_SRID}), ST_SetSRID(ST_MakePoint(c.longitude, c.latitude), ${WGS84_SRID}))
123
      WHERE c.id = $1
124
    `;
125
      const queryResult = await CommonService.query(query, [caveId]);
38✔
126
      return queryResult.rows;
38✔
127
    } catch (e) {
128
      // Fail silently (happens when the longitude and latitude are null for example)
129
      return [];
×
130
    }
131
  },
132

133
  /**
134
   *
135
   * @param
136
   * @returns {Object} the cumulated length of the caves present in the database whose value length is not null
137
   *                and the number of data on which this value is calculated
138
   *                or null if no result or something went wrong
139
   */
140
  getCumulatedLength: async () => {
141
    try {
1✔
142
      const queryResult = await CommonService.query(GET_CUMULATED_LENGTH, []);
1✔
143
      const result = queryResult.rows;
1✔
144
      if (result.length > 0) {
1!
145
        return result[0];
1✔
146
      }
147
      return null;
×
148
    } catch (e) {
149
      return null;
×
150
    }
151
  },
152

153
  async getPopulatedCave(caveId, subEntitiesWhere = {}) {
8✔
154
    const cave = await TCave.findOne(caveId)
10✔
155
      .populate('author')
156
      .populate('reviewer')
157
      .populate('names')
158
      .populate('descriptions')
159
      .populate('entrances')
160
      .populate('documents');
161

162
    if (!cave) return null;
10✔
163

164
    [cave.massifs, cave.descriptions, cave.documents] = await Promise.all([
8✔
165
      module.exports.getMassifs(cave.id),
166
      DescriptionService.getCaveDescriptions(cave.id, subEntitiesWhere),
167
      DocumentService.getDocuments(cave.documents?.map((d) => d.id) ?? []),
2!
168
    ]);
169

170
    const nameAsyncArr = [
8✔
171
      NameService.setNames(cave?.entrances, 'entrance'),
172
      NameService.setNames(cave?.massifs, 'massif'),
173
    ];
174
    if (cave.names.length === 0) {
8✔
175
      // As the name service will also get the entrance name if needed
176
      nameAsyncArr.push(NameService.setNames([cave], 'cave'));
2✔
177
    }
178
    await Promise.all(nameAsyncArr);
8✔
179

180
    // TODO What about other linked entities ?
181
    // - histories
182
    // - riggings
183
    // - comments
184
    // - exploringGrottos
185
    // - partneringGrottos
186

187
    return cave;
8✔
188
  },
189

190
  async createESCave(populatedCave) {
191
    const description =
192
      populatedCave.descriptions.length === 0
2!
193
        ? null
194
        : `${populatedCave.descriptions[0].title} ${populatedCave.descriptions[0].body}`;
195
    await ElasticsearchService.create('caves', populatedCave.id, {
2✔
196
      id: populatedCave.id,
197
      depth: populatedCave.depth,
198
      length: populatedCave.length,
199
      is_diving: populatedCave.isDiving,
200
      temperature: populatedCave.temperature,
201
      name: populatedCave.name,
202
      names: populatedCave.names.map((n) => n.name).join(', '),
2✔
203
      'nb entrances': populatedCave.entrances.length,
204
      deleted: populatedCave.isDeleted,
205
      descriptions: [description],
206
      tags: ['cave'],
207
    });
208
  },
209

210
  async permanentlyDeleteCave(cave, shouldMergeInto, mergeIntoId) {
NEW
211
    await TCave.update({ redirectTo: cave.id }).set({
×
212
      redirectTo: shouldMergeInto ? mergeIntoId : null,
×
213
    });
NEW
214
    await TNotification.destroy({ cave: cave.id });
×
215

NEW
216
    if (cave.documents.length > 0) {
×
NEW
217
      if (shouldMergeInto) {
×
NEW
218
        const newDocuments = cave.documents.map((e) => e.id);
×
NEW
219
        await TCave.addToCollection(mergeIntoId, 'documents', newDocuments);
×
220
      }
NEW
221
      await HDocument.update({ cave: cave.id }).set({ cave: null });
×
NEW
222
      await TCave.updateOne(cave.id).set({ documents: [] });
×
223
    }
224

NEW
225
    if (cave.entrances.length > 0 && shouldMergeInto) {
×
NEW
226
      const newEntrances = cave.entrances.map((e) => e.id);
×
NEW
227
      await TCave.addToCollection(mergeIntoId, 'entrances', newEntrances);
×
228
    }
NEW
229
    await HEntrance.update({ cave: cave.id }).set({ cave: null });
×
230

NEW
231
    if (cave.descriptions.length > 0) {
×
NEW
232
      if (shouldMergeInto) {
×
NEW
233
        await TDescription.update({ cave: cave.id }).set({
×
234
          cave: mergeIntoId,
235
        });
NEW
236
        await HDescription.update({ cave: cave.id }).set({
×
237
          cave: mergeIntoId,
238
        });
239
      } else {
NEW
240
        await TDescription.destroy({ cave: cave.id }); // TDescription first soft delete
×
NEW
241
        await HDescription.destroy({ cave: cave.id });
×
NEW
242
        await TDescription.destroy({ cave: cave.id });
×
243
      }
244
    }
245

NEW
246
    await NameService.permanentDelete({ cave: cave.id });
×
247

NEW
248
    await HCave.destroy({ id: cave.id });
×
NEW
249
    await TCave.destroyOne({ id: cave.id }); // Hard delete
×
250
  },
251
};
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

© 2026 Coveralls, Inc