mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' into 6557-chat-reactive-language-switch
This commit is contained in:
commit
28596b2db3
107
.github/workflows/test-e2e.yml
vendored
107
.github/workflows/test-e2e.yml
vendored
@ -2,8 +2,58 @@ name: ocelot.social end-to-end test CI
|
|||||||
on: push
|
on: push
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
docker_preparation:
|
||||||
|
name: Fullstack test preparation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
pr-number: ${{ steps.pr.outputs.number }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Copy env files
|
||||||
|
run: |
|
||||||
|
cp webapp/.env.template webapp/.env
|
||||||
|
cp backend/.env.template backend/.env
|
||||||
|
|
||||||
|
- name: Build docker images
|
||||||
|
run: |
|
||||||
|
mkdir /tmp/images
|
||||||
|
docker build --target community -t "ocelotsocialnetwork/neo4j-community:test" neo4j/
|
||||||
|
docker save "ocelotsocialnetwork/neo4j-community:test" > /tmp/images/neo4j.tar
|
||||||
|
docker build --target test -t "ocelotsocialnetwork/backend:test" backend/
|
||||||
|
docker save "ocelotsocialnetwork/backend:test" > /tmp/images/backend.tar
|
||||||
|
docker build --target test -t "ocelotsocialnetwork/webapp:test" webapp/
|
||||||
|
docker save "ocelotsocialnetwork/webapp:test" > /tmp/images/webapp.tar
|
||||||
|
|
||||||
|
- name: Install cypress requirements
|
||||||
|
run: |
|
||||||
|
wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386"
|
||||||
|
cd backend
|
||||||
|
yarn install
|
||||||
|
yarn build
|
||||||
|
cd ..
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
- name: Get pr number
|
||||||
|
id: pr
|
||||||
|
uses: 8BitJonny/gh-get-current-pr@2.2.0
|
||||||
|
|
||||||
|
- name: Cache docker images
|
||||||
|
id: cache
|
||||||
|
uses: actions/cache/save@v3.3.1
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
/opt/cucumber-json-formatter
|
||||||
|
/home/runner/.cache/Cypress
|
||||||
|
/home/runner/work/Ocelot-Social/Ocelot-Social
|
||||||
|
/tmp/images/
|
||||||
|
key: e2e-preparation-cache-pr${{ steps.pr.outputs.number }}
|
||||||
|
|
||||||
fullstack_tests:
|
fullstack_tests:
|
||||||
name: Fullstack tests
|
name: Fullstack tests
|
||||||
|
if: success()
|
||||||
|
needs: docker_preparation
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
jobs: 8
|
jobs: 8
|
||||||
@ -12,28 +62,27 @@ jobs:
|
|||||||
# run copies of the current job in parallel
|
# run copies of the current job in parallel
|
||||||
job: [1, 2, 3, 4, 5, 6, 7, 8]
|
job: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Restore cache
|
||||||
uses: actions/checkout@v3
|
uses: actions/cache/restore@v3.3.1
|
||||||
|
id: cache
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
/opt/cucumber-json-formatter
|
||||||
|
/home/runner/.cache/Cypress
|
||||||
|
/home/runner/work/Ocelot-Social/Ocelot-Social
|
||||||
|
/tmp/images/
|
||||||
|
key: e2e-preparation-cache-pr${{ needs.docker_preparation.outputs.pr-number }}
|
||||||
|
fail-on-cache-miss: true
|
||||||
|
|
||||||
- name: webapp | copy env file
|
- name: Boot up test system | docker-compose
|
||||||
run: cp webapp/.env.template webapp/.env
|
|
||||||
|
|
||||||
- name: backend | copy env file
|
|
||||||
run: cp backend/.env.template backend/.env
|
|
||||||
|
|
||||||
- name: boot up test system | docker-compose
|
|
||||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend
|
|
||||||
|
|
||||||
- name: Full stack tests | prepare
|
|
||||||
run: |
|
run: |
|
||||||
wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386"
|
|
||||||
chmod +x /opt/cucumber-json-formatter
|
chmod +x /opt/cucumber-json-formatter
|
||||||
sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter
|
sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter
|
||||||
cd backend
|
docker load < /tmp/images/neo4j.tar
|
||||||
yarn install
|
docker load < /tmp/images/backend.tar
|
||||||
yarn build
|
docker load < /tmp/images/webapp.tar
|
||||||
cd ..
|
docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend
|
||||||
yarn install
|
sleep 90s
|
||||||
|
|
||||||
- name: Full stack tests | run tests
|
- name: Full stack tests | run tests
|
||||||
id: e2e-tests
|
id: e2e-tests
|
||||||
@ -45,16 +94,24 @@ jobs:
|
|||||||
cd cypress/
|
cd cypress/
|
||||||
node create-cucumber-html-report.js
|
node create-cucumber-html-report.js
|
||||||
|
|
||||||
- name: End-to-end tests | if tests failed, get pr number
|
- name: Full stack tests | if tests failed, upload report
|
||||||
id: pr
|
|
||||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
|
||||||
uses: 8BitJonny/gh-get-current-pr@2.2.0
|
|
||||||
|
|
||||||
- name: End-to-end tests | if tests failed, upload report
|
|
||||||
id: e2e-report
|
id: e2e-report
|
||||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ocelot-e2e-test-report-pr${{ steps.pr.outputs.number }}
|
name: ocelot-e2e-test-report-pr${{ needs.docker_preparation.outputs.pr-number }}
|
||||||
path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report
|
path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
name: Cleanup
|
||||||
|
if: always()
|
||||||
|
needs: [docker_preparation, fullstack_tests]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Delete cache
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
gh extension install actions/gh-actions-cache
|
||||||
|
KEY="e2e-preparation-cache-pr${{ needs.docker_preparation.outputs.pr-number }}"
|
||||||
|
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm
|
||||||
@ -13,8 +13,8 @@ export const createRoomMutation = () => {
|
|||||||
|
|
||||||
export const roomQuery = () => {
|
export const roomQuery = () => {
|
||||||
return gql`
|
return gql`
|
||||||
query {
|
query Room($first: Int, $offset: Int, $id: ID) {
|
||||||
Room {
|
Room(first: $first, offset: $offset, id: $id, orderBy: createdAt_desc) {
|
||||||
id
|
id
|
||||||
roomId
|
roomId
|
||||||
roomName
|
roomName
|
||||||
@ -30,3 +30,11 @@ export const roomQuery = () => {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const unreadRoomsQuery = () => {
|
||||||
|
return gql`
|
||||||
|
query {
|
||||||
|
UnreadRooms
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|||||||
@ -408,6 +408,7 @@ export default shield(
|
|||||||
getInviteCode: isAuthenticated, // and inviteRegistration
|
getInviteCode: isAuthenticated, // and inviteRegistration
|
||||||
Room: isAuthenticated,
|
Room: isAuthenticated,
|
||||||
Message: isAuthenticated,
|
Message: isAuthenticated,
|
||||||
|
UnreadRooms: isAuthenticated,
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
'*': deny,
|
'*': deny,
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
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, roomQuery } from '../../graphql/rooms'
|
import { createRoomMutation, roomQuery, unreadRoomsQuery } from '../../graphql/rooms'
|
||||||
|
import { createMessageMutation } from '../../graphql/messages'
|
||||||
import createServer from '../../server'
|
import createServer from '../../server'
|
||||||
|
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
@ -34,6 +35,8 @@ afterAll(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Room', () => {
|
describe('Room', () => {
|
||||||
|
let roomId: string
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
;[chattingUser, otherChattingUser, notChattingUser] = await Promise.all([
|
;[chattingUser, otherChattingUser, notChattingUser] = await Promise.all([
|
||||||
Factory.build('user', {
|
Factory.build('user', {
|
||||||
@ -48,6 +51,14 @@ describe('Room', () => {
|
|||||||
id: 'not-chatting-user',
|
id: 'not-chatting-user',
|
||||||
name: 'Not Chatting User',
|
name: 'Not Chatting User',
|
||||||
}),
|
}),
|
||||||
|
Factory.build('user', {
|
||||||
|
id: 'second-chatting-user',
|
||||||
|
name: 'Second Chatting User',
|
||||||
|
}),
|
||||||
|
Factory.build('user', {
|
||||||
|
id: 'third-chatting-user',
|
||||||
|
name: 'Third Chatting User',
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -68,8 +79,6 @@ describe('Room', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('authenticated', () => {
|
describe('authenticated', () => {
|
||||||
let roomId: string
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
authenticatedUser = await chattingUser.toJson()
|
authenticatedUser = await chattingUser.toJson()
|
||||||
})
|
})
|
||||||
@ -260,4 +269,312 @@ describe('Room', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('unread rooms query', () => {
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('throws authorization error', async () => {
|
||||||
|
authenticatedUser = null
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: unreadRoomsQuery(),
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
errors: [{ message: 'Not Authorized!' }],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
let otherRoomId: string
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
authenticatedUser = await chattingUser.toJson()
|
||||||
|
const result = await mutate({
|
||||||
|
mutation: createRoomMutation(),
|
||||||
|
variables: {
|
||||||
|
userId: 'not-chatting-user',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
otherRoomId = result.data.CreateRoom.roomId
|
||||||
|
await mutate({
|
||||||
|
mutation: createMessageMutation(),
|
||||||
|
variables: {
|
||||||
|
roomId: otherRoomId,
|
||||||
|
content: 'Message to not chatting user',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await mutate({
|
||||||
|
mutation: createMessageMutation(),
|
||||||
|
variables: {
|
||||||
|
roomId,
|
||||||
|
content: '1st message to other chatting user',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await mutate({
|
||||||
|
mutation: createMessageMutation(),
|
||||||
|
variables: {
|
||||||
|
roomId,
|
||||||
|
content: '2nd message to other chatting user',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
authenticatedUser = await otherChattingUser.toJson()
|
||||||
|
const result2 = await mutate({
|
||||||
|
mutation: createRoomMutation(),
|
||||||
|
variables: {
|
||||||
|
userId: 'not-chatting-user',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
otherRoomId = result2.data.CreateRoom.roomId
|
||||||
|
await mutate({
|
||||||
|
mutation: createMessageMutation(),
|
||||||
|
variables: {
|
||||||
|
roomId: otherRoomId,
|
||||||
|
content: 'Other message to not chatting user',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('as chatting user', () => {
|
||||||
|
it('has 0 unread rooms', async () => {
|
||||||
|
authenticatedUser = await chattingUser.toJson()
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: unreadRoomsQuery(),
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
UnreadRooms: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('as other chatting user', () => {
|
||||||
|
it('has 1 unread rooms', async () => {
|
||||||
|
authenticatedUser = await otherChattingUser.toJson()
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: unreadRoomsQuery(),
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
UnreadRooms: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('as not chatting user', () => {
|
||||||
|
it('has 2 unread rooms', async () => {
|
||||||
|
authenticatedUser = await notChattingUser.toJson()
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: unreadRoomsQuery(),
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
UnreadRooms: 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('query several rooms', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
authenticatedUser = await chattingUser.toJson()
|
||||||
|
await mutate({
|
||||||
|
mutation: createRoomMutation(),
|
||||||
|
variables: {
|
||||||
|
userId: 'second-chatting-user',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await mutate({
|
||||||
|
mutation: createRoomMutation(),
|
||||||
|
variables: {
|
||||||
|
userId: 'third-chatting-user',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the rooms paginated', async () => {
|
||||||
|
expect(await query({ query: roomQuery(), variables: { first: 3, offset: 0 } })).toMatchObject(
|
||||||
|
{
|
||||||
|
errors: undefined,
|
||||||
|
data: {
|
||||||
|
Room: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
roomId: expect.any(String),
|
||||||
|
roomName: 'Third Chatting User',
|
||||||
|
users: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
_id: 'chatting-user',
|
||||||
|
id: 'chatting-user',
|
||||||
|
name: 'Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'third-chatting-user',
|
||||||
|
id: 'third-chatting-user',
|
||||||
|
name: 'Third Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
roomId: expect.any(String),
|
||||||
|
roomName: 'Second Chatting User',
|
||||||
|
users: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
_id: 'chatting-user',
|
||||||
|
id: 'chatting-user',
|
||||||
|
name: 'Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'second-chatting-user',
|
||||||
|
id: 'second-chatting-user',
|
||||||
|
name: 'Second Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
roomId: expect.any(String),
|
||||||
|
roomName: 'Not Chatting User',
|
||||||
|
users: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
_id: 'chatting-user',
|
||||||
|
id: 'chatting-user',
|
||||||
|
name: 'Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'not-chatting-user',
|
||||||
|
id: 'not-chatting-user',
|
||||||
|
name: 'Not Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(await query({ query: roomQuery(), variables: { first: 3, offset: 3 } })).toMatchObject(
|
||||||
|
{
|
||||||
|
errors: undefined,
|
||||||
|
data: {
|
||||||
|
Room: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
roomId: expect.any(String),
|
||||||
|
roomName: 'Other Chatting User',
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('query single room', () => {
|
||||||
|
let result: any = null
|
||||||
|
beforeAll(async () => {
|
||||||
|
authenticatedUser = await chattingUser.toJson()
|
||||||
|
result = await query({ query: roomQuery() })
|
||||||
|
})
|
||||||
|
describe('as chatter of room', () => {
|
||||||
|
it('returns the room', async () => {
|
||||||
|
expect(
|
||||||
|
await query({
|
||||||
|
query: roomQuery(),
|
||||||
|
variables: { first: 2, offset: 0, id: result.data.Room[0].id },
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
errors: undefined,
|
||||||
|
data: {
|
||||||
|
Room: [
|
||||||
|
{
|
||||||
|
id: expect.any(String),
|
||||||
|
roomId: expect.any(String),
|
||||||
|
roomName: 'Third Chatting User',
|
||||||
|
users: expect.arrayContaining([
|
||||||
|
{
|
||||||
|
_id: 'chatting-user',
|
||||||
|
id: 'chatting-user',
|
||||||
|
name: 'Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: 'third-chatting-user',
|
||||||
|
id: 'third-chatting-user',
|
||||||
|
name: 'Third Chatting User',
|
||||||
|
avatar: {
|
||||||
|
url: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('as not chatter of room', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
authenticatedUser = await notChattingUser.toJson()
|
||||||
|
})
|
||||||
|
it('returns no room', async () => {
|
||||||
|
authenticatedUser = await notChattingUser.toJson()
|
||||||
|
expect(
|
||||||
|
await query({
|
||||||
|
query: roomQuery(),
|
||||||
|
variables: { first: 2, offset: 0, id: result.data.Room[0].id },
|
||||||
|
}),
|
||||||
|
).toMatchObject({
|
||||||
|
errors: undefined,
|
||||||
|
data: {
|
||||||
|
Room: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -25,6 +25,27 @@ export default {
|
|||||||
}
|
}
|
||||||
return resolved
|
return resolved
|
||||||
},
|
},
|
||||||
|
UnreadRooms: async (object, params, context, resolveInfo) => {
|
||||||
|
const {
|
||||||
|
user: { id: currentUserId },
|
||||||
|
} = context
|
||||||
|
const session = context.driver.session()
|
||||||
|
const readTxResultPromise = session.readTransaction(async (transaction) => {
|
||||||
|
const unreadRoomsCypher = `
|
||||||
|
MATCH (:User { id: $currentUserId })-[:CHATS_IN]->(room:Room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User)
|
||||||
|
WHERE NOT sender.id = $currentUserId AND NOT message.seen
|
||||||
|
RETURN toString(COUNT(DISTINCT room)) AS count
|
||||||
|
`
|
||||||
|
const unreadRoomsTxResponse = await transaction.run(unreadRoomsCypher, { currentUserId })
|
||||||
|
return unreadRoomsTxResponse.records.map((record) => record.get('count'))[0]
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const count = await readTxResultPromise
|
||||||
|
return count
|
||||||
|
} finally {
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreateRoom: async (_parent, params, context, _resolveInfo) => {
|
CreateRoom: async (_parent, params, context, _resolveInfo) => {
|
||||||
|
|||||||
@ -5,6 +5,11 @@
|
|||||||
# users_some: _UserFilter
|
# users_some: _UserFilter
|
||||||
# }
|
# }
|
||||||
|
|
||||||
|
# TODO change this to last message date
|
||||||
|
enum _RoomOrdering {
|
||||||
|
createdAt_desc
|
||||||
|
}
|
||||||
|
|
||||||
type Room {
|
type Room {
|
||||||
id: ID!
|
id: ID!
|
||||||
createdAt: String
|
createdAt: String
|
||||||
@ -24,5 +29,9 @@ type Mutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
Room: [Room]
|
Room(
|
||||||
|
id: ID
|
||||||
|
orderBy: [_RoomOrdering]
|
||||||
|
): [Room]
|
||||||
|
UnreadRooms: Int
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,13 +13,15 @@
|
|||||||
:messages-loaded="messagesLoaded"
|
:messages-loaded="messagesLoaded"
|
||||||
:rooms="JSON.stringify(rooms)"
|
:rooms="JSON.stringify(rooms)"
|
||||||
:room-actions="JSON.stringify(roomActions)"
|
:room-actions="JSON.stringify(roomActions)"
|
||||||
:rooms-loaded="true"
|
:rooms-loaded="roomsLoaded"
|
||||||
|
:loading-rooms="loadingRooms"
|
||||||
show-files="false"
|
show-files="false"
|
||||||
show-audio="false"
|
show-audio="false"
|
||||||
:styles="JSON.stringify(computedChatStyle)"
|
:styles="JSON.stringify(computedChatStyle)"
|
||||||
:show-footer="true"
|
:show-footer="true"
|
||||||
@send-message="sendMessage($event.detail[0])"
|
@send-message="sendMessage($event.detail[0])"
|
||||||
@fetch-messages="fetchMessages($event.detail[0])"
|
@fetch-messages="fetchMessages($event.detail[0])"
|
||||||
|
@fetch-more-rooms="fetchRooms"
|
||||||
:responsive-breakpoint="responsiveBreakpoint"
|
:responsive-breakpoint="responsiveBreakpoint"
|
||||||
:single-room="singleRoom"
|
:single-room="singleRoom"
|
||||||
show-reaction-emojis="false"
|
show-reaction-emojis="false"
|
||||||
@ -129,17 +131,20 @@ export default {
|
|||||||
{ name: 'deleteRoom', title: 'Delete Room' },
|
{ name: 'deleteRoom', title: 'Delete Room' },
|
||||||
*/
|
*/
|
||||||
],
|
],
|
||||||
rooms: [],
|
|
||||||
messages: [],
|
|
||||||
messagesLoaded: true,
|
|
||||||
showDemoOptions: true,
|
showDemoOptions: true,
|
||||||
responsiveBreakpoint: 600,
|
responsiveBreakpoint: 600,
|
||||||
|
rooms: [],
|
||||||
|
roomsLoaded: false,
|
||||||
|
roomPage: 0,
|
||||||
|
roomPageSize: 10, // TODO pagination is a problem with single rooms - cant use
|
||||||
singleRoom: !!this.singleRoomId || false,
|
singleRoom: !!this.singleRoomId || false,
|
||||||
|
selectedRoom: null,
|
||||||
|
loadingRooms: true,
|
||||||
|
messagesLoaded: false,
|
||||||
messagePage: 0,
|
messagePage: 0,
|
||||||
messagePageSize: 20,
|
messagePageSize: 20,
|
||||||
roomPage: 0,
|
messages: [],
|
||||||
roomPageSize: 999, // TODO pagination is a problem with single rooms - cant use
|
|
||||||
selectedRoom: null,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -151,8 +156,8 @@ export default {
|
|||||||
userId: this.singleRoomId,
|
userId: this.singleRoomId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(({ data: { CreateRoom } }) => {
|
||||||
this.$apollo.queries.Rooms.refetch()
|
this.fetchRooms({ room: CreateRoom })
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toast.error(error)
|
this.$toast.error(error)
|
||||||
@ -160,6 +165,8 @@ export default {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
// this.loading = false
|
// this.loading = false
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
this.fetchRooms()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -167,8 +174,6 @@ export default {
|
|||||||
currentUser: 'auth/user',
|
currentUser: 'auth/user',
|
||||||
}),
|
}),
|
||||||
computedChatStyle() {
|
computedChatStyle() {
|
||||||
// TODO light/dark theme still needed?
|
|
||||||
// return this.theme === 'light' ? chatStyle.STYLE.light : chatStyle.STYLE.dark
|
|
||||||
return chatStyle.STYLE.light
|
return chatStyle.STYLE.light
|
||||||
},
|
},
|
||||||
textMessages() {
|
textMessages() {
|
||||||
@ -189,6 +194,45 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async fetchRooms({ room } = {}) {
|
||||||
|
this.roomsLoaded = false
|
||||||
|
const offset = this.roomPage * this.roomPageSize
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { Room },
|
||||||
|
} = await this.$apollo.query({
|
||||||
|
query: roomQuery(),
|
||||||
|
variables: {
|
||||||
|
id: room?.id,
|
||||||
|
first: this.roomPageSize,
|
||||||
|
offset,
|
||||||
|
},
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
|
})
|
||||||
|
|
||||||
|
const newRooms = Room.map((r) => {
|
||||||
|
return {
|
||||||
|
...r,
|
||||||
|
users: r.users.map((u) => {
|
||||||
|
return { ...u, username: u.name, avatar: u.avatar?.url }
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.rooms = [...this.rooms, ...newRooms]
|
||||||
|
|
||||||
|
if (Room.length < this.roomPageSize) {
|
||||||
|
this.roomsLoaded = true
|
||||||
|
}
|
||||||
|
this.roomPage += 1
|
||||||
|
} catch (error) {
|
||||||
|
this.rooms = []
|
||||||
|
this.$toast.error(error.message)
|
||||||
|
}
|
||||||
|
// must be set false after initial rooms are loaded and never changed again
|
||||||
|
this.loadingRooms = false
|
||||||
|
},
|
||||||
|
|
||||||
async fetchMessages({ room, options = {} }) {
|
async fetchMessages({ room, options = {} }) {
|
||||||
if (this.selectedRoom?.id !== room.id) {
|
if (this.selectedRoom?.id !== room.id) {
|
||||||
this.messages = []
|
this.messages = []
|
||||||
@ -212,6 +256,7 @@ export default {
|
|||||||
|
|
||||||
const msgs = []
|
const msgs = []
|
||||||
;[...this.messages, ...Message].forEach((m) => {
|
;[...this.messages, ...Message].forEach((m) => {
|
||||||
|
m.date = new Date(m.date).toDateString()
|
||||||
msgs[m.indexId] = m
|
msgs[m.indexId] = m
|
||||||
})
|
})
|
||||||
this.messages = msgs.filter(Boolean)
|
this.messages = msgs.filter(Boolean)
|
||||||
@ -226,13 +271,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
refetchMessage(roomId) {
|
|
||||||
this.fetchMessages({
|
|
||||||
room: this.rooms.find((r) => r.roomId === roomId),
|
|
||||||
options: { refetch: true },
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
async sendMessage(message) {
|
async sendMessage(message) {
|
||||||
try {
|
try {
|
||||||
await this.$apollo.mutate({
|
await this.$apollo.mutate({
|
||||||
@ -245,7 +283,10 @@ export default {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$toast.error(error.message)
|
this.$toast.error(error.message)
|
||||||
}
|
}
|
||||||
this.refetchMessage(message.roomId)
|
this.fetchMessages({
|
||||||
|
room: this.rooms.find((r) => r.roomId === message.roomId),
|
||||||
|
options: { refetch: true },
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialsName(fullname) {
|
getInitialsName(fullname) {
|
||||||
@ -253,45 +294,6 @@ export default {
|
|||||||
return fullname.match(/\b\w/g).join('').substring(0, 3).toUpperCase()
|
return fullname.match(/\b\w/g).join('').substring(0, 3).toUpperCase()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
apollo: {
|
|
||||||
Rooms: {
|
|
||||||
query() {
|
|
||||||
return roomQuery()
|
|
||||||
},
|
|
||||||
variables() {
|
|
||||||
return {
|
|
||||||
first: this.roomPageSize,
|
|
||||||
offset: this.roomPage * this.roomPageSize,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update({ Room }) {
|
|
||||||
if (!Room) {
|
|
||||||
this.rooms = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backend result needs mapping of the following values
|
|
||||||
// room[i].users[j].name -> room[i].users[j].username
|
|
||||||
// room[i].users[j].avatar.url -> room[i].users[j].avatar
|
|
||||||
// also filter rooms for the single room
|
|
||||||
this.rooms = Room.map((r) => {
|
|
||||||
return {
|
|
||||||
...r,
|
|
||||||
users: r.users.map((u) => {
|
|
||||||
return { ...u, username: u.name, avatar: u.avatar?.url }
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}).filter((r) =>
|
|
||||||
this.singleRoom ? r.users.filter((u) => u.id === this.singleRoomId).length > 0 : true,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
error(error) {
|
|
||||||
this.rooms = []
|
|
||||||
this.$toast.error(error.message)
|
|
||||||
},
|
|
||||||
fetchPolicy: 'no-cache',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@ -8,18 +8,34 @@
|
|||||||
placement: 'bottom-start',
|
placement: 'bottom-start',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<counter-icon icon="chat-bubble" :count="1" danger />
|
<counter-icon icon="chat-bubble" :count="count" danger />
|
||||||
</base-button>
|
</base-button>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CounterIcon from '~/components/_new/generic/CounterIcon/CounterIcon'
|
import CounterIcon from '~/components/_new/generic/CounterIcon/CounterIcon'
|
||||||
|
import { unreadRoomsQuery } from '~/graphql/Rooms'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChatNotificationMenu',
|
name: 'ChatNotificationMenu',
|
||||||
components: {
|
components: {
|
||||||
CounterIcon,
|
CounterIcon,
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
UnreadRooms: {
|
||||||
|
query() {
|
||||||
|
return unreadRoomsQuery()
|
||||||
|
},
|
||||||
|
update({ UnreadRooms }) {
|
||||||
|
this.count = UnreadRooms
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -7,11 +7,17 @@ export const messageQuery = () => {
|
|||||||
_id
|
_id
|
||||||
id
|
id
|
||||||
indexId
|
indexId
|
||||||
senderId
|
|
||||||
content
|
content
|
||||||
|
senderId
|
||||||
author {
|
author {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
username
|
||||||
|
avatar
|
||||||
|
date
|
||||||
|
saved
|
||||||
|
distributed
|
||||||
|
seen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
|
|
||||||
export const roomQuery = () => gql`
|
export const roomQuery = () => gql`
|
||||||
query Room($first: Int, $offset: Int) {
|
query Room($first: Int, $offset: Int, $id: ID) {
|
||||||
Room(first: $first, offset: $offset) {
|
Room(first: $first, offset: $offset, id: $id, orderBy: createdAt_desc) {
|
||||||
id
|
id
|
||||||
roomId
|
roomId
|
||||||
roomName
|
roomName
|
||||||
@ -27,3 +27,11 @@ export const createRoom = () => gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const unreadRoomsQuery = () => {
|
||||||
|
return gql`
|
||||||
|
query {
|
||||||
|
UnreadRooms
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user