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

GrottoCenter / grottocenter-api / 25457721116

06 May 2026 07:47PM UTC coverage: 86.494% (-0.2%) from 86.702%
25457721116

Pull #1565

github

ClemRz
feat(account): consolidate account endpoints and add locale-aware emails

- Add GET /api/v1/account for authenticated user's private data
- Add PATCH /api/v1/account for self-service updates (name, surname,
  nickname, email, password, language)
- Restrict PUT /api/v1/cavers/:caverId to admin-only
- Add optional language param to sign-up endpoint
- Add locale support to send-email helper for recipient-language emails
- Remove orphaned change-email, change-alert-for-news controllers
- Update Swagger spec with consolidated /account path
- Update tests to match new endpoint behavior
- Silence pre-existing func-names lint warnings in property tests
Pull Request #1565: feat(account): consolidate endpoints and add locale-aware emails

3047 of 3661 branches covered (83.23%)

Branch coverage included in aggregate %.

71 of 91 new or added lines in 8 files covered. (78.02%)

27 existing lines in 6 files now uncovered.

6297 of 7142 relevant lines covered (88.17%)

53.2 hits per line

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

79.22
/api/controllers/v1/document/multiple-validate.js
1
const DocumentService = require('../../../services/DocumentService');
1✔
2
const FileService = require('../../../services/FileService');
1✔
3
const RightService = require('../../../services/RightService');
1✔
4
const NotificationService = require('../../../services/NotificationService');
1✔
5

6
async function markDocumentValidated(
7
  documentId,
8
  validationComment,
9
  validationAuthor
10
) {
11
  await TDocument.updateOne(documentId).set({
9✔
12
    isValidated: true,
13
    modifiedDocJson: null,
14
    dateValidation: new Date(),
15
    validationComment,
16
    validator: validationAuthor,
17
  });
18
}
19

20
async function validateAndUpdateDocument(
21
  document,
22
  validationComment,
23
  validationAuthor
24
) {
25
  const {
26
    reviewerId,
27
    documentData,
28
    descriptionData,
29
    modifiedFiles,
30
    deletedFiles,
31
    newFiles,
32
  } = document.modifiedDocJson;
1✔
33

34
  await sails.getDatastore().transaction(async (db) => {
1✔
35
    // Update associated data not handled by TDocument manually
36
    // Updated before the TDocument update so the last_change_document DB trigger will fetch the last updated name
37
    await TDescription.updateOne({ document: document.id })
1✔
38
      .set(descriptionData)
39
      .usingConnection(db);
40

41
    await TDocument.updateOne(document.id)
1✔
42
      .set({
43
        ...documentData,
44
        modifiedDocJson: null,
45
        dateReviewed: new Date(),
46
        reviewer: reviewerId,
47
        dateValidation: new Date(),
48
        isValidated: true,
49
        validationComment,
50
        validator: validationAuthor,
51
      })
52
      .usingConnection(db);
53

54
    const filePromises = [];
1✔
55
    // Files have already been created,
56
    // they just need to be linked to the document.
57
    if (newFiles) {
1!
58
      filePromises.push(
×
59
        ...newFiles.map((f) => TFile.updateOne(f.id).set({ isValidated: true }))
×
60
      );
61
    }
62
    if (modifiedFiles) {
1!
63
      filePromises.push(
×
64
        ...modifiedFiles.map((f) => FileService.document.update(f))
×
65
      );
66
    }
67

68
    if (deletedFiles) {
1!
69
      filePromises.push(
×
70
        ...deletedFiles.map((f) => FileService.document.delete(f))
×
71
      );
72
    }
73
    await Promise.all(filePromises);
1✔
74
  });
75
}
76

77
async function updateSearchAndNotify(req, documentId, userId) {
78
  const document = await DocumentService.getPopulatedDocument(documentId);
7✔
79
  await DocumentService.updateInSearch(document);
7✔
80

81
  await NotificationService.notifySubscribers(
7✔
82
    req,
83
    document,
84
    userId,
85
    NotificationService.NOTIFICATION_TYPES.VALIDATE,
86
    NotificationService.NOTIFICATION_ENTITIES.DOCUMENT
87
  );
88

89
  return document;
7✔
90
}
91

92
module.exports = async (req, res) => {
1✔
93
  const hasRight = RightService.hasGroup(
11✔
94
    req.token.groups,
95
    RightService.G.MODERATOR
96
  );
97
  if (!hasRight) {
11✔
98
    return res.forbidden(
1✔
99
      'You are not authorized to validate multiple documents.'
100
    );
101
  }
102

103
  const documentChanges = [];
10✔
104
  // Validate input
105
  for (const doc of req.param('documents') ?? []) {
10!
106
    // Whether or not the pending changes are accepted or not
107
    const isValidated = doc.isValidated
11!
108
      ? doc.isValidated.toLowerCase() !== 'false'
109
      : true;
110

111
    if (isValidated === false && !doc.validationComment) {
11✔
112
      return res.badRequest(
1✔
113
        `If the document with id ${doc.id} is refused, a comment must be provided.`
114
      );
115
    }
116

117
    documentChanges.push({
10✔
118
      id: doc.id,
119
      isValidated,
120
      validationComment: doc.validationComment,
121
    });
122
  }
123
  const documentIds = documentChanges.map((e) => e.id);
10✔
124
  const foundDocuments = await TDocument.find({ id: documentIds });
9✔
125

126
  // Sequential to preserve partial-success semantics per document
127
  for (const document of foundDocuments) {
9✔
128
    const change = documentChanges.find((d) => d.id === document.id);
12✔
129
    const isAModifiedDoc = !!document.modifiedDocJson;
10✔
130
    if (!change.isValidated) {
10✔
131
      // Validate it but do not update its fields (reject change)
132
      // eslint-disable-next-line no-await-in-loop
133
      await markDocumentValidated(
3✔
134
        document.id,
135
        change.validationComment,
136
        req.token.id
137
      );
138
      // eslint-disable-next-line no-await-in-loop
139
      const rejectedDoc = await DocumentService.getPopulatedDocument(
3✔
140
        document.id
141
      );
142
      // eslint-disable-next-line no-await-in-loop
143
      await NotificationService.notifyAuthor(
3✔
144
        req,
145
        rejectedDoc,
146
        req.token.id,
147
        NotificationService.NOTIFICATION_TYPES.REJECT,
148
        change.validationComment
149
      ).catch((err) =>
UNCOV
150
        sails.log.error(
×
151
          'Document multiple-validate notifyAuthor error',
152
          document,
153
          err
154
        )
155
      );
156
      continue; // eslint-disable-line no-continue
3✔
157
    }
158

159
    if (isAModifiedDoc) {
7✔
160
      // eslint-disable-next-line no-await-in-loop
161
      await validateAndUpdateDocument(
1✔
162
        document,
163
        change.validationComment,
164
        req.token.id
165
      );
166
    } else {
167
      // Likely a document creation
168
      // eslint-disable-next-line no-await-in-loop
169
      await markDocumentValidated(
6✔
170
        document.id,
171
        change.validationComment,
172
        req.token.id
173
      );
174
    }
175

176
    // eslint-disable-next-line no-await-in-loop
177
    const populatedDoc = await updateSearchAndNotify(
7✔
178
      req,
179
      document.id,
180
      req.token.id
181
    ).catch((err) => {
UNCOV
182
      sails.log.error(
×
183
        'Document multiple validate updateSearchAndNotify error',
184
        document,
185
        err
186
      );
UNCOV
187
      return null;
×
188
    });
189

190
    if (populatedDoc) {
7!
191
      // eslint-disable-next-line no-await-in-loop
192
      await NotificationService.notifyAuthor(
7✔
193
        req,
194
        populatedDoc,
195
        req.token.id,
196
        NotificationService.NOTIFICATION_TYPES.VALIDATE,
197
        change.validationComment
198
      ).catch((err) =>
UNCOV
199
        sails.log.error(
×
200
          'Document multiple-validate notifyAuthor error',
201
          document,
202
          err
203
        )
204
      );
205
    }
206
  }
207

208
  return res.ok();
9✔
209
};
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