mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' into chat-notifications
This commit is contained in:
commit
94bb798d72
@ -6,6 +6,10 @@ export const createMessageMutation = () => {
|
|||||||
CreateMessage(roomId: $roomId, content: $content) {
|
CreateMessage(roomId: $roomId, content: $content) {
|
||||||
id
|
id
|
||||||
content
|
content
|
||||||
|
senderId
|
||||||
|
username
|
||||||
|
avatar
|
||||||
|
date
|
||||||
saved
|
saved
|
||||||
distributed
|
distributed
|
||||||
seen
|
seen
|
||||||
@ -17,7 +21,7 @@ export const createMessageMutation = () => {
|
|||||||
export const messageQuery = () => {
|
export const messageQuery = () => {
|
||||||
return gql`
|
return gql`
|
||||||
query ($roomId: ID!, $first: Int, $offset: Int) {
|
query ($roomId: ID!, $first: Int, $offset: Int) {
|
||||||
Message(roomId: $roomId, first: $first, offset: $offset, orderBy: createdAt_desc) {
|
Message(roomId: $roomId, first: $first, offset: $offset, orderBy: indexId_desc) {
|
||||||
_id
|
_id
|
||||||
id
|
id
|
||||||
indexId
|
indexId
|
||||||
|
|||||||
@ -6,6 +6,17 @@ export const createRoomMutation = () => {
|
|||||||
CreateRoom(userId: $userId) {
|
CreateRoom(userId: $userId) {
|
||||||
id
|
id
|
||||||
roomId
|
roomId
|
||||||
|
roomName
|
||||||
|
lastMessageAt
|
||||||
|
unreadCount
|
||||||
|
users {
|
||||||
|
_id
|
||||||
|
id
|
||||||
|
name
|
||||||
|
avatar {
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -18,6 +29,20 @@ export const roomQuery = () => {
|
|||||||
id
|
id
|
||||||
roomId
|
roomId
|
||||||
roomName
|
roomName
|
||||||
|
lastMessageAt
|
||||||
|
unreadCount
|
||||||
|
lastMessage {
|
||||||
|
_id
|
||||||
|
id
|
||||||
|
content
|
||||||
|
senderId
|
||||||
|
username
|
||||||
|
avatar
|
||||||
|
date
|
||||||
|
saved
|
||||||
|
distributed
|
||||||
|
seen
|
||||||
|
}
|
||||||
users {
|
users {
|
||||||
_id
|
_id
|
||||||
id
|
id
|
||||||
|
|||||||
57
backend/src/middleware/chatMiddleware.ts
Normal file
57
backend/src/middleware/chatMiddleware.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { isArray } from 'lodash'
|
||||||
|
|
||||||
|
const setRoomProps = (room) => {
|
||||||
|
if (room.users) {
|
||||||
|
room.users.forEach((user) => {
|
||||||
|
user._id = user.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (room.lastMessage) {
|
||||||
|
room.lastMessage._id = room.lastMessage.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setMessageProps = (message, context) => {
|
||||||
|
message._id = message.id
|
||||||
|
if (message.senderId !== context.user.id) {
|
||||||
|
message.distributed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomProperties = async (resolve, root, args, context, info) => {
|
||||||
|
const resolved = await resolve(root, args, context, info)
|
||||||
|
if (resolved) {
|
||||||
|
if (isArray(resolved)) {
|
||||||
|
resolved.forEach((room) => {
|
||||||
|
setRoomProps(room)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setRoomProps(resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageProperties = async (resolve, root, args, context, info) => {
|
||||||
|
const resolved = await resolve(root, args, context, info)
|
||||||
|
if (resolved) {
|
||||||
|
if (isArray(resolved)) {
|
||||||
|
resolved.forEach((message) => {
|
||||||
|
setMessageProps(message, context)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setMessageProps(resolved, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Query: {
|
||||||
|
Room: roomProperties,
|
||||||
|
Message: messageProperties,
|
||||||
|
},
|
||||||
|
Mutation: {
|
||||||
|
CreateRoom: roomProperties,
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ import login from './login/loginMiddleware'
|
|||||||
import sentry from './sentryMiddleware'
|
import sentry from './sentryMiddleware'
|
||||||
import languages from './languages/languages'
|
import languages from './languages/languages'
|
||||||
import userInteractions from './userInteractions'
|
import userInteractions from './userInteractions'
|
||||||
|
import chatMiddleware from './chatMiddleware'
|
||||||
|
|
||||||
export default (schema) => {
|
export default (schema) => {
|
||||||
const middlewares = {
|
const middlewares = {
|
||||||
@ -31,6 +32,7 @@ export default (schema) => {
|
|||||||
orderBy,
|
orderBy,
|
||||||
languages,
|
languages,
|
||||||
userInteractions,
|
userInteractions,
|
||||||
|
chatMiddleware,
|
||||||
}
|
}
|
||||||
|
|
||||||
let order = [
|
let order = [
|
||||||
@ -49,6 +51,7 @@ export default (schema) => {
|
|||||||
'softDelete',
|
'softDelete',
|
||||||
'includedFields',
|
'includedFields',
|
||||||
'orderBy',
|
'orderBy',
|
||||||
|
'chatMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
// add permisions middleware at the first position (unless we're seeding)
|
// add permisions middleware at the first position (unless we're seeding)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { createTestClient } from 'apollo-server-testing'
|
import { createTestClient } from 'apollo-server-testing'
|
||||||
import Factory, { cleanDatabase } from '../../db/factories'
|
import Factory, { cleanDatabase } from '../../db/factories'
|
||||||
import { getNeode, getDriver } from '../../db/neo4j'
|
import { getNeode, getDriver } from '../../db/neo4j'
|
||||||
import { createRoomMutation } from '../../graphql/rooms'
|
import { createRoomMutation, roomQuery } from '../../graphql/rooms'
|
||||||
import { createMessageMutation, messageQuery, markMessagesAsSeen } from '../../graphql/messages'
|
import { createMessageMutation, messageQuery, markMessagesAsSeen } from '../../graphql/messages'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
@ -22,6 +22,9 @@ beforeAll(async () => {
|
|||||||
driver,
|
driver,
|
||||||
neode,
|
neode,
|
||||||
user: authenticatedUser,
|
user: authenticatedUser,
|
||||||
|
cypherParams: {
|
||||||
|
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -122,6 +125,10 @@ describe('Message', () => {
|
|||||||
CreateMessage: {
|
CreateMessage: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
content: 'Some nice message to other chatting user',
|
content: 'Some nice message to other chatting user',
|
||||||
|
senderId: 'chatting-user',
|
||||||
|
username: 'Chatting User',
|
||||||
|
avatar: expect.any(String),
|
||||||
|
date: expect.any(String),
|
||||||
saved: true,
|
saved: true,
|
||||||
distributed: false,
|
distributed: false,
|
||||||
seen: false,
|
seen: false,
|
||||||
@ -129,6 +136,64 @@ describe('Message', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('room is updated as well', () => {
|
||||||
|
it('has last message set', async () => {
|
||||||
|
const result = await query({ query: roomQuery() })
|
||||||
|
await expect(result).toMatchObject({
|
||||||
|
errors: undefined,
|
||||||
|
data: {
|
||||||
|
Room: [
|
||||||
|
expect.objectContaining({
|
||||||
|
lastMessageAt: expect.any(String),
|
||||||
|
unreadCount: 0,
|
||||||
|
lastMessage: expect.objectContaining({
|
||||||
|
_id: result.data.Room[0].lastMessage.id,
|
||||||
|
id: expect.any(String),
|
||||||
|
content: 'Some nice message to other chatting user',
|
||||||
|
senderId: 'chatting-user',
|
||||||
|
username: 'Chatting User',
|
||||||
|
avatar: expect.any(String),
|
||||||
|
date: expect.any(String),
|
||||||
|
saved: true,
|
||||||
|
distributed: false,
|
||||||
|
seen: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unread count for other user', () => {
|
||||||
|
it('has unread count = 1', async () => {
|
||||||
|
authenticatedUser = await otherChattingUser.toJson()
|
||||||
|
await expect(query({ query: roomQuery() })).resolves.toMatchObject({
|
||||||
|
errors: undefined,
|
||||||
|
data: {
|
||||||
|
Room: [
|
||||||
|
expect.objectContaining({
|
||||||
|
lastMessageAt: expect.any(String),
|
||||||
|
unreadCount: 1,
|
||||||
|
lastMessage: expect.objectContaining({
|
||||||
|
_id: expect.any(String),
|
||||||
|
id: expect.any(String),
|
||||||
|
content: 'Some nice message to other chatting user',
|
||||||
|
senderId: 'chatting-user',
|
||||||
|
username: 'Chatting User',
|
||||||
|
avatar: expect.any(String),
|
||||||
|
date: expect.any(String),
|
||||||
|
saved: true,
|
||||||
|
distributed: false,
|
||||||
|
seen: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('user does not chat in room', () => {
|
describe('user does not chat in room', () => {
|
||||||
|
|||||||
@ -43,12 +43,6 @@ export default {
|
|||||||
}
|
}
|
||||||
// send subscription to author to updated the messages
|
// send subscription to author to updated the messages
|
||||||
}
|
}
|
||||||
resolved.forEach((message) => {
|
|
||||||
message._id = message.id
|
|
||||||
if (message.senderId !== context.user.id) {
|
|
||||||
message.distributed = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return resolved.reverse()
|
return resolved.reverse()
|
||||||
},
|
},
|
||||||
@ -63,8 +57,9 @@ export default {
|
|||||||
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
const writeTxResultPromise = session.writeTransaction(async (transaction) => {
|
||||||
const createMessageCypher = `
|
const createMessageCypher = `
|
||||||
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
|
||||||
|
OPTIONAL MATCH (currentUser)-[:AVATAR_IMAGE]->(image:Image)
|
||||||
OPTIONAL MATCH (m:Message)-[:INSIDE]->(room)<-[:CHATS_IN]-(otherUser:User)
|
OPTIONAL MATCH (m:Message)-[:INSIDE]->(room)<-[:CHATS_IN]-(otherUser:User)
|
||||||
WITH MAX(m.indexId) as maxIndex, room, currentUser, otherUser
|
WITH MAX(m.indexId) as maxIndex, room, currentUser, image, otherUser
|
||||||
CREATE (currentUser)-[:CREATED]->(message:Message {
|
CREATE (currentUser)-[:CREATED]->(message:Message {
|
||||||
createdAt: toString(datetime()),
|
createdAt: toString(datetime()),
|
||||||
id: apoc.create.uuid(),
|
id: apoc.create.uuid(),
|
||||||
@ -74,7 +69,16 @@ export default {
|
|||||||
distributed: false,
|
distributed: false,
|
||||||
seen: false
|
seen: false
|
||||||
})-[:INSIDE]->(room)
|
})-[:INSIDE]->(room)
|
||||||
RETURN message { .*, room: properties(room), senderId: currentUser.id, otherUser: properties(otherUser) }
|
SET room.lastMessageAt = toString(datetime())
|
||||||
|
RETURN message {
|
||||||
|
.*,
|
||||||
|
room: properties(room)
|
||||||
|
otherUser: properties(otherUser)
|
||||||
|
senderId: currentUser.id,
|
||||||
|
username: currentUser.name,
|
||||||
|
avatar: image.url,
|
||||||
|
date: message.createdAt
|
||||||
|
}
|
||||||
`
|
`
|
||||||
const createMessageTxResponse = await transaction.run(createMessageCypher, {
|
const createMessageTxResponse = await transaction.run(createMessageCypher, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
|||||||
@ -22,6 +22,9 @@ beforeAll(async () => {
|
|||||||
driver,
|
driver,
|
||||||
neode,
|
neode,
|
||||||
user: authenticatedUser,
|
user: authenticatedUser,
|
||||||
|
cypherParams: {
|
||||||
|
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -131,6 +134,26 @@ describe('Room', () => {
|
|||||||
CreateRoom: {
|
CreateRoom: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
roomId: result.data.CreateRoom.id,
|
roomId: result.data.CreateRoom.id,
|
||||||
|
roomName: 'Other Chatting User',
|
||||||
|
unreadCount: 0,
|
||||||
|
users: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
_id: 'chatting-user',
|
||||||
|
id: 'chatting-user',
|
||||||
|
name: 'Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'other-chatting-user',
|
||||||
|
id: 'other-chatting-user',
|
||||||
|
name: 'Other Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -228,6 +251,7 @@ describe('Room', () => {
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
roomId: result.data.Room[0].id,
|
roomId: result.data.Room[0].id,
|
||||||
roomName: 'Chatting User',
|
roomName: 'Chatting User',
|
||||||
|
unreadCount: 0,
|
||||||
users: expect.arrayContaining([
|
users: expect.arrayContaining([
|
||||||
{
|
{
|
||||||
_id: 'chatting-user',
|
_id: 'chatting-user',
|
||||||
|
|||||||
@ -20,22 +20,7 @@ export default {
|
|||||||
params.filter.users_some = {
|
params.filter.users_some = {
|
||||||
id: context.user.id,
|
id: context.user.id,
|
||||||
}
|
}
|
||||||
const resolved = await neo4jgraphql(object, params, context, resolveInfo)
|
return neo4jgraphql(object, params, context, resolveInfo)
|
||||||
if (resolved) {
|
|
||||||
resolved.forEach((room) => {
|
|
||||||
if (room.users) {
|
|
||||||
// buggy, you must query the username for this to function correctly
|
|
||||||
room.roomName = room.users.filter((user) => user.id !== context.user.id)[0].name
|
|
||||||
room.avatar =
|
|
||||||
room.users.filter((user) => user.id !== context.user.id)[0].avatar?.url ||
|
|
||||||
'default-avatar'
|
|
||||||
room.users.forEach((user) => {
|
|
||||||
user._id = user.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return resolved
|
|
||||||
},
|
},
|
||||||
UnreadRooms: async (object, params, context, resolveInfo) => {
|
UnreadRooms: async (object, params, context, resolveInfo) => {
|
||||||
const {
|
const {
|
||||||
@ -77,7 +62,17 @@ export default {
|
|||||||
ON CREATE SET
|
ON CREATE SET
|
||||||
room.createdAt = toString(datetime()),
|
room.createdAt = toString(datetime()),
|
||||||
room.id = apoc.create.uuid()
|
room.id = apoc.create.uuid()
|
||||||
RETURN room { .* }
|
WITH room, user, currentUser
|
||||||
|
OPTIONAL MATCH (room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User)
|
||||||
|
WHERE NOT sender.id = $currentUserId AND NOT message.seen
|
||||||
|
WITH room, user, currentUser, message,
|
||||||
|
user.name AS roomName
|
||||||
|
RETURN room {
|
||||||
|
.*,
|
||||||
|
users: [properties(currentUser), properties(user)],
|
||||||
|
roomName: roomName,
|
||||||
|
unreadCount: toString(COUNT(DISTINCT message))
|
||||||
|
}
|
||||||
`
|
`
|
||||||
const createRommTxResponse = await transaction.run(createRoomCypher, {
|
const createRommTxResponse = await transaction.run(createRoomCypher, {
|
||||||
userId,
|
userId,
|
||||||
@ -101,6 +96,7 @@ export default {
|
|||||||
},
|
},
|
||||||
Room: {
|
Room: {
|
||||||
...Resolver('Room', {
|
...Resolver('Room', {
|
||||||
|
undefinedToNull: ['lastMessageAt'],
|
||||||
hasMany: {
|
hasMany: {
|
||||||
users: '<-[:CHATS_IN]-(related:User)',
|
users: '<-[:CHATS_IN]-(related:User)',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
# }
|
# }
|
||||||
|
|
||||||
enum _MessageOrdering {
|
enum _MessageOrdering {
|
||||||
createdAt_asc
|
indexId_desc
|
||||||
createdAt_desc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Message {
|
type Message {
|
||||||
|
|||||||
@ -18,8 +18,23 @@ type Room {
|
|||||||
users: [User]! @relation(name: "CHATS_IN", direction: "IN")
|
users: [User]! @relation(name: "CHATS_IN", direction: "IN")
|
||||||
|
|
||||||
roomId: String! @cypher(statement: "RETURN this.id")
|
roomId: String! @cypher(statement: "RETURN this.id")
|
||||||
roomName: String! ## @cypher(statement: "MATCH (this)<-[:CHATS_IN]-(user:User) WHERE NOT user.id = $cypherParams.currentUserId RETURN user[0].name")
|
roomName: String! @cypher(statement: "MATCH (this)<-[:CHATS_IN]-(user:User) WHERE NOT user.id = $cypherParams.currentUserId RETURN user.name")
|
||||||
avatar: String! ## @cypher match not own user in users array
|
avatar: String! @cypher(statement: "MATCH (this)<-[:CHATS_IN]-(user:User) WHERE NOT user.id = $cypherParams.currentUserId RETURN user.avatar.url")
|
||||||
|
|
||||||
|
lastMessageAt: String
|
||||||
|
|
||||||
|
lastMessage: Message @cypher(statement: """
|
||||||
|
MATCH (this)<-[:INSIDE]-(message:Message)
|
||||||
|
WITH message ORDER BY message.indexId DESC LIMIT 1
|
||||||
|
RETURN message
|
||||||
|
""")
|
||||||
|
|
||||||
|
unreadCount: Int @cypher(statement: """
|
||||||
|
MATCH (this)<-[:INSIDE]-(message:Message)<-[:CREATED]-(user:User)
|
||||||
|
WHERE NOT user.id = $cypherParams.currentUserId
|
||||||
|
AND NOT message.seen
|
||||||
|
RETURN count(message)
|
||||||
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
|||||||
@ -120,20 +120,6 @@ export default {
|
|||||||
text: 'This is the action',
|
text: 'This is the action',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
textMessages: {
|
|
||||||
ROOMS_EMPTY: this.$t('chat.roomsEmpty'),
|
|
||||||
ROOM_EMPTY: this.$t('chat.roomEmpty'),
|
|
||||||
NEW_MESSAGES: this.$t('chat.newMessages'),
|
|
||||||
MESSAGE_DELETED: this.$t('chat.messageDeleted'),
|
|
||||||
MESSAGES_EMPTY: this.$t('chat.messagesEmpty'),
|
|
||||||
CONVERSATION_STARTED: this.$t('chat.conversationStarted'),
|
|
||||||
TYPE_MESSAGE: this.$t('chat.typeMessage'),
|
|
||||||
SEARCH: this.$t('chat.search'),
|
|
||||||
IS_ONLINE: this.$t('chat.isOnline'),
|
|
||||||
LAST_SEEN: this.$t('chat.lastSeen'),
|
|
||||||
IS_TYPING: this.$t('chat.isTyping'),
|
|
||||||
CANCEL_SELECT_MESSAGE: this.$t('chat.cancelSelectMessage'),
|
|
||||||
},
|
|
||||||
roomActions: [
|
roomActions: [
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
@ -190,6 +176,22 @@ export default {
|
|||||||
computedChatStyle() {
|
computedChatStyle() {
|
||||||
return chatStyle.STYLE.light
|
return chatStyle.STYLE.light
|
||||||
},
|
},
|
||||||
|
textMessages() {
|
||||||
|
return {
|
||||||
|
ROOMS_EMPTY: this.$t('chat.roomsEmpty'),
|
||||||
|
ROOM_EMPTY: this.$t('chat.roomEmpty'),
|
||||||
|
NEW_MESSAGES: this.$t('chat.newMessages'),
|
||||||
|
MESSAGE_DELETED: this.$t('chat.messageDeleted'),
|
||||||
|
MESSAGES_EMPTY: this.$t('chat.messagesEmpty'),
|
||||||
|
CONVERSATION_STARTED: this.$t('chat.conversationStarted'),
|
||||||
|
TYPE_MESSAGE: this.$t('chat.typeMessage'),
|
||||||
|
SEARCH: this.$t('chat.search'),
|
||||||
|
IS_ONLINE: this.$t('chat.isOnline'),
|
||||||
|
LAST_SEEN: this.$t('chat.lastSeen'),
|
||||||
|
IS_TYPING: this.$t('chat.isTyping'),
|
||||||
|
CANCEL_SELECT_MESSAGE: this.$t('chat.cancelSelectMessage'),
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchRooms({ room } = {}) {
|
async fetchRooms({ room } = {}) {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import gql from 'graphql-tag'
|
|||||||
export const messageQuery = () => {
|
export const messageQuery = () => {
|
||||||
return gql`
|
return gql`
|
||||||
query ($roomId: ID!, $first: Int, $offset: Int) {
|
query ($roomId: ID!, $first: Int, $offset: Int) {
|
||||||
Message(roomId: $roomId, first: $first, offset: $offset, orderBy: createdAt_desc) {
|
Message(roomId: $roomId, first: $first, offset: $offset, orderBy: indexId_desc) {
|
||||||
_id
|
_id
|
||||||
id
|
id
|
||||||
indexId
|
indexId
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user