@cooperation/vc-storage 1.0.34 → 1.0.40
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.
- package/README.md +1 -1
- package/app.config.js +1 -0
- package/dist/index.js +3 -1
- package/dist/models/CredentialEngine.js +2 -2
- package/dist/models/GoogleDriveStorage.js +121 -273
- package/dist/models/Resume.js +16 -4
- package/dist/models/WASStorage.js +71 -0
- package/dist/scripts/test-was.js +61 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/models/CredentialEngine.d.ts +1 -1
- package/dist/types/models/GoogleDriveStorage.d.ts +31 -55
- package/dist/types/models/Resume.d.ts +3 -2
- package/dist/types/models/WASStorage.d.ts +30 -0
- package/dist/types/scripts/test-was.d.ts +1 -0
- package/dist/types/utils/createWASSpace.d.ts +9 -0
- package/dist/types/utils/credential.d.ts +1 -1
- package/dist/types/utils/getOrCreateAppDID.d.ts +11 -0
- package/dist/types/utils/google.d.ts +1 -1
- package/dist/utils/createWASSpace.js +27 -0
- package/dist/utils/credential.js +7 -16
- package/dist/utils/getOrCreateAppDID.js +22 -0
- package/dist/utils/google.js +1 -1
- package/package.json +6 -3
- package/dist/types/utils/presentation.d.ts +0 -10
- package/dist/utils/presentation.js +0 -45
package/README.md
CHANGED
package/app.config.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export const WAS_BASE_URL = 'http://localhost:4444';
|
package/dist/index.js
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
export * from './models/GoogleDriveStorage.js';
|
2
2
|
export * from './models/CredentialEngine.js';
|
3
3
|
export * from './utils/google.js';
|
4
|
-
export * from './utils/presentation.js';
|
5
4
|
export * from './models/Resume.js';
|
6
5
|
export * from './models/ResumeVC.js';
|
6
|
+
export * from './models/WASStorage.js';
|
7
|
+
export * from './utils/createWASSpace.js';
|
8
|
+
export * from './utils/getOrCreateAppDID.js';
|
@@ -126,7 +126,7 @@ export class CredentialEngine {
|
|
126
126
|
}
|
127
127
|
/**
|
128
128
|
* Sign a Verifiable Credential (VC)
|
129
|
-
* @param {'VC' | 'RECOMMENDATION'} type - The signature type.
|
129
|
+
* @param {'VC' | 'RECOMMENDATION' | 'EMPLOYMENT' | 'VOLUNTEERING' | 'PERFORMANCE_REVIEW'} type - The signature type.
|
130
130
|
* @param {string} issuerId - The ID of the issuer [currently we put it as the did id]
|
131
131
|
* @param {KeyPair} keyPair - The key pair to use for signing.
|
132
132
|
* @param {FormDataI | RecommendationFormDataI} formData - The form data to include in the VC.
|
@@ -269,7 +269,7 @@ export class CredentialEngine {
|
|
269
269
|
throw new Error('SEED environment variable not set. Cannot generate or use any DID.');
|
270
270
|
}
|
271
271
|
// Use deterministic keys from environment seed
|
272
|
-
const { getDidFromEnvSeed } = await import('../utils/decodedSeed');
|
272
|
+
const { getDidFromEnvSeed } = await import('../utils/decodedSeed.js');
|
273
273
|
const result = await getDidFromEnvSeed(encodedSeed);
|
274
274
|
keyPair = result.keyPair;
|
275
275
|
didDocument = result.didDocument;
|
@@ -2,21 +2,25 @@
|
|
2
2
|
* @class GoogleDriveStorage
|
3
3
|
* @description Class to interact with Google Drive API
|
4
4
|
* @param accessToken - Access token to authenticate with Google Drive API
|
5
|
-
* @method
|
6
|
-
* @method save - Save data to Google Drive
|
7
|
-
* @method addCommentToFile - Add a comment to a file in Google Drive
|
8
|
-
* @method addCommenterRoleToFile - Add commenter role to a file in Google Drive
|
5
|
+
* @method saveFile - Save a file to Google Drive
|
9
6
|
* @method retrieve - Retrieve a file from Google Drive
|
10
|
-
* @method
|
11
|
-
* @method
|
12
|
-
* @method
|
13
|
-
* @method
|
7
|
+
* @method createFolder - Create a new folder in Google Drive
|
8
|
+
* @method getOrCreateMediaFolder - Get the ID of the MEDIAs folder
|
9
|
+
* @method uploadBinaryFile - Upload a binary file to Google Drive
|
10
|
+
* @method updateFileData - Update the data of a file
|
11
|
+
* @method updateRelationsFile - Update the relations file
|
14
12
|
* @method delete - Delete a file from Google Drive
|
13
|
+
* @method checkEmailExists - Check if an email VC exists and return its content
|
14
|
+
* @method findFolders - Find folders in Google Drive
|
15
|
+
* @method findFolderFiles - Find files in a folder
|
15
16
|
*/
|
16
17
|
export class GoogleDriveStorage {
|
17
18
|
accessToken;
|
18
|
-
folderCache = {};
|
19
|
-
fileIdsCache = null;
|
19
|
+
static folderCache = {};
|
20
|
+
static fileIdsCache = null;
|
21
|
+
constructor(accessToken) {
|
22
|
+
this.accessToken = accessToken;
|
23
|
+
}
|
20
24
|
async updateFileIdsJson(newFileId) {
|
21
25
|
const constructUrl = () => {
|
22
26
|
const baseUrl = 'https://www.googleapis.com/drive/v3/files';
|
@@ -29,28 +33,28 @@ export class GoogleDriveStorage {
|
|
29
33
|
};
|
30
34
|
try {
|
31
35
|
// ✅ Fetch `file_ids.json` ID once per session (cached)
|
32
|
-
if (!
|
36
|
+
if (!GoogleDriveStorage.fileIdsCache) {
|
33
37
|
const existingFile = await this.fetcher({
|
34
38
|
method: 'GET',
|
35
39
|
headers: {},
|
36
40
|
url: constructUrl(),
|
37
41
|
});
|
38
42
|
if (existingFile.files.length > 0) {
|
39
|
-
|
43
|
+
GoogleDriveStorage.fileIdsCache = existingFile.files[0].id;
|
40
44
|
}
|
41
45
|
else {
|
42
46
|
console.log('No existing file_ids.json found, creating a new one.');
|
43
|
-
|
47
|
+
GoogleDriveStorage.fileIdsCache = null;
|
44
48
|
}
|
45
49
|
}
|
46
50
|
let existingFileIds = [];
|
47
51
|
// ✅ Fetch existing file IDs **only if `file_ids.json` exists**
|
48
|
-
if (
|
52
|
+
if (GoogleDriveStorage.fileIdsCache) {
|
49
53
|
try {
|
50
54
|
const fileContent = await this.fetcher({
|
51
55
|
method: 'GET',
|
52
56
|
headers: {},
|
53
|
-
url: `https://www.googleapis.com/drive/v3/files/${
|
57
|
+
url: `https://www.googleapis.com/drive/v3/files/${GoogleDriveStorage.fileIdsCache}?alt=media`,
|
54
58
|
});
|
55
59
|
existingFileIds = fileContent;
|
56
60
|
}
|
@@ -60,16 +64,13 @@ export class GoogleDriveStorage {
|
|
60
64
|
}
|
61
65
|
// ✅ Append the new file ID to the list
|
62
66
|
existingFileIds.push(newFileId);
|
63
|
-
console.log('File ID saved to appDataFolder.',
|
67
|
+
console.log('File ID saved to appDataFolder.', GoogleDriveStorage.fileIdsCache);
|
64
68
|
}
|
65
69
|
catch (error) {
|
66
70
|
console.error('Error updating file_ids.json:', error.message);
|
67
71
|
throw error;
|
68
72
|
}
|
69
73
|
}
|
70
|
-
constructor(accessToken) {
|
71
|
-
this.accessToken = accessToken;
|
72
|
-
}
|
73
74
|
async fetcher({ method, headers, body, url }) {
|
74
75
|
try {
|
75
76
|
const res = await fetch(url, {
|
@@ -120,6 +121,43 @@ export class GoogleDriveStorage {
|
|
120
121
|
});
|
121
122
|
return result.files || [];
|
122
123
|
}
|
124
|
+
async getOrCreateMediaFolder() {
|
125
|
+
if (GoogleDriveStorage.folderCache['MEDIAs']) {
|
126
|
+
return GoogleDriveStorage.folderCache['MEDIAs'];
|
127
|
+
}
|
128
|
+
const rootFolders = await this.findFolders();
|
129
|
+
let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
|
130
|
+
if (!credentialsFolder) {
|
131
|
+
credentialsFolder = await this.createFolder({ folderName: 'Credentials', parentFolderId: 'root' });
|
132
|
+
}
|
133
|
+
const subfolders = await this.findFolders(credentialsFolder.id);
|
134
|
+
let mediasFolder = subfolders.find((f) => f.name === 'MEDIAs');
|
135
|
+
if (!mediasFolder) {
|
136
|
+
mediasFolder = await this.createFolder({ folderName: 'MEDIAs', parentFolderId: credentialsFolder.id });
|
137
|
+
}
|
138
|
+
GoogleDriveStorage.folderCache['MEDIAs'] = mediasFolder.id;
|
139
|
+
return mediasFolder.id;
|
140
|
+
}
|
141
|
+
async findFolderFiles(folderId) {
|
142
|
+
if (!folderId)
|
143
|
+
throw new Error('Folder ID is required');
|
144
|
+
const files = await this.searchFiles(`'${folderId}' in parents`);
|
145
|
+
if (files.length === 0) {
|
146
|
+
console.log('No files found in the folder.');
|
147
|
+
return [];
|
148
|
+
}
|
149
|
+
const filesWithContent = await Promise.all(files.map(async (file) => {
|
150
|
+
try {
|
151
|
+
const content = await this.getFileContent(file.id);
|
152
|
+
return { ...file, content };
|
153
|
+
}
|
154
|
+
catch (error) {
|
155
|
+
console.error(`Error fetching content for file "${file.name}" (ID: ${file.id}):`, error);
|
156
|
+
return { ...file, content: null };
|
157
|
+
}
|
158
|
+
}));
|
159
|
+
return filesWithContent;
|
160
|
+
}
|
123
161
|
async createFolder({ folderName, parentFolderId }) {
|
124
162
|
if (!parentFolderId) {
|
125
163
|
throw new Error(`Parent folder ID must be provided when creating folder "${folderName}".`);
|
@@ -143,42 +181,29 @@ export class GoogleDriveStorage {
|
|
143
181
|
body: JSON.stringify({ role: 'reader', type: 'anyone' }),
|
144
182
|
});
|
145
183
|
// Invalidate cache for this parent folder
|
146
|
-
if (
|
147
|
-
delete
|
184
|
+
if (GoogleDriveStorage.folderCache[parentFolderId]) {
|
185
|
+
delete GoogleDriveStorage.folderCache[parentFolderId];
|
148
186
|
}
|
149
187
|
// Also clear 'root' cache if parent is root
|
150
|
-
if (parentFolderId === 'root' &&
|
151
|
-
delete
|
188
|
+
if (parentFolderId === 'root' && GoogleDriveStorage.folderCache['root']) {
|
189
|
+
delete GoogleDriveStorage.folderCache['root'];
|
152
190
|
}
|
153
191
|
return folder;
|
154
192
|
}
|
193
|
+
/**
|
194
|
+
* Get the ID of the MEDIAs folder (public wrapper for getOrCreateMediaFolder)
|
195
|
+
* @returns The folder ID for the MEDIAs folder
|
196
|
+
*/
|
155
197
|
async getMediaFolderId() {
|
156
|
-
|
157
|
-
return this.folderCache['MEDIAs'];
|
158
|
-
}
|
159
|
-
const rootFolders = await this.findFolders();
|
160
|
-
let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
|
161
|
-
if (!credentialsFolder) {
|
162
|
-
credentialsFolder = await this.createFolder({ folderName: 'Credentials', parentFolderId: 'root' });
|
163
|
-
}
|
164
|
-
const credentialsFolderId = credentialsFolder.id;
|
165
|
-
const subfolders = await this.findFolders(credentialsFolder.id);
|
166
|
-
let mediasFolder = subfolders.find((f) => f.name === 'MEDIAs');
|
167
|
-
if (!mediasFolder) {
|
168
|
-
mediasFolder = await this.createFolder({ folderName: 'MEDIAs', parentFolderId: credentialsFolderId });
|
169
|
-
}
|
170
|
-
const mediasFolderId = mediasFolder.id;
|
171
|
-
this.folderCache['MEDIAs'] = mediasFolderId;
|
172
|
-
return mediasFolderId;
|
198
|
+
return await this.getOrCreateMediaFolder();
|
173
199
|
}
|
174
200
|
async uploadBinaryFile({ file }) {
|
175
201
|
try {
|
176
|
-
const accessToken = this.accessToken;
|
202
|
+
const accessToken = this.accessToken;
|
177
203
|
if (!accessToken) {
|
178
204
|
throw new Error('Missing Google OAuth access token.');
|
179
205
|
}
|
180
|
-
const folderId = await this.
|
181
|
-
// ✅ Correct metadata for Google Drive API
|
206
|
+
const folderId = await this.getOrCreateMediaFolder();
|
182
207
|
const metadata = {
|
183
208
|
name: file.name,
|
184
209
|
mimeType: file.type,
|
@@ -209,14 +234,20 @@ export class GoogleDriveStorage {
|
|
209
234
|
throw error;
|
210
235
|
}
|
211
236
|
}
|
212
|
-
async saveFile({ data, folderId }) {
|
237
|
+
async saveFile({ data, folderId, fileId }) {
|
213
238
|
console.log('🚀 ~ GoogleDriveStorage ~ saveFile ~ data:', data);
|
214
239
|
try {
|
240
|
+
// If fileId is provided, update the existing file instead of creating a new one
|
241
|
+
if (fileId) {
|
242
|
+
console.log(`Updating existing file with ID: ${fileId}`);
|
243
|
+
return await this.updateFileContent({ fileId, data });
|
244
|
+
}
|
245
|
+
// For new files, folderId is required
|
215
246
|
if (!folderId) {
|
216
|
-
throw new Error('Folder ID is required to save a file.');
|
247
|
+
throw new Error('Folder ID is required to save a new file.');
|
217
248
|
}
|
218
249
|
const fileMetadata = {
|
219
|
-
name: data.fileName || data.name + '.json' || data.credentialSubject
|
250
|
+
name: data.fileName || data.name + '.json' || data.credentialSubject?.person?.name?.formattedName + '.json' || 'Untitled file.json',
|
220
251
|
parents: [folderId],
|
221
252
|
mimeType: data.mimeType || 'application/json',
|
222
253
|
};
|
@@ -248,11 +279,6 @@ export class GoogleDriveStorage {
|
|
248
279
|
throw error;
|
249
280
|
}
|
250
281
|
}
|
251
|
-
/**
|
252
|
-
* Get file from google drive by id
|
253
|
-
* @param id
|
254
|
-
* @returns file content
|
255
|
-
*/
|
256
282
|
async retrieve(id) {
|
257
283
|
const dataUrl = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`;
|
258
284
|
try {
|
@@ -293,63 +319,53 @@ export class GoogleDriveStorage {
|
|
293
319
|
return null;
|
294
320
|
}
|
295
321
|
}
|
296
|
-
/**
|
297
|
-
* Get folder by folderId, if folderId == null you will have them all
|
298
|
-
* @param folderId [Optional]
|
299
|
-
* @returns
|
300
|
-
*/
|
301
322
|
async findFolders(folderId) {
|
302
323
|
const cacheKey = folderId || 'root';
|
303
|
-
if (
|
304
|
-
return
|
324
|
+
if (GoogleDriveStorage.folderCache[cacheKey]) {
|
325
|
+
return GoogleDriveStorage.folderCache[cacheKey];
|
305
326
|
}
|
306
327
|
const query = folderId
|
307
328
|
? `'${folderId}' in parents and mimeType='application/vnd.google-apps.folder'`
|
308
329
|
: `'root' in parents and mimeType='application/vnd.google-apps.folder'`;
|
309
330
|
const folders = await this.searchFiles(query);
|
310
|
-
|
331
|
+
GoogleDriveStorage.folderCache[cacheKey] = folders;
|
311
332
|
return folders;
|
312
333
|
}
|
313
|
-
/**
|
314
|
-
* Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
|
315
|
-
* @param type
|
316
|
-
* @returns
|
317
|
-
*/
|
318
334
|
async getAllFilesByType(type) {
|
319
335
|
try {
|
320
|
-
if (!
|
336
|
+
if (!GoogleDriveStorage.folderCache['Credentials']) {
|
321
337
|
const rootFolders = await this.findFolders();
|
322
|
-
|
338
|
+
GoogleDriveStorage.folderCache['Credentials'] = rootFolders;
|
323
339
|
}
|
324
|
-
const credentialsFolder =
|
340
|
+
const credentialsFolder = GoogleDriveStorage.folderCache['Credentials'].find((f) => f.name === 'Credentials');
|
325
341
|
if (!credentialsFolder) {
|
326
342
|
console.error('Credentials folder not found.');
|
327
343
|
return [];
|
328
344
|
}
|
329
345
|
if (type === 'VCs') {
|
330
|
-
if (!
|
346
|
+
if (!GoogleDriveStorage.folderCache['VCs']) {
|
331
347
|
const vcSubfolder = await this.findFolders(credentialsFolder.id);
|
332
348
|
const vcsFolder = vcSubfolder.find((f) => f.name === 'VCs');
|
333
349
|
const vcSubFolders = await this.findFolders(vcsFolder.id);
|
334
|
-
|
350
|
+
GoogleDriveStorage.folderCache['VCs'] = vcSubFolders.filter((folder) => folder.name.startsWith('VC-'));
|
335
351
|
}
|
336
|
-
const vcSubfolders =
|
352
|
+
const vcSubfolders = GoogleDriveStorage.folderCache['VCs'];
|
337
353
|
if (!vcSubfolders.length) {
|
338
354
|
console.error(`No subfolders found for type: ${type}`);
|
339
355
|
return [];
|
340
356
|
}
|
341
|
-
const allFilesNested = await Promise.all(vcSubfolders.map(async (folder) => await this.
|
357
|
+
const allFilesNested = await Promise.all(vcSubfolders.map(async (folder) => await this.findFolderFiles(folder.id)));
|
342
358
|
const allVcJsonFiles = allFilesNested.flat().filter((file) => file.mimeType === 'application/json');
|
343
359
|
const fileContentsResults = await Promise.allSettled(allVcJsonFiles.map((file) => this.retrieve(file.id)));
|
344
360
|
const validFileContents = fileContentsResults.filter((result) => result.status === 'fulfilled').map((result) => result.value);
|
345
361
|
return validFileContents.filter((file) => file.data.fileName !== 'RELATIONS');
|
346
362
|
}
|
347
|
-
if (!
|
363
|
+
if (!GoogleDriveStorage.folderCache[type]) {
|
348
364
|
const subfolders = await this.findFolders(credentialsFolder.id);
|
349
365
|
const targetFolder = subfolders.find((f) => f.name === type);
|
350
|
-
|
366
|
+
GoogleDriveStorage.folderCache[type] = targetFolder ? targetFolder.id : null;
|
351
367
|
}
|
352
|
-
const targetFolderId =
|
368
|
+
const targetFolderId = GoogleDriveStorage.folderCache[type];
|
353
369
|
if (!targetFolderId) {
|
354
370
|
console.error(`Folder for type ${type} not found.`);
|
355
371
|
return [];
|
@@ -369,70 +385,52 @@ export class GoogleDriveStorage {
|
|
369
385
|
return [];
|
370
386
|
}
|
371
387
|
}
|
372
|
-
|
373
|
-
* Update the name of a file in Google Drive
|
374
|
-
* @param fileId - The ID of the file to update
|
375
|
-
* @param newFileName - The new name for the file
|
376
|
-
* @returns The updated file metadata, including the new name
|
377
|
-
*/
|
378
|
-
async updateFileName(fileId, newFileName) {
|
388
|
+
async updateFileData(fileId, data) {
|
379
389
|
try {
|
380
|
-
const
|
390
|
+
const updateUrl = `https://www.googleapis.com/drive/v3/files/${fileId}`;
|
381
391
|
const updatedFile = await this.fetcher({
|
382
392
|
method: 'PATCH',
|
383
393
|
headers: { 'Content-Type': 'application/json' },
|
384
|
-
body: JSON.stringify(
|
385
|
-
url:
|
394
|
+
body: JSON.stringify({ name: data.fileName }),
|
395
|
+
url: updateUrl,
|
386
396
|
});
|
387
|
-
console.log('File
|
397
|
+
console.log('✅ File renamed successfully:', updatedFile);
|
388
398
|
return updatedFile;
|
389
399
|
}
|
390
400
|
catch (error) {
|
391
|
-
console.error('Error updating file name:', error.message);
|
392
401
|
throw error;
|
393
402
|
}
|
394
403
|
}
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
}
|
403
|
-
async findFilesUnderFolder(folderId) {
|
404
|
-
if (!folderId)
|
405
|
-
throw new Error('Folder ID is required');
|
406
|
-
const files = await this.searchFiles(`'${folderId}' in parents`);
|
407
|
-
if (files.length === 0) {
|
408
|
-
console.log('No files found in the folder.');
|
409
|
-
return [];
|
410
|
-
}
|
411
|
-
const filesWithContent = await Promise.all(files.map(async (file) => {
|
412
|
-
try {
|
413
|
-
const content = await this.getFileContent(file.id);
|
414
|
-
return { ...file, content };
|
415
|
-
}
|
416
|
-
catch (error) {
|
417
|
-
console.error(`Error fetching content for file "${file.name}" (ID: ${file.id}):`, error);
|
418
|
-
return { ...file, content: null };
|
419
|
-
}
|
420
|
-
}));
|
421
|
-
return filesWithContent;
|
422
|
-
}
|
423
|
-
async updateFileData(fileId, data) {
|
404
|
+
/**
|
405
|
+
* Update the content of an existing file in Google Drive
|
406
|
+
* @param fileId - The ID of the file to update
|
407
|
+
* @param data - The new content for the file
|
408
|
+
* @returns The updated file metadata
|
409
|
+
*/
|
410
|
+
async updateFileContent({ fileId, data }) {
|
424
411
|
try {
|
425
|
-
|
426
|
-
|
412
|
+
if (!fileId) {
|
413
|
+
throw new Error('File ID is required to update a file.');
|
414
|
+
}
|
415
|
+
// Convert data to JSON blob
|
416
|
+
const fileBlob = new Blob([JSON.stringify(data)], { type: 'application/json' });
|
417
|
+
// Update the file content using media upload
|
418
|
+
// Note: When using uploadType=media, the response is the updated file resource
|
419
|
+
const response = await this.fetcher({
|
427
420
|
method: 'PATCH',
|
428
|
-
headers: {
|
429
|
-
|
430
|
-
|
421
|
+
headers: {
|
422
|
+
'Content-Type': 'application/json'
|
423
|
+
},
|
424
|
+
body: fileBlob,
|
425
|
+
url: `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media`,
|
431
426
|
});
|
432
|
-
console.log(
|
433
|
-
|
427
|
+
console.log(`✅ File content updated successfully: ${fileId}`);
|
428
|
+
// Return a consistent response format with id and data
|
429
|
+
// The response from media upload might be the file metadata or empty
|
430
|
+
return { id: fileId, data, ...response };
|
434
431
|
}
|
435
432
|
catch (error) {
|
433
|
+
console.error('Error updating file content:', error);
|
436
434
|
throw error;
|
437
435
|
}
|
438
436
|
}
|
@@ -460,12 +458,10 @@ export class GoogleDriveStorage {
|
|
460
458
|
return updateResponse;
|
461
459
|
}
|
462
460
|
async createRelationsFile({ vcFolderId }) {
|
463
|
-
const files = await this.
|
461
|
+
const files = await this.findFolderFiles(vcFolderId);
|
464
462
|
const vcFile = files.find((file) => file.name === 'VC');
|
465
463
|
const vcContent = await this.getFileContent(vcFile.id);
|
466
|
-
console.log('🚀 ~ GoogleDriveStorage ~ createRelationsFile ~ vcContent:', vcContent);
|
467
464
|
const subject = JSON.parse(vcContent.body).credentialSubject;
|
468
|
-
console.log('🚀 ~ GoogleDriveStorage ~ createRelationsFile ~ subject:', subject);
|
469
465
|
const relationsFile = await this.saveFile({
|
470
466
|
data: {
|
471
467
|
fileName: 'RELATIONS',
|
@@ -483,66 +479,6 @@ export class GoogleDriveStorage {
|
|
483
479
|
await this.updateFileIdsJson(relationsFile.id);
|
484
480
|
return relationsFile;
|
485
481
|
}
|
486
|
-
async updateResumeRelation({ authorFolderId, draftFileId, signedFileId, }) {
|
487
|
-
try {
|
488
|
-
const relationFileName = 'relations.json';
|
489
|
-
// Step 1: Check if `relations.json` exists in RESUMES_AUTHOR/
|
490
|
-
const existingRelationFiles = await this.searchFiles(`name='${relationFileName}' and '${authorFolderId}' in parents`);
|
491
|
-
let relationFileId = null;
|
492
|
-
let existingRelations = {};
|
493
|
-
if (existingRelationFiles.length > 0) {
|
494
|
-
relationFileId = existingRelationFiles[0].id;
|
495
|
-
existingRelations = await this.getFileContent(relationFileId);
|
496
|
-
}
|
497
|
-
else {
|
498
|
-
console.log('relations.json does not exist. Will create a new one.');
|
499
|
-
}
|
500
|
-
// Step 2: Update relations object
|
501
|
-
existingRelations[draftFileId] = signedFileId;
|
502
|
-
// Step 3: Create or update the file on Drive
|
503
|
-
const fileBlob = new Blob([JSON.stringify(existingRelations, null, 2)], {
|
504
|
-
type: 'application/json',
|
505
|
-
});
|
506
|
-
const formData = new FormData();
|
507
|
-
const metadata = {
|
508
|
-
name: relationFileName,
|
509
|
-
parents: [authorFolderId],
|
510
|
-
mimeType: 'application/json',
|
511
|
-
};
|
512
|
-
formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
|
513
|
-
formData.append('file', fileBlob);
|
514
|
-
let uploadedFile;
|
515
|
-
if (relationFileId) {
|
516
|
-
// Update existing file
|
517
|
-
uploadedFile = await this.fetcher({
|
518
|
-
method: 'PATCH',
|
519
|
-
headers: {},
|
520
|
-
body: formData,
|
521
|
-
url: `https://www.googleapis.com/upload/drive/v3/files/${relationFileId}?uploadType=multipart&fields=id,parents`,
|
522
|
-
});
|
523
|
-
}
|
524
|
-
else {
|
525
|
-
// Create new file
|
526
|
-
uploadedFile = await this.fetcher({
|
527
|
-
method: 'POST',
|
528
|
-
headers: {},
|
529
|
-
body: formData,
|
530
|
-
url: `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,parents`,
|
531
|
-
});
|
532
|
-
}
|
533
|
-
console.log(`✅ Resume relation updated. File ID: ${uploadedFile.id}`);
|
534
|
-
return uploadedFile;
|
535
|
-
}
|
536
|
-
catch (error) {
|
537
|
-
console.error('❌ Failed to update resume relation:', error);
|
538
|
-
throw error;
|
539
|
-
}
|
540
|
-
}
|
541
|
-
/**
|
542
|
-
* Delete file by id
|
543
|
-
* @param id
|
544
|
-
* @returns
|
545
|
-
*/
|
546
482
|
async delete(id) {
|
547
483
|
try {
|
548
484
|
const response = await this.fetcher({
|
@@ -558,94 +494,6 @@ export class GoogleDriveStorage {
|
|
558
494
|
return null;
|
559
495
|
}
|
560
496
|
}
|
561
|
-
async update(fileId, data) {
|
562
|
-
const metadata = {
|
563
|
-
name: data.fileName || 'resume.json',
|
564
|
-
mimeType: 'application/json',
|
565
|
-
};
|
566
|
-
const uploadUrl = `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`;
|
567
|
-
const formData = new FormData();
|
568
|
-
formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
|
569
|
-
formData.append('file', new Blob([JSON.stringify(data.body)], { type: 'application/json' }));
|
570
|
-
try {
|
571
|
-
const response = await this.fetcher({
|
572
|
-
method: 'PATCH',
|
573
|
-
headers: {},
|
574
|
-
body: formData,
|
575
|
-
url: `${uploadUrl}&fields=id,name,mimeType`,
|
576
|
-
});
|
577
|
-
console.log('✅ File updated successfully:', response);
|
578
|
-
return response;
|
579
|
-
}
|
580
|
-
catch (error) {
|
581
|
-
console.error('❌ Error updating Google Drive file:', error);
|
582
|
-
throw error;
|
583
|
-
}
|
584
|
-
}
|
585
|
-
async getFileIdsFromAppDataFolder() {
|
586
|
-
try {
|
587
|
-
const constructUrl = () => {
|
588
|
-
const baseUrl = 'https://www.googleapis.com/drive/v3/files';
|
589
|
-
const queryParams = new URLSearchParams({
|
590
|
-
spaces: 'appDataFolder',
|
591
|
-
q: "name='file_ids.json'",
|
592
|
-
fields: 'files(id)',
|
593
|
-
});
|
594
|
-
return `${baseUrl}?${queryParams.toString()}`;
|
595
|
-
};
|
596
|
-
// Step 1: Search for the file_ids.json file in the appDataFolder
|
597
|
-
const response = await this.fetcher({
|
598
|
-
method: 'GET',
|
599
|
-
headers: {},
|
600
|
-
url: constructUrl(),
|
601
|
-
});
|
602
|
-
console.log(': GoogleDriveStorage getFileIdsFromAppDataFolder response', response);
|
603
|
-
// Step 2: Check if the file exists
|
604
|
-
if (!response.files || response.files.length === 0) {
|
605
|
-
console.log('No file_ids.json found in appDataFolder.');
|
606
|
-
return [];
|
607
|
-
}
|
608
|
-
// Step 3: Get the file ID of file_ids.json
|
609
|
-
const fileId = response.files[0].id;
|
610
|
-
console.log(': GoogleDriveStorage getFileIdsFromAppDataFolder fileId', fileId);
|
611
|
-
// Step 4: Fetch the content of file_ids.json
|
612
|
-
const fileContent = await this.fetcher({
|
613
|
-
method: 'GET',
|
614
|
-
headers: {},
|
615
|
-
url: `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`,
|
616
|
-
});
|
617
|
-
console.log(': GoogleDriveStorage getFileIdsFromAppDataFolder fileContent', fileContent);
|
618
|
-
// Step 5: Parse the file content (array of file IDs)
|
619
|
-
const fileIds = fileContent;
|
620
|
-
console.log(': GoogleDriveStorage getFileIdsFromAppDataFolder fileIds', fileIds);
|
621
|
-
return fileIds;
|
622
|
-
}
|
623
|
-
catch (error) {
|
624
|
-
console.error('Error fetching file IDs from appDataFolder:', error.message);
|
625
|
-
return [];
|
626
|
-
}
|
627
|
-
}
|
628
|
-
async getAllFilesData() {
|
629
|
-
try {
|
630
|
-
// Step 1: Get the file IDs from appDataFolder
|
631
|
-
const fileIds = await this.getFileIdsFromAppDataFolder();
|
632
|
-
if (fileIds.length === 0) {
|
633
|
-
console.log('No files found.');
|
634
|
-
return [];
|
635
|
-
}
|
636
|
-
// Step 2: Return the array of file IDs
|
637
|
-
return fileIds;
|
638
|
-
}
|
639
|
-
catch (error) {
|
640
|
-
console.error('Error fetching all files data:', error.message);
|
641
|
-
return [];
|
642
|
-
}
|
643
|
-
}
|
644
|
-
/**
|
645
|
-
* Check if an email VC exists and return its content
|
646
|
-
* @param email - The email address to check
|
647
|
-
* @returns {Promise<{data: any, id: string} | null>} - The email VC content and ID if exists, null otherwise
|
648
|
-
*/
|
649
497
|
async checkEmailExists(email) {
|
650
498
|
try {
|
651
499
|
// Get root folders
|
package/dist/models/Resume.js
CHANGED
@@ -61,15 +61,27 @@ export class StorageHandler {
|
|
61
61
|
if (!folder) {
|
62
62
|
throw new Error(`${folderName} folder not found`);
|
63
63
|
}
|
64
|
-
return this.storage.
|
64
|
+
return this.storage.findFolderFiles(folder.id);
|
65
65
|
}
|
66
66
|
}
|
67
67
|
export class Resume extends StorageHandler {
|
68
68
|
constructor(storage) {
|
69
69
|
super(storage);
|
70
70
|
}
|
71
|
-
async saveResume({ resume, type }) {
|
71
|
+
async saveResume({ resume, type, id }) {
|
72
72
|
try {
|
73
|
+
// If an ID is provided, update the existing resume
|
74
|
+
if (id) {
|
75
|
+
console.log(`🔄 Updating existing ${type} resume with ID: ${id}`);
|
76
|
+
// Update the file content directly
|
77
|
+
const updatedResume = await this.storage.updateFileContent({
|
78
|
+
fileId: id,
|
79
|
+
data: resume
|
80
|
+
});
|
81
|
+
// Return the updated resume with its ID
|
82
|
+
return { ...updatedResume, id };
|
83
|
+
}
|
84
|
+
// For new resumes, create folders and save as before
|
73
85
|
let rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
|
74
86
|
console.log('🚀 ~ Resume ~ saveResume ~ rootFolder:', rootFolder);
|
75
87
|
// Get or create the subfolder
|
@@ -106,7 +118,7 @@ export class Resume extends StorageHandler {
|
|
106
118
|
// Find or create the signed resumes folder
|
107
119
|
const signedFolder = await this.getOrCreateFolder(resumeFolderTypes.signed, rootFolder.id);
|
108
120
|
// Retrieve all files from the signed folder
|
109
|
-
const files = await this.storage.
|
121
|
+
const files = await this.storage.findFolderFiles(signedFolder.id);
|
110
122
|
return files;
|
111
123
|
}
|
112
124
|
catch (error) {
|
@@ -120,7 +132,7 @@ export class Resume extends StorageHandler {
|
|
120
132
|
// Find or create the non-signed resumes folder
|
121
133
|
const nonSignedFolder = await this.getOrCreateFolder(resumeFolderTypes.nonSigned, rootFolder.id);
|
122
134
|
// Retrieve all files from the non-signed folder
|
123
|
-
const files = await this.storage.
|
135
|
+
const files = await this.storage.findFolderFiles(nonSignedFolder.id);
|
124
136
|
return files;
|
125
137
|
}
|
126
138
|
catch (error) {
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import { StorageClient } from '@wallet.storage/fetch-client';
|
2
|
+
import { WAS_BASE_URL } from '../../app.config.js';
|
3
|
+
export class LCWStorage {
|
4
|
+
static storageClient;
|
5
|
+
signer;
|
6
|
+
zcap;
|
7
|
+
spaceId;
|
8
|
+
constructor({ signer, zcap, spaceId }) {
|
9
|
+
this.signer = signer;
|
10
|
+
this.zcap = zcap;
|
11
|
+
this.spaceId = spaceId;
|
12
|
+
}
|
13
|
+
getStorageClient() {
|
14
|
+
if (!LCWStorage.storageClient) {
|
15
|
+
LCWStorage.storageClient = new StorageClient(new URL(WAS_BASE_URL));
|
16
|
+
}
|
17
|
+
return LCWStorage.storageClient;
|
18
|
+
}
|
19
|
+
getResource(key) {
|
20
|
+
const space = this.getStorageClient().space({
|
21
|
+
signer: this.signer,
|
22
|
+
id: this.spaceId,
|
23
|
+
});
|
24
|
+
return space.resource(key);
|
25
|
+
}
|
26
|
+
async add(key, value) {
|
27
|
+
const resource = this.getResource(key);
|
28
|
+
const blob = new Blob([JSON.stringify(value)], {
|
29
|
+
type: 'application/json',
|
30
|
+
});
|
31
|
+
const res = await resource.put(blob, {
|
32
|
+
signer: this.signer,
|
33
|
+
});
|
34
|
+
if (!res.ok) {
|
35
|
+
throw new Error(`Failed to add resource. Status: ${res.status}`);
|
36
|
+
}
|
37
|
+
return res;
|
38
|
+
}
|
39
|
+
async read(key) {
|
40
|
+
const resource = this.getResource(key);
|
41
|
+
const res = await resource.get({ signer: this.signer });
|
42
|
+
if (!res.ok) {
|
43
|
+
if (res.status === 404)
|
44
|
+
return null;
|
45
|
+
throw new Error(`Failed to read resource. Status: ${res.status}`);
|
46
|
+
}
|
47
|
+
return await res.json();
|
48
|
+
}
|
49
|
+
async update(key, value) {
|
50
|
+
return this.add(key, value); // Overwrite = update
|
51
|
+
}
|
52
|
+
async delete(key) {
|
53
|
+
const resource = this.getResource(key);
|
54
|
+
const res = await resource.delete({ signer: this.signer });
|
55
|
+
if (!res.ok && res.status !== 404) {
|
56
|
+
throw new Error(`Failed to delete resource. Status: ${res.status}`);
|
57
|
+
}
|
58
|
+
return true;
|
59
|
+
}
|
60
|
+
async list() {
|
61
|
+
// const space = this.getStorageClient().space({
|
62
|
+
// signer: this.signer,
|
63
|
+
// id: this.spaceId as `urn:uuid:${string}`,
|
64
|
+
// });
|
65
|
+
// const res = await space.resources().list({ signer: this.signer });
|
66
|
+
// if (!res.ok) {
|
67
|
+
// throw new Error(`Failed to list resources. Status: ${res.status}`);
|
68
|
+
// }
|
69
|
+
// return await res.json(); // Should contain list of resource IDs or summaries
|
70
|
+
}
|
71
|
+
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import { Ed25519Signer } from '@did.coop/did-key-ed25519';
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
3
|
+
import { LCWStorage } from '../models/WASStorage.js';
|
4
|
+
import { StorageClient } from '@wallet.storage/fetch-client';
|
5
|
+
import { WAS_BASE_URL } from '../../app.config.js';
|
6
|
+
async function main() {
|
7
|
+
const appDidSigner = await Ed25519Signer.generate();
|
8
|
+
console.log('Signer:', appDidSigner);
|
9
|
+
const spaceUUID = uuidv4();
|
10
|
+
const spaceId = `urn:uuid:${spaceUUID}`;
|
11
|
+
console.log('Space ID:', spaceId);
|
12
|
+
const storage = new StorageClient(new URL(WAS_BASE_URL));
|
13
|
+
const space = storage.space({
|
14
|
+
signer: appDidSigner,
|
15
|
+
id: spaceId,
|
16
|
+
});
|
17
|
+
const spaceObject = {
|
18
|
+
id: spaceId,
|
19
|
+
controller: appDidSigner.id.split('#')[0],
|
20
|
+
};
|
21
|
+
console.log('Creating space with object:', spaceObject);
|
22
|
+
const spaceObjectBlob = new Blob([JSON.stringify(spaceObject)], { type: 'application/json' });
|
23
|
+
// Create the space
|
24
|
+
const response = await space.put(spaceObjectBlob, {
|
25
|
+
signer: appDidSigner,
|
26
|
+
});
|
27
|
+
console.log('🚀 ~ main ~ response:', response);
|
28
|
+
console.log('Space PUT response:', {
|
29
|
+
status: response.status,
|
30
|
+
ok: response.ok,
|
31
|
+
});
|
32
|
+
if (!response.ok) {
|
33
|
+
throw new Error(`Failed to initialize space. Status: ${response.status}`);
|
34
|
+
}
|
35
|
+
// Store the signer for future connections
|
36
|
+
const signerJson = await appDidSigner.toJSON();
|
37
|
+
console.log('Signer JSON:', signerJson);
|
38
|
+
const lcwStorage = new LCWStorage({ signer: appDidSigner, zcap: {}, spaceId });
|
39
|
+
const res = await lcwStorage.add('test', { test: 'test' });
|
40
|
+
if (res.ok) {
|
41
|
+
console.log('Record added successfully');
|
42
|
+
}
|
43
|
+
else {
|
44
|
+
console.error('Failed to add record');
|
45
|
+
}
|
46
|
+
const res2 = await lcwStorage.read('test');
|
47
|
+
if (res2) {
|
48
|
+
console.log('Record read successfully');
|
49
|
+
}
|
50
|
+
else {
|
51
|
+
console.error('Failed to read record');
|
52
|
+
}
|
53
|
+
const res3 = await lcwStorage.update('test', { test: 'test2' });
|
54
|
+
if (res3.ok) {
|
55
|
+
console.log('Record updated successfully');
|
56
|
+
}
|
57
|
+
else {
|
58
|
+
console.error('Failed to update record');
|
59
|
+
}
|
60
|
+
}
|
61
|
+
main();
|
package/dist/types/index.d.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
export * from './models/GoogleDriveStorage.js';
|
2
2
|
export * from './models/CredentialEngine.js';
|
3
3
|
export * from './utils/google.js';
|
4
|
-
export * from './utils/presentation.js';
|
5
4
|
export * from './models/Resume.js';
|
6
5
|
export * from './models/ResumeVC.js';
|
6
|
+
export * from './models/WASStorage.js';
|
7
|
+
export * from './utils/createWASSpace.js';
|
8
|
+
export * from './utils/getOrCreateAppDID.js';
|
@@ -50,7 +50,7 @@ export declare class CredentialEngine {
|
|
50
50
|
}>;
|
51
51
|
/**
|
52
52
|
* Sign a Verifiable Credential (VC)
|
53
|
-
* @param {'VC' | 'RECOMMENDATION'} type - The signature type.
|
53
|
+
* @param {'VC' | 'RECOMMENDATION' | 'EMPLOYMENT' | 'VOLUNTEERING' | 'PERFORMANCE_REVIEW'} type - The signature type.
|
54
54
|
* @param {string} issuerId - The ID of the issuer [currently we put it as the did id]
|
55
55
|
* @param {KeyPair} keyPair - The key pair to use for signing.
|
56
56
|
* @param {FormDataI | RecommendationFormDataI} formData - The form data to include in the VC.
|
@@ -3,70 +3,64 @@ type FileType = 'KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs' | '
|
|
3
3
|
* @class GoogleDriveStorage
|
4
4
|
* @description Class to interact with Google Drive API
|
5
5
|
* @param accessToken - Access token to authenticate with Google Drive API
|
6
|
-
* @method
|
7
|
-
* @method save - Save data to Google Drive
|
8
|
-
* @method addCommentToFile - Add a comment to a file in Google Drive
|
9
|
-
* @method addCommenterRoleToFile - Add commenter role to a file in Google Drive
|
6
|
+
* @method saveFile - Save a file to Google Drive
|
10
7
|
* @method retrieve - Retrieve a file from Google Drive
|
11
|
-
* @method
|
12
|
-
* @method
|
13
|
-
* @method
|
14
|
-
* @method
|
8
|
+
* @method createFolder - Create a new folder in Google Drive
|
9
|
+
* @method getOrCreateMediaFolder - Get the ID of the MEDIAs folder
|
10
|
+
* @method uploadBinaryFile - Upload a binary file to Google Drive
|
11
|
+
* @method updateFileData - Update the data of a file
|
12
|
+
* @method updateRelationsFile - Update the relations file
|
15
13
|
* @method delete - Delete a file from Google Drive
|
14
|
+
* @method checkEmailExists - Check if an email VC exists and return its content
|
15
|
+
* @method findFolders - Find folders in Google Drive
|
16
|
+
* @method findFolderFiles - Find files in a folder
|
16
17
|
*/
|
17
18
|
export declare class GoogleDriveStorage {
|
18
19
|
private accessToken;
|
19
|
-
folderCache: any;
|
20
|
-
private fileIdsCache;
|
21
|
-
private updateFileIdsJson;
|
20
|
+
static folderCache: any;
|
21
|
+
private static fileIdsCache;
|
22
22
|
constructor(accessToken: string);
|
23
|
+
private updateFileIdsJson;
|
23
24
|
private fetcher;
|
24
25
|
private getFileContent;
|
25
26
|
private searchFiles;
|
27
|
+
private getOrCreateMediaFolder;
|
28
|
+
findFolderFiles(folderId: string): Promise<any[]>;
|
26
29
|
createFolder({ folderName, parentFolderId }: {
|
27
30
|
folderName: string;
|
28
31
|
parentFolderId: string;
|
29
32
|
}): Promise<any>;
|
30
|
-
|
33
|
+
/**
|
34
|
+
* Get the ID of the MEDIAs folder (public wrapper for getOrCreateMediaFolder)
|
35
|
+
* @returns The folder ID for the MEDIAs folder
|
36
|
+
*/
|
37
|
+
getMediaFolderId(): Promise<string>;
|
31
38
|
uploadBinaryFile({ file }: {
|
32
39
|
file: File;
|
33
40
|
}): Promise<any>;
|
34
|
-
saveFile({ data, folderId }: {
|
41
|
+
saveFile({ data, folderId, fileId }: {
|
35
42
|
data: any;
|
36
|
-
folderId
|
43
|
+
folderId?: string;
|
44
|
+
fileId?: string;
|
37
45
|
}): Promise<any>;
|
38
|
-
/**
|
39
|
-
* Get file from google drive by id
|
40
|
-
* @param id
|
41
|
-
* @returns file content
|
42
|
-
*/
|
43
46
|
retrieve(id: string): Promise<{
|
44
47
|
data: any;
|
45
48
|
id: string;
|
46
49
|
} | null>;
|
47
|
-
/**
|
48
|
-
* Get folder by folderId, if folderId == null you will have them all
|
49
|
-
* @param folderId [Optional]
|
50
|
-
* @returns
|
51
|
-
*/
|
52
50
|
findFolders(folderId?: string): Promise<any[]>;
|
53
|
-
/**
|
54
|
-
* Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
|
55
|
-
* @param type
|
56
|
-
* @returns
|
57
|
-
*/
|
58
51
|
getAllFilesByType(type: FileType): Promise<any[]>;
|
52
|
+
updateFileData(fileId: string, data: {
|
53
|
+
fileName: string;
|
54
|
+
}): Promise<any>;
|
59
55
|
/**
|
60
|
-
* Update the
|
56
|
+
* Update the content of an existing file in Google Drive
|
61
57
|
* @param fileId - The ID of the file to update
|
62
|
-
* @param
|
63
|
-
* @returns The updated file metadata
|
58
|
+
* @param data - The new content for the file
|
59
|
+
* @returns The updated file metadata
|
64
60
|
*/
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
updateFileData(fileId: string, data: {
|
69
|
-
fileName: string;
|
61
|
+
updateFileContent({ fileId, data }: {
|
62
|
+
fileId: string;
|
63
|
+
data: any;
|
70
64
|
}): Promise<any>;
|
71
65
|
getFileParents(fileId: string): Promise<any>;
|
72
66
|
updateRelationsFile({ relationsFileId, recommendationFileId }: {
|
@@ -76,25 +70,7 @@ export declare class GoogleDriveStorage {
|
|
76
70
|
createRelationsFile({ vcFolderId }: {
|
77
71
|
vcFolderId: string;
|
78
72
|
}): Promise<any>;
|
79
|
-
updateResumeRelation({ authorFolderId, draftFileId, signedFileId, }: {
|
80
|
-
authorFolderId: string;
|
81
|
-
draftFileId: string;
|
82
|
-
signedFileId: string;
|
83
|
-
}): Promise<any>;
|
84
|
-
/**
|
85
|
-
* Delete file by id
|
86
|
-
* @param id
|
87
|
-
* @returns
|
88
|
-
*/
|
89
73
|
delete(id: string): Promise<any>;
|
90
|
-
update(fileId: string, data: any): Promise<any>;
|
91
|
-
getFileIdsFromAppDataFolder(): Promise<any>;
|
92
|
-
getAllFilesData(): Promise<any>;
|
93
|
-
/**
|
94
|
-
* Check if an email VC exists and return its content
|
95
|
-
* @param email - The email address to check
|
96
|
-
* @returns {Promise<{data: any, id: string} | null>} - The email VC content and ID if exists, null otherwise
|
97
|
-
*/
|
98
74
|
checkEmailExists(email: string): Promise<{
|
99
75
|
data: any;
|
100
76
|
id: string;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { GoogleDriveStorage } from './GoogleDriveStorage
|
1
|
+
import { GoogleDriveStorage } from './GoogleDriveStorage';
|
2
2
|
export declare const resumeFolderTypes: {
|
3
3
|
root: string;
|
4
4
|
nonSigned: string;
|
@@ -14,9 +14,10 @@ export declare class StorageHandler {
|
|
14
14
|
}
|
15
15
|
export declare class Resume extends StorageHandler {
|
16
16
|
constructor(storage: GoogleDriveStorage);
|
17
|
-
saveResume({ resume, type }: {
|
17
|
+
saveResume({ resume, type, id }: {
|
18
18
|
resume: any;
|
19
19
|
type: 'sign' | 'unsigned';
|
20
|
+
id?: string;
|
20
21
|
}): Promise<any>;
|
21
22
|
find(): Promise<{
|
22
23
|
signed: any[];
|
@@ -0,0 +1,30 @@
|
|
1
|
+
export declare class LCWStorage {
|
2
|
+
private static storageClient;
|
3
|
+
private signer;
|
4
|
+
private zcap;
|
5
|
+
private spaceId;
|
6
|
+
constructor({ signer, zcap, spaceId }: {
|
7
|
+
signer: any;
|
8
|
+
zcap: any;
|
9
|
+
spaceId: string;
|
10
|
+
});
|
11
|
+
private getStorageClient;
|
12
|
+
private getResource;
|
13
|
+
add(key: string, value: any): Promise<{
|
14
|
+
ok: boolean;
|
15
|
+
headers: [string, string][];
|
16
|
+
status: number;
|
17
|
+
blob(): Promise<import("buffer").Blob>;
|
18
|
+
json(): Promise<unknown>;
|
19
|
+
}>;
|
20
|
+
read(key: string): Promise<unknown>;
|
21
|
+
update(key: string, value: any): Promise<{
|
22
|
+
ok: boolean;
|
23
|
+
headers: [string, string][];
|
24
|
+
status: number;
|
25
|
+
blob(): Promise<import("buffer").Blob>;
|
26
|
+
json(): Promise<unknown>;
|
27
|
+
}>;
|
28
|
+
delete(key: string): Promise<boolean>;
|
29
|
+
list(): Promise<void>;
|
30
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { Ed25519Signer } from '@did.coop/did-key-ed25519';
|
2
|
+
/**
|
3
|
+
* Create a new WAS space
|
4
|
+
* @returns {Promise<{ signer: InstanceType<typeof Ed25519Signer>; spaceId: `urn:uuid:${string}` }>}
|
5
|
+
*/
|
6
|
+
export declare function createSpace(): Promise<{
|
7
|
+
signer: InstanceType<typeof Ed25519Signer>;
|
8
|
+
spaceId: `urn:uuid:${string}`;
|
9
|
+
}>;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { KeyPair, DidDocument, FormDataI, RecommendationCredential, Credential, RecommendationFormDataI, VerifiableCredential, EmploymentFormDataI, VolunteeringFormDataI, PerformanceReviewFormDataI } from '../../types
|
1
|
+
import { KeyPair, DidDocument, FormDataI, RecommendationCredential, Credential, RecommendationFormDataI, VerifiableCredential, EmploymentFormDataI, VolunteeringFormDataI, PerformanceReviewFormDataI } from '../../types';
|
2
2
|
/**
|
3
3
|
* Create a DID document using the provided key pair.
|
4
4
|
* @param {KeyPair} keyPair - The key pair used to create the DID document.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
|
2
|
+
export interface AppInstanceKeyPair {
|
3
|
+
controller: string;
|
4
|
+
id: string;
|
5
|
+
publicKeyMultibase: string;
|
6
|
+
privateKeyMultibase: string;
|
7
|
+
}
|
8
|
+
export declare function getOrCreateAppInstanceDid(): Promise<{
|
9
|
+
did: string;
|
10
|
+
keyPair: Ed25519VerificationKey2020;
|
11
|
+
}>;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { GoogleDriveStorage } from '../models/GoogleDriveStorage
|
1
|
+
import { GoogleDriveStorage } from '../models/GoogleDriveStorage';
|
2
2
|
export type FileType = 'VC' | 'DID' | 'SESSION' | 'RECOMMENDATION' | 'KEYPAIR';
|
3
3
|
interface SaveToGooglePropsI {
|
4
4
|
storage: GoogleDriveStorage;
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { StorageClient } from '@wallet.storage/fetch-client';
|
2
|
+
import { Ed25519Signer } from '@did.coop/did-key-ed25519';
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
4
|
+
import { WAS_BASE_URL } from '../../app.config.js';
|
5
|
+
/**
|
6
|
+
* Create a new WAS space
|
7
|
+
* @returns {Promise<{ signer: InstanceType<typeof Ed25519Signer>; spaceId: `urn:uuid:${string}` }>}
|
8
|
+
*/
|
9
|
+
export async function createSpace() {
|
10
|
+
const signer = await Ed25519Signer.generate();
|
11
|
+
const controller = signer.id.split('#')[0];
|
12
|
+
const spaceUUID = uuidv4();
|
13
|
+
const spaceId = `urn:uuid:${spaceUUID}`;
|
14
|
+
const client = new StorageClient(new URL(WAS_BASE_URL));
|
15
|
+
const space = client.space({ signer, id: spaceId });
|
16
|
+
const spaceObject = {
|
17
|
+
id: spaceId,
|
18
|
+
controller,
|
19
|
+
};
|
20
|
+
const blob = new Blob([JSON.stringify(spaceObject)], { type: 'application/json' });
|
21
|
+
const res = await space.put(blob, { signer });
|
22
|
+
if (!res.ok) {
|
23
|
+
throw new Error(`Failed to initialize WAS space. Status: ${res.status}`);
|
24
|
+
}
|
25
|
+
console.log('✅ Provisioned and saved new WAS space');
|
26
|
+
return { signer, spaceId };
|
27
|
+
}
|
package/dist/utils/credential.js
CHANGED
@@ -177,10 +177,7 @@ export function generateUnsignedRecommendation({ vcId, recommendation, issuerDid
|
|
177
177
|
export function generateUnsignedEmployment({ formData, issuerDid }) {
|
178
178
|
const issuanceDate = new Date().toISOString();
|
179
179
|
const unsignedCredential = {
|
180
|
-
'@context': [
|
181
|
-
'https://www.w3.org/2018/credentials/v1',
|
182
|
-
employmentCredentialContext['@context'],
|
183
|
-
],
|
180
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', employmentCredentialContext['@context']],
|
184
181
|
id: '',
|
185
182
|
type: ['VerifiableCredential', 'EmploymentCredential'],
|
186
183
|
issuer: { id: issuerDid, type: ['Profile'] },
|
@@ -192,7 +189,7 @@ export function generateUnsignedEmployment({ formData, issuerDid }) {
|
|
192
189
|
credentialName: formData.credentialName,
|
193
190
|
credentialDuration: formData.credentialDuration,
|
194
191
|
credentialDescription: formData.credentialDescription,
|
195
|
-
portfolio: formData.portfolio.map(item => ({ name: item.name, url: item.url })),
|
192
|
+
portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
|
196
193
|
evidenceLink: formData.evidenceLink,
|
197
194
|
evidenceDescription: formData.evidenceDescription,
|
198
195
|
company: formData.company,
|
@@ -208,10 +205,7 @@ export function generateUnsignedEmployment({ formData, issuerDid }) {
|
|
208
205
|
export function generateUnsignedVolunteering({ formData, issuerDid }) {
|
209
206
|
const issuanceDate = new Date().toISOString();
|
210
207
|
const unsignedCredential = {
|
211
|
-
'@context': [
|
212
|
-
'https://www.w3.org/2018/credentials/v1',
|
213
|
-
volunteeringCredentialContext['@context'],
|
214
|
-
],
|
208
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', volunteeringCredentialContext['@context']],
|
215
209
|
id: '',
|
216
210
|
type: ['VerifiableCredential', 'VolunteeringCredential'],
|
217
211
|
issuer: { id: issuerDid, type: ['Profile'] },
|
@@ -223,10 +217,10 @@ export function generateUnsignedVolunteering({ formData, issuerDid }) {
|
|
223
217
|
volunteerWork: formData.volunteerWork,
|
224
218
|
volunteerOrg: formData.volunteerOrg,
|
225
219
|
volunteerDescription: formData.volunteerDescription,
|
226
|
-
skillsGained: formData.skillsGained ? formData.skillsGained.split(',').map(s => s.trim()) : undefined,
|
220
|
+
skillsGained: formData.skillsGained ? formData.skillsGained.split(',').map((s) => s.trim()) : undefined,
|
227
221
|
duration: formData.duration,
|
228
222
|
volunteerDates: formData.volunteerDates,
|
229
|
-
portfolio: formData.portfolio.map(item => ({ name: item.name, url: item.url })),
|
223
|
+
portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
|
230
224
|
evidenceLink: formData.evidenceLink,
|
231
225
|
evidenceDescription: formData.evidenceDescription,
|
232
226
|
},
|
@@ -240,10 +234,7 @@ export function generateUnsignedVolunteering({ formData, issuerDid }) {
|
|
240
234
|
export function generateUnsignedPerformanceReview({ formData, issuerDid }) {
|
241
235
|
const issuanceDate = new Date().toISOString();
|
242
236
|
const unsignedCredential = {
|
243
|
-
'@context': [
|
244
|
-
'https://www.w3.org/2018/credentials/v1',
|
245
|
-
performanceReviewCredentialContext['@context'],
|
246
|
-
],
|
237
|
+
'@context': ['https://www.w3.org/2018/credentials/v1', performanceReviewCredentialContext['@context']],
|
247
238
|
id: '',
|
248
239
|
type: ['VerifiableCredential', 'PerformanceReviewCredential'],
|
249
240
|
issuer: { id: issuerDid, type: ['Profile'] },
|
@@ -266,7 +257,7 @@ export function generateUnsignedPerformanceReview({ formData, issuerDid }) {
|
|
266
257
|
overallRating: formData.overallRating,
|
267
258
|
reviewComments: formData.reviewComments,
|
268
259
|
goalsNext: formData.goalsNext,
|
269
|
-
portfolio: formData.portfolio.map(item => ({ name: item.name, url: item.url })),
|
260
|
+
portfolio: formData.portfolio.map((item) => ({ name: item.name, url: item.url })),
|
270
261
|
evidenceLink: formData.evidenceLink,
|
271
262
|
evidenceDescription: formData.evidenceDescription,
|
272
263
|
},
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
|
2
|
+
const LOCAL_STORAGE_KEY = 'AppInstanceDID';
|
3
|
+
export async function getOrCreateAppInstanceDid() {
|
4
|
+
const stored = localStorage.getItem(LOCAL_STORAGE_KEY);
|
5
|
+
if (stored) {
|
6
|
+
const parsed = JSON.parse(stored);
|
7
|
+
const keyPair = await Ed25519VerificationKey2020.from(parsed);
|
8
|
+
return { did: keyPair.controller, keyPair };
|
9
|
+
}
|
10
|
+
const keyPair = await Ed25519VerificationKey2020.generate();
|
11
|
+
keyPair.controller = `did:key:${keyPair.publicKeyMultibase}`;
|
12
|
+
keyPair.id = `${keyPair.controller}#${keyPair.publicKeyMultibase}`;
|
13
|
+
keyPair.revoked = false;
|
14
|
+
const did = keyPair.controller;
|
15
|
+
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify({
|
16
|
+
controller: keyPair.controller,
|
17
|
+
id: keyPair.id,
|
18
|
+
publicKeyMultibase: keyPair.publicKeyMultibase,
|
19
|
+
privateKeyMultibase: keyPair.privateKeyMultibase,
|
20
|
+
}));
|
21
|
+
return { did, keyPair };
|
22
|
+
}
|
package/dist/utils/google.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
export const getVCWithRecommendations = async ({ vcId, storage }) => {
|
2
2
|
try {
|
3
3
|
const vcFolderId = await storage.getFileParents(vcId);
|
4
|
-
const files = await storage.
|
4
|
+
const files = await storage.findFolderFiles(vcFolderId);
|
5
5
|
const relationsFile = files.find((f) => f.name === 'RELATIONS');
|
6
6
|
const relationsContent = await storage.retrieve(relationsFile.id);
|
7
7
|
const relationsData = relationsContent.data.body ? JSON.parse(relationsContent.data.body) : relationsContent.data;
|
package/package.json
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
{
|
2
2
|
"name": "@cooperation/vc-storage",
|
3
3
|
"type": "module",
|
4
|
-
"version": "1.0.
|
4
|
+
"version": "1.0.40",
|
5
5
|
"description": "Sign and store your verifiable credentials.",
|
6
6
|
"main": "dist/index.js",
|
7
7
|
"types": "dist/types/index.d.ts",
|
8
8
|
"files": [
|
9
|
-
"dist"
|
9
|
+
"dist",
|
10
|
+
"app.config.js"
|
10
11
|
],
|
11
12
|
"scripts": {
|
12
13
|
"build": "tsc",
|
@@ -16,10 +17,12 @@
|
|
16
17
|
"author": "cooperation",
|
17
18
|
"license": "ISC",
|
18
19
|
"dependencies": {
|
20
|
+
"@did.coop/did-key-ed25519": "^0.0.13",
|
19
21
|
"@digitalbazaar/did-method-key": "^5.2.0",
|
20
22
|
"@digitalbazaar/ed25519-signature-2020": "^5.4.0",
|
21
23
|
"@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
|
22
24
|
"@digitalbazaar/vc": "^6.3.0",
|
25
|
+
"@wallet.storage/fetch-client": "^1.2.0",
|
23
26
|
"add": "^2.0.6",
|
24
27
|
"bnid": "^3.0.0",
|
25
28
|
"crypto-js": "^4.2.0",
|
@@ -37,7 +40,7 @@
|
|
37
40
|
"@types/jest": "^29.5.14",
|
38
41
|
"@types/uuid": "^10.0.0",
|
39
42
|
"babel-jest": "^29.7.0",
|
40
|
-
"typescript": "^5.8.
|
43
|
+
"typescript": "^5.8.3",
|
41
44
|
"vitest": "^3.0.5"
|
42
45
|
}
|
43
46
|
}
|
@@ -1,10 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Create and sign a Verifiable Presentation (VP) from a given Verifiable Credential (VC) file and any associated recommendations.
|
3
|
-
* @param {string} accessTokens - The access tokens for the user.
|
4
|
-
* @param {string} vcFileId - The ID of the Verifiable Credential (VC) file in Google Drive.
|
5
|
-
* @returns {Promise<{ signedPresentation: object } | null>} - The signed Verifiable Presentation (VP) or null if an error occurs.
|
6
|
-
* @throws Will throw an error if the VC is not found, a matching key pair cannot be located, or any part of the signing process fails.
|
7
|
-
*/
|
8
|
-
export declare const createAndSignVerifiablePresentation: (accessTokens: string, vcFileId: string) => Promise<{
|
9
|
-
signedPresentation: object;
|
10
|
-
} | null>;
|
@@ -1,45 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Create and sign a Verifiable Presentation (VP) from a given Verifiable Credential (VC) file and any associated recommendations.
|
3
|
-
* @param {string} accessTokens - The access tokens for the user.
|
4
|
-
* @param {string} vcFileId - The ID of the Verifiable Credential (VC) file in Google Drive.
|
5
|
-
* @returns {Promise<{ signedPresentation: object } | null>} - The signed Verifiable Presentation (VP) or null if an error occurs.
|
6
|
-
* @throws Will throw an error if the VC is not found, a matching key pair cannot be located, or any part of the signing process fails.
|
7
|
-
*/
|
8
|
-
export const createAndSignVerifiablePresentation = async (accessTokens, vcFileId) => {
|
9
|
-
if (!accessTokens || !vcFileId) {
|
10
|
-
console.error('Invalid input: Access tokens and VC file ID are required.');
|
11
|
-
return null;
|
12
|
-
}
|
13
|
-
try {
|
14
|
-
// const storage = new GoogleDriveStorage(accessTokens);
|
15
|
-
// const engine = new CredentialEngine(accessTokens);
|
16
|
-
// // Fetch Verifiable Credential (VC)
|
17
|
-
// const verifiableCredential = await storage.retrieve(vcFileId);
|
18
|
-
// if (!verifiableCredential) {
|
19
|
-
// throw new Error('Verifiable Credential not found.');
|
20
|
-
// }
|
21
|
-
// // Fetch VC comments (potential recommendations)
|
22
|
-
// const verifiableCredentialComments = await storage.getFileComments(vcFileId);
|
23
|
-
// let recommendations: object[] = [];
|
24
|
-
// // Extract recommendations from comments if present
|
25
|
-
// if (verifiableCredentialComments.length > 0) {
|
26
|
-
// for (const comment of verifiableCredentialComments) {
|
27
|
-
// console.log('🚀 ~ createAndSignVerifiablePresentation ~ comment', comment);
|
28
|
-
// const recommendationFile = await storage.retrieve(extractGoogleDriveFileId(comment.content));
|
29
|
-
// console.log('🚀 ~ createAndSignVerifiablePresentation ~ recommendationFile', recommendationFile);
|
30
|
-
// if (recommendationFile) {
|
31
|
-
// recommendations.push(recommendationFile);
|
32
|
-
// }
|
33
|
-
// }
|
34
|
-
// }
|
35
|
-
// // Create Verifiable Presentation (VP) with the retrieved VC
|
36
|
-
// const presentation = await engine.createPresentation([verifiableCredential.data, ...recommendations]); //! do not edit the array order!!
|
37
|
-
// // Use the key pair to sign the presentation
|
38
|
-
// const signedPresentation = await engine.signPresentation(presentation);
|
39
|
-
// return {};
|
40
|
-
}
|
41
|
-
catch (error) {
|
42
|
-
console.error('Error during Verifiable Presentation creation and signing:', error);
|
43
|
-
return null;
|
44
|
-
}
|
45
|
-
};
|