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

GrottoCenter / grottocenter-api / 19548283169

20 Nov 2025 07:04PM UTC coverage: 46.323% (+0.007%) from 46.316%
19548283169

push

github

ClemRz
fix(files): fixes the availability of the crypto dependence while accessing Azure Blob

1018 of 2921 branches covered (34.85%)

Branch coverage included in aggregate %.

2 of 3 new or added lines in 1 file covered. (66.67%)

3083 of 5932 relevant lines covered (51.97%)

6.98 hits per line

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

19.27
/api/services/FileService.js
1
const crypto = require('crypto');
6✔
2
const {
3
  BlobServiceClient,
4
  StorageSharedKeyCredential,
5
  BlobSASPermissions,
6
  generateBlobSASQueryParameters,
7
} = require('@azure/storage-blob');
6✔
8
const stream = require('stream');
6✔
9

10
// Ensure crypto is available globally for Azure SDK
11
if (!global.crypto) {
6!
NEW
12
  global.crypto = crypto;
×
13
}
14

15
const AZURE_ACCOUNT = 'grottocenter';
6✔
16
const AZURE_CONTAINER_DOCUMENTS = 'documents';
6✔
17
const AZURE_CONTAINER_DB_SNAPSHOTS = 'db-exports';
6✔
18

19
const { AZURE_KEY = '' } = process.env;
6✔
20

21
let credentials = null;
6✔
22

23
if (AZURE_KEY) {
6!
24
  const sharedKeyCredential = new StorageSharedKeyCredential(
×
25
    AZURE_ACCOUNT,
26
    AZURE_KEY
27
  );
28
  const blobServiceClient = new BlobServiceClient(
×
29
    `https://${AZURE_ACCOUNT}.blob.core.windows.net/`,
30
    sharedKeyCredential
31
  );
32
  credentials = {
×
33
    sharedKeyCredential,
34
    dbExportBlobClient: blobServiceClient.getContainerClient(
35
      AZURE_CONTAINER_DB_SNAPSHOTS
36
    ),
37
    documentsBlobClient: blobServiceClient.getContainerClient(
38
      AZURE_CONTAINER_DOCUMENTS
39
    ),
40
  };
41
}
42

43
const INVALID_FORMAT = 'INVALID_FORMAT';
6✔
44
const INVALID_NAME = 'INVALID_NAME';
6✔
45
const ERROR_DURING_UPLOAD_TO_AZURE = 'ERROR_DURING_UPLOAD_TO_AZURE';
6✔
46

47
class FileError extends Error {
48
  constructor(message, fileName) {
49
    super(message);
×
50
    this.fileName = fileName;
×
51
  }
52
}
53

54
const generateName = (fileName) => {
6✔
55
  const identifier = Math.random().toString().replace(/0\./, '');
×
56
  const newFileName = fileName.replace(/ /, '_');
×
57
  return `${identifier}-${newFileName}`;
×
58
};
59

60
function noCredentialWarning(name, args) {
61
  const fmtArgs = Object.entries(args)
×
62
    .map((e) => e.join(': '))
×
63
    .join(', ');
64
  sails.log.warn(`Azure ${name} Missing credential, ${fmtArgs}`);
×
65
  return null;
×
66
}
67

68
function getSignedReadUrl(container, path, expiresOnMs) {
69
  const sasQuery = generateBlobSASQueryParameters(
×
70
    {
71
      blobName: path,
72
      containerName: container,
73
      expiresOn: new Date(Date.now() + expiresOnMs),
74
      permissions: BlobSASPermissions.parse('r'),
75
    },
76
    credentials.sharedKeyCredential
77
  );
78

79
  return `https://${AZURE_ACCOUNT}.blob.core.windows.net/${container}/${path}?${sasQuery.toString()}`;
×
80
}
81

82
module.exports = {
6✔
83
  INVALID_FORMAT,
84
  INVALID_NAME,
85
  ERROR_DURING_UPLOAD_TO_AZURE,
86

87
  isCredentials: !!credentials,
88

89
  document: {
90
    getUrl(path) {
91
      // The documents container allow anonymous access
92
      return `https://${AZURE_ACCOUNT}.blob.core.windows.net/${AZURE_CONTAINER_DOCUMENTS}/${path}`;
46✔
93
    },
94

95
    // File is a multer object : https://github.com/expressjs/multer#file-information
96
    /**
97
     *
98
     * @param {*} file
99
     * @param {*} idDocument
100
     * @param {*} fetchResult
101
     * @param {*} isValidated
102
     * @throws {FileError}
103
     * @returns
104
     */
105
    // eslint-disable-next-line consistent-return
106
    async create(file, idDocument, fetchResult = false, isValidated = true) {
×
107
      const name = file.originalname;
×
108
      const pathName = generateName(name);
×
109
      const nameSplit = name.split('.');
×
110
      if (nameSplit.length !== 2) {
×
111
        throw new FileError(INVALID_NAME, name);
×
112
      }
113

114
      const foundFormat = await TFileFormat.find({
×
115
        extension: nameSplit[1].toLowerCase(),
116
      }).limit(1);
117
      if (foundFormat.length === 0) {
×
118
        throw new FileError(INVALID_FORMAT, name);
×
119
      }
120
      const { mimeType } = foundFormat;
×
121

122
      if (!credentials) {
×
123
        noCredentialWarning('Document upload', {
×
124
          name,
125
          mimeType,
126
          size: file.size,
127
        });
128
      } else {
129
        sails.log.info(`Uploading ${name} to Azure Blob...`);
×
130
        try {
×
131
          const blockBlobClient =
132
            credentials.documentsBlobClient.getBlockBlobClient(pathName);
×
133
          await blockBlobClient.uploadData(file.buffer, {
×
134
            blobHTTPHeaders: { blobContentType: mimeType },
135
          });
136
        } catch (err) {
137
          throw new FileError(ERROR_DURING_UPLOAD_TO_AZURE, name);
×
138
        }
139
      }
140

141
      const param = {
×
142
        dateInscription: new Date(),
143
        fileName: name,
144
        document: idDocument,
145
        fileFormat: foundFormat[0].id,
146
        path: pathName,
147
        isValidated,
148
      };
149
      if (fetchResult) {
×
150
        const createdFile = await TFile.create(param).fetch();
×
151
        return createdFile;
×
152
      }
153
      await TFile.create(param);
×
154
    },
155

156
    async update(file) {
157
      const res = await TFile.updateOne(file.id).set({
1✔
158
        fileName: file.fileName,
159
      });
160
      return res;
1✔
161
    },
162

163
    async delete(file) {
164
      const destroyedRecord = await TFile.destroyOne(file.id);
×
165
      if (!credentials) {
×
166
        noCredentialWarning('Document delete', file);
×
167
      } else {
168
        const blockBlobClient =
169
          credentials.documentsBlobClient.getBlockBlobClient(
×
170
            destroyedRecord.path
171
          );
172
        await blockBlobClient.delete({ deleteSnapshots: 'include' });
×
173
      }
174
      return destroyedRecord;
×
175
    },
176
  },
177

178
  dbExport: {
179
    getUrl(path, expiresOnMs) {
180
      if (!credentials) return noCredentialWarning('dbExport getUrl', { path });
×
181
      return getSignedReadUrl(AZURE_CONTAINER_DB_SNAPSHOTS, path, expiresOnMs);
×
182
    },
183

184
    async getMetadata() {
185
      if (!credentials) return noCredentialWarning('dbExport getMetadata', {});
×
186
      const metadataBlobClient =
187
        credentials.dbExportBlobClient.getBlockBlobClient(
×
188
          'exportMetadata.json'
189
        );
190
      const response = await metadataBlobClient.download();
×
191
      let data = '';
×
192
      for await (const chunk of response.readableStreamBody) data += chunk;
×
193
      return JSON.parse(data);
×
194
    },
195

196
    async setMetadata(archiveSize) {
197
      if (!credentials) return noCredentialWarning('dbExport setMetadata', {});
×
198
      const metadataBlobClient =
199
        credentials.dbExportBlobClient.getBlockBlobClient(
×
200
          'exportMetadata.json'
201
        );
202
      const dataStr = JSON.stringify({
×
203
        lastUpdate: new Date().toISOString(),
204
        size: archiveSize,
205
      });
206
      await metadataBlobClient.upload(dataStr, dataStr.length);
×
207
      return null;
×
208
    },
209

210
    upload(filename, mimeType) {
211
      if (!credentials)
×
212
        return noCredentialWarning('dbExport upload', { filename });
×
213

214
      const ONE_MEGABYTE = 1024 * 1024;
×
215
      const BUFFER_SIZE = 2 * ONE_MEGABYTE;
×
216
      const MAX_BUFFERS = 3;
×
217

218
      try {
×
219
        const aStream = stream.PassThrough();
×
220
        const blockBlobClient =
221
          credentials.dbExportBlobClient.getBlockBlobClient(filename);
×
222
        blockBlobClient.uploadStream(aStream, BUFFER_SIZE, MAX_BUFFERS, {
×
223
          blobHTTPHeaders: { blobContentType: mimeType },
224
        });
225
        return aStream;
×
226
      } catch (err) {
227
        throw new FileError(ERROR_DURING_UPLOAD_TO_AZURE, filename);
×
228
      }
229
    },
230
  },
231
};
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