refactor(backend): refactor context (#8434)

* type for neo4j and neode

* fix build

* remove flakyness

* wait for neode to install schema

* remove flakyness

* explain why we wait for a non-promise

* refactor context

missing change

missing change

* adjust test setup

proper cleanup after test

* lint fixes

* fix failing test to use new context
This commit is contained in:
Ulf Gebhardt 2025-05-03 11:43:08 +02:00 committed by GitHub
parent 913cc2f06d
commit c69cef47a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 359 additions and 331 deletions

View File

@ -0,0 +1,3 @@
export const NOTIFICATION_ADDED = 'NOTIFICATION_ADDED'
export const CHAT_MESSAGE_ADDED = 'CHAT_MESSAGE_ADDED'
export const ROOM_COUNT_UPDATED = 'ROOM_COUNT_UPDATED'

View File

@ -0,0 +1,49 @@
import { getDriver, getNeode } from '@db/neo4j'
import type { Driver } from 'neo4j-driver'
export const query =
(driver: Driver) =>
async ({ query, variables = {} }: { driver; query: string; variables: object }) => {
const session = driver.session()
const result = session.readTransaction(async (transaction) => {
const response = await transaction.run(query, variables)
return response
})
try {
return await result
} finally {
await session.close()
}
}
export const mutate =
(driver: Driver) =>
async ({ query, variables = {} }: { driver; query: string; variables: object }) => {
const session = driver.session()
const result = session.writeTransaction(async (transaction) => {
const response = await transaction.run(query, variables)
return response
})
try {
return await result
} finally {
await session.close()
}
}
export default () => {
const driver = getDriver()
const neode = getNeode()
return {
driver,
neode,
query: query(driver),
mutate: mutate(driver),
}
}

View File

@ -0,0 +1,25 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { RedisPubSub } from 'graphql-redis-subscriptions'
import { PubSub } from 'graphql-subscriptions'
import Redis from 'ioredis'
import CONFIG from '@config/index'
export default () => {
if (!CONFIG.REDIS_DOMAIN || CONFIG.REDIS_PORT || CONFIG.REDIS_PASSWORD) {
return new PubSub()
}
const options = {
host: CONFIG.REDIS_DOMAIN,
port: CONFIG.REDIS_PORT,
password: CONFIG.REDIS_PASSWORD,
retryStrategy: (times) => {
return Math.min(times * 50, 2000)
},
}
return new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options),
})
}

View File

@ -2,40 +2,41 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
const driver = getDriver()
const neode = getNeode()
const database = databaseContext()
let variables, mutate, authenticatedUser, commentAuthor, newlyCreatedComment
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
user: authenticatedUser,
}
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
mutate = createTestClient(server).mutate
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
beforeEach(async () => {
variables = {}
await neode.create('Category', {
await database.neode.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
@ -103,7 +104,7 @@ describe('CreateComment', () => {
describe('authenticated', () => {
beforeEach(async () => {
const user = await neode.create('User', { name: 'Author' })
const user = await database.neode.create('User', { name: 'Author' })
authenticatedUser = await user.toJson()
})

View File

@ -1,7 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { getNeode } from '@db/neo4j'

View File

@ -6,8 +6,8 @@
import { createTestClient } from 'apollo-server-testing'
import CONFIG from '@config/index'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import { groupMembersQuery } from '@graphql/queries/groupMembersQuery'
@ -16,10 +16,7 @@ import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation'
import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation'
import { updateGroupMutation } from '@graphql/queries/updateGroupMutation'
import createServer from '@src/server'
const driver = getDriver()
const neode = getNeode()
import createServer, { getContext } from '@src/server'
let authenticatedUser
let user
@ -35,15 +32,12 @@ const descriptionAdditional100 =
' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789'
let variables = {}
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
}
},
})
const database = databaseContext()
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
const { server } = createServer({ context })
const { mutate, query } = createTestClient(server)
const seedBasicsAndClearAuthentication = async () => {
@ -60,25 +54,25 @@ const seedBasicsAndClearAuthentication = async () => {
},
)
await Promise.all([
neode.create('Category', {
database.neode.create('Category', {
id: 'cat4',
name: 'Environment & Nature',
slug: 'environment-nature',
icon: 'tree',
}),
neode.create('Category', {
database.neode.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
slug: 'democracy-politics',
icon: 'university',
}),
neode.create('Category', {
database.neode.create('Category', {
id: 'cat15',
name: 'Consumption & Sustainability',
slug: 'consumption-sustainability',
icon: 'shopping-cart',
}),
neode.create('Category', {
database.neode.create('Category', {
id: 'cat27',
name: 'Animal Protection',
slug: 'animal-protection',
@ -241,7 +235,9 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('in mode', () => {

View File

@ -3,49 +3,47 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import databaseContext from '@context/database'
import pubsubContext from '@context/pubsub'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { createMessageMutation } from '@graphql/queries/createMessageMutation'
import { createRoomMutation } from '@graphql/queries/createRoomMutation'
import { markMessagesAsSeen } from '@graphql/queries/markMessagesAsSeen'
import { messageQuery } from '@graphql/queries/messageQuery'
import { roomQuery } from '@graphql/queries/roomQuery'
import createServer, { pubsub } from '@src/server'
const driver = getDriver()
const neode = getNeode()
import createServer, { getContext } from '@src/server'
let query
let mutate
let authenticatedUser
let chattingUser, otherChattingUser, notChattingUser
const database = databaseContext()
const pubsub = pubsubContext()
const pubsubSpy = jest.spyOn(pubsub, 'publish')
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database, pubsub })
server = createServer({ context }).server
query = createTestClient(server).query
mutate = createTestClient(server).mutate
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('Message', () => {

View File

@ -7,7 +7,7 @@
import { withFilter } from 'graphql-subscriptions'
import { neo4jgraphql } from 'neo4j-graphql-js'
import { pubsub, CHAT_MESSAGE_ADDED } from '@src/server'
import { CHAT_MESSAGE_ADDED } from '@constants/subscriptions'
import Resolver from './helpers/Resolver'
@ -30,7 +30,7 @@ export default {
Subscription: {
chatMessageAdded: {
subscribe: withFilter(
() => pubsub.asyncIterator(CHAT_MESSAGE_ADDED),
(_, __, context) => context.pubsub.asyncIterator(CHAT_MESSAGE_ADDED),
(payload, variables, context) => {
return payload.userId === context.user?.id
},

View File

@ -6,13 +6,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { withFilter } from 'graphql-subscriptions'
import { pubsub, NOTIFICATION_ADDED } from '@src/server'
import { NOTIFICATION_ADDED } from '@constants/subscriptions'
export default {
Subscription: {
notificationAdded: {
subscribe: withFilter(
() => pubsub.asyncIterator(NOTIFICATION_ADDED),
(_, __, context) => context.pubsub.asyncIterator(NOTIFICATION_ADDED),
(payload, variables, context) => {
return payload.notificationAdded.to.id === context.user?.id
},

View File

@ -1,20 +1,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import CONFIG from '@config/index'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { createPostMutation } from '@graphql/queries/createPostMutation'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = false
const driver = getDriver()
const neode = getNeode()
let query
let mutate
let authenticatedUser
@ -40,28 +38,27 @@ const postQuery = gql`
}
`
const database = databaseContext()
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/require-await
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
query = createTestClient(server).query
mutate = createTestClient(server).mutate
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('observing posts', () => {

View File

@ -2,11 +2,12 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import CONFIG from '@config/index'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
import { createCommentMutation } from '@graphql/queries/createCommentMutation'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
@ -17,7 +18,7 @@ import { postQuery } from '@graphql/queries/postQuery'
import { profilePagePosts } from '@graphql/queries/profilePagePosts'
import { searchPosts } from '@graphql/queries/searchPosts'
import { signupVerificationMutation } from '@graphql/queries/signupVerificationMutation'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = false
@ -28,9 +29,6 @@ jest.mock('@constants/groups', () => {
}
})
const driver = getDriver()
const neode = getNeode()
let query
let mutate
let anyUser
@ -42,28 +40,26 @@ let hiddenUser
let authenticatedUser
let newUser
const database = databaseContext()
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
query = createTestClient(server).query
mutate = createTestClient(server).mutate
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('Posts in Groups', () => {

View File

@ -7,8 +7,7 @@
import { withFilter } from 'graphql-subscriptions'
import { neo4jgraphql } from 'neo4j-graphql-js'
// eslint-disable-next-line import/no-cycle
import { pubsub, ROOM_COUNT_UPDATED } from '@src/server'
import { ROOM_COUNT_UPDATED } from '@constants/subscriptions'
import Resolver from './helpers/Resolver'
@ -30,7 +29,7 @@ export default {
Subscription: {
roomCountUpdated: {
subscribe: withFilter(
() => pubsub.asyncIterator(ROOM_COUNT_UPDATED),
(_, __, context) => context.pubsub.asyncIterator(ROOM_COUNT_UPDATED),
(payload, variables, context) => {
return payload.userId === context.user?.id
},

View File

@ -1,7 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { getNeode } from '@db/neo4j'

View File

@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */

View File

@ -3,14 +3,16 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import { categories } from '@constants/categories'
import databaseContext from '@context/database'
import pubsubContext from '@context/pubsub'
import Factory, { cleanDatabase } from '@db/factories'
import User from '@db/models/User'
import { getNeode, getDriver } from '@db/neo4j'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
const categoryIds = ['cat9']
let user
@ -21,8 +23,7 @@ let query
let mutate
let variables
const driver = getDriver()
const neode = getNeode()
const pubsub = pubsubContext()
const deleteUserMutation = gql`
mutation ($id: ID!, $resource: [Deletable]) {
@ -108,25 +109,28 @@ const resetTrophyBadgesSelected = gql`
}
`
const database = databaseContext()
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
}
},
})
query = createTestClient(server).query
mutate = createTestClient(server).mutate
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database, pubsub })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
mutate = createTestClientResult.mutate
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
@ -540,10 +544,10 @@ describe('Delete a User as admin', () => {
describe('connected `EmailAddress` nodes', () => {
it('will be removed completely', async () => {
await expect(neode.all('EmailAddress')).resolves.toHaveLength(2)
await expect(database.neode.all('EmailAddress')).resolves.toHaveLength(2)
await mutate({ mutation: deleteUserMutation, variables })
await expect(neode.all('EmailAddress')).resolves.toHaveLength(1)
await expect(database.neode.all('EmailAddress')).resolves.toHaveLength(1)
})
})
@ -554,9 +558,9 @@ describe('Delete a User as admin', () => {
})
it('will be removed completely', async () => {
await expect(neode.all('SocialMedia')).resolves.toHaveLength(1)
await expect(database.neode.all('SocialMedia')).resolves.toHaveLength(1)
await mutate({ mutation: deleteUserMutation, variables })
await expect(neode.all('SocialMedia')).resolves.toHaveLength(0)
await expect(database.neode.all('SocialMedia')).resolves.toHaveLength(0)
})
})
})
@ -1041,8 +1045,8 @@ describe('updateOnlineStatus', () => {
)
const cypher = 'MATCH (u:User {id: $id}) RETURN u'
const result = await neode.cypher(cypher, { id: authenticatedUser.id })
const dbUser = neode.hydrateFirst(result, 'u', neode.model('User'))
const result = await database.neode.cypher(cypher, { id: authenticatedUser.id })
const dbUser = database.neode.hydrateFirst(result, 'u', database.neode.model('User'))
await expect(dbUser.toJson()).resolves.toMatchObject({
lastOnlineStatus: 'online',
})
@ -1067,8 +1071,8 @@ describe('updateOnlineStatus', () => {
)
const cypher = 'MATCH (u:User {id: $id}) RETURN u'
const result = await neode.cypher(cypher, { id: authenticatedUser.id })
const dbUser = neode.hydrateFirst(result, 'u', neode.model('User'))
const result = await database.neode.cypher(cypher, { id: authenticatedUser.id })
const dbUser = database.neode.hydrateFirst(result, 'u', database.neode.model('User'))
await expect(dbUser.toJson()).resolves.toMatchObject({
lastOnlineStatus: 'away',
awaySince: expect.any(String),
@ -1083,8 +1087,12 @@ describe('updateOnlineStatus', () => {
)
const cypher = 'MATCH (u:User {id: $id}) RETURN u'
const result = await neode.cypher(cypher, { id: authenticatedUser.id })
const dbUser = neode.hydrateFirst<typeof User>(result, 'u', neode.model('User'))
const result = await database.neode.cypher(cypher, { id: authenticatedUser.id })
const dbUser = database.neode.hydrateFirst<typeof User>(
result,
'u',
database.neode.model('User'),
)
await expect(dbUser.toJson()).resolves.toMatchObject({
lastOnlineStatus: 'away',
awaySince: expect.any(String),
@ -1098,8 +1106,8 @@ describe('updateOnlineStatus', () => {
}),
)
const result2 = await neode.cypher(cypher, { id: authenticatedUser.id })
const dbUser2 = neode.hydrateFirst(result2, 'u', neode.model('User'))
const result2 = await database.neode.cypher(cypher, { id: authenticatedUser.id })
const dbUser2 = database.neode.hydrateFirst(result2, 'u', database.neode.model('User'))
await expect(dbUser2.toJson()).resolves.toMatchObject({
lastOnlineStatus: 'away',
awaySince,

View File

@ -4,7 +4,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import nodemailer from 'nodemailer'
import { createTransport } from 'nodemailer'
import { htmlToText } from 'nodemailer-html-to-text'
import CONFIG from '@config/index'
@ -15,7 +15,7 @@ const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD
const hasDKIMData =
CONFIG.SMTP_DKIM_DOMAINNAME && CONFIG.SMTP_DKIM_KEYSELECTOR && CONFIG.SMTP_DKIM_PRIVATKEY
const transporter = nodemailer.createTransport({
const transporter = createTransport({
host: CONFIG.SMTP_HOST,
port: CONFIG.SMTP_PORT,
ignoreTLS: CONFIG.SMTP_IGNORE_TLS,

View File

@ -15,7 +15,6 @@ import hashtags from './hashtags/hashtagsMiddleware'
import includedFields from './includedFieldsMiddleware'
import languages from './languages/languages'
import login from './login/loginMiddleware'
// eslint-disable-next-line import/no-cycle
import notifications from './notifications/notificationsMiddleware'
import orderBy from './orderByMiddleware'
import permissions from './permissionsMiddleware'

View File

@ -1,18 +1,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
import CONFIG from '@src/config'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = false
@ -21,13 +21,10 @@ jest.mock('@middleware/helpers/email/sendMail', () => ({
sendMail: (notification) => sendMailMock(notification),
}))
let server, query, mutate, authenticatedUser, emaillessMember
let query, mutate, authenticatedUser, emaillessMember
let postAuthor, groupMember
const driver = getDriver()
const neode = getNeode()
const mentionString = `
<a class="mention" data-mention-id="group-member" href="/profile/group-member/group-member">@group-member</a>
<a class="mention" data-mention-id="email-less-member" href="/profile/email-less-member/email-less-member">@email-less-member</a>`
@ -97,22 +94,18 @@ const markAllAsRead = async () =>
`,
})
const database = databaseContext()
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
user: authenticatedUser,
neode,
driver,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
server = createServerResult.server
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
mutate = createTestClientResult.mutate
@ -120,7 +113,9 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('emails sent for notifications', () => {
@ -149,7 +144,7 @@ describe('emails sent for notifications', () => {
password: '1234',
},
)
emaillessMember = await neode.create('User', {
emaillessMember = await database.neode.create('User', {
id: 'email-less-member',
name: 'Email-less Member',
slug: 'email-less-member',

View File

@ -1,16 +1,16 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import CONFIG from '@src/config'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = false
@ -19,13 +19,10 @@ jest.mock('@middleware/helpers/email/sendMail', () => ({
sendMail: (notification) => sendMailMock(notification),
}))
let server, query, mutate, authenticatedUser
let query, mutate, authenticatedUser
let postAuthor, firstFollower, secondFollower, thirdFollower, emaillessFollower
const driver = getDriver()
const neode = getNeode()
const createPostMutation = gql`
mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) {
CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) {
@ -71,22 +68,19 @@ const followUserMutation = gql`
}
`
const database = databaseContext()
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
user: authenticatedUser,
neode,
driver,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
server = createServerResult.server
// eslint-disable-next-line @typescript-eslint/require-await
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
mutate = createTestClientResult.mutate
@ -94,7 +88,9 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('following users notifications', () => {
@ -147,7 +143,7 @@ describe('following users notifications', () => {
password: '1234',
},
)
emaillessFollower = await neode.create('User', {
emaillessFollower = await database.neode.create('User', {
id: 'email-less-follower',
name: 'Email-less Follower',
slug: 'email-less-follower',

View File

@ -1,19 +1,19 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
import CONFIG from '@src/config'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = false
@ -22,13 +22,10 @@ jest.mock('@middleware/helpers/email/sendMail', () => ({
sendMail: (notification) => sendMailMock(notification),
}))
let server, query, mutate, authenticatedUser
let query, mutate, authenticatedUser
let postAuthor, groupMember, pendingMember, noMember, emaillessMember
const driver = getDriver()
const neode = getNeode()
const mentionString = `
<a class="mention" data-mention-id="no-member" href="/profile/no-member/no-member">@no-member</a>
<a class="mention" data-mention-id="pending-member" href="/profile/pending-member/pending-member">@pending-member</a>
@ -93,22 +90,18 @@ const markAllAsRead = async () =>
`,
})
const database = databaseContext()
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
user: authenticatedUser,
neode,
driver,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
server = createServerResult.server
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
mutate = createTestClientResult.mutate
@ -116,7 +109,9 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('mentions in groups', () => {
@ -169,7 +164,7 @@ describe('mentions in groups', () => {
password: '1234',
},
)
emaillessMember = await neode.create('User', {
emaillessMember = await database.neode.create('User', {
id: 'email-less-member',
name: 'Email-less Member',
slug: 'email-less-member',

View File

@ -1,14 +1,16 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import CONFIG from '@config/index'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = false
@ -17,13 +19,10 @@ jest.mock('@middleware/helpers/email/sendMail', () => ({
sendMail: (notification) => sendMailMock(notification),
}))
let server, query, mutate, authenticatedUser
let query, mutate, authenticatedUser
let postAuthor, firstCommenter, secondCommenter, emaillessObserver
const driver = getDriver()
const neode = getNeode()
const createPostMutation = gql`
mutation ($id: ID, $title: String!, $content: String!) {
CreatePost(id: $id, title: $title, content: $content) {
@ -78,23 +77,18 @@ const toggleObservePostMutation = gql`
}
}
`
const database = databaseContext()
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
user: authenticatedUser,
neode,
driver,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
server = createServerResult.server
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
mutate = createTestClientResult.mutate
@ -102,7 +96,9 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('notifications for users that observe a post', () => {
@ -143,7 +139,7 @@ describe('notifications for users that observe a post', () => {
password: '1234',
},
)
emaillessObserver = await neode.create('User', {
emaillessObserver = await database.neode.create('User', {
id: 'email-less-observer',
name: 'Email-less Observer',
slug: 'email-less-observer',

View File

@ -1,15 +1,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import CONFIG from '@src/config'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = false
@ -23,13 +22,10 @@ jest.mock('../helpers/isUserOnline', () => ({
isUserOnline: () => isUserOnlineMock(),
}))
let server, mutate, authenticatedUser
let mutate, authenticatedUser
let postAuthor
const driver = getDriver()
const neode = getNeode()
const createPostMutation = gql`
mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) {
CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) {
@ -40,29 +36,24 @@ const createPostMutation = gql`
}
`
const database = databaseContext()
beforeAll(async () => {
await cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
user: authenticatedUser,
neode,
driver,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
server = createServerResult.server
// eslint-disable-next-line @typescript-eslint/require-await
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
const { server } = createServer({ context })
const createTestClientResult = createTestClient(server)
mutate = createTestClientResult.mutate
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
await database.driver.close()
})
afterEach(async () => {

View File

@ -1,19 +1,19 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
import CONFIG from '@src/config'
import createServer from '@src/server'
import createServer, { getContext } from '@src/server'
CONFIG.CATEGORIES_ACTIVE = false
@ -22,13 +22,10 @@ jest.mock('@middleware/helpers/email/sendMail', () => ({
sendMail: (notification) => sendMailMock(notification),
}))
let server, query, mutate, authenticatedUser
let query, mutate, authenticatedUser
let postAuthor, groupMember, pendingMember, emaillessMember
const driver = getDriver()
const neode = getNeode()
const createPostMutation = gql`
mutation ($id: ID, $title: String!, $content: String!, $groupId: ID) {
CreatePost(id: $id, title: $title, content: $content, groupId: $groupId) {
@ -95,22 +92,17 @@ const markAllAsRead = async () =>
`,
})
const database = databaseContext()
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
user: authenticatedUser,
neode,
driver,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}
},
})
server = createServerResult.server
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
mutate = createTestClientResult.mutate
@ -118,7 +110,9 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
describe('notify group members of new posts in group', () => {
@ -159,7 +153,7 @@ describe('notify group members of new posts in group', () => {
password: '1234',
},
)
emaillessMember = await neode.create('User', {
emaillessMember = await database.neode.create('User', {
id: 'email-less-member',
name: 'Email-less Member',
slug: 'email-less-member',

View File

@ -4,11 +4,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import databaseContext from '@context/database'
import pubsubContext from '@context/pubsub'
import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j'
import { changeGroupMemberRoleMutation } from '@graphql/queries/changeGroupMemberRoleMutation'
import { createGroupMutation } from '@graphql/queries/createGroupMutation'
import { createMessageMutation } from '@graphql/queries/createMessageMutation'
@ -16,7 +18,7 @@ import { createRoomMutation } from '@graphql/queries/createRoomMutation'
import { joinGroupMutation } from '@graphql/queries/joinGroupMutation'
import { leaveGroupMutation } from '@graphql/queries/leaveGroupMutation'
import { removeUserFromGroupMutation } from '@graphql/queries/removeUserFromGroupMutation'
import createServer, { pubsub } from '@src/server'
import createServer, { getContext } from '@src/server'
const sendMailMock: (notification) => void = jest.fn()
jest.mock('@middleware/helpers/email/sendMail', () => ({
@ -35,12 +37,12 @@ jest.mock('../helpers/isUserOnline', () => ({
isUserOnline: () => isUserOnlineMock(),
}))
const database = databaseContext()
const pubsub = pubsubContext()
const pubsubSpy = jest.spyOn(pubsub, 'publish')
let server, query, mutate, notifiedUser, authenticatedUser
let query, mutate, notifiedUser, authenticatedUser
const driver = getDriver()
const neode = getNeode()
const categoryIds = ['cat9']
const createPostMutation = gql`
mutation ($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) {
@ -68,19 +70,16 @@ const createCommentMutation = gql`
}
`
let server: ApolloServer
beforeAll(async () => {
await cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
user: authenticatedUser,
neode,
driver,
}
},
})
server = createServerResult.server
const contextUser = async (_req) => authenticatedUser
const context = getContext({ user: contextUser, database, pubsub })
server = createServer({ context }).server
const createTestClientResult = createTestClient(server)
query = createTestClientResult.query
mutate = createTestClientResult.mutate
@ -88,7 +87,9 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDatabase()
await driver.close()
void server.stop()
void database.driver.close()
database.neode.close()
})
beforeEach(async () => {
@ -104,7 +105,7 @@ beforeEach(async () => {
password: '1234',
},
)
await neode.create('Category', {
await database.neode.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',

View File

@ -1,10 +1,14 @@
/* eslint-disable import/no-cycle */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable security/detect-object-injection */
import {
NOTIFICATION_ADDED,
ROOM_COUNT_UPDATED,
CHAT_MESSAGE_ADDED,
} from '@constants/subscriptions'
import { getUnreadRoomsCount } from '@graphql/resolvers/rooms'
import { sendMail } from '@middleware/helpers/email/sendMail'
import {
@ -13,7 +17,6 @@ import {
} from '@middleware/helpers/email/templateBuilder'
import { isUserOnline } from '@middleware/helpers/isUserOnline'
import { validateNotifyUsers } from '@middleware/validation/validationMiddleware'
import { pubsub, NOTIFICATION_ADDED, ROOM_COUNT_UPDATED, CHAT_MESSAGE_ADDED } from '@src/server'
import extractMentionedUsers from './mentions/extractMentionedUsers'
@ -25,7 +28,7 @@ const publishNotifications = async (
): Promise<string[]> => {
const notifications = await notificationsPromise
notifications.forEach((notificationAdded) => {
pubsub.publish(NOTIFICATION_ADDED, { notificationAdded })
context.pubsub.publish(NOTIFICATION_ADDED, { notificationAdded })
if (
notificationAdded.email && // no primary email was found
(notificationAdded.to[emailNotificationSetting] ?? true) &&
@ -482,11 +485,11 @@ const handleCreateMessage = async (resolve, root, args, context, resolveInfo) =>
// send subscriptions
const roomCountUpdated = await getUnreadRoomsCount(recipientUser.id, session)
void pubsub.publish(ROOM_COUNT_UPDATED, {
void context.pubsub.publish(ROOM_COUNT_UPDATED, {
roomCountUpdated,
userId: recipientUser.id,
})
void pubsub.publish(CHAT_MESSAGE_ADDED, {
void context.pubsub.publish(CHAT_MESSAGE_ADDED, {
chatMessageAdded: message,
userId: recipientUser.id,
})

View File

@ -10,62 +10,55 @@ import http from 'node:http'
import { ApolloServer } from 'apollo-server-express'
import bodyParser from 'body-parser'
import express from 'express'
import { RedisPubSub } from 'graphql-redis-subscriptions'
import { PubSub } from 'graphql-subscriptions'
import { graphqlUploadExpress } from 'graphql-upload'
import helmet from 'helmet'
import Redis from 'ioredis'
import databaseContext from '@context/database'
import pubsubContext from '@context/pubsub'
import CONFIG from './config'
import { getNeode, getDriver } from './db/neo4j'
import schema from './graphql/schema'
import decode from './jwt/decode'
// eslint-disable-next-line import/no-cycle
import middleware from './middleware'
export const NOTIFICATION_ADDED = 'NOTIFICATION_ADDED'
export const CHAT_MESSAGE_ADDED = 'CHAT_MESSAGE_ADDED'
export const ROOM_COUNT_UPDATED = 'ROOM_COUNT_UPDATED'
const { REDIS_DOMAIN, REDIS_PORT, REDIS_PASSWORD } = CONFIG
let prodPubsub, devPubsub
const options = {
host: REDIS_DOMAIN,
port: REDIS_PORT,
password: REDIS_PASSWORD,
retryStrategy: (times) => {
return Math.min(times * 50, 2000)
},
}
if (options.host && options.port && options.password) {
prodPubsub = new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options),
})
} else {
devPubsub = new PubSub()
}
export const pubsub = prodPubsub || devPubsub
const driver = getDriver()
const neode = getNeode()
const serverDatabase = databaseContext()
const serverPubsub = pubsubContext()
const getContext = async (req) => {
const user = await decode(driver, req.headers.authorization)
return {
driver,
neode,
user,
req,
cypherParams: {
currentUserId: user ? user.id : null,
},
const databaseUser = async (req) => decode(serverDatabase.driver, req.headers.authorization)
export const getContext =
(
{
database = serverDatabase,
pubsub = serverPubsub,
user = databaseUser,
}: {
database?: ReturnType<typeof databaseContext>
pubsub?: ReturnType<typeof pubsubContext>
user?: (any) => Promise<any>
} = { database: serverDatabase, pubsub: serverPubsub, user: databaseUser },
) =>
async (req) => {
const u = await user(req)
return {
database,
driver: database.driver,
neode: database.neode,
pubsub,
user: u,
req,
cypherParams: {
currentUserId: u ? u.id : null,
},
}
}
}
export const context = async (options) => {
const { connection, req } = options
if (connection) {
return connection.context
} else {
return getContext(req)
return getContext()(req)
}
}
@ -74,9 +67,7 @@ const createServer = (options?) => {
context,
schema: middleware(schema),
subscriptions: {
onConnect: (connectionParams, _webSocket) => {
return getContext(connectionParams)
},
onConnect: (connectionParams) => getContext()(connectionParams),
},
debug: !!CONFIG.DEBUG,
uploads: false,
@ -88,11 +79,10 @@ const createServer = (options?) => {
return error
},
}
const server = new ApolloServer(Object.assign({}, defaults, options))
const server = new ApolloServer(Object.assign(defaults, options))
const app = express()
app.set('driver', driver)
// TODO: this exception is required for the graphql playground, since the playground loads external resources
// See: https://github.com/graphql/graphql-playground/issues/1283
app.use(

View File

@ -32,6 +32,7 @@
"paths": { /* Specify a set of entries that re-map imports to additional lookup locations. */
"@config/*": ["./src/config/*"],
"@constants/*": ["./src/constants/*"],
"@context/*": ["./src/context/*"],
"@db/*": ["./src/db/*"],
"@graphql/*": ["./src/graphql/*"],
"@helpers/*": ["./src/helpers/*"],