@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.
@@ -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 || 'resume.json',
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
@@ -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 folders = await this.storage.findFolders(parentId); // Fetch all child folders of the parent
13
- let folder = folders.find((folder) => {
14
- return folder.name === folderName;
15
- });
16
- if (!folder) {
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
- return folder;
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
- // Get or create the root folder
40
- const rootFolders = await this.storage.findFolders();
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.findRootFolder();
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.findRootFolder();
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 findRootFolder() {
101
- const rootFolders = await this.storage.findFolders(); // Fetch all root-level folders
102
- const rootFolder = rootFolders.find((folder) => folder.name === resumeFolderTypes.root);
103
- if (!rootFolder) {
104
- throw new Error(`Root folder "${resumeFolderTypes.root}" not found in the root directory.`);
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
  }
@@ -18,8 +18,7 @@ export class ResumeVC {
18
18
  suite,
19
19
  documentLoader: customDocumentLoader,
20
20
  });
21
- console.log('🚀 ~ ResumeVC ~ sign ~ signedVC:', JSON.stringify(signedVC));
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
- private findRootFolder;
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 let localOBContext: {
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 let localED25519Context: {
549
+ declare const localED25519Context: {
550
550
  '@context': {
551
551
  id: string;
552
552
  type: string;
@@ -118,7 +118,7 @@ export const inlineResumeContext = {
118
118
  Resume: 'https://schema.hropenstandards.org/4.4#Resume',
119
119
  },
120
120
  };
121
- let localOBContext = {
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
- let localED25519Context = {
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.25",
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.6.2",
36
+ "typescript": "^5.8.2",
37
37
  "vitest": "^3.0.5"
38
38
  }
39
39
  }