From e51345cb9e2e3491c18e598eaebce30114edcfd6 Mon Sep 17 00:00:00 2001 From: Matteo Cominetti Date: Tue, 28 Jul 2020 16:20:45 +0100 Subject: [PATCH] feat(gql): adds streamSearch, adds pagination to userSearch --- modules/core/graph/resolvers/streams.js | 24 ++++++++++++++ .../graph/resolvers/{user.js => users.js} | 18 ++++------ modules/core/graph/schemas/streams.graphql | 7 ++-- modules/core/graph/schemas/user.graphql | 7 +++- modules/core/services/branches.js | 1 - modules/core/services/streams.js | 31 ++++++++++++----- modules/core/services/users.js | 33 +++++++++++-------- 7 files changed, 84 insertions(+), 37 deletions(-) rename modules/core/graph/resolvers/{user.js => users.js} (88%) diff --git a/modules/core/graph/resolvers/streams.js b/modules/core/graph/resolvers/streams.js index df53641ed..5f5420969 100644 --- a/modules/core/graph/resolvers/streams.js +++ b/modules/core/graph/resolvers/streams.js @@ -22,6 +22,28 @@ module.exports = { let stream = await getStream( { streamId: args.id } ) return stream + }, + async streams( parent, args, context, info ) { + await validateScopes( context.scopes, 'streams:read' ) + + if ( args.limit && args.limit > 100 ) + throw new UserInputError( 'Cannot return more than 100 results.' ) + + let totalCount = await getUserStreamsCount( {userId: context.userId, publicOnly: false} ) + + let {cursor, streams} = await getUserStreams( {userId: context.userId, limit: args.limit, cursor: args.cursor, publicOnly: false} ) + return {totalCount, cursor: cursor, items: streams} + }, + async streamSearch( parent, args, context, info ) { + await validateScopes( context.scopes, 'streams:read' ) + + if ( args.limit && args.limit > 100 ) + throw new UserInputError( 'Cannot return more than 100 results.' ) + + let totalCount = await getUserStreamsCount( {userId: context.userId, publicOnly: false, searchQuery: args.query} ) + + let {cursor, streams} = await getUserStreams( {userId: context.userId, limit: args.limit, cursor: args.cursor, publicOnly: false, searchQuery: args.query} ) + return {totalCount, cursor: cursor, items: streams} } }, Stream: { @@ -35,6 +57,8 @@ module.exports = { User: { async streams( parent, args, context, info ) { + if ( args.limit && args.limit > 100 ) + throw new UserInputError( 'Cannot return more than 100 results.' ) // Return only the user's public streams if parent.id !== context.userId let publicOnly = parent.id !== context.userId let totalCount = await getUserStreamsCount( { userId: parent.id, publicOnly } ) diff --git a/modules/core/graph/resolvers/user.js b/modules/core/graph/resolvers/users.js similarity index 88% rename from modules/core/graph/resolvers/user.js rename to modules/core/graph/resolvers/users.js index dbbb04c67..b9a680135 100644 --- a/modules/core/graph/resolvers/user.js +++ b/modules/core/graph/resolvers/users.js @@ -29,24 +29,20 @@ module.exports = { return await getUser( args.id || context.userId ) }, - async userSearchResults( parent, args, context, info ) { + async userSearch( parent, args, context, info ) { await validateServerRole( context, 'server:user' ) await validateScopes( context.scopes, 'profile:read' ) await validateScopes( context.scopes, 'users:read' ) - if ( !args.query ) { - throw new UserInputError( 'You must provide a search query.' ) - } - - if ( args.query.length < 3 ) { + if ( args.query.length < 3 ) throw new UserInputError( 'Search query must be at least 3 carachters.' ) - } + - if ( args.limit && args.limit > 100 ) { + if ( args.limit && args.limit > 100 ) throw new UserInputError( 'Cannot return more than 100 results.' ) - } - - return await searchUsers( args.query, args.limit ) + + let {cursor, users} = await searchUsers( args.query, args.limit, args.cursor ) + return {cursor: cursor, items: users} }, async userPwdStrength( parent, args, context, info ) { diff --git a/modules/core/graph/schemas/streams.graphql b/modules/core/graph/schemas/streams.graphql index 570d69f51..a27a8f0b5 100644 --- a/modules/core/graph/schemas/streams.graphql +++ b/modules/core/graph/schemas/streams.graphql @@ -1,5 +1,7 @@ extend type Query { stream( id: String! ): Stream + streams( limit: Int! = 100, cursor: String ): StreamCollection + streamSearch( query: String!, limit: Int! = 100, cursor: String ): StreamCollection } type Stream { @@ -16,7 +18,7 @@ extend type User { """ All the streams that a user has access to. """ - streams( limit: Int! = 20, cursor: String ): StreamCollectionUser + streams( limit: Int! = 20, cursor: String ): StreamCollection } type StreamCollaborator { @@ -25,12 +27,13 @@ type StreamCollaborator { role: String! } -type StreamCollectionUser { +type StreamCollection { totalCount: Int! cursor: String items: [ Stream ] } + extend type Mutation { """ Creates a new stream. diff --git a/modules/core/graph/schemas/user.graphql b/modules/core/graph/schemas/user.graphql index a4ae70bd5..85bd1cdb3 100644 --- a/modules/core/graph/schemas/user.graphql +++ b/modules/core/graph/schemas/user.graphql @@ -3,7 +3,7 @@ extend type Query { Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header). """ user( id: String ): User - userSearchResults(query: String, limit: Int! = 100): [UserSearchResult!]! + userSearch( query: String!, limit: Int! = 100, cursor: String ): UserSearchResultCollection userPwdStrength( pwd: String! ): JSONObject } @@ -23,6 +23,11 @@ type User { role: String } +type UserSearchResultCollection { + cursor: String + items: [ UserSearchResult ] +} + type UserSearchResult { id: String! username: String diff --git a/modules/core/services/branches.js b/modules/core/services/branches.js index 435c3fe5b..f3becdbec 100644 --- a/modules/core/services/branches.js +++ b/modules/core/services/branches.js @@ -10,7 +10,6 @@ const BranchCommits = ( ) => knex( 'branch_commits' ) module.exports = { async createBranch( { name, description, streamId, authorId } ) { - let branch = {} branch.id = crs( { length: 10 } ) branch.streamId = streamId diff --git a/modules/core/services/streams.js b/modules/core/services/streams.js index 3add28509..216339513 100644 --- a/modules/core/services/streams.js +++ b/modules/core/services/streams.js @@ -89,10 +89,10 @@ module.exports = { return await Streams( ).where( { id: streamId } ).del( ) }, - async getUserStreams( { userId, limit, cursor, publicOnly } ) { + async getUserStreams( {userId, limit, cursor, publicOnly, searchQuery } ) { limit = limit || 100 publicOnly = publicOnly !== false //defaults to true if not provided - + let likeQuery = "%" + searchQuery + "%" let query = Acl( ) .columns( [ { id: 'streams.id' }, 'name', 'description', 'isPublic', 'createdAt', 'updatedAt', 'role' ] ).select( ) .join( 'streams', 'stream_acl.resourceId', 'streams.id' ) @@ -104,16 +104,22 @@ module.exports = { if ( publicOnly ) query.andWhere( 'streams.isPublic', true ) + if ( searchQuery ) + query.andWhere( function () { + this.where( 'name', 'ILIKE', likeQuery ) + .orWhere( 'description', 'ILIKE', likeQuery ) + .orWhere( 'id', 'ILIKE', likeQuery ) //potentially useless? + } ) + query.orderBy( 'streams.updatedAt', 'desc' ).limit( limit ) let rows = await query return { streams: rows, cursor: rows.length > 0 ? rows[ rows.length - 1 ].updatedAt.toISOString( ) : null } }, - async getUserStreamsCount( { userId, publicOnly } ) { - + async getUserStreamsCount( {userId, publicOnly, searchQuery } ) { publicOnly = publicOnly !== false //defaults to true if not provided - + let likeQuery = "%" + searchQuery + "%" let query = Acl( ).count( ) .join( 'streams', 'stream_acl.resourceId', 'streams.id' ) .where( { userId: userId } ) @@ -121,6 +127,13 @@ module.exports = { if ( publicOnly ) query.andWhere( 'streams.isPublic', true ) + if ( searchQuery ) + query.andWhere( function () { + this.where( 'name', 'ILIKE', likeQuery ) + .orWhere( 'description', 'ILIKE', likeQuery ) + .orWhere( 'id', 'ILIKE', likeQuery ) //potentially useless? + } ) + let [ res ] = await query return parseInt( res.count ) }, @@ -128,10 +141,10 @@ module.exports = { async getStreamUsers( { streamId } ) { let query = Acl( ).columns( { role: 'stream_acl.role' }, 'id', 'name' ).select( ) - .where( { resourceId: streamId } ) - .rightJoin( 'users', { 'users.id': 'stream_acl.userId' } ) - .select( 'stream_acl.role', 'username', 'name', 'id' ) - .orderBy( 'stream_acl.role' ) + .where( { resourceId: streamId } ) + .rightJoin( 'users', { 'users.id': 'stream_acl.userId' } ) + .select( 'stream_acl.role', 'username', 'name', 'id' ) + .orderBy( 'stream_acl.role' ) return await query } diff --git a/modules/core/services/users.js b/modules/core/services/users.js index e91e0fe8f..e3d00992f 100644 --- a/modules/core/services/users.js +++ b/modules/core/services/users.js @@ -5,7 +5,7 @@ const appRoot = require( 'app-root-path' ) const knex = require( `${appRoot}/db/knex` ) const Users = () => knex( 'users' ) -const ServerRoles = () => knex( 'server_acl' ) +const Acl = () => knex( 'server_acl' ) module.exports = { @@ -16,7 +16,7 @@ module.exports = { */ async createUser( user ) { - let [ {count} ] = await ServerRoles().where( {role: 'server:admin'} ).count() + let [ {count} ] = await Acl().where( {role: 'server:admin'} ).count() user.id = crs( {length: 10} ) @@ -31,9 +31,9 @@ module.exports = { let res = await Users().returning( 'id' ).insert( user ) if ( parseInt( count ) === 0 ) { - await ServerRoles().insert( {userId: res[0], role: 'server:admin'} ) + await Acl().insert( {userId: res[0], role: 'server:admin'} ) } else { - await ServerRoles().insert( {userId: res[0], role: 'server:user'} ) + await Acl().insert( {userId: res[0], role: 'server:user'} ) } return res[0] @@ -70,7 +70,7 @@ module.exports = { }, async getUserRole( id ) { - let {role} = await ServerRoles().where( {userId: id} ).select( 'role' ).first() + let {role} = await Acl().where( {userId: id} ).select( 'role' ).first() return role }, @@ -82,16 +82,23 @@ module.exports = { await Users().where( {id: id} ).update( user ) }, - async searchUsers( query, limit ) { + async searchUsers( searchQuery, limit, cursor ) { limit = limit || 100 - let likeQuery = "%" + query + "%" - let users = await Users() - .where( {email: query} ) //match full email or partial username / name - .orWhere( 'username', 'like', likeQuery ) - .orWhere( 'name', 'like', likeQuery ) - .limit( limit ) + let likeQuery = "%" + searchQuery + "%" + let query = Users() + .where( function () { + this.where( {email: searchQuery} ) //match full email or partial username / name + .orWhere( 'username', 'ILIKE', likeQuery ) + .orWhere( 'name', 'ILIKE', likeQuery ) + } ) - return users + if ( cursor ) + query.andWhere( 'users.createdAt', '<', cursor ) + + query.orderBy( 'users.createdAt', 'desc' ).limit( limit ) + + let rows = await query + return {users: rows, cursor: rows.length > 0 ? rows[rows.length - 1].createdAt.toISOString() : null} }, async validatePasssword( {email, password} ) {