@cooperation/vc-storage 1.0.13 → 1.0.15
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/index.js +2 -0
- package/dist/models/GoogleDriveStorage.js +62 -76
- package/dist/models/Resume.js +110 -0
- package/dist/models/ResumeVC.js +84 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/models/GoogleDriveStorage.d.ts +13 -10
- package/dist/types/models/Resume.d.ts +28 -0
- package/dist/types/models/ResumeVC.d.ts +17 -0
- package/dist/types/utils/context.d.ts +30 -0
- package/dist/utils/context.js +30 -1
- package/dist/utils/credential.js +2 -2
- package/dist/utils/google.js +13 -25
- package/package.json +2 -1
package/dist/index.js
CHANGED
@@ -51,6 +51,22 @@ export class GoogleDriveStorage {
|
|
51
51
|
throw error;
|
52
52
|
}
|
53
53
|
}
|
54
|
+
async getFileContent(fileId) {
|
55
|
+
const url = `https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`;
|
56
|
+
try {
|
57
|
+
const response = await this.fetcher({
|
58
|
+
method: 'GET',
|
59
|
+
headers: {}, // Add additional headers if required
|
60
|
+
url,
|
61
|
+
});
|
62
|
+
console.log(`Content fetched for file ID: ${fileId}`);
|
63
|
+
return response; // This could be text, JSON, or binary, depending on the file type
|
64
|
+
}
|
65
|
+
catch (error) {
|
66
|
+
console.error(`Error fetching content for file ID: ${fileId}:`, error.message);
|
67
|
+
throw new Error(`Failed to fetch content for file ID: ${fileId}`);
|
68
|
+
}
|
69
|
+
}
|
54
70
|
async searchFiles(query) {
|
55
71
|
const result = await this.fetcher({
|
56
72
|
method: 'GET',
|
@@ -63,11 +79,14 @@ export class GoogleDriveStorage {
|
|
63
79
|
}
|
64
80
|
return result.files;
|
65
81
|
}
|
66
|
-
async createFolder(folderName, parentFolderId) {
|
82
|
+
async createFolder({ folderName, parentFolderId }) {
|
83
|
+
if (!parentFolderId) {
|
84
|
+
throw new Error(`Parent folder ID must be provided when creating folder "${folderName}".`);
|
85
|
+
}
|
67
86
|
const metadata = {
|
68
87
|
name: folderName,
|
69
88
|
mimeType: 'application/vnd.google-apps.folder',
|
70
|
-
parents:
|
89
|
+
parents: [parentFolderId], // Explicitly associate with the parent folder
|
71
90
|
};
|
72
91
|
const folder = await this.fetcher({
|
73
92
|
method: 'POST',
|
@@ -75,27 +94,44 @@ export class GoogleDriveStorage {
|
|
75
94
|
body: JSON.stringify(metadata),
|
76
95
|
url: 'https://www.googleapis.com/drive/v3/files',
|
77
96
|
});
|
78
|
-
console.log(
|
79
|
-
return folder
|
97
|
+
console.log(`Folder created: "${folderName}" with ID: ${folder.id}, Parent: ${parentFolderId}`);
|
98
|
+
return folder;
|
80
99
|
}
|
81
100
|
async saveFile({ data, folderId }) {
|
82
101
|
try {
|
102
|
+
if (!folderId) {
|
103
|
+
throw new Error('Folder ID is required to save a file.');
|
104
|
+
}
|
83
105
|
// Define file metadata, ensure correct folder is assigned
|
84
106
|
const fileMetadata = {
|
85
|
-
name: data.fileName,
|
107
|
+
name: data.fileName || 'resume.json', // Use the provided fileName or default to 'resume.json'
|
86
108
|
parents: [folderId], // Specify the folder ID
|
87
|
-
mimeType:
|
109
|
+
mimeType: 'application/json', // Ensure the MIME type is set to JSON
|
88
110
|
};
|
89
|
-
|
111
|
+
// Check if the parent folder is in the trash
|
112
|
+
const folder = await this.fetcher({
|
113
|
+
method: 'GET',
|
114
|
+
headers: {},
|
115
|
+
url: `https://www.googleapis.com/drive/v3/files/${folderId}?fields=trashed`,
|
116
|
+
});
|
117
|
+
if (folder.trashed) {
|
118
|
+
throw new Error('Parent folder is in trash');
|
119
|
+
}
|
120
|
+
// Prepare the file content as a JSON string
|
121
|
+
const fileContent = JSON.stringify(data);
|
122
|
+
// Create a Blob from the JSON string
|
123
|
+
const fileBlob = new Blob([fileContent], { type: 'application/json' });
|
124
|
+
// Create FormData and append the metadata and file content
|
90
125
|
const formData = new FormData();
|
91
126
|
formData.append('metadata', new Blob([JSON.stringify(fileMetadata)], { type: 'application/json' }));
|
92
|
-
formData.append('file',
|
127
|
+
formData.append('file', fileBlob);
|
93
128
|
// Upload file to Google Drive
|
129
|
+
console.log('Uploading file...');
|
94
130
|
const file = await this.fetcher({
|
95
131
|
method: 'POST',
|
96
132
|
headers: {},
|
97
133
|
body: formData,
|
98
|
-
url:
|
134
|
+
url: `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,parents`,
|
99
135
|
});
|
100
136
|
// Set the file permission to "Anyone with the link" can view
|
101
137
|
const permissionUrl = `https://www.googleapis.com/drive/v3/files/${file.id}/permissions`;
|
@@ -109,8 +145,6 @@ export class GoogleDriveStorage {
|
|
109
145
|
headers: {},
|
110
146
|
body: JSON.stringify(permissionData),
|
111
147
|
});
|
112
|
-
console.log('Permission set to public for file:', file.id);
|
113
|
-
console.log('Parent folder IDs:', file.parents);
|
114
148
|
return file;
|
115
149
|
}
|
116
150
|
catch (error) {
|
@@ -197,58 +231,6 @@ export class GoogleDriveStorage {
|
|
197
231
|
const folders = await this.searchFiles(query);
|
198
232
|
return folders.filter((file) => file.mimeType === 'application/vnd.google-apps.folder');
|
199
233
|
};
|
200
|
-
/**
|
201
|
-
* Get the last file from folder by folderId
|
202
|
-
* @param folderId
|
203
|
-
* @returns last file content from folder by folderId
|
204
|
-
*/
|
205
|
-
findLastFile = async (folderId) => {
|
206
|
-
try {
|
207
|
-
const files = await this.searchFiles(`'${folderId}' in parents`);
|
208
|
-
const fileContents = await Promise.all(files
|
209
|
-
.filter((file) => file.mimeType !== 'application/vnd.google-apps.folder')
|
210
|
-
.map(async (file) => {
|
211
|
-
const content = await this.fetcher({
|
212
|
-
method: 'GET',
|
213
|
-
headers: {},
|
214
|
-
url: `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`,
|
215
|
-
});
|
216
|
-
return {
|
217
|
-
...file,
|
218
|
-
content,
|
219
|
-
};
|
220
|
-
}));
|
221
|
-
// Find the latest file based on the timestamp in the file name
|
222
|
-
const latestFile = fileContents.reduce((latest, current) => {
|
223
|
-
// Check if the file name has the expected structure
|
224
|
-
const nameParts = current.name.split('_');
|
225
|
-
let currentTimestampStr;
|
226
|
-
if (nameParts.length === 3) {
|
227
|
-
// Structure with UUID: `${uuid}_${type}_${timestamp}.json`
|
228
|
-
currentTimestampStr = nameParts[2];
|
229
|
-
}
|
230
|
-
else if (nameParts.length === 2) {
|
231
|
-
// Structure without UUID: `${type}_${timestamp}.json`
|
232
|
-
currentTimestampStr = nameParts[1];
|
233
|
-
}
|
234
|
-
else {
|
235
|
-
// Log warning and skip this file if the structure is not as expected
|
236
|
-
console.warn(`Unexpected file name format: ${current.name}`);
|
237
|
-
return latest;
|
238
|
-
}
|
239
|
-
// Parse the timestamp from the file name
|
240
|
-
const latestTimestamp = latest ? parseInt(latest.name.split('_').pop().split('.')[0], 10) : 0;
|
241
|
-
const currentTimestamp = parseInt(currentTimestampStr.split('.')[0], 10);
|
242
|
-
return currentTimestamp > latestTimestamp ? current : latest;
|
243
|
-
}, null);
|
244
|
-
// Return the content of the latest file
|
245
|
-
return latestFile ? latestFile.content : null;
|
246
|
-
}
|
247
|
-
catch (error) {
|
248
|
-
console.error('Error finding last file:', error);
|
249
|
-
return null;
|
250
|
-
}
|
251
|
-
};
|
252
234
|
/**
|
253
235
|
* Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
|
254
236
|
* @param type
|
@@ -283,18 +265,6 @@ export class GoogleDriveStorage {
|
|
283
265
|
return await this.retrieve(file.id);
|
284
266
|
}));
|
285
267
|
}));
|
286
|
-
// for (const folder of vcSubfolders) {
|
287
|
-
// const files = await this.findFilesUnderFolder(folder.id);
|
288
|
-
// // Fetch the content of each file
|
289
|
-
// for (const file of files) {
|
290
|
-
// try {
|
291
|
-
// const content = await this.retrieve(file.id);
|
292
|
-
// fileContents.push(content);
|
293
|
-
// } catch (error) {
|
294
|
-
// console.error(`Error retrieving content for file ${file.id}:`, error);
|
295
|
-
// }
|
296
|
-
// }
|
297
|
-
// }
|
298
268
|
return fileContents;
|
299
269
|
}
|
300
270
|
// Step 3: Generic handling for other types
|
@@ -358,8 +328,24 @@ export class GoogleDriveStorage {
|
|
358
328
|
if (!folderId)
|
359
329
|
throw new Error('Folder ID is required');
|
360
330
|
console.log('🚀 ~ GoogleDriveStorage ~ findFilesUnderFolder ~ folderId', folderId);
|
331
|
+
// Fetch files under the folder
|
361
332
|
const files = await this.searchFiles(`'${folderId}' in parents`);
|
362
|
-
|
333
|
+
if (files.length === 0) {
|
334
|
+
console.log('No files found in the folder.');
|
335
|
+
return [];
|
336
|
+
}
|
337
|
+
// Fetch content for each file
|
338
|
+
const filesWithContent = await Promise.all(files.map(async (file) => {
|
339
|
+
try {
|
340
|
+
const content = await this.getFileContent(file.id);
|
341
|
+
return { ...file, content }; // Merge file metadata with its content
|
342
|
+
}
|
343
|
+
catch (error) {
|
344
|
+
console.error(`Error fetching content for file "${file.name}" (ID: ${file.id}):`, error);
|
345
|
+
return { ...file, content: null }; // Handle errors gracefully
|
346
|
+
}
|
347
|
+
}));
|
348
|
+
return filesWithContent;
|
363
349
|
}
|
364
350
|
async updateFileData(fileId, data) {
|
365
351
|
const fileMetadata = {
|
@@ -0,0 +1,110 @@
|
|
1
|
+
export const resumeFolderTypes = {
|
2
|
+
root: 'RESUMES_AUTHOR',
|
3
|
+
nonSigned: 'NON_SIGNED_RESUMES',
|
4
|
+
signed: 'SIGNED_RESUMES',
|
5
|
+
};
|
6
|
+
export class StorageHandler {
|
7
|
+
storage;
|
8
|
+
constructor(storage) {
|
9
|
+
this.storage = storage;
|
10
|
+
}
|
11
|
+
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
|
+
});
|
21
|
+
}
|
22
|
+
return folder;
|
23
|
+
}
|
24
|
+
async findFilesInFolder(folderName) {
|
25
|
+
const folders = await this.storage.findFolders();
|
26
|
+
const folder = folders.find((folder) => folder.name === folderName);
|
27
|
+
if (!folder) {
|
28
|
+
throw new Error(`${folderName} folder not found`);
|
29
|
+
}
|
30
|
+
return this.storage.findFilesUnderFolder(folder.id);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
export class Resume extends StorageHandler {
|
34
|
+
constructor(storage) {
|
35
|
+
super(storage);
|
36
|
+
}
|
37
|
+
async saveResume({ resume, type }) {
|
38
|
+
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
|
+
}
|
45
|
+
// Get or create the subfolder
|
46
|
+
const subFolderName = type === 'sign' ? resumeFolderTypes.signed : resumeFolderTypes.nonSigned;
|
47
|
+
const subFolder = await this.getOrCreateFolder(subFolderName, rootFolder.id);
|
48
|
+
// Save the file in the subfolder
|
49
|
+
const savedResume = await this.storage.saveFile({
|
50
|
+
folderId: subFolder.id, // Ensure this points to the subfolder
|
51
|
+
data: resume,
|
52
|
+
});
|
53
|
+
return savedResume;
|
54
|
+
}
|
55
|
+
catch (error) {
|
56
|
+
throw new Error(`Error while saving ${type} resume: ${error.message}`);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
async find() {
|
60
|
+
try {
|
61
|
+
const signedResumes = await this.getSignedResumes();
|
62
|
+
const nonSignedResumes = await this.getNonSignedResumes();
|
63
|
+
return {
|
64
|
+
signed: signedResumes,
|
65
|
+
nonSigned: nonSignedResumes,
|
66
|
+
};
|
67
|
+
}
|
68
|
+
catch (error) {
|
69
|
+
throw new Error('Error while fetching resume: ' + error.message);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
async getSignedResumes() {
|
73
|
+
try {
|
74
|
+
// Find the root folder first
|
75
|
+
const rootFolder = await this.findRootFolder();
|
76
|
+
// Find or create the signed resumes folder
|
77
|
+
const signedFolder = await this.getOrCreateFolder(resumeFolderTypes.signed, rootFolder.id);
|
78
|
+
// Retrieve all files from the signed folder
|
79
|
+
const files = await this.storage.findFilesUnderFolder(signedFolder.id);
|
80
|
+
return files;
|
81
|
+
}
|
82
|
+
catch (error) {
|
83
|
+
throw new Error('Error while fetching signed resumes: ' + error.message);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
async getNonSignedResumes() {
|
87
|
+
try {
|
88
|
+
// Find the root folder first
|
89
|
+
const rootFolder = await this.findRootFolder();
|
90
|
+
// Find or create the non-signed resumes folder
|
91
|
+
const nonSignedFolder = await this.getOrCreateFolder(resumeFolderTypes.nonSigned, rootFolder.id);
|
92
|
+
// Retrieve all files from the non-signed folder
|
93
|
+
const files = await this.storage.findFilesUnderFolder(nonSignedFolder.id);
|
94
|
+
return files;
|
95
|
+
}
|
96
|
+
catch (error) {
|
97
|
+
throw new Error('Error while fetching non-signed resumes: ' + error.message);
|
98
|
+
}
|
99
|
+
}
|
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.`);
|
105
|
+
}
|
106
|
+
return rootFolder;
|
107
|
+
}
|
108
|
+
isResumeFolderExist() { }
|
109
|
+
}
|
110
|
+
export default Resume;
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import { Ed25519Signature2020 } from '@digitalbazaar/ed25519-signature-2020';
|
2
|
+
import { customDocumentLoader } from '../utils/digitalbazaar.js';
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
4
|
+
import * as dbVc from '@digitalbazaar/vc';
|
5
|
+
import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
|
6
|
+
import { generateDIDSchema } from '../utils/credential.js';
|
7
|
+
import { inlineResumeContext } from '../utils/context.js';
|
8
|
+
export class ResumeVC {
|
9
|
+
async sign({ formData, issuerDid, keyPair }) {
|
10
|
+
const unsignedCredential = this.generateUnsignedCredential({ formData, issuerDid });
|
11
|
+
const suite = new Ed25519Signature2020({
|
12
|
+
key: new Ed25519VerificationKey2020(keyPair), // Ensure proper initialization
|
13
|
+
verificationMethod: keyPair.id,
|
14
|
+
});
|
15
|
+
try {
|
16
|
+
const signedVC = await dbVc.issue({
|
17
|
+
credential: unsignedCredential,
|
18
|
+
suite,
|
19
|
+
documentLoader: customDocumentLoader,
|
20
|
+
});
|
21
|
+
console.log('Signed VC:', signedVC);
|
22
|
+
}
|
23
|
+
catch (error) {
|
24
|
+
console.error('Error signing VC:', error.message);
|
25
|
+
if (error.details) {
|
26
|
+
console.error('Error details:', JSON.stringify(error.details, null, 2));
|
27
|
+
}
|
28
|
+
throw error;
|
29
|
+
}
|
30
|
+
return unsignedCredential;
|
31
|
+
}
|
32
|
+
generateUnsignedCredential({ formData, issuerDid }) {
|
33
|
+
const unsignedCredential = {
|
34
|
+
'@context': [
|
35
|
+
'https://www.w3.org/2018/credentials/v1', // Standard VC context
|
36
|
+
inlineResumeContext['@context'], // Inline context
|
37
|
+
],
|
38
|
+
id: `urn:uuid:${uuidv4()}`, // Generate a dynamic UUID
|
39
|
+
type: ['VerifiableCredential'],
|
40
|
+
issuer: issuerDid,
|
41
|
+
issuanceDate: new Date().toISOString(),
|
42
|
+
credentialSubject: {
|
43
|
+
type: 'Resume',
|
44
|
+
person: {
|
45
|
+
name: {
|
46
|
+
formattedName: formData.formattedName,
|
47
|
+
},
|
48
|
+
primaryLanguage: formData.primaryLanguage,
|
49
|
+
},
|
50
|
+
narrative: formData.narrative,
|
51
|
+
employmentHistory: formData.employmentHistory,
|
52
|
+
skills: formData.skills,
|
53
|
+
educationAndLearning: formData.educationAndLearning,
|
54
|
+
},
|
55
|
+
};
|
56
|
+
return unsignedCredential;
|
57
|
+
}
|
58
|
+
generateKeyPair = async (address) => {
|
59
|
+
// Generate the key pair using the library's method
|
60
|
+
const keyPair = await Ed25519VerificationKey2020.generate();
|
61
|
+
// Configure key pair attributes
|
62
|
+
const a = address || keyPair.publicKeyMultibase;
|
63
|
+
keyPair.controller = `did:key:${a}`;
|
64
|
+
keyPair.id = `${keyPair.controller}#${a}`;
|
65
|
+
keyPair.revoked = false;
|
66
|
+
// The `signer` is already provided by the `Ed25519VerificationKey2020` instance
|
67
|
+
return keyPair;
|
68
|
+
};
|
69
|
+
/**
|
70
|
+
* Create a new DID with Digital Bazaar's Ed25519VerificationKey2020 key pair.
|
71
|
+
* @returns {Promise<{didDocument: object, keyPair: object}>} The created DID document and key pair.
|
72
|
+
* @throws Will throw an error if DID creation fails.
|
73
|
+
*/
|
74
|
+
async createDID({ keyPair }) {
|
75
|
+
try {
|
76
|
+
const didDocument = await generateDIDSchema(keyPair);
|
77
|
+
return didDocument;
|
78
|
+
}
|
79
|
+
catch (error) {
|
80
|
+
console.error('Error creating DID:', error);
|
81
|
+
throw error;
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
package/dist/types/index.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { DataToSaveI
|
1
|
+
import { DataToSaveI } from '../../types';
|
2
2
|
interface FileContent {
|
3
3
|
name: string;
|
4
4
|
content: any;
|
@@ -23,10 +23,19 @@ export declare class GoogleDriveStorage {
|
|
23
23
|
private accessToken;
|
24
24
|
constructor(accessToken: string);
|
25
25
|
private fetcher;
|
26
|
+
private getFileContent;
|
26
27
|
private searchFiles;
|
27
|
-
createFolder(folderName
|
28
|
+
createFolder({ folderName, parentFolderId }: {
|
29
|
+
folderName: string;
|
30
|
+
parentFolderId: string;
|
31
|
+
}): Promise<{
|
32
|
+
id: string;
|
33
|
+
name: string;
|
34
|
+
mimeType: string;
|
35
|
+
parents: string[];
|
36
|
+
}>;
|
28
37
|
saveFile({ data, folderId }: {
|
29
|
-
data:
|
38
|
+
data: any;
|
30
39
|
folderId: string;
|
31
40
|
}): Promise<any>;
|
32
41
|
/**
|
@@ -45,12 +54,6 @@ export declare class GoogleDriveStorage {
|
|
45
54
|
* @returns
|
46
55
|
*/
|
47
56
|
findFolders: (folderId?: string) => Promise<any[]>;
|
48
|
-
/**
|
49
|
-
* Get the last file from folder by folderId
|
50
|
-
* @param folderId
|
51
|
-
* @returns last file content from folder by folderId
|
52
|
-
*/
|
53
|
-
findLastFile: (folderId: string) => Promise<any>;
|
54
57
|
/**
|
55
58
|
* Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
|
56
59
|
* @param type
|
@@ -64,7 +67,7 @@ export declare class GoogleDriveStorage {
|
|
64
67
|
* @returns The updated file metadata, including the new name
|
65
68
|
*/
|
66
69
|
updateFileName(fileId: string, newFileName: string): Promise<any>;
|
67
|
-
findFileByName(name:
|
70
|
+
findFileByName(name: string): Promise<any>;
|
68
71
|
findFilesUnderFolder(folderId: string): Promise<any[]>;
|
69
72
|
updateFileData(fileId: string, data: DataToSaveI): Promise<any>;
|
70
73
|
getFileParents(fileId: string): Promise<any>;
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { GoogleDriveStorage } from './GoogleDriveStorage.js';
|
2
|
+
export declare const resumeFolderTypes: {
|
3
|
+
root: string;
|
4
|
+
nonSigned: string;
|
5
|
+
signed: string;
|
6
|
+
};
|
7
|
+
export declare class StorageHandler {
|
8
|
+
protected storage: GoogleDriveStorage;
|
9
|
+
constructor(storage: GoogleDriveStorage);
|
10
|
+
getOrCreateFolder(folderName: string, parentId: string): Promise<any>;
|
11
|
+
protected findFilesInFolder(folderName: string): Promise<any[]>;
|
12
|
+
}
|
13
|
+
export declare class Resume extends StorageHandler {
|
14
|
+
constructor(storage: GoogleDriveStorage);
|
15
|
+
saveResume({ resume, type }: {
|
16
|
+
resume: any;
|
17
|
+
type: 'sign' | 'unsigned';
|
18
|
+
}): Promise<any>;
|
19
|
+
find(): Promise<{
|
20
|
+
signed: any[];
|
21
|
+
nonSigned: any[];
|
22
|
+
}>;
|
23
|
+
getSignedResumes(): Promise<any[]>;
|
24
|
+
getNonSignedResumes(): Promise<any[]>;
|
25
|
+
private findRootFolder;
|
26
|
+
private isResumeFolderExist;
|
27
|
+
}
|
28
|
+
export default Resume;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
export declare class ResumeVC {
|
2
|
+
sign({ formData, issuerDid, keyPair }: {
|
3
|
+
formData: any;
|
4
|
+
issuerDid: string;
|
5
|
+
keyPair: any;
|
6
|
+
}): Promise<any>;
|
7
|
+
private generateUnsignedCredential;
|
8
|
+
generateKeyPair: (address?: string) => Promise<any>;
|
9
|
+
/**
|
10
|
+
* Create a new DID with Digital Bazaar's Ed25519VerificationKey2020 key pair.
|
11
|
+
* @returns {Promise<{didDocument: object, keyPair: object}>} The created DID document and key pair.
|
12
|
+
* @throws Will throw an error if DID creation fails.
|
13
|
+
*/
|
14
|
+
createDID({ keyPair }: {
|
15
|
+
keyPair: any;
|
16
|
+
}): Promise<import("../../types/credential.js").DidDocument>;
|
17
|
+
}
|
@@ -1,3 +1,33 @@
|
|
1
|
+
export declare const inlineResumeContext: {
|
2
|
+
'@context': {
|
3
|
+
'@vocab': string;
|
4
|
+
name: string;
|
5
|
+
formattedName: string;
|
6
|
+
primaryLanguage: string;
|
7
|
+
narrative: string;
|
8
|
+
text: string;
|
9
|
+
employmentHistory: {
|
10
|
+
'@id': string;
|
11
|
+
'@container': string;
|
12
|
+
};
|
13
|
+
company: string;
|
14
|
+
position: string;
|
15
|
+
duration: string;
|
16
|
+
skills: {
|
17
|
+
'@id': string;
|
18
|
+
'@container': string;
|
19
|
+
};
|
20
|
+
educationAndLearning: string;
|
21
|
+
degree: string;
|
22
|
+
institution: string;
|
23
|
+
year: string;
|
24
|
+
issuanceDate: string;
|
25
|
+
issuer: string;
|
26
|
+
credentialSubject: string;
|
27
|
+
person: string;
|
28
|
+
Resume: string;
|
29
|
+
};
|
30
|
+
};
|
1
31
|
declare let localOBContext: {
|
2
32
|
'@context': {
|
3
33
|
'@protected': boolean;
|
package/dist/utils/context.js
CHANGED
@@ -1,4 +1,33 @@
|
|
1
|
-
|
1
|
+
export const inlineResumeContext = {
|
2
|
+
'@context': {
|
3
|
+
'@vocab': 'https://schema.hropenstandards.org/4.4/',
|
4
|
+
name: 'https://schema.org/name',
|
5
|
+
formattedName: 'https://schema.org/formattedName',
|
6
|
+
primaryLanguage: 'https://schema.org/primaryLanguage',
|
7
|
+
narrative: 'https://schema.org/narrative',
|
8
|
+
text: 'https://schema.org/text',
|
9
|
+
employmentHistory: {
|
10
|
+
'@id': 'https://schema.org/employmentHistory',
|
11
|
+
'@container': '@list', // Specify list container
|
12
|
+
},
|
13
|
+
company: 'https://schema.org/company',
|
14
|
+
position: 'https://schema.org/jobTitle',
|
15
|
+
duration: 'https://schema.org/temporalCoverage',
|
16
|
+
skills: {
|
17
|
+
'@id': 'https://schema.org/skills',
|
18
|
+
'@container': '@list', // Specify list container
|
19
|
+
},
|
20
|
+
educationAndLearning: 'https://schema.org/educationAndLearning',
|
21
|
+
degree: 'https://schema.org/degree',
|
22
|
+
institution: 'https://schema.org/institution',
|
23
|
+
year: 'https://schema.org/year',
|
24
|
+
issuanceDate: 'https://schema.org/issuanceDate',
|
25
|
+
issuer: 'https://schema.org/issuer',
|
26
|
+
credentialSubject: 'https://schema.org/credentialSubject',
|
27
|
+
person: 'https://schema.org/Person', // Added person
|
28
|
+
Resume: 'https://schema.hropenstandards.org/4.4#Resume', // Map Resume to an absolute IRI
|
29
|
+
},
|
30
|
+
};
|
2
31
|
let localOBContext = {
|
3
32
|
'@context': {
|
4
33
|
'@protected': true,
|
package/dist/utils/credential.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Ed25519VerificationKey2020 } from '@digitalbazaar/ed25519-verification-key-2020';
|
2
|
-
import crypto from 'crypto';
|
3
2
|
import { v4 as uuidv4 } from 'uuid';
|
3
|
+
import CryptoJS from 'crypto-js';
|
4
4
|
/**
|
5
5
|
* Utility function to generate a hashed ID for a credential.
|
6
6
|
* Excludes the `id` field when hashing.
|
@@ -11,7 +11,7 @@ function generateHashedId(credential) {
|
|
11
11
|
// Exclude the `id` field from the hash
|
12
12
|
const credentialWithoutId = { ...credential, id: undefined };
|
13
13
|
const serialized = JSON.stringify(credentialWithoutId);
|
14
|
-
return
|
14
|
+
return CryptoJS.SHA256(serialized).toString(CryptoJS.enc.Hex);
|
15
15
|
}
|
16
16
|
/**
|
17
17
|
* Create a DID document using the provided key pair.
|
package/dist/utils/google.js
CHANGED
@@ -29,37 +29,31 @@ export async function saveToGoogleDrive({ storage, data, type }) {
|
|
29
29
|
};
|
30
30
|
// Get all root folders
|
31
31
|
const rootFolders = await storage.findFolders();
|
32
|
-
console.log('Root folders:', rootFolders);
|
33
32
|
// Find or create the "Credentials" folder
|
34
33
|
let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
|
35
34
|
let credentialsFolderId;
|
36
35
|
if (!credentialsFolder) {
|
37
|
-
|
38
|
-
}
|
39
|
-
else {
|
40
|
-
credentialsFolderId = credentialsFolder.id;
|
36
|
+
credentialsFolder = await storage.createFolder({ folderName: 'Credentials', parentFolderId: 'root' });
|
41
37
|
}
|
38
|
+
credentialsFolderId = credentialsFolder.id;
|
42
39
|
// Get subfolders within the "Credentials" folder
|
43
40
|
const subfolders = await storage.findFolders(credentialsFolderId);
|
44
41
|
// Find or create the specific subfolder (DIDs or VCs)
|
45
42
|
let typeFolder = subfolders.find((f) => f.name === `${type}s`);
|
46
43
|
let typeFolderId;
|
47
44
|
if (!typeFolder) {
|
48
|
-
|
49
|
-
}
|
50
|
-
else {
|
51
|
-
typeFolderId = typeFolder.id;
|
45
|
+
typeFolder = await storage.createFolder({ folderName: `${type}s`, parentFolderId: credentialsFolderId });
|
52
46
|
}
|
47
|
+
typeFolderId = typeFolder.id;
|
53
48
|
if (type === 'VC') {
|
54
49
|
// save the data in Credentials/VCs/VC-timestamp/vc.json
|
55
|
-
const
|
56
|
-
const file = await storage.saveFile({ data: fileData, folderId:
|
57
|
-
console.log(`File uploaded: ${file?.id} under ${fileData.fileName} folder in VCs folder`);
|
50
|
+
const vcFolder = await storage.createFolder({ folderName: `${fileData.fileName}-${Date.now()}`, parentFolderId: typeFolderId });
|
51
|
+
const file = await storage.saveFile({ data: fileData, folderId: vcFolder.id });
|
58
52
|
return file;
|
59
53
|
}
|
60
54
|
// Save the file in the specific subfolder
|
61
55
|
const file = await storage.saveFile({ data: fileData, folderId: typeFolderId });
|
62
|
-
console.log(
|
56
|
+
console.log('🚀 ~ file:', file);
|
63
57
|
return file;
|
64
58
|
}
|
65
59
|
catch (error) {
|
@@ -78,22 +72,16 @@ export async function uploadImageToGoogleDrive(storage, imageFile) {
|
|
78
72
|
try {
|
79
73
|
const rootFolders = await storage.findFolders();
|
80
74
|
let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
|
81
|
-
let credentialsFolderId;
|
82
75
|
if (!credentialsFolder) {
|
83
|
-
|
76
|
+
credentialsFolder = await storage.createFolder({ folderName: 'Credentials', parentFolderId: 'root' });
|
84
77
|
}
|
85
|
-
|
86
|
-
|
87
|
-
}
|
88
|
-
const subfolders = await storage.findFolders(credentialsFolderId);
|
78
|
+
const credentialsFolderId = credentialsFolder.id;
|
79
|
+
const subfolders = await storage.findFolders(credentialsFolder.id);
|
89
80
|
let mediasFolder = subfolders.find((f) => f.name === 'MEDIAs');
|
90
|
-
let mediasFolderId;
|
91
81
|
if (!mediasFolder) {
|
92
|
-
|
93
|
-
}
|
94
|
-
else {
|
95
|
-
mediasFolderId = mediasFolder.id;
|
82
|
+
mediasFolder = await storage.createFolder({ folderName: 'MEDIAs', parentFolderId: credentialsFolderId });
|
96
83
|
}
|
84
|
+
const mediasFolderId = mediasFolder.id;
|
97
85
|
// Prepare the image file data
|
98
86
|
const imageData = {
|
99
87
|
fileName: imageFile.name,
|
@@ -105,7 +93,7 @@ export async function uploadImageToGoogleDrive(storage, imageFile) {
|
|
105
93
|
data: imageData,
|
106
94
|
folderId: mediasFolderId,
|
107
95
|
});
|
108
|
-
console.log(
|
96
|
+
console.log('🚀 ~ uploadedImage:', uploadedImage);
|
109
97
|
return uploadedImage;
|
110
98
|
}
|
111
99
|
catch (error) {
|
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.15",
|
5
5
|
"description": "Sign and store your verifiable credentials.",
|
6
6
|
"main": "dist/index.js",
|
7
7
|
"types": "dist/types/index.d.ts",
|
@@ -20,6 +20,7 @@
|
|
20
20
|
"@digitalbazaar/ed25519-signature-2020": "^5.3.0",
|
21
21
|
"@digitalbazaar/ed25519-verification-key-2020": "^4.1.0",
|
22
22
|
"@digitalbazaar/vc": "^6.3.0",
|
23
|
+
"crypto-js": "^4.2.0",
|
23
24
|
"ethers": "^6.13.2",
|
24
25
|
"ts-node": "^10.9.2",
|
25
26
|
"tsc": "^2.0.4",
|