diff --git a/app.js b/app.js index 8158a8c0a..ffa9a5f3d 100644 --- a/app.js +++ b/app.js @@ -17,7 +17,6 @@ exports.init = ( ) => { // Error responses app.use( ( err, req, res, next ) => { - debug( err ) res.status( err.status || 500 ) res.json( { message: err.message, diff --git a/modules/core/migrations/000-users.js b/modules/core/migrations/000-users.js index 4db52f45e..48474ba43 100644 --- a/modules/core/migrations/000-users.js +++ b/modules/core/migrations/000-users.js @@ -5,7 +5,7 @@ exports.up = async knex => { await knex.raw( 'CREATE EXTENSION IF NOT EXISTS "pgcrypto"' ) await knex.schema.createTable( 'users', table => { - table.uuid( 'id' ).defaultTo( knex.raw( 'gen_random_uuid()' ) ).unique( ).primary( ) + table.text( 'id' ).unique( ).primary( ) table.text( 'username' ).unique( ).notNullable( ) table.timestamp( 'created_at' ).defaultTo( knex.fn.now( ) ) table.text( 'name' ).notNullable( ) @@ -18,7 +18,7 @@ exports.up = async knex => { await knex.schema.createTable( 'api_token', table => { table.text( 'id' ).unique( ).primary( ) table.text( 'token_digest' ).unique( ) - table.uuid( 'owner_id' ).references( 'id' ).inTable( 'users' ).notNullable( ) + table.text( 'owner_id' ).references( 'id' ).inTable( 'users' ).notNullable( ) table.text( 'name' ) table.text( 'last_chars' ) table.specificType( 'scopes', 'text[]' ) diff --git a/modules/core/migrations/001-streams.js b/modules/core/migrations/001-streams.js index 6fb656a69..69021b570 100644 --- a/modules/core/migrations/001-streams.js +++ b/modules/core/migrations/001-streams.js @@ -7,11 +7,11 @@ exports.up = async knex => { // Streams Table await knex.schema.createTable( 'streams', table => { - table.uuid( 'id' ).defaultTo( knex.raw( 'gen_random_uuid()' ) ).unique( ).primary( ) + table.text( 'id' ).unique( ).primary( ) table.text( 'name' ) table.text( 'description' ) table.boolean( 'isPublic' ).defaultTo( true ) - table.uuid( 'cloned_from' ).references( 'id' ).inTable( 'streams' ) + table.text( 'cloned_from' ).references( 'id' ).inTable( 'streams' ) table.timestamp( 'created_at' ).defaultTo( knex.fn.now( ) ) table.timestamp( 'updated_at' ).defaultTo( knex.fn.now( ) ) // table.unique( [ 'owner_id', 'name' ] ) @@ -28,8 +28,8 @@ exports.up = async knex => { ` ) await knex.schema.createTable( 'stream_acl', table => { - table.uuid( 'user_id' ).references( 'id' ).inTable( 'users' ).notNullable( ) - table.uuid( 'resource_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) + table.text( 'user_id' ).references( 'id' ).inTable( 'users' ).notNullable( ) + table.text( 'resource_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) table.primary( [ 'user_id', 'resource_id' ] ) table.unique( [ 'user_id', 'resource_id' ] ) table.specificType( 'role', 'speckle_acl_role_type' ).defaultTo( 'write' ) @@ -41,7 +41,7 @@ exports.up = async knex => { table.text( 'speckle_type' ).defaultTo( 'Base' ).notNullable( ) table.text( 'applicationId' ) table.jsonb( 'data' ) - table.uuid( 'author' ).references( 'id' ).inTable( 'users' ) + table.text( 'author' ).references( 'id' ).inTable( 'users' ) table.timestamp( 'created_at' ).defaultTo( knex.fn.now( ) ) table.index( [ 'speckle_type' ], 'type_index' ) } ) @@ -72,8 +72,8 @@ 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.uuid( 'stream_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) - table.uuid( 'author' ).references( 'id' ).inTable( 'users' ) + table.text( 'stream_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) + table.text( 'author' ).references( 'id' ).inTable( 'users' ) table.text( 'name' ) table.specificType( 'type', 'speckle_reference_type' ).defaultTo( 'branch' ) table.text( 'description' ) @@ -95,12 +95,12 @@ 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.uuid( 'stream_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) + table.text( 'stream_id' ).references( 'id' ).inTable( 'streams' ).notNullable( ) table.text( 'commit_id' ).references( 'hash' ).inTable( 'objects' ).notNullable( ) } ) await knex.schema.createTable( 'user_commits', table => { - table.uuid( 'owner_id' ).references( 'id' ).inTable( 'users' ).notNullable( ) + table.text( 'owner_id' ).references( 'id' ).inTable( 'users' ).notNullable( ) table.text( 'commit_id' ).references( 'hash' ).inTable( 'objects' ) } ) } diff --git a/modules/core/streams/controllers.js b/modules/core/streams/controllers.js index 6d96ff949..e62b98160 100644 --- a/modules/core/streams/controllers.js +++ b/modules/core/streams/controllers.js @@ -1,12 +1,16 @@ 'use strict' const debug = require( 'debug' )( 'speckle:test' ) -const { getStream, createStream, updateStream, grantPermissionsStream, revokePermissionsStream } = require( './services' ) +const { getUserStreams, getStreamUsers, getStream, createStream, updateStream, grantPermissionsStream, revokePermissionsStream } = require( './services' ) module.exports = { getStreams: async ( req, res, next ) => { - res.status( 418 ).send( { todo: true } ) - next( ) + try { + let streams = await getUserStreams( req.user.id ) + res.status( 200 ).send( streams ) + } catch ( err ) { + next( err ) + } }, getStream: async ( req, res, next ) => { @@ -45,18 +49,23 @@ module.exports = { grantPermissions: async ( req, res, next ) => { try { - await grantPermissionsStream( req.params.resourceId, req.body.id, req.body.role ) - + await grantPermissionsStream( req.params.resourceId, req.body.id, req.body.role || 'read' ) + res.status( 201 ).send( { success: true } ) + + req.eventData = { id: req.params.resourceId, userId: req.body.id } next( ) } catch ( err ) { next( err ) } - }, revokePermissions: async ( req, res, next ) => { try { + await revokePermissionsStream( req.params.resourceId, req.body.id ) + res.status( 200 ).send( { success: true } ) + req.eventData = { id: req.params.resourceId, userId: req.body.id } + next( ) } catch ( err ) { next( err ) } @@ -64,8 +73,10 @@ module.exports = { getStreamUsers: async ( req, res, next ) => { try { - + let users = await getStreamUsers( req.params.resourceId ) + res.status( 200 ).send( users ) } catch ( err ) { + console.log( err ) next( err ) } } diff --git a/modules/core/streams/index.js b/modules/core/streams/index.js index f13bd7b7d..6fd171523 100644 --- a/modules/core/streams/index.js +++ b/modules/core/streams/index.js @@ -52,4 +52,6 @@ streams.delete( authorize( 'stream_acl', 'streams', 'owner' ), revokePermissions, announce( 'stream-deleted', 'user' ) -) \ No newline at end of file +) + +// console.log( streams.stack ) \ No newline at end of file diff --git a/modules/core/streams/services.js b/modules/core/streams/services.js index ea05a9fc4..4718c0493 100644 --- a/modules/core/streams/services.js +++ b/modules/core/streams/services.js @@ -1,5 +1,5 @@ 'use strict' - +const crs = require( 'crypto-random-string' ) const root = require( 'app-root-path' ) const knex = require( `${root}/db/knex` ) @@ -7,11 +7,11 @@ const Streams = ( ) => knex( 'streams' ) const Acl = ( ) => knex( 'stream_acl' ) module.exports = { - + createStream: async ( stream, ownerId ) => { - delete stream.id delete stream.created_at stream.updated_at = knex.fn.now( ) + stream.id = crs( { length: 10 } ) let [ res ] = await Streams( ).returning( 'id' ).insert( stream ) await Acl( ).insert( { user_id: ownerId, resource_id: res, role: 'owner' } ) @@ -62,7 +62,7 @@ module.exports = { throw new Error( 'not implemented' ) }, - getStreamsUser: async ( userId, offset, limit ) => { + getUserStreams: async ( userId, offset, limit ) => { offset = offset || 0 limit = limit || 100 @@ -70,4 +70,10 @@ module.exports = { .rightJoin( 'streams', { 'streams.id': 'stream_acl.resource_id' } ) .limit( limit ).offset( offset ) }, + + getStreamUsers: async ( streamId ) => { + return Acl( ).where( { resource_id: streamId } ) + .rightJoin( 'users', { 'users.id': 'stream_acl.user_id' } ) + .select( 'role', 'username', 'name', 'id' ) + } } \ No newline at end of file diff --git a/modules/core/tests/001-streams.spec.js b/modules/core/tests/001-streams.spec.js index 94d34005d..f5507cad9 100644 --- a/modules/core/tests/001-streams.spec.js +++ b/modules/core/tests/001-streams.spec.js @@ -11,7 +11,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 { createStream, getStream, updateStream, deleteStream, getUserStreams, getStreamUsers, grantPermissionsStream, revokePermissionsStream } = require( '../streams/services' ) describe( 'Streams', ( ) => { @@ -68,7 +68,7 @@ describe( 'Streams', ( ) => { } ) it( 'Should get all streams for a user', async ( ) => { - let all = await getStreamsUser( userOne.id ) + let all = await getUserStreams( userOne.id ) expect( all ).to.have.lengthOf( 2 ) } ) } ) @@ -91,15 +91,22 @@ describe( 'Streams', ( ) => { } ) it( 'Stream should show up in the other users` list', async ( ) => { - let userTwoStreams = await getStreamsUser( userTwo.id ) + let userTwoStreams = await getUserStreams( userTwo.id ) expect( userTwoStreams ).to.have.lengthOf( 1 ) expect( userTwoStreams[ 0 ] ).to.have.property( 'role' ) expect( userTwoStreams[ 0 ].role ).to.equal( 'write' ) } ) + it( 'Should get the users with access to a stream', async ( ) => { + let users = await getStreamUsers( testStream.id ) + expect( users ).to.have.lengthOf( 2 ) + expect( users[ 0 ] ).to.not.have.property( 'email' ) + expect( users[ 0 ] ).to.have.property( 'id' ) + } ) + it( 'Should revoke permissions on stream', async ( ) => { await revokePermissionsStream( testStream.id, userTwo.id ) - let userTwoStreams = await getStreamsUser( userTwo.id ) + let userTwoStreams = await getUserStreams( userTwo.id ) expect( userTwoStreams ).to.have.lengthOf( 0 ) } ) @@ -112,14 +119,14 @@ describe( 'Streams', ( ) => { } } ) - it( '🤔 DUBIOUS: A stream should not have more than one owner', async ( ) => { + it( '🤔 DUBIOUS DESIGN DECISION: A stream should not have more than one owner', async ( ) => { let newStream = { name: 'XXX' } newStream.id = await createStream( newStream, userOne.id ) await grantPermissionsStream( newStream.id, userTwo.id, 'owner' ) - let usrStreams1 = await getStreamsUser( userOne.id ) + let usrStreams1 = await getUserStreams( userOne.id ) let s1 = usrStreams1.find( s => s.name === 'XXX' ) - let usrStreams2 = await getStreamsUser( userTwo.id ) + let usrStreams2 = await getUserStreams( userTwo.id ) let s2 = usrStreams2.find( s => s.name === 'XXX' ) expect( s1.role ).to.not.equal( 'owner' ) @@ -174,6 +181,12 @@ describe( 'Streams', ( ) => { expect( res.body ).to.have.property( 'name' ) } ) + it( 'Should get the all the streams of an user', async ( ) => { + const res = await chai.request( app ).get( `/streams` ).set( 'Authorization', `Bearer ${tokenA}` ) + expect( res ).to.have.status( 200 ) + expect( res.body ).to.have.lengthOf( 2 ) + } ) + it( 'Should get a public stream, even if user is anonymous', async ( ) => { const res = await chai.request( app ).get( `/streams/${publicStream.id}` ) expect( res ).to.have.status( 200 ) @@ -204,27 +217,33 @@ describe( 'Streams', ( ) => { } ) it( 'Should grant permissions on a stream', async ( ) => { - const shareRes = await chai.request( app ).post( `/streams/users/${privateStream.id}` ).send( { id: userB.id, role: 'read' } ).set( 'Authorization', `Bearer ${tokenA}` ) - console.log(shareRes) - expect( shareRes ).to.have.status( 200 ) + const shareRes = await chai.request( app ).post( `/streams/${privateStream.id}/users` ).send( { id: userB.id, role: 'read' } ).set( 'Authorization', `Bearer ${tokenA}` ) + expect( shareRes ).to.have.status( 201 ) + const userBRes = await chai.request( app ).get( `/streams/${privateStream.id}` ).set( 'Authorization', `Bearer ${tokenB}` ) - console.log(userBRes.status) expect( userBRes ).to.have.status( 200 ) expect( userBRes.body ).to.have.property( 'name' ) expect( userBRes.body ).to.have.property( 'description' ) } ) - it( 'Should revoke permissions on a stream', async ( ) => { - assert.fail( ) + it( 'Should get all users with access to a stream', async ( ) => { + const userRes = await chai.request( app ).get( `/streams/${privateStream.id}/users` ).set( 'Authorization', `Bearer ${tokenB}` ) + expect( userRes ).to.have.status( 200 ) + expect( userRes.body ).to.have.lengthOf( 2 ) + } ) + it( 'Should revoke permissions on a stream', async ( ) => { + const revokeRes = await chai.request( app ).delete( `/streams/${privateStream.id}/users` ).send( { id: userB.id, role: 'read' } ).set( 'Authorization', `Bearer ${tokenA}` ) + expect( revokeRes ).to.have.status( 200 ) + + const userBRes = await chai.request( app ).get( `/streams/${privateStream.id}` ).set( 'Authorization', `Bearer ${tokenB}` ) + expect( userBRes ).to.have.status( 401 ) } ) it( 'Should delete a stream', async ( ) => { assert.fail( 'Not implemented yet.' ) } ) - - } ) } ) \ No newline at end of file diff --git a/modules/core/users/services.js b/modules/core/users/services.js index ef9b0522e..016e9faf0 100644 --- a/modules/core/users/services.js +++ b/modules/core/users/services.js @@ -9,7 +9,7 @@ const Keys = ( ) => knex( 'api_token' ) module.exports = { createUser: async ( user ) => { - delete user.id + user.id = crs( { length: 10 } ) if ( user.password ) { user.password_digest = await bcrypt.hash( user.password, 10 )