Merge branch 'master' into backend-email-notification-settings

This commit is contained in:
mahula 2025-04-07 11:35:57 +02:00 committed by Ulf Gebhardt
commit 1db7fbbc65
Signed by: ulfgebhardt
GPG Key ID: DA6B843E748679C9
82 changed files with 786 additions and 326 deletions

View File

@ -61,14 +61,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Log in to the Container registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@70b2cdc6480c1a8b86edf1777157f8f437de2166
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
@ -81,7 +81,7 @@ jobs:
type=sha
- name: Build and push Docker images
id: push
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4
with:
context: ${{ matrix.app.context }}
target: ${{ matrix.app.target }}

209
backend/.eslintrc.cjs Normal file
View File

@ -0,0 +1,209 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
root: true,
env: {
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint', 'import', 'n', 'promise', 'security', 'no-catch-all',],
extends: [
'standard',
'eslint:recommended',
'plugin:prettier/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:security/recommended-legacy',
'plugin:@eslint-community/eslint-comments/recommended',
],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: ['./tsconfig.json'],
},
node: true,
},
},
rules: {
'no-catch-all/no-catch-all': 'error',
'no-console': 'error',
camelcase: 'error',
'no-debugger': 'error',
'prettier/prettier': [
'error',
{
htmlWhitespaceSensitivity: 'ignore',
},
],
// import
'import/export': 'error',
// 'import/no-deprecated': 'error',
'import/no-empty-named-blocks': 'error',
// 'import/no-extraneous-dependencies': 'error',
'import/no-mutable-exports': 'error',
'import/no-unused-modules': 'error',
'import/no-named-as-default': 'error',
'import/no-named-as-default-member': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-nodejs-modules': 'off',
'import/unambiguous': 'off', // not compatible with scriptless vue files
'import/default': 'error',
// 'import/named': 'error',
'import/namespace': 'error',
'import/no-absolute-path': 'error',
'import/no-cycle': 'error',
'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error',
// 'import/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
'import/no-self-import': 'error',
'import/no-unresolved': 'error',
'import/no-useless-path-segments': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/consistent-type-specifier-style': 'error',
'import/exports-last': 'off',
'import/extensions': 'error',
'import/first': 'error',
'import/group-exports': 'off',
'import/newline-after-import': 'error',
// 'import/no-anonymous-default-export': 'error',
// 'import/no-default-export': 'error',
'import/no-duplicates': 'error',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
'import/no-unassigned-import': 'error',
// 'import/order': [
// 'error',
// {
// groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
// 'newlines-between': 'always',
// pathGroups: [
// {
// pattern: '@?*/**',
// group: 'external',
// position: 'after',
// },
// {
// pattern: '@/**',
// group: 'external',
// position: 'after',
// },
// ],
// alphabetize: {
// order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
// caseInsensitive: true /* ignore case. Options: [true, false] */,
// },
// distinctGroup: true,
// },
// ],
'import/prefer-default-export': 'off',
// n
'n/handle-callback-err': 'error',
'n/no-callback-literal': 'error',
'n/no-exports-assign': 'error',
// 'n/no-extraneous-import': 'error',
'n/no-extraneous-require': 'error',
'n/no-hide-core-modules': 'error',
'n/no-missing-import': 'off', // not compatible with typescript
'n/no-missing-require': 'error',
'n/no-new-require': 'error',
'n/no-path-concat': 'error',
'n/no-process-exit': 'error',
'n/no-unpublished-bin': 'error',
'n/no-unpublished-import': 'off', // TODO need to exclude seeds
'n/no-unpublished-require': 'error',
'n/no-unsupported-features': ['error', { ignores: ['modules'] }],
'n/no-unsupported-features/es-builtins': 'error',
'n/no-unsupported-features/es-syntax': 'error',
'n/no-unsupported-features/node-builtins': 'error',
'n/process-exit-as-throw': 'error',
'n/shebang': 'error',
//'n/callback-return': 'error',
'n/exports-style': 'error',
'n/file-extension-in-import': 'off',
'n/global-require': 'error',
'n/no-mixed-requires': 'error',
'n/no-process-env': 'error',
'n/no-restricted-import': 'error',
'n/no-restricted-require': 'error',
// 'n/no-sync': 'error',
'n/prefer-global/buffer': 'error',
'n/prefer-global/console': 'error',
'n/prefer-global/process': 'error',
'n/prefer-global/text-decoder': 'error',
'n/prefer-global/text-encoder': 'error',
'n/prefer-global/url': 'error',
'n/prefer-global/url-search-params': 'error',
'n/prefer-promises/dns': 'error',
'n/prefer-promises/fs': 'error',
// promise
'promise/catch-or-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/always-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn',
'promise/prefer-await-to-callbacks': 'error',
'promise/no-multiple-resolved': 'error',
// eslint comments
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
'@eslint-community/eslint-comments/no-restricted-disable': 'error',
'@eslint-community/eslint-comments/no-use': 'off',
'@eslint-community/eslint-comments/require-description': 'off',
},
overrides: [
// only for ts files
{
files: ['*.ts', '*.tsx'],
extends: [
// 'plugin:@typescript-eslint/recommended',
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
// 'plugin:@typescript-eslint/strict',
],
rules: {
// allow explicitly defined dangling promises
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
// ignore prefer-regexp-exec rule to allow string.match(regex)
'@typescript-eslint/prefer-regexp-exec': 'off',
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
'import/unambiguous': 'off',
// this is not compatible with typeorm, due to joined tables can be null, but are not defined as nullable
'@typescript-eslint/no-unnecessary-condition': 'off',
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
// this is to properly reference the referenced project database without requirement of compiling it
// eslint-disable-next-line camelcase
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},
},
{
files: ['*.spec.ts'],
plugins: ['jest'],
env: {
jest: true,
},
rules: {
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'error',
'jest/valid-expect': 'error',
'@typescript-eslint/unbound-method': 'off',
'jest/unbound-method': 'error',
},
},
],
};

View File

@ -1,219 +0,0 @@
module.exports = {
root: true,
env: {
// es6: true,
node: true,
},
/* parserOptions: {
parser: 'babel-eslint'
},*/
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint' /*, 'import', 'n', 'promise'*/],
extends: [
'standard',
// 'eslint:recommended',
'plugin:prettier/recommended',
// 'plugin:import/recommended',
// 'plugin:import/typescript',
// 'plugin:security/recommended',
// 'plugin:@eslint-community/eslint-comments/recommended',
],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: ['./tsconfig.json'],
},
node: true,
},
},
/* rules: {
//'indent': [ 'error', 2 ],
//'quotes': [ "error", "single"],
// 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
> 'no-console': ['error'],
> 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
> 'prettier/prettier': ['error'],
}, */
rules: {
'no-console': 'error',
camelcase: 'error',
'no-debugger': 'error',
'prettier/prettier': [
'error',
{
htmlWhitespaceSensitivity: 'ignore',
},
],
// import
// 'import/export': 'error',
// 'import/no-deprecated': 'error',
// 'import/no-empty-named-blocks': 'error',
// 'import/no-extraneous-dependencies': 'error',
// 'import/no-mutable-exports': 'error',
// 'import/no-unused-modules': 'error',
// 'import/no-named-as-default': 'error',
// 'import/no-named-as-default-member': 'error',
// 'import/no-amd': 'error',
// 'import/no-commonjs': 'error',
// 'import/no-import-module-exports': 'error',
// 'import/no-nodejs-modules': 'off',
// 'import/unambiguous': 'error',
// 'import/default': 'error',
// 'import/named': 'error',
// 'import/namespace': 'error',
// 'import/no-absolute-path': 'error',
// 'import/no-cycle': 'error',
// 'import/no-dynamic-require': 'error',
// 'import/no-internal-modules': 'off',
// 'import/no-relative-packages': 'error',
// 'import/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
// 'import/no-self-import': 'error',
// 'import/no-unresolved': 'error',
// 'import/no-useless-path-segments': 'error',
// 'import/no-webpack-loader-syntax': 'error',
// 'import/consistent-type-specifier-style': 'error',
// 'import/exports-last': 'off',
// 'import/extensions': 'error',
// 'import/first': 'error',
// 'import/group-exports': 'off',
// 'import/newline-after-import': 'error',
// 'import/no-anonymous-default-export': 'error',
// 'import/no-default-export': 'error',
// 'import/no-duplicates': 'error',
// 'import/no-named-default': 'error',
// 'import/no-namespace': 'error',
// 'import/no-unassigned-import': 'error',
// 'import/order': [
// 'error',
// {
// groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
// 'newlines-between': 'always',
// pathGroups: [
// {
// pattern: '@?*/**',
// group: 'external',
// position: 'after',
// },
// {
// pattern: '@/**',
// group: 'external',
// position: 'after',
// },
// ],
// alphabetize: {
// order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
// caseInsensitive: true /* ignore case. Options: [true, false] */,
// },
// distinctGroup: true,
// },
// ],
// 'import/prefer-default-export': 'off',
// n
// 'n/handle-callback-err': 'error',
// 'n/no-callback-literal': 'error',
// 'n/no-exports-assign': 'error',
// 'n/no-extraneous-import': 'error',
// 'n/no-extraneous-require': 'error',
// 'n/no-hide-core-modules': 'error',
// 'n/no-missing-import': 'off', // not compatible with typescript
// 'n/no-missing-require': 'error',
// 'n/no-new-require': 'error',
// 'n/no-path-concat': 'error',
// 'n/no-process-exit': 'error',
// 'n/no-unpublished-bin': 'error',
// 'n/no-unpublished-import': 'off', // TODO need to exclude seeds
// 'n/no-unpublished-require': 'error',
// 'n/no-unsupported-features': ['error', { ignores: ['modules'] }],
// 'n/no-unsupported-features/es-builtins': 'error',
// 'n/no-unsupported-features/es-syntax': 'error',
// 'n/no-unsupported-features/node-builtins': 'error',
// 'n/process-exit-as-throw': 'error',
// 'n/shebang': 'error',
// 'n/callback-return': 'error',
// 'n/exports-style': 'error',
// 'n/file-extension-in-import': 'off',
// 'n/global-require': 'error',
// 'n/no-mixed-requires': 'error',
// 'n/no-process-env': 'error',
// 'n/no-restricted-import': 'error',
// 'n/no-restricted-require': 'error',
// 'n/no-sync': 'error',
// 'n/prefer-global/buffer': 'error',
// 'n/prefer-global/console': 'error',
// 'n/prefer-global/process': 'error',
// 'n/prefer-global/text-decoder': 'error',
// 'n/prefer-global/text-encoder': 'error',
// 'n/prefer-global/url': 'error',
// 'n/prefer-global/url-search-params': 'error',
// 'n/prefer-promises/dns': 'error',
// 'n/prefer-promises/fs': 'error',
// promise
// 'promise/catch-or-return': 'error',
// 'promise/no-return-wrap': 'error',
// 'promise/param-names': 'error',
// 'promise/always-return': 'error',
// 'promise/no-native': 'off',
// 'promise/no-nesting': 'warn',
// 'promise/no-promise-in-callback': 'warn',
// 'promise/no-callback-in-promise': 'warn',
// 'promise/avoid-new': 'warn',
// 'promise/no-new-statics': 'error',
// 'promise/no-return-in-finally': 'warn',
// 'promise/valid-params': 'warn',
// 'promise/prefer-await-to-callbacks': 'error',
// 'promise/no-multiple-resolved': 'error',
// eslint comments
// '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
// '@eslint-community/eslint-comments/no-restricted-disable': 'error',
// '@eslint-community/eslint-comments/no-use': 'off',
// '@eslint-community/eslint-comments/require-description': 'off',
},
overrides: [
// only for ts files
{
files: ['*.ts', '*.tsx'],
extends: [
// 'plugin:@typescript-eslint/recommended',
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
// 'plugin:@typescript-eslint/strict',
],
rules: {
// allow explicitly defined dangling promises
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
// ignore prefer-regexp-exec rule to allow string.match(regex)
'@typescript-eslint/prefer-regexp-exec': 'off',
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
'import/unambiguous': 'off',
// this is not compatible with typeorm, due to joined tables can be null, but are not defined as nullable
'@typescript-eslint/no-unnecessary-condition': 'off',
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
// this is to properly reference the referenced project database without requirement of compiling it
// eslint-disable-next-line camelcase
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
},
},
{
files: ['*.spec.ts'],
plugins: ['jest'],
env: {
jest: true,
},
rules: {
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error',
'jest/no-identical-title': 'error',
'jest/prefer-to-have-length': 'error',
'jest/valid-expect': 'error',
'@typescript-eslint/unbound-method': 'off',
// 'jest/unbound-method': 'error',
},
},
],
};

View File

@ -76,7 +76,7 @@
"metascraper-video": "^5.46.11",
"metascraper-youtube": "^5.46.11",
"migrate": "^2.1.0",
"mime-types": "^2.1.35",
"mime-types": "^3.0.1",
"minimatch": "^9.0.4",
"mustache": "^4.2.0",
"neo4j-driver": "^4.4.11",
@ -95,6 +95,7 @@
"xregexp": "^5.1.2"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
"@faker-js/faker": "9.6.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.14.0",
@ -108,6 +109,7 @@
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-security": "^3.0.1",
@ -121,7 +123,9 @@
},
"resolutions": {
"**/**/fs-capacitor": "^6.2.0",
"**/graphql-upload": "^11.0.0",
"nan": "2.17.0"
"**/graphql-upload": "^11.0.0"
},
"engines": {
"node": ">=20.12.1"
}
}

View File

@ -1,11 +1,14 @@
import dotenv from 'dotenv'
/* eslint-disable n/no-process-env */
/* eslint-disable n/no-unpublished-require */
/* eslint-disable n/no-missing-require */
import { config } from 'dotenv'
import emails from './emails'
import metadata from './metadata'
// Load env file
if (require.resolve) {
try {
dotenv.config({ path: require.resolve('../../.env') })
config({ path: require.resolve('../../.env') })
} catch (error) {
// This error is thrown when the .env is not found
if (error.code !== 'MODULE_NOT_FOUND') {

View File

@ -1,5 +1,6 @@
/* eslint-disable n/no-process-exit */
import CONFIG from '../config'
import { cleanDatabase } from '../db/factories'
import { cleanDatabase } from './factories'
if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
throw new Error(`You cannot clean the database in a non-staging and real production environment!`)
@ -10,6 +11,7 @@ if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) {
await cleanDatabase()
console.log('Successfully deleted all nodes and relations!') // eslint-disable-line no-console
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
process.exit(1)

View File

@ -1,2 +1,5 @@
/* eslint-disable import/no-commonjs */
// eslint-disable-next-line n/no-unpublished-require
const tsNode = require('ts-node')
module.exports = tsNode.register

View File

@ -1,4 +1,4 @@
import { getDriver, getNeode } from '../../db/neo4j'
import { getDriver, getNeode } from '../neo4j'
import { hashSync } from 'bcryptjs'
import { v4 as uuid } from 'uuid'
import { categories } from '../../constants/categories'
@ -30,6 +30,7 @@ const createCategories = async (session) => {
try {
await createCategoriesTxResultPromise
console.log('Successfully created categories!') // eslint-disable-line no-console
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console
}
@ -44,6 +45,7 @@ const createDefaultAdminUser = async (session) => {
try {
const userCount = parseInt(String(await readTxResultPromise))
if (userCount === 0) createAdmin = true
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
}
@ -70,6 +72,7 @@ const createDefaultAdminUser = async (session) => {
try {
await createAdminTxResultPromise
console.log('Successfully created default admin user!') // eslint-disable-line no-console
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
}
@ -92,6 +95,7 @@ class Store {
// eslint-disable-next-line no-console
console.log('Successfully created database indices and constraints!')
next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
next(error, null)
@ -121,6 +125,7 @@ class Store {
}
const [{ title: lastRun }] = migrations
next(null, { lastRun, migrations })
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
next(error)
@ -156,6 +161,7 @@ class Store {
try {
await writeTxResultPromise
next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
next(error)
@ -165,4 +171,4 @@ class Store {
}
}
module.exports = Store
export default Store

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = ''

View File

@ -1,7 +1,8 @@
/* eslint-disable promise/prefer-await-to-callbacks */
import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators'
import { getDriver } from '../neo4j'
import normalizeEmail from '../../schema/resolvers//helpers/normalizeEmail'
import normalizeEmail from '../../schema/resolvers/helpers/normalizeEmail'
export const description = `
This migration merges duplicate :User and :EmailAddress nodes. It became

View File

@ -1,3 +1,4 @@
/* eslint-disable promise/prefer-await-to-callbacks */
import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError } from 'rxjs/operators'
import { getDriver } from '../neo4j'

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
This migration creates a MUTED relationship between two edges(:User) that have a pre-existing BLOCKED relationship.
@ -21,6 +21,7 @@ export async function up(next) {
`,
)
await transaction.commit()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
@ -38,6 +39,7 @@ export function down(next) {
try {
// Rollback your migration here.
next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (err) {
next(err)
} finally {

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
This migration swaps the value stored in Location.lat with the value

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description =
'This migration adds a fulltext index for the tags in order to search for Hasthags.'

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
We introduced a new node label 'Image' and we need a primary key for it. Best
@ -48,6 +48,7 @@ export async function down(next) {
`)
await transaction.commit()
next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)

View File

@ -1,4 +1,5 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable security/detect-non-literal-fs-filename */
import { getDriver } from '../neo4j'
import { existsSync, createReadStream } from 'fs'
import path from 'path'
import { S3 } from 'aws-sdk'
@ -95,6 +96,7 @@ export async function down(next) {
await transaction.run(``)
await transaction.commit()
next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)

View File

@ -1,5 +1,5 @@
/* eslint-disable no-console */
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
Refactor all our image properties on posts and users to a dedicated type

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description =
'We should not maintain obsolete attributes for users who have been deleted.'

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description =
'We should not maintain obsolete attributes for posts which have been deleted.'

View File

@ -1,4 +1,5 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable security/detect-non-literal-fs-filename */
import { getDriver } from '../neo4j'
import { existsSync } from 'fs'
export const description = `

View File

@ -1,9 +1,9 @@
'use strict'
module.exports.up = function (next) {
export async function up(next) {
next()
}
module.exports.down = function (next) {
export async function down(next) {
next()
}

View File

@ -1,10 +1,10 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
This migration adds the clickedCount property to all posts, setting it to 0.
`
module.exports.up = async function (next) {
export async function up(next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
@ -28,7 +28,7 @@ module.exports.up = async function (next) {
}
}
module.exports.down = async function (next) {
export async function down(next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()

View File

@ -1,10 +1,10 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
This migration adds the viewedTeaserCount property to all posts, setting it to 0.
`
module.exports.up = async function (next) {
export async function up(next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
@ -28,7 +28,7 @@ module.exports.up = async function (next) {
}
}
module.exports.down = async function (next) {
export async function down(next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
import { v4 as uuid } from 'uuid'
export const description =

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = ''

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
We introduced a new node label 'Group' and we need two primary keys 'id' and 'slug' for it.

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = ''

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = 'Add to all existing posts the Article label'

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = 'Add postType property Article to all posts'

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
Transform event start and end date of format 'YYYY-MM-DD HH:MM:SS' in CEST

View File

@ -1,4 +1,4 @@
import { getDriver } from '../../db/neo4j'
import { getDriver } from '../neo4j'
export const description = `
All authors observe their posts.

View File

@ -1,5 +1,6 @@
/* eslint-disable import/no-named-as-default-member */
import neo4j from 'neo4j-driver'
import CONFIG from './../config'
import CONFIG from '../config'
import Neode from 'neode'
import models from '../models'

View File

@ -1,10 +1,11 @@
/* eslint-disable n/no-process-exit */
import sample from 'lodash/sample'
import { createTestClient } from 'apollo-server-testing'
import CONFIG from '../config'
import createServer from '../server'
import { faker } from '@faker-js/faker'
import Factory from '../db/factories'
import { getNeode, getDriver } from '../db/neo4j'
import Factory from './factories'
import { getNeode, getDriver } from './neo4j'
import {
createGroupMutation,
joinGroupMutation,
@ -1565,6 +1566,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl']
await driver.close()
await neode.close()
process.exit(0)
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (err) {
/* eslint-disable-next-line no-console */
console.error(err)

View File

@ -1,3 +1,5 @@
/* eslint-disable promise/prefer-await-to-callbacks */
/* eslint-disable security/detect-object-injection */
/**
* Provide a way to iterate for each element in an array while waiting for async functions to finish
*

View File

@ -1,3 +1,4 @@
/* eslint-disable promise/avoid-new */
// sometime we have to wait to check a db state by having a look into the db in a certain moment
// or we wait a bit to check if we missed to set an await somewhere
// see: https://www.sitepoint.com/delay-sleep-pause-wait/

View File

@ -1,3 +1,5 @@
/* eslint-disable promise/prefer-await-to-callbacks */
/* eslint-disable security/detect-object-injection */
/**
* iterate through all fields and replace it with the callback result
* @property data Array

View File

@ -1,5 +1,5 @@
import jwt from 'jsonwebtoken'
import CONFIG from './../config'
import CONFIG from '../config'
export default async (driver, authorizationHeader) => {
if (!authorizationHeader) return null
@ -8,6 +8,7 @@ export default async (driver, authorizationHeader) => {
try {
const decoded = await jwt.verify(token, CONFIG.JWT_SECRET)
id = decoded.sub
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (err) {
return null
}

View File

@ -1,6 +1,6 @@
import encode from './encode'
import jwt from 'jsonwebtoken'
import CONFIG from './../config'
import CONFIG from '../config'
describe('encode', () => {
let payload

View File

@ -1,5 +1,5 @@
import jwt from 'jsonwebtoken'
import CONFIG from './../config'
import CONFIG from '../config'
// Generate an Access Token for the given User ID
export default function encode(user) {

View File

@ -1,4 +1,5 @@
import * as cheerio from 'cheerio'
import { load } from 'cheerio'
// eslint-disable-next-line import/extensions
import { exec, build } from 'xregexp/xregexp-all.js'
// formats of a Hashtag:
// https://en.wikipedia.org/w/index.php?title=Hashtag&oldid=905141980#Style
@ -10,7 +11,7 @@ const regX = build('^((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$')
export default function (content?) {
if (!content) return []
const $ = cheerio.load(content)
const $ = load(content)
// We can not search for class '.hashtag', because the classes are removed at the 'xss' middleware.
// But we have to know, which Hashtags are removed from the content as well, so we search for the 'a' html-tag.
const ids = $('a[data-hashtag-id]')

View File

@ -1,4 +1,4 @@
import extractHashtags from '../hashtags/extractHashtags'
import extractHashtags from './extractHashtags'
const updateHashtagsOfPost = async (postId, hashtags, context) => {
if (!hashtags.length) return

View File

@ -1,3 +1,4 @@
/* eslint-disable security/detect-unsafe-regex */
import sanitizeHtml from 'sanitize-html'
import linkifyHtml from 'linkify-html'

View File

@ -1,5 +1,5 @@
import CONFIG from '../../../config'
import { cleanHtml } from '../../../middleware/helpers/cleanHtml'
import { cleanHtml } from '../cleanHtml'
import nodemailer from 'nodemailer'
import { htmlToText } from 'nodemailer-html-to-text'

View File

@ -6,6 +6,7 @@ import {
resetPasswordTemplate,
wrongAccountTemplate,
notificationTemplate,
chatMessageTemplate,
} from './templateBuilder'
const englishHint = 'English version below!'
@ -34,6 +35,12 @@ const resetPasswordTemplateData = () => ({
name: 'Mr Example',
},
})
const chatMessageTemplateData = {
email: 'test@example.org',
variables: {
name: 'Mr Example',
},
}
const wrongAccountTemplateData = () => ({
email: 'test@example.org',
variables: {},
@ -163,6 +170,31 @@ describe('templateBuilder', () => {
})
})
describe('chatMessageTemplate', () => {
describe('multi language', () => {
it('e-mail is build with all data', () => {
const subject = 'Neue Chatnachricht | New chat message'
const actionUrl = new URL('/chat', CONFIG.CLIENT_URI).toString()
const enContent = 'You have received a new chat message.'
const deContent = 'Du hast eine neue Chatnachricht erhalten.'
testEmailData(null, chatMessageTemplate, chatMessageTemplateData, [
...textsStandard,
{
templPropName: 'subject',
isContaining: false,
text: subject,
},
englishHint,
actionUrl,
chatMessageTemplateData.variables.name,
enContent,
deContent,
supportUrl,
])
})
})
})
describe('wrongAccountTemplate', () => {
describe('multi language', () => {
it('e-mail is build with all data', () => {

View File

@ -1,3 +1,4 @@
/* eslint-disable import/no-namespace */
import mustache from 'mustache'
import CONFIG from '../../../config'
import metadata from '../../../config/metadata'
@ -71,6 +72,19 @@ export const resetPasswordTemplate = ({ email, variables: { nonce, name } }) =>
}
}
export const chatMessageTemplate = ({ email, variables: { name } }) => {
const subject = 'Neue Chatnachricht | New chat message'
const actionUrl = new URL('/chat', CONFIG.CLIENT_URI)
const renderParams = { ...defaultParams, englishHint, actionUrl, name, subject }
return {
from,
to: email,
subject,
html: mustache.render(templates.layout, renderParams, { content: templates.chatMessage }),
}
}
export const wrongAccountTemplate = ({ email, _variables = {} }) => {
const subject = 'Falsche Mailadresse? | Wrong E-mail?'
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)

View File

@ -0,0 +1,105 @@
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="{{{ welcomeImageUrl }}}"
width="300" height="" alt="Welcome image" border="0"
style="width: 100%; max-width: 300px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block; padding: 20px;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hallo {{ name }}!</h1>
<p style="margin: 0;">Du hast eine neue Chatnachricht erhalten.</p>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Chat anzeigen</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
</table>
<!-- Email Body German : END -->
<!-- Email Body English : BEGIN -->
<table class="email-english" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
<td style="background-color: #ffffff;">
<img
src="{{{ welcomeImageUrl }}}"
width="300" height="" alt="Welcome image" border="0"
style="width: 100%; max-width: 300px; height: auto; background: #ffffff; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; color: #555555; margin: auto; display: block; padding: 20px;"
class="g-img">
</td>
</tr>
<!-- Hero Image, Flush : END -->
<!-- 1 Column Text + Button : BEGIN -->
<tr>
<td style="background-color: #ffffff; padding: 20px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td
style="padding: 20px; padding-top: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<h1
style="margin: 0 0 10px 0; font-family: Lato, sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;">
Hello {{ name }}!</h1>
<p style="margin: 0;">You have received a new chat message.</p>
</td>
</tr>
<tr>
<td style="padding: 0 20px;">
<!-- Button : BEGIN -->
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto;">
<tr>
<td class="button-td button-td-primary" style="border-radius: 4px; background: #17b53e;">
<a class="button-a button-a-primary" href="{{{ actionUrl }}}"
style="background: #17b53e; font-family: Lato, sans-serif; font-size: 16px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;">Show Chat</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
</table>
</td>
</tr>
<!-- 1 Column Text + Button : END -->
</table>
<!-- Email Body English : END -->

View File

@ -1,3 +1,4 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import fs from 'fs'
import path from 'path'

View File

@ -1,3 +1,4 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import fs from 'fs'
import path from 'path'

View File

@ -1,3 +1,4 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import fs from 'fs'
import path from 'path'
@ -7,5 +8,6 @@ export const signup = readFile('./signup.html')
export const passwordReset = readFile('./resetPassword.html')
export const wrongAccount = readFile('./wrongAccount.html')
export const emailVerification = readFile('./emailVerification.html')
export const chatMessage = readFile('./chatMessage.html')
export const layout = readFile('./layout.html')

View File

@ -0,0 +1,46 @@
import { isUserOnline } from './isUserOnline'
let user
describe('isUserOnline', () => {
beforeEach(() => {
user = {
properties: {
lastActiveAt: null,
awaySince: null,
lastOnlineStatus: null,
},
}
})
describe('user has lastOnlineStatus `online`', () => {
it('returns true if he was active within the last 90 seconds', () => {
user.properties.lastOnlineStatus = 'online'
user.properties.lastActiveAt = new Date()
expect(isUserOnline(user)).toBe(true)
})
it('returns false if he was not active within the last 90 seconds', () => {
user.properties.lastOnlineStatus = 'online'
user.properties.lastActiveAt = new Date().getTime() - 90001
expect(isUserOnline(user)).toBe(false)
})
})
describe('user has lastOnlineStatus `away`', () => {
it('returns true if he went away less then 180 seconds ago', () => {
user.properties.lastOnlineStatus = 'away'
user.properties.awaySince = new Date()
expect(isUserOnline(user)).toBe(true)
})
it('returns false if he went away more then 180 seconds ago', () => {
user.properties.lastOnlineStatus = 'away'
user.properties.awaySince = new Date().getTime() - 180001
expect(isUserOnline(user)).toBe(false)
})
})
describe('user is freshly created and has never logged in', () => {
it('returns false', () => {
expect(isUserOnline(user)).toBe(false)
})
})
})

View File

@ -0,0 +1,16 @@
export const isUserOnline = (user) => {
// Is Recipient considered online
const lastActive = new Date(user.properties.lastActiveAt).getTime()
const awaySince = new Date(user.properties.awaySince).getTime()
const now = new Date().getTime()
const status = user.properties.lastOnlineStatus
if (
// online & last active less than 1.5min -> online
(status === 'online' && now - lastActive < 90000) ||
// away for less then 3min -> online
(status === 'away' && now - awaySince < 180000)
) {
return true
}
return false
}

View File

@ -1,5 +1,6 @@
/* eslint-disable security/detect-object-injection */
import { applyMiddleware } from 'graphql-middleware'
import CONFIG from './../config'
import CONFIG from '../config'
import softDelete from './softDelete/softDeleteMiddleware'
import sluggify from './sluggifyMiddleware'
import excerpt from './excerptMiddleware'
@ -8,6 +9,7 @@ import permissions from './permissionsMiddleware'
import includedFields from './includedFieldsMiddleware'
import orderBy from './orderByMiddleware'
import validation from './validation/validationMiddleware'
// eslint-disable-next-line import/no-cycle
import notifications from './notifications/notificationsMiddleware'
import hashtags from './hashtags/hashtagsMiddleware'
import login from './login/loginMiddleware'

View File

@ -1,8 +1,8 @@
import * as cheerio from 'cheerio'
import { load } from 'cheerio'
export default (content?) => {
if (!content) return []
const $ = cheerio.load(content)
const $ = load(content)
const userIds = $('a.mention[data-mention-id]')
.map((_, el) => {
return $(el).attr('data-mention-id')

View File

@ -1,5 +1,5 @@
import gql from 'graphql-tag'
import { cleanDatabase } from '../../db/factories'
import Factory, { cleanDatabase } from '../../db/factories'
import { createTestClient } from 'apollo-server-testing'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer, { pubsub } from '../../server'
@ -10,6 +10,23 @@ import {
changeGroupMemberRoleMutation,
removeUserFromGroupMutation,
} from '../../graphql/groups'
import { createMessageMutation } from '../../graphql/messages'
import { createRoomMutation } from '../../graphql/rooms'
const sendMailMock = jest.fn()
jest.mock('../helpers/email/sendMail', () => ({
sendMail: () => sendMailMock(),
}))
const chatMessageTemplateMock = jest.fn()
jest.mock('../helpers/email/templateBuilder', () => ({
chatMessageTemplate: () => chatMessageTemplateMock(),
}))
let isUserOnlineMock = jest.fn()
jest.mock('../helpers/isUserOnline', () => ({
isUserOnline: () => isUserOnlineMock(),
}))
let server, query, mutate, notifiedUser, authenticatedUser
let publishSpy
@ -633,6 +650,115 @@ describe('notifications', () => {
})
})
describe('chat email notifications', () => {
let chatSender
let chatReceiver
let roomId
beforeEach(async () => {
jest.clearAllMocks()
chatSender = await neode.create(
'User',
{
id: 'chatSender',
name: 'chatSender',
slug: 'chatSender',
},
{
email: 'chatSender@example.org',
password: '1234',
},
)
chatReceiver = await Factory.build(
'user',
{ id: 'chatReceiver', name: 'chatReceiver', slug: 'chatReceiver' },
{ email: 'user@example.org' },
)
authenticatedUser = await chatSender.toJson()
const room = await mutate({
mutation: createRoomMutation(),
variables: {
userId: 'chatReceiver',
},
})
roomId = room.data.CreateRoom.id
})
describe('chatReceiver is online', () => {
it('sends no email', async () => {
isUserOnlineMock = jest.fn().mockReturnValue(true)
await mutate({
mutation: createMessageMutation(),
variables: {
roomId,
content: 'Some nice message to chatReceiver',
},
})
expect(sendMailMock).not.toHaveBeenCalled()
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
})
})
describe('chatReceiver is offline', () => {
it('sends an email', async () => {
isUserOnlineMock = jest.fn().mockReturnValue(false)
await mutate({
mutation: createMessageMutation(),
variables: {
roomId,
content: 'Some nice message to chatReceiver',
},
})
expect(sendMailMock).toHaveBeenCalledTimes(1)
expect(chatMessageTemplateMock).toHaveBeenCalledTimes(1)
})
})
describe('chatReceiver has blocked chatSender', () => {
it('sends no email', async () => {
isUserOnlineMock = jest.fn().mockReturnValue(false)
await chatReceiver.relateTo(chatSender, 'blocked')
await mutate({
mutation: createMessageMutation(),
variables: {
roomId,
content: 'Some nice message to chatReceiver',
},
})
expect(sendMailMock).not.toHaveBeenCalled()
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
})
})
describe('chatReceiver has disabled email notifications', () => {
it('sends no email', async () => {
isUserOnlineMock = jest.fn().mockReturnValue(false)
await chatReceiver.update({ sendNotificationEmails: false })
await mutate({
mutation: createMessageMutation(),
variables: {
roomId,
content: 'Some nice message to chatReceiver',
},
})
expect(sendMailMock).not.toHaveBeenCalled()
expect(chatMessageTemplateMock).not.toHaveBeenCalled()
})
})
})
describe('group notifications', () => {
let groupOwner

View File

@ -1,8 +1,11 @@
/* eslint-disable security/detect-object-injection */
// eslint-disable-next-line import/no-cycle
import { pubsub, NOTIFICATION_ADDED } from '../../server'
import extractMentionedUsers from './mentions/extractMentionedUsers'
import { validateNotifyUsers } from '../validation/validationMiddleware'
import { sendMail } from '../helpers/email/sendMail'
import { notificationTemplate } from '../helpers/email/templateBuilder'
import { chatMessageTemplate, notificationTemplate } from '../helpers/email/templateBuilder'
import { isUserOnline } from '../helpers/isUserOnline'
const queryNotificationEmails = async (context, notificationUserIds) => {
if (!(notificationUserIds && notificationUserIds.length)) return []
@ -335,6 +338,56 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => {
}
}
const handleCreateMessage = async (resolve, root, args, context, resolveInfo) => {
// Execute resolver
const result = await resolve(root, args, context, resolveInfo)
// Query Parameters
const { roomId } = args
const {
user: { id: currentUserId },
} = context
// Find Recipient
const session = context.driver.session()
const messageRecipient = session.readTransaction(async (transaction) => {
const messageRecipientCypher = `
MATCH (currentUser:User { id: $currentUserId })-[:CHATS_IN]->(room:Room { id: $roomId })
MATCH (room)<-[:CHATS_IN]-(recipientUser:User)-[:PRIMARY_EMAIL]->(emailAddress:EmailAddress)
WHERE NOT recipientUser.id = $currentUserId
AND NOT (recipientUser)-[:BLOCKED]-(currentUser)
AND recipientUser.sendNotificationEmails = true
RETURN recipientUser, emailAddress {.email}
`
const txResponse = await transaction.run(messageRecipientCypher, {
currentUserId,
roomId,
})
return {
user: await txResponse.records.map((record) => record.get('recipientUser'))[0],
email: await txResponse.records.map((record) => record.get('emailAddress'))[0]?.email,
}
})
try {
// Execute Query
const { user, email } = await messageRecipient
// Send EMail if we found a user(not blocked) and he is not considered online
if (user && !isUserOnline(user)) {
void sendMail(chatMessageTemplate({ email, variables: { name: user.properties.name } }))
}
// Return resolver result to client
return result
} catch (error) {
throw new Error(error)
} finally {
session.close()
}
}
export default {
Mutation: {
CreatePost: handleContentDataOfPost,
@ -345,5 +398,6 @@ export default {
LeaveGroup: handleLeaveGroup,
ChangeGroupMemberRole: handleChangeGroupMemberRole,
RemoveUserFromGroup: handleRemoveUserFromGroup,
CreateMessage: handleCreateMessage,
},
}

View File

@ -1,6 +1,7 @@
import { sentry } from 'graphql-middleware-sentry'
import CONFIG from '../config'
// eslint-disable-next-line import/no-mutable-exports
let sentryMiddleware: any = (resolve, root, args, context, resolveInfo) =>
resolve(root, args, context, resolveInfo)

View File

@ -1,5 +1,5 @@
import walkRecursive from '../helpers/walkRecursive'
import { cleanHtml } from '../middleware/helpers/cleanHtml'
import { cleanHtml } from './helpers/cleanHtml'
// exclamation mark separetes field names, that should not be sanitized
const fields = [

View File

@ -1,3 +1,5 @@
/* eslint-disable n/no-missing-require */
/* eslint-disable n/global-require */
// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm
// module that is not browser-compatible. Node's `fs` module is server-side only
declare let Cypress: any | undefined

View File

@ -2,6 +2,7 @@ import generateNonce from './helpers/generateNonce'
import Resolver from './helpers/Resolver'
import existingEmailAddress from './helpers/existingEmailAddress'
import { UserInputError } from 'apollo-server'
// eslint-disable-next-line import/extensions
import Validator from 'neode/build/Services/Validator.js'
import normalizeEmail from './helpers/normalizeEmail'

View File

@ -1,3 +1,7 @@
/* eslint-disable n/no-extraneous-require */
/* eslint-disable n/global-require */
/* eslint-disable import/no-commonjs */
/* eslint-disable import/no-named-as-default */
import Metascraper from 'metascraper'
import fetch from 'node-fetch'
@ -37,6 +41,7 @@ const fetchEmbed = async (url) => {
try {
const response = await fetch(endpointUrl)
json = await response.json()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (err) {
error(`Error fetching embed data: ${err.message}`)
return {}

View File

@ -1,3 +1,4 @@
/* eslint-disable security/detect-object-injection */
import log from './databaseLogger'
export const undefinedToNullResolver = (list) => {

View File

@ -1,4 +1,6 @@
/* eslint-disable import/no-named-as-default */
import Debug from 'debug'
const debugCypher = Debug('human-connection:neo4j:cypher')
const debugStats = Debug('human-connection:neo4j:stats')

View File

@ -1,4 +1,4 @@
import CONSTANTS_REGISTRATION from './../../../constants/registration'
import CONSTANTS_REGISTRATION from '../../../constants/registration'
export default function generateInviteCode() {
// 6 random numbers in [ 0, 35 ] are 36 possible numbers (10 [0-9] + 26 [A-Z])

View File

@ -1,4 +1,4 @@
import CONSTANTS_REGISTRATION from './../../../constants/registration'
import CONSTANTS_REGISTRATION from '../../../constants/registration'
// TODO: why this is not used in resolver 'requestPasswordReset'?
export default function generateNonce() {

View File

@ -1,4 +1,5 @@
import Resolver from './helpers/Resolver'
export default {
Image: {
...Resolver('Image', {

View File

@ -1,3 +1,4 @@
/* eslint-disable promise/prefer-await-to-callbacks */
import { deleteImage, mergeImage } from './images'
import { getNeode, getDriver } from '../../../db/neo4j'
import Factory, { cleanDatabase } from '../../../db/factories'
@ -90,6 +91,7 @@ describe('deleteImage', () => {
})
throw new Error('Ouch!')
})
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (err) {
// nothing has been deleted
await expect(neode.all('Image')).resolves.toHaveLength(1)
@ -251,6 +253,7 @@ describe('mergeImage', () => {
})
return transaction.run('Ooops invalid cypher!', { image })
})
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (err) {
// nothing has been created
await expect(neode.all('Image')).resolves.toHaveLength(0)

View File

@ -1,3 +1,5 @@
/* eslint-disable promise/avoid-new */
/* eslint-disable security/detect-non-literal-fs-filename */
import path from 'path'
import { v4 as uuid } from 'uuid'
import { S3 } from 'aws-sdk'

View File

@ -1,9 +1,10 @@
/* eslint-disable security/detect-non-literal-regexp */
import Factory, { cleanDatabase } from '../../db/factories'
import { getDriver } from '../../db/neo4j'
import gql from 'graphql-tag'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
import CONSTANTS_REGISTRATION from './../../constants/registration'
import CONSTANTS_REGISTRATION from '../../constants/registration'
let user
let query

View File

@ -2,7 +2,7 @@ import Factory, { cleanDatabase } from '../../db/factories'
import gql from 'graphql-tag'
import { getDriver } from '../../db/neo4j'
import { createTestClient } from 'apollo-server-testing'
import createServer from '../.././server'
import createServer from '../../server'
import {
markAsReadMutation,
markAllAsReadMutation,

View File

@ -1,7 +1,7 @@
import Factory, { cleanDatabase } from '../../db/factories'
import gql from 'graphql-tag'
import { getNeode, getDriver } from '../../db/neo4j'
import CONSTANTS_REGISTRATION from './../../constants/registration'
import CONSTANTS_REGISTRATION from '../../constants/registration'
import createPasswordReset from './helpers/createPasswordReset'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'

View File

@ -1,6 +1,6 @@
import { v4 as uuid } from 'uuid'
import bcrypt from 'bcryptjs'
import CONSTANTS_REGISTRATION from './../../constants/registration'
import CONSTANTS_REGISTRATION from '../../constants/registration'
import createPasswordReset from './helpers/createPasswordReset'
export default {

View File

@ -1,5 +1,5 @@
import { createTestClient } from 'apollo-server-testing'
import createServer from '../.././server'
import createServer from '../../server'
import Factory, { cleanDatabase } from '../../db/factories'
import gql from 'graphql-tag'
import { getDriver, getNeode } from '../../db/neo4j'

View File

@ -1,3 +1,4 @@
/* eslint-disable security/detect-object-injection */
import log from './helpers/databaseLogger'
export default {

View File

@ -1,5 +1,6 @@
/* eslint-disable promise/prefer-await-to-callbacks */
import jwt from 'jsonwebtoken'
import CONFIG from './../../config'
import CONFIG from '../../config'
import Factory, { cleanDatabase } from '../../db/factories'
import gql from 'graphql-tag'
import { loginMutation } from '../../graphql/userManagement'

View File

@ -1,3 +1,6 @@
/* eslint-disable promise/avoid-new */
/* eslint-disable promise/prefer-await-to-callbacks */
/* eslint-disable import/no-named-as-default */
import request from 'request'
import { UserInputError } from 'apollo-server'
import Debug from 'debug'

View File

@ -1,8 +1,10 @@
/* eslint-disable import/no-named-as-default-member */
import express from 'express'
import http from 'http'
import helmet from 'helmet'
import { ApolloServer } from 'apollo-server-express'
import CONFIG from './config'
// eslint-disable-next-line import/no-cycle
import middleware from './middleware'
import { getNeode, getDriver } from './db/neo4j'
import decode from './jwt/decode'

View File

@ -1091,6 +1091,14 @@
dependencies:
tslib "^2.4.0"
"@eslint-community/eslint-plugin-eslint-comments@^4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-4.4.1.tgz#dbfab6f2447c22be8758a0a9a9c80e56d2e2b93f"
integrity sha512-lb/Z/MzbTf7CaVYM9WCFNQZ4L1yi3ev2fsFPF99h31ljhSEyUoyEsKsNWiU+qD1glbYTDJdqgyaLKtyTkkqtuQ==
dependencies:
escape-string-regexp "^4.0.0"
ignore "^5.2.4"
"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
@ -4606,6 +4614,11 @@ eslint-plugin-n@^16.6.2:
resolve "^1.22.2"
semver "^7.5.3"
eslint-plugin-no-catch-all@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-no-catch-all/-/eslint-plugin-no-catch-all-1.1.0.tgz#f2e8950cc2b0bdde5faa4ab339d0986c6ae32fb0"
integrity sha512-VkP62jLTmccPrFGN/W6V7a3SEwdtTZm+Su2k4T3uyJirtkm0OMMm97h7qd8pRFAHus/jQg9FpUpLRc7sAylBEQ==
eslint-plugin-prettier@^5.2.6:
version "5.2.6"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz#be39e3bb23bb3eeb7e7df0927cdb46e4d7945096"
@ -7353,29 +7366,29 @@ migrate@^2.1.0:
mkdirp "^3.0.1"
slug "^8.2.2"
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.19, mime-types@~2.1.34:
mime-db@^1.54.0:
version "1.54.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.22, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
mime-types@~2.1.22, mime-types@~2.1.24:
version "2.1.26"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06"
integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==
mime-types@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.1.tgz#b1d94d6997a9b32fd69ebaed0db73de8acb519ce"
integrity sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==
dependencies:
mime-db "1.43.0"
mime-db "^1.54.0"
mime@1.6.0:
version "1.6.0"
@ -7534,10 +7547,10 @@ n-gram@^1.0.0:
resolved "https://registry.yarnpkg.com/n-gram/-/n-gram-1.1.1.tgz#a374dc176a9063a2388d1be18ed7c35828be2a97"
integrity sha512-qibRqvUghLIVsq+RTwVuwOzgOxf0l4DDZKVYAK0bMam5sG9ZzaJ6BUSJyG2Td8kTc7c/HcMUtjiN5ShobZA2bA==
nan@2.17.0, nan@^2.20.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
nan@^2.20.0:
version "2.22.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.2.tgz#6b504fd029fb8f38c0990e52ad5c26772fdacfbb"
integrity sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==
nanoid@^3.3.6:
version "3.3.7"

8
package-lock.json generated
View File

@ -15,7 +15,7 @@
"@badeball/cypress-cucumber-preprocessor": "^22.0.1",
"@cucumber/cucumber": "11.2.0",
"@cypress/browserify-preprocessor": "^3.0.2",
"@faker-js/faker": "9.5.0",
"@faker-js/faker": "9.6.0",
"auto-changelog": "^2.5.0",
"bcryptjs": "^2.4.3",
"cross-env": "^7.0.3",
@ -2786,9 +2786,9 @@
}
},
"node_modules/@faker-js/faker": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.5.0.tgz",
"integrity": "sha512-3qbjLv+fzuuCg3umxc9/7YjrEXNaKwHgmig949nfyaTx8eL4FAsvFbu+1JcFUj1YAXofhaDn6JdEUBTYuk0Ssw==",
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.6.0.tgz",
"integrity": "sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw==",
"dev": true,
"funding": [
{

View File

@ -39,7 +39,7 @@
"@badeball/cypress-cucumber-preprocessor": "^22.0.1",
"@cucumber/cucumber": "11.2.0",
"@cypress/browserify-preprocessor": "^3.0.2",
"@faker-js/faker": "9.5.0",
"@faker-js/faker": "9.6.0",
"auto-changelog": "^2.5.0",
"bcryptjs": "^2.4.3",
"cross-env": "^7.0.3",

View File

@ -48,7 +48,7 @@
"nuxt": "~2.12.1",
"nuxt-dropzone": "^1.0.4",
"nuxt-env": "~0.1.0",
"sass": "^1.85.0",
"sass": "^1.86.3",
"stack-utils": "^2.0.3",
"tippy.js": "^4.3.5",
"tiptap": "~1.26.6",
@ -56,7 +56,7 @@
"trunc-html": "^1.1.2",
"v-mapbox": "^1.11.2",
"v-tooltip": "~2.1.3",
"validator": "^13.12.0",
"validator": "^13.15.0",
"vue-advanced-chat": "^2.0.11",
"vue-count-to": "~1.0.13",
"vue-infinite-loading": "^2.4.5",
@ -73,7 +73,7 @@
"@babel/core": "^7.25.8",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.25.8",
"@faker-js/faker": "9.5.0",
"@faker-js/faker": "9.6.0",
"@storybook/addon-a11y": "^8.0.8",
"@storybook/addon-actions": "^5.3.21",
"@storybook/addon-notes": "^5.3.18",

View File

@ -2447,10 +2447,10 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@faker-js/faker@9.5.0":
version "9.5.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.5.0.tgz#ce254c83706250ca8a5a0e05683608160610dd84"
integrity sha512-3qbjLv+fzuuCg3umxc9/7YjrEXNaKwHgmig949nfyaTx8eL4FAsvFbu+1JcFUj1YAXofhaDn6JdEUBTYuk0Ssw==
"@faker-js/faker@9.6.0":
version "9.6.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d"
integrity sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw==
"@hapi/address@2.x.x":
version "2.0.0"
@ -17426,10 +17426,10 @@ sass-resources-loader@^2.2.1:
glob "^7.1.6"
loader-utils "^2.0.0"
sass@^1.85.0:
version "1.85.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.85.0.tgz#0127ef697d83144496401553f0a0e87be83df45d"
integrity sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==
sass@^1.86.3:
version "1.86.3"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.86.3.tgz#0a0d9ea97cb6665e73f409639f8533ce057464c9"
integrity sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==
dependencies:
chokidar "^4.0.0"
immutable "^5.0.2"
@ -17823,12 +17823,7 @@ source-list-map@^2.0.0:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
source-map-js@^1.0.2, source-map-js@^1.2.0:
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
@ -19666,10 +19661,10 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
validator@^13.12.0:
version "13.12.0"
resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f"
integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==
validator@^13.15.0:
version "13.15.0"
resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.0.tgz#2dc7ce057e7513a55585109eec29b2c8e8c1aefd"
integrity sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==
vary@^1, vary@^1.1.2, vary@~1.1.2:
version "1.1.2"

View File

@ -1543,10 +1543,10 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699"
integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==
"@faker-js/faker@9.5.0":
version "9.5.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.5.0.tgz#ce254c83706250ca8a5a0e05683608160610dd84"
integrity sha512-3qbjLv+fzuuCg3umxc9/7YjrEXNaKwHgmig949nfyaTx8eL4FAsvFbu+1JcFUj1YAXofhaDn6JdEUBTYuk0Ssw==
"@faker-js/faker@9.6.0":
version "9.6.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d"
integrity sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw==
"@fastify/busboy@^2.0.0":
version "2.1.1"