cozy-client 41.2.1 → 41.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2703,7 +2703,7 @@ var CozyClient = /*#__PURE__*/function () {
2703
2703
  CozyClient.hooks = CozyClient.hooks || {};
2704
2704
  CozyClient.fetchPolicies = _policies.default; //COZY_CLIENT_VERSION_PACKAGE in replaced by babel. See babel config
2705
2705
 
2706
- CozyClient.version = "41.2.1";
2706
+ CozyClient.version = "41.3.0";
2707
2707
 
2708
2708
  _microee.default.mixin(CozyClient);
2709
2709
 
@@ -144,5 +144,9 @@
144
144
  "undefined": "Undefined",
145
145
  "work_study": "Work & Study"
146
146
  }
147
+ },
148
+ "MagicFolders": {
149
+ "administrative": "Administrative",
150
+ "photos": "Photos"
147
151
  }
148
152
  }
@@ -144,5 +144,9 @@
144
144
  "undefined": "Indéfini",
145
145
  "work_study": "Travail & Études"
146
146
  }
147
+ },
148
+ "MagicFolders": {
149
+ "administrative": "Administratif",
150
+ "photos": "Photos"
147
151
  }
148
152
  }
@@ -5,7 +5,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.getBoundT = void 0;
8
+ exports.getLocalizer = exports.getBoundT = void 0;
9
9
 
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
11
 
@@ -40,8 +40,7 @@ for (var _i = 0, _langs = langs; _i < _langs.length; _i++) {
40
40
 
41
41
 
42
42
  var getBoundT = function getBoundT(lang) {
43
- var polyglot = polyglots[lang] || polyglots['en'];
44
- var t = polyglot.t.bind(polyglot);
43
+ var t = getLocalizer(lang);
45
44
  return function (label) {
46
45
  var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
47
46
 
@@ -53,5 +52,18 @@ var getBoundT = function getBoundT(lang) {
53
52
  return emojiCountry ? "".concat(t(label, newOpts), " ").concat(emojiCountry) : t(label, newOpts);
54
53
  };
55
54
  };
55
+ /**
56
+ * @param {string} lang - fr, en, etc
57
+ * @returns {Function} - localization function
58
+ */
59
+
60
+
61
+ exports.getBoundT = getBoundT;
62
+
63
+ var getLocalizer = function getLocalizer(lang) {
64
+ var polyglot = polyglots[lang] || polyglots['en'];
65
+ var t = polyglot.t.bind(polyglot);
66
+ return t;
67
+ };
56
68
 
57
- exports.getBoundT = getBoundT;
69
+ exports.getLocalizer = getLocalizer;
@@ -181,7 +181,7 @@ var getReferencedFolder = /*#__PURE__*/function () {
181
181
  _yield$client$collect = _context3.sent;
182
182
  included = _yield$client$collect.included;
183
183
  foldersOutsideTrash = included.filter(function (folder) {
184
- return !/^\/\.cozy_trash/.test(folder.attributes.path);
184
+ return !/^\/\.cozy_trash/.test(folder.path);
185
185
  }); // there can be multiple folders with the same reference in some edge cases
186
186
  // when this happens we return the most recent one
187
187
 
@@ -29,15 +29,11 @@ describe('Folder model', () => {
29
29
  it('should return first existing magic folder', async () => {
30
30
  const existingMagicFolders = [
31
31
  {
32
- attributes: {
33
- path: '/Administrative'
34
- },
32
+ path: '/Administrative',
35
33
  created_at: '2020-03-02T14:57:07.661588+01:00'
36
34
  },
37
35
  {
38
- attributes: {
39
- path: '/Administrative2'
40
- },
36
+ path: '/Administrative2',
41
37
  created_at: '2019-03-02T14:57:07.661588+01:00'
42
38
  }
43
39
  ]
@@ -96,15 +92,11 @@ describe('Folder model', () => {
96
92
  describe('getReferencedFolder', () => {
97
93
  it('should filter trashed folders', async () => {
98
94
  const referencedFolder = {
99
- attributes: {
100
- path: '/Reference/Folder'
101
- }
95
+ path: '/Reference/Folder'
102
96
  }
103
97
 
104
98
  const trashFolder = {
105
- attributes: {
106
- path: '/.cozy_trash/Old/Reference/Folder'
107
- }
99
+ path: '/.cozy_trash/Old/Reference/Folder'
108
100
  }
109
101
 
110
102
  mockClient.findReferencedBy.mockResolvedValue({
@@ -118,15 +110,11 @@ describe('Folder model', () => {
118
110
 
119
111
  it('should return the most recently created folder', async () => {
120
112
  const oldFolder = {
121
- attributes: {
122
- path: '/Old/folder'
123
- },
113
+ path: '/Old/folder',
124
114
  created_at: '2019-03-02T14:57:07.661588+01:00'
125
115
  }
126
116
  const recentFolder = {
127
- attributes: {
128
- path: '/Recent/folder'
129
- },
117
+ path: '/Recent/folder',
130
118
  created_at: '2020-03-02T14:57:07.661588+01:00'
131
119
  }
132
120
 
@@ -5,7 +5,7 @@ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWild
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.user = exports.paper = exports.dacc = exports.sharing = exports.timeseries = exports.document = exports.contact = exports.utils = exports.permission = exports.note = exports.account = exports.folder = exports.file = exports.applications = exports.instance = exports.trigger = exports.accounts = exports.triggers = void 0;
8
+ exports.user = exports.paper = exports.dacc = exports.sharing = exports.timeseries = exports.document = exports.contact = exports.utils = exports.permission = exports.note = exports.account = exports.konnectorFolder = exports.folder = exports.file = exports.applications = exports.instance = exports.trigger = exports.accounts = exports.triggers = void 0;
9
9
 
10
10
  var trigger = _interopRequireWildcard(require("./trigger"));
11
11
 
@@ -27,6 +27,10 @@ var folder = _interopRequireWildcard(require("./folder"));
27
27
 
28
28
  exports.folder = folder;
29
29
 
30
+ var konnectorFolder = _interopRequireWildcard(require("./konnectorFolder"));
31
+
32
+ exports.konnectorFolder = konnectorFolder;
33
+
30
34
  var account = _interopRequireWildcard(require("./account"));
31
35
 
32
36
  exports.account = account;
@@ -0,0 +1,380 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.buildFolderPermission = exports.buildFolderPath = exports.statDirectoryByPath = exports.createDirectoryByPath = exports.ensureKonnectorFolder = void 0;
9
+
10
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
+
12
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
13
+
14
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
15
+
16
+ var _folder = require("./folder");
17
+
18
+ var _CozyClient = _interopRequireDefault(require("../CozyClient"));
19
+
20
+ var _account = require("./account");
21
+
22
+ var _locales = require("./document/locales");
23
+
24
+ //@ts-check
25
+ var FILES_DOCTYPE = 'io.cozy.files';
26
+ var PERMISSIONS_DOCTYPE = 'io.cozy.permissions'; // Default name for base directory
27
+
28
+ var DEFAULT_LOCALIZED_BASE_DIR = 'Administrative';
29
+ /**
30
+ * Ensures the destination folder of a konnector exists and is initiated with proper permissions and references
31
+ *
32
+ * @param {CozyClient} client - CozyClient instance
33
+ * @param {Object} options - options object
34
+ * @param {import('../types').IOCozyKonnector} options.konnector - io.cozy.konnectors document
35
+ * @param {import('../types').IOCozyAccount} options.account - io.cozy.accounts document
36
+ * @param {String} options.lang - instance current language. ex: 'fr'
37
+ * @returns {Promise<import('../types').IOCozyFolder>}
38
+ */
39
+
40
+ var ensureKonnectorFolder = /*#__PURE__*/function () {
41
+ var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client, _ref) {
42
+ var konnector, account, lang, permissions, files, t, _yield$Promise$all, _yield$Promise$all2, adminFolder, photosFolder, path, folder;
43
+
44
+ return _regenerator.default.wrap(function _callee$(_context) {
45
+ while (1) {
46
+ switch (_context.prev = _context.next) {
47
+ case 0:
48
+ konnector = _ref.konnector, account = _ref.account, lang = _ref.lang;
49
+ permissions = client.collection(PERMISSIONS_DOCTYPE);
50
+ files = client.collection(FILES_DOCTYPE);
51
+ t = (0, _locales.getLocalizer)(lang);
52
+ _context.next = 6;
53
+ return Promise.all([(0, _folder.ensureMagicFolder)(client, _folder.MAGIC_FOLDERS.ADMINISTRATIVE, t('MagicFolders.administrative')), (0, _folder.ensureMagicFolder)(client, _folder.MAGIC_FOLDERS.PHOTOS, t('MagicFolders.photos'))]);
54
+
55
+ case 6:
56
+ _yield$Promise$all = _context.sent;
57
+ _yield$Promise$all2 = (0, _slicedToArray2.default)(_yield$Promise$all, 2);
58
+ adminFolder = _yield$Promise$all2[0];
59
+ photosFolder = _yield$Promise$all2[1];
60
+ path = buildFolderPath(konnector, account, {
61
+ administrative: adminFolder.path,
62
+ photos: photosFolder.path
63
+ });
64
+ _context.next = 13;
65
+ return statDirectoryByPath(client, path);
66
+
67
+ case 13:
68
+ _context.t0 = _context.sent;
69
+
70
+ if (_context.t0) {
71
+ _context.next = 18;
72
+ break;
73
+ }
74
+
75
+ _context.next = 17;
76
+ return createDirectoryByPath(client, path);
77
+
78
+ case 17:
79
+ _context.t0 = _context.sent;
80
+
81
+ case 18:
82
+ folder = _context.t0;
83
+ _context.next = 21;
84
+ return permissions.add(konnector, buildFolderPermission(folder));
85
+
86
+ case 21:
87
+ _context.next = 23;
88
+ return files.addReferencesTo(konnector, [folder]);
89
+
90
+ case 23:
91
+ return _context.abrupt("return", folder);
92
+
93
+ case 24:
94
+ case "end":
95
+ return _context.stop();
96
+ }
97
+ }
98
+ }, _callee);
99
+ }));
100
+
101
+ return function ensureKonnectorFolder(_x, _x2) {
102
+ return _ref2.apply(this, arguments);
103
+ };
104
+ }();
105
+ /**
106
+ * Creates a directory from a given path
107
+ *
108
+ * @param {CozyClient} client CozyClient
109
+ * @param {string} path Directory path
110
+ * @returns {Promise<import('../types').IOCozyFolder>} Directory attributes
111
+ */
112
+
113
+
114
+ exports.ensureKonnectorFolder = ensureKonnectorFolder;
115
+
116
+ var createDirectoryByPath = /*#__PURE__*/function () {
117
+ var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(client, path) {
118
+ var _yield$client$collect, data;
119
+
120
+ return _regenerator.default.wrap(function _callee2$(_context2) {
121
+ while (1) {
122
+ switch (_context2.prev = _context2.next) {
123
+ case 0:
124
+ _context2.next = 2;
125
+ return client.collection(FILES_DOCTYPE).createDirectoryByPath(path);
126
+
127
+ case 2:
128
+ _yield$client$collect = _context2.sent;
129
+ data = _yield$client$collect.data;
130
+ return _context2.abrupt("return", data);
131
+
132
+ case 5:
133
+ case "end":
134
+ return _context2.stop();
135
+ }
136
+ }
137
+ }, _callee2);
138
+ }));
139
+
140
+ return function createDirectoryByPath(_x3, _x4) {
141
+ return _ref3.apply(this, arguments);
142
+ };
143
+ }();
144
+ /**
145
+ * Retrieves a directory from its path
146
+ *
147
+ * @param {CozyClient} client CozyClient
148
+ * @param {string} path Directory path
149
+ * @returns {Promise<import('../types').IOCozyFolder|null>} Created io.cozy.files document
150
+ * @throws will throw an error on any error without status === 404
151
+ */
152
+
153
+
154
+ exports.createDirectoryByPath = createDirectoryByPath;
155
+
156
+ var statDirectoryByPath = /*#__PURE__*/function () {
157
+ var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(client, path) {
158
+ var response;
159
+ return _regenerator.default.wrap(function _callee3$(_context3) {
160
+ while (1) {
161
+ switch (_context3.prev = _context3.next) {
162
+ case 0:
163
+ _context3.prev = 0;
164
+ _context3.next = 3;
165
+ return client.collection(FILES_DOCTYPE).statByPath(path);
166
+
167
+ case 3:
168
+ response = _context3.sent;
169
+
170
+ if (!response.data.trashed) {
171
+ _context3.next = 6;
172
+ break;
173
+ }
174
+
175
+ return _context3.abrupt("return", null);
176
+
177
+ case 6:
178
+ return _context3.abrupt("return", response.data);
179
+
180
+ case 9:
181
+ _context3.prev = 9;
182
+ _context3.t0 = _context3["catch"](0);
183
+
184
+ if (!(_context3.t0 && _context3.t0.status === 404)) {
185
+ _context3.next = 13;
186
+ break;
187
+ }
188
+
189
+ return _context3.abrupt("return", null);
190
+
191
+ case 13:
192
+ throw new Error(_context3.t0.message);
193
+
194
+ case 14:
195
+ case "end":
196
+ return _context3.stop();
197
+ }
198
+ }
199
+ }, _callee3, null, [[0, 9]]);
200
+ }));
201
+
202
+ return function statDirectoryByPath(_x5, _x6) {
203
+ return _ref4.apply(this, arguments);
204
+ };
205
+ }();
206
+ /**
207
+ * Build folder path for a given konnector and a given account.
208
+ *
209
+ * If konnector.folders[0].defaultDir exists, it is used as default directory.
210
+ *
211
+ * Occurrences of following strings in base directory are replaced by:
212
+ * * `$administrative`: Administrative folder
213
+ * * `$photos`: Photos folder
214
+ *
215
+ * Occurrences of following strings in path are replaced by:
216
+ * * `$account: Account label (id or name)`
217
+ * * `$konnector`: Konnector name
218
+ *
219
+ * If no konnectors.folders[0].defaultDir is set, the default dir used is
220
+ * * `$administrative/$konnector/$account`
221
+ *
222
+ * @param {import('../types').IOCozyKonnector} konnector Konnector document
223
+ * @param {import('../types').IOCozyAccount} account Account document
224
+ * @param {Object<string, string>} magicFolders Object containing a mapping from folder
225
+ * identifiers (ex: $administrative) to their localized values (ex:
226
+ * Administratif).
227
+ * @returns {String} The result path
228
+ */
229
+
230
+
231
+ exports.statDirectoryByPath = statDirectoryByPath;
232
+
233
+ var buildFolderPath = function buildFolderPath(konnector, account) {
234
+ var _konnector$folders, _konnector$folders$;
235
+
236
+ var magicFolders = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
237
+ var fullPath = (konnector === null || konnector === void 0 ? void 0 : (_konnector$folders = konnector.folders) === null || _konnector$folders === void 0 ? void 0 : (_konnector$folders$ = _konnector$folders[0]) === null || _konnector$folders$ === void 0 ? void 0 : _konnector$folders$.defaultDir) || '$administrative/$konnector/$account'; // Trim `/` and avoid multiple `/` characters with regexp
238
+
239
+ var sanitizedPath = trim(fullPath.replace(/(\/+)/g, '/'), '/'); // If the konnector doesn't have any of our base dir, we set it to $administrative
240
+
241
+ if (!hasBaseDir(sanitizedPath)) {
242
+ sanitizedPath = '$administrative/' + sanitizedPath;
243
+ }
244
+ /**
245
+ * Now that we have our sanitizedPath, we can split it in two strings
246
+ * * `baseDir` containing the baseDir path
247
+ * * `buildedSubDir` containing the rest of the path (ie the path without baseDir)
248
+ */
249
+
250
+
251
+ var baseDir = sanitizedPath.split('/', 1);
252
+ var buildedSubDir = buildSubDir(sanitizedPath, baseDir[0]);
253
+ var renderedBaseDir = renderBaseDir(baseDir[0], magicFolders);
254
+ var renderedPath = renderSubDir(buildedSubDir, {
255
+ // When adding a new allowed variable here, please keep documentation
256
+ // of `renderSubDir` function up to date.
257
+ konnector: konnector.name,
258
+ account: (0, _account.getAccountName)(account).replace(sanitizeAccountIdentifierRx, '-')
259
+ });
260
+ return "/".concat(renderedBaseDir, "/").concat(renderedPath);
261
+ };
262
+ /**
263
+ * Check if the provided Path start withs our allowedBaseDirPath to see
264
+ *
265
+ * @param {String} path - path to test
266
+ * @returns {Boolean}
267
+ */
268
+
269
+
270
+ exports.buildFolderPath = buildFolderPath;
271
+
272
+ var hasBaseDir = function hasBaseDir(path) {
273
+ return allowedBaseDirVariables.some(function (baseDirVar) {
274
+ return path.startsWith(baseDirVar);
275
+ });
276
+ };
277
+ /**
278
+ * Base directories are directory where konnector may copy their data.
279
+ * They are expressed as variables which then need to be localized.
280
+ * Default is `$administrative`.
281
+ */
282
+
283
+
284
+ var allowedBaseDirVariables = ['$administrative', '$photos'];
285
+ /**
286
+ * This method creates the subDir. We can't have an empty subDir, so we set
287
+ * it to our default '$konnector/$account'
288
+ *
289
+ * @param {String} fullPath String containing potentially the defaultDir
290
+ * @param {String} defaultDir String to remove from the fullPath
291
+ * @returns {String}
292
+ */
293
+
294
+ var buildSubDir = function buildSubDir(fullPath, defaultDir) {
295
+ var buildedSubDir = fullPath.substring(defaultDir.length);
296
+
297
+ if (buildedSubDir === '') {
298
+ buildedSubDir = '$konnector/$account';
299
+ }
300
+
301
+ return buildedSubDir;
302
+ };
303
+ /**
304
+ * Render base directory, based on given magicFolders object.
305
+ * For example, it will render `$administrative` with the given value passed in
306
+ * folders object. We expect to find in folders a localized value.
307
+ *
308
+ * @param {String} baseDir base directory variable, expects `$administrative`
309
+ * or `$photos`
310
+ * @param {Object<string, string>} magicFolders Object indexing base directory variable with
311
+ * corresponding localized name.
312
+ * @returns {String} Localized directory
313
+ */
314
+
315
+
316
+ var renderBaseDir = function renderBaseDir(baseDir) {
317
+ var magicFolders = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
318
+ // Look for variable name into folders but without $ prefix
319
+ var renderedBaseDir = magicFolders[baseDir.slice(1)] || DEFAULT_LOCALIZED_BASE_DIR; // Trim `/` and avoid multiple `/` characters with regexp
320
+
321
+ return trim(renderedBaseDir.replace(/(\/+)/g, '/'), '/');
322
+ };
323
+ /**
324
+ * Render the given folder path using the given `variables` object.
325
+ * Available variable are `$konnector` (konnector name) and `$account`
326
+ * (account label, i.e. id or name)
327
+ *
328
+ * @param {String} path Path to render : ex '/Administratif/$konnector/$account'
329
+ * @param {Object} variables Object mapping variable to actual values
330
+ * @param {import('../types').IOCozyKonnector['name']} variables.konnector - konnector name
331
+ * @param {String} variables.account - account name
332
+ * @returns {String} Rendered path
333
+ */
334
+
335
+
336
+ var renderSubDir = function renderSubDir(path, variables) {
337
+ // Trim `/` and avoid multiple `/` characters with regexp
338
+ var sanitizedPath = trim(path.replace(/(\/+)/g, '/'), '/'); // Let's get only full variable name limited by '/'. We want to avoid false
339
+ // positive like parsing `$variableInString` to `valueInString`
340
+
341
+ var segments = sanitizedPath.split('/');
342
+ return segments.map(function (segment) {
343
+ return (variables === null || variables === void 0 ? void 0 : variables[segment.slice(1)]) || segment;
344
+ }).join('/');
345
+ };
346
+
347
+ var sanitizeAccountIdentifierRx = /\//g;
348
+ /**
349
+ * Returns a permission ready to be passed to
350
+ * client.collection('io.cozy.permissions').add().
351
+ *
352
+ * @param {import('../types').IOCozyFolder} folder The folder which the konnector should have access
353
+ * @returns {Object} Permission object
354
+ */
355
+
356
+ var buildFolderPermission = function buildFolderPermission(folder) {
357
+ return {
358
+ // Legacy name
359
+ saveFolder: {
360
+ type: 'io.cozy.files',
361
+ values: [folder._id],
362
+ verbs: ['GET', 'PATCH', 'POST']
363
+ }
364
+ };
365
+ };
366
+ /**
367
+ * Replacer of the lodash/trim function
368
+ *
369
+ * @param {String} str - Input string
370
+ * @param {String} c - String to trim from the input string
371
+ * @returns {String}
372
+ */
373
+
374
+
375
+ exports.buildFolderPermission = buildFolderPermission;
376
+
377
+ var trim = function trim(str) {
378
+ var c = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '\\s';
379
+ return str.replace(new RegExp("^([".concat(c, "]*)(.*?)([").concat(c, "]*)$")), '$2');
380
+ };
@@ -0,0 +1,136 @@
1
+ import { ensureKonnectorFolder } from './konnectorFolder'
2
+
3
+ describe('ensureKonnectorFolder', () => {
4
+ const mockClient = {
5
+ collection: () => mockClient,
6
+ findReferencedBy: jest.fn(),
7
+ statByPath: jest.fn(),
8
+ add: jest.fn(),
9
+ addReferencesTo: jest.fn(),
10
+ createDirectoryByPath: jest.fn()
11
+ }
12
+ const existingMagicFolder = [
13
+ {
14
+ path: '/Administratif',
15
+ created_at: '2023-03-02T14:57:07.661588+01:00'
16
+ }
17
+ ]
18
+ const konnector = {
19
+ name: 'konnectorName',
20
+ folders: [
21
+ {
22
+ defaultDir: '$administrative/$konnector/$account'
23
+ }
24
+ ]
25
+ }
26
+
27
+ const account = { auth: { accountName: 'testAccountName' } }
28
+ beforeEach(() => {
29
+ mockClient.findReferencedBy.mockResolvedValue({
30
+ included: existingMagicFolder
31
+ })
32
+ })
33
+
34
+ afterEach(() => {
35
+ jest.resetAllMocks()
36
+ })
37
+
38
+ it('should create a folder when it does not exist and return it', async () => {
39
+ mockClient.statByPath.mockRejectedValueOnce({ status: 404 })
40
+ mockClient.createDirectoryByPath.mockResolvedValueOnce({
41
+ data: {
42
+ _id: 'createdfolderid'
43
+ }
44
+ })
45
+ const result = await ensureKonnectorFolder(mockClient, {
46
+ konnector,
47
+ account,
48
+ lang: 'fr'
49
+ })
50
+ expect(result).toStrictEqual({ _id: 'createdfolderid' })
51
+ expect(mockClient.statByPath).toHaveBeenCalledWith(
52
+ '/Administratif/konnectorName/testAccountName'
53
+ )
54
+ expect(mockClient.createDirectoryByPath).toHaveBeenCalledWith(
55
+ '/Administratif/konnectorName/testAccountName'
56
+ )
57
+ expect(mockClient.add).toHaveBeenCalledWith(konnector, {
58
+ saveFolder: {
59
+ type: 'io.cozy.files',
60
+ values: ['createdfolderid'],
61
+ verbs: ['GET', 'PATCH', 'POST']
62
+ }
63
+ })
64
+ expect(mockClient.addReferencesTo).toHaveBeenCalledWith(konnector, [
65
+ { _id: 'createdfolderid' }
66
+ ])
67
+ })
68
+ it('should not create a folder if it already exist', async () => {
69
+ const existingMagicFolder = [
70
+ {
71
+ path: '/Administratif',
72
+ created_at: '2023-03-02T14:57:07.661588+01:00'
73
+ }
74
+ ]
75
+ mockClient.findReferencedBy.mockResolvedValue({
76
+ included: existingMagicFolder
77
+ })
78
+ mockClient.statByPath.mockResolvedValueOnce({
79
+ data: {
80
+ _id: 'alreadyexistingfolderid'
81
+ }
82
+ })
83
+ const result = await ensureKonnectorFolder(mockClient, {
84
+ konnector: {
85
+ name: 'konnectorName',
86
+ folders: [
87
+ {
88
+ defaultDir: '$administrative/$konnector/$account'
89
+ }
90
+ ]
91
+ },
92
+ account: { auth: { accountName: 'testAccountName' } },
93
+ lang: 'fr'
94
+ })
95
+ expect(result).toStrictEqual({ _id: 'alreadyexistingfolderid' })
96
+ expect(mockClient.statByPath).toHaveBeenCalledWith(
97
+ '/Administratif/konnectorName/testAccountName'
98
+ )
99
+ expect(mockClient.createDirectoryByPath).not.toHaveBeenCalled()
100
+ })
101
+ it('should recreate a folder when the destination folder is in the trash', async () => {
102
+ mockClient.statByPath.mockResolvedValueOnce({
103
+ data: {
104
+ _id: 'folderintrash',
105
+ trashed: true
106
+ }
107
+ })
108
+ mockClient.createDirectoryByPath.mockResolvedValueOnce({
109
+ data: {
110
+ _id: 'createdfolderid'
111
+ }
112
+ })
113
+ const result = await ensureKonnectorFolder(mockClient, {
114
+ konnector,
115
+ account,
116
+ lang: 'fr'
117
+ })
118
+ expect(result).toStrictEqual({ _id: 'createdfolderid' })
119
+ expect(mockClient.statByPath).toHaveBeenCalledWith(
120
+ '/Administratif/konnectorName/testAccountName'
121
+ )
122
+ expect(mockClient.createDirectoryByPath).toHaveBeenCalledWith(
123
+ '/Administratif/konnectorName/testAccountName'
124
+ )
125
+ expect(mockClient.add).toHaveBeenCalledWith(konnector, {
126
+ saveFolder: {
127
+ type: 'io.cozy.files',
128
+ values: ['createdfolderid'],
129
+ verbs: ['GET', 'PATCH', 'POST']
130
+ }
131
+ })
132
+ expect(mockClient.addReferencesTo).toHaveBeenCalledWith(konnector, [
133
+ { _id: 'createdfolderid' }
134
+ ])
135
+ })
136
+ })
package/dist/types.js CHANGED
@@ -312,6 +312,7 @@ var _dsl = require("./queries/dsl");
312
312
  * @property {string} name - Name of the folder
313
313
  * @property {object} metadata - Metadata of the folder
314
314
  * @property {object} type - Type of the folder
315
+ * @property {string} path - Folder path
315
316
  * @typedef {CozyClientDocument & FolderDocument} IOCozyFolder - An io.cozy.files document
316
317
  */
317
318
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-client",
3
- "version": "41.2.1",
3
+ "version": "41.3.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.node.js",
6
6
  "browser": "dist/index.js",
@@ -75,5 +75,5 @@
75
75
  "react-native-ios11-devicecheck": "https://github.com/cozy/react-native-devicecheck#app-attest-v0.1"
76
76
  },
77
77
  "sideEffects": false,
78
- "gitHead": "f637f8b66229a89f5bc41bc4b9ccf8e96345f7c0"
78
+ "gitHead": "b69af4c4c28456f2d24e3dffa19f7921cb489e4e"
79
79
  }
@@ -6,3 +6,8 @@ export function getBoundT(lang: string): (label: string, opts?: {
6
6
  country?: string;
7
7
  smart_count?: number;
8
8
  }) => string;
9
+ /**
10
+ * @param {string} lang - fr, en, etc
11
+ * @returns {Function} - localization function
12
+ */
13
+ export function getLocalizer(lang: string): Function;
@@ -4,6 +4,7 @@ import * as instance from "./instance";
4
4
  import * as applications from "./applications";
5
5
  import * as file from "./file";
6
6
  import * as folder from "./folder";
7
+ import * as konnectorFolder from "./konnectorFolder";
7
8
  import * as note from "./note";
8
9
  import * as account from "./account";
9
10
  export const accounts: typeof account;
@@ -16,4 +17,4 @@ import * as sharing from "./sharing";
16
17
  import * as dacc from "./dacc";
17
18
  import * as paper from "./paper";
18
19
  import * as user from "./user";
19
- export { trigger, instance, applications, file, folder, note, account, permission, utils, contact, document, timeseries, sharing, dacc, paper, user };
20
+ export { trigger, instance, applications, file, folder, konnectorFolder, note, account, permission, utils, contact, document, timeseries, sharing, dacc, paper, user };
@@ -0,0 +1,12 @@
1
+ export function ensureKonnectorFolder(client: CozyClient, { konnector, account, lang }: {
2
+ konnector: import('../types').IOCozyKonnector;
3
+ account: import('../types').IOCozyAccount;
4
+ lang: string;
5
+ }): Promise<import('../types').IOCozyFolder>;
6
+ export function createDirectoryByPath(client: CozyClient, path: string): Promise<import('../types').IOCozyFolder>;
7
+ export function statDirectoryByPath(client: CozyClient, path: string): Promise<import('../types').IOCozyFolder | null>;
8
+ export function buildFolderPath(konnector: import('../types').IOCozyKonnector, account: import('../types').IOCozyAccount, magicFolders?: {
9
+ [x: string]: string;
10
+ }): string;
11
+ export function buildFolderPermission(folder: import('../types').IOCozyFolder): any;
12
+ import CozyClient from "../CozyClient";
@@ -52,7 +52,7 @@ export type Document = {
52
52
  _type: string;
53
53
  type: string;
54
54
  };
55
- export type PermissionVerb = "POST" | "DELETE" | "PUT" | "GET" | "ALL" | "PATCH";
55
+ export type PermissionVerb = "POST" | "DELETE" | "PUT" | "GET" | "PATCH" | "ALL";
56
56
  export type PermissionItem = {
57
57
  /**
58
58
  * - ALL, GET, PUT, PATCH, DELETE, POST…
package/types/types.d.ts CHANGED
@@ -579,6 +579,10 @@ export type FolderDocument = {
579
579
  * - Type of the folder
580
580
  */
581
581
  type: object;
582
+ /**
583
+ * - Folder path
584
+ */
585
+ path: string;
582
586
  };
583
587
  /**
584
588
  * - An io.cozy.files document