feat(ifc): uses api to create commit, thus generating activity

adds extra services to generate a one-time use api token for the given user, which is then revoked
after the commit is created
This commit is contained in:
Dimitrie Stefanescu
2021-09-28 13:30:25 +01:00
parent 3676b445dc
commit e545670fdb
4 changed files with 3536 additions and 1185 deletions
+3435 -1116
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -17,9 +17,11 @@
"url": "https://github.com/specklesystems/speckle-server/issues"
},
"dependencies": {
"bcrypt": "^5.0.1",
"crypto-random-string": "^3.3.1",
"eslint": "^7.29.0",
"knex": "^0.95.11",
"node-fetch": "^2.6.5",
"pg": "^8.7.1",
"web-ifc": "0.0.24"
}
+74 -63
View File
@@ -1,6 +1,7 @@
'use strict'
const crypto = require( 'crypto' )
const crs = require( 'crypto-random-string' )
const bcrypt = require( 'bcrypt' )
const knex = require( '../knex' )
const Streams = ( ) => knex( 'streams' )
@@ -8,6 +9,8 @@ const Branches = ( ) => knex( 'branches' )
const Commits = ( ) => knex( 'commits' )
const Objects = ( ) => knex( 'objects' )
const Closures = ( ) => knex( 'object_children_closure' )
const ApiTokens = ( ) => knex( 'api_tokens' )
const TokenScopes = ( ) => knex( 'token_scopes' )
const StreamCommits = ( ) => knex( 'stream_commits' )
const BranchCommits = ( ) => knex( 'branch_commits' )
@@ -65,78 +68,86 @@ module.exports = class ServerAPI {
return insertionObject.id
}
prepInsertionObject( streamId, obj ) {
const MAX_OBJECT_SIZE = 10 * 1024 * 1024
prepInsertionObject( streamId, obj ) {
const MAX_OBJECT_SIZE = 10 * 1024 * 1024
if ( obj.hash )
obj.id = obj.hash
else
obj.id = obj.id || crypto.createHash( 'md5' ).update( JSON.stringify( obj ) ).digest( 'hex' ) // generate a hash if none is present
if ( obj.hash )
obj.id = obj.hash
else
obj.id = obj.id || crypto.createHash( 'md5' ).update( JSON.stringify( obj ) ).digest( 'hex' ) // generate a hash if none is present
let stringifiedObj = JSON.stringify( obj )
if ( stringifiedObj.length > MAX_OBJECT_SIZE ) {
throw new Error( `Object too large (${stringifiedObj.length} > ${MAX_OBJECT_SIZE})` )
let stringifiedObj = JSON.stringify( obj )
if ( stringifiedObj.length > MAX_OBJECT_SIZE ) {
throw new Error( `Object too large (${stringifiedObj.length} > ${MAX_OBJECT_SIZE})` )
}
return {
data: stringifiedObj, // stored in jsonb column
streamId: streamId,
id: obj.id,
speckleType: obj.speckleType
}
}
return {
data: stringifiedObj, // stored in jsonb column
streamId: streamId,
id: obj.id,
speckleType: obj.speckleType
}
}
async createCommitByBranchName( { streamId, branchName, objectId, authorId, message, sourceApplication, totalChildrenCount, parents } ) {
branchName = branchName.toLowerCase( )
let myBranch = await this.getBranchByNameAndStreamId( { streamId: streamId, name: branchName } )
if ( !myBranch )
throw new Error( `Failed to find branch with name ${branchName}.` )
return await this.createCommitByBranchId( { streamId, branchId: myBranch.id, objectId, authorId, message, sourceApplication, totalChildrenCount, parents } )
}
async getBranchByNameAndStreamId( { streamId, name } ) {
let query = Branches( ).select( '*' ).where( { streamId: streamId } ).andWhere( knex.raw( 'LOWER(name) = ?', [ name ] ) ).first( )
return await query
}
async createBranch( { name, description, streamId, authorId } ) {
let branch = {}
branch.id = crs( { length: 10 } )
branch.streamId = streamId
branch.authorId = authorId
branch.name = name.toLowerCase( )
branch.description = description
await Branches( ).returning( 'id' ).insert( branch )
// update stream updated at
await Streams().where( { id: streamId } ).update( { updatedAt: knex.fn.now() } )
return branch.id
async getBranchByNameAndStreamId( { streamId, name } ) {
let query = Branches( ).select( '*' ).where( { streamId: streamId } ).andWhere( knex.raw( 'LOWER(name) = ?', [ name ] ) ).first( )
return await query
}
async createCommitByBranchId( { streamId, branchId, objectId, authorId, message, sourceApplication, totalChildrenCount, parents } ) {
// Create main table entry
let [ id ] = await Commits( ).returning( 'id' ).insert( {
id: crs( { length: 10 } ),
referencedObject: objectId,
author: authorId,
sourceApplication,
totalChildrenCount,
parents,
message
} )
async createBranch( { name, description, streamId, authorId } ) {
let branch = {}
branch.id = crs( { length: 10 } )
branch.streamId = streamId
branch.authorId = authorId
branch.name = name.toLowerCase( )
branch.description = description
// Link it to a branch
await BranchCommits( ).insert( { branchId: branchId, commitId: id } )
// Link it to a stream
await StreamCommits( ).insert( { streamId: streamId,commitId: id } )
await Branches( ).returning( 'id' ).insert( branch )
// update stream updated at
await Streams().where( { id: streamId } ).update( { updatedAt: knex.fn.now() } )
return id
// update stream updated at
await Streams().where( { id: streamId } ).update( { updatedAt: knex.fn.now() } )
return branch.id
}
async createBareToken( ) {
let tokenId = crs( { length: 10 } )
let tokenString = crs( { length: 32 } )
let tokenHash = await bcrypt.hash( tokenString, 10 )
let lastChars = tokenString.slice( tokenString.length - 6, tokenString.length )
return { tokenId, tokenString, tokenHash, lastChars }
}
async createToken( { userId, name, scopes, lifespan } ) {
let { tokenId, tokenString, tokenHash, lastChars } = await this.createBareToken( )
if ( scopes.length === 0 ) throw new Error( 'No scopes provided' )
let token = {
id: tokenId,
tokenDigest: tokenHash,
lastChars: lastChars,
owner: userId,
name: name,
lifespan: lifespan
}
let tokenScopes = scopes.map( scope => ( { tokenId: tokenId, scopeName: scope } ) )
await ApiTokens( ).insert( token )
await TokenScopes( ).insert( tokenScopes )
return { id: tokenId, token: tokenId + tokenString }
}
async revokeTokenById( tokenId ) {
let delCount = await ApiTokens( ).where( { id: tokenId.slice( 0, 10 ) } ).del( )
if ( delCount === 0 )
throw new Error( 'Token revokation failed' )
return true
}
}
+25 -6
View File
@@ -1,4 +1,5 @@
const fs = require( 'fs' )
const fetch = require( 'node-fetch' )
const Parser = require( './parser' )
const ServerAPI = require( './api.js' )
@@ -13,7 +14,7 @@ const userId = 'e24eb8e7e4'
// const data = fs.readFileSync( './ifcs/20160414office_model_CV2_fordesign.ifc' )
// const data = fs.readFileSync( './ifcs/hospital.ifc' )
// const data = fs.readFileSync( './ifcs/primark.ifc' )
// const data = fs.readFileSync( './ifcs/231110AC11-Institute-Var-2-IFC.ifc' )
const data = fs.readFileSync( './ifcs/231110AC11-Institute-Var-2-IFC.ifc' )
// const data = fs.readFileSync( './ifcs/small.ifc' )
// const data = fs.readFileSync( './ifcs/example.ifc' )
// const data = fs.readFileSync( './ifcs/steelplates.ifc' )
@@ -21,7 +22,7 @@ const userId = 'e24eb8e7e4'
// const data = fs.readFileSync( './ifcs/railing.ifc' )
// const data = fs.readFileSync( './ifcs/hall.ifc' )
// const data = fs.readFileSync( './ifcs/231110ADT-FZK-Haus-2005-2006.ifc' )
const data = fs.readFileSync( './ifcs/crazy.ifc' )
// const data = fs.readFileSync( './ifcs/crazy.ifc' )
async function parseAndCreateCommit( { data, streamId, branchName = 'uploads', userId, message = 'Manual IFC file upload' } ) {
const serverApi = new ServerAPI( { streamId } )
@@ -33,7 +34,7 @@ async function parseAndCreateCommit( { data, streamId, branchName = 'uploads', u
streamId: streamId,
branchName: branchName,
objectId: id,
authorId: userId,
// authorId: userId, // not needed anymore (using raw api call for this)
message: message,
sourceApplication: 'IFC',
totalChildrenCount: tCount
@@ -50,10 +51,28 @@ async function parseAndCreateCommit( { data, streamId, branchName = 'uploads', u
} )
}
await serverApi.createCommitByBranchName( commit )
let { token:userToken } = await serverApi.createToken( { userId, name: 'temp upload token', scopes: [ 'streams:write' ], lifespan: 3000 } )
console.log( commit )
// console.log( "TODO: save commit" )
// TODO: in case there's a custom server port, perhaps we need to use an env variable here instead of the default 3000?
const response = await fetch( 'http://localhost:3000/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userToken}`
},
body: JSON.stringify( {
query: 'mutation createCommit( $myCommitInput: CommitCreateInput!) { commitCreate( commit: $myCommitInput ) }',
variables:{
myCommitInput: commit
}
} )
} )
let json = await response.json()
console.log( json )
await serverApi.revokeTokenById( userToken )
}
parseAndCreateCommit( {