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:
Generated
+3435
-1116
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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( {
|
||||
|
||||
Reference in New Issue
Block a user