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

GrottoCenter / grottocenter-api / 9305110134

30 May 2024 03:23PM UTC coverage: 48.96% (+0.02%) from 48.943%
9305110134

push

github

vmarseguerra
fix(document): cannot modify or delete files

727 of 1971 branches covered (36.88%)

Branch coverage included in aggregate %.

4 of 14 new or added lines in 3 files covered. (28.57%)

2 existing lines in 1 file now uncovered.

2520 of 4661 relevant lines covered (54.07%)

4.43 hits per line

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

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

10
const AZURE_ACCOUNT = 'grottocenter';
6✔
11
const AZURE_CONTAINER_DOCUMENTS = 'documents';
6✔
12
const AZURE_CONTAINER_DB_SNAPSHOTS = 'db-exports';
6✔
13

14
const { AZURE_KEY = '' } = process.env;
6✔
15

16
let credentials = null;
6✔
17

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

38
const INVALID_FORMAT = 'INVALID_FORMAT';
6✔
39
const INVALID_NAME = 'INVALID_NAME';
6✔
40
const ERROR_DURING_UPLOAD_TO_AZURE = 'ERROR_DURING_UPLOAD_TO_AZURE';
6✔
41

42
class FileError extends Error {
43
  constructor(message, fileName) {
44
    super(message);
×
45
    this.fileName = fileName;
×
46
  }
47
}
48

49
const generateName = (fileName) => {
6✔
50
  const identifier = Math.random().toString().replace(/0\./, '');
×
51
  const newFileName = fileName.replace(/ /, '_');
×
52
  return `${identifier}-${newFileName}`;
×
53
};
54

55
function noCredentialWarning(name, args) {
56
  const fmtArgs = Object.entries(args)
×
57
    .map((e) => e.join(': '))
×
58
    .join(', ');
59
  sails.log.warn(`Azure ${name} Missing credential, ${fmtArgs}`);
×
60
  return null;
×
61
}
62

63
function getSignedReadUrl(container, path, expiresOnMs) {
64
  const sasQuery = generateBlobSASQueryParameters(
×
65
    {
66
      blobName: path,
67
      containerName: container,
68
      expiresOn: new Date(Date.now() + expiresOnMs),
69
      permissions: BlobSASPermissions.parse('r'),
70
    },
71
    credentials.sharedKeyCredential
72
  );
73

74
  return `https://${AZURE_ACCOUNT}.blob.core.windows.net/${container}/${path}?${sasQuery.toString()}`;
×
75
}
76

77
module.exports = {
6✔
78
  INVALID_FORMAT,
79
  INVALID_NAME,
80
  ERROR_DURING_UPLOAD_TO_AZURE,
81

82
  isCredentials: !!credentials,
83

84
  document: {
85
    getUrl(path) {
86
      // The documents container allow anonymous access
87
      return `https://${AZURE_ACCOUNT}.blob.core.windows.net/${AZURE_CONTAINER_DOCUMENTS}/${path}`;
46✔
88
    },
89

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

110
      const foundFormat = await TFileFormat.find({
×
111
        mimeType,
112
        extension: nameSplit[1].toLowerCase(),
113
      }).limit(1);
114
      if (ramda.isEmpty(foundFormat)) {
×
115
        throw new FileError(INVALID_FORMAT, name);
×
116
      }
117

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

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

152
    async update(file) {
153
      const res = await TFile.updateOne(file.id).set({
1✔
154
        fileName: file.fileName,
155
      });
156
      return res;
1✔
157
    },
158

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

174
  dbExport: {
175
    getUrl(path, expiresOnMs) {
176
      if (!credentials) return noCredentialWarning('dbExport getUrl', { path });
×
177
      return getSignedReadUrl(AZURE_CONTAINER_DB_SNAPSHOTS, path, expiresOnMs);
×
178
    },
179

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

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

206
    upload(filename, mimeType) {
207
      if (!credentials)
×
208
        return noCredentialWarning('dbExport upload', { filename });
×
209

210
      const ONE_MEGABYTE = 1024 * 1024;
×
211
      const BUFFER_SIZE = 2 * ONE_MEGABYTE;
×
212
      const MAX_BUFFERS = 3;
×
213

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