mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge remote-tracking branch 'origin/master' into docs-improve_installation_instructions
This commit is contained in:
commit
506906e344
@ -28,7 +28,7 @@ script:
|
||||
- docker-compose exec webapp yarn run lint
|
||||
- docker-compose exec webapp yarn run test --ci --verbose=false
|
||||
- docker-compose exec -d backend yarn run test:before:seeder
|
||||
- yarn run cypress:run --record --key $CYPRESS_TOKEN
|
||||
- yarn run cypress:run
|
||||
|
||||
after_success:
|
||||
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js -e js,graphql",
|
||||
"lint": "eslint src --config .eslintrc.js",
|
||||
"test": "run-s test:jest test:cucumber",
|
||||
"test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 yarn run dev",
|
||||
"test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub yarn run dev",
|
||||
"test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 yarn run dev 2> /dev/null",
|
||||
"test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub yarn run dev 2> /dev/null",
|
||||
"test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand",
|
||||
"test:cucumber:cmd": "wait-on tcp:4001 tcp:4123 && cucumber-js --require-module @babel/register --exit test/",
|
||||
"test:jest:cmd:debug": "wait-on tcp:4001 tcp:4123 && node --inspect-brk ./node_modules/.bin/jest -i --forceExit --detectOpenHandles --runInBand",
|
||||
@ -50,7 +50,7 @@
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~3.0.2",
|
||||
"graphql-shield": "~5.3.2",
|
||||
"graphql-shield": "~5.3.3",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"graphql-yoga": "~1.17.4",
|
||||
"helmet": "~3.16.0",
|
||||
@ -84,7 +84,7 @@
|
||||
"cucumber": "~5.1.0",
|
||||
"eslint": "~5.16.0",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-plugin-import": "~2.16.0",
|
||||
"eslint-plugin-import": "~2.17.1",
|
||||
"eslint-plugin-jest": "~22.4.1",
|
||||
"eslint-plugin-node": "~8.0.1",
|
||||
"eslint-plugin-promise": "~4.1.1",
|
||||
@ -94,4 +94,4 @@
|
||||
"nodemon": "~1.18.11",
|
||||
"supertest": "~4.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import reports from './resolvers/reports.js'
|
||||
import posts from './resolvers/posts.js'
|
||||
import moderation from './resolvers/moderation.js'
|
||||
import rewards from './resolvers/rewards.js'
|
||||
import socialMedia from './resolvers/socialMedia.js'
|
||||
import notifications from './resolvers/notifications'
|
||||
|
||||
export const typeDefs = fs
|
||||
@ -27,6 +28,7 @@ export const resolvers = {
|
||||
...posts.Mutation,
|
||||
...moderation.Mutation,
|
||||
...rewards.Mutation,
|
||||
...socialMedia.Mutation,
|
||||
...notifications.Mutation
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,12 +10,14 @@ import permissionsMiddleware from './permissionsMiddleware'
|
||||
import userMiddleware from './userMiddleware'
|
||||
import includedFieldsMiddleware from './includedFieldsMiddleware'
|
||||
import orderByMiddleware from './orderByMiddleware'
|
||||
import validUrlMiddleware from './validUrlMiddleware'
|
||||
import notificationsMiddleware from './notificationsMiddleware'
|
||||
|
||||
export default schema => {
|
||||
let middleware = [
|
||||
passwordMiddleware,
|
||||
dateTimeMiddleware,
|
||||
validUrlMiddleware,
|
||||
sluggifyMiddleware,
|
||||
excerptMiddleware,
|
||||
xssMiddleware,
|
||||
|
||||
@ -74,6 +74,7 @@ const permissions = shield({
|
||||
UpdateBadge: isAdmin,
|
||||
DeleteBadge: isAdmin,
|
||||
AddUserBadges: isAdmin,
|
||||
CreateSocialMedia: isAuthenticated,
|
||||
// AddBadgeRewarded: isAdmin,
|
||||
// RemoveBadgeRewarded: isAdmin,
|
||||
reward: isAdmin,
|
||||
|
||||
18
backend/src/middleware/validUrlMiddleware.js
Normal file
18
backend/src/middleware/validUrlMiddleware.js
Normal file
@ -0,0 +1,18 @@
|
||||
const validURL = str => {
|
||||
const isValid = str.match(/^(?:https?:\/\/)(?:[^@\n])?(?:www\.)?([^:/\n?]+)/g)
|
||||
return !!isValid
|
||||
}
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateSocialMedia: async (resolve, root, args, context, info) => {
|
||||
let socialMedia
|
||||
if (validURL(args.url)) {
|
||||
socialMedia = await resolve(root, args, context, info)
|
||||
} else {
|
||||
throw Error('Input is not a URL')
|
||||
}
|
||||
return socialMedia
|
||||
}
|
||||
}
|
||||
}
|
||||
21
backend/src/resolvers/socialMedia.js
Normal file
21
backend/src/resolvers/socialMedia.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
CreateSocialMedia: async (object, params, context, resolveInfo) => {
|
||||
const socialMedia = await neo4jgraphql(object, params, context, resolveInfo, true)
|
||||
const session = context.driver.session()
|
||||
await session.run(
|
||||
`MATCH (owner:User {id: $userId}), (socialMedia:SocialMedia {id: $socialMediaId})
|
||||
MERGE (socialMedia)<-[:OWNED]-(owner)
|
||||
RETURN owner`, {
|
||||
userId: context.user.id,
|
||||
socialMediaId: socialMedia.id
|
||||
}
|
||||
)
|
||||
session.close()
|
||||
|
||||
return socialMedia
|
||||
}
|
||||
}
|
||||
}
|
||||
49
backend/src/resolvers/socialMedia.spec.js
Normal file
49
backend/src/resolvers/socialMedia.spec.js
Normal file
@ -0,0 +1,49 @@
|
||||
import Factory from '../seed/factories'
|
||||
import { GraphQLClient } from 'graphql-request'
|
||||
import { host, login } from '../jest/helpers'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
describe('CreateSocialMedia', () => {
|
||||
let client
|
||||
let headers
|
||||
const mutation = `
|
||||
mutation($url: String!) {
|
||||
CreateSocialMedia(url: $url) {
|
||||
url
|
||||
}
|
||||
}
|
||||
`
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg',
|
||||
id: 'acb2d923-f3af-479e-9f00-61b12e864666',
|
||||
name: 'Matilde Hermiston',
|
||||
slug: 'matilde-hermiston',
|
||||
role: 'user',
|
||||
email: 'test@example.org',
|
||||
password: '1234'
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
})
|
||||
|
||||
it('rejects empty string', async () => {
|
||||
const variables = { url: '' }
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL')
|
||||
})
|
||||
|
||||
it('validates URLs', async () => {
|
||||
const variables = { url: 'not-a-url' }
|
||||
await expect(client.request(mutation, variables)).rejects.toThrow('Input is not a URL')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -128,6 +128,7 @@ type User {
|
||||
location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
|
||||
locationName: String
|
||||
about: String
|
||||
socialMedia: [SocialMedia]! @relation(name: "OWNED", direction: "OUT")
|
||||
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
@ -318,3 +319,10 @@ type SharedInboxEndpoint {
|
||||
id: ID!
|
||||
uri: String
|
||||
}
|
||||
|
||||
type SocialMedia {
|
||||
id: ID!
|
||||
url: String
|
||||
ownedBy: [User]! @relation(name: "OWNED", direction: "IN")
|
||||
}
|
||||
|
||||
|
||||
@ -1104,10 +1104,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.9":
|
||||
version "0.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.9.tgz#8a619ac4d2b8dcacb0d81345746018303b479919"
|
||||
integrity sha512-C7HdLLs1ZNPbYeNsSX++fMosxWAwzVeUs9wc76XlKJrKvLEyNwXMDUjag75EVAPxlZ36YiRJ6iTy4zc5Dbtndw==
|
||||
"@types/yup@0.26.12":
|
||||
version "0.26.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.26.12.tgz#60fc1a485923a929699d2107fac46e6769707c4a"
|
||||
integrity sha512-lWCsvLer6G84Gj7yh+oFGRuGHsqZd1Dwu47CVVL0ATw+bOnGDgMNHbTn80p1onT66fvLfN8FnRA3eRANsnnbbQ==
|
||||
|
||||
"@types/zen-observable@^0.5.3":
|
||||
version "0.5.4"
|
||||
@ -1609,6 +1609,14 @@ array-flatten@1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||
|
||||
array-includes@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
|
||||
integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
es-abstract "^1.7.0"
|
||||
|
||||
array-map@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
|
||||
@ -2892,7 +2900,7 @@ es-abstract@^1.4.3:
|
||||
is-callable "^1.1.3"
|
||||
is-regex "^1.0.4"
|
||||
|
||||
es-abstract@^1.5.1:
|
||||
es-abstract@^1.5.1, es-abstract@^1.7.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
|
||||
integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
|
||||
@ -2989,10 +2997,10 @@ eslint-import-resolver-node@^0.3.2:
|
||||
debug "^2.6.9"
|
||||
resolve "^1.5.0"
|
||||
|
||||
eslint-module-utils@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz#546178dab5e046c8b562bbb50705e2456d7bda49"
|
||||
integrity sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==
|
||||
eslint-module-utils@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a"
|
||||
integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==
|
||||
dependencies:
|
||||
debug "^2.6.8"
|
||||
pkg-dir "^2.0.0"
|
||||
@ -3005,21 +3013,22 @@ eslint-plugin-es@^1.3.1:
|
||||
eslint-utils "^1.3.0"
|
||||
regexpp "^2.0.1"
|
||||
|
||||
eslint-plugin-import@~2.16.0:
|
||||
version "2.16.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz#97ac3e75d0791c4fac0e15ef388510217be7f66f"
|
||||
integrity sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==
|
||||
eslint-plugin-import@~2.17.1:
|
||||
version "2.17.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.1.tgz#b888feb4d9b3ee155113c8dccdd4bec5db33bdf4"
|
||||
integrity sha512-lzD9uvRvW4MsHzIOMJEDSb5MOV9LzgxRPBaovvOhJqzgxRHYfGy9QOrMuwHIh5ehKFJ7Z3DcrcGKDQ0IbP0EdQ==
|
||||
dependencies:
|
||||
array-includes "^3.0.3"
|
||||
contains-path "^0.1.0"
|
||||
debug "^2.6.9"
|
||||
doctrine "1.5.0"
|
||||
eslint-import-resolver-node "^0.3.2"
|
||||
eslint-module-utils "^2.3.0"
|
||||
eslint-module-utils "^2.4.0"
|
||||
has "^1.0.3"
|
||||
lodash "^4.17.11"
|
||||
minimatch "^3.0.4"
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.9.0"
|
||||
resolve "^1.10.0"
|
||||
|
||||
eslint-plugin-jest@~22.4.1:
|
||||
version "22.4.1"
|
||||
@ -3743,12 +3752,12 @@ graphql-request@~1.8.2:
|
||||
dependencies:
|
||||
cross-fetch "2.2.2"
|
||||
|
||||
graphql-shield@~5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.2.tgz#2d47907ed9882a0636cb8ade6087123309d215ef"
|
||||
integrity sha512-fib7rSr5aS/WHL3+Aa5LXhcCuPGEIDXmzfGtFjUXkUiZ6E5u+bDSL+9KRXo/p14A28GkJF+1Vu1hlg9H/QFG1w==
|
||||
graphql-shield@~5.3.3:
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/graphql-shield/-/graphql-shield-5.3.3.tgz#e3fbdb2a5f927fe1bb660ccf60c614defcc3aed4"
|
||||
integrity sha512-9Hdmp71ewi9w7Tj1x8CSl3arWvtQOYKpZrsSBid2Vpr6BISAKe/2edEfgP4xYIKAkmpclG0Gl7ID5+qt1RJu7A==
|
||||
dependencies:
|
||||
"@types/yup" "0.26.9"
|
||||
"@types/yup" "0.26.12"
|
||||
lightercollective "^0.2.0"
|
||||
object-hash "^1.3.1"
|
||||
yup "^0.27.0"
|
||||
@ -6677,7 +6686,7 @@ resolve@1.1.7:
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
|
||||
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
|
||||
|
||||
resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0:
|
||||
resolve@^1.10.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.5.0, resolve@^1.8.1:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
|
||||
integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
|
||||
|
||||
@ -61,3 +61,52 @@ Then(
|
||||
'I can see my new name {string} when I click on my profile picture in the top right',
|
||||
name => matchNameInUserMenu(name)
|
||||
)
|
||||
|
||||
When('I click on the {string} link', link => {
|
||||
cy.get('a')
|
||||
.contains(link)
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('I should be on the {string} page', page => {
|
||||
cy.location()
|
||||
.should(loc => {
|
||||
expect(loc.pathname).to.eq(page)
|
||||
})
|
||||
.get('h3')
|
||||
.should('contain', 'Social media')
|
||||
})
|
||||
|
||||
Then('I add a social media link', () => {
|
||||
cy.get("input[name='social-media']")
|
||||
.type('https://freeradical.zone/peter-pan')
|
||||
.get('button')
|
||||
.contains('Add link')
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('it gets saved successfully', () => {
|
||||
cy.get('.iziToast-message')
|
||||
.should('contain', 'Updated user')
|
||||
})
|
||||
|
||||
Then('the new social media link shows up on the page', () => {
|
||||
cy.get('a[href="https://freeradical.zone/peter-pan"]')
|
||||
.should('have.length', 1)
|
||||
})
|
||||
|
||||
Given('I have added a social media link', () => {
|
||||
cy.openPage('/settings/my-social-media')
|
||||
.get("input[name='social-media']")
|
||||
.type('https://freeradical.zone/peter-pan')
|
||||
.get('button')
|
||||
.contains('Add link')
|
||||
.click()
|
||||
})
|
||||
|
||||
Then('they should be able to see my social media links', () => {
|
||||
cy.get('.ds-card-content')
|
||||
.contains('Where else can I find Peter Pan?')
|
||||
.get('a[href="https://freeradical.zone/peter-pan"]')
|
||||
.should('have.length', 1)
|
||||
})
|
||||
|
||||
21
cypress/integration/user_profile/SocialMedia.feature
Normal file
21
cypress/integration/user_profile/SocialMedia.feature
Normal file
@ -0,0 +1,21 @@
|
||||
Feature: List Social Media Accounts
|
||||
As a User
|
||||
I'd like to enter my social media
|
||||
So I can show them to other users to get in contact
|
||||
|
||||
Background:
|
||||
Given I have a user account
|
||||
And I am logged in
|
||||
|
||||
Scenario: Adding Social Media
|
||||
Given I am on the "settings" page
|
||||
And I click on the "Social media" link
|
||||
Then I should be on the "/settings/my-social-media" page
|
||||
When I add a social media link
|
||||
Then it gets saved successfully
|
||||
And the new social media link shows up on the page
|
||||
|
||||
Scenario: Other user's viewing my Social Media
|
||||
Given I have added a social media link
|
||||
When people visit my profile page
|
||||
Then they should be able to see my social media links
|
||||
@ -98,6 +98,10 @@ export default app => {
|
||||
}
|
||||
}
|
||||
}
|
||||
socialMedia {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
"followers": "Folgen",
|
||||
"following": "Folgt",
|
||||
"shouted": "Empfohlen",
|
||||
"commented": "Kommentiert"
|
||||
"commented": "Kommentiert",
|
||||
"socialMedia": "Wo sonst finde ich"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Suchen",
|
||||
@ -51,6 +52,11 @@
|
||||
},
|
||||
"languages": {
|
||||
"name": "Sprachen"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Soziale Medien",
|
||||
"submit": "Link hinzufügen",
|
||||
"success": "Profil aktualisiert"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
"followers": "Followers",
|
||||
"following": "Following",
|
||||
"shouted": "Shouted",
|
||||
"commented": "Commented"
|
||||
"commented": "Commented",
|
||||
"socialMedia": "Where else can I find"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Search",
|
||||
@ -51,6 +52,11 @@
|
||||
},
|
||||
"languages": {
|
||||
"name": "Languages"
|
||||
},
|
||||
"social-media": {
|
||||
"name": "Social media",
|
||||
"submit": "Add link",
|
||||
"success": "Updated user profile"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
"string-hash": "^1.1.3",
|
||||
"tiptap": "^1.14.0",
|
||||
"tiptap-extensions": "^1.14.0",
|
||||
"v-tooltip": "~2.0.0",
|
||||
"v-tooltip": "~2.0.1",
|
||||
"vue-count-to": "~1.0.13",
|
||||
"vue-izitoast": "1.1.2",
|
||||
"vue-sweetalert-icons": "~3.2.0",
|
||||
@ -62,7 +62,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.4.3",
|
||||
"@babel/preset-env": "~7.4.3",
|
||||
"@vue/cli-shared-utils": "~3.5.1",
|
||||
"@vue/cli-shared-utils": "~3.6.0",
|
||||
"@vue/eslint-config-prettier": "~4.0.1",
|
||||
"@vue/server-test-utils": "~1.0.0-beta.29",
|
||||
"@vue/test-utils": "~1.0.0-beta.29",
|
||||
|
||||
@ -202,6 +202,37 @@
|
||||
</p>
|
||||
</template>
|
||||
</ds-card>
|
||||
<ds-space
|
||||
v-if="user.socialMedia && user.socialMedia.length"
|
||||
margin="large"
|
||||
>
|
||||
<ds-card style="position: relative; height: auto;">
|
||||
<ds-space
|
||||
margin="x-small"
|
||||
>
|
||||
<ds-text
|
||||
tag="h5"
|
||||
color="soft"
|
||||
>
|
||||
{{ $t('profile.socialMedia') }} {{ user.name | truncate(15) }}?
|
||||
</ds-text>
|
||||
<template>
|
||||
<ds-space
|
||||
v-for="link in socialMediaLinks"
|
||||
:key="link.username"
|
||||
margin="x-small"
|
||||
>
|
||||
<a :href="link.url">
|
||||
<ds-avatar
|
||||
:image="link.favicon"
|
||||
/>
|
||||
{{ link.username }}
|
||||
</a>
|
||||
</ds-space>
|
||||
</template>
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</ds-space>
|
||||
</ds-flex-item>
|
||||
<ds-flex-item :width="{ base: '100%', sm: 3, md: 5, lg: 3 }">
|
||||
<ds-flex
|
||||
@ -348,6 +379,19 @@ export default {
|
||||
return []
|
||||
}
|
||||
return this.uniq(this.user.contributions.filter(post => !post.deleted))
|
||||
},
|
||||
socialMediaLinks() {
|
||||
const { socialMedia = [] } = this.user
|
||||
return socialMedia.map(socialMedia => {
|
||||
const { url } = socialMedia
|
||||
const matches = url.match(
|
||||
/^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:\/\n?]+)/g
|
||||
)
|
||||
const [domain] = matches || []
|
||||
const favicon = domain ? `${domain}/favicon.ico` : null
|
||||
const username = url.split('/').pop()
|
||||
return { url, username, favicon }
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
@ -34,6 +34,10 @@ export default {
|
||||
{
|
||||
name: this.$t('settings.security.name'),
|
||||
path: `/settings/security`
|
||||
},
|
||||
{
|
||||
name: this.$t('settings.social-media.name'),
|
||||
path: `/settings/my-social-media`
|
||||
}
|
||||
// TODO implement
|
||||
/* {
|
||||
@ -59,6 +63,7 @@ export default {
|
||||
/* {
|
||||
name: this.$t('settings.languages.name'),
|
||||
path: `/settings/languages`
|
||||
},
|
||||
} */
|
||||
]
|
||||
}
|
||||
|
||||
88
webapp/pages/settings/my-social-media.spec.js
Normal file
88
webapp/pages/settings/my-social-media.spec.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import MySocialMedia from './my-social-media.vue'
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Vuex)
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('my-social-media.vue', () => {
|
||||
let wrapper
|
||||
let Wrapper
|
||||
let store
|
||||
let mocks
|
||||
let getters
|
||||
let input
|
||||
let submitBtn
|
||||
const socialMediaUrl = 'https://freeradical.zone/@mattwr18'
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
$t: jest.fn(),
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockRejectedValue({ message: 'Ouch!' })
|
||||
.mockResolvedValueOnce({
|
||||
data: { CreateSocialMeda: { id: 's1', url: socialMediaUrl } }
|
||||
})
|
||||
},
|
||||
$toast: {
|
||||
error: jest.fn(),
|
||||
success: jest.fn()
|
||||
}
|
||||
}
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
const Wrapper = () => {
|
||||
store = new Vuex.Store({
|
||||
getters
|
||||
})
|
||||
return mount(MySocialMedia, { store, mocks, localVue })
|
||||
}
|
||||
|
||||
it('renders', () => {
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.contains('div')).toBe(true)
|
||||
})
|
||||
|
||||
describe('given currentUser has a social media account linked', () => {
|
||||
beforeEach(() => {
|
||||
getters = {
|
||||
'auth/user': () => {
|
||||
return {
|
||||
socialMedia: [{ id: 's1', url: socialMediaUrl }]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("displays a link to the currentUser's social media", () => {
|
||||
wrapper = Wrapper()
|
||||
const socialMediaLink = wrapper.find('a').attributes().href
|
||||
expect(socialMediaLink).toBe(socialMediaUrl)
|
||||
})
|
||||
})
|
||||
|
||||
describe('currentUser does not have a social media account linked', () => {
|
||||
it('allows a user to add a social media link', () => {
|
||||
wrapper = Wrapper()
|
||||
input = wrapper.find({ name: 'social-media' })
|
||||
input.element.value = socialMediaUrl
|
||||
input.trigger('input')
|
||||
submitBtn = wrapper.find('.ds-button')
|
||||
submitBtn.trigger('click')
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
107
webapp/pages/settings/my-social-media.vue
Normal file
107
webapp/pages/settings/my-social-media.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<ds-card :header="$t('settings.social-media.name')">
|
||||
<ds-space
|
||||
v-if="socialMediaLinks"
|
||||
margin-top="base"
|
||||
margin="x-small"
|
||||
>
|
||||
<ds-list>
|
||||
<ds-list-item
|
||||
v-for="link in socialMediaLinks"
|
||||
:key="link.url"
|
||||
>
|
||||
<a :href="link.url">
|
||||
<img
|
||||
:src="link.favicon"
|
||||
alt="Social Media link"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
{{ link.url }}
|
||||
</a>
|
||||
</ds-list-item>
|
||||
</ds-list>
|
||||
</ds-space>
|
||||
<div>
|
||||
<ds-input
|
||||
v-model="value"
|
||||
placeholder="Add social media url"
|
||||
name="social-media"
|
||||
:schema="{type: 'url'}"
|
||||
/>
|
||||
</div>
|
||||
<ds-space margin-top="base">
|
||||
<div>
|
||||
<ds-button
|
||||
primary
|
||||
@click="handleAddSocialMedia"
|
||||
>
|
||||
{{ $t('settings.social-media.submit') }}
|
||||
</ds-button>
|
||||
</div>
|
||||
</ds-space>
|
||||
</ds-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import { mapGetters, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
currentUser: 'auth/user'
|
||||
}),
|
||||
socialMediaLinks() {
|
||||
const { socialMedia = [] } = this.currentUser
|
||||
return socialMedia.map(socialMedia => {
|
||||
const { url } = socialMedia
|
||||
const matches = url.match(
|
||||
/^(?:https?:\/\/)?(?:[^@\n])?(?:www\.)?([^:\/\n?]+)/g
|
||||
)
|
||||
const [domain] = matches || []
|
||||
const favicon = domain ? `${domain}/favicon.ico` : null
|
||||
return { url, favicon }
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
setCurrentUser: 'auth/SET_USER'
|
||||
}),
|
||||
handleAddSocialMedia() {
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: gql`
|
||||
mutation($url: String!) {
|
||||
CreateSocialMedia(url: $url) {
|
||||
url
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
url: this.value
|
||||
},
|
||||
update: (store, { data }) => {
|
||||
const socialMedia = [
|
||||
...this.currentUser.socialMedia,
|
||||
data.CreateSocialMedia
|
||||
]
|
||||
this.setCurrentUser({
|
||||
...this.currentUser,
|
||||
socialMedia
|
||||
})
|
||||
}
|
||||
})
|
||||
.then(
|
||||
this.$toast.success(this.$t('settings.social-media.success')),
|
||||
(this.value = '')
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -83,6 +83,10 @@ export const actions = {
|
||||
role
|
||||
about
|
||||
locationName
|
||||
socialMedia {
|
||||
id
|
||||
url
|
||||
}
|
||||
}
|
||||
}`)
|
||||
})
|
||||
|
||||
@ -1473,10 +1473,10 @@
|
||||
"@vue/babel-plugin-transform-vue-jsx" "^1.0.0-beta.3"
|
||||
camelcase "^5.0.0"
|
||||
|
||||
"@vue/cli-shared-utils@~3.5.1":
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.5.1.tgz#71d66f06fc619ba28df279bd7d37ba1ba29c9066"
|
||||
integrity sha512-hCB7UbKeeC41w2Q8+Q7jmw3gHdq+ltRqp80S3uDRRGxwiOhxrSmdBHMzKUjh01L8bXOBRgvLey+BERi1Nj9n6Q==
|
||||
"@vue/cli-shared-utils@~3.6.0":
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-3.6.0.tgz#43937a2ea42b809dcd35d9348edf318ebc28b0d9"
|
||||
integrity sha512-C8nTiJ7o+dncNLyOIOZF8P4bMJdOVXhWOuwyZKqn8k3CcsQVzuLyCKUHHezWc+sI+PJi4wIg2ZffCiueeIXZ+w==
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
execa "^1.0.0"
|
||||
@ -1485,10 +1485,10 @@
|
||||
lru-cache "^5.1.1"
|
||||
node-ipc "^9.1.1"
|
||||
opn "^5.3.0"
|
||||
ora "^3.1.0"
|
||||
ora "^3.4.0"
|
||||
request "^2.87.0"
|
||||
request-promise-native "^1.0.7"
|
||||
semver "^5.5.0"
|
||||
semver "^6.0.0"
|
||||
string.prototype.padstart "^3.0.0"
|
||||
|
||||
"@vue/component-compiler-utils@^2.5.1":
|
||||
@ -1816,6 +1816,11 @@ ansi-regex@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9"
|
||||
integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==
|
||||
|
||||
ansi-regex@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
@ -6758,11 +6763,6 @@ lodash.memoize@^4.1.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||
|
||||
lodash.merge@^4.6.1:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
|
||||
integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==
|
||||
|
||||
lodash.mergewith@^4.6.0:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
|
||||
@ -7676,16 +7676,16 @@ optionator@^0.8.1, optionator@^0.8.2:
|
||||
type-check "~0.3.2"
|
||||
wordwrap "~1.0.0"
|
||||
|
||||
ora@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ora/-/ora-3.2.0.tgz#67e98a7e11f7f0ac95deaaaf11bb04de3d09e481"
|
||||
integrity sha512-XHMZA5WieCbtg+tu0uPF8CjvwQdNzKCX6BVh3N6GFsEXH40mTk5dsw/ya1lBTUGJslcEFJFQ8cBhOgkkZXQtMA==
|
||||
ora@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"
|
||||
integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
cli-cursor "^2.1.0"
|
||||
cli-spinners "^2.0.0"
|
||||
log-symbols "^2.2.0"
|
||||
strip-ansi "^5.0.0"
|
||||
strip-ansi "^5.2.0"
|
||||
wcwidth "^1.0.1"
|
||||
|
||||
orderedmap@^1.0.0:
|
||||
@ -10047,6 +10047,13 @@ strip-ansi@^5.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^4.0.0"
|
||||
|
||||
strip-ansi@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
|
||||
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
|
||||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-bom@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
|
||||
@ -10807,12 +10814,12 @@ uuid@^3.1.0, uuid@^3.3.2:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||
|
||||
v-tooltip@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.0.tgz#022c9019198119cd735f9f09d7208043c651bdae"
|
||||
integrity sha512-AXUUA7G06AiGmmxh+Om0/kgw52OTNzNxAXxomgH+ehPVr5GRJKp9b8lxJBWwQkUcdULYOE5u8Nf2IQ1gIJeP3g==
|
||||
v-tooltip@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.1.tgz#ba959552651dfa33210170ac9cfcce3525abe797"
|
||||
integrity sha512-Ifby9zx34MtzZRSYlr+tFQjXxHd8NZG9YoEHdeSQb8qwFGRQZhc8JVnxKBKUDGFowLUEzfX4e3mVmqrLw7b5oQ==
|
||||
dependencies:
|
||||
lodash.merge "^4.6.1"
|
||||
lodash "^4.17.11"
|
||||
popper.js "^1.15.0"
|
||||
vue-resize "^0.4.5"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user