@cooperation/vc-storage 1.0.25 → 1.0.27
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/dist/models/GoogleDriveStorage.js +56 -1
- package/dist/models/Resume.js +69 -24
- package/dist/models/ResumeVC.js +4 -2
- package/dist/types/models/GoogleDriveStorage.d.ts +5 -0
- package/dist/types/models/Resume.d.ts +3 -1
- package/dist/types/utils/context.d.ts +2 -2
- package/dist/utils/context.js +2 -2
- package/package.json +2 -2
@@ -208,7 +208,7 @@ export class GoogleDriveStorage {
|
|
208
208
|
throw new Error('Folder ID is required to save a file.');
|
209
209
|
}
|
210
210
|
const fileMetadata = {
|
211
|
-
name: data.fileName || '
|
211
|
+
name: data.fileName || data.name + '.json' || data.credentialSubject.person.name.formattedName + '.json' || 'Untitled file.json',
|
212
212
|
parents: [folderId],
|
213
213
|
mimeType: data.mimeType || 'application/json',
|
214
214
|
};
|
@@ -475,6 +475,61 @@ export class GoogleDriveStorage {
|
|
475
475
|
await this.updateFileIdsJson(relationsFile.id);
|
476
476
|
return relationsFile;
|
477
477
|
}
|
478
|
+
async updateResumeRelation({ authorFolderId, draftFileId, signedFileId, }) {
|
479
|
+
try {
|
480
|
+
const relationFileName = 'relations.json';
|
481
|
+
// Step 1: Check if `relations.json` exists in RESUMES_AUTHOR/
|
482
|
+
const existingRelationFiles = await this.searchFiles(`name='${relationFileName}' and '${authorFolderId}' in parents`);
|
483
|
+
let relationFileId = null;
|
484
|
+
let existingRelations = {};
|
485
|
+
if (existingRelationFiles.length > 0) {
|
486
|
+
relationFileId = existingRelationFiles[0].id;
|
487
|
+
existingRelations = await this.getFileContent(relationFileId);
|
488
|
+
}
|
489
|
+
else {
|
490
|
+
console.log('relations.json does not exist. Will create a new one.');
|
491
|
+
}
|
492
|
+
// Step 2: Update relations object
|
493
|
+
existingRelations[draftFileId] = signedFileId;
|
494
|
+
// Step 3: Create or update the file on Drive
|
495
|
+
const fileBlob = new Blob([JSON.stringify(existingRelations, null, 2)], {
|
496
|
+
type: 'application/json',
|
497
|
+
});
|
498
|
+
const formData = new FormData();
|
499
|
+
const metadata = {
|
500
|
+
name: relationFileName,
|
501
|
+
parents: [authorFolderId],
|
502
|
+
mimeType: 'application/json',
|
503
|
+
};
|
504
|
+
formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
|
505
|
+
formData.append('file', fileBlob);
|
506
|
+
let uploadedFile;
|
507
|
+
if (relationFileId) {
|
508
|
+
// Update existing file
|
509
|
+
uploadedFile = await this.fetcher({
|
510
|
+
method: 'PATCH',
|
511
|
+
headers: {},
|
512
|
+
body: formData,
|
513
|
+
url: `https://www.googleapis.com/upload/drive/v3/files/${relationFileId}?uploadType=multipart&fields=id,parents`,
|
514
|
+
});
|
515
|
+
}
|
516
|
+
else {
|
517
|
+
// Create new file
|
518
|
+
uploadedFile = await this.fetcher({
|
519
|
+
method: 'POST',
|
520
|
+
headers: {},
|
521
|
+
body: formData,
|
522
|
+
url: `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,parents`,
|
523
|
+
});
|
524
|
+
}
|
525
|
+
console.log(`✅ Resume relation updated. File ID: ${uploadedFile.id}`);
|
526
|
+
return uploadedFile;
|
527
|
+
}
|
528
|
+
catch (error) {
|
529
|
+
console.error('❌ Failed to update resume relation:', error);
|
530
|
+
throw error;
|
531
|
+
}
|
532
|
+
}
|
478
533
|
/**
|
479
534
|
* Delete file by id
|
480
535
|
* @param id
|
package/dist/models/Resume.js
CHANGED
@@ -5,21 +5,55 @@ export const resumeFolderTypes = {
|
|
5
5
|
};
|
6
6
|
export class StorageHandler {
|
7
7
|
storage;
|
8
|
+
folderCreationPromises = {};
|
9
|
+
createdFolders = {};
|
8
10
|
constructor(storage) {
|
9
11
|
this.storage = storage;
|
10
12
|
}
|
11
13
|
async getOrCreateFolder(folderName, parentId) {
|
12
|
-
const
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
folder = await this.storage.createFolder({
|
18
|
-
folderName,
|
19
|
-
parentFolderId: parentId,
|
20
|
-
});
|
14
|
+
const cacheKey = `${parentId}_${folderName}`;
|
15
|
+
// Check our local cache first (this is separate from the storage's cache)
|
16
|
+
if (this.createdFolders[cacheKey]) {
|
17
|
+
console.log(`Using cached folder ${folderName} (${this.createdFolders[cacheKey].id}) under ${parentId}`);
|
18
|
+
return this.createdFolders[cacheKey];
|
21
19
|
}
|
22
|
-
|
20
|
+
// If there's an existing promise for this folder, wait for it
|
21
|
+
if (this.folderCreationPromises[cacheKey]) {
|
22
|
+
console.log(`Waiting for existing folder creation: ${folderName} under ${parentId}`);
|
23
|
+
return this.folderCreationPromises[cacheKey];
|
24
|
+
}
|
25
|
+
// Create a new promise for this folder operation
|
26
|
+
this.folderCreationPromises[cacheKey] = (async () => {
|
27
|
+
try {
|
28
|
+
// Double-check if folder exists
|
29
|
+
console.log(`Searching for folder ${folderName} under ${parentId}`);
|
30
|
+
const folders = await this.storage.findFolders(parentId);
|
31
|
+
let folder = folders.find((f) => f.name === folderName);
|
32
|
+
if (folder) {
|
33
|
+
console.log(`Found existing folder ${folderName} (${folder.id}) under ${parentId}`);
|
34
|
+
}
|
35
|
+
else {
|
36
|
+
console.log(`Creating folder ${folderName} under ${parentId} (no existing folder found)`);
|
37
|
+
folder = await this.storage.createFolder({
|
38
|
+
folderName,
|
39
|
+
parentFolderId: parentId,
|
40
|
+
});
|
41
|
+
console.log(`Created folder ${folderName} (${folder.id}) under ${parentId}`);
|
42
|
+
}
|
43
|
+
// Store in our local cache
|
44
|
+
this.createdFolders[cacheKey] = folder;
|
45
|
+
return folder;
|
46
|
+
}
|
47
|
+
catch (error) {
|
48
|
+
console.error(`Error in getOrCreateFolder(${folderName}, ${parentId}):`, error);
|
49
|
+
throw error;
|
50
|
+
}
|
51
|
+
finally {
|
52
|
+
// Clean up the promise after completion
|
53
|
+
delete this.folderCreationPromises[cacheKey];
|
54
|
+
}
|
55
|
+
})();
|
56
|
+
return this.folderCreationPromises[cacheKey];
|
23
57
|
}
|
24
58
|
async findFilesInFolder(folderName) {
|
25
59
|
const folders = await this.storage.findFolders();
|
@@ -36,12 +70,8 @@ export class Resume extends StorageHandler {
|
|
36
70
|
}
|
37
71
|
async saveResume({ resume, type }) {
|
38
72
|
try {
|
39
|
-
|
40
|
-
|
41
|
-
let rootFolder = rootFolders.find((folder) => folder.name === resumeFolderTypes.root);
|
42
|
-
if (!rootFolder) {
|
43
|
-
rootFolder = await this.storage.createFolder({ folderName: resumeFolderTypes.root, parentFolderId: 'root' });
|
44
|
-
}
|
73
|
+
let rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
|
74
|
+
console.log('🚀 ~ Resume ~ saveResume ~ rootFolder:', rootFolder);
|
45
75
|
// Get or create the subfolder
|
46
76
|
const subFolderName = type === 'sign' ? resumeFolderTypes.signed : resumeFolderTypes.nonSigned;
|
47
77
|
const subFolder = await this.getOrCreateFolder(subFolderName, rootFolder.id);
|
@@ -72,7 +102,7 @@ export class Resume extends StorageHandler {
|
|
72
102
|
async getSignedResumes() {
|
73
103
|
try {
|
74
104
|
// Find the root folder first
|
75
|
-
const rootFolder = await this.
|
105
|
+
const rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
|
76
106
|
// Find or create the signed resumes folder
|
77
107
|
const signedFolder = await this.getOrCreateFolder(resumeFolderTypes.signed, rootFolder.id);
|
78
108
|
// Retrieve all files from the signed folder
|
@@ -86,7 +116,7 @@ export class Resume extends StorageHandler {
|
|
86
116
|
async getNonSignedResumes() {
|
87
117
|
try {
|
88
118
|
// Find the root folder first
|
89
|
-
const rootFolder = await this.
|
119
|
+
const rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
|
90
120
|
// Find or create the non-signed resumes folder
|
91
121
|
const nonSignedFolder = await this.getOrCreateFolder(resumeFolderTypes.nonSigned, rootFolder.id);
|
92
122
|
// Retrieve all files from the non-signed folder
|
@@ -97,13 +127,28 @@ export class Resume extends StorageHandler {
|
|
97
127
|
throw new Error('Error while fetching non-signed resumes: ' + error.message);
|
98
128
|
}
|
99
129
|
}
|
100
|
-
async
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
130
|
+
async saveResumeDraft(data, signedResumeId) {
|
131
|
+
try {
|
132
|
+
const fileName = `FinalDraft_${signedResumeId}.json`;
|
133
|
+
// 1. Find or create root and NON_SIGNED_RESUMES folder
|
134
|
+
const rootFolder = await this.getOrCreateFolder(resumeFolderTypes.root, 'root');
|
135
|
+
const nonSignedFolder = await this.getOrCreateFolder(resumeFolderTypes.nonSigned, rootFolder.id);
|
136
|
+
const dataWithFileName = {
|
137
|
+
...data,
|
138
|
+
fileName: `FinalDraft_${signedResumeId}.json`,
|
139
|
+
};
|
140
|
+
// Save the file
|
141
|
+
const savedDraft = await this.storage.saveFile({
|
142
|
+
data: dataWithFileName,
|
143
|
+
folderId: nonSignedFolder.id,
|
144
|
+
});
|
145
|
+
console.log(`✅ Draft saved as ${fileName}`);
|
146
|
+
return savedDraft;
|
147
|
+
}
|
148
|
+
catch (error) {
|
149
|
+
console.error('❌ Error saving resume draft:', error);
|
150
|
+
throw new Error('Failed to save resume draft: ' + error.message);
|
105
151
|
}
|
106
|
-
return rootFolder;
|
107
152
|
}
|
108
153
|
isResumeFolderExist() { }
|
109
154
|
}
|
package/dist/models/ResumeVC.js
CHANGED
@@ -18,8 +18,7 @@ export class ResumeVC {
|
|
18
18
|
suite,
|
19
19
|
documentLoader: customDocumentLoader,
|
20
20
|
});
|
21
|
-
console.log('
|
22
|
-
console.log('Signed VC:', JSON.stringify(signedVC));
|
21
|
+
console.log('Signed VC:', signedVC);
|
23
22
|
}
|
24
23
|
catch (error) {
|
25
24
|
console.error('Error signing VC:', error.message);
|
@@ -73,6 +72,7 @@ export class ResumeVC {
|
|
73
72
|
},
|
74
73
|
title: exp.title || '',
|
75
74
|
description: exp.description || '',
|
75
|
+
duration: exp.duration || '',
|
76
76
|
startDate: exp.startDate || '',
|
77
77
|
endDate: exp.endDate || '',
|
78
78
|
stillEmployed: exp.stillEmployed || false,
|
@@ -85,6 +85,7 @@ export class ResumeVC {
|
|
85
85
|
institution: edu.institution || '',
|
86
86
|
degree: edu.degree || '',
|
87
87
|
fieldOfStudy: edu.fieldOfStudy || '',
|
88
|
+
duration: edu.duration || '',
|
88
89
|
startDate: edu.startDate || '',
|
89
90
|
endDate: edu.endDate || '',
|
90
91
|
verificationStatus: edu.verificationStatus || 'unverified',
|
@@ -113,6 +114,7 @@ export class ResumeVC {
|
|
113
114
|
name: proj.name || '',
|
114
115
|
description: proj.description || '',
|
115
116
|
url: proj.url || '',
|
117
|
+
duration: proj.duration || '',
|
116
118
|
startDate: proj.startDate || '',
|
117
119
|
endDate: proj.endDate || '',
|
118
120
|
verificationStatus: proj.verificationStatus || 'unverified',
|
@@ -76,6 +76,11 @@ export declare class GoogleDriveStorage {
|
|
76
76
|
createRelationsFile({ vcFolderId }: {
|
77
77
|
vcFolderId: string;
|
78
78
|
}): Promise<any>;
|
79
|
+
updateResumeRelation({ authorFolderId, draftFileId, signedFileId, }: {
|
80
|
+
authorFolderId: string;
|
81
|
+
draftFileId: string;
|
82
|
+
signedFileId: string;
|
83
|
+
}): Promise<any>;
|
79
84
|
/**
|
80
85
|
* Delete file by id
|
81
86
|
* @param id
|
@@ -6,6 +6,8 @@ export declare const resumeFolderTypes: {
|
|
6
6
|
};
|
7
7
|
export declare class StorageHandler {
|
8
8
|
protected storage: GoogleDriveStorage;
|
9
|
+
private folderCreationPromises;
|
10
|
+
private createdFolders;
|
9
11
|
constructor(storage: GoogleDriveStorage);
|
10
12
|
getOrCreateFolder(folderName: string, parentId: string): Promise<any>;
|
11
13
|
protected findFilesInFolder(folderName: string): Promise<any[]>;
|
@@ -22,7 +24,7 @@ export declare class Resume extends StorageHandler {
|
|
22
24
|
}>;
|
23
25
|
getSignedResumes(): Promise<any[]>;
|
24
26
|
getNonSignedResumes(): Promise<any[]>;
|
25
|
-
|
27
|
+
saveResumeDraft(data: any, signedResumeId: string): Promise<any>;
|
26
28
|
private isResumeFolderExist;
|
27
29
|
}
|
28
30
|
export default Resume;
|
@@ -102,7 +102,7 @@ export declare const inlineResumeContext: {
|
|
102
102
|
Resume: string;
|
103
103
|
};
|
104
104
|
};
|
105
|
-
declare
|
105
|
+
declare const localOBContext: {
|
106
106
|
'@context': {
|
107
107
|
'@protected': boolean;
|
108
108
|
id: string;
|
@@ -546,7 +546,7 @@ declare let localOBContext: {
|
|
546
546
|
};
|
547
547
|
};
|
548
548
|
};
|
549
|
-
declare
|
549
|
+
declare const localED25519Context: {
|
550
550
|
'@context': {
|
551
551
|
id: string;
|
552
552
|
type: string;
|
package/dist/utils/context.js
CHANGED
@@ -118,7 +118,7 @@ export const inlineResumeContext = {
|
|
118
118
|
Resume: 'https://schema.hropenstandards.org/4.4#Resume',
|
119
119
|
},
|
120
120
|
};
|
121
|
-
|
121
|
+
const localOBContext = {
|
122
122
|
'@context': {
|
123
123
|
'@protected': true,
|
124
124
|
id: '@id',
|
@@ -562,7 +562,7 @@ let localOBContext = {
|
|
562
562
|
},
|
563
563
|
},
|
564
564
|
};
|
565
|
-
|
565
|
+
const localED25519Context = {
|
566
566
|
'@context': {
|
567
567
|
id: '@id',
|
568
568
|
type: '@type',
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@cooperation/vc-storage",
|
3
3
|
"type": "module",
|
4
|
-
"version": "1.0.
|
4
|
+
"version": "1.0.27",
|
5
5
|
"description": "Sign and store your verifiable credentials.",
|
6
6
|
"main": "dist/index.js",
|
7
7
|
"types": "dist/types/index.d.ts",
|
@@ -33,7 +33,7 @@
|
|
33
33
|
"@types/jest": "^29.5.14",
|
34
34
|
"@types/uuid": "^10.0.0",
|
35
35
|
"babel-jest": "^29.7.0",
|
36
|
-
"typescript": "^5.
|
36
|
+
"typescript": "^5.8.2",
|
37
37
|
"vitest": "^3.0.5"
|
38
38
|
}
|
39
39
|
}
|