diff --git a/app.js b/app.js index 8c9837ddd..225ad0e4b 100644 --- a/app.js +++ b/app.js @@ -15,9 +15,14 @@ exports.init = ( ) => { app.use( bodyParser.json( ) ) app.use( bodyParser.urlencoded( { extended: false } ) ) + app.get( '/', ( req, res ) => { + res.send( { fantastic: 'speckle' } ) + } ) + + require( './modules' )( app ) + // Error responses app.use( ( err, req, res, next ) => { - console.log( "ERRRRRR") if ( process.env.NODE_ENV === 'test' ) { debug( err ) } @@ -28,11 +33,5 @@ exports.init = ( ) => { } ) } ) - app.get( '/', ( req, res ) => { - res.send( { fantastic: 'speckle' } ) - } ) - - require( './modules' )( app ) - return app } \ No newline at end of file diff --git a/modules/core/migrations/001-streams.js b/modules/core/migrations/001-streams.js index b00fb433c..db47144c9 100644 --- a/modules/core/migrations/001-streams.js +++ b/modules/core/migrations/001-streams.js @@ -28,8 +28,8 @@ exports.up = async knex => { ` ) await knex.schema.createTable( 'stream_acl', table => { - table.text( 'user_id' ).references( 'id' ).inTable( 'users' ).notNullable( ) - table.text( 'resource_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) + table.text( 'user_id' ).references( 'id' ).inTable( 'users' ).notNullable( ).onDelete( 'cascade' ) + table.text( 'resource_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ).onDelete( 'cascade' ) table.primary( [ 'user_id', 'resource_id' ] ) table.unique( [ 'user_id', 'resource_id' ] ) table.specificType( 'role', 'speckle_acl_role_type' ).defaultTo( 'write' ) @@ -72,7 +72,7 @@ exports.up = async knex => { // Reference table. A reference can be a branch or a tag. await knex.schema.createTable( 'references', table => { table.uuid( 'id' ).defaultTo( knex.raw( 'gen_random_uuid()' ) ).unique( ).primary( ) - table.text( 'stream_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) + table.text( 'stream_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ).onDelete( 'cascade' ) table.text( 'author' ).references( 'id' ).inTable( 'users' ) table.text( 'name' ) table.specificType( 'type', 'speckle_reference_type' ).defaultTo( 'branch' ) @@ -87,7 +87,7 @@ exports.up = async knex => { // Junction Table Branches >- -< Commits // Note: Branches >- -< Commits is a many-to-many relationship (one commit can belong to multiple branches, one branch can have multiple commits) await knex.schema.createTable( 'branch_commits', table => { - table.uuid( 'branch_id' ).references( 'id' ).inTable( 'references' ).notNullable( ) + table.uuid( 'branch_id' ).references( 'id' ).inTable( 'references' ).notNullable( ).onDelete('cascade') table.text( 'commit_id' ).references( 'hash' ).inTable( 'objects' ).notNullable( ) table.primary( [ 'branch_id', 'commit_id' ] ) } ) @@ -95,7 +95,7 @@ exports.up = async knex => { // Flat table to store all commits to this stream, regardless of branch. // Optional, might be removed as you can get all the commits from each branch... await knex.schema.createTable( 'stream_commits', table => { - table.text( 'stream_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) + table.text( 'stream_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ).onDelete('cascade') table.text( 'commit_id' ).references( 'hash' ).inTable( 'objects' ).notNullable( ) table.primary( [ 'stream_id', 'commit_id' ] ) } ) diff --git a/modules/core/objects/controllers.js b/modules/core/objects/controllers.js index 4c63ea540..db0279b06 100644 --- a/modules/core/objects/controllers.js +++ b/modules/core/objects/controllers.js @@ -16,7 +16,7 @@ module.exports = { async createCommit( req, res, next ) { try { let id = await createCommit( req.params.resourceId, req.user.id, req.body ) - res.status( 201 ).send( { success: true, id: id } ) + res.status( 201 ).send( id ) next( ) } catch ( err ) { next( err ) @@ -37,7 +37,7 @@ module.exports = { async createObjects( req, res, next ) { try { - let hashes = await createObjects( req.params.resourceId, req.user.id, req.body ) + let hashes = await createObjects( req.body ) res.status( 201 ).send( hashes ) next( ) } catch ( err ) { diff --git a/modules/core/objects/services.js b/modules/core/objects/services.js index b0ef9619c..f424df28d 100644 --- a/modules/core/objects/services.js +++ b/modules/core/objects/services.js @@ -22,7 +22,9 @@ module.exports = { async createCommit( streamId, userId, object ) { object.speckle_type = 'commit' - let hash = await module.exports.createObject( streamId, userId, object ) + object.author = userId + + let hash = await module.exports.createObject( object ) let query = StreamCommits( ).insert( { stream_id: streamId, commit_id: hash } ).toString( ) + ' on conflict do nothing' await knex.raw( query ) @@ -38,14 +40,13 @@ module.exports = { /* Objects Proper */ - async createObject( streamId, userId, object ) { + async createObject( object ) { // Prep tree refs let objTreeRefs = object.hasOwnProperty( '__tree' ) && object.__tree ? object.__tree.map( entry => { return { parent: entry.split( '.' )[ 0 ], path: entry } } ) : [ ] let insertionObject = prepInsertionObject( object ) - insertionObject.author = userId let q1 = Objects( ).insert( insertionObject ).toString( ) + ' on conflict do nothing' await knex.raw( q1 ) @@ -58,7 +59,7 @@ module.exports = { return insertionObject.hash }, - createObjects: async ( streamId, userId, objects ) => { + createObjects: async ( objects ) => { let batches = [ ] let maxBatchSize = process.env.MAX_BATCH_SIZE || 1000 objects = [ ...objects ] @@ -86,7 +87,6 @@ module.exports = { } let insertionObject = prepInsertionObject( obj ) - insertionObject.author = userId objsToInsert.push( insertionObject ) hashes.push( insertionObject.hash ) diff --git a/modules/core/references/controllers.js b/modules/core/references/controllers.js index d0af43d2d..4f56aeca4 100644 --- a/modules/core/references/controllers.js +++ b/modules/core/references/controllers.js @@ -1,24 +1,106 @@ 'use strict' +const { + createTag, + updateTag, + getTagById, + deleteTagById, + getTagsByStreamId, + createBranch, + updateBranch, + getBranchById, + deleteBranchById, + getBranchesByStreamId, + getStreamReferences +} = require( './services' ) module.exports = { - getReferences: ( req, res, next ) => { - res.send( [ 1, 3, 4 ] ) + async getReferences( req, res, next ) { + try { + const references = await getStreamReferences( req.params.resourceId ) + res.send( references ) + next( ) + } catch ( err ) { + next( err ) + } }, - getReference: ( req, res, next ) => { - res.send( { todo: true } ) + async getTag( req, res, next ) { + try { + const reference = await getTagById( req.params.referenceId ) + res.send( reference ) + next( ) + } catch ( err ) { + next( err ) + } }, - createReference: ( req, res, next ) => { - res.send( { todo: true } ) + async createTag( req, res, next ) { + try { + let id = await createTag( req.body, req.params.resourceId, req.user.id ) + res.status( 201 ).send( { id: id } ) + next( ) + } catch ( err ) { + next( err ) + } }, - updateReference: ( req, res, next ) => { - res.send( { todo: true } ) + async updateTag( req, res, next ) { + try { + req.body.id = req.params.referenceId + await updateTag( req.body ) + res.status( 200 ).send( { success: true } ) + next( ) + } catch ( err ) { + next( err ) + } }, - deleteReference: ( req, res, next ) => { - res.send( { todo: true } ) + async deleteTag( req, res, next ) { + try { + await deleteTagById( req.params.referenceId ) + res.status( 200 ).send( { success: true } ) + next( ) + } catch ( err ) { + next( err ) + } + }, + + async getBranch( req, res, next ) { + try { + const reference = await getBranchById( req.params.referenceId ) + res.send( reference ) + next( ) + } catch ( err ) { + next( err ) + } + }, + + async createBranch( req, res, next ) { + try { + const id = await createBranch( req.body, req.params.resourceId, req.user.id ) + res.send( { id: id } ) + next( ) + } catch ( err ) { + next( err ) + } + }, + + async updateBranch( req, res, next ) { + try { + req.body.id = req.params.referenceId + await updateBranch( req.body ) + res.status( 200 ).send( { success: true } ) + } catch ( err ) { + next( err ) + } + }, + + async deleteBranch( req, res, next ) { + try { + await deleteBranchById( req.params.referenceId ) + res.send( { success: true } ) + } catch ( err ) { + next( err ) + } } -} - +} \ No newline at end of file diff --git a/modules/core/references/index.js b/modules/core/references/index.js index bd2fc3c13..f6c86ca0b 100644 --- a/modules/core/references/index.js +++ b/modules/core/references/index.js @@ -1,14 +1,25 @@ 'use strict' const root = require( 'app-root-path' ) -const { getReferences, getReference, createReference, updateReference, deleteReference } = require( './controllers' ) const { authenticate, authorize, announce } = require( `${root}/modules/shared` ) +const { + getReferences, + getTag, + createTag, + updateTag, + deleteTag, + getBranch, + createBranch, + updateBranch, + deleteBranch +} = require( './controllers' ) + // References (branches & tags) const references = require( 'express' ).Router( { mergeParams: true } ) module.exports = references -// Get all branches and tags +// Get all branches and tags for a stream references.get( '/streams/:resourceId/references', authenticate( 'streams:read', false ), @@ -16,35 +27,80 @@ references.get( getReferences ) -// Get specific tag or branch +/* + Tags + */ + +// Get specific tag references.get( - '/streams/:streamId/references/:referenceId', + '/streams/:resourceId/tags/:referenceId', authenticate( 'streams:read', false ), authorize( 'streams_acl', 'streams', 'read' ), - getReference + getTag ) -// Create a branch or a tag +// Create a tag references.post( - '/streams/:streamId/references', + '/streams/:resourceId/tags', authenticate( 'streams:write' ), authorize( 'stream_acl', 'streams', 'write' ), - createReference, + createTag, announce( 'reference-created', 'stream' ) ) -// Edit a branch or a tag +// Edit a tag references.put( - '/streams/:streamId/references/:referenceId', + '/streams/:resourceId/tags/:referenceId', authenticate( 'streams:write' ), authorize( 'stream_acl', 'streams', 'write' ), + updateTag, announce( 'reference-updated', 'stream' ) ) +// Delete a tag references.delete( - '/streams/:streamId/references/:referenceId', + '/streams/:resourceId/tags/:referenceId', authenticate( 'streams:write' ), authorize( 'stream_acl', 'streams', 'write' ), - deleteReference, + deleteTag, + announce( 'reference-deleted', 'stream' ) +) + +/* + Branches + */ + +// Get specific branch +references.get( + '/streams/:resourceId/branches/:referenceId', + authenticate( 'streams:read', false ), + authorize( 'streams_acl', 'streams', 'read' ), + getBranch +) + +// Create a branch +references.post( + '/streams/:resourceId/branches', + authenticate( 'streams:write' ), + authorize( 'stream_acl', 'streams', 'write' ), + createBranch, + announce( 'reference-created', 'stream' ) +) + +// Edit a branch +references.put( + '/streams/:resourceId/branches/:referenceId', + authenticate( 'streams:write' ), + authorize( 'stream_acl', 'streams', 'write' ), + updateBranch, + announce( 'reference-updated', 'stream' ) +) + +// Delete a branch +references.delete( + '/streams/:resourceId/branches/:referenceId', + authenticate( 'streams:write' ), + authorize( 'stream_acl', 'streams', 'write' ), + deleteBranch, announce( 'reference-deleted', 'stream' ) ) \ No newline at end of file diff --git a/modules/core/references/services.js b/modules/core/references/services.js index 966769e0c..f4b60bf80 100644 --- a/modules/core/references/services.js +++ b/modules/core/references/services.js @@ -34,7 +34,7 @@ module.exports = { }, deleteTagById: async ( tagId ) => { - return Refs( ).where( { id: tag.id, type: 'tag' } ).del( ) + return Refs( ).where( { id: tagId, type: 'tag' } ).del( ) }, getTagsByStreamId: async ( streamId ) => { @@ -69,7 +69,7 @@ module.exports = { let branchCommits = commits.map( commitId => { return { branch_id: branch.id, commit_id: commitId } } ) await knex.raw( BranchCommits( ).insert( branchCommits ) + ' on conflict do nothing' ) } - + await Refs( ).where( { id: branch.id } ).update( branch ) }, @@ -89,6 +89,9 @@ module.exports = { return Refs( ).where( { stream_id: streamId, type: 'branch' } ).select( '*' ) }, + async deleteBranchById( branchId ) { + await Refs( ).where( { id: branchId, type: 'branch' } ).del( ) + }, /* Generic */ diff --git a/modules/core/tests/002-objects.spec.js b/modules/core/tests/002-objects.spec.js index 0076c82e3..0686beb0f 100644 --- a/modules/core/tests/002-objects.spec.js +++ b/modules/core/tests/002-objects.spec.js @@ -78,8 +78,8 @@ describe( 'Objects', ( ) => { } ) it( 'Should create objects', async ( ) => { - sampleObject.hash = await createObject( stream.id, userOne.id, sampleObject ) - sampleCommit.hash = await createObject( stream.id, userOne.id, sampleCommit ) + sampleObject.hash = await createObject( sampleObject ) + sampleCommit.hash = await createObject( sampleCommit ) } ) let objCount_1 = 10 @@ -100,7 +100,7 @@ describe( 'Objects', ( ) => { } ) } - let hashes = await createObjects( stream.id, userOne.id, objs ) + let hashes = await createObjects( objs ) expect( hashes ).to.have.lengthOf( objCount_1 ) @@ -124,7 +124,7 @@ describe( 'Objects', ( ) => { } ) } - let hashes = await createObjects( stream.id, userOne.id, objs2 ) + let hashes = await createObjects( objs2 ) hashes.forEach( ( h, i ) => objs2[ i ].hash = h ) diff --git a/modules/core/tests/003-references.spec.js b/modules/core/tests/003-references.spec.js index ead091313..5c5f5cbae 100644 --- a/modules/core/tests/003-references.spec.js +++ b/modules/core/tests/003-references.spec.js @@ -12,7 +12,7 @@ chai.use( chaiHttp ) const { createUser, createToken, revokeToken, revokeTokenById, validateToken, getUserTokens } = require( '../users/services' ) const { createStream, getStream, updateStream, deleteStream, getStreamsUser, grantPermissionsStream, revokePermissionsStream } = require( '../streams/services' ) -const { createObject, createObjects, getObject, getObjects } = require( '../objects/services' ) +const { createObject, createCommit, createObjects, getObject, getObjects } = require( '../objects/services' ) const { createTag, updateTag, @@ -27,43 +27,42 @@ const { } = require( '../references/services' ) describe( 'Tags & Branches', ( ) => { + let user = { + username: 'dim4242', + name: 'Dimitrie Stefanescu', + email: 'didimitrie4342@gmail.com', + password: 'sn3aky-1337-b1m' + } + + let stream = { + name: 'Test Stream References', + description: 'Whatever goes in here usually...' + } + + let commit1 = { description: 'First Commit' } + + let commit2 = { description: 'Second Commit' } + + let branch = { + name: '🧨 super branch 🧨', + description: 'a test branch' + } + + let tag = { + name: 'v.1.20.3', + description: 'release version shite' + } describe( 'Services/Queries', ( ) => { - let user = { - username: 'dim4242', - name: 'Dimitrie Stefanescu', - email: 'didimitrie4342@gmail.com', - password: 'sn3aky-1337-b1m' - } - - let stream = { - name: 'Test Stream References', - description: 'Whatever goes in here usually...' - } - - let commit1 = { description: 'First Commit' } - - let commit2 = { description: 'Second Commit' } - - let branch = { - name: '🧨 super branch 🧨', - description: 'a test branch' - } - - let tag = { - name: 'v.1.20.3', - description: 'release version shite' - } - before( async ( ) => { await knex.migrate.latest( ) user.id = await createUser( user ) stream.id = await createStream( stream, user.id ) - commit1.hash = await createObject( stream.id, user.id, commit1 ) + commit1.hash = await createCommit( stream.id, user.id, commit1 ) commit2.parents = [ commit1.hash ] - commit2.hash = await createObject( stream.id, user.id, commit2 ) + commit2.hash = await createCommit( stream.id, user.id, commit2 ) tag.commit_id = commit2.hash } ) @@ -106,7 +105,7 @@ describe( 'Tags & Branches', ( ) => { expect( myBranchAfterFirstUpdate.commits ).to.have.lengthOf( 1 ) let newCommit = { test: 'test', best: true } - newCommit.hash = await createObject( stream.id, user.id, newCommit ) + newCommit.hash = await createCommit( stream.id, user.id, newCommit ) branch.commits = [ commit2.hash, newCommit.hash, commit1.hash ] branch.name = 'A Different Name' @@ -170,4 +169,84 @@ describe( 'Tags & Branches', ( ) => { expect( branches ).to.have.lengthOf( 3 ) } ) } ) + + describe( 'Integration (API)', ( ) => { + let token + + before( async ( ) => { + await knex.migrate.latest( ) + + user.id = await createUser( user ) + token = await createToken( user.id, 'Generic Token', [ 'streams:read', 'streams:write' ] ) + + stream.id = await createStream( stream, user.id ) + + commit1.hash = await createCommit( stream.id, user.id, commit1 ) + commit2.parents = [ commit1.hash ] + commit2.hash = await createCommit( stream.id, user.id, commit2 ) + tag.commit_id = commit2.hash + } ) + + after( async ( ) => { + await knex.migrate.rollback( ) + } ) + + it( 'Should create a tag', async ( ) => { + const response = await chai.request( app ).post( `/streams/${stream.id}/tags` ).send( tag ).set( 'Authorization', `Bearer ${token}` ) + expect( response ).to.have.status( 201 ) + expect( response.body ).to.have.property( 'id' ) + tag.id = response.body.id + } ) + + + it( 'Should update a tag', async ( ) => { + const response = await chai.request( app ).put( `/streams/${stream.id}/tags/${tag.id}` ).send( { name: 'semver love' } ).set( 'Authorization', `Bearer ${token}` ) + expect( response ).to.have.status( 200 ) + } ) + + + it( 'Should get a tag', async ( ) => { + const tagResponse = await chai.request( app ).get( `/streams/${stream.id}/tags/${tag.id}` ).set( 'Authorization', `Bearer ${token}` ) + expect( tagResponse ).to.have.status( 200 ) + expect( tagResponse.body.name ).to.equal( 'semver love' ) + } ) + + + it( 'Should delete a tag', async ( ) => { + const deleteResponse = await chai.request( app ).delete( `/streams/${stream.id}/tags/${tag.id}` ).set( 'Authorization', `Bearer ${token}` ) + expect( deleteResponse ).to.have.status( 200 ) + } ) + + + it( 'Should create a branch', async ( ) => { + branch.commits = [ commit1.hash, commit1.hash ] + + const response = await chai.request( app ).post( `/streams/${stream.id}/branches` ).send( branch ).set( 'Authorization', `Bearer ${token}` ) + expect( response ).to.have.status( 200 ) + expect( response.body ).to.have.property( 'id' ) + branch.id = response.body.id + } ) + + + it( 'Should update a branch', async ( ) => { + const response = await chai.request( app ).put( `/streams/${stream.id}/branches/${branch.id}` ).send( { name: 'semver love' } ).set( 'Authorization', `Bearer ${token}` ) + expect( response ).to.have.status( 200 ) + } ) + + + it( 'Should get a branch', async ( ) => { + const branchResponse = await chai.request( app ).get( `/streams/${stream.id}/branches/${branch.id}` ).set( 'Authorization', `Bearer ${token}` ) + expect( branchResponse ).to.have.status( 200 ) + expect( branchResponse.body ).to.have.property( 'name' ) + expect( branchResponse.body.name ).to.equal( 'semver love' ) + } ) + + + it( 'Should delete create a branch', async ( ) => { + const deleteResponse = await chai.request( app ).delete( `/streams/${stream.id}/branches/${branch.id}` ).set( 'Authorization', `Bearer ${token}` ) + expect( deleteResponse ).to.have.status( 200 ) + } ) + + } ) + } ) \ No newline at end of file