refactor(gql): services & resolvers around user streams, branches, etc
This commit is contained in:
@@ -22,7 +22,7 @@ const {
|
||||
} = require( '../../services/branches' )
|
||||
|
||||
module.exports = {
|
||||
Query: {},
|
||||
Query: { },
|
||||
Stream: {
|
||||
async branches( parent, args, context, info ) {
|
||||
throw new ApolloError( 'not implemented' )
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
'use strict'
|
||||
const { AuthorizationError, ApolloError } = require( 'apollo-server-express' )
|
||||
const appRoot = require( 'app-root-path' )
|
||||
const { createStream, getStream, updateStream, deleteStream, getUserStreams, getStreamUsers, grantPermissionsStream, revokePermissionsStream } = require( '../../services/streams' )
|
||||
const {
|
||||
createStream,
|
||||
getStream,
|
||||
updateStream,
|
||||
deleteStream,
|
||||
getUserStreams,
|
||||
getUserStreamsCount,
|
||||
getStreamUsers,
|
||||
grantPermissionsStream,
|
||||
revokePermissionsStream
|
||||
} = require( '../../services/streams' )
|
||||
const { validateServerRole, validateScopes, authorizeResolver } = require( `${appRoot}/modules/shared` )
|
||||
|
||||
module.exports = {
|
||||
@@ -24,9 +34,12 @@ module.exports = {
|
||||
async streams( parent, args, context, info ) {
|
||||
// Return only the user's public streams if parent.id !== context.userId
|
||||
let publicOnly = parent.id !== context.userId
|
||||
let streams = await getUserStreams( { userId: parent.id, offset: args.offset, limit: args.limit, publicOnly } )
|
||||
// TODO: Implement offsets in service, not in friggin array slice
|
||||
return { totalCount: streams.length, streams: streams.slice( args.offset, args.offset + args.limit ) }
|
||||
|
||||
let totalCount = await getUserStreamsCount( { userId: parent.id } )
|
||||
|
||||
let { cursor, streams } = await getUserStreams( { userId: parent.id, limit: args.limit, cursor: args.cursor, publicOnly: publicOnly } )
|
||||
|
||||
return { totalCount, cursor: cursor, streams: streams }
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
@@ -70,4 +83,4 @@ module.exports = {
|
||||
return await revokePermissionsStream( { ...args } )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,4 +60,4 @@ module.exports = {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
extend type Query {
|
||||
stream(id: String!): Stream
|
||||
stream( id: String! ): Stream
|
||||
}
|
||||
|
||||
type Stream {
|
||||
@@ -9,24 +9,24 @@ type Stream {
|
||||
isPublic: Boolean!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
users: [User]!
|
||||
users: [ User ]!
|
||||
}
|
||||
|
||||
extend type User {
|
||||
"""
|
||||
All the streams that a user has access to.
|
||||
"""
|
||||
streams(offset: Int! = 0, limit: Int! = 100): StreamCollection
|
||||
streams( limit: Int! = 20, cursor: String ): StreamCollectionUser
|
||||
"""
|
||||
The role this user has on a specific stream (can be populated when accessing a stream's users).
|
||||
"""
|
||||
role: String
|
||||
}
|
||||
|
||||
type StreamCollection {
|
||||
type StreamCollectionUser {
|
||||
totalCount: Int!
|
||||
streams: [Stream]
|
||||
cursor: String
|
||||
streams: [ Stream ]
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
@@ -43,7 +43,7 @@ extend type Mutation {
|
||||
"""
|
||||
streamDelete( id: String! ): Boolean!
|
||||
"""
|
||||
Grants permissions to an user on a given stream.
|
||||
Grants permissions to an user on a given stream.
|
||||
"""
|
||||
streamGrantPermission( streamId: String!, userId: String!, role: String! ): Boolean
|
||||
"""
|
||||
@@ -64,4 +64,4 @@ input StreamUpdateInput {
|
||||
name: String
|
||||
description: String
|
||||
isPublic: Boolean
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,8 @@ module.exports = {
|
||||
|
||||
async getCommitsByStreamId( { streamId, limit, cursor } ) {
|
||||
limit = limit || 20
|
||||
let query = StreamCommits( ).columns( [ 'commitId', 'message', 'referencedObject', { author: 'name' }, { authorId: 'users.id' }, 'commits.createdAt' ] ).select( )
|
||||
let query = StreamCommits( )
|
||||
.columns( [ 'commitId', 'message', 'referencedObject', { author: 'name' }, { authorId: 'users.id' }, 'commits.createdAt' ] ).select( )
|
||||
.join( 'commits', 'commits.id', 'stream_commits.commitId' )
|
||||
.join( 'users', 'commits.author', 'users.id' )
|
||||
.where( 'streamId', streamId )
|
||||
@@ -163,4 +164,4 @@ module.exports = {
|
||||
let [ res ] = await Commits( ).count( ).where( 'author', userId )
|
||||
return parseInt( res.count )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ module.exports = {
|
||||
throw new Error( 'Stream has only one ownership link left - cannot revoke permissions.' )
|
||||
|
||||
// TODO: below behaviour not correct. Flow:
|
||||
// Count owners
|
||||
// Count owners
|
||||
// If owner count > 1, then proceed to delete, otherwise throw an error (can't delete last owner - delete stream)
|
||||
|
||||
let aclEntry = await Acl( ).where( { resourceId: streamId, userId: userId } ).select( '*' ).first( )
|
||||
@@ -85,22 +85,37 @@ module.exports = {
|
||||
return true
|
||||
},
|
||||
|
||||
async deleteStream( { streamId } ) {
|
||||
async deleteStream( { streamId } ) {
|
||||
return await Streams( ).where( { id: streamId } ).del( )
|
||||
},
|
||||
|
||||
async getUserStreams( { userId, offset, limit, publicOnly } ) {
|
||||
offset = offset || 0
|
||||
async getUserStreams( { userId, limit, cursor, publicOnly } ) {
|
||||
limit = limit || 100
|
||||
publicOnly = publicOnly !== false //defaults to true if not provided
|
||||
|
||||
let query = { userId: userId }
|
||||
|
||||
if ( publicOnly ) query.isPublic = true
|
||||
|
||||
return Acl( ).where( query )
|
||||
.rightJoin( 'streams', { 'streams.id': 'stream_acl.resourceId' } )
|
||||
.limit( limit ).offset( offset )
|
||||
let query = Acl( )
|
||||
.columns( [ { id: 'streams.id' }, 'name', 'description', 'isPublic', 'createdAt', 'updatedAt' ] ).select( )
|
||||
.join( 'streams', 'stream_acl.resourceId', 'streams.id' )
|
||||
.where( 'stream_acl.userId', userId )
|
||||
|
||||
if ( cursor )
|
||||
query.andWhere( 'streams.updatedAt', '<', cursor )
|
||||
|
||||
if ( publicOnly )
|
||||
query.andWhere( 'streams.isPublic', true )
|
||||
|
||||
query.orderBy( 'streams.updatedAt', 'desc' ).limit( limit )
|
||||
|
||||
let rows = await query
|
||||
return { streams: rows, cursor: rows.length > 0 ? rows[ rows.length - 1 ].updatedAt : null }
|
||||
},
|
||||
|
||||
async getUserStreamsCount( { userId } ) {
|
||||
let [ res ] = await Acl( ).count( ).where( { userId: userId } )
|
||||
return parseInt( res.count )
|
||||
},
|
||||
|
||||
async getStreamUsers( { streamId } ) {
|
||||
@@ -108,4 +123,4 @@ module.exports = {
|
||||
.rightJoin( 'users', { 'users.id': 'stream_acl.userId' } )
|
||||
.select( 'role', 'username', 'name', 'id' )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ describe( 'GraphQL API Core', ( ) => {
|
||||
expect( res1.body.data.apiTokenCreate ).to.be.a( 'string' )
|
||||
|
||||
token1 = `Bearer ${res1.body.data.apiTokenCreate}`
|
||||
|
||||
const res2 = await sendRequest( userA.token, { query: `mutation { apiTokenCreate(name:"Token 1", scopes: ["streams:write", "streams:read", "users:email"]) }` } )
|
||||
token2 = `Bearer ${res2.body.data.apiTokenCreate}`
|
||||
|
||||
@@ -414,12 +413,14 @@ describe( 'GraphQL API Core', ( ) => {
|
||||
|
||||
|
||||
it( 'Should retrieve my streams', async ( ) => {
|
||||
const res = await sendRequest( userA.token, { query: `{ user { streamCollection { totalCount streams { id name role } } } }` } )
|
||||
const res = await sendRequest( userA.token, { query: `{ user { streams { totalCount streams { id name } } } }` } )
|
||||
// console.log( res.body.errors[0].locations )
|
||||
console.log( res.body.data )
|
||||
expect( res ).to.be.json
|
||||
expect( res.body.errors ).to.not.exist
|
||||
expect( res.body.data.user.streamCollection.totalCount ).to.equal( 3 )
|
||||
expect( res.body.data.user.streams.totalCount ).to.equal( 3 )
|
||||
|
||||
let streams = res.body.data.user.streamCollection.streams
|
||||
let streams = res.body.data.user.streams.streams
|
||||
let s1 = streams.find( s => s.name === 'TS1 (u A) Private UPDATED' )
|
||||
expect( s1 ).to.exist
|
||||
} )
|
||||
@@ -541,6 +542,7 @@ describe( 'GraphQL API Core', ( ) => {
|
||||
|
||||
} )
|
||||
} )
|
||||
|
||||
describe( 'Objects', ( ) => {
|
||||
let myCommit
|
||||
let myObjs
|
||||
@@ -665,6 +667,7 @@ describe( 'GraphQL API Core', ( ) => {
|
||||
} )
|
||||
|
||||
describe( 'Server Info', ( ) => {
|
||||
|
||||
it( 'Should return a valid server information object', async ( ) => {
|
||||
let q = `
|
||||
query{
|
||||
|
||||
@@ -6,7 +6,7 @@ const knex = require( `${appRoot}/db/knex` )
|
||||
const { validateToken } = require( `${appRoot}/modules/core/services/tokens` )
|
||||
|
||||
/*
|
||||
|
||||
|
||||
Graphql server context helper
|
||||
|
||||
*/
|
||||
@@ -41,14 +41,14 @@ async function contextMiddleware( req, res, next ) {
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
Keeps track of all the available roles on this server. It's seeded by the methods below.
|
||||
|
||||
*/
|
||||
let roles
|
||||
|
||||
/*
|
||||
|
||||
|
||||
Validates a user's server-bound role (admin, normal user, etc.)
|
||||
|
||||
*/
|
||||
@@ -72,7 +72,7 @@ async function validateServerRole( context, requiredRole ) {
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
Graphql scope validator
|
||||
|
||||
*/
|
||||
@@ -85,7 +85,7 @@ async function validateScopes( scopes, scope ) {
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
Graphql authorization: checks user id against access control lists
|
||||
|
||||
*/
|
||||
@@ -94,7 +94,7 @@ async function authorizeResolver( userId, resourceId, requiredRole ) {
|
||||
if ( !roles )
|
||||
roles = await knex( 'user_roles' ).select( '*' )
|
||||
|
||||
// TODO: Cache these results with a TTL of 1 mins or so, it's pointless to query the db every time we get a ping.
|
||||
// TODO: Cache these results with a TTL of 1 mins or so, it's pointless to query the db every time we get a ping.
|
||||
|
||||
let role = roles.find( r => r.name === requiredRole )
|
||||
|
||||
@@ -126,4 +126,4 @@ module.exports = {
|
||||
validateServerRole,
|
||||
validateScopes,
|
||||
authorizeResolver
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"dev:frontend": "cd frontend && npm run serve",
|
||||
"build:frontend": "cd frontend && npm run build",
|
||||
"dev:server": "NODE_ENV=development POSTGRES_URL=postgres://localhost/speckle2_dev DEBUG=www:server,speckle:* nodemon ./bin/www --watch . --watch ./bin/www -e js,graphql,env",
|
||||
"dev:server:test": "NODE_ENV=test POSTGRES_URL=postgres://localhost/speckle2_dev DEBUG=www:server,speckle:* nodemon ./bin/www --watch . --watch ./bin/www -e js,graphql,env",
|
||||
"test:server": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test POSTGRES_URL=postgres://localhost/speckle2_test nyc nyc --reporter html --reporter lcovonly mocha -s 0 --timeout 2000 --exit",
|
||||
"test:server:watch": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test POSTGRES_URL=postgres://localhost/speckle2_test mocha --watch -s 0 --exit",
|
||||
"test:server:graph": "PORT=3001 DEBUG=speckle:test,speckle:errors NODE_ENV=test POSTGRES_URL=postgres://localhost/speckle2_test mocha ./modules/core/tests/graph.spec.js --watch -s 0 --exit --no-config",
|
||||
|
||||
Reference in New Issue
Block a user