@cooperation/vc-storage 1.0.14 → 1.0.16

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 CHANGED
@@ -3,3 +3,4 @@ export * from './models/CredentialEngine.js';
3
3
  export * from './utils/google.js';
4
4
  export * from './utils/presentation.js';
5
5
  export * from './models/Resume.js';
6
+ export * from './models/ResumeVC.js';
@@ -22,10 +22,10 @@ export class GoogleDriveStorage {
22
22
  try {
23
23
  const res = await fetch(url, {
24
24
  method,
25
- headers: new Headers({
25
+ headers: {
26
26
  Authorization: `Bearer ${this.accessToken}`,
27
27
  ...headers,
28
- }),
28
+ },
29
29
  body,
30
30
  });
31
31
  // Check the Content-Type to ensure it's JSON before parsing
@@ -104,11 +104,11 @@ export class GoogleDriveStorage {
104
104
  }
105
105
  // Define file metadata, ensure correct folder is assigned
106
106
  const fileMetadata = {
107
- name: data.fileName,
107
+ name: data.fileName || 'resume.json', // Use the provided fileName or default to 'resume.json'
108
108
  parents: [folderId], // Specify the folder ID
109
- mimeType: data.mimeType || 'application/json',
109
+ mimeType: 'application/json', // Ensure the MIME type is set to JSON
110
110
  };
111
- // make sure the parentId is not in trash
111
+ // Check if the parent folder is in the trash
112
112
  const folder = await this.fetcher({
113
113
  method: 'GET',
114
114
  headers: {},
@@ -117,17 +117,21 @@ export class GoogleDriveStorage {
117
117
  if (folder.trashed) {
118
118
  throw new Error('Parent folder is in trash');
119
119
  }
120
- let uploadUrl = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart';
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
121
125
  const formData = new FormData();
122
126
  formData.append('metadata', new Blob([JSON.stringify(fileMetadata)], { type: 'application/json' }));
123
- formData.append('file', new Blob([data.body], { type: fileMetadata.mimeType })); // Set file data and MIME type
127
+ formData.append('file', fileBlob);
124
128
  // Upload file to Google Drive
125
129
  console.log('Uploading file...');
126
130
  const file = await this.fetcher({
127
131
  method: 'POST',
128
132
  headers: {},
129
133
  body: formData,
130
- url: `${uploadUrl}&fields=id,parents`, // Request the file ID and parent folder IDs
134
+ url: `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,parents`,
131
135
  });
132
136
  // Set the file permission to "Anyone with the link" can view
133
137
  const permissionUrl = `https://www.googleapis.com/drive/v3/files/${file.id}/permissions`;
@@ -227,58 +231,6 @@ export class GoogleDriveStorage {
227
231
  const folders = await this.searchFiles(query);
228
232
  return folders.filter((file) => file.mimeType === 'application/vnd.google-apps.folder');
229
233
  };
230
- /**
231
- * Get the last file from folder by folderId
232
- * @param folderId
233
- * @returns last file content from folder by folderId
234
- */
235
- findLastFile = async (folderId) => {
236
- try {
237
- const files = await this.searchFiles(`'${folderId}' in parents`);
238
- const fileContents = await Promise.all(files
239
- .filter((file) => file.mimeType !== 'application/vnd.google-apps.folder')
240
- .map(async (file) => {
241
- const content = await this.fetcher({
242
- method: 'GET',
243
- headers: {},
244
- url: `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`,
245
- });
246
- return {
247
- ...file,
248
- content,
249
- };
250
- }));
251
- // Find the latest file based on the timestamp in the file name
252
- const latestFile = fileContents.reduce((latest, current) => {
253
- // Check if the file name has the expected structure
254
- const nameParts = current.name.split('_');
255
- let currentTimestampStr;
256
- if (nameParts.length === 3) {
257
- // Structure with UUID: `${uuid}_${type}_${timestamp}.json`
258
- currentTimestampStr = nameParts[2];
259
- }
260
- else if (nameParts.length === 2) {
261
- // Structure without UUID: `${type}_${timestamp}.json`
262
- currentTimestampStr = nameParts[1];
263
- }
264
- else {
265
- // Log warning and skip this file if the structure is not as expected
266
- console.warn(`Unexpected file name format: ${current.name}`);
267
- return latest;
268
- }
269
- // Parse the timestamp from the file name
270
- const latestTimestamp = latest ? parseInt(latest.name.split('_').pop().split('.')[0], 10) : 0;
271
- const currentTimestamp = parseInt(currentTimestampStr.split('.')[0], 10);
272
- return currentTimestamp > latestTimestamp ? current : latest;
273
- }, null);
274
- // Return the content of the latest file
275
- return latestFile ? latestFile.content : null;
276
- }
277
- catch (error) {
278
- console.error('Error finding last file:', error);
279
- return null;
280
- }
281
- };
282
234
  /**
283
235
  * Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
284
236
  * @param type
@@ -9,23 +9,16 @@ export class StorageHandler {
9
9
  this.storage = storage;
10
10
  }
11
11
  async getOrCreateFolder(folderName, parentId) {
12
- console.log(`Searching for folder: "${folderName}", Parent ID: ${parentId}`);
13
12
  const folders = await this.storage.findFolders(parentId); // Fetch all child folders of the parent
14
13
  let folder = folders.find((folder) => {
15
- console.log('🚀 ~ StorageHandler ~ getOrCreateFolder ~ folder:', folder);
16
14
  return folder.name === folderName;
17
15
  });
18
- if (folder && folder.name === 'RESUMES_AUTHOR') {
19
- console.log('🚀 ~ StorageHandler ~ getOrCreateFolder ~ folder:', folder);
20
- }
21
16
  if (!folder) {
22
- console.log(`Folder "${folderName}" not found. Creating under parent: ${parentId}`);
23
17
  folder = await this.storage.createFolder({
24
18
  folderName,
25
19
  parentFolderId: parentId,
26
20
  });
27
21
  }
28
- console.log(`Resolved folder: "${folderName}" with ID: ${folder.id}`);
29
22
  return folder;
30
23
  }
31
24
  async findFilesInFolder(folderName) {
@@ -41,32 +34,22 @@ export class Resume extends StorageHandler {
41
34
  constructor(storage) {
42
35
  super(storage);
43
36
  }
44
- signResume({ resume }) {
45
- // genetrate unsingned resume
46
- // sign resume
47
- }
48
37
  async saveResume({ resume, type }) {
49
38
  try {
50
39
  // Get or create the root folder
51
- console.log('Checking for root folder...');
52
40
  const rootFolders = await this.storage.findFolders();
53
41
  let rootFolder = rootFolders.find((folder) => folder.name === resumeFolderTypes.root);
54
42
  if (!rootFolder) {
55
- console.log('Root folder not found. Creating...');
56
43
  rootFolder = await this.storage.createFolder({ folderName: resumeFolderTypes.root, parentFolderId: 'root' });
57
44
  }
58
- console.log('🚀 Root folder resolved:', rootFolder);
59
45
  // Get or create the subfolder
60
46
  const subFolderName = type === 'sign' ? resumeFolderTypes.signed : resumeFolderTypes.nonSigned;
61
47
  const subFolder = await this.getOrCreateFolder(subFolderName, rootFolder.id);
62
- console.log(`🚀 Subfolder resolved for type "${type}":`, subFolder);
63
48
  // Save the file in the subfolder
64
- console.log(`Saving file in subfolder "${subFolderName}"...`);
65
49
  const savedResume = await this.storage.saveFile({
66
50
  folderId: subFolder.id, // Ensure this points to the subfolder
67
51
  data: resume,
68
52
  });
69
- console.log(`🚀 File saved in folder "${subFolderName}" (ID: ${subFolder.id}):`, savedResume);
70
53
  return savedResume;
71
54
  }
72
55
  catch (error) {
@@ -94,7 +77,6 @@ export class Resume extends StorageHandler {
94
77
  const signedFolder = await this.getOrCreateFolder(resumeFolderTypes.signed, rootFolder.id);
95
78
  // Retrieve all files from the signed folder
96
79
  const files = await this.storage.findFilesUnderFolder(signedFolder.id);
97
- console.log(`Files found in "SIGNED_RESUMES":`, files);
98
80
  return files;
99
81
  }
100
82
  catch (error) {
@@ -105,13 +87,10 @@ export class Resume extends StorageHandler {
105
87
  try {
106
88
  // Find the root folder first
107
89
  const rootFolder = await this.findRootFolder();
108
- console.log('🚀 ~ Resume ~ getNonSignedResumes ~ rootFolder:', rootFolder);
109
90
  // Find or create the non-signed resumes folder
110
91
  const nonSignedFolder = await this.getOrCreateFolder(resumeFolderTypes.nonSigned, rootFolder.id);
111
- console.log('🚀 ~ Resume ~ getNonSignedResumes ~ nonSignedFolder:', nonSignedFolder);
112
92
  // Retrieve all files from the non-signed folder
113
93
  const files = await this.storage.findFilesUnderFolder(nonSignedFolder.id);
114
- console.log(`Files found in "NON_SIGNED_RESUMES":`, files);
115
94
  return files;
116
95
  }
117
96
  catch (error) {
@@ -119,16 +98,13 @@ export class Resume extends StorageHandler {
119
98
  }
120
99
  }
121
100
  async findRootFolder() {
122
- console.log('Searching for the root folder...');
123
101
  const rootFolders = await this.storage.findFolders(); // Fetch all root-level folders
124
102
  const rootFolder = rootFolders.find((folder) => folder.name === resumeFolderTypes.root);
125
103
  if (!rootFolder) {
126
104
  throw new Error(`Root folder "${resumeFolderTypes.root}" not found in the root directory.`);
127
105
  }
128
- console.log('Root folder found:', rootFolder);
129
106
  return rootFolder;
130
107
  }
131
- generarteUnsignedResume() { }
132
108
  isResumeFolderExist() { }
133
109
  }
134
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
+ }
@@ -3,3 +3,4 @@ export * from './models/CredentialEngine.js';
3
3
  export * from './utils/google.js';
4
4
  export * from './utils/presentation.js';
5
5
  export * from './models/Resume.js';
6
+ export * from './models/ResumeVC.js';
@@ -54,12 +54,6 @@ export declare class GoogleDriveStorage {
54
54
  * @returns
55
55
  */
56
56
  findFolders: (folderId?: string) => Promise<any[]>;
57
- /**
58
- * Get the last file from folder by folderId
59
- * @param folderId
60
- * @returns last file content from folder by folderId
61
- */
62
- findLastFile: (folderId: string) => Promise<any>;
63
57
  /**
64
58
  * Get all files content for the specified type ('KEYPAIRs' | 'VCs' | 'SESSIONs' | 'DIDs' | 'RECOMMENDATIONs')
65
59
  * @param type
@@ -1,4 +1,4 @@
1
- import { GoogleDriveStorage } from './GoogleDriveStorage';
1
+ import { GoogleDriveStorage } from './GoogleDriveStorage.js';
2
2
  export declare const resumeFolderTypes: {
3
3
  root: string;
4
4
  nonSigned: string;
@@ -7,14 +7,11 @@ export declare const resumeFolderTypes: {
7
7
  export declare class StorageHandler {
8
8
  protected storage: GoogleDriveStorage;
9
9
  constructor(storage: GoogleDriveStorage);
10
- protected getOrCreateFolder(folderName: string, parentId: string): Promise<any>;
10
+ getOrCreateFolder(folderName: string, parentId: string): Promise<any>;
11
11
  protected findFilesInFolder(folderName: string): Promise<any[]>;
12
12
  }
13
13
  export declare class Resume extends StorageHandler {
14
14
  constructor(storage: GoogleDriveStorage);
15
- signResume({ resume }: {
16
- resume: any;
17
- }): void;
18
15
  saveResume({ resume, type }: {
19
16
  resume: any;
20
17
  type: 'sign' | 'unsigned';
@@ -26,7 +23,6 @@ export declare class Resume extends StorageHandler {
26
23
  getSignedResumes(): Promise<any[]>;
27
24
  getNonSignedResumes(): Promise<any[]>;
28
25
  private findRootFolder;
29
- private generarteUnsignedResume;
30
26
  private isResumeFolderExist;
31
27
  }
32
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;
@@ -1,4 +1,33 @@
1
- // Load the local context files
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,
@@ -29,7 +29,6 @@ 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;
@@ -50,12 +49,11 @@ export async function saveToGoogleDrive({ storage, data, type }) {
50
49
  // save the data in Credentials/VCs/VC-timestamp/vc.json
51
50
  const vcFolder = await storage.createFolder({ folderName: `${fileData.fileName}-${Date.now()}`, parentFolderId: typeFolderId });
52
51
  const file = await storage.saveFile({ data: fileData, folderId: vcFolder.id });
53
- console.log(`File uploaded: ${file?.id} under ${fileData.fileName} folder in VCs folder`);
54
52
  return file;
55
53
  }
56
54
  // Save the file in the specific subfolder
57
55
  const file = await storage.saveFile({ data: fileData, folderId: typeFolderId });
58
- console.log(`File uploaded: ${file?.id} under ${type}s with ID ${typeFolderId} folder in Credentials folder`);
56
+ console.log('🚀 ~ file:', file);
59
57
  return file;
60
58
  }
61
59
  catch (error) {
@@ -75,7 +73,6 @@ export async function uploadImageToGoogleDrive(storage, imageFile) {
75
73
  const rootFolders = await storage.findFolders();
76
74
  let credentialsFolder = rootFolders.find((f) => f.name === 'Credentials');
77
75
  if (!credentialsFolder) {
78
- console.log('Credentials folder not found. Creating...');
79
76
  credentialsFolder = await storage.createFolder({ folderName: 'Credentials', parentFolderId: 'root' });
80
77
  }
81
78
  const credentialsFolderId = credentialsFolder.id;
@@ -96,7 +93,7 @@ export async function uploadImageToGoogleDrive(storage, imageFile) {
96
93
  data: imageData,
97
94
  folderId: mediasFolderId,
98
95
  });
99
- console.log(`Image uploaded: ${uploadedImage?.id} to MEDIAs folder in Credentials`);
96
+ console.log('🚀 ~ uploadedImage:', uploadedImage);
100
97
  return uploadedImage;
101
98
  }
102
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.14",
4
+ "version": "1.0.16",
5
5
  "description": "Sign and store your verifiable credentials.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/types/index.d.ts",