mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch '779-tags-of-contribution-in-text' of https://github.com/Human-Connection/Human-Connection into 779-tags-of-contribution-in-text
This commit is contained in:
commit
58810c6add
@ -5,6 +5,11 @@ GRAPHQL_PORT=4000
|
||||
GRAPHQL_URI=http://localhost:4000
|
||||
CLIENT_URI=http://localhost:3000
|
||||
MOCKS=false
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_IGNORE_TLS=true
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
|
||||
JWT_SECRET="b/&&7b78BF&fv/Vd"
|
||||
MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"
|
||||
|
||||
@ -44,6 +44,9 @@ or start the backend in production environment with:
|
||||
yarn run start
|
||||
```
|
||||
|
||||
For e-mail delivery, please configure at least `SMTP_HOST` and `SMTP_PORT` in
|
||||
your `.env` configuration file.
|
||||
|
||||
Your backend is up and running at [http://localhost:4000/](http://localhost:4000/)
|
||||
This will start the GraphQL service \(by default on localhost:4000\) where you
|
||||
can issue GraphQL requests or access GraphQL Playground in the browser.
|
||||
|
||||
@ -44,15 +44,15 @@
|
||||
"dependencies": {
|
||||
"activitystrea.ms": "~2.1.3",
|
||||
"apollo-cache-inmemory": "~1.6.2",
|
||||
"apollo-client": "~2.6.2",
|
||||
"apollo-link-context": "~1.0.14",
|
||||
"apollo-link-http": "~1.5.14",
|
||||
"apollo-server": "~2.6.3",
|
||||
"apollo-client": "~2.6.3",
|
||||
"apollo-link-context": "~1.0.18",
|
||||
"apollo-link-http": "~1.5.15",
|
||||
"apollo-server": "~2.6.4",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"cheerio": "~1.0.0-rc.3",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~5.2.0",
|
||||
"date-fns": "2.0.0-alpha.33",
|
||||
"date-fns": "2.0.0-alpha.35",
|
||||
"debug": "~4.1.1",
|
||||
"dotenv": "~8.0.0",
|
||||
"express": "~4.17.1",
|
||||
@ -61,9 +61,9 @@
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.2",
|
||||
"graphql-shield": "~5.3.8",
|
||||
"graphql-shield": "~5.7.1",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"graphql-yoga": "~1.17.4",
|
||||
"graphql-yoga": "~1.18.0",
|
||||
"helmet": "~3.18.0",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"linkifyjs": "~2.1.8",
|
||||
@ -72,6 +72,7 @@
|
||||
"neo4j-driver": "~1.7.4",
|
||||
"neo4j-graphql-js": "git+https://github.com/Human-Connection/neo4j-graphql-js.git#temporary_fixes",
|
||||
"node-fetch": "~2.6.0",
|
||||
"nodemailer": "^6.2.1",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"request": "~2.88.0",
|
||||
"sanitize-html": "~1.20.1",
|
||||
@ -87,17 +88,17 @@
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||
"@babel/preset-env": "~7.4.5",
|
||||
"@babel/register": "~7.4.4",
|
||||
"apollo-server-testing": "~2.6.3",
|
||||
"apollo-server-testing": "~2.6.4",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.0.1",
|
||||
"babel-eslint": "~10.0.2",
|
||||
"babel-jest": "~24.8.0",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "~5.1.0",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-prettier": "~4.3.0",
|
||||
"eslint-config-prettier": "~5.0.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-plugin-import": "~2.17.3",
|
||||
"eslint-plugin-jest": "~22.6.4",
|
||||
"eslint-plugin-jest": "~22.7.0",
|
||||
"eslint-plugin-node": "~9.1.0",
|
||||
"eslint-plugin-prettier": "~3.1.0",
|
||||
"eslint-plugin-promise": "~4.1.1",
|
||||
|
||||
@ -2,23 +2,33 @@ import dotenv from 'dotenv'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export const requiredConfigs = {
|
||||
MAPBOX_TOKEN: process.env.MAPBOX_TOKEN,
|
||||
JWT_SECRET: process.env.JWT_SECRET,
|
||||
PRIVATE_KEY_PASSPHRASE: process.env.PRIVATE_KEY_PASSPHRASE,
|
||||
}
|
||||
const {
|
||||
MAPBOX_TOKEN,
|
||||
JWT_SECRET,
|
||||
PRIVATE_KEY_PASSPHRASE,
|
||||
SMTP_IGNORE_TLS = true,
|
||||
SMTP_HOST,
|
||||
SMTP_PORT,
|
||||
SMTP_USERNAME,
|
||||
SMTP_PASSWORD,
|
||||
NEO4J_URI = 'bolt://localhost:7687',
|
||||
NEO4J_USERNAME = 'neo4j',
|
||||
NEO4J_PASSWORD = 'neo4j',
|
||||
GRAPHQL_PORT = 4000,
|
||||
CLIENT_URI = 'http://localhost:3000',
|
||||
GRAPHQL_URI = 'http://localhost:4000',
|
||||
} = process.env
|
||||
|
||||
export const neo4jConfigs = {
|
||||
NEO4J_URI: process.env.NEO4J_URI || 'bolt://localhost:7687',
|
||||
NEO4J_USERNAME: process.env.NEO4J_USERNAME || 'neo4j',
|
||||
NEO4J_PASSWORD: process.env.NEO4J_PASSWORD || 'neo4j',
|
||||
}
|
||||
|
||||
export const serverConfigs = {
|
||||
GRAPHQL_PORT: process.env.GRAPHQL_PORT || 4000,
|
||||
CLIENT_URI: process.env.CLIENT_URI || 'http://localhost:3000',
|
||||
GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost:4000',
|
||||
export const requiredConfigs = { MAPBOX_TOKEN, JWT_SECRET, PRIVATE_KEY_PASSPHRASE }
|
||||
export const smtpConfigs = {
|
||||
SMTP_HOST,
|
||||
SMTP_PORT,
|
||||
SMTP_IGNORE_TLS,
|
||||
SMTP_USERNAME,
|
||||
SMTP_PASSWORD,
|
||||
}
|
||||
export const neo4jConfigs = { NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD }
|
||||
export const serverConfigs = { GRAPHQL_PORT, CLIENT_URI, GRAPHQL_URI }
|
||||
|
||||
export const developmentConfigs = {
|
||||
DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG === 'true',
|
||||
@ -29,6 +39,7 @@ export const developmentConfigs = {
|
||||
|
||||
export default {
|
||||
...requiredConfigs,
|
||||
...smtpConfigs,
|
||||
...neo4jConfigs,
|
||||
...serverConfigs,
|
||||
...developmentConfigs,
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
const legacyUrls = [
|
||||
'https://api-alpha.human-connection.org',
|
||||
'https://staging-api.human-connection.org',
|
||||
'http://localhost:3000',
|
||||
]
|
||||
|
||||
export const fixUrl = url => {
|
||||
legacyUrls.forEach(legacyUrl => {
|
||||
url = url.replace(legacyUrl, '')
|
||||
})
|
||||
if (!url.startsWith('/')) {
|
||||
url = `/${url}`
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const checkUrl = thing => {
|
||||
return (
|
||||
thing &&
|
||||
typeof thing === 'string' &&
|
||||
legacyUrls.find(legacyUrl => {
|
||||
return thing.indexOf(legacyUrl) === 0
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export const fixImageURLs = (result, recursive) => {
|
||||
if (checkUrl(result)) {
|
||||
result = fixUrl(result)
|
||||
} else if (result && Array.isArray(result)) {
|
||||
result.forEach((res, index) => {
|
||||
result[index] = fixImageURLs(result[index], true)
|
||||
})
|
||||
} else if (result && typeof result === 'object') {
|
||||
Object.keys(result).forEach(key => {
|
||||
result[key] = fixImageURLs(result[key], true)
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: async (resolve, root, args, context, info) => {
|
||||
const result = await resolve(root, args, context, info)
|
||||
return fixImageURLs(result)
|
||||
},
|
||||
Query: async (resolve, root, args, context, info) => {
|
||||
let result = await resolve(root, args, context, info)
|
||||
return fixImageURLs(result)
|
||||
},
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
import { fixImageURLs } from './fixImageUrlsMiddleware'
|
||||
|
||||
describe('fixImageURLs', () => {
|
||||
describe('edge case: image url is exact match of legacy url', () => {
|
||||
it('replaces it with `/`', () => {
|
||||
const url = 'https://api-alpha.human-connection.org'
|
||||
expect(fixImageURLs(url)).toEqual('/')
|
||||
})
|
||||
})
|
||||
|
||||
describe('image url of legacy alpha', () => {
|
||||
it('removes domain', () => {
|
||||
const url =
|
||||
'https://api-alpha.human-connection.org/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png'
|
||||
expect(fixImageURLs(url)).toEqual(
|
||||
'/uploads/4bfaf9172c4ba03d7645108bbbd16f0a696a37d01eacd025fb131e5da61b15d9.png',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('image url of legacy staging', () => {
|
||||
it('removes domain', () => {
|
||||
const url =
|
||||
'https://staging-api.human-connection.org/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg'
|
||||
expect(fixImageURLs(url)).toEqual(
|
||||
'/uploads/1b3c39a24f27e2fb62b69074b2f71363b63b263f0c4574047d279967124c026e.jpeg',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('object', () => {
|
||||
it('returns untouched', () => {
|
||||
const object = { some: 'thing' }
|
||||
expect(fixImageURLs(object)).toEqual(object)
|
||||
})
|
||||
})
|
||||
|
||||
describe('some string', () => {
|
||||
it('returns untouched', () => {})
|
||||
const string = "Yeah I'm a String"
|
||||
expect(fixImageURLs(string)).toEqual(string)
|
||||
})
|
||||
})
|
||||
@ -3,7 +3,6 @@ import activityPub from './activityPubMiddleware'
|
||||
import password from './passwordMiddleware'
|
||||
import softDelete from './softDeleteMiddleware'
|
||||
import sluggify from './sluggifyMiddleware'
|
||||
import fixImageUrls from './fixImageUrlsMiddleware'
|
||||
import excerpt from './excerptMiddleware'
|
||||
import dateTime from './dateTimeMiddleware'
|
||||
import xss from './xssMiddleware'
|
||||
@ -25,7 +24,6 @@ export default schema => {
|
||||
excerpt: excerpt,
|
||||
notifications: notifications,
|
||||
xss: xss,
|
||||
fixImageUrls: fixImageUrls,
|
||||
softDelete: softDelete,
|
||||
user: user,
|
||||
includedFields: includedFields,
|
||||
@ -42,7 +40,6 @@ export default schema => {
|
||||
'excerpt',
|
||||
'notifications',
|
||||
'xss',
|
||||
'fixImageUrls',
|
||||
'softDelete',
|
||||
'user',
|
||||
'includedFields',
|
||||
|
||||
@ -105,8 +105,8 @@ const permissions = shield(
|
||||
Query: {
|
||||
'*': deny,
|
||||
findPosts: allow,
|
||||
Category: isAdmin,
|
||||
Tag: isAdmin,
|
||||
Category: allow,
|
||||
Tag: allow,
|
||||
Report: isModerator,
|
||||
Notification: isAdmin,
|
||||
statistics: allow,
|
||||
@ -147,6 +147,8 @@ const permissions = shield(
|
||||
CreateComment: isAuthenticated,
|
||||
DeleteComment: isAuthor,
|
||||
DeleteUser: isDeletingOwnAccount,
|
||||
requestPasswordReset: allow,
|
||||
resetPassword: allow,
|
||||
},
|
||||
User: {
|
||||
email: isMyOwn,
|
||||
|
||||
72
backend/src/schema/resolvers/passwordReset.js
Normal file
72
backend/src/schema/resolvers/passwordReset.js
Normal file
@ -0,0 +1,72 @@
|
||||
import uuid from 'uuid/v4'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import CONFIG from '../../config'
|
||||
import nodemailer from 'nodemailer'
|
||||
import { resetPasswordMail, wrongAccountMail } from './passwordReset/emailTemplates'
|
||||
|
||||
const transporter = () => {
|
||||
const configs = {
|
||||
host: CONFIG.SMTP_HOST,
|
||||
port: CONFIG.SMTP_PORT,
|
||||
ignoreTLS: CONFIG.SMTP_IGNORE_TLS,
|
||||
secure: false, // true for 465, false for other ports
|
||||
}
|
||||
const { SMTP_USERNAME: user, SMTP_PASSWORD: pass } = CONFIG
|
||||
if (user && pass) {
|
||||
configs.auth = { user, pass }
|
||||
}
|
||||
return nodemailer.createTransport(configs)
|
||||
}
|
||||
|
||||
export async function createPasswordReset(options) {
|
||||
const { driver, code, email, issuedAt = new Date() } = options
|
||||
const session = driver.session()
|
||||
const cypher = `
|
||||
MATCH (u:User) WHERE u.email = $email
|
||||
CREATE(pr:PasswordReset {code: $code, issuedAt: datetime($issuedAt), usedAt: NULL})
|
||||
MERGE (u)-[:REQUESTED]->(pr)
|
||||
RETURN u
|
||||
`
|
||||
const transactionRes = await session.run(cypher, {
|
||||
issuedAt: issuedAt.toISOString(),
|
||||
code,
|
||||
email,
|
||||
})
|
||||
const users = transactionRes.records.map(record => record.get('u'))
|
||||
session.close()
|
||||
return users
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
requestPasswordReset: async (_, { email }, { driver }) => {
|
||||
const code = uuid().substring(0, 6)
|
||||
const [user] = await createPasswordReset({ driver, code, email })
|
||||
if (CONFIG.SMTP_HOST && CONFIG.SMTP_PORT) {
|
||||
const name = (user && user.name) || ''
|
||||
const mailTemplate = user ? resetPasswordMail : wrongAccountMail
|
||||
await transporter().sendMail(mailTemplate({ email, code, name }))
|
||||
}
|
||||
return true
|
||||
},
|
||||
resetPassword: async (_, { email, code, newPassword }, { driver }) => {
|
||||
const session = driver.session()
|
||||
const stillValid = new Date()
|
||||
stillValid.setDate(stillValid.getDate() - 1)
|
||||
const newHashedPassword = await bcrypt.hashSync(newPassword, 10)
|
||||
const cypher = `
|
||||
MATCH (pr:PasswordReset {code: $code})
|
||||
MATCH (u:User {email: $email})-[:REQUESTED]->(pr)
|
||||
WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL
|
||||
SET pr.usedAt = datetime()
|
||||
SET u.password = $newHashedPassword
|
||||
RETURN pr
|
||||
`
|
||||
let transactionRes = await session.run(cypher, { stillValid, email, code, newHashedPassword })
|
||||
const [reset] = transactionRes.records.map(record => record.get('pr'))
|
||||
const result = !!(reset && reset.properties.usedAt)
|
||||
session.close()
|
||||
return result
|
||||
},
|
||||
},
|
||||
}
|
||||
180
backend/src/schema/resolvers/passwordReset.spec.js
Normal file
180
backend/src/schema/resolvers/passwordReset.spec.js
Normal file
@ -0,0 +1,180 @@
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import Factory from '../../seed/factories'
|
||||
import { host } from '../../jest/helpers'
|
||||
import { getDriver } from '../../bootstrap/neo4j'
|
||||
import { createPasswordReset } from './passwordReset'
|
||||
|
||||
const factory = Factory()
|
||||
let client
|
||||
const driver = getDriver()
|
||||
|
||||
const getAllPasswordResets = async () => {
|
||||
const session = driver.session()
|
||||
let transactionRes = await session.run('MATCH (r:PasswordReset) RETURN r')
|
||||
const resets = transactionRes.records.map(record => record.get('r'))
|
||||
session.close()
|
||||
return resets
|
||||
}
|
||||
|
||||
describe('passwordReset', () => {
|
||||
beforeEach(async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await factory.create('User', {
|
||||
email: 'user@example.org',
|
||||
role: 'user',
|
||||
password: '1234',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('requestPasswordReset', () => {
|
||||
const mutation = `mutation($email: String!) { requestPasswordReset(email: $email) }`
|
||||
|
||||
describe('with invalid email', () => {
|
||||
const variables = { email: 'non-existent@example.org' }
|
||||
|
||||
it('resolves anyways', async () => {
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
requestPasswordReset: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('creates no node', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const resets = await getAllPasswordResets()
|
||||
expect(resets).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a valid email', () => {
|
||||
const variables = { email: 'user@example.org' }
|
||||
|
||||
it('resolves', async () => {
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
requestPasswordReset: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('creates node with label `PasswordReset`', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const resets = await getAllPasswordResets()
|
||||
expect(resets).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('creates a reset code', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const resets = await getAllPasswordResets()
|
||||
const [reset] = resets
|
||||
const { code } = reset.properties
|
||||
expect(code).toHaveLength(6)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('resetPassword', () => {
|
||||
const setup = async (options = {}) => {
|
||||
const { email = 'user@example.org', issuedAt = new Date(), code = 'abcdef' } = options
|
||||
|
||||
const session = driver.session()
|
||||
await createPasswordReset({ driver, email, issuedAt, code })
|
||||
session.close()
|
||||
}
|
||||
|
||||
const mutation = `mutation($code: String!, $email: String!, $newPassword: String!) { resetPassword(code: $code, email: $email, newPassword: $newPassword) }`
|
||||
let email = 'user@example.org'
|
||||
let code = 'abcdef'
|
||||
let newPassword = 'supersecret'
|
||||
let variables
|
||||
|
||||
describe('invalid email', () => {
|
||||
it('resolves to false', async () => {
|
||||
await setup()
|
||||
variables = { newPassword, email: 'non-existent@example.org', code }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({ resetPassword: false })
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid email', () => {
|
||||
describe('but invalid code', () => {
|
||||
it('resolves to false', async () => {
|
||||
await setup()
|
||||
variables = { newPassword, email, code: 'slkdjf' }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
resetPassword: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('and valid code', () => {
|
||||
beforeEach(() => {
|
||||
variables = {
|
||||
newPassword,
|
||||
email: 'user@example.org',
|
||||
code: 'abcdef',
|
||||
}
|
||||
})
|
||||
|
||||
describe('and code not expired', () => {
|
||||
beforeEach(async () => {
|
||||
await setup()
|
||||
})
|
||||
|
||||
it('resolves to true', async () => {
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
resetPassword: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('updates PasswordReset `usedAt` property', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const requests = await getAllPasswordResets()
|
||||
const [request] = requests
|
||||
const { usedAt } = request.properties
|
||||
expect(usedAt).not.toBeFalsy()
|
||||
})
|
||||
|
||||
it('updates password of the user', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const checkLoginMutation = `
|
||||
mutation($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password)
|
||||
}
|
||||
`
|
||||
const expected = expect.objectContaining({ login: expect.any(String) })
|
||||
await expect(
|
||||
client.request(checkLoginMutation, {
|
||||
email: 'user@example.org',
|
||||
password: 'supersecret',
|
||||
}),
|
||||
).resolves.toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('but expired code', () => {
|
||||
beforeEach(async () => {
|
||||
const issuedAt = new Date()
|
||||
issuedAt.setDate(issuedAt.getDate() - 1)
|
||||
await setup({ issuedAt })
|
||||
})
|
||||
|
||||
it('resolves to false', async () => {
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
resetPassword: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not update PasswordReset `usedAt` property', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const requests = await getAllPasswordResets()
|
||||
const [request] = requests
|
||||
const { usedAt } = request.properties
|
||||
expect(usedAt).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
85
backend/src/schema/resolvers/passwordReset/emailTemplates.js
Normal file
85
backend/src/schema/resolvers/passwordReset/emailTemplates.js
Normal file
@ -0,0 +1,85 @@
|
||||
import CONFIG from '../../../config'
|
||||
|
||||
export const from = '"Human Connection" <info@human-connection.org>'
|
||||
|
||||
export const resetPasswordMail = options => {
|
||||
const {
|
||||
name,
|
||||
email,
|
||||
code,
|
||||
subject = 'Use this link to reset your password. The link is only valid for 24 hours.',
|
||||
supportUrl = 'https://human-connection.org/en/contact/',
|
||||
} = options
|
||||
const actionUrl = new URL('/password-reset/change-password', CONFIG.CLIENT_URI)
|
||||
actionUrl.searchParams.set('code', code)
|
||||
actionUrl.searchParams.set('email', email)
|
||||
|
||||
return {
|
||||
to: email,
|
||||
subject,
|
||||
text: `
|
||||
Hi ${name}!
|
||||
|
||||
You recently requested to reset your password for your Human Connection account.
|
||||
Use the link below to reset it. This password reset is only valid for the next
|
||||
24 hours.
|
||||
|
||||
${actionUrl}
|
||||
|
||||
If you did not request a password reset, please ignore this email or contact
|
||||
support if you have questions:
|
||||
|
||||
${supportUrl}
|
||||
|
||||
Thanks,
|
||||
The Human Connection Team
|
||||
|
||||
If you're having trouble with the link above, you can manually copy and
|
||||
paste the following code into your browser window:
|
||||
|
||||
${code}
|
||||
|
||||
Human Connection gemeinnützige GmbH
|
||||
Bahnhofstr. 11
|
||||
73235 Weilheim / Teck
|
||||
Deutschland
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
export const wrongAccountMail = options => {
|
||||
const {
|
||||
email,
|
||||
subject = `We received a request to reset your password with this email address (${email})`,
|
||||
supportUrl = 'https://human-connection.org/en/contact/',
|
||||
} = options
|
||||
const actionUrl = new URL('/password-reset/request', CONFIG.CLIENT_URI)
|
||||
return {
|
||||
to: email,
|
||||
subject,
|
||||
text: `
|
||||
We received a request to reset the password to access Human Connection with your
|
||||
email address, but we were unable to find an account associated with this
|
||||
address.
|
||||
|
||||
If you use Human Connection and were expecting this email, consider trying to
|
||||
request a password reset using the email address associated with your account.
|
||||
Try a different email:
|
||||
|
||||
${actionUrl}
|
||||
|
||||
If you do not use Human Connection or did not request a password reset, please
|
||||
ignore this email. Feel free to contact support if you have further questions:
|
||||
|
||||
${supportUrl}
|
||||
|
||||
Thanks,
|
||||
The Human Connection Team
|
||||
|
||||
Human Connection gemeinnützige GmbH
|
||||
Bahnhofstr. 11
|
||||
73235 Weilheim / Teck
|
||||
Deutschland
|
||||
`,
|
||||
}
|
||||
}
|
||||
@ -59,7 +59,7 @@ export default {
|
||||
changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => {
|
||||
const session = driver.session()
|
||||
let result = await session.run(
|
||||
`MATCH (user:User {email: $userEmail})
|
||||
`MATCH (user:User {email: $userEmail})
|
||||
RETURN user {.id, .email, .password}`,
|
||||
{
|
||||
userEmail: user.email,
|
||||
|
||||
@ -25,6 +25,8 @@ type Mutation {
|
||||
login(email: String!, password: String!): String!
|
||||
signup(email: String!, password: String!): Boolean!
|
||||
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||
requestPasswordReset(email: String!): Boolean!
|
||||
resetPassword(email: String!, code: String!, newPassword: String!): Boolean!
|
||||
report(id: ID!, description: String): Report
|
||||
disable(id: ID!): ID
|
||||
enable(id: ID!): ID
|
||||
|
||||
@ -1020,16 +1020,7 @@
|
||||
"@types/node" "*"
|
||||
"@types/range-parser" "*"
|
||||
|
||||
"@types/express@*", "@types/express@^4.11.1":
|
||||
version "4.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.0.tgz#6d8bc42ccaa6f35cf29a2b7c3333cb47b5a32a19"
|
||||
integrity sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/express@4.17.0":
|
||||
"@types/express@*", "@types/express@4.17.0", "@types/express@^4.11.1":
|
||||
version "4.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287"
|
||||
integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==
|
||||
@ -1119,10 +1110,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
|
||||
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
|
||||
|
||||
"@types/yup@0.26.16":
|
||||
version "0.26.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.16.tgz#75c428236207c48d9f8062dd1495cda8c5485a15"
|
||||
integrity sha512-E2RNc7DSeQ+2EIJ1H3+yFjYu6YiyQBUJ7yNpIxomrYJ3oFizLZ5yDS3T1JTUNBC2OCRkgnhLS0smob5UuCHfNA==
|
||||
"@types/yup@0.26.17":
|
||||
version "0.26.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.17.tgz#5cb7cfc211d8e985b21d88289542591c92cad9dc"
|
||||
integrity sha512-MN7VHlPsZQ2MTBxLE2Gl+Qfg2WyKsoz+vIr8xN0OSZ4AvJDrrKBlxc8b59UXCCIG9tPn9XhxTXh3j/htHbzC2Q==
|
||||
|
||||
"@types/zen-observable@^0.5.3":
|
||||
version "0.5.4"
|
||||
@ -1322,10 +1313,10 @@ apollo-cache@1.3.2, apollo-cache@^1.3.2:
|
||||
apollo-utilities "^1.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-client@~2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.2.tgz#03b6af651e09b6e413e486ddc87464c85bd6e514"
|
||||
integrity sha512-oks1MaT5x7gHcPeC8vPC1UzzsKaEIC0tye+jg72eMDt5OKc7BobStTeS/o2Ib3e0ii40nKxGBnMdl/Xa/p56Yg==
|
||||
apollo-client@~2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.3.tgz#9bb2d42fb59f1572e51417f341c5f743798d22db"
|
||||
integrity sha512-DS8pmF5CGiiJ658dG+mDn8pmCMMQIljKJSTeMNHnFuDLV0uAPZoeaAwVFiAmB408Ujqt92oIZ/8yJJAwSIhd4A==
|
||||
dependencies:
|
||||
"@types/zen-observable" "^0.8.0"
|
||||
apollo-cache "1.3.2"
|
||||
@ -1351,17 +1342,17 @@ apollo-engine-reporting-protobuf@0.3.1:
|
||||
dependencies:
|
||||
protobufjs "^6.8.6"
|
||||
|
||||
apollo-engine-reporting@1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.1.tgz#f2c2c63f865871a57c15cdbb2a3bcd4b4af28115"
|
||||
integrity sha512-e0Xp+0yite8DH/xm9fnJt42CxfWAcY6waiq3icCMAgO9T7saXzVOPpl84SkuA+hIJUBtfaKrTnC+7Jxi/I7OrQ==
|
||||
apollo-engine-reporting@1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.3.2.tgz#b2569f79eb1a7a7380f49340db61465f449284fe"
|
||||
integrity sha512-Q9XUZ3CTqddjCswlbn+OD2oYxZ5p4lCAnsWOGMfYnSmCXLagyNK28UFFQodjFOy73p6nlTAg9cwaJ9yMOBeeXA==
|
||||
dependencies:
|
||||
apollo-engine-reporting-protobuf "0.3.1"
|
||||
apollo-graphql "^0.3.0"
|
||||
apollo-server-core "2.6.3"
|
||||
apollo-graphql "^0.3.2"
|
||||
apollo-server-core "2.6.4"
|
||||
apollo-server-env "2.4.0"
|
||||
async-retry "^1.2.1"
|
||||
graphql-extensions "0.7.2"
|
||||
graphql-extensions "0.7.3"
|
||||
|
||||
apollo-env@0.5.1:
|
||||
version "0.5.1"
|
||||
@ -1380,49 +1371,49 @@ apollo-errors@^1.9.0:
|
||||
assert "^1.4.1"
|
||||
extendable-error "^0.1.5"
|
||||
|
||||
apollo-graphql@^0.3.0:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.1.tgz#d13b80cc0cae3fe7066b81b80914c6f983fac8d7"
|
||||
integrity sha512-tbhtzNAAhNI34v4XY9OlZGnH7U0sX4BP1cJrUfSiNzQnZRg1UbQYZ06riHSOHpi5RSndFcA9LDM5C1ZKKOUeBg==
|
||||
apollo-graphql@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.2.tgz#8881a87f1d5fcf80837b34dba90737e664eabe9a"
|
||||
integrity sha512-YbzYGR14GV0023m//EU66vOzZ3i7c04V/SF8Qk+60vf1sOWyKgO6mxZJ4BKhw10qWUayirhSDxq3frYE+qSG0A==
|
||||
dependencies:
|
||||
apollo-env "0.5.1"
|
||||
lodash.sortby "^4.7.0"
|
||||
|
||||
apollo-link-context@~1.0.14:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.17.tgz#439272cfb43ec1891506dd175ed907845b7de36c"
|
||||
integrity sha512-W5UUfHcrrlP5uqJs5X1zbf84AMXhPZGAqX/7AQDgR6wY/7//sMGfJvm36KDkpIeSOElztGtM9z6zdPN1NbT41Q==
|
||||
apollo-link-context@~1.0.18:
|
||||
version "1.0.18"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.18.tgz#9e700e3314da8ded50057fee0a18af2bfcedbfc3"
|
||||
integrity sha512-aG5cbUp1zqOHHQjAJXG7n/izeMQ6LApd/whEF5z6qZp5ATvcyfSNkCfy3KRJMMZZ3iNfVTs6jF+IUA8Zvf+zeg==
|
||||
dependencies:
|
||||
apollo-link "^1.2.11"
|
||||
apollo-link "^1.2.12"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-link-http-common@^0.2.13:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350"
|
||||
integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==
|
||||
apollo-link-http-common@^0.2.14:
|
||||
version "0.2.14"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.14.tgz#d3a195c12e00f4e311c417f121181dcc31f7d0c8"
|
||||
integrity sha512-v6mRU1oN6XuX8beVIRB6OpF4q1ULhSnmy7ScnHnuo1qV6GaFmDcbdvXqxIkAV1Q8SQCo2lsv4HeqJOWhFfApOg==
|
||||
dependencies:
|
||||
apollo-link "^1.2.11"
|
||||
ts-invariant "^0.3.2"
|
||||
apollo-link "^1.2.12"
|
||||
ts-invariant "^0.4.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-link-http@~1.5.14:
|
||||
version "1.5.14"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.14.tgz#ed6292248d1819ccd16523e346d35203a1b31109"
|
||||
integrity sha512-XEoPXmGpxFG3wioovgAlPXIarWaW4oWzt8YzjTYZ87R4R7d1A3wKR/KcvkdMV1m5G7YSAHcNkDLe/8hF2nH6cg==
|
||||
apollo-link-http@~1.5.15:
|
||||
version "1.5.15"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.15.tgz#106ab23bb8997bd55965d05855736d33119652cf"
|
||||
integrity sha512-epZFhCKDjD7+oNTVK3P39pqWGn4LEhShAoA1Q9e2tDrBjItNfviiE33RmcLcCURDYyW5JA6SMgdODNI4Is8tvQ==
|
||||
dependencies:
|
||||
apollo-link "^1.2.11"
|
||||
apollo-link-http-common "^0.2.13"
|
||||
apollo-link "^1.2.12"
|
||||
apollo-link-http-common "^0.2.14"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-link@^1.0.0, apollo-link@^1.2.11, apollo-link@^1.2.3:
|
||||
version "1.2.11"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d"
|
||||
integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA==
|
||||
apollo-link@^1.0.0, apollo-link@^1.2.12, apollo-link@^1.2.3:
|
||||
version "1.2.12"
|
||||
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.12.tgz#014b514fba95f1945c38ad4c216f31bcfee68429"
|
||||
integrity sha512-fsgIAXPKThyMVEMWQsUN22AoQI+J/pVXcjRGAShtk97h7D8O+SPskFinCGEkxPeQpE83uKaqafB2IyWdjN+J3Q==
|
||||
dependencies:
|
||||
apollo-utilities "^1.2.1"
|
||||
ts-invariant "^0.3.2"
|
||||
apollo-utilities "^1.3.0"
|
||||
ts-invariant "^0.4.0"
|
||||
tslib "^1.9.3"
|
||||
zen-observable-ts "^0.8.18"
|
||||
zen-observable-ts "^0.8.19"
|
||||
|
||||
apollo-server-caching@0.4.0:
|
||||
version "0.4.0"
|
||||
@ -1431,24 +1422,24 @@ apollo-server-caching@0.4.0:
|
||||
dependencies:
|
||||
lru-cache "^5.0.0"
|
||||
|
||||
apollo-server-core@2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.3.tgz#786c8251c82cf29acb5cae9635a321f0644332ae"
|
||||
integrity sha512-tfC0QO1NbJW3ShkB5pRCnUaYEkW2AwnswaTeedkfv//EO3yiC/9LeouCK5F22T8stQG+vGjvCqf0C8ldI/XsIA==
|
||||
apollo-server-core@2.6.4:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.6.4.tgz#0372e3a28f221b9db83bdfbb0fd0b2960cd29bab"
|
||||
integrity sha512-GBF+tQoJ/ysaY2CYMkuuAwJM1nk1yLJumrsBTFfcvalSzS64VdS5VN/zox1eRI+LHQQzHM18HYEAgDGa/EX+gw==
|
||||
dependencies:
|
||||
"@apollographql/apollo-tools" "^0.3.6"
|
||||
"@apollographql/graphql-playground-html" "1.6.20"
|
||||
"@types/ws" "^6.0.0"
|
||||
apollo-cache-control "0.7.2"
|
||||
apollo-datasource "0.5.0"
|
||||
apollo-engine-reporting "1.3.1"
|
||||
apollo-engine-reporting "1.3.2"
|
||||
apollo-server-caching "0.4.0"
|
||||
apollo-server-env "2.4.0"
|
||||
apollo-server-errors "2.3.0"
|
||||
apollo-server-plugin-base "0.5.2"
|
||||
apollo-server-plugin-base "0.5.3"
|
||||
apollo-tracing "0.7.2"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
graphql-extensions "0.7.2"
|
||||
graphql-extensions "0.7.3"
|
||||
graphql-subscriptions "^1.0.0"
|
||||
graphql-tag "^2.9.2"
|
||||
graphql-tools "^4.0.0"
|
||||
@ -1479,10 +1470,10 @@ apollo-server-errors@2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.0.tgz#700622b66a16dffcad3b017e4796749814edc061"
|
||||
integrity sha512-rUvzwMo2ZQgzzPh2kcJyfbRSfVKRMhfIlhY7BzUfM4x6ZT0aijlgsf714Ll3Mbf5Fxii32kD0A/DmKsTecpccw==
|
||||
|
||||
apollo-server-express@2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.3.tgz#62034c978f84207615c0430fb37ab006f71146fe"
|
||||
integrity sha512-8ca+VpKArgNzFar0D3DesWnn0g9YDtFLhO56TQprHh2Spxu9WxTnYNjsYs2MCCNf+iV/uy7vTvEknErvnIcZaQ==
|
||||
apollo-server-express@2.6.4:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.6.4.tgz#fc1d661be73fc1880aa53a56e1abe3733d08eada"
|
||||
integrity sha512-U6hiZxty/rait39V5d+QeueNHlwfl68WbYtsutDUVxnq2Jws2ZDrvIkaWWN6HQ77+nBy5gGVxycvWIyoHHfi+g==
|
||||
dependencies:
|
||||
"@apollographql/graphql-playground-html" "1.6.20"
|
||||
"@types/accepts" "^1.3.5"
|
||||
@ -1490,7 +1481,7 @@ apollo-server-express@2.6.3:
|
||||
"@types/cors" "^2.8.4"
|
||||
"@types/express" "4.17.0"
|
||||
accepts "^1.3.5"
|
||||
apollo-server-core "2.6.3"
|
||||
apollo-server-core "2.6.4"
|
||||
body-parser "^1.18.3"
|
||||
cors "^2.8.4"
|
||||
graphql-subscriptions "^1.0.0"
|
||||
@ -1518,25 +1509,25 @@ apollo-server-module-graphiql@^1.3.4, apollo-server-module-graphiql@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-module-graphiql/-/apollo-server-module-graphiql-1.4.0.tgz#c559efa285578820709f1769bb85d3b3eed3d8ec"
|
||||
integrity sha512-GmkOcb5he2x5gat+TuiTvabnBf1m4jzdecal3XbXBh/Jg+kx4hcvO3TTDFQ9CuTprtzdcVyA11iqG7iOMOt7vA==
|
||||
|
||||
apollo-server-plugin-base@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.2.tgz#f97ba983f1e825fec49cba8ff6a23d00e1901819"
|
||||
integrity sha512-j81CpadRLhxikBYHMh91X4aTxfzFnmmebEiIR9rruS6dywWCxV2aLW87l9ocD1MiueNam0ysdwZkX4F3D4csNw==
|
||||
apollo-server-plugin-base@0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.5.3.tgz#234c6330c412a2e83ff49305a0c2f991fb40a266"
|
||||
integrity sha512-Ax043vQTzPgFeJk6m6hmPm9NMfogO3LlTKJfrWHuyZhPNeTLweHNK30vpdzzgPalcryyDMDfLYFzxuDm0W+rRQ==
|
||||
|
||||
apollo-server-testing@~2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.3.tgz#a0199a5d42000e60ecf0dea44b851f5f581e280e"
|
||||
integrity sha512-LTkegcGVSkM+pA0FINDSYVl3TiFYKZyfjlKrEr/LN6wLiL6gbRgy6LMtk2j+qli/bnTDqqQREX8OEqmV8FKUoQ==
|
||||
apollo-server-testing@~2.6.4:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.6.4.tgz#615dfaa6ea840b6ea3ce4fd2297b28402f2d5208"
|
||||
integrity sha512-s9AeZnnndhz4WRBmgFM388BFKqD2H90L6ax0e6uNEmtZk3/iODqd16RbTNHbx+PkxFvZ8BQbX1/4rbvQn6r9CA==
|
||||
dependencies:
|
||||
apollo-server-core "2.6.3"
|
||||
apollo-server-core "2.6.4"
|
||||
|
||||
apollo-server@~2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.3.tgz#71235325449c6d3881a5143975ca44c07a07d2d7"
|
||||
integrity sha512-pTIXE5xEMAikKLTIBIqLNvimMETiZbzmiqDb6BGzIUicAz4Rxa1/+bDi1ZeJWrZQjE/TfBLd2Si3qam7dZGrjw==
|
||||
apollo-server@~2.6.4:
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.6.4.tgz#34b3a50135e20b8df8c194a14e4636eb9c2898b2"
|
||||
integrity sha512-f0TZOc969XNNlSm8sVsU34D8caQfPNwS0oqmWUxb8xXl88HlFzB+HBmOU6ZEKdpMCksTNDbqYo0jXiGJ0rL/0g==
|
||||
dependencies:
|
||||
apollo-server-core "2.6.3"
|
||||
apollo-server-express "2.6.3"
|
||||
apollo-server-core "2.6.4"
|
||||
apollo-server-express "2.6.4"
|
||||
express "^4.0.0"
|
||||
graphql-subscriptions "^1.0.0"
|
||||
graphql-tools "^4.0.0"
|
||||
@ -1566,7 +1557,7 @@ apollo-upload-server@^7.0.0:
|
||||
http-errors "^1.7.0"
|
||||
object-path "^0.11.4"
|
||||
|
||||
apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.2.1, apollo-utilities@^1.3.2:
|
||||
apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.2.tgz#8cbdcf8b012f664cd6cb5767f6130f5aed9115c9"
|
||||
integrity sha512-JWNHj8XChz7S4OZghV6yc9FNnzEXj285QYp/nLNh943iObycI5GTDO3NGR9Dth12LRrSFMeDOConPfPln+WGfg==
|
||||
@ -1790,10 +1781,10 @@ babel-core@~7.0.0-0:
|
||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
||||
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
|
||||
|
||||
babel-eslint@~10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
|
||||
integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
|
||||
babel-eslint@~10.0.2:
|
||||
version "10.0.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
|
||||
integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@babel/parser" "^7.0.0"
|
||||
@ -2070,6 +2061,13 @@ busboy@^0.2.14:
|
||||
dicer "0.2.5"
|
||||
readable-stream "1.1.x"
|
||||
|
||||
busboy@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
|
||||
integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==
|
||||
dependencies:
|
||||
dicer "0.3.0"
|
||||
|
||||
bytes@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
@ -2586,10 +2584,10 @@ data-urls@^1.0.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@2.0.0-alpha.33:
|
||||
version "2.0.0-alpha.33"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.33.tgz#c2f73c3cc50ac301c9217eb93603c9bc40e891bf"
|
||||
integrity sha512-tqUVEk3oxnJuNIvwAMKHAMo4uFRG0zXvjxZQll+BonoPt+m4NMcUgO14NDxbHuy7uYcrVErd2GdSsw02EDZQ7w==
|
||||
date-fns@2.0.0-alpha.35:
|
||||
version "2.0.0-alpha.35"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.35.tgz#185813cdc51b05cc1468a95116494bb3f3440934"
|
||||
integrity sha512-dAY1ujqRtyUsa9mVeupyMzUluWo1d7kAMwyXTQHFImKYSHKvxDw/dipiY6fdswQOs8CwpGoiKysGfaaRP5r3bA==
|
||||
|
||||
debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
@ -2721,6 +2719,13 @@ dicer@0.2.5:
|
||||
readable-stream "1.1.x"
|
||||
streamsearch "0.1.2"
|
||||
|
||||
dicer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
|
||||
integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==
|
||||
dependencies:
|
||||
streamsearch "0.1.2"
|
||||
|
||||
diff-sequences@^24.3.0:
|
||||
version "24.3.0"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
|
||||
@ -2995,10 +3000,10 @@ escodegen@^1.9.1:
|
||||
optionalDependencies:
|
||||
source-map "~0.6.1"
|
||||
|
||||
eslint-config-prettier@~4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-4.3.0.tgz#c55c1fcac8ce4518aeb77906984e134d9eb5a4f0"
|
||||
integrity sha512-sZwhSTHVVz78+kYD3t5pCWSYEdVSBR0PXnwjDRsUs8ytIrK8PLXw+6FKp8r3Z7rx4ZszdetWlXYKOHoUrrwPlA==
|
||||
eslint-config-prettier@~5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-5.0.0.tgz#f7a94b2b8ae7cbf25842c36fa96c6d32cd0a697c"
|
||||
integrity sha512-c17Aqiz5e8LEqoc/QPmYnaxQFAHTx2KlCZBPxXXjEMmNchOLnV/7j0HoPZuC+rL/tDC9bazUYOKJW9bOhftI/w==
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
|
||||
@ -3048,10 +3053,10 @@ eslint-plugin-import@~2.17.3:
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.11.0"
|
||||
|
||||
eslint-plugin-jest@~22.6.4:
|
||||
version "22.6.4"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.6.4.tgz#2895b047dd82f90f43a58a25cf136220a21c9104"
|
||||
integrity sha512-36OqnZR/uMCDxXGmTsqU4RwllR0IiB/XF8GW3ODmhsjiITKuI0GpgultWFt193ipN3HARkaIcKowpE6HBvRHNg==
|
||||
eslint-plugin-jest@~22.7.0:
|
||||
version "22.7.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.7.0.tgz#a1d325bccb024b04f5354c56fe790baba54a454c"
|
||||
integrity sha512-0U9nBd9V6+GKpM/KvRDcmMuPsewSsdM7NxCozgJkVAh8IrwHmQ0aw44/eYuVkhT8Fcdhsz0zYiyPtKg147eXMQ==
|
||||
|
||||
eslint-plugin-node@~9.1.0:
|
||||
version "9.1.0"
|
||||
@ -3535,6 +3540,11 @@ fs-capacitor@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-1.0.1.tgz#ff9dbfa14dfaf4472537720f19c3088ed9278df0"
|
||||
integrity sha512-XdZK0Q78WP29Vm3FGgJRhRhrBm51PagovzWtW2kJ3Q6cYJbGtZqWSGTSPwvtEkyjIirFd7b8Yes/dpOYjt4RRQ==
|
||||
|
||||
fs-capacitor@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-2.0.4.tgz#5a22e72d40ae5078b4fe64fe4d08c0d3fc88ad3c"
|
||||
integrity sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA==
|
||||
|
||||
fs-minipass@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
|
||||
@ -3718,6 +3728,13 @@ graphql-extensions@0.7.2:
|
||||
dependencies:
|
||||
"@apollographql/apollo-tools" "^0.3.6"
|
||||
|
||||
graphql-extensions@0.7.3:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.7.3.tgz#2ab7331c72ae657e4cbfa4ff004c400b19f56cdf"
|
||||
integrity sha512-D+FZM0t5gFntJUizeRCurZuUqsyVP13CRejRX+cDJivyGkE6OMWYkCWlzHcbye79q+hYN1m6a3NhlrJRaD9D0w==
|
||||
dependencies:
|
||||
"@apollographql/apollo-tools" "^0.3.6"
|
||||
|
||||
graphql-extensions@^0.0.x, graphql-extensions@~0.0.9:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.0.10.tgz#34bdb2546d43f6a5bc89ab23c295ec0466c6843d"
|
||||
@ -3772,12 +3789,12 @@ graphql-request@~1.8.2:
|
||||
dependencies:
|
||||
cross-fetch "2.2.2"
|
||||
|
||||
graphql-shield@~5.3.8:
|
||||
version "5.3.8"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.8.tgz#f9e7ad2285f6cfbe20a8a49154ce6c1b184e3893"
|
||||
integrity sha512-33rQ8U5jMurHIapctHk7hBcUg3nxC7fmMIMtyWiomJXhBmztFq/SG7jNaapnL5M7Q/0BmoaSQd3FLSpelP9KPw==
|
||||
graphql-shield@~5.7.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.7.1.tgz#04095fb8148a463997f7c509d4aeb2a6abf79f98"
|
||||
integrity sha512-UZ0K1uAqRAoGA1U2DsUu4vIZX2Vents4Xim99GFEUBTgvSDkejiE+k/Dywqfu76lJFEE8qu3vG5fhJN3SmnKbA==
|
||||
dependencies:
|
||||
"@types/yup" "0.26.16"
|
||||
"@types/yup" "0.26.17"
|
||||
lightercollective "^0.3.0"
|
||||
object-hash "^1.3.1"
|
||||
yup "^0.27.0"
|
||||
@ -3812,20 +3829,20 @@ graphql-tools@^4.0.0, graphql-tools@^4.0.4:
|
||||
iterall "^1.1.3"
|
||||
uuid "^3.1.0"
|
||||
|
||||
graphql-upload@^8.0.2:
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-8.0.2.tgz#1c1f116f15b7f8485cf40ff593a21368f0f58856"
|
||||
integrity sha512-u8a5tKPfJ0rU4MY+B3skabL8pEjMkm3tUzq25KBx6nT0yEWmqUO7Z5tdwvwYLFpkLwew94Gue0ARbZtar3gLTw==
|
||||
graphql-upload@^8.0.0, graphql-upload@^8.0.2:
|
||||
version "8.0.7"
|
||||
resolved "https://registry.yarnpkg.com/graphql-upload/-/graphql-upload-8.0.7.tgz#8644264e241529552ea4b3797e7ee15809cf01a3"
|
||||
integrity sha512-gi2yygbDPXbHPC7H0PNPqP++VKSoNoJO4UrXWq4T0Bi4IhyUd3Ycop/FSxhx2svWIK3jdXR/i0vi91yR1aAF0g==
|
||||
dependencies:
|
||||
busboy "^0.2.14"
|
||||
fs-capacitor "^1.0.0"
|
||||
http-errors "^1.7.1"
|
||||
busboy "^0.3.1"
|
||||
fs-capacitor "^2.0.4"
|
||||
http-errors "^1.7.2"
|
||||
object-path "^0.11.4"
|
||||
|
||||
graphql-yoga@~1.17.4:
|
||||
version "1.17.4"
|
||||
resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-1.17.4.tgz#6d325a6270399edf0776fb5f60a2e9e19512e63c"
|
||||
integrity sha512-zOXFtmS43xDLoECKiuA3xVWH/wLDvLH1D/5fBKcaMFdF43ifDfnA9N6dlGggqAoOhqBnrqHwDpoKlJ6sI1LuxA==
|
||||
graphql-yoga@~1.18.0:
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/graphql-yoga/-/graphql-yoga-1.18.0.tgz#2668278e94a0bd1b2ff8c60f928c4e18d62e381a"
|
||||
integrity sha512-WEibitQA2oFTmD7XBO8/ps8DWeVpkzOzgbB3EvtM2oIpyGhPCzRZYrC7OS9MmijvRwLRXsgHImHWUm82ZrIOWA==
|
||||
dependencies:
|
||||
"@types/cors" "^2.8.4"
|
||||
"@types/express" "^4.11.1"
|
||||
@ -3847,6 +3864,7 @@ graphql-yoga@~1.17.4:
|
||||
graphql-playground-middleware-lambda "1.7.12"
|
||||
graphql-subscriptions "^0.5.8"
|
||||
graphql-tools "^4.0.0"
|
||||
graphql-upload "^8.0.0"
|
||||
subscriptions-transport-ws "^0.9.8"
|
||||
|
||||
"graphql@^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0", graphql@^14.2.1, graphql@~14.3.1:
|
||||
@ -4045,7 +4063,7 @@ htmlparser2@^3.10.0, htmlparser2@^3.9.1:
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^3.0.6"
|
||||
|
||||
http-errors@1.7.2, http-errors@~1.7.2:
|
||||
http-errors@1.7.2, http-errors@^1.7.2, http-errors@~1.7.2:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||
@ -4056,7 +4074,7 @@ http-errors@1.7.2, http-errors@~1.7.2:
|
||||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
http-errors@^1.7.0, http-errors@^1.7.1:
|
||||
http-errors@^1.7.0:
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.1.tgz#6a4ffe5d35188e1c39f872534690585852e1f027"
|
||||
integrity sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==
|
||||
@ -5693,6 +5711,11 @@ node-releases@^1.1.19:
|
||||
dependencies:
|
||||
semver "^5.3.0"
|
||||
|
||||
nodemailer@^6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.2.1.tgz#20d773925eb8f7a06166a0b62c751dc8290429f3"
|
||||
integrity sha512-TagB7iuIi9uyNgHExo8lUDq3VK5/B0BpbkcjIgNvxbtVrjNqq0DwAOTuzALPVkK76kMhTSzIgHqg8X1uklVs6g==
|
||||
|
||||
nodemon@~1.19.1:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.1.tgz#576f0aad0f863aabf8c48517f6192ff987cd5071"
|
||||
@ -7543,13 +7566,6 @@ trunc-text@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.1.tgz#58f876d8ac59b224b79834bb478b8656e69622b5"
|
||||
integrity sha1-WPh22KxZsiS3mDS7R4uGVuaWIrU=
|
||||
|
||||
ts-invariant@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0"
|
||||
integrity sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
|
||||
ts-invariant@^0.4.0:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.2.tgz#8685131b8083e67c66d602540e78763408be9113"
|
||||
@ -8112,10 +8128,10 @@ yup@^0.27.0:
|
||||
synchronous-promise "^2.0.6"
|
||||
toposort "^2.0.2"
|
||||
|
||||
zen-observable-ts@^0.8.18:
|
||||
version "0.8.18"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.18.tgz#ade44b1060cc4a800627856ec10b9c67f5f639c8"
|
||||
integrity sha512-q7d05s75Rn1j39U5Oapg3HI2wzriVwERVo4N7uFGpIYuHB9ff02P/E92P9B8T7QVC93jCMHpbXH7X0eVR5LA7A==
|
||||
zen-observable-ts@^0.8.19:
|
||||
version "0.8.19"
|
||||
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.19.tgz#c094cd20e83ddb02a11144a6e2a89706946b5694"
|
||||
integrity sha512-u1a2rpE13G+jSzrg3aiCqXU5tN2kw41b+cBZGmnc+30YimdkKiDj9bTowcB41eL77/17RF/h+393AuVgShyheQ==
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
zen-observable "^0.8.0"
|
||||
|
||||
@ -5,6 +5,11 @@ data:
|
||||
MONGODB_PASSWORD: "TU9OR09EQl9QQVNTV09SRA=="
|
||||
PRIVATE_KEY_PASSPHRASE: "YTdkc2Y3OHNhZGc4N2FkODdzZmFnc2FkZzc4"
|
||||
MAPBOX_TOKEN: "cGsuZXlKMUlqb2lhSFZ0WVc0dFkyOXVibVZqZEdsdmJpSXNJbUVpT2lKamFqbDBjbkJ1Ykdvd2VUVmxNM1Z3WjJsek5UTnVkM1p0SW4wLktaOEtLOWw3MG9talhiRWtrYkhHc1EK"
|
||||
SMTP_HOST:
|
||||
SMTP_PORT: 587
|
||||
SMTP_USERNAME:
|
||||
SMTP_PASSWORD:
|
||||
SMTP_IGNORE_TLS:
|
||||
metadata:
|
||||
name: human-connection
|
||||
namespace: human-connection
|
||||
|
||||
@ -45,7 +45,7 @@ MERGE(b:Badge {id: badge._id["$oid"]})
|
||||
ON CREATE SET
|
||||
b.key = badge.key,
|
||||
b.type = badge.type,
|
||||
b.icon = badge.image.path,
|
||||
b.icon = replace(badge.image.path, 'https://api-alpha.human-connection.org', ''),
|
||||
b.status = badge.status,
|
||||
b.createdAt = badge.createdAt.`$date`,
|
||||
b.updatedAt = badge.updatedAt.`$date`
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
[?] unique: true, // Unique value is not enforced in Nitro?
|
||||
[-] index: true
|
||||
},
|
||||
[ ] type: {
|
||||
[ ] type: { // db.getCollection('contributions').distinct('type') -> 'DELETED', 'cando', 'post'
|
||||
[ ] type: String,
|
||||
[ ] required: true,
|
||||
[-] index: true
|
||||
@ -50,7 +50,7 @@
|
||||
[?] required: true // Not required in Nitro
|
||||
},
|
||||
[ ] hasMore: { type: Boolean },
|
||||
[?] teaserImg: { type: String }, // Path is incorrect in Nitro
|
||||
[X] teaserImg: { type: String },
|
||||
[ ] language: {
|
||||
[ ] type: String,
|
||||
[ ] required: true,
|
||||
@ -131,7 +131,7 @@ MERGE (p:Post {id: post._id["$oid"]})
|
||||
ON CREATE SET
|
||||
p.title = post.title,
|
||||
p.slug = post.slug,
|
||||
p.image = post.teaserImg,
|
||||
p.image = replace(post.teaserImg, 'https://api-alpha.human-connection.org', ''),
|
||||
p.content = post.content,
|
||||
p.contentExcerpt = post.contentExcerpt,
|
||||
p.visibility = toLower(post.visibility),
|
||||
|
||||
@ -49,8 +49,8 @@
|
||||
}
|
||||
},
|
||||
[ ] timezone: { type: String },
|
||||
[?] avatar: { type: String }, // Path is incorrect in Nitro
|
||||
[?] coverImg: { type: String }, // Path is incorrect in Nitro, was not modeled in latest Nitro - do we want this?
|
||||
[X] avatar: { type: String },
|
||||
[X] coverImg: { type: String },
|
||||
[ ] doiToken: { type: String },
|
||||
[ ] confirmedAt: { type: Date },
|
||||
[?] badgeIds: [], // Verify this is working properly
|
||||
@ -102,8 +102,8 @@ u.name = user.name,
|
||||
u.slug = user.slug,
|
||||
u.email = user.email,
|
||||
u.password = user.password,
|
||||
u.avatar = user.avatar,
|
||||
u.coverImg = user.coverImg,
|
||||
u.avatar = replace(user.avatar, 'https://api-alpha.human-connection.org', ''),
|
||||
u.coverImg = replace(user.coverImg, 'https://api-alpha.human-connection.org', ''),
|
||||
u.wasInvited = user.wasInvited,
|
||||
u.wasSeeded = user.wasSeeded,
|
||||
u.role = toLower(user.role),
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
version: "3.4"
|
||||
|
||||
services:
|
||||
mailserver:
|
||||
image: djfarrelly/maildev
|
||||
ports:
|
||||
- 1080:80
|
||||
networks:
|
||||
- hc-network
|
||||
webapp:
|
||||
build:
|
||||
context: webapp
|
||||
@ -20,6 +26,10 @@ services:
|
||||
- backend_node_modules:/nitro-backend/node_modules
|
||||
- uploads:/nitro-backend/public/uploads
|
||||
command: yarn run dev
|
||||
environment:
|
||||
- SMTP_HOST=mailserver
|
||||
- SMTP_PORT=25
|
||||
- SMTP_IGNORE_TLS=true
|
||||
neo4j:
|
||||
environment:
|
||||
- NEO4J_AUTH=none
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { config, mount, createLocalVue } from '@vue/test-utils'
|
||||
import ContributionForm from './index.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
config.stubs['no-ssr'] = '<span><slot /></span>'
|
||||
@ -55,8 +57,16 @@ describe('ContributionForm.vue', () => {
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const getters = {
|
||||
'editor/placeholder': () => {
|
||||
return 'some cool placeholder'
|
||||
},
|
||||
}
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
const Wrapper = () => {
|
||||
return mount(ContributionForm, { mocks, localVue, computed })
|
||||
return mount(ContributionForm, { mocks, localVue, computed, store })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -4,7 +4,12 @@
|
||||
<ds-card>
|
||||
<ds-input model="title" class="post-title" placeholder="Title" name="title" autofocus />
|
||||
<no-ssr>
|
||||
<hc-editor :users="users" :value="form.content" @input="updateEditorContent" />
|
||||
<hc-editor
|
||||
:users="users"
|
||||
:hashtags="hashtags"
|
||||
:value="form.content"
|
||||
@input="updateEditorContent"
|
||||
/>
|
||||
</no-ssr>
|
||||
<ds-space margin-bottom="xxx-large" />
|
||||
<ds-input
|
||||
@ -89,6 +94,7 @@ export default {
|
||||
disabled: false,
|
||||
slug: null,
|
||||
users: [],
|
||||
hashtags: [],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -173,17 +179,34 @@ export default {
|
||||
apollo: {
|
||||
User: {
|
||||
query() {
|
||||
return gql(`{
|
||||
User(orderBy: slug_asc) {
|
||||
id
|
||||
slug
|
||||
return gql`
|
||||
{
|
||||
User(orderBy: slug_asc) {
|
||||
id
|
||||
slug
|
||||
}
|
||||
}
|
||||
}`)
|
||||
`
|
||||
},
|
||||
result(result) {
|
||||
this.users = result.data.User
|
||||
},
|
||||
},
|
||||
Tag: {
|
||||
query() {
|
||||
return gql`
|
||||
{
|
||||
Tag(orderBy: name_asc) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
result(result) {
|
||||
this.hashtags = result.data.Tag
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -83,6 +83,7 @@ export default {
|
||||
deleteContributions: false,
|
||||
deleteComments: false,
|
||||
deleteEnabled: false,
|
||||
enableDeletionValue: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
@ -3,17 +3,19 @@
|
||||
<div v-show="showSuggestions" ref="suggestions" class="suggestion-list">
|
||||
<template v-if="hasResults">
|
||||
<div
|
||||
v-for="(user, index) in filteredUsers"
|
||||
:key="user.id"
|
||||
v-for="(item, index) in filteredItems"
|
||||
:key="item.id"
|
||||
class="suggestion-list__item"
|
||||
:class="{ 'is-selected': navigatedUserIndex === index }"
|
||||
@click="selectUser(user)"
|
||||
:class="{ 'is-selected': navigatedItemIndex === index }"
|
||||
@click="selectItem(item)"
|
||||
>
|
||||
@{{ user.slug }}
|
||||
<div v-if="isMention">@{{ item.slug }}</div>
|
||||
<div v-if="isHashtag">#{{ item.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="suggestion-list__item is-empty">
|
||||
No users found
|
||||
<div v-if="isMention">{{ $t('editor.mention.noUsersFound') }}</div>
|
||||
<div v-if="isHashtag">{{ $t('editor.hashtag.noHashtagsFound') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -175,6 +177,8 @@ import {
|
||||
History,
|
||||
} from 'tiptap-extensions'
|
||||
import Mention from './nodes/Mention.js'
|
||||
import Hashtag from './nodes/Hashtag.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
let throttleInputEvent
|
||||
|
||||
@ -186,6 +190,7 @@ export default {
|
||||
},
|
||||
props: {
|
||||
users: { type: Array, default: () => [] },
|
||||
hashtags: { type: Array, default: () => [] },
|
||||
value: { type: String, default: '' },
|
||||
doc: { type: Object, default: () => {} },
|
||||
},
|
||||
@ -212,38 +217,40 @@ export default {
|
||||
new ListItem(),
|
||||
new Placeholder({
|
||||
emptyNodeClass: 'is-empty',
|
||||
emptyNodeText: this.$t('editor.placeholder'),
|
||||
emptyNodeText: this.placeholder || this.$t('editor.placeholder'),
|
||||
}),
|
||||
new History(),
|
||||
new Mention({
|
||||
items: () => {
|
||||
this.suggestionType = this.mentionSuggestionType
|
||||
return this.users
|
||||
},
|
||||
onEnter: ({ items, query, range, command, virtualNode }) => {
|
||||
this.query = query
|
||||
this.filteredUsers = items
|
||||
this.filteredItems = items
|
||||
this.suggestionRange = range
|
||||
this.renderPopup(virtualNode)
|
||||
// we save the command for inserting a selected mention
|
||||
// this allows us to call it inside of our custom popup
|
||||
// via keyboard navigation and on click
|
||||
this.insertMention = command
|
||||
this.insertMentionOrHashtag = command
|
||||
},
|
||||
// is called when a suggestion has changed
|
||||
onChange: ({ items, query, range, virtualNode }) => {
|
||||
this.query = query
|
||||
this.filteredUsers = items
|
||||
this.filteredItems = items
|
||||
this.suggestionRange = range
|
||||
this.navigatedUserIndex = 0
|
||||
this.navigatedItemIndex = 0
|
||||
this.renderPopup(virtualNode)
|
||||
},
|
||||
// is called when a suggestion is cancelled
|
||||
onExit: () => {
|
||||
// reset all saved values
|
||||
this.suggestionType = this.nullSuggestionType
|
||||
this.query = null
|
||||
this.filteredUsers = []
|
||||
this.filteredItems = []
|
||||
this.suggestionRange = null
|
||||
this.navigatedUserIndex = 0
|
||||
this.navigatedItemIndex = 0
|
||||
this.destroyPopup()
|
||||
},
|
||||
// is called on every keyDown event while a suggestion is active
|
||||
@ -260,7 +267,74 @@ export default {
|
||||
}
|
||||
// pressing enter
|
||||
if (event.keyCode === 13) {
|
||||
this.enterHandler()
|
||||
this.enterHandler(this.mentionSuggestionType)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
// is called when a suggestion has changed
|
||||
// this function is optional because there is basic filtering built-in
|
||||
// you can overwrite it if you prefer your own filtering
|
||||
// in this example we use fuse.js with support for fuzzy search
|
||||
onFilter: (items, query) => {
|
||||
if (!query) {
|
||||
return items
|
||||
}
|
||||
const fuse = new Fuse(items, {
|
||||
threshold: 0.2,
|
||||
keys: ['slug'],
|
||||
})
|
||||
return fuse.search(query)
|
||||
},
|
||||
}),
|
||||
new Hashtag({
|
||||
items: () => {
|
||||
this.suggestionType = this.hashtagSuggestionType
|
||||
return this.hashtags
|
||||
},
|
||||
onEnter: ({ items, query, range, command, virtualNode }) => {
|
||||
this.query = query
|
||||
this.filteredItems = items
|
||||
this.suggestionRange = range
|
||||
this.renderPopup(virtualNode)
|
||||
// we save the command for inserting a selected mention
|
||||
// this allows us to call it inside of our custom popup
|
||||
// via keyboard navigation and on click
|
||||
this.insertMentionOrHashtag = command
|
||||
},
|
||||
// is called when a suggestion has changed
|
||||
onChange: ({ items, query, range, virtualNode }) => {
|
||||
this.query = query
|
||||
this.filteredItems = items
|
||||
this.suggestionRange = range
|
||||
this.navigatedItemIndex = 0
|
||||
this.renderPopup(virtualNode)
|
||||
},
|
||||
// is called when a suggestion is cancelled
|
||||
onExit: () => {
|
||||
// reset all saved values
|
||||
this.suggestionType = this.nullSuggestionType
|
||||
this.query = null
|
||||
this.filteredItems = []
|
||||
this.suggestionRange = null
|
||||
this.navigatedItemIndex = 0
|
||||
this.destroyPopup()
|
||||
},
|
||||
// is called on every keyDown event while a suggestion is active
|
||||
onKeyDown: ({ event }) => {
|
||||
// pressing up arrow
|
||||
if (event.keyCode === 38) {
|
||||
this.upHandler()
|
||||
return true
|
||||
}
|
||||
// pressing down arrow
|
||||
if (event.keyCode === 40) {
|
||||
this.downHandler()
|
||||
return true
|
||||
}
|
||||
// pressing enter or pressing space
|
||||
if (event.keyCode === 13 || event.keyCode === 32) {
|
||||
this.enterHandler(this.hashtagSuggestionType)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -288,21 +362,32 @@ export default {
|
||||
}),
|
||||
linkUrl: null,
|
||||
linkMenuIsActive: false,
|
||||
nullSuggestionType: '',
|
||||
mentionSuggestionType: 'mention',
|
||||
hashtagSuggestionType: 'hashtag',
|
||||
suggestionType: this.nullSuggestionType,
|
||||
query: null,
|
||||
suggestionRange: null,
|
||||
filteredUsers: [],
|
||||
navigatedUserIndex: 0,
|
||||
insertMention: () => {},
|
||||
filteredItems: [],
|
||||
navigatedItemIndex: 0,
|
||||
insertMentionOrHashtag: () => {},
|
||||
observer: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ placeholder: 'editor/placeholder' }),
|
||||
hasResults() {
|
||||
return this.filteredUsers.length
|
||||
return this.filteredItems.length
|
||||
},
|
||||
showSuggestions() {
|
||||
return this.query || this.hasResults
|
||||
},
|
||||
isMention() {
|
||||
return this.suggestionType === this.mentionSuggestionType
|
||||
},
|
||||
isHashtag() {
|
||||
return this.suggestionType === this.hashtagSuggestionType
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
@ -316,47 +401,58 @@ export default {
|
||||
this.editor.setContent(content)
|
||||
},
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$root.$on('changeLanguage', () => {
|
||||
this.changePlaceHolderText()
|
||||
})
|
||||
placeholder: {
|
||||
immediate: true,
|
||||
handler: function(val) {
|
||||
if (!val) {
|
||||
return
|
||||
}
|
||||
this.editor.extensions.options.placeholder.emptyNodeText = val
|
||||
},
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off('changeLanguage')
|
||||
this.editor.destroy()
|
||||
},
|
||||
methods: {
|
||||
changePlaceHolderText() {
|
||||
this.editor.extensions.options.placeholder.emptyNodeText = this.$t('editor.placeholder')
|
||||
},
|
||||
// navigate to the previous item
|
||||
// if it's the first item, navigate to the last one
|
||||
upHandler() {
|
||||
this.navigatedUserIndex =
|
||||
(this.navigatedUserIndex + this.filteredUsers.length - 1) % this.filteredUsers.length
|
||||
this.navigatedItemIndex =
|
||||
(this.navigatedItemIndex + this.filteredItems.length - 1) % this.filteredItems.length
|
||||
},
|
||||
// navigate to the next item
|
||||
// if it's the last item, navigate to the first one
|
||||
downHandler() {
|
||||
this.navigatedUserIndex = (this.navigatedUserIndex + 1) % this.filteredUsers.length
|
||||
this.navigatedItemIndex = (this.navigatedItemIndex + 1) % this.filteredItems.length
|
||||
},
|
||||
// For hashtags handles pressing of space as enter.
|
||||
enterHandler() {
|
||||
const user = this.filteredUsers[this.navigatedUserIndex]
|
||||
if (user) {
|
||||
this.selectUser(user)
|
||||
const item = this.filteredItems[this.navigatedItemIndex]
|
||||
if (item) {
|
||||
this.selectItem(item)
|
||||
} else if (this.suggestionType === this.hashtagSuggestionType && this.query !== '') {
|
||||
this.selectItem({ name: this.query }, this.hashtagSuggestionType)
|
||||
}
|
||||
},
|
||||
// we have to replace our suggestion text with a mention
|
||||
// so it's important to pass also the position of your suggestion text
|
||||
selectUser(user) {
|
||||
this.insertMention({
|
||||
range: this.suggestionRange,
|
||||
attrs: {
|
||||
selectItem(item) {
|
||||
const typeAttrs = {
|
||||
mention: {
|
||||
// TODO: use router here
|
||||
url: `/profile/${user.id}`,
|
||||
label: user.slug,
|
||||
url: `/profile/${item.id}`,
|
||||
label: item.slug,
|
||||
},
|
||||
hashtag: {
|
||||
// TODO: Fill up with input hashtag in search field
|
||||
url: `/search/hashtag:${item.name}`,
|
||||
label: item.name,
|
||||
},
|
||||
}
|
||||
this.insertMentionOrHashtag({
|
||||
range: this.suggestionRange,
|
||||
attrs: typeAttrs[this.suggestionType],
|
||||
})
|
||||
this.editor.focus()
|
||||
},
|
||||
@ -535,6 +631,12 @@ li > p {
|
||||
.mention-suggestion {
|
||||
color: $color-primary;
|
||||
}
|
||||
.hashtag {
|
||||
color: $color-primary;
|
||||
}
|
||||
.hashtag-suggestion {
|
||||
color: $color-primary;
|
||||
}
|
||||
&__floating-menu {
|
||||
position: absolute;
|
||||
margin-top: -0.25rem;
|
||||
|
||||
44
webapp/components/Editor/nodes/Hashtag.js
Normal file
44
webapp/components/Editor/nodes/Hashtag.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { Mention as TipTapMention } from 'tiptap-extensions'
|
||||
|
||||
export default class Hashtag extends TipTapMention {
|
||||
get name() {
|
||||
return 'hashtag'
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
matcher: {
|
||||
char: '#',
|
||||
allowSpaces: false,
|
||||
startOfLine: false,
|
||||
},
|
||||
mentionClass: 'hashtag',
|
||||
suggestionClass: 'hashtag-suggestion',
|
||||
}
|
||||
}
|
||||
|
||||
get schema() {
|
||||
const patchedSchema = super.schema
|
||||
|
||||
patchedSchema.attrs = {
|
||||
url: {},
|
||||
label: {},
|
||||
}
|
||||
patchedSchema.toDOM = node => {
|
||||
return [
|
||||
'a',
|
||||
{
|
||||
class: this.options.mentionClass,
|
||||
href: node.attrs.url,
|
||||
target: '_blank',
|
||||
// contenteditable: 'true',
|
||||
},
|
||||
`${this.options.matcher.char}${node.attrs.label}`,
|
||||
]
|
||||
}
|
||||
patchedSchema.parseDOM = [
|
||||
// this is not implemented
|
||||
]
|
||||
return patchedSchema
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,10 @@
|
||||
import { Mention as TipTapMention } from 'tiptap-extensions'
|
||||
|
||||
export default class Mention extends TipTapMention {
|
||||
get name() {
|
||||
return 'mention'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
const patchedSchema = super.schema
|
||||
|
||||
|
||||
@ -1,31 +1,43 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Editor from './'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('Editor.vue', () => {
|
||||
let wrapper
|
||||
let propsData
|
||||
let mocks
|
||||
let getters
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
mocks = {
|
||||
$t: () => {},
|
||||
}
|
||||
getters = {
|
||||
'editor/placeholder': () => {
|
||||
return 'some cool placeholder'
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
let Wrapper = () => {
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
return (wrapper = mount(Editor, {
|
||||
mocks,
|
||||
propsData,
|
||||
localVue,
|
||||
sync: false,
|
||||
stubs: { transition: false },
|
||||
store,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -43,5 +55,13 @@ describe('Editor.vue', () => {
|
||||
expect(wrapper.find('.ProseMirror').text()).toContain('I am a piece of text')
|
||||
})
|
||||
})
|
||||
|
||||
describe('uses the placeholder', () => {
|
||||
it('from the store', () => {
|
||||
expect(wrapper.vm.editor.extensions.options.placeholder.emptyNodeText).toEqual(
|
||||
'some cool placeholder',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import FilterMenu from './FilterMenu.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import VTooltip from 'v-tooltip'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(VTooltip)
|
||||
|
||||
describe('FilterMenu.vue', () => {
|
||||
let wrapper
|
||||
|
||||
@ -2,13 +2,16 @@
|
||||
<ds-card>
|
||||
<ds-flex>
|
||||
<ds-flex-item class="filter-menu-title">
|
||||
<ds-heading size="h3">
|
||||
{{ $t('filter-menu.title') }}
|
||||
</ds-heading>
|
||||
<ds-heading size="h3">{{ $t('filter-menu.title') }}</ds-heading>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item>
|
||||
<div class="filter-menu-buttons">
|
||||
<ds-button
|
||||
v-tooltip="{
|
||||
content: this.$t('contribution.filterFollow'),
|
||||
placement: 'left',
|
||||
delay: { show: 500 },
|
||||
}"
|
||||
name="filter-by-followed-authors-only"
|
||||
icon="user-plus"
|
||||
:primary="!!filterAuthorIsFollowedById"
|
||||
|
||||
68
webapp/components/LocaleSwitch/LocaleSwitch.spec.js
Normal file
68
webapp/components/LocaleSwitch/LocaleSwitch.spec.js
Normal file
@ -0,0 +1,68 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
import VTooltip from 'v-tooltip'
|
||||
import LocaleSwitch from './LocaleSwitch.vue'
|
||||
import { mutations } from '~/store/editor'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
localVue.use(VTooltip)
|
||||
|
||||
describe('LocaleSwitch.vue', () => {
|
||||
let wrapper
|
||||
let mocks
|
||||
let computed
|
||||
let deutschLanguageItem
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$i18n: {
|
||||
locale: () => 'de',
|
||||
set: jest.fn(),
|
||||
},
|
||||
$t: jest.fn(),
|
||||
setPlaceholderText: jest.fn(),
|
||||
}
|
||||
computed = {
|
||||
current: () => {
|
||||
return { code: 'en' }
|
||||
},
|
||||
routes: () => {
|
||||
return [
|
||||
{
|
||||
name: 'English',
|
||||
path: 'en',
|
||||
},
|
||||
{
|
||||
name: 'Deutsch',
|
||||
path: 'de',
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const store = new Vuex.Store({
|
||||
mutations: {
|
||||
'editor/SET_PLACEHOLDER_TEXT': mutations.SET_PLACEHOLDER_TEXT,
|
||||
},
|
||||
})
|
||||
const Wrapper = () => {
|
||||
return mount(LocaleSwitch, { mocks, localVue, store, computed })
|
||||
}
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('.locale-menu').trigger('click')
|
||||
deutschLanguageItem = wrapper.findAll('li').at(1)
|
||||
deutschLanguageItem.trigger('click')
|
||||
})
|
||||
|
||||
it("changes a user's locale", () => {
|
||||
expect(mocks.$i18n.set).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -36,6 +36,7 @@
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import find from 'lodash/find'
|
||||
import orderBy from 'lodash/orderBy'
|
||||
import { mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -65,10 +66,11 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({ setPlaceholderText: 'editor/SET_PLACEHOLDER_TEXT' }),
|
||||
changeLanguage(locale, toggleMenu) {
|
||||
this.$i18n.set(locale)
|
||||
toggleMenu()
|
||||
this.$root.$emit('changeLanguage')
|
||||
this.setPlaceholderText(this.$t('editor.placeholder'))
|
||||
},
|
||||
matcher(locale) {
|
||||
return locale === this.$i18n.locale()
|
||||
@ -11,18 +11,21 @@
|
||||
id="oldPassword"
|
||||
model="oldPassword"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:label="$t('settings.security.change-password.label-old-password')"
|
||||
/>
|
||||
<ds-input
|
||||
id="newPassword"
|
||||
model="newPassword"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:label="$t('settings.security.change-password.label-new-password')"
|
||||
/>
|
||||
<ds-input
|
||||
id="confirmPassword"
|
||||
model="confirmPassword"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:label="$t('settings.security.change-password.label-new-password-confirm')"
|
||||
/>
|
||||
<password-strength :password="formData.newPassword" />
|
||||
|
||||
83
webapp/components/PasswordReset/ChangePassword.spec.js
Normal file
83
webapp/components/PasswordReset/ChangePassword.spec.js
Normal file
@ -0,0 +1,83 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import ChangePassword from './ChangePassword'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('ChangePassword ', () => {
|
||||
let wrapper
|
||||
let Wrapper
|
||||
let mocks
|
||||
let propsData
|
||||
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
mocks = {
|
||||
$toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
$t: jest.fn(),
|
||||
$apollo: {
|
||||
loading: false,
|
||||
mutate: jest.fn().mockResolvedValue({ data: { resetPassword: true } }),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
Wrapper = () => {
|
||||
return mount(ChangePassword, {
|
||||
mocks,
|
||||
propsData,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
describe('given email and verification code', () => {
|
||||
beforeEach(() => {
|
||||
propsData.email = 'mail@example.org'
|
||||
propsData.code = '123456'
|
||||
})
|
||||
|
||||
describe('submitting new password', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('input#newPassword').setValue('supersecret')
|
||||
wrapper.find('input#confirmPassword').setValue('supersecret')
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('calls resetPassword graphql mutation', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('delivers new password to backend', () => {
|
||||
const expected = expect.objectContaining({
|
||||
variables: { code: '123456', email: 'mail@example.org', newPassword: 'supersecret' },
|
||||
})
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
describe('password reset successful', () => {
|
||||
it('displays success message', () => {
|
||||
const expected = 'verify-code.form.change-password.success'
|
||||
expect(mocks.$t).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
describe('after animation', () => {
|
||||
beforeEach(jest.runAllTimers)
|
||||
|
||||
it('emits `change-password-sucess`', () => {
|
||||
expect(wrapper.emitted('passwordResetResponse')).toEqual([['success']])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
140
webapp/components/PasswordReset/ChangePassword.vue
Normal file
140
webapp/components/PasswordReset/ChangePassword.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<ds-card class="verify-code">
|
||||
<ds-space margin="large">
|
||||
<template>
|
||||
<ds-form
|
||||
v-if="!changePasswordResult"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="handleSubmitPassword"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
class="change-password"
|
||||
>
|
||||
<ds-input
|
||||
id="newPassword"
|
||||
model="newPassword"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:label="$t('settings.security.change-password.label-new-password')"
|
||||
/>
|
||||
<ds-input
|
||||
id="confirmPassword"
|
||||
model="confirmPassword"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:label="$t('settings.security.change-password.label-new-password-confirm')"
|
||||
/>
|
||||
<password-strength :password="formData.newPassword" />
|
||||
<ds-space margin-top="base">
|
||||
<ds-button :loading="$apollo.loading" :disabled="disabled" primary>
|
||||
{{ $t('settings.security.change-password.button') }}
|
||||
</ds-button>
|
||||
</ds-space>
|
||||
</ds-form>
|
||||
<ds-text v-else>
|
||||
<template v-if="changePasswordResult === 'success'">
|
||||
<sweetalert-icon icon="success" />
|
||||
<ds-text>
|
||||
{{ $t(`verify-code.form.change-password.success`) }}
|
||||
</ds-text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<sweetalert-icon icon="error" />
|
||||
<ds-text align="left">
|
||||
{{ $t(`verify-code.form.change-password.error`) }}
|
||||
{{ $t('verify-code.form.change-password.help') }}
|
||||
</ds-text>
|
||||
<a href="mailto:support@human-connection.org">support@human-connection.org</a>
|
||||
</template>
|
||||
</ds-text>
|
||||
</template>
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PasswordStrength from '../Password/Strength'
|
||||
import gql from 'graphql-tag'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SweetalertIcon,
|
||||
PasswordStrength,
|
||||
},
|
||||
props: {
|
||||
email: { type: String, required: true },
|
||||
code: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
formSchema: {
|
||||
newPassword: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
message: this.$t('settings.security.change-password.message-new-password-required'),
|
||||
},
|
||||
confirmPassword: [
|
||||
{ validator: this.matchPassword },
|
||||
{
|
||||
type: 'string',
|
||||
required: true,
|
||||
message: this.$t(
|
||||
'settings.security.change-password.message-new-password-confirm-required',
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
disabled: true,
|
||||
changePasswordResult: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleInput() {
|
||||
this.disabled = true
|
||||
},
|
||||
async handleInputValid() {
|
||||
this.disabled = false
|
||||
},
|
||||
async handleSubmitPassword() {
|
||||
const mutation = gql`
|
||||
mutation($code: String!, $email: String!, $newPassword: String!) {
|
||||
resetPassword(code: $code, email: $email, newPassword: $newPassword)
|
||||
}
|
||||
`
|
||||
const { newPassword } = this.formData
|
||||
const { email, code } = this
|
||||
const variables = { newPassword, email, code }
|
||||
try {
|
||||
const {
|
||||
data: { resetPassword },
|
||||
} = await this.$apollo.mutate({ mutation, variables })
|
||||
this.changePasswordResult = resetPassword ? 'success' : 'error'
|
||||
setTimeout(() => {
|
||||
this.$emit('passwordResetResponse', this.changePasswordResult)
|
||||
}, 3000)
|
||||
this.formData = {
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
}
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
matchPassword(rule, value, callback, source, options) {
|
||||
var errors = []
|
||||
if (this.formData.newPassword !== value) {
|
||||
errors.push(
|
||||
new Error(this.$t('settings.security.change-password.message-new-password-missmatch')),
|
||||
)
|
||||
}
|
||||
callback(errors)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
77
webapp/components/PasswordReset/Request.spec.js
Normal file
77
webapp/components/PasswordReset/Request.spec.js
Normal file
@ -0,0 +1,77 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Request from './Request'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('Request', () => {
|
||||
let wrapper
|
||||
let Wrapper
|
||||
let mocks
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
$t: jest.fn(),
|
||||
$apollo: {
|
||||
loading: false,
|
||||
mutate: jest.fn().mockResolvedValue({ data: { reqestPasswordReset: true } }),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
Wrapper = () => {
|
||||
return mount(Request, {
|
||||
mocks,
|
||||
localVue,
|
||||
})
|
||||
}
|
||||
|
||||
it('renders a password reset form', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.password-reset').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('submit', () => {
|
||||
beforeEach(async () => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('input#email').setValue('mail@example.org')
|
||||
await wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('calls requestPasswordReset graphql mutation', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('delivers email to backend', () => {
|
||||
const expected = expect.objectContaining({ variables: { email: 'mail@example.org' } })
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected)
|
||||
})
|
||||
|
||||
it('hides form to avoid re-submission', () => {
|
||||
expect(wrapper.find('form').exists()).not.toBeTruthy()
|
||||
})
|
||||
|
||||
it('displays a message that a password email was requested', () => {
|
||||
const expected = ['password-reset.form.submitted', { email: 'mail@example.org' }]
|
||||
expect(mocks.$t).toHaveBeenCalledWith(...expected)
|
||||
})
|
||||
|
||||
describe('after animation', () => {
|
||||
beforeEach(jest.runAllTimers)
|
||||
|
||||
it('emits `handleSubmitted`', () => {
|
||||
expect(wrapper.emitted('handleSubmitted')).toEqual([[{ email: 'mail@example.org' }]])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
107
webapp/components/PasswordReset/Request.vue
Normal file
107
webapp/components/PasswordReset/Request.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<ds-card class="password-reset">
|
||||
<ds-space margin="large">
|
||||
<ds-form
|
||||
v-if="!submitted"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<ds-input
|
||||
:placeholder="$t('login.email')"
|
||||
type="email"
|
||||
id="email"
|
||||
model="email"
|
||||
name="email"
|
||||
icon="envelope"
|
||||
/>
|
||||
<ds-space margin-botton="large">
|
||||
<ds-text>
|
||||
{{ $t('password-reset.form.description') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<ds-button
|
||||
:disabled="disabled"
|
||||
:loading="$apollo.loading"
|
||||
primary
|
||||
fullwidth
|
||||
name="submit"
|
||||
type="submit"
|
||||
icon="envelope"
|
||||
>
|
||||
{{ $t('password-reset.form.submit') }}
|
||||
</ds-button>
|
||||
</ds-form>
|
||||
<div v-else>
|
||||
<transition name="ds-transition-fade">
|
||||
<ds-flex centered>
|
||||
<sweetalert-icon icon="info" />
|
||||
</ds-flex>
|
||||
</transition>
|
||||
<ds-text v-html="submitMessage" />
|
||||
</div>
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { SweetalertIcon } from 'vue-sweetalert-icons'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SweetalertIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
email: '',
|
||||
},
|
||||
formSchema: {
|
||||
email: {
|
||||
type: 'email',
|
||||
required: true,
|
||||
message: this.$t('common.validations.email'),
|
||||
},
|
||||
},
|
||||
disabled: true,
|
||||
submitted: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
submitMessage() {
|
||||
const { email } = this.formData
|
||||
return this.$t('password-reset.form.submitted', { email })
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleInput() {
|
||||
this.disabled = true
|
||||
},
|
||||
handleInputValid() {
|
||||
this.disabled = false
|
||||
},
|
||||
async handleSubmit() {
|
||||
const mutation = gql`
|
||||
mutation($email: String!) {
|
||||
requestPasswordReset(email: $email)
|
||||
}
|
||||
`
|
||||
const { email } = this.formData
|
||||
|
||||
try {
|
||||
await this.$apollo.mutate({ mutation, variables: { email } })
|
||||
this.submitted = true
|
||||
|
||||
setTimeout(() => {
|
||||
this.$emit('handleSubmitted', { email })
|
||||
}, 3000)
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
53
webapp/components/PasswordReset/VerifyCode.spec.js
Normal file
53
webapp/components/PasswordReset/VerifyCode.spec.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import VerifyCode from './VerifyCode'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('VerifyCode ', () => {
|
||||
let wrapper
|
||||
let Wrapper
|
||||
let mocks
|
||||
let propsData
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
}
|
||||
propsData = {
|
||||
email: 'mail@example.org',
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(jest.useFakeTimers)
|
||||
|
||||
Wrapper = () => {
|
||||
return mount(VerifyCode, {
|
||||
mocks,
|
||||
localVue,
|
||||
propsData,
|
||||
})
|
||||
}
|
||||
|
||||
it('renders a verify code form', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.verify-code').exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('after verification code given', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
wrapper.find('input#code').setValue('123456')
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('emits `verifyCode`', () => {
|
||||
const expected = [[{ code: '123456', email: 'mail@example.org' }]]
|
||||
expect(wrapper.emitted('verification')).toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
67
webapp/components/PasswordReset/VerifyCode.vue
Normal file
67
webapp/components/PasswordReset/VerifyCode.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<ds-card class="verify-code">
|
||||
<ds-space margin="large">
|
||||
<ds-form
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="handleSubmitVerify"
|
||||
@input="handleInput"
|
||||
@input-valid="handleInputValid"
|
||||
>
|
||||
<ds-input
|
||||
:placeholder="$t('verify-code.form.code')"
|
||||
model="code"
|
||||
name="code"
|
||||
id="code"
|
||||
icon="question-circle"
|
||||
/>
|
||||
<ds-space margin-botton="large">
|
||||
<ds-text>
|
||||
{{ $t('verify-code.form.description') }}
|
||||
</ds-text>
|
||||
</ds-space>
|
||||
<ds-button :disabled="disabled" primary fullwidth name="submit" type="submit">
|
||||
{{ $t('verify-code.form.next') }}
|
||||
</ds-button>
|
||||
</ds-form>
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
email: { type: String, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
code: '',
|
||||
},
|
||||
formSchema: {
|
||||
code: {
|
||||
type: 'string',
|
||||
min: 6,
|
||||
max: 6,
|
||||
required: true,
|
||||
message: this.$t('common.validations.verification-code'),
|
||||
},
|
||||
},
|
||||
disabled: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleInput() {
|
||||
this.disabled = true
|
||||
},
|
||||
async handleInputValid() {
|
||||
this.disabled = false
|
||||
},
|
||||
handleSubmitVerify() {
|
||||
const { code } = this.formData
|
||||
const email = this.email
|
||||
this.$emit('verification', { email, code })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,9 +1,10 @@
|
||||
import { mount, createLocalVue, createWrapper } from '@vue/test-utils'
|
||||
import CommentForm from './index.vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('CommentForm.vue', () => {
|
||||
@ -35,8 +36,16 @@ describe('CommentForm.vue', () => {
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const getters = {
|
||||
'editor/placeholder': () => {
|
||||
return 'some cool placeholder'
|
||||
},
|
||||
}
|
||||
const store = new Vuex.Store({
|
||||
getters,
|
||||
})
|
||||
const Wrapper = () => {
|
||||
return mount(CommentForm, { mocks, localVue, propsData })
|
||||
return mount(CommentForm, { mocks, localVue, propsData, store })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -86,7 +86,7 @@ describe('CommentList.vue', () => {
|
||||
})
|
||||
|
||||
it('displays comments when there are comments to display', () => {
|
||||
expect(wrapper.find('div#comments').text()).toEqual('this is a comment')
|
||||
expect(wrapper.find('div.comments').text()).toEqual('this is a comment')
|
||||
})
|
||||
|
||||
it("refetches a post's comments from the backend", () => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="comments">
|
||||
<h3 style="margin-top: -10px;">
|
||||
<span>
|
||||
<ds-icon name="comments" />
|
||||
@ -16,7 +16,7 @@
|
||||
</span>
|
||||
</h3>
|
||||
<ds-space margin-bottom="large" />
|
||||
<div v-if="comments && comments.length" id="comments" class="comments">
|
||||
<div v-if="comments && comments.length" class="comments">
|
||||
<comment
|
||||
v-for="(comment, index) in comments"
|
||||
:key="comment.id"
|
||||
|
||||
@ -92,7 +92,7 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
import SearchInput from '~/components/SearchInput.vue'
|
||||
import Modal from '~/components/Modal'
|
||||
import NotificationMenu from '~/components/notifications/NotificationMenu'
|
||||
|
||||
@ -8,20 +8,47 @@
|
||||
"logout": "Ausloggen",
|
||||
"email": "Deine E-Mail",
|
||||
"password": "Dein Passwort",
|
||||
"forgotPassword": "Passwort vergessen?",
|
||||
"moreInfo": "Was ist Human Connection?",
|
||||
"moreInfoURL": "https://human-connection.org",
|
||||
"moreInfoHint": "zur Präsentationsseite",
|
||||
"hello": "Hallo"
|
||||
},
|
||||
"password-reset": {
|
||||
"title": "Passwort zurücksetzen",
|
||||
"form": {
|
||||
"description": "Eine Mail zum Zurücksetzen des Passworts wird an die angegebene E-Mail Adresse geschickt.",
|
||||
"submit": "Email anfordern",
|
||||
"submitted": "Eine E-Mail mit weiteren Instruktionen wurde verschickt an <b>{email}</b>"
|
||||
}
|
||||
},
|
||||
"verify-code": {
|
||||
"form": {
|
||||
"code": "Code eingeben",
|
||||
"description": "Öffne dein E-Mail Postfach und gib den Code ein, den wir geschickt haben.",
|
||||
"next": "Weiter",
|
||||
"change-password":{
|
||||
"success": "Änderung des Passworts war erfolgreich!",
|
||||
"error": "Passwort Änderung fehlgeschlagen. Möglicherweise falscher Sicherheitscode?",
|
||||
"help": "Falls Probleme auftreten, schreib uns gerne eine Mail an:"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"placeholder": "Schreib etwas Inspirierendes..."
|
||||
"placeholder": "Schreib etwas Inspirierendes...",
|
||||
"mention": {
|
||||
"noUsersFound": "Keine Benutzer gefunden"
|
||||
},
|
||||
"hashtag": {
|
||||
"noHashtagsFound": "Keine Hashtags gefunden"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mein Profil",
|
||||
"memberSince": "Mitglied seit",
|
||||
"follow": "Folgen",
|
||||
"followers": "Folgen",
|
||||
"following": "Folgt",
|
||||
"follow": "abonnieren",
|
||||
"followers": "Abonnenten",
|
||||
"following": "abonniert",
|
||||
"shouted": "Empfohlen",
|
||||
"commented": "Kommentiert",
|
||||
"userAnonym": "Anonymus",
|
||||
@ -106,9 +133,9 @@
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"name": "Systemverwaltung",
|
||||
"name": "Admin",
|
||||
"dashboard": {
|
||||
"name": "Startzentrale",
|
||||
"name": "Dashboard",
|
||||
"users": "Benutzer",
|
||||
"posts": "Beiträge",
|
||||
"comments": "Kommentare",
|
||||
@ -117,7 +144,7 @@
|
||||
"projects": "Projekte",
|
||||
"invites": "Einladungen",
|
||||
"follows": "Folgen",
|
||||
"shouts": "Shouts"
|
||||
"shouts": "Empfehlungen"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organisationen"
|
||||
@ -193,7 +220,11 @@
|
||||
"name": "Name",
|
||||
"loadMore": "mehr laden",
|
||||
"loading": "wird geladen",
|
||||
"reportContent": "Melden"
|
||||
"reportContent": "Melden",
|
||||
"validations": {
|
||||
"email": "muss eine gültige E-Mail Adresse sein",
|
||||
"verification-code": "muss genau 6 Buchstaben lang sein"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"loading": "lade",
|
||||
@ -271,7 +302,7 @@
|
||||
},
|
||||
"followButton": {
|
||||
"follow": "Folgen",
|
||||
"following": "Folge Ich"
|
||||
"following": "Folge ich"
|
||||
},
|
||||
"shoutButton": {
|
||||
"shouted": "empfohlen"
|
||||
@ -303,9 +334,13 @@
|
||||
}
|
||||
},
|
||||
"contribution": {
|
||||
"newPost": "Erstelle einen neuen Beitrag",
|
||||
"filterFollow": "Beiträge filtern von Usern denen ich folge",
|
||||
"filterALL": "Alle Beiträge anzeigen",
|
||||
"success": "Gespeichert!",
|
||||
"languageSelectLabel": "Sprache",
|
||||
"tagsInputLabel": "Tags",
|
||||
"tagsInputPlaceholder": "Hinzufügen ein Tag"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -8,13 +8,40 @@
|
||||
"logout": "Logout",
|
||||
"email": "Your Email",
|
||||
"password": "Your Password",
|
||||
"forgotPassword": "Forgot Password?",
|
||||
"moreInfo": "What is Human Connection?",
|
||||
"moreInfoURL": "https://human-connection.org/en/",
|
||||
"moreInfoHint": "to the presentation page",
|
||||
"hello": "Hello"
|
||||
},
|
||||
"password-reset": {
|
||||
"title": "Reset your password",
|
||||
"form": {
|
||||
"description": "A password reset email will be sent to the given email address.",
|
||||
"submit": "Request email",
|
||||
"submitted": "A mail with further instruction has been sent to <b>{email}</b>"
|
||||
}
|
||||
},
|
||||
"verify-code": {
|
||||
"form": {
|
||||
"code": "Enter your code",
|
||||
"description": "Open your inbox and enter the code that we've sent to you.",
|
||||
"next": "Continue",
|
||||
"change-password": {
|
||||
"success": "Changing your password was successful!",
|
||||
"error": "Changing your password failed. Maybe the security code was not correct?",
|
||||
"help": "In case of problems, feel free to ask for help by sending us a mail to:"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editor": {
|
||||
"placeholder": "Leave your inspirational thoughts..."
|
||||
"placeholder": "Leave your inspirational thoughts...",
|
||||
"mention": {
|
||||
"noUsersFound": "No users found"
|
||||
},
|
||||
"hashtag": {
|
||||
"noHashtagsFound": "No hashtags found"
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"name": "My Profile",
|
||||
@ -22,7 +49,7 @@
|
||||
"follow": "Follow",
|
||||
"followers": "Followers",
|
||||
"following": "Following",
|
||||
"shouted": "Shouted",
|
||||
"shouted": "Recommended",
|
||||
"commented": "Commented",
|
||||
"userAnonym": "Anonymous",
|
||||
"socialMedia": "Where else can I find",
|
||||
@ -38,7 +65,7 @@
|
||||
},
|
||||
"notifications": {
|
||||
"menu": {
|
||||
"mentioned": "has mentioned you in a post"
|
||||
"mentioned": "mentioned you in a post"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
@ -52,7 +79,7 @@
|
||||
"name": "Your data",
|
||||
"labelName": "Your Name",
|
||||
"namePlaceholder": "Femanon Funny",
|
||||
"labelCity": "Your City or Region",
|
||||
"labelCity": "Su ciudad o región",
|
||||
"labelBio": "About You",
|
||||
"success": "Your data was successfully updated!"
|
||||
},
|
||||
@ -117,7 +144,7 @@
|
||||
"projects": "Projects",
|
||||
"invites": "Invites",
|
||||
"follows": "Follows",
|
||||
"shouts": "Shouts"
|
||||
"shouts": "Recommended"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizations"
|
||||
@ -193,7 +220,11 @@
|
||||
"name": "Name",
|
||||
"loadMore": "load more",
|
||||
"loading": "loading",
|
||||
"reportContent": "Report"
|
||||
"reportContent": "Report",
|
||||
"validations": {
|
||||
"email": "must be a valid email address",
|
||||
"verification-code": "must be 6 characters long"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"loading": "loading",
|
||||
@ -302,6 +333,9 @@
|
||||
}
|
||||
},
|
||||
"contribution": {
|
||||
"newPost": "Create a new Post",
|
||||
"filterFollow": "Filter contributions from users I follow",
|
||||
"filterALL": "View all contributions",
|
||||
"success": "Saved!",
|
||||
"languageSelectLabel": "Language",
|
||||
"tagsInputLabel": "Tags",
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
{
|
||||
"filter-menu": {
|
||||
"title": "Su burbuja de filtro"
|
||||
},
|
||||
"login": {
|
||||
"copy": "Si ya tiene una cuenta de Human Connection, inicie sesión aquí.",
|
||||
"login": "Iniciar sesión",
|
||||
@ -6,39 +9,85 @@
|
||||
"email": "Tu correo electrónico",
|
||||
"password": "Tu contraseña",
|
||||
"moreInfo": "¿Qué es Human Connection?",
|
||||
"moreInfoURL": "https://human-connection.org/es/",
|
||||
"moreInfoHint": "a la página de presentación",
|
||||
"hello": "Hola"
|
||||
},
|
||||
"editor": {
|
||||
"placeholder": "Write something inspiring..."
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mi perfil",
|
||||
"name": "Mi Perfil",
|
||||
"memberSince": "Miembro desde",
|
||||
"follow": "Seguir",
|
||||
"followers": "Seguidores",
|
||||
"following": "Siguiendo",
|
||||
"shouted": "Gritar",
|
||||
"commented": "Comentado"
|
||||
"shouted": "Recomendado",
|
||||
"commented": "Comentado",
|
||||
"userAnonym": "Anónimo",
|
||||
"socialMedia": "¿Dónde más puedo encontrar"
|
||||
},
|
||||
"notifications": {
|
||||
"menu": {
|
||||
"mentioned": "te ha mencionado en un post"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Buscar",
|
||||
"hint": "¿Qué estás buscando?",
|
||||
"failed": "no encontró nada"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Configuración",
|
||||
"data": {
|
||||
"name": "Sus datos"
|
||||
"name": "Sus datos",
|
||||
"labelName": "Su nombre",
|
||||
"namePlaceholder": "Femanon Funny",
|
||||
"labelCity": "Your City or Region",
|
||||
"labelBio": "Acerca de usted",
|
||||
"success": "Sus datos han sido actualizados con éxito!"
|
||||
},
|
||||
"security": {
|
||||
"name": "Seguridad"
|
||||
"name": "Seguridad",
|
||||
"change-password": {
|
||||
"button": "Cambiar contraseña",
|
||||
"success": "Contraseña cambiada con éxito!",
|
||||
"label-old-password": "Su contraseña antigua",
|
||||
"label-new-password": "Su nueva contraseña",
|
||||
"label-new-password-confirm": "Confirm new password",
|
||||
"message-old-password-required": "Ingrese su contraseña anterior",
|
||||
"message-new-password-required": "Introduzca una nueva contraseña",
|
||||
"message-new-password-confirm-required": "Confirme su nueva contraseña",
|
||||
"message-new-password-missmatch": "Vuelva a escribir la misma contraseña",
|
||||
"passwordSecurity": "Seguridad de la contraseña",
|
||||
"passwordStrength0": "Contraseña muy insegura",
|
||||
"passwordStrength1": "Contraseña insegura",
|
||||
"passwordStrength2": "Contraseña mediocre",
|
||||
"passwordStrength3": "Contraseña segura",
|
||||
"passwordStrength4": "Contraseña muy sólida"
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invita"
|
||||
"name": "invitaciones"
|
||||
},
|
||||
"download": {
|
||||
"name": "Descargar datos"
|
||||
},
|
||||
"delete": {
|
||||
"name": "Borrar cuenta"
|
||||
"name": "Eliminar cuenta"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Mis organizaciones"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Idiomas"
|
||||
"name": "idiomas"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Medios de comunicación social",
|
||||
"placeholder": "Agregar una URL de Social-Media",
|
||||
"submit": "Añadir enlace",
|
||||
"successAdd": "Social-Media agregó. Perfil actualizado!",
|
||||
"successDelete": "Social-Media borrado. Perfil actualizado!"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
@ -53,7 +102,7 @@
|
||||
"projects": "Proyectos",
|
||||
"invites": "Invita",
|
||||
"follows": "Sigue",
|
||||
"shouts": "Gritos"
|
||||
"shouts": "Recomendado"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organizaciones"
|
||||
@ -105,6 +154,11 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"your": {
|
||||
"post": "Your Post ::: Your Posts",
|
||||
"comment": "Your Comment ::: Your Comments",
|
||||
"shout": "Your Shout ::: Your Shouts"
|
||||
},
|
||||
"post": "Mensaje ::: Mensajes",
|
||||
"comment": "Comentario ::: Comentarios",
|
||||
"letsTalk": "Hablemos",
|
||||
@ -119,6 +173,113 @@
|
||||
"tag": "Etiqueta ::: Etiquetas",
|
||||
"name": "Nombre",
|
||||
"loadMore": "cargar más",
|
||||
"loading": "cargando"
|
||||
"loading": "cargando",
|
||||
"reportContent": "Report"
|
||||
},
|
||||
"actions": {
|
||||
"loading": "cargamento",
|
||||
"loadMore": "cargar más",
|
||||
"create": "Crear",
|
||||
"save": "Guardar",
|
||||
"edit": "Edite",
|
||||
"delete": "Delete",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"moderation": {
|
||||
"name": "Moderación",
|
||||
"reports": {
|
||||
"empty": "Felicitaciones, nada que moderar.",
|
||||
"name": "Informes",
|
||||
"submitter": "comunicado por",
|
||||
"disabledBy": "desactivado por"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"submit": "Desactivar",
|
||||
"cancel": "Cancelar",
|
||||
"success": "Discapacitado con éxito",
|
||||
"user": {
|
||||
"title": "Desactivar usuario",
|
||||
"type": "Usuario",
|
||||
"message": "¿Realmente quieres deshabilitar el usuario \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Deshabilitar contribución",
|
||||
"type": "Contribución",
|
||||
"message": "¿Realmente quieres deshabilitar la contribución \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Desactivar comentario",
|
||||
"type": "Comentario",
|
||||
"message": "¿Realmente quieres deshabilitar el comentario de \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"submit": "Borrar",
|
||||
"cancel": "Cancelar",
|
||||
"contribution": {
|
||||
"title": "Borrar contribución",
|
||||
"type": "Contribución",
|
||||
"message": "¿Realmente desea eliminar la Contribución \"<b>{name}</b>\" ?",
|
||||
"success": "Contribución eliminada con éxito!"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Eliminar comentario",
|
||||
"type": "Comentario",
|
||||
"message": "¿Realmente quieres borrar el comentario de \"<b>{name}</b>\" ?",
|
||||
"success": "Comentario eliminado con éxito!"
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"submit": "Informe",
|
||||
"cancel": "Cancelar",
|
||||
"success": "Gracias por informarnos!",
|
||||
"user": {
|
||||
"title": "Usuario de informe",
|
||||
"type": "Usuario",
|
||||
"message": "¿Realmente quieres reportar al usuario \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Informe Contribución",
|
||||
"type": "Contribución",
|
||||
"message": "¿Realmente quieres informar al usuario de la contribución \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Informe Comentario",
|
||||
"type": "Comentario",
|
||||
"message": "¿Realmente quieres reportar el comentario de \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"followButton": {
|
||||
"follow": "Folgen",
|
||||
"following": "Folge Ich"
|
||||
},
|
||||
"shoutButton": {
|
||||
"shouted": "empfohlen"
|
||||
},
|
||||
"release": {
|
||||
"submit": "Liberación",
|
||||
"cancel": "Cancelar",
|
||||
"success": "Liberar con éxito!",
|
||||
"user": {
|
||||
"title": "Usuario de la versión ",
|
||||
"type": "Usuario",
|
||||
"message": "¿Realmente quieres liberar al usuario \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Contribución de la versión ",
|
||||
"type": "Contribución",
|
||||
"message": "¿Realmente quieres liberar la contribución \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Comentario de la versión",
|
||||
"type": "Comentario",
|
||||
"message": "¿Realmente quieres liberar el comentario de \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"avatar": {
|
||||
"submitted": "Carga con éxito"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
{
|
||||
"filter-menu": {
|
||||
"title": "Votre bulle de filtre"
|
||||
},
|
||||
"login": {
|
||||
"copy": "Si vous avez déjà un compte human-connection, connectez-vous ici.",
|
||||
"login": "Connexion",
|
||||
@ -6,25 +9,66 @@
|
||||
"email": "Votre courriel",
|
||||
"password": "Votre mot de passe",
|
||||
"moreInfo": "Qu'est-ce que Human Connection?",
|
||||
"moreInfoURL": "https://human-connection.org/fr/",
|
||||
"moreInfoHint": "à la page de présentation",
|
||||
"hello": "Bonjour"
|
||||
},
|
||||
"editor": {
|
||||
"placeholder": "Écrivez quelque chose d'inspirant..."
|
||||
},
|
||||
"profile": {
|
||||
"name": "Mon profil",
|
||||
"memberSince": "Membre depuis",
|
||||
"follow": "Suivre",
|
||||
"followers": "Suiveurs",
|
||||
"following": "Suivant"
|
||||
"following": "Suivant",
|
||||
"shouted": "Recommandé",
|
||||
"commented": "Comentado",
|
||||
"userAnonym": "Anónimo",
|
||||
"socialMedia": "Où d'autre puis-je trouver"
|
||||
},
|
||||
"notifications": {
|
||||
"menu": {
|
||||
"mentioned": "a parlé de vous dans un article"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Rechercher",
|
||||
"hint": "Qu'est-ce que vous cherchez ?",
|
||||
"failed": "Rien trouvé"
|
||||
},
|
||||
"settings": {
|
||||
"name": "Paramètres",
|
||||
"name": "Configurations",
|
||||
"data": {
|
||||
"name": "Vos données"
|
||||
"name": "Vos données",
|
||||
"labelName": "Votre nom",
|
||||
"namePlaceholder": "Fémanon Funny",
|
||||
"labelCity": "Votre ville ou région",
|
||||
"labelBio": "À propos de vous",
|
||||
"success": "Vos données ont été mises à jour avec succès !"
|
||||
},
|
||||
"security": {
|
||||
"name": "Sécurité"
|
||||
"name": "Sécurité",
|
||||
"change-password": {
|
||||
"button": "Modifier le mot de passe",
|
||||
"success": "Mot de passe modifié avec succès !",
|
||||
"label-old-password": "Votre ancien mot de passe",
|
||||
"label-new-password": "Votre nouveau mot de passe",
|
||||
"label-new-password-confirm": "Confirmez votre nouveau mot de passe",
|
||||
"message-old-password-required": "Entrez votre ancien mot de passe",
|
||||
"message-new-password-required": "Entrez un nouveau mot de passe",
|
||||
"message-new-password-confirm-required": "Confirmez votre nouveau mot de passe",
|
||||
"message-new-password-missmatch": "Tapez à nouveau le même mot de passe",
|
||||
"passwordSecurity": "Sécurité par mot de passe",
|
||||
"passwordStrength0": "Mot de passe très peu sûr",
|
||||
"passwordStrength1": "Mot de passe non sécurisé",
|
||||
"passwordStrength2": "Mot de passe médiocre",
|
||||
"passwordStrength3": "Mot de passe fort",
|
||||
"passwordStrength4": "Mot de passe très fort"
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invite"
|
||||
"name": "invitations"
|
||||
},
|
||||
"download": {
|
||||
"name": "Télécharger les données"
|
||||
@ -36,7 +80,14 @@
|
||||
"name": "Mes organisations"
|
||||
},
|
||||
"languages": {
|
||||
"name": "Langues"
|
||||
"name": "langues"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Médias sociaux",
|
||||
"placeholder": "Ajouter une URL pour les médias sociaux",
|
||||
"submit": "Ajouter un lien",
|
||||
"successAdd": "Les médias sociaux ont été ajoutés. Profil mis à jour !",
|
||||
"successDelete": "Médias sociaux supprimé. Profil mis à jour !"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
@ -51,7 +102,7 @@
|
||||
"projects": "Projets",
|
||||
"invites": "Invite",
|
||||
"follows": "Suit",
|
||||
"shouts": "Cris"
|
||||
"shouts": "Recommandé"
|
||||
},
|
||||
"organizations": {
|
||||
"name": "Organisations"
|
||||
@ -95,13 +146,18 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"your": {
|
||||
"post": "Votre message ::: Votre messages",
|
||||
"comment": "Votre Commentaire ::: Votre Commentaires ",
|
||||
"shout": "Votre Recommandation ::: Votre Recommandations"
|
||||
},
|
||||
"post": "Message ::: Messages",
|
||||
"comment": "Commentaire ::: Commentaires",
|
||||
"letsTalk": "Parlons-en",
|
||||
"versus": "Versus",
|
||||
"moreInfo": "Plus d'infos",
|
||||
"takeAction": "Passer à l'action",
|
||||
"shout": "Shout ::: Shouts",
|
||||
"shout": "Recommandation ::: Recommandations",
|
||||
"user": "Utilisateur ::: Utilisateurs",
|
||||
"category": "Catégorie ::: Catégories",
|
||||
"organization": "Organisation ::: Organisations",
|
||||
@ -112,33 +168,64 @@
|
||||
"loading": "chargement",
|
||||
"reportContent": "Signaler"
|
||||
},
|
||||
"actions": {
|
||||
"loading": "chargement",
|
||||
"loadMore": "charger plus",
|
||||
"create": "Créer",
|
||||
"save": "sauvegarde",
|
||||
"edit": "Modifier",
|
||||
"delete": "Supprimer",
|
||||
"cancel": "Annuler"
|
||||
},
|
||||
"moderation": {
|
||||
"name": "Modération",
|
||||
"reports": {
|
||||
"empty": "Félicitations, rien à modérer.",
|
||||
"name": "Signalisations",
|
||||
"reporter": "signalé par"
|
||||
"name": "Rapports",
|
||||
"submitter": "signalé par",
|
||||
"disabledBy": "ddésactivé par"
|
||||
}
|
||||
},
|
||||
"disable": {
|
||||
"submit": "Désactiver",
|
||||
"cancel": "Annuler",
|
||||
"success": "Désactivé avec succès",
|
||||
"user": {
|
||||
"title": "Désactiver l'utilisateur",
|
||||
"type": "Utilisateur",
|
||||
"message": "Souhaitez-vous vraiment désactiver l'utilisateur \" <b> {name} </b> \"?"
|
||||
"message": "Voulez-vous vraiment désactiver l'utilisateur \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Désactiver l'apport",
|
||||
"type": "apport",
|
||||
"message": "Souhaitez-vous vraiment signaler l'entrée\" <b> {name} </b> \"?"
|
||||
"title": "Cotisation d'invalidité",
|
||||
"type": "Contribution",
|
||||
"message": "Voulez-vous vraiment désactiver la contribution \"<b> {name} </b> \"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Désactiver le commentaire",
|
||||
"title": "Désactiver commentaire",
|
||||
"type": "Commentaire",
|
||||
"message": "Souhaitez-vous vraiment désactiver le commentaire de \"<b>{name}</b>\" ?"
|
||||
"message": "Voulez-vous vraiment désactiver le commentaire de \"<b>{name}</b>\" ?"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"submit": "Supprimer",
|
||||
"cancel": "Annuler",
|
||||
"contribution": {
|
||||
"title": "Supprimer la contribution",
|
||||
"type": "Contribution",
|
||||
"message": "Voulez-vous vraiment supprimer la contribution \"<b>{name}</b>\" löschen möchtest?",
|
||||
"success": "Contribution supprimée avec succès !"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Supprimer un commentaire",
|
||||
"type": "Commentaire",
|
||||
"message": "Voulez-vous vraiment supprimer le commentaire de \"<b>{name}</b>\" löschen möchtest?",
|
||||
"success": "Commentaire supprimé avec succès !"
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"submit": "Envoyer le rapport",
|
||||
"submit": "Rapport",
|
||||
"cancel": "Annuler",
|
||||
"success": "Merci de nous avoir fait part de vos commentaires!",
|
||||
"user": {
|
||||
"title": "Signaler l'utilisateur",
|
||||
"type": "Utilisateur",
|
||||
@ -155,15 +242,36 @@
|
||||
"message": "Souhaitez-vous vraiment signaler l'utilisateur \" <b> {name} </b> \"?"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"cancel": "Annuler"
|
||||
"followButton": {
|
||||
"follow": "découler",
|
||||
"following": "Je suis les"
|
||||
},
|
||||
"contribution": {
|
||||
"edit": "Rédiger l'apport",
|
||||
"delete": "Supprimer l'entrée"
|
||||
"shoutButton": {
|
||||
"shouted": "recommandé"
|
||||
},
|
||||
"comment": {
|
||||
"edit": "Rédiger un commentaire",
|
||||
"delete": "Supprimer le commentaire"
|
||||
"release": {
|
||||
"submit": "Relâchez",
|
||||
"cancel": "Annuler",
|
||||
"success": "Relâchez avec succès!",
|
||||
"user": {
|
||||
"title": "Validation par l'utilisateur",
|
||||
"type": "Utilisateur",
|
||||
"message": "Voulez-vous vraiment libérer l'utilisateur \"<b>{name}</b>\"?"
|
||||
},
|
||||
"contribution": {
|
||||
"title": "Versement de la contribution",
|
||||
"type": "Contribution",
|
||||
"message": "Voulez-vous vraiment débloquer la contribution \"<b>{name}</b>\"?"
|
||||
},
|
||||
"comment": {
|
||||
"title": "Publication des commentaires",
|
||||
"type": "Commentaire",
|
||||
"message": "Voulez-vous vraiment publier le commentaire de \"<b>{name}</b>\"?"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"avatar": {
|
||||
"submitted": "Téléchargement réussi"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,18 @@ module.exports = {
|
||||
|
||||
env: {
|
||||
// pages which do NOT require a login
|
||||
publicPages: ['login', 'logout', 'register', 'signup', 'reset', 'reset-token', 'pages-slug'],
|
||||
publicPages: [
|
||||
'login',
|
||||
'logout',
|
||||
'password-reset-request',
|
||||
'password-reset-verify-code',
|
||||
'password-reset-change-password',
|
||||
'register',
|
||||
'signup',
|
||||
'reset',
|
||||
'reset-token',
|
||||
'pages-slug',
|
||||
],
|
||||
// pages to keep alive
|
||||
keepAlivePages: ['index'],
|
||||
// active locales
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
"!**/?(*.)+(spec|test).js?(x)"
|
||||
],
|
||||
"coverageReporters": [
|
||||
"text",
|
||||
"lcov"
|
||||
],
|
||||
"transform": {
|
||||
@ -56,10 +57,10 @@
|
||||
"@nuxtjs/style-resources": "~0.1.2",
|
||||
"accounting": "~0.4.1",
|
||||
"apollo-cache-inmemory": "~1.5.1",
|
||||
"apollo-client": "~2.6.2",
|
||||
"apollo-client": "~2.6.3",
|
||||
"cookie-universal-nuxt": "~2.0.16",
|
||||
"cross-env": "~5.2.0",
|
||||
"date-fns": "2.0.0-alpha.33",
|
||||
"date-fns": "2.0.0-alpha.35",
|
||||
"express": "~4.17.1",
|
||||
"graphql": "~14.3.1",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
@ -87,14 +88,14 @@
|
||||
"@vue/server-test-utils": "~1.0.0-beta.29",
|
||||
"@vue/test-utils": "~1.0.0-beta.29",
|
||||
"babel-core": "~7.0.0-bridge.0",
|
||||
"babel-eslint": "~10.0.1",
|
||||
"babel-eslint": "~10.0.2",
|
||||
"babel-jest": "~24.8.0",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-prettier": "~4.3.0",
|
||||
"eslint-config-prettier": "~5.0.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-loader": "~2.1.2",
|
||||
"eslint-plugin-import": "~2.17.3",
|
||||
"eslint-plugin-jest": "~22.6.4",
|
||||
"eslint-plugin-jest": "~22.7.0",
|
||||
"eslint-plugin-node": "~9.1.0",
|
||||
"eslint-plugin-prettier": "~3.1.0",
|
||||
"eslint-plugin-promise": "~4.1.1",
|
||||
@ -110,4 +111,4 @@
|
||||
"vue-jest": "~3.0.4",
|
||||
"vue-svg-loader": "~0.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,6 +45,11 @@
|
||||
name="password"
|
||||
type="password"
|
||||
/>
|
||||
<ds-space class="password-reset-link" margin-bottom="large">
|
||||
<nuxt-link to="/password-reset/request">
|
||||
{{ $t('login.forgotPassword') }}
|
||||
</nuxt-link>
|
||||
</ds-space>
|
||||
<ds-button
|
||||
:loading="pending"
|
||||
primary
|
||||
@ -73,7 +78,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LocaleSwitch from '~/components/LocaleSwitch'
|
||||
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
22
webapp/pages/password-reset.vue
Normal file
22
webapp/pages/password-reset.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<ds-container width="small">
|
||||
<ds-flex>
|
||||
<ds-flex-item :width="{ base: '100%' }" centered>
|
||||
<ds-space style="text-align: center;" margin-top="small" margin-bottom="xxx-small" centered>
|
||||
<nuxt-child />
|
||||
</ds-space>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
</ds-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
layout: 'default',
|
||||
asyncData({ store, redirect }) {
|
||||
if (store.getters['auth/isLoggedIn']) {
|
||||
redirect('/')
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
28
webapp/pages/password-reset/change-password.vue
Normal file
28
webapp/pages/password-reset/change-password.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<change-password
|
||||
:email="email"
|
||||
:code="code"
|
||||
@passwordResetResponse="handlePasswordResetResponse"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ChangePassword from '~/components/PasswordReset/ChangePassword'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const { email = '', code = '' } = this.$route.query
|
||||
return { email, code }
|
||||
},
|
||||
components: {
|
||||
ChangePassword,
|
||||
},
|
||||
methods: {
|
||||
handlePasswordResetResponse(response) {
|
||||
if (response === 'success') {
|
||||
this.$router.push('/login')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
18
webapp/pages/password-reset/request.vue
Normal file
18
webapp/pages/password-reset/request.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<request @handleSubmitted="handlePasswordResetRequested" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Request from '~/components/PasswordReset/Request'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Request,
|
||||
},
|
||||
methods: {
|
||||
handlePasswordResetRequested({ email }) {
|
||||
this.$router.push({ path: 'verify-code', query: { email } })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
22
webapp/pages/password-reset/verify-code.vue
Normal file
22
webapp/pages/password-reset/verify-code.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<verify-code :email="email" @verification="handleVerification" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VerifyCode from '~/components/PasswordReset/VerifyCode'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VerifyCode,
|
||||
},
|
||||
data() {
|
||||
const { email = '' } = this.$route.query
|
||||
return { email }
|
||||
},
|
||||
methods: {
|
||||
handleVerification({ email, code }) {
|
||||
this.$router.push({ path: 'change-password', query: { email, code } })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -77,6 +77,17 @@ export default {
|
||||
]
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
if (to.hash === '#comments') {
|
||||
window.scroll({
|
||||
top: document.getElementById('comments').offsetTop,
|
||||
left: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
17
webapp/store/editor.js
Normal file
17
webapp/store/editor.js
Normal file
@ -0,0 +1,17 @@
|
||||
export const state = () => {
|
||||
return {
|
||||
placeholder: null,
|
||||
}
|
||||
}
|
||||
|
||||
export const getters = {
|
||||
placeholder(state) {
|
||||
return state.placeholder
|
||||
},
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
SET_PLACEHOLDER_TEXT(state, text) {
|
||||
state.placeholder = text
|
||||
},
|
||||
}
|
||||
20
webapp/store/editor.spec.js
Normal file
20
webapp/store/editor.spec.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { getters, mutations } from './editor.js'
|
||||
|
||||
let state
|
||||
|
||||
describe('getters', () => {
|
||||
describe('placeholder', () => {
|
||||
it('return the value in state', () => {
|
||||
state = { placeholder: null }
|
||||
expect(getters.placeholder(state)).toBe(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mutations', () => {
|
||||
it('SET_PLACEHOLDER_TEXT', () => {
|
||||
state = { placeholder: null }
|
||||
mutations.SET_PLACEHOLDER_TEXT(state, 'new placeholder')
|
||||
expect(getters.placeholder(state)).toBe('new placeholder')
|
||||
})
|
||||
})
|
||||
@ -1893,10 +1893,10 @@ apollo-cache@1.3.2, apollo-cache@^1.2.1:
|
||||
apollo-utilities "^1.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
apollo-client@^2.5.1, apollo-client@~2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.2.tgz#03b6af651e09b6e413e486ddc87464c85bd6e514"
|
||||
integrity sha512-oks1MaT5x7gHcPeC8vPC1UzzsKaEIC0tye+jg72eMDt5OKc7BobStTeS/o2Ib3e0ii40nKxGBnMdl/Xa/p56Yg==
|
||||
apollo-client@^2.5.1, apollo-client@~2.6.3:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.3.tgz#9bb2d42fb59f1572e51417f341c5f743798d22db"
|
||||
integrity sha512-DS8pmF5CGiiJ658dG+mDn8pmCMMQIljKJSTeMNHnFuDLV0uAPZoeaAwVFiAmB408Ujqt92oIZ/8yJJAwSIhd4A==
|
||||
dependencies:
|
||||
"@types/zen-observable" "^0.8.0"
|
||||
apollo-cache "1.3.2"
|
||||
@ -2310,10 +2310,10 @@ babel-core@~7.0.0-bridge.0:
|
||||
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
|
||||
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
|
||||
|
||||
babel-eslint@~10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
|
||||
integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
|
||||
babel-eslint@~10.0.2:
|
||||
version "10.0.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
|
||||
integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@babel/parser" "^7.0.0"
|
||||
@ -3754,10 +3754,10 @@ data-urls@^1.0.0:
|
||||
whatwg-mimetype "^2.2.0"
|
||||
whatwg-url "^7.0.0"
|
||||
|
||||
date-fns@2.0.0-alpha.33:
|
||||
version "2.0.0-alpha.33"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.33.tgz#c2f73c3cc50ac301c9217eb93603c9bc40e891bf"
|
||||
integrity sha512-tqUVEk3oxnJuNIvwAMKHAMo4uFRG0zXvjxZQll+BonoPt+m4NMcUgO14NDxbHuy7uYcrVErd2GdSsw02EDZQ7w==
|
||||
date-fns@2.0.0-alpha.35:
|
||||
version "2.0.0-alpha.35"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.0-alpha.35.tgz#185813cdc51b05cc1468a95116494bb3f3440934"
|
||||
integrity sha512-dAY1ujqRtyUsa9mVeupyMzUluWo1d7kAMwyXTQHFImKYSHKvxDw/dipiY6fdswQOs8CwpGoiKysGfaaRP5r3bA==
|
||||
|
||||
date-now@^0.1.4:
|
||||
version "0.1.4"
|
||||
@ -4236,10 +4236,10 @@ eslint-config-prettier@^3.3.0:
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
|
||||
eslint-config-prettier@~4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-4.3.0.tgz#c55c1fcac8ce4518aeb77906984e134d9eb5a4f0"
|
||||
integrity sha512-sZwhSTHVVz78+kYD3t5pCWSYEdVSBR0PXnwjDRsUs8ytIrK8PLXw+6FKp8r3Z7rx4ZszdetWlXYKOHoUrrwPlA==
|
||||
eslint-config-prettier@~5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-5.0.0.tgz#f7a94b2b8ae7cbf25842c36fa96c6d32cd0a697c"
|
||||
integrity sha512-c17Aqiz5e8LEqoc/QPmYnaxQFAHTx2KlCZBPxXXjEMmNchOLnV/7j0HoPZuC+rL/tDC9bazUYOKJW9bOhftI/w==
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
|
||||
@ -4300,10 +4300,10 @@ eslint-plugin-import@~2.17.3:
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.11.0"
|
||||
|
||||
eslint-plugin-jest@~22.6.4:
|
||||
version "22.6.4"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.6.4.tgz#2895b047dd82f90f43a58a25cf136220a21c9104"
|
||||
integrity sha512-36OqnZR/uMCDxXGmTsqU4RwllR0IiB/XF8GW3ODmhsjiITKuI0GpgultWFt193ipN3HARkaIcKowpE6HBvRHNg==
|
||||
eslint-plugin-jest@~22.7.0:
|
||||
version "22.7.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.7.0.tgz#a1d325bccb024b04f5354c56fe790baba54a454c"
|
||||
integrity sha512-0U9nBd9V6+GKpM/KvRDcmMuPsewSsdM7NxCozgJkVAh8IrwHmQ0aw44/eYuVkhT8Fcdhsz0zYiyPtKg147eXMQ==
|
||||
|
||||
eslint-plugin-node@~9.1.0:
|
||||
version "9.1.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user