diff --git a/backend/src/db/admin.ts b/backend/src/db/admin.ts index 1f62c8733..3e65c86e4 100644 --- a/backend/src/db/admin.ts +++ b/backend/src/db/admin.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ -/* eslint-disable @typescript-eslint/require-await */ import { hashSync } from 'bcryptjs' import { v4 as uuid } from 'uuid' @@ -18,7 +17,7 @@ const defaultAdmin = { const createDefaultAdminUser = async () => { const driver = getDriver() const session = driver.session() - const createAdminTxResultPromise = session.writeTransaction(async (txc) => { + const createAdminTxResultPromise = session.writeTransaction((txc) => { txc.run( `MERGE (e:EmailAddress { email: "${defaultAdmin.email}", diff --git a/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts b/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts index f63be216d..4c7723f0c 100644 --- a/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts +++ b/backend/src/db/migrations-examples/20200206190233-swap_latitude_with_longitude.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-call */ @@ -38,10 +37,10 @@ const swap = async function (next) { } } -export async function up(next) { +export function up(next) { swap(next) } -export async function down(next) { +export function down(next) { swap(next) } diff --git a/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts b/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts index b75324a78..8e786975b 100644 --- a/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts +++ b/backend/src/db/migrations-examples/20200323140300-remove_deleted_users_obsolete_attributes.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ @@ -42,7 +41,7 @@ export async function up(next) { } } -export async function down(next) { +export function down(next) { // eslint-disable-next-line no-console console.log('Irreversible migration') next() diff --git a/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts b/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts index 597eb1d83..219f45ff7 100644 --- a/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts +++ b/backend/src/db/migrations-examples/20200323160336-remove_deleted_posts_obsolete_attributes.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ @@ -44,7 +43,7 @@ export async function up(next) { } } -export async function down(next) { +export function down(next) { // eslint-disable-next-line no-console console.log('Irreversible migration') next() diff --git a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts index 1109ac623..0d42bb03b 100644 --- a/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts +++ b/backend/src/db/migrations-examples/20200326160326-remove_dangling_image_urls.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ @@ -65,6 +64,6 @@ export async function up(next) { } } -export async function down() { +export function down() { throw new Error('Irreversible migration') } diff --git a/backend/src/db/migrations/20231017141022-fix-event-dates.ts b/backend/src/db/migrations/20231017141022-fix-event-dates.ts index 259e3ff65..33268aa5e 100644 --- a/backend/src/db/migrations/20231017141022-fix-event-dates.ts +++ b/backend/src/db/migrations/20231017141022-fix-event-dates.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-base-to-string */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { getDriver } from '@db/neo4j' @@ -31,9 +30,9 @@ export async function up(_next) { eventEnd = date.toISOString() } await transaction.run(` - MATCH (e:Event { id: '${id}' }) - SET e.eventStart = '${eventStart}' - SET (CASE WHEN exists(e.eventEnd) THEN e END).eventEnd = '${eventEnd}' + MATCH (e:Event { id: '${String(id)}' }) + SET e.eventStart = '${String(eventStart)}' + SET (CASE WHEN exists(e.eventEnd) THEN e END).eventEnd = '${String(eventEnd)}' RETURN e `) } diff --git a/backend/src/db/reset-with-migrations.ts b/backend/src/db/reset-with-migrations.ts index 3cdb8c298..9e382831e 100644 --- a/backend/src/db/reset-with-migrations.ts +++ b/backend/src/db/reset-with-migrations.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-floating-promises */ import CONFIG from '@config/index' @@ -16,7 +15,7 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) { process.exit(0) // eslint-disable-next-line no-catch-all/no-catch-all } catch (err) { - console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`) // eslint-disable-line no-console + console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${String(err)}`) // eslint-disable-line no-console process.exit(1) } })() diff --git a/backend/src/db/reset.ts b/backend/src/db/reset.ts index 5155c0738..e577400f5 100644 --- a/backend/src/db/reset.ts +++ b/backend/src/db/reset.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-floating-promises */ import CONFIG from '@config/index' @@ -16,7 +15,7 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) { process.exit(0) // eslint-disable-next-line no-catch-all/no-catch-all } catch (err) { - console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`) // eslint-disable-line no-console + console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${String(err)}`) // eslint-disable-line no-console process.exit(1) } })() diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts index 4b14c6947..ec59833ff 100644 --- a/backend/src/db/seed.ts +++ b/backend/src/db/seed.ts @@ -3,7 +3,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-floating-promises */ /* eslint-disable n/no-unpublished-import */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-confusing-void-expression */ import { faker } from '@faker-js/faker' diff --git a/backend/src/graphql/resolvers/badges.ts b/backend/src/graphql/resolvers/badges.ts index b5e0c1688..f86efc7d3 100644 --- a/backend/src/graphql/resolvers/badges.ts +++ b/backend/src/graphql/resolvers/badges.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* 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-member-access */ @@ -29,7 +28,7 @@ export const defaultVerificationBadge = { export default { Query: { - Badge: async (object, args, context, resolveInfo) => + Badge: (object, args, context, resolveInfo) => neo4jgraphql(object, args, context, resolveInfo), }, @@ -172,7 +171,7 @@ export default { }, }, Badge: { - isDefault: async (parent, _params, _context, _resolveInfo) => + isDefault: (parent, _params, _context, _resolveInfo) => [defaultTrophyBadge.id, defaultVerificationBadge.id].includes(parent.id), }, } diff --git a/backend/src/graphql/resolvers/embeds.ts b/backend/src/graphql/resolvers/embeds.ts index 8ce144b4f..c16054fca 100644 --- a/backend/src/graphql/resolvers/embeds.ts +++ b/backend/src/graphql/resolvers/embeds.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import scrape from './embeds/scraper' @@ -6,7 +5,7 @@ import { undefinedToNullResolver } from './helpers/Resolver' export default { Query: { - embed: async (_object, { url }, _context, _resolveInfo) => { + embed: (_object, { url }, _context, _resolveInfo) => { return scrape(url) }, }, @@ -25,7 +24,7 @@ export default { 'lang', 'html', ]), - sources: async (parent, _params, _context, _resolveInfo) => { + sources: (parent, _params, _context, _resolveInfo) => { return typeof parent.sources === 'undefined' ? [] : parent.sources }, }, diff --git a/backend/src/graphql/resolvers/embeds/scraper.ts b/backend/src/graphql/resolvers/embeds/scraper.ts index de9f9a2fd..4ecd79c57 100644 --- a/backend/src/graphql/resolvers/embeds/scraper.ts +++ b/backend/src/graphql/resolvers/embeds/scraper.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ @@ -53,7 +52,7 @@ const fetchEmbed = async (url) => { json = await response.json() // eslint-disable-next-line no-catch-all/no-catch-all } catch (err) { - error(`Error fetching embed data: ${err.message}`) + error(`Error fetching embed data: ${String(err.message)}`) return {} } diff --git a/backend/src/graphql/resolvers/groups.ts b/backend/src/graphql/resolvers/groups.ts index 219dfa126..0fa303c42 100644 --- a/backend/src/graphql/resolvers/groups.ts +++ b/backend/src/graphql/resolvers/groups.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ @@ -6,7 +5,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/no-shadow */ -/* eslint-disable @typescript-eslint/no-use-before-define */ import { v4 as uuid } from 'uuid' import { CATEGORIES_MIN, CATEGORIES_MAX } from '@constants/categories' @@ -20,6 +18,35 @@ import { createOrUpdateLocations } from './users/location' import type { Context } from '@src/context' +const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId) => { + return session.writeTransaction(async (transaction) => { + const removeUserFromGroupCypher = ` + MATCH (user:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId}) + DELETE membership + WITH user, group + OPTIONAL MATCH (author:User)-[:WROTE]->(p:Post)-[:IN]->(group) + WHERE NOT group.groupType = 'public' + AND NOT author.id = $userId + WITH user, collect(p) AS posts + FOREACH (post IN posts | + MERGE (user)-[:CANNOT_SEE]->(post)) + RETURN user {.*}, NULL as membership + ` + + const transactionResponse = await transaction.run(removeUserFromGroupCypher, { + groupId, + userId, + }) + const [result] = transactionResponse.records.map((record) => { + return { user: record.get('user'), membership: record.get('membership') } + }) + if (!result) { + throw new UserInputError('User is not a member of this group') + } + return result + }) +} + export default { Query: { Group: async (_object, params, context: Context, _resolveInfo) => { @@ -488,13 +515,13 @@ export default { membersCount: '<-[:MEMBER_OF]-(related:User)', }, }), - name: async (parent, _args, context: Context, _resolveInfo) => { + name: (parent, _args, context: Context, _resolveInfo) => { if (!context.user) { return parent.groupType === 'hidden' ? '' : parent.name } return parent.name }, - about: async (parent, _args, context: Context, _resolveInfo) => { + about: (parent, _args, context: Context, _resolveInfo) => { if (!context.user) { return parent.groupType === 'hidden' ? '' : parent.about } @@ -502,32 +529,3 @@ export default { }, }, } - -const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId) => { - return session.writeTransaction(async (transaction) => { - const removeUserFromGroupCypher = ` - MATCH (user:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId}) - DELETE membership - WITH user, group - OPTIONAL MATCH (author:User)-[:WROTE]->(p:Post)-[:IN]->(group) - WHERE NOT group.groupType = 'public' - AND NOT author.id = $userId - WITH user, collect(p) AS posts - FOREACH (post IN posts | - MERGE (user)-[:CANNOT_SEE]->(post)) - RETURN user {.*}, NULL as membership - ` - - const transactionResponse = await transaction.run(removeUserFromGroupCypher, { - groupId, - userId, - }) - const [result] = transactionResponse.records.map((record) => { - return { user: record.get('user'), membership: record.get('membership') } - }) - if (!result) { - throw new UserInputError('User is not a member of this group') - } - return result - }) -} diff --git a/backend/src/graphql/resolvers/helpers/Resolver.ts b/backend/src/graphql/resolvers/helpers/Resolver.ts index e7161589a..92b7decbf 100644 --- a/backend/src/graphql/resolvers/helpers/Resolver.ts +++ b/backend/src/graphql/resolvers/helpers/Resolver.ts @@ -1,7 +1,5 @@ /* eslint-disable @typescript-eslint/no-dynamic-delete */ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ @@ -11,7 +9,7 @@ export const undefinedToNullResolver = (list) => { const resolvers = {} list.forEach((key) => { - resolvers[key] = async (parent) => { + resolvers[key] = (parent) => { return typeof parent[key] === 'undefined' ? null : parent[key] } }) @@ -37,7 +35,7 @@ export default function Resolver(type, options: any = {}) { try { let response = await session.readTransaction(async (txc) => { const cypher = ` - MATCH(:${type} {${idAttribute}: $id})${connection} + MATCH(:${String(type)} {${String(idAttribute)}: $id})${String(connection)} RETURN related {.*} as related ` const result = await txc.run(cypher, { id, cypherParams }) @@ -62,7 +60,7 @@ export default function Resolver(type, options: any = {}) { try { return await session.readTransaction(async (txc) => { const nodeCondition = condition.replace('this', 'this {id: $id}') - const cypher = `${nodeCondition} as ${key}` + const cypher = `${String(nodeCondition)} as ${key}` const result = await txc.run(cypher, { id, cypherParams }) const [response] = result.records.map((r) => r.get(key)) return response @@ -85,7 +83,7 @@ export default function Resolver(type, options: any = {}) { return await session.readTransaction(async (txc) => { const id = parent[idAttribute] const cypher = ` - MATCH(u:${type} {${idAttribute}: $id})${connection} + MATCH(u:${String(type)} {${String(idAttribute)}: $id})${String(connection)} RETURN COUNT(DISTINCT(related)) as count ` const result = await txc.run(cypher, { id, cypherParams }) @@ -141,7 +139,7 @@ export const convertObjectToCypherMapLiteral = (params, addSpaceInfrontIfMapIsNo let mapLiteral = '' paramsEntries.forEach((ele, index) => { mapLiteral += index === 0 ? '{' : '' - mapLiteral += `${ele[0]}: "${ele[1]}"` + mapLiteral += `${ele[0]}: "${String(ele[1])}"` mapLiteral += index < paramsEntries.length - 1 ? ', ' : '}' }) mapLiteral = (addSpaceInfrontIfMapIsNotEmpty && mapLiteral.length > 0 ? ' ' : '') + mapLiteral diff --git a/backend/src/graphql/resolvers/helpers/events.ts b/backend/src/graphql/resolvers/helpers/events.ts index 15ee652b1..3818a8202 100644 --- a/backend/src/graphql/resolvers/helpers/events.ts +++ b/backend/src/graphql/resolvers/helpers/events.ts @@ -2,9 +2,31 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-use-before-define */ import { UserInputError } from '@graphql/errors' +const validateEventDate = (dateString) => { + const date = new Date(dateString) + if (date.toString() === 'Invalid Date') + throw new UserInputError('Event start date must be a valid date!') + if (date.toISOString() !== dateString) + throw new UserInputError('Event start date must be in ISO format!') + const now = new Date() + if (date.getTime() < now.getTime()) { + throw new UserInputError('Event start date must be in the future!') + } +} + +const validateEventEnd = (start, end) => { + const endDate = new Date(end) + if (endDate.toString() === 'Invalid Date') + throw new UserInputError('Event end date must be a valid date!') + if (endDate.toISOString() !== end) + throw new UserInputError('Event end date must be in ISO format!') + const startDate = new Date(start) + if (endDate < startDate) + throw new UserInputError('Event end date must be a after event start date!') +} + export const validateEventParams = (params) => { let locationName = null if (params.postType && params.postType === 'Event') { @@ -34,26 +56,3 @@ export const validateEventParams = (params) => { delete params.eventInput return locationName } - -const validateEventDate = (dateString) => { - const date = new Date(dateString) - if (date.toString() === 'Invalid Date') - throw new UserInputError('Event start date must be a valid date!') - if (date.toISOString() !== dateString) - throw new UserInputError('Event start date must be in ISO format!') - const now = new Date() - if (date.getTime() < now.getTime()) { - throw new UserInputError('Event start date must be in the future!') - } -} - -const validateEventEnd = (start, end) => { - const endDate = new Date(end) - if (endDate.toString() === 'Invalid Date') - throw new UserInputError('Event end date must be a valid date!') - if (endDate.toISOString() !== end) - throw new UserInputError('Event end date must be in ISO format!') - const startDate = new Date(start) - if (endDate < startDate) - throw new UserInputError('Event end date must be a after event start date!') -} diff --git a/backend/src/graphql/resolvers/images.ts b/backend/src/graphql/resolvers/images.ts index fcd29f41a..afb987078 100644 --- a/backend/src/graphql/resolvers/images.ts +++ b/backend/src/graphql/resolvers/images.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ import crypto from 'node:crypto' import { join as joinPath } from 'node:path/posix' @@ -15,6 +13,16 @@ type UrlResolver = ( }: Pick, ) => string +const chain: (...methods: UrlResolver[]) => UrlResolver = (...methods) => { + return (parent, args, context) => { + let { url } = parent + for (const method of methods) { + url = method({ url }, args, context) + } + return url + } +} + const pointUrlToImagor: (opts: { transformations: UrlResolver[] }) => UrlResolver = ({ transformations }) => ({ url }, _args, context) => { @@ -59,22 +67,12 @@ const resize: UrlResolver = ({ url }, { height, width }) => { if (!(height || width)) { return url } - const window = `/fit-in/${width ?? FALLBACK_MAXIMUM_LENGTH}x${height ?? FALLBACK_MAXIMUM_LENGTH}` + const window = `/fit-in/${String(width ?? FALLBACK_MAXIMUM_LENGTH)}x${String(height ?? FALLBACK_MAXIMUM_LENGTH)}` const newUrl = new URL(url) newUrl.pathname = window + newUrl.pathname return newUrl.href } -const chain: (...methods: UrlResolver[]) => UrlResolver = (...methods) => { - return (parent, args, context) => { - let { url } = parent - for (const method of methods) { - url = method({ url }, args, context) - } - return url - } -} - export default { Image: { ...Resolver('Image', { diff --git a/backend/src/graphql/resolvers/images/imagesS3.ts b/backend/src/graphql/resolvers/images/imagesS3.ts index 61624f677..33d7d543c 100644 --- a/backend/src/graphql/resolvers/images/imagesS3.ts +++ b/backend/src/graphql/resolvers/images/imagesS3.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable @typescript-eslint/no-shadow */ import path from 'node:path' @@ -41,6 +40,17 @@ export const images = (config: S3Config) => { return image } + const uploadImageFile = async (uploadPromise: Promise | undefined) => { + if (!uploadPromise) return undefined + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const upload = await uploadPromise + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + const { name, ext } = path.parse(upload.filename) + const uniqueFilename = `${uuid()}-${slug(name)}${ext}` + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + return await s3.uploadFile({ ...upload, uniqueFilename }) + } + const mergeImage: Images['mergeImage'] = async ( resource, relationshipType, @@ -84,17 +94,6 @@ export const images = (config: S3Config) => { return mergedImage } - const uploadImageFile = async (uploadPromise: Promise | undefined) => { - if (!uploadPromise) return undefined - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const upload = await uploadPromise - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access - const { name, ext } = path.parse(upload.filename) - const uniqueFilename = `${uuid()}-${slug(name)}${ext}` - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - return await s3.uploadFile({ ...upload, uniqueFilename }) - } - const images = { deleteImage, mergeImage, diff --git a/backend/src/graphql/resolvers/messages.ts b/backend/src/graphql/resolvers/messages.ts index 0ec5cb9a1..2e7a54c96 100644 --- a/backend/src/graphql/resolvers/messages.ts +++ b/backend/src/graphql/resolvers/messages.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ @@ -16,7 +15,7 @@ import Resolver from './helpers/Resolver' import type { File } from './attachments/attachments' -const setMessagesAsDistributed = async (undistributedMessagesIds, session) => { +const setMessagesAsDistributed = (undistributedMessagesIds, session) => { return session.writeTransaction(async (transaction) => { const setDistributedCypher = ` MATCH (m:Message) WHERE m.id IN $undistributedMessagesIds diff --git a/backend/src/graphql/resolvers/notifications.ts b/backend/src/graphql/resolvers/notifications.ts index abf594553..db740060d 100644 --- a/backend/src/graphql/resolvers/notifications.ts +++ b/backend/src/graphql/resolvers/notifications.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -45,8 +43,8 @@ export default { default: orderByClause = '' } - const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : '' - const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : '' + const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${String(args.offset)}` : '' + const limit = args.first && typeof args.first === 'number' ? `LIMIT ${String(args.first)}` : '' const readTxResultPromise = session.readTransaction(async (transaction) => { const notificationsTransactionResponse = await transaction.run( @@ -145,9 +143,9 @@ export default { }, }, NOTIFIED: { - id: async (parent) => { + id: (parent) => { // serialize an ID to help the client update the cache - return `${parent.reason}/${parent.from.id}/${parent.to.id}` + return `${String(parent.reason)}/${String(parent.from.id)}/${String(parent.to.id)}` }, }, } diff --git a/backend/src/graphql/resolvers/posts.ts b/backend/src/graphql/resolvers/posts.ts index e8e908e91..efcc38b88 100644 --- a/backend/src/graphql/resolvers/posts.ts +++ b/backend/src/graphql/resolvers/posts.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -186,7 +185,7 @@ export default { SET post.sortDate = toString(datetime()) SET post.clickedCount = 0 SET post.viewedTeaserCount = 0 - SET post:${params.postType} + SET post:${String(params.postType)} WITH post MATCH (author:User {id: $userId}) MERGE (post)<-[:WROTE]-(author) @@ -260,7 +259,7 @@ export default { updatePostCypher += ` REMOVE post:Article REMOVE post:Event - SET post:${params.postType} + SET post:${String(params.postType)} WITH post ` } diff --git a/backend/src/graphql/resolvers/reports.ts b/backend/src/graphql/resolvers/reports.ts index 84d800f6d..fe0678109 100644 --- a/backend/src/graphql/resolvers/reports.ts +++ b/backend/src/graphql/resolvers/reports.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -80,8 +79,8 @@ export default { const filterClause = filterClauses.join(' ') const offset = - params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : '' - const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : '' + params.offset && typeof params.offset === 'number' ? `SKIP ${String(params.offset)}` : '' + const limit = params.first && typeof params.first === 'number' ? `LIMIT ${String(params.first)}` : '' const reportsReadTxPromise = session.readTransaction(async (transaction) => { const reportsTransactionResponse = await transaction.run( diff --git a/backend/src/graphql/resolvers/roles.ts b/backend/src/graphql/resolvers/roles.ts index 006d4f5ba..13b249051 100644 --- a/backend/src/graphql/resolvers/roles.ts +++ b/backend/src/graphql/resolvers/roles.ts @@ -1,7 +1,6 @@ -/* eslint-disable @typescript-eslint/require-await */ export default { Query: { - availableRoles: async (_parent, _args, _context, _resolveInfo) => { + availableRoles: (_parent, _args, _context, _resolveInfo) => { return ['admin', 'moderator', 'user'] }, }, diff --git a/backend/src/graphql/resolvers/rooms.ts b/backend/src/graphql/resolvers/rooms.ts index 060eccf34..44c0faba4 100644 --- a/backend/src/graphql/resolvers/rooms.ts +++ b/backend/src/graphql/resolvers/rooms.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ @@ -38,7 +37,7 @@ export default { }, }, Query: { - Room: async (object, params, context, resolveInfo) => { + Room: (object, params, context, resolveInfo) => { if (!params.filter) params.filter = {} params.filter.users_some = { id: context.user.id, diff --git a/backend/src/graphql/resolvers/searches.ts b/backend/src/graphql/resolvers/searches.ts index 2f1a74662..87f690bf1 100644 --- a/backend/src/graphql/resolvers/searches.ts +++ b/backend/src/graphql/resolvers/searches.ts @@ -1,8 +1,6 @@ -/* eslint-disable @typescript-eslint/require-await */ /* 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/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ import { queryString } from './searches/queryString' @@ -10,16 +8,16 @@ import { queryString } from './searches/queryString' // see http://lucene.apache.org/core/8_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description const cypherTemplate = (setup) => ` - CALL db.index.fulltext.queryNodes('${setup.fulltextIndex}', $query) + CALL db.index.fulltext.queryNodes('${String(setup.fulltextIndex)}', $query) YIELD node AS resource, score - ${setup.match} - ${setup.whereClause} - ${setup.withClause} - RETURN - ${setup.returnClause} + ${String(setup.match)} + ${String(setup.whereClause)} + ${String(setup.withClause)} + RETURN + ${String(setup.returnClause)} AS result SKIP toInteger($skip) - ${setup.limit} + ${String(setup.limit)} ` const simpleWhereClause = @@ -148,7 +146,7 @@ const multiSearchMap = [ export default { Query: { - searchPosts: async (_parent, args, context, _resolveInfo) => { + searchPosts: (_parent, args, context, _resolveInfo) => { const { query, postsOffset, firstPosts } = args let userId = null if (context.user) userId = context.user.id @@ -171,7 +169,7 @@ export default { }), } }, - searchUsers: async (_parent, args, context, _resolveInfo) => { + searchUsers: (_parent, args, context, _resolveInfo) => { const { query, usersOffset, firstUsers } = args return { userCount: getSearchResults( @@ -190,7 +188,7 @@ export default { }), } }, - searchHashtags: async (_parent, args, context, _resolveInfo) => { + searchHashtags: (_parent, args, context, _resolveInfo) => { const { query, hashtagsOffset, firstHashtags } = args return { hashtagCount: getSearchResults( @@ -209,7 +207,7 @@ export default { }), } }, - searchGroups: async (_parent, args, context, _resolveInfo) => { + searchGroups: (_parent, args, context, _resolveInfo) => { const { query, groupsOffset, firstGroups } = args let userId = null if (context.user) userId = context.user.id diff --git a/backend/src/graphql/resolvers/searches/queryString.ts b/backend/src/graphql/resolvers/searches/queryString.ts index f81acd75d..6338759e7 100644 --- a/backend/src/graphql/resolvers/searches/queryString.ts +++ b/backend/src/graphql/resolvers/searches/queryString.ts @@ -2,48 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-use-before-define */ -export function queryString(str) { - const normalizedString = normalizeWhitespace(str) - const escapedString = escapeSpecialCharacters(normalizedString) - return ` -${matchWholeText(escapedString)} -${matchEachWordExactly(escapedString)} -${matchSomeWordsExactly(escapedString)} -${matchBeginningOfWords(escapedString)} -` -} - -const matchWholeText = (str, boost = 8) => { - return `"${str}"^${boost}` -} - -const matchEachWordExactly = (str, boost = 4) => { - if (!str.includes(' ')) return '' - const tmp = str - .split(' ') - .map((s, i) => (i === 0 ? `"${s}"` : `AND "${s}"`)) - .join(' ') - return `(${tmp})^${boost}` -} - -const matchSomeWordsExactly = (str, boost = 2) => { - if (!str.includes(' ')) return '' - return str - .split(' ') - .map((s) => `"${s}"^${boost}`) - .join(' ') -} - -const matchBeginningOfWords = (str) => { - return str - .split(' ') - .filter((s) => s.length >= 2) - .map((s) => s + '*') - .join(' ') -} export function normalizeWhitespace(str) { // delete the first character if it is !, @ or # @@ -56,3 +15,43 @@ export function normalizeWhitespace(str) { export function escapeSpecialCharacters(str) { return str.replace(/(["[\]&|\\{}+!()^~*?:/-])/g, '\\$1') } + +const matchWholeText = (str, boost = 8) => { + return `"${String(str)}"^${String(boost)}` +} + +const matchEachWordExactly = (str, boost = 4) => { + if (!str.includes(' ')) return '' + const tmp = str + .split(' ') + .map((s, i) => (i === 0 ? `"${String(s)}"` : `AND "${String(s)}"`)) + .join(' ') + return `(${tmp})^${String(boost)}` +} + +const matchSomeWordsExactly = (str, boost = 2) => { + if (!str.includes(' ')) return '' + return str + .split(' ') + .map((s) => `"${String(s)}"^${String(boost)}`) + .join(' ') +} + +const matchBeginningOfWords = (str) => { + return str + .split(' ') + .filter((s) => s.length >= 2) + .map((s) => s + '*') + .join(' ') +} + +export function queryString(str) { + const normalizedString = normalizeWhitespace(str) + const escapedString = escapeSpecialCharacters(normalizedString) + return ` +${matchWholeText(escapedString)} +${matchEachWordExactly(escapedString)} +${matchSomeWordsExactly(escapedString)} +${matchBeginningOfWords(escapedString)} +` +} diff --git a/backend/src/graphql/resolvers/userData.ts b/backend/src/graphql/resolvers/userData.ts index cd87f09ad..f06e4ed91 100644 --- a/backend/src/graphql/resolvers/userData.ts +++ b/backend/src/graphql/resolvers/userData.ts @@ -2,7 +2,13 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-use-before-define */ + +const byCreationDate = (a, b) => { + if (a.createdAt < b.createdAt) return -1 + if (a.createdAt > b.createdAt) return 1 + return 0 +} + export default { Query: { userData: async (_object, _args, context, _resolveInfo) => { @@ -53,9 +59,3 @@ export default { }, }, } - -const byCreationDate = (a, b) => { - if (a.createdAt < b.createdAt) return -1 - if (a.createdAt > b.createdAt) return 1 - return 0 -} diff --git a/backend/src/graphql/resolvers/users.ts b/backend/src/graphql/resolvers/users.ts index 25d122e34..0270c1ff8 100644 --- a/backend/src/graphql/resolvers/users.ts +++ b/backend/src/graphql/resolvers/users.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -215,11 +214,11 @@ export default { await Promise.all( resource.map(async (node) => { if (!allowedLabels.includes(node)) { - throw new UserInputError(`Invalid resource type: ${node}`) + throw new UserInputError(`Invalid resource type: ${String(node)}`) } const txResult = await transaction.run( ` - MATCH (resource:${node})<-[:WROTE]-(author:User {id: $userId}) + MATCH (resource:${String(node)})<-[:WROTE]-(author:User {id: $userId}) OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment) SET resource.deleted = true SET resource.content = 'UNAVAILABLE' @@ -371,7 +370,7 @@ export default { if (slot >= TROPHY_BADGES_SELECTED_MAX || slot < 0) { throw new Error( - `Invalid slot! There is only ${TROPHY_BADGES_SELECTED_MAX} badge-slots to fill`, + `Invalid slot! There is only ${String(TROPHY_BADGES_SELECTED_MAX)} badge-slots to fill`, ) } diff --git a/backend/src/graphql/resolvers/users/location.ts b/backend/src/graphql/resolvers/users/location.ts index 10a9497f1..80ed79808 100644 --- a/backend/src/graphql/resolvers/users/location.ts +++ b/backend/src/graphql/resolvers/users/location.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/restrict-plus-operands */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ @@ -19,7 +18,7 @@ const REQUEST_TIMEOUT = 3000 const createLocation = async (session, mapboxData) => { const data = { - id: mapboxData.id + (mapboxData.address ? `-${mapboxData.address}` : ''), + id: mapboxData.id + (mapboxData.address ? `-${String(mapboxData.address)}` : ''), nameEN: mapboxData.text_en, nameDE: mapboxData.text_de, nameFR: mapboxData.text_fr, @@ -77,9 +76,9 @@ export const createOrUpdateLocations = async ( if (locationName !== null) { const response: any = await fetch( `https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent( - locationName, + String(locationName), )}.json?access_token=${ - context.config.MAPBOX_TOKEN + String(context.config.MAPBOX_TOKEN) }&types=region,place,country,address&language=${locales.join(',')}`, { signal: AbortSignal.timeout(REQUEST_TIMEOUT), @@ -115,7 +114,7 @@ export const createOrUpdateLocations = async ( let parent = data if (parent.address) { - parent.id += `-${parent.address}` + parent.id += `-${String(parent.address)}` } if (data.context) { @@ -147,7 +146,7 @@ export const createOrUpdateLocations = async ( await session.writeTransaction((transaction) => { return transaction.run( ` - MATCH (node:${nodeLabel} {id: $nodeId}) + MATCH (node:${String(nodeLabel)} {id: $nodeId}) OPTIONAL MATCH (node)-[relationship:IS_IN]->(:Location) DELETE relationship WITH node @@ -162,7 +161,7 @@ export const createOrUpdateLocations = async ( export const queryLocations = async ({ place, lang }, context: Context) => { const res: any = await fetch( - `https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${context.config.MAPBOX_TOKEN}&types=region,place,country&language=${lang}`, + `https://api.mapbox.com/geocoding/v5/mapbox.places/${String(place)}.json?access_token=${String(context.config.MAPBOX_TOKEN)}&types=region,place,country&language=${String(lang)}`, { signal: AbortSignal.timeout(REQUEST_TIMEOUT), }, diff --git a/backend/src/middleware/excerptMiddleware.ts b/backend/src/middleware/excerptMiddleware.ts index b3b23555a..d9aa99096 100644 --- a/backend/src/middleware/excerptMiddleware.ts +++ b/backend/src/middleware/excerptMiddleware.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ @@ -9,33 +8,33 @@ import { DESCRIPTION_EXCERPT_HTML_LENGTH } from '@constants/groups' import type { IMiddlewareResolver } from 'graphql-middleware/dist/types' -const createGroup: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const createGroup: IMiddlewareResolver = (resolve, root, args, context, info) => { args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html return resolve(root, args, context, info) } -const updateGroup: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const updateGroup: IMiddlewareResolver = (resolve, root, args, context, info) => { if (args.description) args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html return resolve(root, args, context, info) } -const createPost: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const createPost: IMiddlewareResolver = (resolve, root, args, context, info) => { args.contentExcerpt = trunc(args.content, 120).html return resolve(root, args, context, info) } -const updatePost: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const updatePost: IMiddlewareResolver = (resolve, root, args, context, info) => { args.contentExcerpt = trunc(args.content, 120).html return resolve(root, args, context, info) } -const createComment: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const createComment: IMiddlewareResolver = (resolve, root, args, context, info) => { args.contentExcerpt = trunc(args.content, 180).html return resolve(root, args, context, info) } -const updateComment: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const updateComment: IMiddlewareResolver = (resolve, root, args, context, info) => { args.contentExcerpt = trunc(args.content, 180).html return resolve(root, args, context, info) } diff --git a/backend/src/middleware/notifications/notificationsMiddleware.ts b/backend/src/middleware/notifications/notificationsMiddleware.ts index 24c6273c6..7c02fdba0 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.ts +++ b/backend/src/middleware/notifications/notificationsMiddleware.ts @@ -4,7 +4,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable security/detect-object-injection */ -/* eslint-disable @typescript-eslint/no-use-before-define */ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/restrict-plus-operands */ import { @@ -43,136 +42,6 @@ const publishNotifications = async ( return emailsSent } -const handleJoinGroup: IMiddlewareResolver = async (resolve, root, args, context, resolveInfo) => { - const { groupId, userId } = args - const user = await resolve(root, args, context, resolveInfo) - if (user) { - await publishNotifications( - context, - notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context), - 'emailNotificationsGroupMemberJoined', - ) - } - return user -} - -const handleLeaveGroup: IMiddlewareResolver = async (resolve, root, args, context, resolveInfo) => { - const { groupId, userId } = args - const user = await resolve(root, args, context, resolveInfo) - if (user) { - await publishNotifications( - context, - notifyOwnersOfGroup(groupId, userId, 'user_left_group', context), - 'emailNotificationsGroupMemberLeft', - ) - } - return user -} - -const handleChangeGroupMemberRole: IMiddlewareResolver = async ( - resolve, - root, - args, - context, - resolveInfo, -) => { - const { groupId, userId } = args - const user = await resolve(root, args, context, resolveInfo) - if (user) { - await publishNotifications( - context, - notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context), - 'emailNotificationsGroupMemberRoleChanged', - ) - } - return user -} - -const handleRemoveUserFromGroup: IMiddlewareResolver = async ( - resolve, - root, - args, - context, - resolveInfo, -) => { - const { groupId, userId } = args - const user = await resolve(root, args, context, resolveInfo) - if (user) { - await publishNotifications( - context, - notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context), - 'emailNotificationsGroupMemberRemoved', - ) - } - return user -} - -const handleContentDataOfPost: IMiddlewareResolver = async ( - resolve, - root, - args, - context, - resolveInfo, -) => { - const { groupId } = args - const idsOfUsers = extractMentionedUsers(args.content) - const post = await resolve(root, args, context, resolveInfo) - if (post) { - const sentEmails: string[] = await publishNotifications( - context, - notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context), - 'emailNotificationsMention', - ) - sentEmails.concat( - await publishNotifications( - context, - notifyFollowingUsers(post.id, groupId, context), - 'emailNotificationsFollowingUsers', - sentEmails, - ), - ) - await publishNotifications( - context, - notifyGroupMembersOfNewPost(post.id, groupId, context), - 'emailNotificationsPostInGroup', - sentEmails, - ) - } - return post -} - -const handleContentDataOfComment: IMiddlewareResolver = async ( - resolve, - root, - args, - context, - resolveInfo, -) => { - const { content } = args - let idsOfMentionedUsers = extractMentionedUsers(content) - const comment = await resolve(root, args, context, resolveInfo) - const [postAuthor] = await postAuthorOfComment(comment.id, { context }) - idsOfMentionedUsers = idsOfMentionedUsers.filter((id) => id !== postAuthor.id) - const sentEmails: string[] = await publishNotifications( - context, - notifyUsersOfMention( - 'Comment', - comment.id, - idsOfMentionedUsers, - 'mentioned_in_comment', - context, - ), - 'emailNotificationsMention', - ) - await publishNotifications( - context, - notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context), - 'emailNotificationsCommentOnObservedPost', - sentEmails, - ) - return comment -} - const postAuthorOfComment = async (commentId, { context }) => { const session = context.driver.session() let postAuthorId @@ -448,6 +317,136 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => { } } +const handleJoinGroup: IMiddlewareResolver = async (resolve, root, args, context, resolveInfo) => { + const { groupId, userId } = args + const user = await resolve(root, args, context, resolveInfo) + if (user) { + await publishNotifications( + context, + notifyOwnersOfGroup(groupId, userId, 'user_joined_group', context), + 'emailNotificationsGroupMemberJoined', + ) + } + return user +} + +const handleLeaveGroup: IMiddlewareResolver = async (resolve, root, args, context, resolveInfo) => { + const { groupId, userId } = args + const user = await resolve(root, args, context, resolveInfo) + if (user) { + await publishNotifications( + context, + notifyOwnersOfGroup(groupId, userId, 'user_left_group', context), + 'emailNotificationsGroupMemberLeft', + ) + } + return user +} + +const handleChangeGroupMemberRole: IMiddlewareResolver = async ( + resolve, + root, + args, + context, + resolveInfo, +) => { + const { groupId, userId } = args + const user = await resolve(root, args, context, resolveInfo) + if (user) { + await publishNotifications( + context, + notifyMemberOfGroup(groupId, userId, 'changed_group_member_role', context), + 'emailNotificationsGroupMemberRoleChanged', + ) + } + return user +} + +const handleRemoveUserFromGroup: IMiddlewareResolver = async ( + resolve, + root, + args, + context, + resolveInfo, +) => { + const { groupId, userId } = args + const user = await resolve(root, args, context, resolveInfo) + if (user) { + await publishNotifications( + context, + notifyMemberOfGroup(groupId, userId, 'removed_user_from_group', context), + 'emailNotificationsGroupMemberRemoved', + ) + } + return user +} + +const handleContentDataOfPost: IMiddlewareResolver = async ( + resolve, + root, + args, + context, + resolveInfo, +) => { + const { groupId } = args + const idsOfUsers = extractMentionedUsers(args.content) + const post = await resolve(root, args, context, resolveInfo) + if (post) { + const sentEmails: string[] = await publishNotifications( + context, + notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context), + 'emailNotificationsMention', + ) + sentEmails.concat( + await publishNotifications( + context, + notifyFollowingUsers(post.id, groupId, context), + 'emailNotificationsFollowingUsers', + sentEmails, + ), + ) + await publishNotifications( + context, + notifyGroupMembersOfNewPost(post.id, groupId, context), + 'emailNotificationsPostInGroup', + sentEmails, + ) + } + return post +} + +const handleContentDataOfComment: IMiddlewareResolver = async ( + resolve, + root, + args, + context, + resolveInfo, +) => { + const { content } = args + let idsOfMentionedUsers = extractMentionedUsers(content) + const comment = await resolve(root, args, context, resolveInfo) + const [postAuthor] = await postAuthorOfComment(comment.id, { context }) + idsOfMentionedUsers = idsOfMentionedUsers.filter((id) => id !== postAuthor.id) + const sentEmails: string[] = await publishNotifications( + context, + notifyUsersOfMention( + 'Comment', + comment.id, + idsOfMentionedUsers, + 'mentioned_in_comment', + context, + ), + 'emailNotificationsMention', + ) + await publishNotifications( + context, + notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context), + 'emailNotificationsCommentOnObservedPost', + sentEmails, + ) + return comment +} + const handleCreateMessage: IMiddlewareResolver = async ( resolve, root, diff --git a/backend/src/middleware/permissionsMiddleware.ts b/backend/src/middleware/permissionsMiddleware.ts index 3cbcdd0b5..2971febbe 100644 --- a/backend/src/middleware/permissionsMiddleware.ts +++ b/backend/src/middleware/permissionsMiddleware.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unnecessary-type-conversion */ @@ -22,27 +21,27 @@ const neode = getNeode() const isAuthenticated = rule({ cache: 'contextual', -})(async (_parent, _args, ctx, _info) => { +})((_parent, _args, ctx, _info) => { return !!ctx?.user?.id }) -const isModerator = rule()(async (_parent, _args, { user }: Context, _info) => { +const isModerator = rule()((_parent, _args, { user }: Context, _info) => { return !!(user && (user.role === 'moderator' || user.role === 'admin')) }) -const isAdmin = rule()(async (_parent, _args, { user }: Context, _info) => { +const isAdmin = rule()((_parent, _args, { user }: Context, _info) => { return !!(user?.role === 'admin') }) const onlyYourself = rule({ cache: 'no_cache', -})(async (_parent, args, context: Context, _info) => { +})((_parent, args, context: Context, _info) => { return context.user?.id === args.id }) const isMyOwn = rule({ cache: 'no_cache', -})(async (parent, _args, { user }: Context, _info) => { +})((parent, _args, { user }: Context, _info) => { return !!(user && user.id === parent.id) }) @@ -362,21 +361,21 @@ const isAuthor = rule({ const isDeletingOwnAccount = rule({ cache: 'no_cache', -})(async (_parent, args, context: Context, _info) => { +})((_parent, args, context: Context, _info) => { return context.user?.id === args.id }) const noEmailFilter = rule({ cache: 'no_cache', -})(async (_, args) => { +})((_, args) => { return !('email' in args) }) const publicRegistration = rule()( - async (_parent, _args, context: Context) => context.config.PUBLIC_REGISTRATION, + (_parent, _args, context: Context) => context.config.PUBLIC_REGISTRATION, ) -const inviteRegistration = rule()(async (_parent, args, context: Context) => { +const inviteRegistration = rule()((_parent, args, context: Context) => { if (!context.config.INVITE_REGISTRATION) return false const { inviteCode } = args return validateInviteCode(context, inviteCode) diff --git a/backend/src/middleware/slugify/uniqueSlug.ts b/backend/src/middleware/slugify/uniqueSlug.ts index ace6d49b7..9d238f71f 100644 --- a/backend/src/middleware/slugify/uniqueSlug.ts +++ b/backend/src/middleware/slugify/uniqueSlug.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-shadow */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ import slugify from 'slugify' slugify.extend({ Ä: 'AE', ä: 'ae', Ö: 'OE', ö: 'oe', Ü: 'UE', ü: 'ue', ß: 'ss' }) @@ -16,7 +15,7 @@ export default async function uniqueSlug(str: string, isUnique: IsUnique) { let uniqueSlug: string do { count += 1 - uniqueSlug = `${slug}-${count}` + uniqueSlug = `${slug}-${String(count)}` } while (!(await isUnique(uniqueSlug))) return uniqueSlug } diff --git a/backend/src/middleware/softDelete/softDeleteMiddleware.ts b/backend/src/middleware/softDelete/softDeleteMiddleware.ts index 6f277e7d6..0ea49bad2 100644 --- a/backend/src/middleware/softDelete/softDeleteMiddleware.ts +++ b/backend/src/middleware/softDelete/softDeleteMiddleware.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-return */ @@ -8,7 +7,7 @@ const isModerator = ({ user }) => { return user && (user.role === 'moderator' || user.role === 'admin') } -const setDefaultFilters: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const setDefaultFilters: IMiddlewareResolver = (resolve, root, args, context, info) => { args.deleted = false if (!isModerator(context)) { @@ -17,7 +16,7 @@ const setDefaultFilters: IMiddlewareResolver = async (resolve, root, args, conte return resolve(root, args, context, info) } -const obfuscate: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const obfuscate: IMiddlewareResolver = (resolve, root, args, context, info) => { if (root.deleted || (!isModerator(context) && root.disabled)) { root.content = 'UNAVAILABLE' root.contentExcerpt = 'UNAVAILABLE' @@ -31,7 +30,7 @@ const obfuscate: IMiddlewareResolver = async (resolve, root, args, context, info return resolve(root, args, context, info) } -const mutationDefaults: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const mutationDefaults: IMiddlewareResolver = (resolve, root, args, context, info) => { args.disabled = false // TODO: remove as soon as our factories don't need this anymore if (typeof args.deleted !== 'boolean') { diff --git a/backend/src/middleware/userInteractions.ts b/backend/src/middleware/userInteractions.ts index 3433d8adb..f79f77598 100644 --- a/backend/src/middleware/userInteractions.ts +++ b/backend/src/middleware/userInteractions.ts @@ -2,16 +2,15 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ import type { IMiddlewareResolver } from 'graphql-middleware/dist/types' const createRelatedCypher = (relation) => ` MATCH (user:User { id: $currentUser}) MATCH (post:Post { id: $postId}) -OPTIONAL MATCH (post)<-[r:${relation}]-(u:User) +OPTIONAL MATCH (post)<-[r:${String(relation)}]-(u:User) WHERE NOT u.disabled AND NOT u.deleted WITH user, post, count(DISTINCT u) AS count -MERGE (user)-[relation:${relation} { }]->(post) +MERGE (user)-[relation:${String(relation)} { }]->(post) ON CREATE SET relation.count = 1, relation.createdAt = toString(datetime()), diff --git a/backend/src/middleware/validation/validationMiddleware.ts b/backend/src/middleware/validation/validationMiddleware.ts index 331c8414c..c6c87539c 100644 --- a/backend/src/middleware/validation/validationMiddleware.ts +++ b/backend/src/middleware/validation/validationMiddleware.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -16,7 +14,7 @@ const validateCreateComment: IMiddlewareResolver = async (resolve, root, args, c const { postId } = args if (!args.content || content.length < COMMENT_MIN_LENGTH) { - throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`) + throw new UserInputError(`Comment must be at least ${String(COMMENT_MIN_LENGTH)} character long!`) } const session = context.driver.session() try { @@ -43,16 +41,16 @@ const validateCreateComment: IMiddlewareResolver = async (resolve, root, args, c } } -const validateUpdateComment: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const validateUpdateComment: IMiddlewareResolver = (resolve, root, args, context, info) => { const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim() if (!args.content || content.length < COMMENT_MIN_LENGTH) { - throw new UserInputError(`Comment must be at least ${COMMENT_MIN_LENGTH} character long!`) + throw new UserInputError(`Comment must be at least ${String(COMMENT_MIN_LENGTH)} character long!`) } return resolve(root, args, context, info) } -const validateReport: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const validateReport: IMiddlewareResolver = (resolve, root, args, context, info) => { const { resourceId } = args const { user } = context if (resourceId === user.id) throw new Error('You cannot report yourself!') @@ -92,14 +90,14 @@ const validateReview: IMiddlewareResolver = async (resolve, root, args, context, existingReportedResource = existingReportedResource[0] if (!existingReportedResource.filed) throw new Error( - `Before starting the review process, please report the ${existingReportedResource.label}!`, + `Before starting the review process, please report the ${String(existingReportedResource.label)}!`, ) const authorId = existingReportedResource.label !== 'User' && existingReportedResource.author ? existingReportedResource.author.properties.id : null if (authorId && authorId === user.id) - throw new Error(`You cannot review your own ${existingReportedResource.label}!`) + throw new Error(`You cannot review your own ${String(existingReportedResource.label)}!`) } finally { await session.close() } @@ -107,7 +105,7 @@ const validateReview: IMiddlewareResolver = async (resolve, root, args, context, return resolve(root, args, context, info) } -export const validateNotifyUsers = async (label: string, reason: string): Promise => { +export const validateNotifyUsers = (label: string, reason: string): void => { const reasonsAllowed = [ 'mentioned_in_post', 'mentioned_in_comment', @@ -124,10 +122,10 @@ export const validateNotifyUsers = async (label: string, reason: string): Promis } } -const validateUpdateUser: IMiddlewareResolver = async (resolve, root, params, context, info) => { +const validateUpdateUser: IMiddlewareResolver = (resolve, root, params, context, info) => { const { name } = params if (typeof name === 'string' && name.trim().length < USERNAME_MIN_LENGTH) - throw new UserInputError(`Username must be at least ${USERNAME_MIN_LENGTH} character long!`) + throw new UserInputError(`Username must be at least ${String(USERNAME_MIN_LENGTH)} character long!`) return resolve(root, params, context, info) } diff --git a/backend/src/middleware/xssMiddleware.ts b/backend/src/middleware/xssMiddleware.ts index e7863efb2..0b9d58bbf 100644 --- a/backend/src/middleware/xssMiddleware.ts +++ b/backend/src/middleware/xssMiddleware.ts @@ -1,7 +1,6 @@ /* eslint-disable security/detect-object-injection */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable promise/prefer-await-to-callbacks */ -/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ @@ -47,7 +46,7 @@ const fields = [ { field: 'descriptionExcerpt' }, ] -const mutationXss: IMiddlewareResolver = async (resolve, root, args, context, info) => { +const mutationXss: IMiddlewareResolver = (resolve, root, args, context, info) => { args = walkRecursive(args, fields, info.fieldName, cleanHtml) return resolve(root, args, context, info) } diff --git a/backend/src/server.ts b/backend/src/server.ts index 9909da75e..e10106939 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -102,8 +102,7 @@ const createServer = async (options?: CreateServerOptions) => { plugins: [ ApolloServerPluginDrainHttpServer({ httpServer }), { - // eslint-disable-next-line @typescript-eslint/require-await - async serverWillStart() { + serverWillStart() { return { async drainServer() { await serverCleanup.dispose()