@cooperation/vc-storage 1.0.14 → 1.0.16

Sign up to get free protection for your applications and to get access to all the features.
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",