github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/kat/src/clients/database.ts (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import { config } from '../lib/config'; 16 import { ClientEventType, IClientEventListener, IDatabaseProvider, IDBAssetDefinition, IDBAssetInstance, IDBBatch, IDBBlockchainData, IDBMember, IDBPaymentDefinition, IDBPaymentInstance, IStoredSubscriptions } from '../lib/interfaces'; 17 import * as utils from '../lib/utils'; 18 import MongoDBProvider from './db-providers/mongodb'; 19 import NEDBProvider from './db-providers/nedb'; 20 const log = utils.getLogger('handlers/asset-trade.ts'); 21 22 let databaseProvider: IDatabaseProvider; 23 24 export const init = async () => { 25 if (config.mongodb !== undefined) { 26 databaseProvider = new MongoDBProvider(); 27 } else { 28 databaseProvider = new NEDBProvider(); 29 } 30 await databaseProvider.init(); 31 }; 32 33 let listeners: IClientEventListener[] = []; 34 35 // COLLECTION AGNOSTIC QUERIES 36 37 export const createCollection = (collectionName: string, indexes: { fields: string[], unique?: boolean }[]) => { 38 return databaseProvider.createCollection(collectionName, indexes); 39 }; 40 41 // MEMBER QUERIES 42 43 export const retrieveMemberByAddress = (address: string): Promise<IDBMember | null> => { 44 return databaseProvider.findOne<IDBMember>('members', { address }); 45 }; 46 47 export const retrieveMembers = (query: object, skip: number, limit: number): Promise<IDBMember[]> => { 48 return databaseProvider.find<IDBMember>('members', query, { name: 1 }, skip, limit); 49 }; 50 51 export const upsertMember = async (member: IDBMember) => { 52 await databaseProvider.updateOne('members', { address: member.address }, { $set: member }, true); 53 emitEvent('member-registered', member); 54 }; 55 56 // ASSET DEFINITION QUERIES 57 58 export const retrieveAssetDefinitions = (query: object, skip: number, limit: number): Promise<IDBAssetDefinition[]> => { 59 return databaseProvider.find<IDBAssetDefinition>('asset-definitions', query, { name: 1 }, skip, limit) 60 }; 61 62 export const countAssetDefinitions = (query: object): Promise<number> => { 63 return databaseProvider.count('asset-definitions', query); 64 }; 65 66 export const retrieveAssetDefinitionByID = (assetDefinitionID: string): Promise<IDBAssetDefinition | null> => { 67 return databaseProvider.findOne<IDBAssetDefinition>('asset-definitions', { assetDefinitionID }); 68 }; 69 70 export const retrieveAssetDefinitionByName = (name: string): Promise<IDBAssetDefinition | null> => { 71 return databaseProvider.findOne<IDBAssetDefinition>('asset-definitions', { name }); 72 }; 73 74 export const upsertAssetDefinition = async (assetDefinition: IDBAssetDefinition) => { 75 await databaseProvider.updateOne('asset-definitions', { assetDefinitionID: assetDefinition.assetDefinitionID }, { $set: assetDefinition }, true); 76 if (assetDefinition.submitted !== undefined) { 77 emitEvent('asset-definition-submitted', assetDefinition); 78 } else if (assetDefinition.transactionHash !== undefined) { 79 emitEvent('asset-definition-created', assetDefinition); 80 } 81 }; 82 83 export const markAssetDefinitionAsConflict = async (assetDefinitionID: string, timestamp: number) => { 84 await databaseProvider.updateOne('asset-definitions', { assetDefinitionID }, { $set: { timestamp, conflict: true } }, false); 85 emitEvent('asset-definition-name-conflict', { assetDefinitionID }) 86 }; 87 88 // PAYMENT DEFINITION QUERIES 89 90 export const retrievePaymentDefinitions = (query: object, skip: number, limit: number): Promise<IDBPaymentDefinition[]> => { 91 return databaseProvider.find<IDBPaymentDefinition>('payment-definitions', query, { name: 1 }, skip, limit); 92 }; 93 94 export const countPaymentDefinitions = (query: object): Promise<number> => { 95 return databaseProvider.count('payment-definitions', query); 96 }; 97 98 export const retrievePaymentDefinitionByID = (paymentDefinitionID: string): Promise<IDBPaymentDefinition | null> => { 99 return databaseProvider.findOne<IDBPaymentDefinition>('payment-definitions', { paymentDefinitionID }); 100 }; 101 102 export const retrievePaymentDefinitionByName = (name: string): Promise<IDBPaymentDefinition | null> => { 103 return databaseProvider.findOne<IDBPaymentDefinition>('payment-definitions', { name }); 104 }; 105 106 export const upsertPaymentDefinition = async (paymentDefinition: IDBPaymentDefinition) => { 107 await databaseProvider.updateOne('payment-definitions', { paymentDefinitionID: paymentDefinition.paymentDefinitionID }, { $set: paymentDefinition }, true) 108 if (paymentDefinition.submitted !== undefined) { 109 emitEvent('payment-definition-submitted', paymentDefinition); 110 } else if (paymentDefinition.transactionHash !== undefined) { 111 emitEvent('payment-definition-created', paymentDefinition); 112 } 113 }; 114 115 export const markPaymentDefinitionAsConflict = async (paymentDefinitionID: string, timestamp: number) => { 116 await databaseProvider.updateOne('payment-definitions', { paymentDefinitionID }, { $set: { conflict: true, timestamp } }, false); 117 emitEvent('payment-definition-name-conflict', { paymentDefinitionID }) 118 }; 119 120 // ASSET INSTANCE QUERIES 121 122 export const retrieveAssetInstances = (assetDefinitionID: string, query: object, sort: object, skip: number, limit: number): Promise<IDBAssetInstance[]> => { 123 return databaseProvider.find<IDBAssetInstance>(`asset-instance-${assetDefinitionID}`, query, sort, skip, limit); 124 }; 125 126 export const countAssetInstances = (assetDefinitionID: string, query: object): Promise<number> => { 127 return databaseProvider.count(`asset-instance-${assetDefinitionID}`, query); 128 }; 129 130 export const retrieveAssetInstanceByID = (assetDefinitionID: string, assetInstanceID: string): Promise<IDBAssetInstance | null> => { 131 return databaseProvider.findOne<IDBAssetInstance>(`asset-instance-${assetDefinitionID}`, { assetInstanceID }); 132 }; 133 134 export const retrieveAssetInstanceByDefinitionIDAndContentHash = (assetDefinitionID: string, contentHash: string): Promise<IDBAssetInstance | null> => { 135 return databaseProvider.findOne<IDBAssetInstance>(`asset-instance-${assetDefinitionID}`, { contentHash }); 136 }; 137 138 export const upsertAssetInstance = async (assetInstance: IDBAssetInstance) => { 139 await databaseProvider.updateOne(`asset-instance-${assetInstance.assetDefinitionID}`, { assetInstanceID: assetInstance.assetInstanceID }, { $set: assetInstance }, true); 140 if (assetInstance.submitted !== undefined) { 141 emitEvent('asset-instance-submitted', assetInstance); 142 } else if (assetInstance.transactionHash !== undefined) { 143 emitEvent('asset-instance-created', assetInstance); 144 } 145 }; 146 147 export const setAssetInstanceReceipt = async (assetDefinitionID: string, assetInstanceID: string, receipt: string) => { 148 await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, { $set: { receipt } }, true); 149 }; 150 151 export const setAssetInstancePrivateContent = async (assetDefinitionID: string, assetInstanceID: string, content: object | undefined, filename: string | undefined) => { 152 await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, { $set: { content, filename } }, true); 153 log.info(`Emitting event for private-asset-instance-content-stored`); 154 emitEvent('private-asset-instance-content-stored', { assetDefinitionID, assetInstanceID, content, filename }); 155 }; 156 157 export const markAssetInstanceAsConflict = async (assetDefinitionID: string, assetInstanceID: string, timestamp: number) => { 158 await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, { $set: { conflict: true, timestamp } }, false); 159 emitEvent('asset-instance-content-conflict', { assetDefinitionID, assetInstanceID }); 160 }; 161 162 export const setSubmittedAssetInstanceProperty = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, value: string, submitted: number, batchID?: string) => { 163 await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, 164 { 165 $set: { 166 [`properties.${author}.${key}.value`]: value, 167 [`properties.${author}.${key}.submitted`]: submitted, 168 [`properties.${author}.${key}.batchID`]: batchID, 169 } 170 }, false); 171 emitEvent('asset-instance-property-submitted', { assetDefinitionID, assetInstanceID, key, value, submitted, batchID }); 172 }; 173 174 export const setAssetInstancePropertyReceipt = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, receipt: string) => { 175 await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, 176 { 177 $set: { 178 [`properties.${author}.${key}.receipt`]: receipt 179 } 180 }, false); 181 }; 182 183 export const setConfirmedAssetInstanceProperty = async (assetDefinitionID: string, assetInstanceID: string, author: string, key: string, value: string, timestamp: number, { blockNumber, transactionHash }: IDBBlockchainData) => { 184 await databaseProvider.updateOne(`asset-instance-${assetDefinitionID}`, { assetInstanceID }, 185 { 186 $set: { 187 [`properties.${author}.${key}.value`]: value, 188 [`properties.${author}.${key}.history.${timestamp}`]: { value, timestamp, blockNumber, transactionHash } 189 } 190 }, false); 191 emitEvent('asset-instance-property-set', { assetDefinitionID, assetInstanceID, author, key, value, timestamp, blockNumber, transactionHash }); 192 }; 193 194 // PAYMENT INSTANCE QUERIES 195 196 export const retrievePaymentInstances = (query: object, sort: object, skip: number, limit: number): Promise<IDBPaymentInstance[]> => { 197 return databaseProvider.find<IDBPaymentInstance>('payment-instances', query, sort, skip, limit); 198 }; 199 200 export const countPaymentInstances = (query: object): Promise<number> => { 201 return databaseProvider.count('payment-instances', query); 202 }; 203 204 export const retrievePaymentInstanceByID = (paymentInstanceID: string): Promise<IDBPaymentInstance | null> => { 205 return databaseProvider.findOne<IDBPaymentInstance>('payment-instances', { paymentInstanceID }); 206 }; 207 208 export const upsertPaymentInstance = async (paymentInstance: IDBPaymentInstance) => { 209 await databaseProvider.updateOne('payment-instances', { paymentInstanceID: paymentInstance.paymentInstanceID }, { $set: paymentInstance }, true); 210 if (paymentInstance.submitted !== undefined) { 211 emitEvent('payment-instance-submitted', paymentInstance); 212 } else { 213 emitEvent('payment-instance-created', paymentInstance); 214 } 215 }; 216 217 218 // BATCH QUERIES 219 220 export const retrieveBatches = (query: object, skip: number, limit: number, sort: {[f: string]: number} = {}): Promise<IDBBatch[]> => { 221 return databaseProvider.find<IDBBatch>('batches', query, sort, skip, limit); 222 }; 223 224 export const retrieveBatchByID = (batchID: string): Promise<IDBBatch | null> => { 225 return databaseProvider.findOne<IDBBatch>('batches', { batchID }); 226 }; 227 228 export const retrieveBatchByHash = (batchHash: string): Promise<IDBBatch | null> => { 229 return databaseProvider.findOne<IDBBatch>('batches', { batchHash }); 230 }; 231 232 export const upsertBatch = async (batch: IDBBatch) => { 233 await databaseProvider.updateOne('batches', { batchID: batch.batchID }, { $set: batch }, true); 234 }; 235 236 // SUBSCRIPTION MANAGEMENT 237 238 export const retrieveSubscriptions = (): Promise<IStoredSubscriptions | null> => { 239 return databaseProvider.findOne<IStoredSubscriptions>('state', { key: 'subscriptions' }); 240 }; 241 242 export const upsertSubscriptions = (subscriptions: IStoredSubscriptions): Promise<void> => { 243 return databaseProvider.updateOne('state', { key: 'subscriptions' }, { $set: subscriptions }, true); 244 }; 245 246 // EVENT HANDLING 247 248 export const addListener = (listener: IClientEventListener) => { 249 listeners.push(listener); 250 }; 251 252 export const removeListener = (listener: IClientEventListener) => { 253 listeners = listeners.filter(entry => entry != listener); 254 }; 255 256 const emitEvent = (eventType: ClientEventType, content: object) => { 257 for (const listener of listeners) { 258 listener(eventType, content); 259 } 260 }; 261 262 export const shutDown = () => { 263 databaseProvider.shutDown(); 264 };