mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-04-06 01:25:38 +00:00
fix easy types
This commit is contained in:
parent
fc5d3aca8e
commit
a3db2ae89e
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
|
|
||||||
import { hashSync } from 'bcryptjs'
|
import { hashSync } from 'bcryptjs'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
@ -18,7 +17,7 @@ const defaultAdmin = {
|
|||||||
const createDefaultAdminUser = async () => {
|
const createDefaultAdminUser = async () => {
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
const createAdminTxResultPromise = session.writeTransaction(async (txc) => {
|
const createAdminTxResultPromise = session.writeTransaction((txc) => {
|
||||||
txc.run(
|
txc.run(
|
||||||
`MERGE (e:EmailAddress {
|
`MERGE (e:EmailAddress {
|
||||||
email: "${defaultAdmin.email}",
|
email: "${defaultAdmin.email}",
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
/* 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-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* 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)
|
swap(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(next) {
|
export function down(next) {
|
||||||
swap(next)
|
swap(next)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* 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
|
// eslint-disable-next-line no-console
|
||||||
console.log('Irreversible migration')
|
console.log('Irreversible migration')
|
||||||
next()
|
next()
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* 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
|
// eslint-disable-next-line no-console
|
||||||
console.log('Irreversible migration')
|
console.log('Irreversible migration')
|
||||||
next()
|
next()
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* 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')
|
throw new Error('Irreversible migration')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-base-to-string */
|
/* eslint-disable @typescript-eslint/no-base-to-string */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
|
|
||||||
import { getDriver } from '@db/neo4j'
|
import { getDriver } from '@db/neo4j'
|
||||||
|
|
||||||
@ -31,9 +30,9 @@ export async function up(_next) {
|
|||||||
eventEnd = date.toISOString()
|
eventEnd = date.toISOString()
|
||||||
}
|
}
|
||||||
await transaction.run(`
|
await transaction.run(`
|
||||||
MATCH (e:Event { id: '${id}' })
|
MATCH (e:Event { id: '${String(id)}' })
|
||||||
SET e.eventStart = '${eventStart}'
|
SET e.eventStart = '${String(eventStart)}'
|
||||||
SET (CASE WHEN exists(e.eventEnd) THEN e END).eventEnd = '${eventEnd}'
|
SET (CASE WHEN exists(e.eventEnd) THEN e END).eventEnd = '${String(eventEnd)}'
|
||||||
RETURN e
|
RETURN e
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
|
|
||||||
import CONFIG from '@config/index'
|
import CONFIG from '@config/index'
|
||||||
@ -16,7 +15,7 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
|||||||
process.exit(0)
|
process.exit(0)
|
||||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||||
} catch (err) {
|
} 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)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
|
|
||||||
import CONFIG from '@config/index'
|
import CONFIG from '@config/index'
|
||||||
@ -16,7 +15,7 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
|
|||||||
process.exit(0)
|
process.exit(0)
|
||||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||||
} catch (err) {
|
} 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)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
/* eslint-disable n/no-unpublished-import */
|
/* eslint-disable n/no-unpublished-import */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
|
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
|
||||||
|
|
||||||
import { faker } from '@faker-js/faker'
|
import { faker } from '@faker-js/faker'
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* 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-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
@ -29,7 +28,7 @@ export const defaultVerificationBadge = {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
Badge: async (object, args, context, resolveInfo) =>
|
Badge: (object, args, context, resolveInfo) =>
|
||||||
neo4jgraphql(object, args, context, resolveInfo),
|
neo4jgraphql(object, args, context, resolveInfo),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -172,7 +171,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Badge: {
|
Badge: {
|
||||||
isDefault: async (parent, _params, _context, _resolveInfo) =>
|
isDefault: (parent, _params, _context, _resolveInfo) =>
|
||||||
[defaultTrophyBadge.id, defaultVerificationBadge.id].includes(parent.id),
|
[defaultTrophyBadge.id, defaultVerificationBadge.id].includes(parent.id),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
import scrape from './embeds/scraper'
|
import scrape from './embeds/scraper'
|
||||||
@ -6,7 +5,7 @@ import { undefinedToNullResolver } from './helpers/Resolver'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
embed: async (_object, { url }, _context, _resolveInfo) => {
|
embed: (_object, { url }, _context, _resolveInfo) => {
|
||||||
return scrape(url)
|
return scrape(url)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -25,7 +24,7 @@ export default {
|
|||||||
'lang',
|
'lang',
|
||||||
'html',
|
'html',
|
||||||
]),
|
]),
|
||||||
sources: async (parent, _params, _context, _resolveInfo) => {
|
sources: (parent, _params, _context, _resolveInfo) => {
|
||||||
return typeof parent.sources === 'undefined' ? [] : parent.sources
|
return typeof parent.sources === 'undefined' ? [] : parent.sources
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* 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-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
@ -53,7 +52,7 @@ const fetchEmbed = async (url) => {
|
|||||||
json = await response.json()
|
json = await response.json()
|
||||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error(`Error fetching embed data: ${err.message}`)
|
error(`Error fetching embed data: ${String(err.message)}`)
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
@ -6,7 +5,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||||
/* eslint-disable @typescript-eslint/no-shadow */
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
import { CATEGORIES_MIN, CATEGORIES_MAX } from '@constants/categories'
|
import { CATEGORIES_MIN, CATEGORIES_MAX } from '@constants/categories'
|
||||||
@ -20,6 +18,35 @@ import { createOrUpdateLocations } from './users/location'
|
|||||||
|
|
||||||
import type { Context } from '@src/context'
|
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 {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
Group: async (_object, params, context: Context, _resolveInfo) => {
|
Group: async (_object, params, context: Context, _resolveInfo) => {
|
||||||
@ -488,13 +515,13 @@ export default {
|
|||||||
membersCount: '<-[:MEMBER_OF]-(related:User)',
|
membersCount: '<-[:MEMBER_OF]-(related:User)',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
name: async (parent, _args, context: Context, _resolveInfo) => {
|
name: (parent, _args, context: Context, _resolveInfo) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
return parent.groupType === 'hidden' ? '' : parent.name
|
return parent.groupType === 'hidden' ? '' : parent.name
|
||||||
}
|
}
|
||||||
return parent.name
|
return parent.name
|
||||||
},
|
},
|
||||||
about: async (parent, _args, context: Context, _resolveInfo) => {
|
about: (parent, _args, context: Context, _resolveInfo) => {
|
||||||
if (!context.user) {
|
if (!context.user) {
|
||||||
return parent.groupType === 'hidden' ? '' : parent.about
|
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-dynamic-delete */
|
/* eslint-disable @typescript-eslint/no-dynamic-delete */
|
||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* 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-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
@ -11,7 +9,7 @@
|
|||||||
export const undefinedToNullResolver = (list) => {
|
export const undefinedToNullResolver = (list) => {
|
||||||
const resolvers = {}
|
const resolvers = {}
|
||||||
list.forEach((key) => {
|
list.forEach((key) => {
|
||||||
resolvers[key] = async (parent) => {
|
resolvers[key] = (parent) => {
|
||||||
return typeof parent[key] === 'undefined' ? null : parent[key]
|
return typeof parent[key] === 'undefined' ? null : parent[key]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -37,7 +35,7 @@ export default function Resolver(type, options: any = {}) {
|
|||||||
try {
|
try {
|
||||||
let response = await session.readTransaction(async (txc) => {
|
let response = await session.readTransaction(async (txc) => {
|
||||||
const cypher = `
|
const cypher = `
|
||||||
MATCH(:${type} {${idAttribute}: $id})${connection}
|
MATCH(:${String(type)} {${String(idAttribute)}: $id})${String(connection)}
|
||||||
RETURN related {.*} as related
|
RETURN related {.*} as related
|
||||||
`
|
`
|
||||||
const result = await txc.run(cypher, { id, cypherParams })
|
const result = await txc.run(cypher, { id, cypherParams })
|
||||||
@ -62,7 +60,7 @@ export default function Resolver(type, options: any = {}) {
|
|||||||
try {
|
try {
|
||||||
return await session.readTransaction(async (txc) => {
|
return await session.readTransaction(async (txc) => {
|
||||||
const nodeCondition = condition.replace('this', 'this {id: $id}')
|
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 result = await txc.run(cypher, { id, cypherParams })
|
||||||
const [response] = result.records.map((r) => r.get(key))
|
const [response] = result.records.map((r) => r.get(key))
|
||||||
return response
|
return response
|
||||||
@ -85,7 +83,7 @@ export default function Resolver(type, options: any = {}) {
|
|||||||
return await session.readTransaction(async (txc) => {
|
return await session.readTransaction(async (txc) => {
|
||||||
const id = parent[idAttribute]
|
const id = parent[idAttribute]
|
||||||
const cypher = `
|
const cypher = `
|
||||||
MATCH(u:${type} {${idAttribute}: $id})${connection}
|
MATCH(u:${String(type)} {${String(idAttribute)}: $id})${String(connection)}
|
||||||
RETURN COUNT(DISTINCT(related)) as count
|
RETURN COUNT(DISTINCT(related)) as count
|
||||||
`
|
`
|
||||||
const result = await txc.run(cypher, { id, cypherParams })
|
const result = await txc.run(cypher, { id, cypherParams })
|
||||||
@ -141,7 +139,7 @@ export const convertObjectToCypherMapLiteral = (params, addSpaceInfrontIfMapIsNo
|
|||||||
let mapLiteral = ''
|
let mapLiteral = ''
|
||||||
paramsEntries.forEach((ele, index) => {
|
paramsEntries.forEach((ele, index) => {
|
||||||
mapLiteral += index === 0 ? '{' : ''
|
mapLiteral += index === 0 ? '{' : ''
|
||||||
mapLiteral += `${ele[0]}: "${ele[1]}"`
|
mapLiteral += `${ele[0]}: "${String(ele[1])}"`
|
||||||
mapLiteral += index < paramsEntries.length - 1 ? ', ' : '}'
|
mapLiteral += index < paramsEntries.length - 1 ? ', ' : '}'
|
||||||
})
|
})
|
||||||
mapLiteral = (addSpaceInfrontIfMapIsNotEmpty && mapLiteral.length > 0 ? ' ' : '') + mapLiteral
|
mapLiteral = (addSpaceInfrontIfMapIsNotEmpty && mapLiteral.length > 0 ? ' ' : '') + mapLiteral
|
||||||
|
|||||||
@ -2,9 +2,31 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
||||||
import { UserInputError } from '@graphql/errors'
|
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) => {
|
export const validateEventParams = (params) => {
|
||||||
let locationName = null
|
let locationName = null
|
||||||
if (params.postType && params.postType === 'Event') {
|
if (params.postType && params.postType === 'Event') {
|
||||||
@ -34,26 +56,3 @@ export const validateEventParams = (params) => {
|
|||||||
delete params.eventInput
|
delete params.eventInput
|
||||||
return locationName
|
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!')
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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 crypto from 'node:crypto'
|
||||||
import { join as joinPath } from 'node:path/posix'
|
import { join as joinPath } from 'node:path/posix'
|
||||||
|
|
||||||
@ -15,6 +13,16 @@ type UrlResolver = (
|
|||||||
}: Pick<Context, 'config'>,
|
}: Pick<Context, 'config'>,
|
||||||
) => string
|
) => 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 =
|
const pointUrlToImagor: (opts: { transformations: UrlResolver[] }) => UrlResolver =
|
||||||
({ transformations }) =>
|
({ transformations }) =>
|
||||||
({ url }, _args, context) => {
|
({ url }, _args, context) => {
|
||||||
@ -59,22 +67,12 @@ const resize: UrlResolver = ({ url }, { height, width }) => {
|
|||||||
if (!(height || width)) {
|
if (!(height || width)) {
|
||||||
return url
|
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)
|
const newUrl = new URL(url)
|
||||||
newUrl.pathname = window + newUrl.pathname
|
newUrl.pathname = window + newUrl.pathname
|
||||||
return newUrl.href
|
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 {
|
export default {
|
||||||
Image: {
|
Image: {
|
||||||
...Resolver('Image', {
|
...Resolver('Image', {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
||||||
/* eslint-disable @typescript-eslint/no-shadow */
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
@ -41,6 +40,17 @@ export const images = (config: S3Config) => {
|
|||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadImageFile = async (uploadPromise: Promise<FileUpload> | 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 (
|
const mergeImage: Images['mergeImage'] = async (
|
||||||
resource,
|
resource,
|
||||||
relationshipType,
|
relationshipType,
|
||||||
@ -84,17 +94,6 @@ export const images = (config: S3Config) => {
|
|||||||
return mergedImage
|
return mergedImage
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadImageFile = async (uploadPromise: Promise<FileUpload> | 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 = {
|
const images = {
|
||||||
deleteImage,
|
deleteImage,
|
||||||
mergeImage,
|
mergeImage,
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
@ -16,7 +15,7 @@ import Resolver from './helpers/Resolver'
|
|||||||
|
|
||||||
import type { File } from './attachments/attachments'
|
import type { File } from './attachments/attachments'
|
||||||
|
|
||||||
const setMessagesAsDistributed = async (undistributedMessagesIds, session) => {
|
const setMessagesAsDistributed = (undistributedMessagesIds, session) => {
|
||||||
return session.writeTransaction(async (transaction) => {
|
return session.writeTransaction(async (transaction) => {
|
||||||
const setDistributedCypher = `
|
const setDistributedCypher = `
|
||||||
MATCH (m:Message) WHERE m.id IN $undistributedMessagesIds
|
MATCH (m:Message) WHERE m.id IN $undistributedMessagesIds
|
||||||
|
|||||||
@ -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-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
@ -45,8 +43,8 @@ export default {
|
|||||||
default:
|
default:
|
||||||
orderByClause = ''
|
orderByClause = ''
|
||||||
}
|
}
|
||||||
const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : ''
|
const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${String(args.offset)}` : ''
|
||||||
const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : ''
|
const limit = args.first && typeof args.first === 'number' ? `LIMIT ${String(args.first)}` : ''
|
||||||
|
|
||||||
const readTxResultPromise = session.readTransaction(async (transaction) => {
|
const readTxResultPromise = session.readTransaction(async (transaction) => {
|
||||||
const notificationsTransactionResponse = await transaction.run(
|
const notificationsTransactionResponse = await transaction.run(
|
||||||
@ -145,9 +143,9 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
NOTIFIED: {
|
NOTIFIED: {
|
||||||
id: async (parent) => {
|
id: (parent) => {
|
||||||
// serialize an ID to help the client update the cache
|
// 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)}`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* 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-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
@ -186,7 +185,7 @@ export default {
|
|||||||
SET post.sortDate = toString(datetime())
|
SET post.sortDate = toString(datetime())
|
||||||
SET post.clickedCount = 0
|
SET post.clickedCount = 0
|
||||||
SET post.viewedTeaserCount = 0
|
SET post.viewedTeaserCount = 0
|
||||||
SET post:${params.postType}
|
SET post:${String(params.postType)}
|
||||||
WITH post
|
WITH post
|
||||||
MATCH (author:User {id: $userId})
|
MATCH (author:User {id: $userId})
|
||||||
MERGE (post)<-[:WROTE]-(author)
|
MERGE (post)<-[:WROTE]-(author)
|
||||||
@ -260,7 +259,7 @@ export default {
|
|||||||
updatePostCypher += `
|
updatePostCypher += `
|
||||||
REMOVE post:Article
|
REMOVE post:Article
|
||||||
REMOVE post:Event
|
REMOVE post:Event
|
||||||
SET post:${params.postType}
|
SET post:${String(params.postType)}
|
||||||
WITH post
|
WITH post
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
@ -80,8 +79,8 @@ export default {
|
|||||||
const filterClause = filterClauses.join(' ')
|
const filterClause = filterClauses.join(' ')
|
||||||
|
|
||||||
const offset =
|
const offset =
|
||||||
params.offset && typeof params.offset === 'number' ? `SKIP ${params.offset}` : ''
|
params.offset && typeof params.offset === 'number' ? `SKIP ${String(params.offset)}` : ''
|
||||||
const limit = params.first && typeof params.first === 'number' ? `LIMIT ${params.first}` : ''
|
const limit = params.first && typeof params.first === 'number' ? `LIMIT ${String(params.first)}` : ''
|
||||||
|
|
||||||
const reportsReadTxPromise = session.readTransaction(async (transaction) => {
|
const reportsReadTxPromise = session.readTransaction(async (transaction) => {
|
||||||
const reportsTransactionResponse = await transaction.run(
|
const reportsTransactionResponse = await transaction.run(
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
availableRoles: async (_parent, _args, _context, _resolveInfo) => {
|
availableRoles: (_parent, _args, _context, _resolveInfo) => {
|
||||||
return ['admin', 'moderator', 'user']
|
return ['admin', 'moderator', 'user']
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
@ -38,7 +37,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Query: {
|
Query: {
|
||||||
Room: async (object, params, context, resolveInfo) => {
|
Room: (object, params, context, resolveInfo) => {
|
||||||
if (!params.filter) params.filter = {}
|
if (!params.filter) params.filter = {}
|
||||||
params.filter.users_some = {
|
params.filter.users_some = {
|
||||||
id: context.user.id,
|
id: context.user.id,
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* 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-member-access */
|
||||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||||
import { queryString } from './searches/queryString'
|
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
|
// see http://lucene.apache.org/core/8_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description
|
||||||
|
|
||||||
const cypherTemplate = (setup) => `
|
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
|
YIELD node AS resource, score
|
||||||
${setup.match}
|
${String(setup.match)}
|
||||||
${setup.whereClause}
|
${String(setup.whereClause)}
|
||||||
${setup.withClause}
|
${String(setup.withClause)}
|
||||||
RETURN
|
RETURN
|
||||||
${setup.returnClause}
|
${String(setup.returnClause)}
|
||||||
AS result
|
AS result
|
||||||
SKIP toInteger($skip)
|
SKIP toInteger($skip)
|
||||||
${setup.limit}
|
${String(setup.limit)}
|
||||||
`
|
`
|
||||||
|
|
||||||
const simpleWhereClause =
|
const simpleWhereClause =
|
||||||
@ -148,7 +146,7 @@ const multiSearchMap = [
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
searchPosts: async (_parent, args, context, _resolveInfo) => {
|
searchPosts: (_parent, args, context, _resolveInfo) => {
|
||||||
const { query, postsOffset, firstPosts } = args
|
const { query, postsOffset, firstPosts } = args
|
||||||
let userId = null
|
let userId = null
|
||||||
if (context.user) userId = context.user.id
|
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
|
const { query, usersOffset, firstUsers } = args
|
||||||
return {
|
return {
|
||||||
userCount: getSearchResults(
|
userCount: getSearchResults(
|
||||||
@ -190,7 +188,7 @@ export default {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchHashtags: async (_parent, args, context, _resolveInfo) => {
|
searchHashtags: (_parent, args, context, _resolveInfo) => {
|
||||||
const { query, hashtagsOffset, firstHashtags } = args
|
const { query, hashtagsOffset, firstHashtags } = args
|
||||||
return {
|
return {
|
||||||
hashtagCount: getSearchResults(
|
hashtagCount: getSearchResults(
|
||||||
@ -209,7 +207,7 @@ export default {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchGroups: async (_parent, args, context, _resolveInfo) => {
|
searchGroups: (_parent, args, context, _resolveInfo) => {
|
||||||
const { query, groupsOffset, firstGroups } = args
|
const { query, groupsOffset, firstGroups } = args
|
||||||
let userId = null
|
let userId = null
|
||||||
if (context.user) userId = context.user.id
|
if (context.user) userId = context.user.id
|
||||||
|
|||||||
@ -2,48 +2,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* 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-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) {
|
export function normalizeWhitespace(str) {
|
||||||
// delete the first character if it is !, @ or #
|
// delete the first character if it is !, @ or #
|
||||||
@ -56,3 +15,43 @@ export function normalizeWhitespace(str) {
|
|||||||
export function escapeSpecialCharacters(str) {
|
export function escapeSpecialCharacters(str) {
|
||||||
return str.replace(/(["[\]&|\\{}+!()^~*?:/-])/g, '\\$1')
|
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)}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|||||||
@ -2,7 +2,13 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* 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 {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
userData: async (_object, _args, context, _resolveInfo) => {
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
@ -215,11 +214,11 @@ export default {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
resource.map(async (node) => {
|
resource.map(async (node) => {
|
||||||
if (!allowedLabels.includes(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(
|
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)
|
OPTIONAL MATCH (resource)<-[:COMMENTS]-(comment:Comment)
|
||||||
SET resource.deleted = true
|
SET resource.deleted = true
|
||||||
SET resource.content = 'UNAVAILABLE'
|
SET resource.content = 'UNAVAILABLE'
|
||||||
@ -371,7 +370,7 @@ export default {
|
|||||||
|
|
||||||
if (slot >= TROPHY_BADGES_SELECTED_MAX || slot < 0) {
|
if (slot >= TROPHY_BADGES_SELECTED_MAX || slot < 0) {
|
||||||
throw new Error(
|
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`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* 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-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
@ -19,7 +18,7 @@ const REQUEST_TIMEOUT = 3000
|
|||||||
|
|
||||||
const createLocation = async (session, mapboxData) => {
|
const createLocation = async (session, mapboxData) => {
|
||||||
const data = {
|
const data = {
|
||||||
id: mapboxData.id + (mapboxData.address ? `-${mapboxData.address}` : ''),
|
id: mapboxData.id + (mapboxData.address ? `-${String(mapboxData.address)}` : ''),
|
||||||
nameEN: mapboxData.text_en,
|
nameEN: mapboxData.text_en,
|
||||||
nameDE: mapboxData.text_de,
|
nameDE: mapboxData.text_de,
|
||||||
nameFR: mapboxData.text_fr,
|
nameFR: mapboxData.text_fr,
|
||||||
@ -77,9 +76,9 @@ export const createOrUpdateLocations = async (
|
|||||||
if (locationName !== null) {
|
if (locationName !== null) {
|
||||||
const response: any = await fetch(
|
const response: any = await fetch(
|
||||||
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(
|
||||||
locationName,
|
String(locationName),
|
||||||
)}.json?access_token=${
|
)}.json?access_token=${
|
||||||
context.config.MAPBOX_TOKEN
|
String(context.config.MAPBOX_TOKEN)
|
||||||
}&types=region,place,country,address&language=${locales.join(',')}`,
|
}&types=region,place,country,address&language=${locales.join(',')}`,
|
||||||
{
|
{
|
||||||
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
||||||
@ -115,7 +114,7 @@ export const createOrUpdateLocations = async (
|
|||||||
let parent = data
|
let parent = data
|
||||||
|
|
||||||
if (parent.address) {
|
if (parent.address) {
|
||||||
parent.id += `-${parent.address}`
|
parent.id += `-${String(parent.address)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.context) {
|
if (data.context) {
|
||||||
@ -147,7 +146,7 @@ export const createOrUpdateLocations = async (
|
|||||||
await session.writeTransaction((transaction) => {
|
await session.writeTransaction((transaction) => {
|
||||||
return transaction.run(
|
return transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (node:${nodeLabel} {id: $nodeId})
|
MATCH (node:${String(nodeLabel)} {id: $nodeId})
|
||||||
OPTIONAL MATCH (node)-[relationship:IS_IN]->(:Location)
|
OPTIONAL MATCH (node)-[relationship:IS_IN]->(:Location)
|
||||||
DELETE relationship
|
DELETE relationship
|
||||||
WITH node
|
WITH node
|
||||||
@ -162,7 +161,7 @@ export const createOrUpdateLocations = async (
|
|||||||
|
|
||||||
export const queryLocations = async ({ place, lang }, context: Context) => {
|
export const queryLocations = async ({ place, lang }, context: Context) => {
|
||||||
const res: any = await fetch(
|
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),
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* 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'
|
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
|
args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html
|
||||||
return resolve(root, args, context, info)
|
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)
|
if (args.description)
|
||||||
args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html
|
args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html
|
||||||
return resolve(root, args, context, info)
|
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
|
args.contentExcerpt = trunc(args.content, 120).html
|
||||||
return resolve(root, args, context, info)
|
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
|
args.contentExcerpt = trunc(args.content, 120).html
|
||||||
return resolve(root, args, context, info)
|
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
|
args.contentExcerpt = trunc(args.content, 180).html
|
||||||
return resolve(root, args, context, info)
|
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
|
args.contentExcerpt = trunc(args.content, 180).html
|
||||||
return resolve(root, args, context, info)
|
return resolve(root, args, context, info)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable security/detect-object-injection */
|
/* 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/prefer-nullish-coalescing */
|
||||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
import {
|
import {
|
||||||
@ -43,136 +42,6 @@ const publishNotifications = async (
|
|||||||
return emailsSent
|
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 postAuthorOfComment = async (commentId, { context }) => {
|
||||||
const session = context.driver.session()
|
const session = context.driver.session()
|
||||||
let postAuthorId
|
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 (
|
const handleCreateMessage: IMiddlewareResolver = async (
|
||||||
resolve,
|
resolve,
|
||||||
root,
|
root,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* 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-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-type-conversion */
|
/* eslint-disable @typescript-eslint/no-unnecessary-type-conversion */
|
||||||
@ -22,27 +21,27 @@ const neode = getNeode()
|
|||||||
|
|
||||||
const isAuthenticated = rule({
|
const isAuthenticated = rule({
|
||||||
cache: 'contextual',
|
cache: 'contextual',
|
||||||
})(async (_parent, _args, ctx, _info) => {
|
})((_parent, _args, ctx, _info) => {
|
||||||
return !!ctx?.user?.id
|
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'))
|
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')
|
return !!(user?.role === 'admin')
|
||||||
})
|
})
|
||||||
|
|
||||||
const onlyYourself = rule({
|
const onlyYourself = rule({
|
||||||
cache: 'no_cache',
|
cache: 'no_cache',
|
||||||
})(async (_parent, args, context: Context, _info) => {
|
})((_parent, args, context: Context, _info) => {
|
||||||
return context.user?.id === args.id
|
return context.user?.id === args.id
|
||||||
})
|
})
|
||||||
|
|
||||||
const isMyOwn = rule({
|
const isMyOwn = rule({
|
||||||
cache: 'no_cache',
|
cache: 'no_cache',
|
||||||
})(async (parent, _args, { user }: Context, _info) => {
|
})((parent, _args, { user }: Context, _info) => {
|
||||||
return !!(user && user.id === parent.id)
|
return !!(user && user.id === parent.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -362,21 +361,21 @@ const isAuthor = rule({
|
|||||||
|
|
||||||
const isDeletingOwnAccount = rule({
|
const isDeletingOwnAccount = rule({
|
||||||
cache: 'no_cache',
|
cache: 'no_cache',
|
||||||
})(async (_parent, args, context: Context, _info) => {
|
})((_parent, args, context: Context, _info) => {
|
||||||
return context.user?.id === args.id
|
return context.user?.id === args.id
|
||||||
})
|
})
|
||||||
|
|
||||||
const noEmailFilter = rule({
|
const noEmailFilter = rule({
|
||||||
cache: 'no_cache',
|
cache: 'no_cache',
|
||||||
})(async (_, args) => {
|
})((_, args) => {
|
||||||
return !('email' in args)
|
return !('email' in args)
|
||||||
})
|
})
|
||||||
|
|
||||||
const publicRegistration = rule()(
|
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
|
if (!context.config.INVITE_REGISTRATION) return false
|
||||||
const { inviteCode } = args
|
const { inviteCode } = args
|
||||||
return validateInviteCode(context, inviteCode)
|
return validateInviteCode(context, inviteCode)
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-shadow */
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
import slugify from 'slugify'
|
import slugify from 'slugify'
|
||||||
|
|
||||||
slugify.extend({ Ä: 'AE', ä: 'ae', Ö: 'OE', ö: 'oe', Ü: 'UE', ü: 'ue', ß: 'ss' })
|
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
|
let uniqueSlug: string
|
||||||
do {
|
do {
|
||||||
count += 1
|
count += 1
|
||||||
uniqueSlug = `${slug}-${count}`
|
uniqueSlug = `${slug}-${String(count)}`
|
||||||
} while (!(await isUnique(uniqueSlug)))
|
} while (!(await isUnique(uniqueSlug)))
|
||||||
return uniqueSlug
|
return uniqueSlug
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/require-await */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
@ -8,7 +7,7 @@ const isModerator = ({ user }) => {
|
|||||||
return user && (user.role === 'moderator' || user.role === 'admin')
|
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
|
args.deleted = false
|
||||||
|
|
||||||
if (!isModerator(context)) {
|
if (!isModerator(context)) {
|
||||||
@ -17,7 +16,7 @@ const setDefaultFilters: IMiddlewareResolver = async (resolve, root, args, conte
|
|||||||
return resolve(root, args, context, info)
|
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)) {
|
if (root.deleted || (!isModerator(context) && root.disabled)) {
|
||||||
root.content = 'UNAVAILABLE'
|
root.content = 'UNAVAILABLE'
|
||||||
root.contentExcerpt = 'UNAVAILABLE'
|
root.contentExcerpt = 'UNAVAILABLE'
|
||||||
@ -31,7 +30,7 @@ const obfuscate: IMiddlewareResolver = async (resolve, root, args, context, info
|
|||||||
return 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
|
args.disabled = false
|
||||||
// TODO: remove as soon as our factories don't need this anymore
|
// TODO: remove as soon as our factories don't need this anymore
|
||||||
if (typeof args.deleted !== 'boolean') {
|
if (typeof args.deleted !== 'boolean') {
|
||||||
|
|||||||
@ -2,16 +2,15 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
import type { IMiddlewareResolver } from 'graphql-middleware/dist/types'
|
import type { IMiddlewareResolver } from 'graphql-middleware/dist/types'
|
||||||
|
|
||||||
const createRelatedCypher = (relation) => `
|
const createRelatedCypher = (relation) => `
|
||||||
MATCH (user:User { id: $currentUser})
|
MATCH (user:User { id: $currentUser})
|
||||||
MATCH (post:Post { id: $postId})
|
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
|
WHERE NOT u.disabled AND NOT u.deleted
|
||||||
WITH user, post, count(DISTINCT u) AS count
|
WITH user, post, count(DISTINCT u) AS count
|
||||||
MERGE (user)-[relation:${relation} { }]->(post)
|
MERGE (user)-[relation:${String(relation)} { }]->(post)
|
||||||
ON CREATE
|
ON CREATE
|
||||||
SET relation.count = 1,
|
SET relation.count = 1,
|
||||||
relation.createdAt = toString(datetime()),
|
relation.createdAt = toString(datetime()),
|
||||||
|
|||||||
@ -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-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
@ -16,7 +14,7 @@ const validateCreateComment: IMiddlewareResolver = async (resolve, root, args, c
|
|||||||
const { postId } = args
|
const { postId } = args
|
||||||
|
|
||||||
if (!args.content || content.length < COMMENT_MIN_LENGTH) {
|
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()
|
const session = context.driver.session()
|
||||||
try {
|
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()
|
const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim()
|
||||||
if (!args.content || content.length < COMMENT_MIN_LENGTH) {
|
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)
|
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 { resourceId } = args
|
||||||
const { user } = context
|
const { user } = context
|
||||||
if (resourceId === user.id) throw new Error('You cannot report yourself!')
|
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]
|
existingReportedResource = existingReportedResource[0]
|
||||||
if (!existingReportedResource.filed)
|
if (!existingReportedResource.filed)
|
||||||
throw new Error(
|
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 =
|
const authorId =
|
||||||
existingReportedResource.label !== 'User' && existingReportedResource.author
|
existingReportedResource.label !== 'User' && existingReportedResource.author
|
||||||
? existingReportedResource.author.properties.id
|
? existingReportedResource.author.properties.id
|
||||||
: null
|
: null
|
||||||
if (authorId && authorId === user.id)
|
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 {
|
} finally {
|
||||||
await session.close()
|
await session.close()
|
||||||
}
|
}
|
||||||
@ -107,7 +105,7 @@ const validateReview: IMiddlewareResolver = async (resolve, root, args, context,
|
|||||||
return resolve(root, args, context, info)
|
return resolve(root, args, context, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const validateNotifyUsers = async (label: string, reason: string): Promise<void> => {
|
export const validateNotifyUsers = (label: string, reason: string): void => {
|
||||||
const reasonsAllowed = [
|
const reasonsAllowed = [
|
||||||
'mentioned_in_post',
|
'mentioned_in_post',
|
||||||
'mentioned_in_comment',
|
'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
|
const { name } = params
|
||||||
if (typeof name === 'string' && name.trim().length < USERNAME_MIN_LENGTH)
|
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)
|
return resolve(root, params, context, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable security/detect-object-injection */
|
/* eslint-disable security/detect-object-injection */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable promise/prefer-await-to-callbacks */
|
/* 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-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
@ -47,7 +46,7 @@ const fields = [
|
|||||||
{ field: 'descriptionExcerpt' },
|
{ 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)
|
args = walkRecursive(args, fields, info.fieldName, cleanHtml)
|
||||||
return resolve(root, args, context, info)
|
return resolve(root, args, context, info)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,8 +102,7 @@ const createServer = async (options?: CreateServerOptions) => {
|
|||||||
plugins: [
|
plugins: [
|
||||||
ApolloServerPluginDrainHttpServer({ httpServer }),
|
ApolloServerPluginDrainHttpServer({ httpServer }),
|
||||||
{
|
{
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
serverWillStart() {
|
||||||
async serverWillStart() {
|
|
||||||
return {
|
return {
|
||||||
async drainServer() {
|
async drainServer() {
|
||||||
await serverCleanup.dispose()
|
await serverCleanup.dispose()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user