feat(server): add comment storage and retreive backedn

This commit is contained in:
Gergő Jedlicska
2022-02-25 14:16:49 +01:00
parent 6365589850
commit 72334a8f06
7 changed files with 116 additions and 49 deletions
@@ -66,6 +66,7 @@ export default {
methods: {
async addComment() {
let commentInput = {
streamId: this.$route.params.streamId,
resources: [
{ type: 'stream', id: this.$route.params.streamId },
{
@@ -3,9 +3,33 @@ const { authorizeResolver, pubsub } = require( `${appRoot}/modules/shared` )
const { ForbiddenError, UserInputError, ApolloError, withFilter } = require( 'apollo-server-express' )
const { getStream } = require( `${appRoot}/modules/core/services/streams` )
const { createComment } = require( `${appRoot}/modules/comments/services` )
const { getComment, getComments, createComment } = require( `${appRoot}/modules/comments/services` )
const authorizeStreamAccess = async ( { streamId, userId, auth } ) => {
const stream = await getStream( { streamId, userId } )
if ( !stream )
throw new ApolloError( 'Stream not found' )
if ( !stream.isPublic && auth === false )
throw new ForbiddenError( 'You are not authorized.' )
if ( !stream.isPublic ) {
await authorizeResolver( userId, streamId, 'stream:reviewer' )
}
}
module.exports = {
Query: {
async comment( parent, args, context, info ) {
await authorizeStreamAccess( { streamId: args.streamId, userId: context.userId, auth: context.auth } )
return await getComment( args.id )
},
async comments( parent, args, context, info ) {
await authorizeStreamAccess( { streamId: args.streamId, userId: context.userId, auth: context.auth } )
return await getComments( args )
}
},
Stream: {
async comments( parent, args, context, info ) {
// TODO
@@ -32,16 +56,7 @@ module.exports = {
Mutation: {
// Used for broadcasting real time chat head bubbles and status. Does not persist anything!
async userCommentActivityBroadcast( parent, args, context, info ) {
let stream = await getStream( { streamId: args.streamId, userId: context.userId } )
if ( !stream )
throw new ApolloError( 'Stream not found' )
if ( !stream.isPublic && context.auth === false )
throw new ForbiddenError( 'You are not authorized.' )
if ( !stream.isPublic ) {
await authorizeResolver( context.userId, args.streamId, 'stream:reviewer' )
}
await authorizeStreamAccess( { streamId: args.streamId, userId: context.userId, auth: context.auth } )
await pubsub.publish( 'COMMENT_ACTIVITY', {
userCommentActivity: args.data,
@@ -1,3 +1,9 @@
extend type Query {
comment(id: String!, streamId: String!): Comment
comments(streamId: String!, resources: [ResourceIdentifierInput]!, limit: Int = 25, cursor: String): CommentCollection
}
type Comment {
id: String!
authorId: String!
@@ -13,8 +19,8 @@ type Comment {
type CommentCollection {
totalCount: Int!
cursor: String
items: [Comment]
cursor: DateTime!
items: [Comment]!
}
type ResourceIdentifier {
@@ -50,7 +56,7 @@ type CommentReplyCollection {
}
input CommentCreateInput {
# streamId: String!
streamId: String!
resources: [ResourceIdentifierInput]!
text: String!
data: JSONObject!
@@ -7,6 +7,7 @@ exports.up = async ( knex ) => {
table.timestamp( 'updatedAt' ).defaultTo( knex.fn.now( ) )
table.string( 'text' )
table.jsonb( 'data' )
table.boolean( 'archived' ).defaultTo( false ).notNullable()
table.string( 'parentComment', 10 ).references( 'id' ).inTable( 'comments' ).defaultTo( null )
} )
@@ -7,24 +7,45 @@ const Comments = () => knex( 'comments' )
const StreamComments = () => knex( 'stream_comments' )
const CommitComments = () => knex( 'commit_comments' )
const ObjectComments = () => knex( 'object_comments' )
const CommentReplies = () => knex( 'comment_replies' )
const intersection = require( 'lodash.intersection' )
const persistResourceLinks = async ( commentId, resources ) => {
for ( const resource of resources ) {
switch ( resource.type ) {
// having the type as a string here, kinda makes having two N mapping tables useless
case 'stream':
return await StreamComments().insert( { stream: resource.id, comment: commentId } )
await StreamComments().insert( { stream: resource.id, comment: commentId } )
break
case 'commit':
return await CommitComments().insert( { commit: resource.id, comment: commentId } )
await CommitComments().insert( { commit: resource.id, comment: commentId } )
break
case 'object':
return await ObjectComments().insert( { commit: resource.id, comment: commentId } )
await ObjectComments().insert( { object: resource.id, comment: commentId } )
break
default:
throw Error( `resource type ${resource.type} is not supported as a comment target` )
}
}
}
const getCommentsIdsForResource = async ( { id, type } ) => {
let commentLinks
switch ( type ) {
case 'stream':
commentLinks = await StreamComments().where( { stream: id } )
break
case 'commit':
commentLinks = await CommitComments().where( { commit: id } )
break
case 'object':
commentLinks = await ObjectComments().where( { object: id } )
break
default:
throw Error( `No comments are supported for ${resource.type}` )
}
return commentLinks.map( l => l.comment )
}
module.exports = {
async createComment( { userId, input } ) {
let comment = { ...input }
@@ -44,51 +65,62 @@ module.exports = {
// TODO
},
// async archiveComment( {} ) {
// // TODO
// },
async archiveComment( {} ) {
// TODO
},
async createCommentReply( {} ) {
// TODO
},
async editCommentReply( {} ) {
// TODO
},
// async editCommentReply( {} ) {
// // TODO
// },
// async archiveCommentReply( {} ) {
// // TODO
// },
async getComments({ resources }) {
async getComment( id ) {
const [ comment ] = await Comments().where( { id } )
return comment
},
async getStreamComments( { streamId, limit, archived, cursor } ) {
// TODO
limit = limit || 25
let raw = `SELECT * from stream_comments
JOIN comments ON comments.id = stream_comments."comment"
WHERE stream_comments.stream = 'a55537c38f'
ORDER BY comments."createdAt" DESC
LIMIT 25
`
let query = Comments()
.columns( [ 'id', 'authorId', 'archived', 'createdAt', 'updatedAt', 'text', 'data' ] )
.select()
.join( 'stream_comments', 'comment.id', 'stream_comments.commit' )
async getComments( { resources, limit, cursor } ) {
// maybe since we are so streamId limited, asking for a streamId here would make sense
// and not treat the stream as a resource
const commentIds = await Promise.all( resources.map( r => getCommentsIdsForResource( r ) ) )
const relevantComments = intersection( ...commentIds )
const items = await Comments().whereIn( 'id', relevantComments ).orderBy( 'createdAt' ).limit( limit )
cursor = items[items.length - 1].createdAt
return { items, cursor, totalCount: relevantComments.length }
},
async getCommitComments( { commitId, limit, archived, cursor } ) {
// TODO
},
// async getStreamComments( { streamId, limit, archived, cursor } ) {
// // TODO
// limit = limit || 25
// let raw = `SELECT * from stream_comments
// JOIN comments ON comments.id = stream_comments."comment"
// WHERE stream_comments.stream = 'a55537c38f'
// ORDER BY comments."createdAt" DESC
// LIMIT 25
// `
// let query = Comments()
// .columns( [ 'id', 'authorId', 'archived', 'createdAt', 'updatedAt', 'text', 'data' ] )
// .select()
// .join( 'stream_comments', 'comment.id', 'stream_comments.commit' )
// },
async getObjectComments( { objectId, limit, archived, cursor } ) {
// TODO
},
// async getCommitComments( { commitId, limit, archived, cursor } ) {
// // TODO
// },
async getCommentReplies( { commentId } ) {
// TODO
}
// async getObjectComments( { objectId, limit, archived, cursor } ) {
// // TODO
// },
// async getCommentReplies( { commentId } ) {
// // TODO
// }
}
+11
View File
@@ -37,6 +37,7 @@
"lodash.chunk": "^4.2.0",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.intersection": "^4.4.0",
"lodash.merge": "^4.6.2",
"lodash.set": "^4.3.2",
"lodash.values": "^4.3.0",
@@ -15234,6 +15235,11 @@
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"node_modules/lodash.intersection": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.intersection/-/lodash.intersection-4.4.0.tgz",
"integrity": "sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU="
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
@@ -35100,6 +35106,11 @@
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.intersection": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.intersection/-/lodash.intersection-4.4.0.tgz",
"integrity": "sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU="
},
"lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+1
View File
@@ -46,6 +46,7 @@
"lodash.chunk": "^4.2.0",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.intersection": "^4.4.0",
"lodash.merge": "^4.6.2",
"lodash.set": "^4.3.2",
"lodash.values": "^4.3.0",