feat(comments): scaffolding migrations, resolvers, etc

This commit is contained in:
Dimitrie Stefanescu
2022-02-22 19:36:22 +00:00
parent 26b17941b5
commit b8f352ea9d
5 changed files with 266 additions and 45 deletions
@@ -3,9 +3,27 @@ const { authorizeResolver, pubsub } = require( `${appRoot}/modules/shared` )
const { ForbiddenError, UserInputError, ApolloError, withFilter } = require( 'apollo-server-express' )
const { getStream } = require( '../../../core/services/streams' )
module.exports = {
Query: {},
Mutation:{
module.exports = {
Stream: {
async comments( parent, args, context, info ) {
// TODO
},
async comment( parent, args, context, info ) {
// TODO
}
},
Commit: {
async comments( parent, args, context, info ) {
// TODO
}
},
Object: {
async comments( parent, args, context, info ) {
// TODO
}
},
Mutation: {
// Used for broadcasting real time chat head bubbles and status.
async userCommentActivityBroadcast( parent, args, context, info ) {
let stream = await getStream( { streamId: args.streamId, userId: context.userId } )
if ( !stream )
@@ -25,16 +43,33 @@ module.exports = {
} )
return true
},
async userCommentCreate( parent, args, context, info ) {
async commentCreate( parent, args, context, info ) {
// TODO: check perms, persist comment
console.log( args )
// TODO: persist comment
await pubsub.publish( 'COMMENT_CREATED', {
userCommentCreated: args.comment,
commentCreated: args.comment,
streamId: args.streamId,
resourceId: args.resourceId
} )
return true
}
},
async commentEdit( parent, args, context, info ) {
// TODO
},
async commentReply( parent, args, context, info ) {
// TODO
await pubsub.publish( 'COMMENT_REPLY_CREATED', {
commentCreated: args.comment,
streamId: args.streamId,
resourceId: args.resourceId
} )
return true
},
async commentReplyEdit( parent, args, context, info ) {
// TODO
},
},
Subscription:{
userCommentActivity: {
@@ -43,11 +78,17 @@ module.exports = {
return payload.streamId === variables.streamId && payload.resourceId === variables.resourceId
} )
},
userCommentCreated: {
commentCreated: {
subscribe: withFilter( () => pubsub.asyncIterator( [ 'COMMENT_CREATED' ] ), async( payload, variables, context ) => {
await authorizeResolver( context.userId, payload.streamId, 'stream:reviewer' )
return payload.streamId === variables.streamId && payload.resourceId === variables.resourceId
} )
},
commentReplyCreated: {
subscribe: withFilter( () => pubsub.asyncIterator( [ 'COMMENT_REPLY_CREATED' ] ), async( payload, variables, context ) => {
await authorizeResolver( context.userId, payload.streamId, 'stream:reviewer' )
return payload.streamId === variables.streamId && payload.resourceId === variables.resourceId
} )
}
}
}
@@ -1,15 +1,97 @@
type Comment {
id: String!
authorId: String!
archived: Boolean!
createdAt: DateTime!
updatedAt: DateTime!
text: String!
data: JSONObject!
resources: [String]!
replies: CommentReplyCollection
}
type CommentReply {
id: String!
authorId: String!
archived: Boolean!
createdAt: DateTime!
updatedAt: DateTime!
text: String!
}
type CommentCollection {
totalCount: Int!
cursor: String
items: [Comment]
}
type CommentReplyCollection {
totalCount: Int!
cursor: String
items: [CommentReply]
}
input CommentCreateInput {
streamId: String!
resources: [String]!
text: String!
data: JSONObject!
}
input CommentEditInput {
streamId: String!
id: String!
text: String!
data: JSONObject!
}
input ReplyCreateInput {
text: String!
data: JSONObject!
}
input ReplyEditInput {
id: String!
text: String!
data: JSONObject!
}
extend type Stream {
comments(limit: Int! = 20, archived: Boolean = false, cursor: String): Boolean
comment(id: String!): Comment
}
extend type Commit {
comments(limit: Int! = 20, cursor: String): Boolean
}
extend type Object {
comments(limit: Int! = 20, cursor: String): Boolean
}
extend type Mutation {
# Used for broadcasting real time chat head bubbles and status.
userCommentActivityBroadcast(
streamId: String!
resourceId: String!
data: JSONObject
): Boolean! @hasRole(role: "server:user") @hasScope(scope: "streams:read")
userCommentCreate(
streamId: String!
resourceId: String!
comment: JSONObject!
): Boolean! @hasRole(role: "server:user") @hasScope(scope: "streams:read")
commentCreate(input: CommentCreateInput!): Boolean!
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
commentEdit(streamId: String!, comment: JSONObject!): Boolean!
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
commentReply(streamId: String!, commentReply: JSONObject!): Boolean!
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
commentReplyEdit(streamId: String!, commentReply: JSONObject!): Boolean!
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
}
extend type Subscription {
@@ -17,14 +99,11 @@ extend type Subscription {
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
userCommentCreated(streamId: String!, resourceId: String!): JSONObject
commentCreated(streamId: String!, resourceId: String!): JSONObject
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
commentReplyCreated(streamId: String!, commentId: String!): JSONObject
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
}
type Comment {
id: String!
text: String!
authorId: String!
# TBD: how do we index on streams, commits, objects, selected objects, etc.
}
@@ -0,0 +1,50 @@
// /* istanbul ignore file */
exports.up = async ( knex ) => {
await knex.schema.createTable( 'comments', table => {
table.string( 'id', 10 ).primary( )
table.string( 'authorId', 10 ).references( 'id' ).inTable( 'users' ).notNullable().index( )
table.boolean( 'archived' ).defaultTo( false ).index( )
table.timestamp( 'createdAt' ).defaultTo( knex.fn.now( ) )
table.timestamp( 'updatedAt' ).defaultTo( knex.fn.now( ) )
table.string( 'text' )
table.jsonb( 'data' )
} )
// Comments -< Replies
await knex.schema.createTable( 'comment_replies', table => {
table.string( 'id', 10 ).primary( )
table.string( 'authorId', 10 ).references( 'id' ).inTable( 'users' ).notNullable().index( )
table.boolean( 'archived' ).defaultTo( false ).index( )
table.timestamp( 'createdAt' ).defaultTo( knex.fn.now( ) )
table.timestamp( 'updatedAt' ).defaultTo( knex.fn.now( ) )
table.string( 'text' )
} )
// Streams >- -< Comments
// Minor futureproofing: a comment can be written "on top of" multiple resources from multiple streams.
await knex.schema.createTable( 'stream_comments', table => {
table.string( 'stream', 10 ).references( 'id' ).inTable( 'streams' ).notNullable()
table.string( 'comment', 10 ).references( 'id' ).inTable( 'comments' ).notNullable()
} )
// Commits >- -< Comments
await knex.schema.createTable( 'commit_comments', table => {
table.string( 'commit', 10 ).references( 'id' ).inTable( 'commits' ).notNullable()
table.string( 'comment', 10 ).references( 'id' ).inTable( 'comments' ).notNullable()
} )
// Objects >- -< Comments
await knex.schema.createTable( 'object_comments', table => {
table.string( 'object' ) // note: not a FK because we don't enforce uniqeness to speed things up
table.string( 'comment', 10 ).references( 'id' ).inTable( 'comments' ).notNullable()
table.index( [ 'object', 'comment' ] )
} )
}
exports.down = async ( knex ) => {
await knex.schema.dropTableIfExists( 'object_comments' )
await knex.schema.dropTableIfExists( 'commit_comments' )
await knex.schema.dropTableIfExists( 'stream_comments' )
await knex.schema.dropTableIfExists( 'comment_replies' )
await knex.schema.dropTableIfExists( 'comments' )
}
@@ -0,0 +1,50 @@
'use strict'
const appRoot = require( 'app-root-path' )
const knex = require( `${appRoot}/db/knex` )
const Comments = () => knex( 'comments' )
const CommentReplies = () => knex( 'comments' )
module.exports = {
async createComment( {} ) {
// TODO
},
async editComment( {} ) {
// TODO
},
async archiveComment( {} ) {
// TODO
},
async createCommentReply( {} ) {
// TODO
},
async editCommentReply( {} ) {
// TODO
},
async archiveCommentReply( {} ) {
// TODO
},
async getStreamComments( {} ) {
// TODO
},
async getCommitComments( {} ) {
// TODO
},
async getObjectComments( {} ) {
// TODO
},
async getCommentReplies( {} ) {
// TODO
}
}
@@ -2,16 +2,21 @@ extend type Query {
"""
Returns a specific stream.
"""
stream( id: String! ): Stream
stream(id: String!): Stream
"""
All the streams of the current user, pass in the `query` parameter to search by name, description or ID.
"""
streams( query: String, limit: Int = 25, cursor: String ): StreamCollection
streams(query: String, limit: Int = 25, cursor: String): StreamCollection
@hasScope(scope: "streams:read")
adminStreams( offset: Int = 0, query: String, orderBy: String, visibility: String, limit: Int = 25 ): StreamCollection
@hasRole(role: "server:admin")
adminStreams(
offset: Int = 0
query: String
orderBy: String
visibility: String
limit: Int = 25
): StreamCollection @hasRole(role: "server:admin")
}
type Stream {
@@ -25,7 +30,7 @@ type Stream {
role: String
createdAt: DateTime!
updatedAt: DateTime!
collaborators: [ StreamCollaborator ]!
collaborators: [StreamCollaborator]!
size: String
}
@@ -33,7 +38,7 @@ extend type User {
"""
All the streams that a user has access to.
"""
streams( limit: Int! = 25, cursor: String ): StreamCollection
streams(limit: Int! = 25, cursor: String): StreamCollection
}
type StreamCollaborator {
@@ -47,48 +52,45 @@ type StreamCollaborator {
type StreamCollection {
totalCount: Int!
cursor: String
items: [ Stream ]
items: [Stream]
}
extend type Mutation {
"""
Creates a new stream.
"""
streamCreate( stream: StreamCreateInput! ): String
streamCreate(stream: StreamCreateInput!): String
@hasRole(role: "server:user")
@hasScope(scope: "streams:write")
"""
Updates an existing stream.
"""
streamUpdate( stream: StreamUpdateInput! ): Boolean!
streamUpdate(stream: StreamUpdateInput!): Boolean!
@hasRole(role: "server:user")
@hasScope(scope: "streams:write")
"""
Deletes an existing stream.
"""
streamDelete( id: String! ): Boolean!
streamDelete(id: String!): Boolean!
@hasRole(role: "server:user")
@hasScope(scope: "streams:write")
streamsDelete( ids: [String!] ): Boolean!
@hasRole(role: "server:admin")
streamsDelete(ids: [String!]): Boolean! @hasRole(role: "server:admin")
"""
Grants permissions to a user on a given stream.
"""
streamGrantPermission( permissionParams: StreamGrantPermissionInput! ): Boolean
streamGrantPermission(permissionParams: StreamGrantPermissionInput!): Boolean
@hasRole(role: "server:user")
@hasScope(scope: "streams:write")
"""
Revokes the permissions of a user on a given stream.
"""
streamRevokePermission( permissionParams: StreamRevokePermissionInput! ): Boolean
@hasRole(role: "server:user")
@hasScope(scope: "streams:write")
streamRevokePermission(
permissionParams: StreamRevokePermissionInput!
): Boolean @hasRole(role: "server:user") @hasScope(scope: "streams:write")
}
extend type Subscription {
#
# User bound subscriptions that operate on the stream collection of an user
# Example relevant view/usecase: updating the list of streams for a user.
@@ -118,17 +120,16 @@ extend type Subscription {
"""
Subscribes to stream updated event. Use this in clients/components that pertain only to this stream.
"""
streamUpdated( streamId: String ): JSONObject
streamUpdated(streamId: String): JSONObject
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
"""
Subscribes to stream deleted event. Use this in clients/components that pertain only to this stream.
"""
streamDeleted( streamId: String ): JSONObject
streamDeleted(streamId: String): JSONObject
@hasRole(role: "server:user")
@hasScope(scope: "streams:read")
}
input StreamCreateInput {
@@ -145,12 +146,12 @@ input StreamUpdateInput {
}
input StreamGrantPermissionInput {
streamId: String!,
userId: String!,
streamId: String!
userId: String!
role: String!
}
input StreamRevokePermissionInput {
streamId: String!,
streamId: String!
userId: String!
}