Merge branch 'master' of github.com:Ocelot-Social-Community/Ocelot-Social into 6439-fix-terms-and-condition+data-privacy-links-on-registration

This commit is contained in:
Wolfgang Huß 2023-06-20 14:14:40 +02:00
commit 853f6f8fad
226 changed files with 2079 additions and 857 deletions

View File

@ -27,6 +27,10 @@ jobs:
- name: cypress | Fullstack tests - name: cypress | Fullstack tests
id: e2e-tests id: e2e-tests
run: | run: |
cd backend
yarn install
yarn build
cd ..
yarn install yarn install
yarn run cypress:run --spec $(cypress/parallel-features.sh ${{ matrix.job }} ${{ env.jobs }} ) yarn run cypress:run --spec $(cypress/parallel-features.sh ${{ matrix.job }} ${{ env.jobs }} )

View File

@ -1,18 +1,19 @@
module.exports = { module.exports = {
verbose: true, verbose: true,
preset: 'ts-jest',
collectCoverage: true, collectCoverage: true,
collectCoverageFrom: [ collectCoverageFrom: [
'**/*.js', '**/*.ts',
'!**/node_modules/**', '!**/node_modules/**',
'!**/test/**', '!**/test/**',
'!**/build/**', '!**/build/**',
'!**/src/**/?(*.)+(spec|test).js?(x)' '!**/src/**/?(*.)+(spec|test).ts?(x)'
], ],
coverageThreshold: { coverageThreshold: {
global: { global: {
lines: 57, lines: 57,
}, },
}, },
testMatch: ['**/src/**/?(*.)+(spec|test).js?(x)'], testMatch: ['**/src/**/?(*.)+(spec|test).ts?(x)'],
setupFilesAfterEnv: ['<rootDir>/test/setup.js'] setupFilesAfterEnv: ['<rootDir>/test/setup.ts']
} }

View File

@ -6,21 +6,21 @@
"author": "ocelot.social Community", "author": "ocelot.social Community",
"license": "MIT", "license": "MIT",
"private": false, "private": false,
"main": "src/index.js", "main": "src/index.ts",
"scripts": { "scripts": {
"__migrate": "migrate --compiler 'js:@babel/register' --migrations-dir ./src/db/migrations", "__migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations",
"prod:migrate": "migrate --migrations-dir ./build/db/migrations --store ./build/db/migrate/store.js", "prod:migrate": "migrate --migrations-dir ./build/db/migrations --store ./build/db/migrate/store.js",
"start": "node build/", "start": "node build/",
"build": "tsc && ./scripts/build.copy.files.sh", "build": "tsc && ./scripts/build.copy.files.sh",
"dev": "nodemon --exec ts-node src/ -e js,ts,gql", "dev": "nodemon --exec ts-node src/ -e js,ts,gql",
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql", "dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,ts,gql",
"lint": "eslint src --config .eslintrc.js", "lint": "eslint src --config .eslintrc.js",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles", "test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles",
"db:clean": "babel-node src/db/clean.js", "db:clean": "ts-node src/db/clean.ts",
"db:reset": "yarn run db:clean", "db:reset": "yarn run db:clean",
"db:seed": "babel-node src/db/seed.js", "db:seed": "ts-node src/db/seed.ts",
"db:migrate": "yarn run __migrate --store ./src/db/migrate/store.js", "db:migrate": "yarn run __migrate --store ./src/db/migrate/store.ts",
"db:migrate:create": "yarn run __migrate --template-file ./src/db/migrate/template.js --date-format 'yyyymmddHHmmss' create" "db:migrate:create": "yarn run __migrate --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create"
}, },
"dependencies": { "dependencies": {
"@babel/cli": "~7.8.4", "@babel/cli": "~7.8.4",
@ -76,7 +76,7 @@
"metascraper-url": "^5.34.2", "metascraper-url": "^5.34.2",
"metascraper-video": "^5.33.5", "metascraper-video": "^5.33.5",
"metascraper-youtube": "^5.33.5", "metascraper-youtube": "^5.33.5",
"migrate": "^1.7.0", "migrate": "^2.0.0",
"mime-types": "^2.1.26", "mime-types": "^2.1.26",
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
"mustache": "^4.2.0", "mustache": "^4.2.0",
@ -97,6 +97,8 @@
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "7.6.0", "@faker-js/faker": "7.6.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.2.5",
"apollo-server-testing": "~2.11.0", "apollo-server-testing": "~2.11.0",
"chai": "~4.2.0", "chai": "~4.2.0",
"cucumber": "~6.0.5", "cucumber": "~6.0.5",
@ -113,6 +115,7 @@
"nodemon": "~2.0.2", "nodemon": "~2.0.2",
"prettier": "~2.3.2", "prettier": "~2.3.2",
"rosie": "^2.0.1", "rosie": "^2.0.1",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.0.4" "typescript": "^5.0.4"
}, },

View File

@ -11,11 +11,15 @@ import { v4 as uuid } from 'uuid'
import CONFIG from '../config' import CONFIG from '../config'
const debug = require('debug')('ea') const debug = require('debug')('ea')
let activityPub = null let activityPub: any = null
export { activityPub } export { activityPub }
export default class ActivityPub { export default class ActivityPub {
endpoint: any
dataSource: any
collections: any
host: any
constructor(activityPubEndpointUri, internalGraphQlUri) { constructor(activityPubEndpointUri, internalGraphQlUri) {
this.endpoint = activityPubEndpointUri this.endpoint = activityPubEndpointUri
this.dataSource = new NitroDataSource(internalGraphQlUri) this.dataSource = new NitroDataSource(internalGraphQlUri)
@ -211,16 +215,16 @@ export default class ActivityPub {
// serve the rest // serve the rest
activity.to.map(async (recipient) => { activity.to.map(async (recipient) => {
debug('serve rest') debug('serve rest')
const actorObject = await this.getActorObject(recipient) const actorObject: any = await this.getActorObject(recipient)
return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox) return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox)
}) })
} else if (typeof activity.to === 'string') { } else if (typeof activity.to === 'string') {
debug('is string') debug('is string')
const actorObject = await this.getActorObject(activity.to) const actorObject: any = await this.getActorObject(activity.to)
return this.trySend(activity, fromName, new URL(activity.to).host, actorObject.inbox) return this.trySend(activity, fromName, new URL(activity.to).host, actorObject.inbox)
} else if (Array.isArray(activity.to)) { } else if (Array.isArray(activity.to)) {
activity.to.map(async (recipient) => { activity.to.map(async (recipient) => {
const actorObject = await this.getActorObject(recipient) const actorObject: any = await this.getActorObject(recipient)
return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox) return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox)
}) })
} }
@ -231,7 +235,7 @@ export default class ActivityPub {
return await signAndSend(activity, fromName, host, url) return await signAndSend(activity, fromName, host, url)
} catch (e) { } catch (e) {
if (tries > 0) { if (tries > 0) {
setTimeout(function () { setTimeout(() => {
return this.trySend(activity, fromName, host, url, --tries) return this.trySend(activity, fromName, host, url, --tries)
}, 20000) }, 20000)
} }

View File

@ -1,4 +1,5 @@
export default class Collections { export default class Collections {
dataSource: any
constructor(dataSource) { constructor(dataSource) {
this.dataSource = dataSource this.dataSource = dataSource
} }

View File

@ -17,15 +17,17 @@ import trunc from 'trunc-html'
const debug = require('debug')('ea:datasource') const debug = require('debug')('ea:datasource')
export default class NitroDataSource { export default class NitroDataSource {
uri: any
client: any
constructor(uri) { constructor(uri) {
this.uri = uri this.uri = uri
const defaultOptions = { const defaultOptions: any = {
query: { query: {
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
errorPolicy: 'all', errorPolicy: 'all',
}, },
} }
const link = createHttpLink({ uri: this.uri, fetch: fetch }) // eslint-disable-line const link = createHttpLink({ uri: this.uri, fetch: fetch } as any) // eslint-disable-line
const cache = new InMemoryCache() const cache = new InMemoryCache()
const authLink = setContext((_, { headers }) => { const authLink = setContext((_, { headers }) => {
// generate the authentication token (maybe from env? Which user?) // generate the authentication token (maybe from env? Which user?)
@ -95,7 +97,7 @@ export default class NitroDataSource {
const followers = actor.followedBy const followers = actor.followedBy
const followersCount = actor.followedByCount const followersCount = actor.followedByCount
const followersCollection = createOrderedCollectionPage(slug, 'followers') const followersCollection: any = createOrderedCollectionPage(slug, 'followers')
followersCollection.totalItems = followersCount followersCollection.totalItems = followersCount
debug(`followers = ${JSON.stringify(followers, null, 2)}`) debug(`followers = ${JSON.stringify(followers, null, 2)}`)
await Promise.all( await Promise.all(
@ -157,7 +159,7 @@ export default class NitroDataSource {
const following = actor.following const following = actor.following
const followingCount = actor.followingCount const followingCount = actor.followingCount
const followingCollection = createOrderedCollectionPage(slug, 'following') const followingCollection: any = createOrderedCollectionPage(slug, 'following')
followingCollection.totalItems = followingCount followingCollection.totalItems = followingCount
await Promise.all( await Promise.all(
@ -235,7 +237,7 @@ export default class NitroDataSource {
const actor = result.data.User[0] const actor = result.data.User[0]
const posts = actor.contributions const posts = actor.contributions
const outboxCollection = createOrderedCollectionPage(slug, 'outbox') const outboxCollection: any = createOrderedCollectionPage(slug, 'outbox')
outboxCollection.totalItems = posts.length outboxCollection.totalItems = posts.length
await Promise.all( await Promise.all(
posts.map(async (post) => { posts.map(async (post) => {

View File

@ -11,8 +11,8 @@ export default function () {
cors(), cors(),
express.json({ express.json({
type: ['application/activity+json', 'application/ld+json', 'application/json'], type: ['application/activity+json', 'application/ld+json', 'application/json'],
}), }) as any,
express.urlencoded({ extended: true }), express.urlencoded({ extended: true }) as any,
user, user,
) )
router.use( router.use(
@ -20,8 +20,8 @@ export default function () {
cors(), cors(),
express.json({ express.json({
type: ['application/activity+json', 'application/ld+json', 'application/json'], type: ['application/activity+json', 'application/ld+json', 'application/json'],
}), }) as any,
express.urlencoded({ extended: true }), express.urlencoded({ extended: true }) as any,
verify, verify,
inbox, inbox,
) )

View File

@ -54,6 +54,6 @@ export async function handler(req, res) {
export default function () { export default function () {
const router = express.Router() const router = express.Router()
router.use('/webfinger', cors(), express.urlencoded({ extended: true }), handler) router.use('/webfinger', cors(), express.urlencoded({ extended: true }) as any, handler)
return router return router
} }

View File

@ -8,7 +8,7 @@ const debug = require('debug')('ea:security')
// TODO Does this reference a local config? Why? // TODO Does this reference a local config? Why?
// dotenv.config({ path: resolve('src', 'activitypub', '.env') }) // dotenv.config({ path: resolve('src', 'activitypub', '.env') })
export function generateRsaKeyPair(options = {}) { export function generateRsaKeyPair(options: any = {}) {
const { passphrase = CONFIG.PRIVATE_KEY_PASSPHRASE } = options const { passphrase = CONFIG.PRIVATE_KEY_PASSPHRASE } = options
return crypto.generateKeyPairSync('rsa', { return crypto.generateKeyPairSync('rsa', {
modulusLength: 4096, modulusLength: 4096,

View File

@ -100,18 +100,19 @@ export async function getActorId(name) {
// } // }
export function isPublicAddressed(postObject) { export function isPublicAddressed(postObject) {
const result: { to: any[]} = { to: []}
if (typeof postObject.to === 'string') { if (typeof postObject.to === 'string') {
postObject.to = [postObject.to] result.to = [postObject.to]
} }
if (typeof postObject === 'string') { if (typeof postObject === 'string') {
postObject.to = [postObject] result.to = [postObject]
} }
if (Array.isArray(postObject)) { if (Array.isArray(postObject)) {
postObject.to = postObject result.to = postObject
} }
return ( return (
postObject.to.includes('Public') || result.to.includes('Public') ||
postObject.to.includes('as:Public') || result.to.includes('as:Public') ||
postObject.to.includes('https://www.w3.org/ns/activitystreams#Public') result.to.includes('https://www.w3.org/ns/activitystreams#Public')
) )
} }

View File

@ -101,7 +101,7 @@ export function signAndSend(activity, fromName, targetDomain, url) {
} else { } else {
debug('Response Headers:', JSON.stringify(response.headers, null, 2)) debug('Response Headers:', JSON.stringify(response.headers, null, 2))
debug('Response Body:', JSON.stringify(response.body, null, 2)) debug('Response Body:', JSON.stringify(response.body, null, 2))
resolve() resolve(response)
} }
}, },
) )

View File

@ -1,6 +1,6 @@
import dotenv from 'dotenv' import dotenv from 'dotenv'
import emails from './emails.js' import emails from './emails'
import metadata from './metadata.js' import metadata from './metadata'
// Load env file // Load env file
if (require.resolve) { if (require.resolve) {
@ -15,10 +15,11 @@ if (require.resolve) {
} }
// Use Cypress env or process.env // Use Cypress env or process.env
declare var Cypress: any | undefined
const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env // eslint-disable-line no-undef const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env // eslint-disable-line no-undef
const environment = { const environment = {
NODE_ENV: env.NODE_ENV || process.NODE_ENV, NODE_ENV: env.NODE_ENV || process.env.NODE_ENV,
DEBUG: env.NODE_ENV !== 'production' && env.DEBUG, DEBUG: env.NODE_ENV !== 'production' && env.DEBUG,
TEST: env.NODE_ENV === 'test', TEST: env.NODE_ENV === 'test',
PRODUCTION: env.NODE_ENV === 'production', PRODUCTION: env.NODE_ENV === 'production',
@ -90,14 +91,11 @@ const options = {
} }
// Check if all required configs are present // Check if all required configs are present
if (require.resolve) {
// are we in a nodejs environment?
Object.entries(required).map((entry) => { Object.entries(required).map((entry) => {
if (!entry[1]) { if (!entry[1]) {
throw new Error(`ERROR: "${entry[0]}" env variable is missing.`) throw new Error(`ERROR: "${entry[0]}" env variable is missing.`)
} }
}) })
}
export default { export default {
...environment, ...environment,

View File

@ -1,4 +1,4 @@
// this file is duplicated in `backend/src/config/logos.js` and `webapp/constants/logos.js` and replaced on rebranding // this file is duplicated in `backend/src/config/logos` and `webapp/constants/logos.js` and replaced on rebranding
// this are the paths in the webapp // this are the paths in the webapp
export default { export default {
LOGO_HEADER_PATH: '/img/custom/logo-horizontal.svg', LOGO_HEADER_PATH: '/img/custom/logo-horizontal.svg',

View File

@ -1,4 +1,4 @@
// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js` and replaced on rebranding // this file is duplicated in `backend/src/config/metadata` and `webapp/constants/metadata.js` and replaced on rebranding
export default { export default {
APPLICATION_NAME: 'ocelot.social', APPLICATION_NAME: 'ocelot.social',
APPLICATION_SHORT_NAME: 'ocelot', APPLICATION_SHORT_NAME: 'ocelot',

View File

@ -1,4 +1,4 @@
// this file is duplicated in `backend/src/constants/metadata.js` and `webapp/constants/metadata.js` // this file is duplicated in `backend/src/constants/metadata` and `webapp/constants/metadata.js`
export const CATEGORIES_MIN = 1 export const CATEGORIES_MIN = 1
export const CATEGORIES_MAX = 3 export const CATEGORIES_MAX = 3

View File

@ -1,3 +1,3 @@
// this file is duplicated in `backend/src/constants/group.js` and `webapp/constants/group.js` // this file is duplicated in `backend/src/constants/group` and `webapp/constants/group.js`
export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 50 // with removed HTML tags export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 50 // with removed HTML tags
export const DESCRIPTION_EXCERPT_HTML_LENGTH = 250 // with removed HTML tags export const DESCRIPTION_EXCERPT_HTML_LENGTH = 250 // with removed HTML tags

View File

@ -1,5 +0,0 @@
// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js`
export default {
NONCE_LENGTH: 5,
INVITE_CODE_LENGTH: 6,
}

View File

@ -0,0 +1,5 @@
// this file is duplicated in `backend/src/config/metadata` and `webapp/constants/metadata.js`
export default {
NONCE_LENGTH: 5,
INVITE_CODE_LENGTH: 6,
}

View File

@ -0,0 +1,2 @@
const tsNode = require('ts-node');
module.exports = tsNode.register;

View File

@ -4,8 +4,8 @@ import { hashSync } from 'bcryptjs'
import { Factory } from 'rosie' import { Factory } from 'rosie'
import { faker } from '@faker-js/faker' import { faker } from '@faker-js/faker'
import { getDriver, getNeode } from './neo4j' import { getDriver, getNeode } from './neo4j'
import CONFIG from '../config/index.js' import CONFIG from '../config/index'
import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode.js' import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode'
const neode = getNeode() const neode = getNeode()
@ -15,7 +15,7 @@ const uniqueImageUrl = (imageUrl) => {
return newUrl.toString() return newUrl.toString()
} }
export const cleanDatabase = async (options = {}) => { export const cleanDatabase = async (options: any = {}) => {
const { driver = getDriver() } = options const { driver = getDriver() } = options
const session = driver.session() const session = driver.session()
try { try {

View File

@ -18,13 +18,13 @@ export function up(next) {
rxSession rxSession
.beginTransaction() .beginTransaction()
.pipe( .pipe(
flatMap((txc) => flatMap((txc: any) =>
concat( concat(
txc txc
.run('MATCH (email:EmailAddress) RETURN email {.email}') .run('MATCH (email:EmailAddress) RETURN email {.email}')
.records() .records()
.pipe( .pipe(
map((record) => { map((record: any) => {
const { email } = record.get('email') const { email } = record.get('email')
const normalizedEmail = normalizeEmail(email) const normalizedEmail = normalizeEmail(email)
return { email, normalizedEmail } return { email, normalizedEmail }
@ -45,7 +45,7 @@ export function up(next) {
) )
.records() .records()
.pipe( .pipe(
map((r) => ({ map((r: any) => ({
oldEmail: email, oldEmail: email,
email: r.get('email'), email: r.get('email'),
user: r.get('user'), user: r.get('user'),

View File

@ -12,7 +12,7 @@ export function up(next) {
rxSession rxSession
.beginTransaction() .beginTransaction()
.pipe( .pipe(
flatMap((transaction) => flatMap((transaction: any) =>
concat( concat(
transaction transaction
.run( .run(
@ -23,7 +23,7 @@ export function up(next) {
) )
.records() .records()
.pipe( .pipe(
map((record) => { map((record: any) => {
const { id: locationId } = record.get('location') const { id: locationId } = record.get('location')
return { locationId } return { locationId }
}), }),
@ -40,7 +40,7 @@ export function up(next) {
) )
.records() .records()
.pipe( .pipe(
map((record) => ({ map((record: any) => ({
location: record.get('location'), location: record.get('location'),
updatedLocation: record.get('updatedLocation'), updatedLocation: record.get('updatedLocation'),
})), })),

View File

@ -3,7 +3,7 @@ import { existsSync, createReadStream } from 'fs'
import path from 'path' import path from 'path'
import { S3 } from 'aws-sdk' import { S3 } from 'aws-sdk'
import mime from 'mime-types' import mime from 'mime-types'
import { s3Configs } from '../../config' import s3Configs from '../../config'
import https from 'https' import https from 'https'
export const description = ` export const description = `

View File

@ -11,13 +11,13 @@ export async function up(next) {
const transaction = session.beginTransaction() const transaction = session.beginTransaction()
try { try {
// Implement your migration here. // Those two indexes already exist
await transaction.run(` // await transaction.run(`
CREATE CONSTRAINT ON ( group:Group ) ASSERT group.id IS UNIQUE // CREATE CONSTRAINT ON ( group:Group ) ASSERT group.id IS UNIQUE
`) // `)
await transaction.run(` // await transaction.run(`
CREATE CONSTRAINT ON ( group:Group ) ASSERT group.slug IS UNIQUE // CREATE CONSTRAINT ON ( group:Group ) ASSERT group.slug IS UNIQUE
`) // `)
await transaction.run(` await transaction.run(`
CALL db.index.fulltext.createNodeIndex("group_fulltext_search",["Group"],["name", "slug", "about", "description"]) CALL db.index.fulltext.createNodeIndex("group_fulltext_search",["Group"],["name", "slug", "about", "description"])
`) `)

View File

@ -10,7 +10,7 @@ export async function up(next) {
try { try {
// Drop indexes if they exist because due to legacy code they might be set already // Drop indexes if they exist because due to legacy code they might be set already
const indexesResponse = await transaction.run(`CALL db.indexes()`) const indexesResponse = await transaction.run(`CALL db.indexes()`)
const indexes = indexesResponse.records.map((record) => record.get('indexName')) const indexes = indexesResponse.records.map((record) => record.get('name'))
if (indexes.indexOf('user_fulltext_search') > -1) { if (indexes.indexOf('user_fulltext_search') > -1) {
await transaction.run(`CALL db.index.fulltext.drop("user_fulltext_search")`) await transaction.run(`CALL db.index.fulltext.drop("user_fulltext_search")`)
} }

View File

@ -0,0 +1,32 @@
import gql from 'graphql-tag'
export const createMessageMutation = () => {
return gql`
mutation (
$roomId: ID!
$content: String!
) {
CreateMessage(
roomId: $roomId
content: $content
) {
id
content
}
}
`
}
export const messageQuery = () => {
return gql`
query($roomId: ID!) {
Message(roomId: $roomId) {
id
content
author {
id
}
}
}
`
}

View File

@ -0,0 +1,28 @@
import gql from 'graphql-tag'
export const createRoomMutation = () => {
return gql`
mutation (
$userId: ID!
) {
CreateRoom(
userId: $userId
) {
id
}
}
`
}
export const roomQuery = () => {
return gql`
query {
Room {
id
users {
id
}
}
}
`
}

View File

@ -5,7 +5,7 @@
* @property fieldName String * @property fieldName String
* @property callback Function * @property callback Function
*/ */
function walkRecursive(data, fields, fieldName, callback, _key) { function walkRecursive(data, fields, fieldName, callback, _key?) {
if (!Array.isArray(fields)) { if (!Array.isArray(fields)) {
throw new Error('please provide an fields array for the walkRecursive helper') throw new Error('please provide an fields array for the walkRecursive helper')
} }

View File

@ -8,7 +8,7 @@ import { exec, build } from 'xregexp/xregexp-all.js'
// 2. If it starts with a digit '0-9' than a unicode letter has to follow. // 2. If it starts with a digit '0-9' than a unicode letter has to follow.
const regX = build('^((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$') const regX = build('^((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$')
export default function (content) { export default function (content?) {
if (!content) return [] if (!content) return []
const $ = cheerio.load(content) const $ = cheerio.load(content)
// We can not search for class '.hashtag', because the classes are removed at the 'xss' middleware. // We can not search for class '.hashtag', because the classes are removed at the 'xss' middleware.
@ -18,7 +18,7 @@ export default function (content) {
return $(el).attr('data-hashtag-id') return $(el).attr('data-hashtag-id')
}) })
.get() .get()
const hashtags = [] const hashtags: any = []
ids.forEach((id) => { ids.forEach((id) => {
const match = exec(id, regX) const match = exec(id, regX)
if (match != null) { if (match != null) {

View File

@ -1,12 +1,12 @@
import CONFIG from '../../../config' import CONFIG from '../../../config'
import { cleanHtml } from '../../../middleware/helpers/cleanHtml.js' import { cleanHtml } from '../../../middleware/helpers/cleanHtml'
import nodemailer from 'nodemailer' import nodemailer from 'nodemailer'
import { htmlToText } from 'nodemailer-html-to-text' import { htmlToText } from 'nodemailer-html-to-text'
const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT const hasEmailConfig = CONFIG.SMTP_HOST && CONFIG.SMTP_PORT
const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD const hasAuthData = CONFIG.SMTP_USERNAME && CONFIG.SMTP_PASSWORD
let sendMailCallback = async () => {} let sendMailCallback: any = async () => {}
if (!hasEmailConfig) { if (!hasEmailConfig) {
if (!CONFIG.TEST) { if (!CONFIG.TEST) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -29,7 +29,7 @@ if (!hasEmailConfig) {
cleanHtml(templateArgs.html, 'dummyKey', { cleanHtml(templateArgs.html, 'dummyKey', {
allowedTags: ['a'], allowedTags: ['a'],
allowedAttributes: { a: ['href'] }, allowedAttributes: { a: ['href'] },
}).replace(/&amp;/g, '&'), } as any).replace(/&amp;/g, '&'),
) )
} }
} }

View File

@ -1,5 +1,5 @@
import CONFIG from '../../../config' import CONFIG from '../../../config'
import logosWebapp from '../../../config/logos.js' import logosWebapp from '../../../config/logos'
import { import {
signupTemplate, signupTemplate,
emailVerificationTemplate, emailVerificationTemplate,

View File

@ -1,7 +1,7 @@
import mustache from 'mustache' import mustache from 'mustache'
import CONFIG from '../../../config' import CONFIG from '../../../config'
import metadata from '../../../config/metadata.js' import metadata from '../../../config/metadata'
import logosWebapp from '../../../config/logos.js' import logosWebapp from '../../../config/logos'
import * as templates from './templates' import * as templates from './templates'
import * as templatesEN from './templates/en' import * as templatesEN from './templates/en'

View File

@ -1,5 +1,5 @@
import LanguageDetect from 'languagedetect' import LanguageDetect from 'languagedetect'
import { removeHtmlTags } from '../helpers/cleanHtml.js' import { removeHtmlTags } from '../helpers/cleanHtml'
const setPostLanguage = (text) => { const setPostLanguage = (text) => {
const lngDetector = new LanguageDetect() const lngDetector = new LanguageDetect()

View File

@ -576,7 +576,7 @@ describe('notifications', () => {
read: false, read: false,
}, },
}), }),
).resolves.toMatchObject(expected, { errors: undefined }) ).resolves.toMatchObject({ ...expected, errors: undefined })
}) })
}) })

View File

@ -15,7 +15,7 @@ const queryNotificationEmails = async (context, notificationUserIds) => {
RETURN emailAddress {.email} RETURN emailAddress {.email}
` `
const session = context.driver.session() const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => { const writeTxResultPromise = session.readTransaction(async (transaction) => {
const emailAddressTransactionResponse = await transaction.run(userEmailCypher, { const emailAddressTransactionResponse = await transaction.run(userEmailCypher, {
notificationUserIds, notificationUserIds,
}) })
@ -238,7 +238,7 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
[(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors,
[(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts
WITH resource, user, notification, authors, posts, WITH resource, user, notification, authors, posts,
resource {.*, __typename: filter(l IN labels(resource) WHERE l IN ['Post', 'Comment', 'Group'])[0], author: authors[0], post: posts[0]} AS finalResource resource {.*, __typename: [l IN labels(resource) WHERE l IN ['Post', 'Comment', 'Group']][0], author: authors[0], post: posts[0]} AS finalResource
SET notification.read = FALSE SET notification.read = FALSE
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
SET notification.updatedAt = toString(datetime()) SET notification.updatedAt = toString(datetime())

View File

@ -406,6 +406,8 @@ export default shield(
queryLocations: isAuthenticated, queryLocations: isAuthenticated,
availableRoles: isAdmin, availableRoles: isAdmin,
getInviteCode: isAuthenticated, // and inviteRegistration getInviteCode: isAuthenticated, // and inviteRegistration
Room: isAuthenticated,
Message: isAuthenticated,
}, },
Mutation: { Mutation: {
'*': deny, '*': deny,
@ -459,6 +461,8 @@ export default shield(
switchUserRole: isAdmin, switchUserRole: isAdmin,
markTeaserAsViewed: allow, markTeaserAsViewed: allow,
saveCategorySettings: isAuthenticated, saveCategorySettings: isAuthenticated,
CreateRoom: isAuthenticated,
CreateMessage: isAuthenticated,
}, },
User: { User: {
email: or(isMyOwn, isAdmin), email: or(isMyOwn, isAdmin),

View File

@ -1,7 +1,7 @@
import { sentry } from 'graphql-middleware-sentry' import { sentry } from 'graphql-middleware-sentry'
import CONFIG from '../config' import CONFIG from '../config'
let sentryMiddleware = (resolve, root, args, context, resolveInfo) => let sentryMiddleware: any = (resolve, root, args, context, resolveInfo) =>
resolve(root, args, context, resolveInfo) resolve(root, args, context, resolveInfo)
if (CONFIG.SENTRY_DSN_BACKEND) { if (CONFIG.SENTRY_DSN_BACKEND) {
@ -12,7 +12,7 @@ if (CONFIG.SENTRY_DSN_BACKEND) {
release: CONFIG.COMMIT, release: CONFIG.COMMIT,
environment: CONFIG.NODE_ENV, environment: CONFIG.NODE_ENV,
}, },
withScope: (scope, error, context) => { withScope: (scope, error, context: any) => {
scope.setUser({ scope.setUser({
id: context.user && context.user.id, id: context.user && context.user.id,
}) })

Some files were not shown because too many files have changed in this diff Show More