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

GrottoCenter / grottocenter-api / 10996013011

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

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 %.

528 of 1295 new or added lines in 114 files covered. (40.77%)

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

17.31
/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

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

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

15
let credentials = null;
6✔
16

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

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

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

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

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

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

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

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

81
  isCredentials: !!credentials,
82

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

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

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

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

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

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

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

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

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

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

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

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

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