fix conflicts with merge

This commit is contained in:
Kapil Jain 2019-10-17 15:17:54 -04:00
commit f6e70bee25
128 changed files with 5429 additions and 2617 deletions

View File

@ -2,138 +2,62 @@
## [Unreleased](https://github.com/Human-Connection/Human-Connection/tree/HEAD)
[Full Changelog](https://github.com/Human-Connection/Human-Connection/compare/0.1.0...HEAD)
**Fixed bugs:**
- 🐛 \[Bug\] Update maintenance page email [\#1731](https://github.com/Human-Connection/Human-Connection/issues/1731)
- 🐛 \[Bug\] Editing comments is not reactive again [\#1718](https://github.com/Human-Connection/Human-Connection/issues/1718)
- 🐛 \[Bug\] Comments with mentions in the end not displayed [\#1665](https://github.com/Human-Connection/Human-Connection/issues/1665)
- 🐛 \[Bug\] Delete the Sleep Icon [\#1659](https://github.com/Human-Connection/Human-Connection/issues/1659)
- 🐛 \[Bug\] Far to less Characters per Contribution \(2000\) [\#1639](https://github.com/Human-Connection/Human-Connection/issues/1639)
- 🐛 \[Bug\] Notifications do just update with page reload [\#1637](https://github.com/Human-Connection/Human-Connection/issues/1637)
- 🐛 \[Bug\] Can no users used all hashtags be correct? [\#1632](https://github.com/Human-Connection/Human-Connection/issues/1632)
- 🐛 \[Bug\] Create account has no info about email, no localisation, no HC logo [\#1631](https://github.com/Human-Connection/Human-Connection/issues/1631)
- 🐛 \[Bug\] Embeds are displayed when creating comments but get removed [\#1547](https://github.com/Human-Connection/Human-Connection/issues/1547)
- 🐛 \[Bug\] One cypress test fails but it does not fail the build [\#1312](https://github.com/Human-Connection/Human-Connection/issues/1312)
- 🐛 \[Bug\] TypeError: Cannot read property 'offsetTop' of null [\#1273](https://github.com/Human-Connection/Human-Connection/issues/1273)
[Full Changelog](https://github.com/Human-Connection/Human-Connection/compare/0.1.4...HEAD)
**Closed issues:**
- 🚀 \[Feature\] Extend Emoticons [\#1745](https://github.com/Human-Connection/Human-Connection/issues/1745)
- 🚀 \[Feature\] Change slug [\#1650](https://github.com/Human-Connection/Human-Connection/issues/1650)
- 🚀 \[Feature\] Make the slug more visible and usable [\#1486](https://github.com/Human-Connection/Human-Connection/issues/1486)
- 🚀 \[Feature\] Report with reason [\#1469](https://github.com/Human-Connection/Human-Connection/issues/1469)
- 🚀 \[Feature\] Make Invite an Registration E-Mails translatable and pretty [\#1186](https://github.com/Human-Connection/Human-Connection/issues/1186)
- 🚀 \[Feature\] @Username: Unique user identification if identical profile names exist [\#1069](https://github.com/Human-Connection/Human-Connection/issues/1069)
- 🚀 \[Feature\] Register as a new User [\#1785](https://github.com/Human-Connection/Human-Connection/issues/1785)
**Merged pull requests:**
- Bump metascraper-logo from 5.7.5 to 5.7.6 in /backend [\#1783](https://github.com/Human-Connection/Human-Connection/pull/1783) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-url from 5.7.5 to 5.7.6 in /backend [\#1782](https://github.com/Human-Connection/Human-Connection/pull/1782) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql-middleware from 3.0.5 to 4.0.1 in /backend [\#1781](https://github.com/Human-Connection/Human-Connection/pull/1781) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint from 6.4.0 to 6.5.1 in /backend [\#1780](https://github.com/Human-Connection/Human-Connection/pull/1780) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-audio from 5.7.5 to 5.7.6 in /backend [\#1779](https://github.com/Human-Connection/Human-Connection/pull/1779) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-publisher from 5.7.4 to 5.7.6 in /backend [\#1778](https://github.com/Human-Connection/Human-Connection/pull/1778) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-youtube from 5.7.5 to 5.7.6 in /backend [\#1777](https://github.com/Human-Connection/Human-Connection/pull/1777) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/preset-env from 7.6.0 to 7.6.2 in /backend [\#1776](https://github.com/Human-Connection/Human-Connection/pull/1776) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-soundcloud from 5.7.4 to 5.7.6 in /backend [\#1775](https://github.com/Human-Connection/Human-Connection/pull/1775) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-author from 5.7.4 to 5.7.6 in /backend [\#1774](https://github.com/Human-Connection/Human-Connection/pull/1774) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Fix failing test [\#1772](https://github.com/Human-Connection/Human-Connection/pull/1772) ([aonomike](https://github.com/aonomike))
- Bump metascraper-date from 5.7.4 to 5.7.6 in /backend [\#1771](https://github.com/Human-Connection/Human-Connection/pull/1771) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump tiptap-extensions from 1.27.0 to 1.28.0 in /webapp [\#1770](https://github.com/Human-Connection/Human-Connection/pull/1770) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump date-fns from 2.3.0 to 2.4.1 in /backend [\#1769](https://github.com/Human-Connection/Human-Connection/pull/1769) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump date-fns from 2.4.0 to 2.4.1 in /webapp [\#1768](https://github.com/Human-Connection/Human-Connection/pull/1768) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql-middleware-sentry from 3.2.0 to 3.2.1 in /backend [\#1767](https://github.com/Human-Connection/Human-Connection/pull/1767) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-lang from 5.7.4 to 5.7.6 in /backend [\#1766](https://github.com/Human-Connection/Human-Connection/pull/1766) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump tiptap from 1.25.0 to 1.26.0 in /webapp [\#1765](https://github.com/Human-Connection/Human-Connection/pull/1765) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-video from 5.7.5 to 5.7.6 in /backend [\#1764](https://github.com/Human-Connection/Human-Connection/pull/1764) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump apollo-server from 2.9.3 to 2.9.4 in /backend [\#1762](https://github.com/Human-Connection/Human-Connection/pull/1762) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-image from 5.7.5 to 5.7.6 in /backend [\#1761](https://github.com/Human-Connection/Human-Connection/pull/1761) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-loader from 3.0.1 to 3.0.2 in /webapp [\#1760](https://github.com/Human-Connection/Human-Connection/pull/1760) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-title from 5.7.5 to 5.7.6 in /backend [\#1759](https://github.com/Human-Connection/Human-Connection/pull/1759) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump nodemon from 1.19.2 to 1.19.3 in /backend [\#1758](https://github.com/Human-Connection/Human-Connection/pull/1758) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- fix email middleware transport config [\#1757](https://github.com/Human-Connection/Human-Connection/pull/1757) ([vbelolapotkov](https://github.com/vbelolapotkov))
- 1273 fix post page nav suggestions [\#1756](https://github.com/Human-Connection/Human-Connection/pull/1756) ([roschaefer](https://github.com/roschaefer))
- docs: moves storybook into webapp/README.md [\#1755](https://github.com/Human-Connection/Human-Connection/pull/1755) ([roschaefer](https://github.com/roschaefer))
- Bump date-fns from 2.2.1 to 2.4.0 in /webapp [\#1752](https://github.com/Human-Connection/Human-Connection/pull/1752) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- fix: Github's security vulnerability warning [\#1751](https://github.com/Human-Connection/Human-Connection/pull/1751) ([roschaefer](https://github.com/roschaefer))
- update neo4j docker-compose config [\#1750](https://github.com/Human-Connection/Human-Connection/pull/1750) ([vbelolapotkov](https://github.com/vbelolapotkov))
- 🍰 Try to fix VSCode format works against ESLint [\#1749](https://github.com/Human-Connection/Human-Connection/pull/1749) ([Tirokk](https://github.com/Tirokk))
- Bump neo4j from 3.5.9 to 3.5.11 in /neo4j [\#1739](https://github.com/Human-Connection/Human-Connection/pull/1739) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql from 14.5.7 to 14.5.8 in /backend [\#1738](https://github.com/Human-Connection/Human-Connection/pull/1738) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-youtube from 5.7.4 to 5.7.5 in /backend [\#1737](https://github.com/Human-Connection/Human-Connection/pull/1737) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Update to 0.1.1 [\#1734](https://github.com/Human-Connection/Human-Connection/pull/1734) ([roschaefer](https://github.com/roschaefer))
- Update maintenance page email to support@... [\#1732](https://github.com/Human-Connection/Human-Connection/pull/1732) ([mattwr18](https://github.com/mattwr18))
- Bump @babel/register from 7.6.0 to 7.6.2 in /backend [\#1730](https://github.com/Human-Connection/Human-Connection/pull/1730) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql from 14.5.7 to 14.5.8 in /webapp [\#1729](https://github.com/Human-Connection/Human-Connection/pull/1729) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.6.0 to 7.6.2 in /backend [\#1728](https://github.com/Human-Connection/Human-Connection/pull/1728) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-loader from 3.0.0 to 3.0.1 in /webapp [\#1727](https://github.com/Human-Connection/Human-Connection/pull/1727) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump apollo-server-express from 2.9.3 to 2.9.4 in /backend [\#1726](https://github.com/Human-Connection/Human-Connection/pull/1726) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/node from 7.6.1 to 7.6.2 in /backend [\#1725](https://github.com/Human-Connection/Human-Connection/pull/1725) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Fix bug UpdateComment, Fix styling on Comment [\#1719](https://github.com/Human-Connection/Human-Connection/pull/1719) ([mattwr18](https://github.com/mattwr18))
- Bump apollo-server-testing from 2.9.3 to 2.9.4 in /backend [\#1717](https://github.com/Human-Connection/Human-Connection/pull/1717) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-title from 5.7.4 to 5.7.5 in /backend [\#1715](https://github.com/Human-Connection/Human-Connection/pull/1715) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump date-fns from 2.2.1 to 2.3.0 in /backend [\#1714](https://github.com/Human-Connection/Human-Connection/pull/1714) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/cli from 7.6.0 to 7.6.2 in /backend [\#1713](https://github.com/Human-Connection/Human-Connection/pull/1713) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- \[WIP\]1706 refactor shout spec [\#1712](https://github.com/Human-Connection/Human-Connection/pull/1712) ([aonomike](https://github.com/aonomike))
- Remove repetitive labels from emote button [\#1702](https://github.com/Human-Connection/Human-Connection/pull/1702) ([roschaefer](https://github.com/roschaefer))
- fix the bug with scrolling post comments into view [\#1701](https://github.com/Human-Connection/Human-Connection/pull/1701) ([vbelolapotkov](https://github.com/vbelolapotkov))
- Bump metascraper-description from 5.7.4 to 5.7.5 in /backend [\#1700](https://github.com/Human-Connection/Human-Connection/pull/1700) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-logo from 5.7.4 to 5.7.5 in /backend [\#1698](https://github.com/Human-Connection/Human-Connection/pull/1698) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-video from 5.7.4 to 5.7.5 in /backend [\#1697](https://github.com/Human-Connection/Human-Connection/pull/1697) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/core from 7.6.0 to 7.6.2 in /webapp [\#1696](https://github.com/Human-Connection/Human-Connection/pull/1696) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-image from 5.7.4 to 5.7.5 in /backend [\#1695](https://github.com/Human-Connection/Human-Connection/pull/1695) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @babel/preset-env from 7.6.0 to 7.6.2 in /webapp [\#1694](https://github.com/Human-Connection/Human-Connection/pull/1694) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-audio from 5.7.4 to 5.7.5 in /backend [\#1693](https://github.com/Human-Connection/Human-Connection/pull/1693) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump metascraper-url from 5.7.4 to 5.7.5 in /backend [\#1692](https://github.com/Human-Connection/Human-Connection/pull/1692) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bugfix create user page - missing submit buttons [\#1690](https://github.com/Human-Connection/Human-Connection/pull/1690) ([roschaefer](https://github.com/roschaefer))
- Remove sleep icon from comments list [\#1689](https://github.com/Human-Connection/Human-Connection/pull/1689) ([alina-beck](https://github.com/alina-beck))
- Configure docker to work with storybook [\#1688](https://github.com/Human-Connection/Human-Connection/pull/1688) ([mattwr18](https://github.com/mattwr18))
- Add Comment story, add spacing above user info [\#1685](https://github.com/Human-Connection/Human-Connection/pull/1685) ([mattwr18](https://github.com/mattwr18))
- Fix create account page has no logo, localisation [\#1681](https://github.com/Human-Connection/Human-Connection/pull/1681) ([roschaefer](https://github.com/roschaefer))
- Fix intermittent backend specs [\#1679](https://github.com/Human-Connection/Human-Connection/pull/1679) ([roschaefer](https://github.com/roschaefer))
- Improve comments output [\#1678](https://github.com/Human-Connection/Human-Connection/pull/1678) ([mattwr18](https://github.com/mattwr18))
- Fix intermittent failing test [\#1677](https://github.com/Human-Connection/Human-Connection/pull/1677) ([mattwr18](https://github.com/mattwr18))
- Bump graphql from 14.5.6 to 14.5.7 in /webapp [\#1676](https://github.com/Human-Connection/Human-Connection/pull/1676) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump graphql from 14.5.6 to 14.5.7 in /backend [\#1675](https://github.com/Human-Connection/Human-Connection/pull/1675) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cookie-universal-nuxt from 2.0.17 to 2.0.18 in /webapp [\#1674](https://github.com/Human-Connection/Human-Connection/pull/1674) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @hapi/joi from 16.1.2 to 16.1.4 in /backend [\#1673](https://github.com/Human-Connection/Human-Connection/pull/1673) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump helmet from 3.21.0 to 3.21.1 in /backend [\#1672](https://github.com/Human-Connection/Human-Connection/pull/1672) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cypress-file-upload from 3.3.3 to 3.3.4 [\#1671](https://github.com/Human-Connection/Human-Connection/pull/1671) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump codecov from 3.6.0 to 3.6.1 [\#1670](https://github.com/Human-Connection/Human-Connection/pull/1670) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Make Human Connection a Progressive Web App [\#1668](https://github.com/Human-Connection/Human-Connection/pull/1668) ([roschaefer](https://github.com/roschaefer))
- Remove contentExcerpt from comments [\#1667](https://github.com/Human-Connection/Human-Connection/pull/1667) ([mattwr18](https://github.com/mattwr18))
- Remove follow type enum [\#1660](https://github.com/Human-Connection/Human-Connection/pull/1660) ([vbelolapotkov](https://github.com/vbelolapotkov))
- 🍰 Notifications self update and refactoring [\#1658](https://github.com/Human-Connection/Human-Connection/pull/1658) ([Tirokk](https://github.com/Tirokk))
- Bump mustache from 3.0.3 to 3.1.0 in /backend [\#1655](https://github.com/Human-Connection/Human-Connection/pull/1655) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @hapi/joi from 16.1.1 to 16.1.2 in /backend [\#1654](https://github.com/Human-Connection/Human-Connection/pull/1654) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @nuxtjs/apollo from 4.0.0-rc13 to 4.0.0-rc13.1 in /webapp [\#1653](https://github.com/Human-Connection/Human-Connection/pull/1653) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump codecov from 3.5.0 to 3.6.0 [\#1652](https://github.com/Human-Connection/Human-Connection/pull/1652) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Change your own slug [\#1651](https://github.com/Human-Connection/Human-Connection/pull/1651) ([roschaefer](https://github.com/roschaefer))
- Fix bug where short comments scrub links [\#1649](https://github.com/Human-Connection/Human-Connection/pull/1649) ([mattwr18](https://github.com/mattwr18))
- Fix styling issue in comments list [\#1648](https://github.com/Human-Connection/Human-Connection/pull/1648) ([mattwr18](https://github.com/mattwr18))
- Provider list approval hard cut [\#1647](https://github.com/Human-Connection/Human-Connection/pull/1647) ([ogerly](https://github.com/ogerly))
- Point the changelog to Github [\#1646](https://github.com/Human-Connection/Human-Connection/pull/1646) ([roschaefer](https://github.com/roschaefer))
- Bump eslint-plugin-prettier from 3.1.0 to 3.1.1 in /webapp [\#1643](https://github.com/Human-Connection/Human-Connection/pull/1643) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump eslint-plugin-prettier from 3.1.0 to 3.1.1 in /backend [\#1642](https://github.com/Human-Connection/Human-Connection/pull/1642) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Fix \#1639 - No limits for post length [\#1641](https://github.com/Human-Connection/Human-Connection/pull/1641) ([roschaefer](https://github.com/roschaefer))
- 1486 make slug more usable [\#1640](https://github.com/Human-Connection/Human-Connection/pull/1640) ([roschaefer](https://github.com/roschaefer))
- Update de.json [\#1636](https://github.com/Human-Connection/Human-Connection/pull/1636) ([datenbrei](https://github.com/datenbrei))
- Exclude broken maintenance-worker docker image [\#1635](https://github.com/Human-Connection/Human-Connection/pull/1635) ([roschaefer](https://github.com/roschaefer))
- Fix bug where about must not be empty string [\#1630](https://github.com/Human-Connection/Human-Connection/pull/1630) ([mattwr18](https://github.com/mattwr18))
- Bump @storybook/addon-a11y from 5.2.0 to 5.2.1 in /webapp [\#1627](https://github.com/Human-Connection/Human-Connection/pull/1627) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @storybook/addon-actions from 5.2.0 to 5.2.1 in /webapp [\#1625](https://github.com/Human-Connection/Human-Connection/pull/1625) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @storybook/vue from 5.2.0 to 5.2.1 in /webapp [\#1624](https://github.com/Human-Connection/Human-Connection/pull/1624) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cross-env from 5.2.1 to 6.0.0 in /backend [\#1623](https://github.com/Human-Connection/Human-Connection/pull/1623) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump @hapi/joi from 16.0.1 to 16.1.1 in /backend [\#1622](https://github.com/Human-Connection/Human-Connection/pull/1622) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Bump cross-env from 5.2.1 to 6.0.0 [\#1621](https://github.com/Human-Connection/Human-Connection/pull/1621) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- 1612 refactor moderator spec [\#1620](https://github.com/Human-Connection/Human-Connection/pull/1620) ([aonomike](https://github.com/aonomike))
- Fix disappearing embeds on comments [\#1618](https://github.com/Human-Connection/Human-Connection/pull/1618) ([mattwr18](https://github.com/mattwr18))
- links\_to\_imprint\_and\_privacy\_policy\_changed\_to\_human-connection.org [\#1615](https://github.com/Human-Connection/Human-Connection/pull/1615) ([ogerly](https://github.com/ogerly))
- Bump metascraper-author from 5.6.6 to 5.7.4 in /backend [\#1610](https://github.com/Human-Connection/Human-Connection/pull/1610) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Configure emails [\#1599](https://github.com/Human-Connection/Human-Connection/pull/1599) ([alina-beck](https://github.com/alina-beck))
- Improve follow/ufollow mutation [\#1596](https://github.com/Human-Connection/Human-Connection/pull/1596) ([vbelolapotkov](https://github.com/vbelolapotkov))
- Refactor embed settings page [\#1861](https://github.com/Human-Connection/Human-Connection/pull/1861) ([roschaefer](https://github.com/roschaefer))
- Implement public registration [\#1814](https://github.com/Human-Connection/Human-Connection/pull/1814) ([roschaefer](https://github.com/roschaefer))
## [0.1.4](https://github.com/Human-Connection/Human-Connection/tree/0.1.4) (2019-10-10)
[Full Changelog](https://github.com/Human-Connection/Human-Connection/compare/0.1.3...0.1.4)
**Fixed bugs:**
- 🐛 \[Bug\] extend Title to 100 characters [\#1812](https://github.com/Human-Connection/Human-Connection/issues/1812)
**Closed issues:**
- 🚀 \[Feature\] Make Email of a User visible for Moderators [\#1704](https://github.com/Human-Connection/Human-Connection/issues/1704)
- 🚀 \[Feature\] save embeds iframe permanently [\#1684](https://github.com/Human-Connection/Human-Connection/issues/1684)
**Merged pull requests:**
- Add Hall of Fame to README [\#1859](https://github.com/Human-Connection/Human-Connection/pull/1859) ([roschaefer](https://github.com/roschaefer))
- build\(deps-dev\): bump cypress-cucumber-preprocessor from 1.16.1 to 1.16.2 [\#1855](https://github.com/Human-Connection/Human-Connection/pull/1855) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump nodemailer from 6.3.0 to 6.3.1 in /backend [\#1854](https://github.com/Human-Connection/Human-Connection/pull/1854) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Save user setting to show embed code II [\#1852](https://github.com/Human-Connection/Human-Connection/pull/1852) ([ogerly](https://github.com/ogerly))
- Title character increased from 64 to 100 [\#1850](https://github.com/Human-Connection/Human-Connection/pull/1850) ([ogerly](https://github.com/ogerly))
- build\(deps-dev\): bump @babel/core from 7.6.2 to 7.6.3 in /webapp [\#1848](https://github.com/Human-Connection/Human-Connection/pull/1848) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump @babel/cli from 7.6.2 to 7.6.3 in /backend [\#1847](https://github.com/Human-Connection/Human-Connection/pull/1847) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump @babel/preset-env from 7.6.2 to 7.6.3 in /backend [\#1846](https://github.com/Human-Connection/Human-Connection/pull/1846) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump @babel/node from 7.6.2 to 7.6.3 in /backend [\#1843](https://github.com/Human-Connection/Human-Connection/pull/1843) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump styleguide from `808b3c5` to `d46fc15` [\#1839](https://github.com/Human-Connection/Human-Connection/pull/1839) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump @storybook/addon-actions from 5.2.1 to 5.2.3 in /webapp [\#1838](https://github.com/Human-Connection/Human-Connection/pull/1838) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump node from 12.11.0-alpine to 12.11.1-alpine in /backend [\#1837](https://github.com/Human-Connection/Human-Connection/pull/1837) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump @storybook/addon-a11y from 5.2.1 to 5.2.3 in /webapp [\#1836](https://github.com/Human-Connection/Human-Connection/pull/1836) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump node from 12.11.0-alpine to 12.11.1-alpine in /webapp [\#1835](https://github.com/Human-Connection/Human-Connection/pull/1835) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump @storybook/vue from 5.2.1 to 5.2.3 in /webapp [\#1834](https://github.com/Human-Connection/Human-Connection/pull/1834) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump cucumber from 6.0.1 to 6.0.2 in /backend [\#1833](https://github.com/Human-Connection/Human-Connection/pull/1833) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Fix typo in email template [\#1829](https://github.com/Human-Connection/Human-Connection/pull/1829) ([alina-beck](https://github.com/alina-beck))
- build\(deps-dev\): bump cucumber from 5.1.0 to 6.0.1 in /backend [\#1827](https://github.com/Human-Connection/Human-Connection/pull/1827) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump node from 12.10.0-alpine to 12.11.0-alpine in /backend [\#1826](https://github.com/Human-Connection/Human-Connection/pull/1826) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump node from 12.10.0-alpine to 12.11.0-alpine in /webapp [\#1824](https://github.com/Human-Connection/Human-Connection/pull/1824) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump apollo-server-testing from 2.9.4 to 2.9.5 in /backend [\#1823](https://github.com/Human-Connection/Human-Connection/pull/1823) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump tiptap-extensions from 1.28.0 to 1.28.3 in /webapp [\#1822](https://github.com/Human-Connection/Human-Connection/pull/1822) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump tiptap from 1.26.0 to 1.26.3 in /webapp [\#1821](https://github.com/Human-Connection/Human-Connection/pull/1821) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump eslint-config-prettier from 6.3.0 to 6.4.0 in /webapp [\#1819](https://github.com/Human-Connection/Human-Connection/pull/1819) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump eslint-config-prettier from 6.3.0 to 6.4.0 in /backend [\#1818](https://github.com/Human-Connection/Human-Connection/pull/1818) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps\): bump @hapi/joi from 16.1.5 to 16.1.7 in /backend [\#1817](https://github.com/Human-Connection/Human-Connection/pull/1817) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- build\(deps-dev\): bump cypress-cucumber-preprocessor from 1.16.0 to 1.16.1 [\#1816](https://github.com/Human-Connection/Human-Connection/pull/1816) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- Update to 0.1.3 [\#1813](https://github.com/Human-Connection/Human-Connection/pull/1813) ([mattwr18](https://github.com/mattwr18))
- Display user email for administrators [\#1808](https://github.com/Human-Connection/Human-Connection/pull/1808) ([aonomike](https://github.com/aonomike))
- build\(deps\): bump nuxt from 2.9.2 to 2.10.0 in /webapp [\#1804](https://github.com/Human-Connection/Human-Connection/pull/1804) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview))
- \[TEST\] Save user setting to show embed code [\#1686](https://github.com/Human-Connection/Human-Connection/pull/1686) ([ogerly](https://github.com/ogerly))

View File

@ -50,6 +50,8 @@ Join our friendly open-source community on [Discord](https://discordapp.com/invi
Just introduce yourself at `#introduce-yourself` and mention `@@Mentor` to get you onboard :neckbeard:
Check out the [contribution guideline](./CONTRIBUTING.md), too!
[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/0)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/0)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/1)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/1)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/2)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/2)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/3)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/3)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/4)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/4)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/5)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/5)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/6)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/6)[![](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/images/7)](https://sourcerer.io/fame/roschaefer/Human-Connection/Human-Connection/links/7)
## Attributions

View File

@ -1 +1 @@
0.1.2
0.1.3

View File

@ -17,3 +17,4 @@ PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
SENTRY_DSN_BACKEND=
COMMIT=
PUBLIC_REGISTRATION=false

View File

@ -1,4 +1,4 @@
FROM node:12.10.0-alpine as base
FROM node:12.12.0-alpine as base
LABEL Description="Backend of the Social Network Human-Connection.org" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
EXPOSE 4000
@ -24,4 +24,5 @@ FROM base as production
ENV NODE_ENV=production
COPY --from=build-and-test /nitro-backend/dist ./dist
COPY ./public/img/ ./public/img/
COPY ./public/providers.json ./public/providers.json
RUN yarn install --production=true --frozen-lockfile --non-interactive --no-cache

View File

@ -41,22 +41,22 @@
]
},
"dependencies": {
"@hapi/joi": "^16.1.4",
"@sentry/node": "^5.6.2",
"@hapi/joi": "^16.1.7",
"@sentry/node": "^5.7.0",
"apollo-cache-inmemory": "~1.6.3",
"apollo-client": "~2.6.4",
"apollo-link-context": "~1.0.19",
"apollo-link-http": "~1.5.16",
"apollo-server": "~2.9.4",
"apollo-server-express": "^2.9.4",
"apollo-server": "~2.9.6",
"apollo-server-express": "^2.9.6",
"babel-plugin-transform-runtime": "^6.23.0",
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.3",
"cors": "~2.8.5",
"cross-env": "~6.0.2",
"date-fns": "2.4.1",
"cross-env": "~6.0.3",
"date-fns": "2.5.0",
"debug": "~4.1.1",
"dotenv": "~8.1.0",
"dotenv": "~8.2.0",
"express": "^4.17.1",
"faker": "Marak/faker.js#master",
"graphql": "^14.5.8",
@ -93,7 +93,7 @@
"neo4j-graphql-js": "^2.7.2",
"neode": "^0.3.3",
"node-fetch": "~2.6.0",
"nodemailer": "^6.3.0",
"nodemailer": "^6.3.1",
"nodemailer-html-to-text": "^3.1.0",
"npm-run-all": "~4.1.5",
"request": "~2.88.0",
@ -105,30 +105,30 @@
"xregexp": "^4.2.4"
},
"devDependencies": {
"@babel/cli": "~7.6.2",
"@babel/core": "~7.6.2",
"@babel/node": "~7.6.2",
"@babel/cli": "~7.6.4",
"@babel/core": "~7.6.4",
"@babel/node": "~7.6.3",
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.6.2",
"@babel/preset-env": "~7.6.3",
"@babel/register": "~7.6.2",
"apollo-server-testing": "~2.9.4",
"apollo-server-testing": "~2.9.6",
"babel-core": "~7.0.0-0",
"babel-eslint": "~10.0.3",
"babel-jest": "~24.9.0",
"chai": "~4.2.0",
"cucumber": "~5.1.0",
"cucumber": "~6.0.2",
"eslint": "~6.5.1",
"eslint-config-prettier": "~6.3.0",
"eslint-config-prettier": "~6.4.0",
"eslint-config-standard": "~14.1.0",
"eslint-plugin-import": "~2.18.2",
"eslint-plugin-jest": "~22.17.0",
"eslint-plugin-jest": "~22.19.0",
"eslint-plugin-node": "~10.0.0",
"eslint-plugin-prettier": "~3.1.1",
"eslint-plugin-promise": "~4.2.1",
"eslint-plugin-standard": "~4.0.1",
"graphql-request": "~1.8.2",
"jest": "~24.9.0",
"nodemon": "~1.19.3",
"nodemon": "~1.19.4",
"prettier": "~1.18.2",
"supertest": "~4.0.2"
}

View File

@ -21,7 +21,12 @@ const {
GRAPHQL_URI = 'http://localhost:4000',
} = process.env
export const requiredConfigs = { MAPBOX_TOKEN, JWT_SECRET, PRIVATE_KEY_PASSPHRASE }
export const requiredConfigs = {
MAPBOX_TOKEN,
JWT_SECRET,
PRIVATE_KEY_PASSPHRASE,
}
export const smtpConfigs = {
SMTP_HOST,
SMTP_PORT,
@ -30,7 +35,12 @@ export const smtpConfigs = {
SMTP_PASSWORD,
}
export const neo4jConfigs = { NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD }
export const serverConfigs = { GRAPHQL_PORT, CLIENT_URI, GRAPHQL_URI }
export const serverConfigs = {
GRAPHQL_PORT,
CLIENT_URI,
GRAPHQL_URI,
PUBLIC_REGISTRATION: process.env.PUBLIC_REGISTRATION === 'true',
}
export const developmentConfigs = {
DEBUG: process.env.NODE_ENV !== 'production' && process.env.DEBUG,

View File

@ -14,6 +14,7 @@ export default async (driver, authorizationHeader) => {
const session = driver.session()
const query = `
MATCH (user:User {id: $id, deleted: false, disabled: false })
SET user.lastActiveAt = toString(datetime())
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
LIMIT 1
`

View File

@ -1,9 +1,10 @@
import Factory from '../seed/factories/index'
import { getDriver } from '../bootstrap/neo4j'
import { getDriver, neode as getNeode } from '../bootstrap/neo4j'
import decode from './decode'
const factory = Factory()
const driver = getDriver()
const neode = getNeode()
// here is the decoded JWT token:
// {
@ -85,6 +86,33 @@ describe('decode', () => {
})
})
it('sets `lastActiveAt`', async () => {
let user = await neode.first('User', { id: 'u3' })
await expect(user.toJson()).resolves.not.toHaveProperty('lastActiveAt')
await decode(driver, authorizationHeader)
user = await neode.first('User', { id: 'u3' })
await expect(user.toJson()).resolves.toMatchObject({
lastActiveAt: expect.any(String),
})
})
it('updates `lastActiveAt` for every authenticated request', async () => {
let user = await neode.first('User', { id: 'u3' })
await user.update({
updatedAt: new Date().toISOString(),
lastActiveAt: '2019-10-03T23:33:08.598Z',
})
await expect(user.toJson()).resolves.toMatchObject({
lastActiveAt: '2019-10-03T23:33:08.598Z',
})
await decode(driver, authorizationHeader)
user = await neode.first('User', { id: 'u3' })
await expect(user.toJson()).resolves.toMatchObject({
// should be a different time by now ;)
lastActiveAt: expect.not.stringContaining('2019-10-03T23:33'),
})
})
describe('but user is deleted', () => {
beforeEach(async () => {
await user.update({ updatedAt: new Date().toISOString(), deleted: true })
@ -92,6 +120,7 @@ describe('decode', () => {
it('returns null', returnsNull)
})
describe('but user is disabled', () => {
beforeEach(async () => {
await user.update({ updatedAt: new Date().toISOString(), disabled: true })

View File

@ -18,7 +18,7 @@ export const signupTemplate = ({ email, nonce }) => {
subject,
html: mustache.render(
templates.layout,
{ actionUrl, supportUrl, subject },
{ actionUrl, nonce, supportUrl, subject },
{ content: templates.signup },
),
}

View File

@ -1,10 +1,6 @@
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
@ -61,7 +57,7 @@
<td
style="padding: 20px; padding-bottom: 0; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Falls Du deine E-Mail Adresse doch nicht ändern möchtest, kannst du diese Nachricht
einfach ignorieren. Mlde Dich gerne <a href="{{{ supportUrl }}}" style="color: #17b53e;">bei
einfach ignorieren. Melde Dich gerne <a href="{{{ supportUrl }}}" style="color: #17b53e;">bei
unserem Support Team</a>, wenn du noch Fragen hast!</p>
</td>
</tr>

View File

@ -142,60 +142,6 @@
}
}
</style>
<!-- LANGUAGE TOGGLE -->
<style>
.toggle+label {
display: inline-block;
height: 40px;
padding: 0 12px;
margin-top: 40px;
line-height: 38px;
font-family: Lato, sans-serif;
font-size: 16px;
border: 1px solid #cbc7d1;
cursor: pointer;
}
.toggle+label:hover {
background-color: #bee876;
}
.toggle:checked+label {
background-color: #19c243;
color: white;
}
.toggle-english+label {
border-bottom-right-radius: 50px;
border-top-right-radius: 50px;
border-left: none;
margin-left: -2px;
}
.toggle-german+label {
border-bottom-left-radius: 50px;
border-top-left-radius: 50px;
border-right: none;
margin-right: -2px;
}
.toggle-german:checked~table.email-german {
display: block;
}
.toggle-german:checked~table.email-english {
display: none;
}
.toggle-english:checked~table.email-english {
display: block;
}
.toggle-english:checked~table.email-german {
display: none;
}
</style>
</head>
<body width="100%" style="margin: 0; padding: 0 !important; mso-line-height-rule: exactly; background-color: #f5f4f6;">
@ -213,13 +159,7 @@
<td>
<![endif]-->
<!-- LANGUAGE TOGGLE -->
<input type="radio" name="language" class="toggle toggle-german" style="display: none;" id="toggle-german"
checked="checked">
<label for="toggle-german">Deutsch</label>
<input type="radio" name="language" class="toggle toggle-english" style="display:none;" id="toggle-english">
<label for="toggle-english">English</label>
<p style="margin: 0;"></p>
<p style="color:#19c243; font-style: italic; font-family: Lato, sans-serif; font-size: 16px; padding-top: 20px;">English version below!</p>
{{> content}}

View File

@ -1,10 +1,6 @@
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>

View File

@ -1,10 +1,6 @@
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>
@ -64,7 +60,9 @@
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">Falls Du Dich nicht selbst bei <a href="https://human-connection.org"
<p style="margin: 0;">Sollte der Button für Dich nicht funktionieren, kannst Du auch folgenden Code in Dein Browserfenster kopieren: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0;">Das funktioniert allerdings nur, wenn du Dich über unsere Website registriert hast.</p>
<p style="margin: 0; margin-top: 10px;">Falls Du Dich nicht selbst bei <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> angemeldet hast, schau doch mal vorbei!
Wir sind ein gemeinnütziges Aktionsnetzwerk von Menschen für Menschen.</p>
<p style="margin: 0; margin-top: 10px;">PS: Wenn Du keinen Account bei uns möchtest, kannst Du diese
@ -173,7 +171,9 @@
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 20px; font-family: Lato, sans-serif; font-size: 16px; line-height: 22px; color: #555555;">
<p style="margin: 0;">If you didn't sign up for <a href="https://human-connection.org"
<p style="margin: 0;">If the above button doesn't work, you can also copy the following code into your browser window: <span style="color: #17b53e;">{{{ nonce }}}</span></p>
<p style="margin: 0;">However, this only works if you have registered through our website.</p>
<p style="margin: 0; margin-top: 10px;">If you didn't sign up for <a href="https://human-connection.org"
style="color: #17b53e;">Human Connection</a> we recommend you to check it out!
It's a social network from people for people who want to connect and change the world together.</p>
<p style="margin: 0; margin-top: 10px;">PS: If you ignore this e-mail we will not create an account

View File

@ -1,10 +1,6 @@
<!-- Email Body German : BEGIN -->
<table class="email-german" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"
style="margin: auto;">
<tr>
<td style="padding: 20px 0; text-align: center">
</td>
</tr>
<!-- Hero Image, Flush : BEGIN -->
<tr>

View File

@ -111,6 +111,8 @@ const noEmailFilter = rule({
return !('email' in args)
})
const publicRegistration = rule()(() => !!CONFIG.PUBLIC_REGISTRATION)
// Permissions
const permissions = shield(
{
@ -120,7 +122,7 @@ const permissions = shield(
embed: allow,
Category: allow,
Tag: allow,
Report: isModerator,
reports: isModerator,
statistics: allow,
currentUser: allow,
Post: or(onlyEnabledContent, isModerator),
@ -137,7 +139,7 @@ const permissions = shield(
'*': deny,
login: allow,
SignupByInvitation: allow,
Signup: isAdmin,
Signup: or(publicRegistration, isAdmin),
SignupVerification: allow,
CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)),
UpdateUser: onlyYourself,
@ -174,7 +176,7 @@ const permissions = shield(
VerifyEmailAddress: isAuthenticated,
},
User: {
email: isMyOwn,
email: or(isMyOwn, isAdmin),
},
},
{

View File

@ -1,22 +1,63 @@
import { GraphQLClient } from 'graphql-request'
import { createTestClient } from 'apollo-server-testing'
import createServer from '../server'
import Factory from '../seed/factories'
import { host, login } from '../jest/helpers'
import { gql } from '../jest/helpers'
import { getDriver, neode as getNeode } from '../bootstrap/neo4j'
const factory = Factory()
const instance = getNeode()
const driver = getDriver()
let query, authenticatedUser, owner, anotherRegularUser, administrator, variables, moderator
const userQuery = gql`
query($name: String) {
User(name: $name) {
email
}
}
`
describe('authorization', () => {
beforeAll(async () => {
await factory.cleanDatabase()
const { server } = createServer({
context: () => ({
driver,
instance,
user: authenticatedUser,
}),
})
query = createTestClient(server).query
})
describe('given two existing users', () => {
beforeEach(async () => {
await factory.create('User', {
email: 'owner@example.org',
name: 'Owner',
password: 'iamtheowner',
})
await factory.create('User', {
email: 'someone@example.org',
name: 'Someone else',
password: 'else',
})
;[owner, anotherRegularUser, administrator, moderator] = await Promise.all([
factory.create('User', {
email: 'owner@example.org',
name: 'Owner',
password: 'iamtheowner',
}),
factory.create('User', {
email: 'another.regular.user@example.org',
name: 'Another Regular User',
password: 'else',
}),
factory.create('User', {
email: 'admin@example.org',
name: 'Admin',
password: 'admin',
role: 'admin',
}),
factory.create('User', {
email: 'moderator@example.org',
name: 'Moderator',
password: 'moderator',
role: 'moderator',
}),
])
variables = {}
})
afterEach(async () => {
@ -24,66 +65,77 @@ describe('authorization', () => {
})
describe('access email address', () => {
let headers = {}
let loginCredentials = null
const action = async () => {
if (loginCredentials) {
headers = await login(loginCredentials)
}
const graphQLClient = new GraphQLClient(host, { headers })
return graphQLClient.request('{User(name: "Owner") { email } }')
}
describe('not logged in', () => {
it('rejects', async () => {
await expect(action()).rejects.toThrow('Not Authorised!')
})
it("does not expose the owner's email address", async () => {
let response = {}
try {
await action()
} catch (error) {
response = error.response.data
} finally {
expect(response).toEqual({ User: [null] })
}
})
})
describe('as owner', () => {
describe('unauthenticated', () => {
beforeEach(() => {
loginCredentials = {
email: 'owner@example.org',
password: 'iamtheowner',
}
authenticatedUser = null
})
it("exposes the owner's email address", async () => {
await expect(action()).resolves.toEqual({ User: [{ email: 'owner@example.org' }] })
it("throws an error and does not expose the owner's email address", async () => {
await expect(
query({ query: userQuery, variables: { name: 'Owner' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }],
data: { User: [null] },
})
})
})
describe('authenticated as another user', () => {
beforeEach(async () => {
loginCredentials = {
email: 'someone@example.org',
password: 'else',
}
describe('authenticated', () => {
describe('as the owner', () => {
beforeEach(async () => {
authenticatedUser = await owner.toJson()
})
it("exposes the owner's email address", async () => {
variables = { name: 'Owner' }
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
data: { User: [{ email: 'owner@example.org' }] },
errors: undefined,
})
})
})
it('rejects', async () => {
await expect(action()).rejects.toThrow('Not Authorised!')
describe('as another regular user', () => {
beforeEach(async () => {
authenticatedUser = await anotherRegularUser.toJson()
})
it("throws an error and does not expose the owner's email address", async () => {
await expect(
query({ query: userQuery, variables: { name: 'Owner' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }],
data: { User: [null] },
})
})
})
it("does not expose the owner's email address", async () => {
let response
try {
await action()
} catch (error) {
response = error.response.data
}
expect(response).toEqual({ User: [null] })
describe('as a moderator', () => {
beforeEach(async () => {
authenticatedUser = await moderator.toJson()
})
it("throws an error and does not expose the owner's email address", async () => {
await expect(
query({ query: userQuery, variables: { name: 'Owner' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorised!' }],
data: { User: [null] },
})
})
})
describe('administrator', () => {
beforeEach(async () => {
authenticatedUser = await administrator.toJson()
})
it("exposes the owner's email address", async () => {
variables = { name: 'Owner' }
await expect(query({ query: userQuery, variables })).resolves.toMatchObject({
data: { User: [{ email: 'owner@example.org' }] },
errors: undefined,
})
})
})
})
})

View File

@ -57,11 +57,37 @@ const validateUpdatePost = async (resolve, root, args, context, info) => {
return validatePost(resolve, root, args, context, info)
}
const validateReport = async (resolve, root, args, context, info) => {
const { resourceId } = args
const { user, driver } = context
if (resourceId === user.id) throw new Error('You cannot report yourself!')
const session = driver.session()
const reportQueryRes = await session.run(
`
MATCH (:User {id:$submitterId})-[:REPORTED]->(resource {id:$resourceId})
RETURN labels(resource)[0] as label
`,
{
resourceId,
submitterId: user.id,
},
)
const [existingReportedResource] = reportQueryRes.records.map(record => {
return {
label: record.get('label'),
}
})
if (existingReportedResource) throw new Error(`${existingReportedResource.label}`)
return resolve(root, args, context, info)
}
export default {
Mutation: {
CreateComment: validateCommentCreation,
UpdateComment: validateUpdateComment,
CreatePost: validatePost,
UpdatePost: validateUpdatePost,
report: validateReport,
},
}

View File

@ -85,7 +85,7 @@ function clean(dirty) {
return dirty
}
const fields = ['content', 'contentExcerpt']
const fields = ['content', 'contentExcerpt', 'reasonDescription']
export default {
Mutation: async (resolve, root, args, context, info) => {

View File

@ -55,6 +55,7 @@ module.exports = {
direction: 'in',
},
invitedBy: { type: 'relationship', relationship: 'INVITED', target: 'User', direction: 'in' },
lastActiveAt: { type: 'string', isoDate: true },
createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() },
updatedAt: {
type: 'string',
@ -113,4 +114,8 @@ module.exports = {
target: 'Location',
direction: 'out',
},
};
allowEmbedIframes: {
type: 'boolean',
default: false,
},
}

View File

@ -23,6 +23,7 @@ export default applyScalars(
'Location',
'SocialMedia',
'NOTIFIED',
'REPORTED',
],
// add 'User' here as soon as possible
},
@ -35,7 +36,6 @@ export default applyScalars(
'Notfication',
'Post',
'Comment',
'Report',
'Statistics',
'LoggedInUser',
'Location',
@ -43,6 +43,7 @@ export default applyScalars(
'User',
'EMOTED',
'NOTIFIED',
'REPORTED',
],
// add 'User' here as soon as possible
},

View File

@ -2,7 +2,11 @@ import fs from 'fs'
import path from 'path'
import minimatch from 'minimatch'
let oEmbedProvidersFile = fs.readFileSync(path.join(__dirname, './providers.json'), 'utf8')
let oEmbedProvidersFile = fs.readFileSync(
path.join(__dirname, '../../../../public/providers.json'),
'utf8',
)
// some providers allow a format parameter
// we need JSON
oEmbedProvidersFile = oEmbedProvidersFile.replace(/\{format\}/g, 'json')

View File

@ -18,7 +18,6 @@ export default async function fileUpload(params, { file, url }, uploadCallback =
const fileLocation = `/uploads/${Date.now()}-${slug(name)}`
await uploadCallback({ createReadStream, fileLocation })
delete params[file]
params[url] = fileLocation
}

View File

@ -1,83 +1,125 @@
import uuid from 'uuid/v4'
export default {
Mutation: {
report: async (parent, { id, description }, { driver, req, user }, resolveInfo) => {
const reportId = uuid()
report: async (_parent, params, context, _resolveInfo) => {
let createdRelationshipWithNestedAttributes
const { resourceId, reasonCategory, reasonDescription } = params
const { driver, user } = context
const session = driver.session()
const reportData = {
id: reportId,
createdAt: new Date().toISOString(),
description: description,
}
const reportQueryRes = await session.run(
`
match (u:User {id:$submitterId}) -[:REPORTED]->(report)-[:REPORTED]-> (resource {id: $resourceId})
return labels(resource)[0] as label
`,
{
resourceId: id,
submitterId: user.id,
},
)
const [rep] = reportQueryRes.records.map(record => {
return {
label: record.get('label'),
}
const writeTxResultPromise = session.writeTransaction(async txc => {
const reportRelationshipTransactionResponse = await txc.run(
`
MATCH (submitter:User {id: $submitterId})
MATCH (resource {id: $resourceId})
WHERE resource:User OR resource:Comment OR resource:Post
CREATE (resource)<-[report:REPORTED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter)
RETURN report, submitter, resource, labels(resource)[0] as type
`,
{
resourceId,
submitterId: user.id,
createdAt: new Date().toISOString(),
reasonCategory,
reasonDescription,
},
)
return reportRelationshipTransactionResponse.records.map(record => ({
report: record.get('report'),
submitter: record.get('submitter'),
resource: record.get('resource').properties,
type: record.get('type'),
}))
})
if (rep) {
throw new Error(rep.label)
}
const res = await session.run(
`
MATCH (submitter:User {id: $userId})
MATCH (resource {id: $resourceId})
WHERE resource:User OR resource:Comment OR resource:Post
MERGE (report:Report {id: {reportData}.id })
MERGE (resource)<-[:REPORTED]-(report)
MERGE (report)<-[:REPORTED]-(submitter)
RETURN report, submitter, resource, labels(resource)[0] as type
`,
{
resourceId: id,
userId: user.id,
reportData,
},
)
session.close()
const [dbResponse] = res.records.map(r => {
return {
report: r.get('report'),
submitter: r.get('submitter'),
resource: r.get('resource'),
type: r.get('type'),
try {
const txResult = await writeTxResultPromise
if (!txResult[0]) return null
const { report, submitter, resource, type } = txResult[0]
createdRelationshipWithNestedAttributes = {
...report.properties,
post: null,
comment: null,
user: null,
submitter: submitter.properties,
type,
}
})
if (!dbResponse) return null
const { report, submitter, resource, type } = dbResponse
const response = {
...report.properties,
post: null,
comment: null,
user: null,
submitter: submitter.properties,
type,
switch (type) {
case 'Post':
createdRelationshipWithNestedAttributes.post = resource
break
case 'Comment':
createdRelationshipWithNestedAttributes.comment = resource
break
case 'User':
createdRelationshipWithNestedAttributes.user = resource
break
}
} finally {
session.close()
}
switch (type) {
case 'Post':
response.post = resource.properties
return createdRelationshipWithNestedAttributes
},
},
Query: {
reports: async (_parent, params, context, _resolveInfo) => {
const { driver } = context
const session = driver.session()
let response
let orderByClause
switch (params.orderBy) {
case 'createdAt_asc':
orderByClause = 'ORDER BY report.createdAt ASC'
break
case 'Comment':
response.comment = resource.properties
break
case 'User':
response.user = resource.properties
case 'createdAt_desc':
orderByClause = 'ORDER BY report.createdAt DESC'
break
default:
orderByClause = ''
}
try {
const cypher = `
MATCH (submitter:User)-[report:REPORTED]->(resource)
WHERE resource:User OR resource:Comment OR resource:Post
RETURN report, submitter, resource, labels(resource)[0] as type
${orderByClause}
`
const result = await session.run(cypher, {})
const dbResponse = result.records.map(r => {
return {
report: r.get('report'),
submitter: r.get('submitter'),
resource: r.get('resource'),
type: r.get('type'),
}
})
if (!dbResponse) return null
response = []
dbResponse.forEach(ele => {
const { report, submitter, resource, type } = ele
const responseEle = {
...report.properties,
post: null,
comment: null,
user: null,
submitter: submitter.properties,
type,
}
switch (type) {
case 'Post':
responseEle.post = resource.properties
break
case 'Comment':
responseEle.comment = resource.properties
break
case 'User':
responseEle.user = resource.properties
break
}
response.push(responseEle)
})
} finally {
session.close()
}
return response

View File

@ -1,35 +1,73 @@
import { GraphQLClient } from 'graphql-request'
import Factory from '../../seed/factories'
import { host, login } from '../../jest/helpers'
import { neode } from '../../bootstrap/neo4j'
import { host, login, gql } from '../../jest/helpers'
import { getDriver, neode } from '../../bootstrap/neo4j'
import { createTestClient } from 'apollo-server-testing'
import createServer from '../.././server'
const factory = Factory()
const instance = neode()
const driver = getDriver()
describe('report', () => {
let mutation
describe('report mutation', () => {
let reportMutation
let headers
let returnedObject
let client
let variables
let createPostVariables
let user
const categoryIds = ['cat9']
const action = () => {
reportMutation = gql`
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(
resourceId: $resourceId
reasonCategory: $reasonCategory
reasonDescription: $reasonDescription
) {
createdAt
reasonCategory
reasonDescription
type
submitter {
email
}
user {
name
}
post {
title
}
comment {
content
}
}
}
`
client = new GraphQLClient(host, {
headers,
})
return client.request(reportMutation, variables)
}
beforeEach(async () => {
returnedObject = '{ description }'
variables = {
id: 'whatever',
resourceId: 'whatever',
reasonCategory: 'other',
reasonDescription: 'Violates code of conduct !!!',
}
headers = {}
user = await factory.create('User', {
id: 'u1',
role: 'user',
email: 'test@example.org',
password: '1234',
id: 'u1',
})
await factory.create('User', {
id: 'u2',
name: 'abusive-user',
role: 'user',
name: 'abusive-user',
email: 'abusive-user@example.org',
})
await instance.create('Category', {
@ -43,59 +81,57 @@ describe('report', () => {
await factory.cleanDatabase()
})
let client
const action = () => {
mutation = `
mutation($id: ID!) {
report(
id: $id,
description: "Violates code of conduct"
) ${returnedObject}
}
`
client = new GraphQLClient(host, {
headers,
})
return client.request(mutation, variables)
}
describe('unauthenticated', () => {
it('throws authorization error', async () => {
await expect(action()).rejects.toThrow('Not Authorised')
})
})
describe('authenticated', () => {
beforeEach(async () => {
headers = await login({
email: 'test@example.org',
password: '1234',
describe('authenticated', () => {
beforeEach(async () => {
headers = await login({
email: 'test@example.org',
password: '1234',
})
})
describe('invalid resource id', () => {
it('returns null', async () => {
await expect(action()).resolves.toEqual({
report: null,
})
})
})
describe('invalid resource id', () => {
it('returns null', async () => {
await expect(action()).resolves.toEqual({
report: null,
})
})
})
describe('valid resource id', () => {
describe('valid resource id', () => {
describe('reported resource is a user', () => {
beforeEach(async () => {
variables = {
id: 'u2',
...variables,
resourceId: 'u2',
}
})
/*
it('creates a report', async () => {
await expect(action()).resolves.toEqual({
type: null,
})
})
*/
it('returns type "User"', async () => {
await expect(action()).resolves.toMatchObject({
report: {
type: 'User',
},
})
})
it('returns resource in user attribute', async () => {
await expect(action()).resolves.toMatchObject({
report: {
user: {
name: 'abusive-user',
},
},
})
})
it('returns the submitter', async () => {
returnedObject = '{ submitter { email } }'
await expect(action()).resolves.toEqual({
await expect(action()).resolves.toMatchObject({
report: {
submitter: {
email: 'test@example.org',
@ -104,138 +140,382 @@ describe('report', () => {
})
})
describe('reported resource is a user', () => {
it('returns type "User"', async () => {
returnedObject = '{ type }'
await expect(action()).resolves.toEqual({
report: {
type: 'User',
},
})
})
it('returns resource in user attribute', async () => {
returnedObject = '{ user { name } }'
await expect(action()).resolves.toEqual({
report: {
user: {
name: 'abusive-user',
},
},
})
it('returns a date', async () => {
await expect(action()).resolves.toMatchObject({
report: {
createdAt: expect.any(String),
},
})
})
describe('reported resource is a post', () => {
beforeEach(async () => {
await factory.create('Post', {
author: user,
id: 'p23',
title: 'Matt and Robert having a pair-programming',
categoryIds,
})
variables = {
id: 'p23',
}
})
it('returns type "Post"', async () => {
returnedObject = '{ type }'
await expect(action()).resolves.toEqual({
report: {
type: 'Post',
},
})
})
it('returns resource in post attribute', async () => {
returnedObject = '{ post { title } }'
await expect(action()).resolves.toEqual({
report: {
post: {
title: 'Matt and Robert having a pair-programming',
},
},
})
})
it('returns null in user attribute', async () => {
returnedObject = '{ user { name } }'
await expect(action()).resolves.toEqual({
report: {
user: null,
},
})
it('returns the reason category', async () => {
variables = {
...variables,
reasonCategory: 'criminal_behavior_violation_german_law',
}
await expect(action()).resolves.toMatchObject({
report: {
reasonCategory: 'criminal_behavior_violation_german_law',
},
})
})
/* An der Stelle würde ich den p23 noch mal prüfen, diesmal muss aber eine error meldung kommen.
it('gives an error if the reason category is not in enum "ReasonCategory"', async () => {
variables = {
...variables,
reasonCategory: 'my_category',
}
await expect(action()).rejects.toThrow(
'got invalid value "my_category"; Expected type ReasonCategory',
)
})
it('returns the reason description', async () => {
variables = {
...variables,
reasonDescription: 'My reason!',
}
await expect(action()).resolves.toMatchObject({
report: {
reasonDescription: 'My reason!',
},
})
})
it('sanitize the reason description', async () => {
variables = {
...variables,
reasonDescription: 'My reason <sanitize></sanitize>!',
}
await expect(action()).resolves.toMatchObject({
report: {
reasonDescription: 'My reason !',
},
})
})
})
describe('reported resource is a post', () => {
beforeEach(async () => {
await factory.create('Post', {
author: user,
id: 'p23',
title: 'Matt and Robert having a pair-programming',
categoryIds,
})
variables = {
...variables,
resourceId: 'p23',
}
})
it('returns type "Post"', async () => {
await expect(action()).resolves.toMatchObject({
report: {
type: 'Post',
},
})
})
it('returns resource in post attribute', async () => {
await expect(action()).resolves.toMatchObject({
report: {
post: {
title: 'Matt and Robert having a pair-programming',
},
},
})
})
it('returns null in user attribute', async () => {
await expect(action()).resolves.toMatchObject({
report: {
user: null,
},
})
})
})
/* An der Stelle würde ich den p23 noch mal prüfen, diesmal muss aber eine error meldung kommen.
At this point I would check the p23 again, but this time there must be an error message. */
describe('reported resource is a comment', () => {
beforeEach(async () => {
createPostVariables = {
id: 'p1',
title: 'post to comment on',
content: 'please comment on me',
categoryIds,
}
await factory.create('Post', { ...createPostVariables, author: user })
await factory.create('Comment', {
author: user,
postId: 'p1',
id: 'c34',
content: 'Robert getting tired.',
})
variables = {
id: 'c34',
}
describe('reported resource is a comment', () => {
beforeEach(async () => {
createPostVariables = {
id: 'p1',
title: 'post to comment on',
content: 'please comment on me',
categoryIds,
}
await factory.create('Post', { ...createPostVariables, author: user })
await factory.create('Comment', {
author: user,
postId: 'p1',
id: 'c34',
content: 'Robert getting tired.',
})
variables = {
...variables,
resourceId: 'c34',
}
})
it('returns type "Comment"', async () => {
returnedObject = '{ type }'
await expect(action()).resolves.toEqual({
report: {
type: 'Comment',
},
})
})
it('returns resource in comment attribute', async () => {
returnedObject = '{ comment { content } }'
await expect(action()).resolves.toEqual({
report: {
comment: {
content: 'Robert getting tired.',
},
},
})
it('returns type "Comment"', async () => {
await expect(action()).resolves.toMatchObject({
report: {
type: 'Comment',
},
})
})
/* An der Stelle würde ich den c34 noch mal prüfen, diesmal muss aber eine error meldung kommen.
it('returns resource in comment attribute', async () => {
await expect(action()).resolves.toMatchObject({
report: {
comment: {
content: 'Robert getting tired.',
},
},
})
})
})
/* An der Stelle würde ich den c34 noch mal prüfen, diesmal muss aber eine error meldung kommen.
At this point I would check the c34 again, but this time there must be an error message. */
describe('reported resource is a tag', () => {
beforeEach(async () => {
await factory.create('Tag', {
id: 't23',
})
variables = {
id: 't23',
}
})
it('returns null', async () => {
await expect(action()).resolves.toEqual({
report: null,
})
describe('reported resource is a tag', () => {
beforeEach(async () => {
await factory.create('Tag', {
id: 't23',
})
variables = {
...variables,
resourceId: 't23',
}
})
/* An der Stelle würde ich den t23 noch mal prüfen, diesmal muss aber eine error meldung kommen.
At this point I would check the t23 again, but this time there must be an error message. */
it('returns null', async () => {
await expect(action()).resolves.toMatchObject({
report: null,
})
})
})
/* An der Stelle würde ich den t23 noch mal prüfen, diesmal muss aber eine error meldung kommen.
At this point I would check the t23 again, but this time there must be an error message. */
})
})
})
describe('reports query', () => {
let query, mutate, authenticatedUser, moderator, user, author
const categoryIds = ['cat9']
const reportMutation = gql`
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(
resourceId: $resourceId
reasonCategory: $reasonCategory
reasonDescription: $reasonDescription
) {
type
}
}
`
const reportsQuery = gql`
query {
reports(orderBy: createdAt_desc) {
createdAt
reasonCategory
reasonDescription
submitter {
id
}
type
user {
id
}
post {
id
}
comment {
id
}
}
}
`
beforeAll(async () => {
await factory.cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
user: authenticatedUser,
}
},
})
query = createTestClient(server).query
mutate = createTestClient(server).mutate
})
beforeEach(async () => {
authenticatedUser = null
moderator = await factory.create('User', {
id: 'mod1',
role: 'moderator',
email: 'moderator@example.org',
password: '1234',
})
user = await factory.create('User', {
id: 'user1',
role: 'user',
email: 'test@example.org',
password: '1234',
})
author = await factory.create('User', {
id: 'auth1',
role: 'user',
name: 'abusive-user',
email: 'abusive-user@example.org',
})
await instance.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
icon: 'university',
})
await Promise.all([
factory.create('Post', {
author,
id: 'p1',
categoryIds,
content: 'Interesting Knowledge',
}),
factory.create('Post', {
author: moderator,
id: 'p2',
categoryIds,
content: 'More things to do …',
}),
factory.create('Post', {
author: user,
id: 'p3',
categoryIds,
content: 'I am at school …',
}),
])
await Promise.all([
factory.create('Comment', {
author: user,
id: 'c1',
postId: 'p1',
}),
])
authenticatedUser = await user.toJson()
await Promise.all([
mutate({
mutation: reportMutation,
variables: {
resourceId: 'p1',
reasonCategory: 'other',
reasonDescription: 'This comment is bigoted',
},
}),
mutate({
mutation: reportMutation,
variables: {
resourceId: 'c1',
reasonCategory: 'discrimination_etc',
reasonDescription: 'This post is bigoted',
},
}),
mutate({
mutation: reportMutation,
variables: {
resourceId: 'auth1',
reasonCategory: 'doxing',
reasonDescription: 'This user is harassing me with bigoted remarks',
},
}),
])
authenticatedUser = null
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
authenticatedUser = null
expect(query({ query: reportsQuery })).resolves.toMatchObject({
data: { reports: null },
errors: [{ message: 'Not Authorised!' }],
})
})
it('role "user" gets no reports', async () => {
authenticatedUser = await user.toJson()
expect(query({ query: reportsQuery })).resolves.toMatchObject({
data: { reports: null },
errors: [{ message: 'Not Authorised!' }],
})
})
it('role "moderator" gets reports', async () => {
const expected = {
// to check 'orderBy: createdAt_desc' is not possible here, because 'createdAt' does not differ
reports: expect.arrayContaining([
expect.objectContaining({
createdAt: expect.any(String),
reasonCategory: 'doxing',
reasonDescription: 'This user is harassing me with bigoted remarks',
submitter: expect.objectContaining({
id: 'user1',
}),
type: 'User',
user: expect.objectContaining({
id: 'auth1',
}),
post: null,
comment: null,
}),
expect.objectContaining({
createdAt: expect.any(String),
reasonCategory: 'other',
reasonDescription: 'This comment is bigoted',
submitter: expect.objectContaining({
id: 'user1',
}),
type: 'Post',
user: null,
post: expect.objectContaining({
id: 'p1',
}),
comment: null,
}),
expect.objectContaining({
createdAt: expect.any(String),
reasonCategory: 'discrimination_etc',
reasonDescription: 'This post is bigoted',
submitter: expect.objectContaining({
id: 'user1',
}),
type: 'Comment',
user: null,
post: null,
comment: expect.objectContaining({
id: 'c1',
}),
}),
]),
}
authenticatedUser = await moderator.toJson()
const { data } = await query({ query: reportsQuery })
expect(data).toEqual(expected)
})
})
})

View File

@ -1,31 +1,49 @@
import { GraphQLClient } from 'graphql-request'
import { createTestClient } from 'apollo-server-testing'
import Factory from '../../seed/factories'
import { host, login, gql } from '../../jest/helpers'
import { gql } from '../../jest/helpers'
import { neode as getNeode, getDriver } from '../../bootstrap/neo4j'
import createServer from '../../server'
const factory = Factory()
let user
let badge
const driver = getDriver()
const instance = getNeode()
let authenticatedUser, regularUser, administrator, moderator, badge, query, mutate
describe('rewards', () => {
const variables = {
from: 'indiegogo_en_rhino',
to: 'u1',
to: 'regular-user-id',
}
beforeAll(async () => {
const { server } = createServer({
context: () => {
return {
driver,
neode: instance,
user: authenticatedUser,
}
},
})
query = createTestClient(server).query
mutate = createTestClient(server).mutate
})
beforeEach(async () => {
user = await factory.create('User', {
id: 'u1',
regularUser = await factory.create('User', {
id: 'regular-user-id',
role: 'user',
email: 'user@example.org',
password: '1234',
})
await factory.create('User', {
id: 'u2',
moderator = await factory.create('User', {
id: 'moderator-id',
role: 'moderator',
email: 'moderator@example.org',
})
await factory.create('User', {
id: 'u3',
administrator = await factory.create('User', {
id: 'admin-id',
role: 'admin',
email: 'admin@example.org',
})
@ -42,7 +60,7 @@ describe('rewards', () => {
})
describe('reward', () => {
const mutation = gql`
const rewardMutation = gql`
mutation($from: ID!, $to: ID!) {
reward(badgeKey: $from, userId: $to) {
id
@ -54,51 +72,61 @@ describe('rewards', () => {
`
describe('unauthenticated', () => {
let client
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
authenticatedUser = null
await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({
data: { reward: null },
errors: [{ message: 'Not Authorised!' }],
})
})
})
describe('authenticated admin', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'admin@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
authenticatedUser = await administrator.toJson()
})
describe('badge for id does not exist', () => {
it('rejects with a telling error message', async () => {
it('rejects with an informative error message', async () => {
await expect(
client.request(mutation, {
...variables,
from: 'bullshit',
mutate({
mutation: rewardMutation,
variables: { to: 'regular-user-id', from: 'non-existent-badge-id' },
}),
).rejects.toThrow("Couldn't find a badge with that id")
).resolves.toMatchObject({
data: { reward: null },
errors: [{ message: "Couldn't find a badge with that id" }],
})
})
})
describe('user for id does not exist', () => {
describe('non-existent user', () => {
it('rejects with a telling error message', async () => {
await expect(
client.request(mutation, {
...variables,
to: 'bullshit',
mutate({
mutation: rewardMutation,
variables: { to: 'non-existent-user-id', from: 'indiegogo_en_rhino' },
}),
).rejects.toThrow("Couldn't find a user with that id")
).resolves.toMatchObject({
data: { reward: null },
errors: [{ message: "Couldn't find a user with that id" }],
})
})
})
it('rewards a badge to user', async () => {
const expected = {
reward: {
id: 'u1',
badges: [{ id: 'indiegogo_en_rhino' }],
data: {
reward: {
id: 'regular-user-id',
badges: [{ id: 'indiegogo_en_rhino' }],
},
},
errors: undefined,
}
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject(
expected,
)
})
it('rewards a second different badge to same user', async () => {
@ -108,42 +136,74 @@ describe('rewards', () => {
})
const badges = [{ id: 'indiegogo_en_racoon' }, { id: 'indiegogo_en_rhino' }]
const expected = {
reward: {
id: 'u1',
badges: expect.arrayContaining(badges),
data: {
reward: {
id: 'regular-user-id',
badges: expect.arrayContaining(badges),
},
},
errors: undefined,
}
await client.request(mutation, variables)
await mutate({
mutation: rewardMutation,
variables: {
to: 'regular-user-id',
from: 'indiegogo_en_rhino',
},
})
await expect(
client.request(mutation, {
...variables,
from: 'indiegogo_en_racoon',
mutate({
mutation: rewardMutation,
variables: {
to: 'regular-user-id',
from: 'indiegogo_en_racoon',
},
}),
).resolves.toEqual(expected)
).resolves.toMatchObject(expected)
})
it('rewards the same badge as well to another user', async () => {
const expected = {
reward: {
id: 'u2',
badges: [{ id: 'indiegogo_en_rhino' }],
data: {
reward: {
id: 'regular-user-2-id',
badges: [{ id: 'indiegogo_en_rhino' }],
},
},
errors: undefined,
}
await factory.create('User', {
id: 'regular-user-2-id',
email: 'regular2@email.com',
})
await mutate({
mutation: rewardMutation,
variables,
})
await expect(
client.request(mutation, {
...variables,
to: 'u2',
mutate({
mutation: rewardMutation,
variables: {
to: 'regular-user-2-id',
from: 'indiegogo_en_rhino',
},
}),
).resolves.toEqual(expected)
).resolves.toMatchObject(expected)
})
it('creates no duplicate reward relationships', async () => {
await client.request(mutation, variables)
await client.request(mutation, variables)
await mutate({
mutation: rewardMutation,
variables,
})
await mutate({
mutation: rewardMutation,
variables,
})
const query = gql`
const userQuery = gql`
{
User(id: "u1") {
User(id: "regular-user-id") {
badgesCount
badges {
id
@ -151,22 +211,26 @@ describe('rewards', () => {
}
}
`
const expected = { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] }
const expected = {
data: { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] },
errors: undefined,
}
await expect(client.request(query)).resolves.toEqual(expected)
await expect(query({ query: userQuery })).resolves.toMatchObject(expected)
})
})
describe('authenticated moderator', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
authenticatedUser = moderator.toJson()
})
describe('rewards bage to user', () => {
describe('rewards badge to user', () => {
it('throws authorization error', async () => {
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
await expect(mutate({ mutation: rewardMutation, variables })).resolves.toMatchObject({
data: { reward: null },
errors: [{ message: 'Not Authorised!' }],
})
})
})
})
@ -174,11 +238,14 @@ describe('rewards', () => {
describe('unreward', () => {
beforeEach(async () => {
await user.relateTo(badge, 'rewarded')
await regularUser.relateTo(badge, 'rewarded')
})
const expected = { unreward: { id: 'u1', badges: [] } }
const expected = {
data: { unreward: { id: 'regular-user-id', badges: [] } },
errors: undefined,
}
const mutation = gql`
const unrewardMutation = gql`
mutation($from: ID!, $to: ID!) {
unreward(badgeKey: $from, userId: $to) {
id
@ -191,9 +258,10 @@ describe('rewards', () => {
describe('check test setup', () => {
it('user has one badge', async () => {
const query = gql`
authenticatedUser = regularUser.toJson()
const userQuery = gql`
{
User(id: "u1") {
User(id: "regular-user-id") {
badgesCount
badges {
id
@ -201,48 +269,54 @@ describe('rewards', () => {
}
}
`
const expected = { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] }
const client = new GraphQLClient(host)
await expect(client.request(query)).resolves.toEqual(expected)
const expected = {
data: { User: [{ badgesCount: 1, badges: [{ id: 'indiegogo_en_rhino' }] }] },
errors: undefined,
}
await expect(query({ query: userQuery })).resolves.toMatchObject(expected)
})
})
describe('unauthenticated', () => {
let client
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
authenticatedUser = null
await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({
data: { unreward: null },
errors: [{ message: 'Not Authorised!' }],
})
})
})
describe('authenticated admin', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'admin@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
authenticatedUser = await administrator.toJson()
})
it('removes a badge from user', async () => {
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject(
expected,
)
})
it('does not crash when unrewarding multiple times', async () => {
await client.request(mutation, variables)
await expect(client.request(mutation, variables)).resolves.toEqual(expected)
await mutate({ mutation: unrewardMutation, variables })
await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject(
expected,
)
})
})
describe('authenticated moderator', () => {
let client
beforeEach(async () => {
const headers = await login({ email: 'moderator@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
authenticatedUser = await moderator.toJson()
})
describe('removes bage from user', () => {
it('throws authorization error', async () => {
await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised')
await expect(mutate({ mutation: unrewardMutation, variables })).resolves.toMatchObject({
data: { unreward: null },
errors: [{ message: 'Not Authorised!' }],
})
})
})
})

View File

@ -9,7 +9,7 @@ export default {
countPosts: 'Post',
countComments: 'Comment',
countNotifications: 'NOTIFIED',
countInvites: 'InvitationCode',
countEmails: 'EmailAddress',
countFollows: 'FOLLOWS',
countShouts: 'SHOUTED',
}
@ -28,6 +28,11 @@ export default {
const stat = statistics[mapping[key]]
response[key] = stat ? stat.toNumber() : 0
})
/*
* Note: invites count is calculated this way because invitation codes are not in use yet
*/
response.countInvites = response.countEmails - response.countUsers
} finally {
session.close()
}

View File

@ -176,6 +176,7 @@ export default {
'about',
'termsAndConditionsAgreedVersion',
'termsAndConditionsAgreedAt',
'allowEmbedIframes',
],
boolean: {
followedByCurrentUser:

View File

@ -86,6 +86,7 @@ describe('UpdateUser', () => {
name: 'John Doe',
termsAndConditionsAgreedVersion: null,
termsAndConditionsAgreedAt: null,
allowEmbedIframes: false,
}
variables = {

View File

@ -24,7 +24,6 @@ type Mutation {
changePassword(oldPassword: String!, newPassword: String!): String!
requestPasswordReset(email: String!): Boolean!
resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean!
report(id: ID!, description: String): Report
disable(id: ID!): ID
enable(id: ID!): ID
# Shout the given Type and ID
@ -35,18 +34,6 @@ type Mutation {
unfollowUser(id: ID!): User
}
type Report {
id: ID!
submitter: User @relation(name: "REPORTED", direction: "IN")
description: String
type: String!
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]")
createdAt: String
comment: Comment @relation(name: "REPORTED", direction: "OUT")
post: Post @relation(name: "REPORTED", direction: "OUT")
user: User @relation(name: "REPORTED", direction: "OUT")
}
enum Deletable {
Post
Comment

View File

@ -0,0 +1,43 @@
type REPORTED {
createdAt: String
reasonCategory: ReasonCategory
reasonDescription: String
submitter: User
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN user")
# not yet supported
# resource: ReportResource
# @cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN resource")
type: String
@cypher(statement: "MATCH (resource)<-[:REPORTED]-(user:User) RETURN labels(resource)[0]")
user: User
post: Post
comment: Comment
}
# this list equals the strings of an array in file "webapp/constants/modals.js"
enum ReasonCategory {
other
discrimination_etc
pornographic_content_links
glorific_trivia_of_cruel_inhuman_acts
doxing
intentional_intimidation_stalking_persecution
advert_products_services_commercial
criminal_behavior_violation_german_law
}
# not yet supported
# union ReportResource = User | Post | Comment
enum ReportOrdering {
createdAt_asc
createdAt_desc
}
type Query {
reports(orderBy: ReportOrdering): [REPORTED]
}
type Mutation {
report(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): REPORTED
}

View File

@ -27,6 +27,8 @@ type User {
termsAndConditionsAgreedVersion: String
termsAndConditionsAgreedAt: String
allowEmbedIframes: Boolean
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)")
@ -166,6 +168,7 @@ type Mutation {
about: String
termsAndConditionsAgreedVersion: String
termsAndConditionsAgreedAt: String
allowEmbedIframes: Boolean
): User
DeleteUser(id: ID!, resource: [Deletable]): User

View File

@ -16,6 +16,7 @@ export default function create() {
about: faker.lorem.paragraph(),
termsAndConditionsAgreedVersion: '0.0.1',
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
allowEmbedIframes: false,
}
defaults.slug = slugify(defaults.name, { lower: true })
args = {

View File

@ -24,10 +24,8 @@ import { gql } from '../jest/helpers'
})
const { mutate } = createTestClient(server)
const f = Factory()
const [Hamburg, Berlin, Germany, Paris, France] = await Promise.all([
f.create('Location', {
factory.create('Location', {
id: 'region.5127278006398860',
name: 'Hamburg',
type: 'region',
@ -42,7 +40,7 @@ import { gql } from '../jest/helpers'
nameNL: 'Hamburg',
namePL: 'Hamburg',
}),
f.create('Location', {
factory.create('Location', {
id: 'region.14880313158564380',
type: 'region',
name: 'Berlin',
@ -57,7 +55,7 @@ import { gql } from '../jest/helpers'
nameNL: 'Berlijn',
namePL: 'Berlin',
}),
f.create('Location', {
factory.create('Location', {
id: 'country.10743216036480410',
name: 'Germany',
type: 'country',
@ -70,7 +68,7 @@ import { gql } from '../jest/helpers'
nameIT: 'Germania',
nameEN: 'Germany',
}),
f.create('Location', {
factory.create('Location', {
id: 'region.9397217726497330',
name: 'Paris',
type: 'region',
@ -85,7 +83,7 @@ import { gql } from '../jest/helpers'
nameNL: 'Parijs',
namePL: 'Paryż',
}),
f.create('Location', {
factory.create('Location', {
id: 'country.9759535382641660',
name: 'France',
type: 'country',
@ -106,27 +104,27 @@ import { gql } from '../jest/helpers'
])
const [racoon, rabbit, wolf, bear, turtle, rhino] = await Promise.all([
f.create('Badge', {
factory.create('Badge', {
id: 'indiegogo_en_racoon',
icon: '/img/badges/indiegogo_en_racoon.svg',
}),
f.create('Badge', {
factory.create('Badge', {
id: 'indiegogo_en_rabbit',
icon: '/img/badges/indiegogo_en_rabbit.svg',
}),
f.create('Badge', {
factory.create('Badge', {
id: 'indiegogo_en_wolf',
icon: '/img/badges/indiegogo_en_wolf.svg',
}),
f.create('Badge', {
factory.create('Badge', {
id: 'indiegogo_en_bear',
icon: '/img/badges/indiegogo_en_bear.svg',
}),
f.create('Badge', {
factory.create('Badge', {
id: 'indiegogo_en_turtle',
icon: '/img/badges/indiegogo_en_turtle.svg',
}),
f.create('Badge', {
factory.create('Badge', {
id: 'indiegogo_en_rhino',
icon: '/img/badges/indiegogo_en_rhino.svg',
}),
@ -141,49 +139,49 @@ import { gql } from '../jest/helpers'
louie,
dagobert,
] = await Promise.all([
f.create('User', {
factory.create('User', {
id: 'u1',
name: 'Peter Lustig',
slug: 'peter-lustig',
role: 'admin',
email: 'admin@example.org',
}),
f.create('User', {
factory.create('User', {
id: 'u2',
name: 'Bob der Baumeister',
slug: 'bob-der-baumeister',
role: 'moderator',
email: 'moderator@example.org',
}),
f.create('User', {
factory.create('User', {
id: 'u3',
name: 'Jenny Rostock',
slug: 'jenny-rostock',
role: 'user',
email: 'user@example.org',
}),
f.create('User', {
factory.create('User', {
id: 'u4',
name: 'Huey',
slug: 'huey',
role: 'user',
email: 'huey@example.org',
}),
f.create('User', {
factory.create('User', {
id: 'u5',
name: 'Dewey',
slug: 'dewey',
role: 'user',
email: 'dewey@example.org',
}),
f.create('User', {
factory.create('User', {
id: 'u6',
name: 'Louie',
slug: 'louie',
role: 'user',
email: 'louie@example.org',
}),
f.create('User', {
factory.create('User', {
id: 'u7',
name: 'Dagobert',
slug: 'dagobert',
@ -226,97 +224,97 @@ import { gql } from '../jest/helpers'
])
await Promise.all([
f.create('Category', {
factory.create('Category', {
id: 'cat1',
name: 'Just For Fun',
slug: 'just-for-fun',
icon: 'smile',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat2',
name: 'Happiness & Values',
slug: 'happiness-values',
icon: 'heart-o',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat3',
name: 'Health & Wellbeing',
slug: 'health-wellbeing',
icon: 'medkit',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat4',
name: 'Environment & Nature',
slug: 'environment-nature',
icon: 'tree',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat5',
name: 'Animal Protection',
slug: 'animal-protection',
icon: 'paw',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat6',
name: 'Human Rights & Justice',
slug: 'human-rights-justice',
icon: 'balance-scale',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat7',
name: 'Education & Sciences',
slug: 'education-sciences',
icon: 'graduation-cap',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat8',
name: 'Cooperation & Development',
slug: 'cooperation-development',
icon: 'users',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat9',
name: 'Democracy & Politics',
slug: 'democracy-politics',
icon: 'university',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat10',
name: 'Economy & Finances',
slug: 'economy-finances',
icon: 'money',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat11',
name: 'Energy & Technology',
slug: 'energy-technology',
icon: 'flash',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat12',
name: 'IT, Internet & Data Privacy',
slug: 'it-internet-data-privacy',
icon: 'mouse-pointer',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat13',
name: 'Art, Culture & Sport',
slug: 'art-culture-sport',
icon: 'paint-brush',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat14',
name: 'Freedom of Speech',
slug: 'freedom-of-speech',
icon: 'bullhorn',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat15',
name: 'Consumption & Sustainability',
slug: 'consumption-sustainability',
icon: 'shopping-cart',
}),
f.create('Category', {
factory.create('Category', {
id: 'cat16',
name: 'Global Peace & Nonviolence',
slug: 'global-peace-nonviolence',
@ -325,16 +323,16 @@ import { gql } from '../jest/helpers'
])
const [environment, nature, democracy, freedom] = await Promise.all([
f.create('Tag', {
factory.create('Tag', {
id: 'Environment',
}),
f.create('Tag', {
factory.create('Tag', {
id: 'Nature',
}),
f.create('Tag', {
factory.create('Tag', {
id: 'Democracy',
}),
f.create('Tag', {
factory.create('Tag', {
id: 'Freedom',
}),
])
@ -649,10 +647,15 @@ import { gql } from '../jest/helpers'
])
authenticatedUser = null
// There is no error logged or the 'try' fails if this mutation is wrong. Why?
const reportMutation = gql`
mutation($id: ID!, $description: String!) {
report(description: $description, id: $id) {
id
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(
resourceId: $resourceId
reasonCategory: $reasonCategory
reasonDescription: $reasonDescription
) {
type
}
}
`
@ -661,22 +664,25 @@ import { gql } from '../jest/helpers'
mutate({
mutation: reportMutation,
variables: {
description: 'This comment is bigoted',
id: 'c1',
resourceId: 'c1',
reasonCategory: 'other',
reasonDescription: 'This comment is bigoted',
},
}),
mutate({
mutation: reportMutation,
variables: {
description: 'This post is bigoted',
id: 'p1',
resourceId: 'p1',
reasonCategory: 'discrimination_etc',
reasonDescription: 'This post is bigoted',
},
}),
mutate({
mutation: reportMutation,
variables: {
description: 'This user is harassing me with bigoted remarks',
id: 'u1',
resourceId: 'u1',
reasonCategory: 'doxing',
reasonDescription: 'This user is harassing me with bigoted remarks',
},
}),
])
@ -684,7 +690,223 @@ import { gql } from '../jest/helpers'
await Promise.all(
[...Array(30).keys()].map(i => {
return f.create('User')
return factory.create('User')
}),
)
await Promise.all(
[...Array(30).keys()].map(() => {
return factory.create('Post', {
author: jennyRostock,
image: faker.image.unsplash.objects(),
})
}),
)
await Promise.all(
[...Array(6).keys()].map(() => {
return factory.create('Comment', {
author: jennyRostock,
postId: 'p2',
})
}),
)
await Promise.all(
[...Array(4).keys()].map(() => {
return factory.create('Comment', {
author: jennyRostock,
postId: 'p15',
})
}),
)
await Promise.all(
[...Array(2).keys()].map(() => {
return factory.create('Comment', {
author: jennyRostock,
postId: 'p4',
})
}),
)
await Promise.all(
[...Array(21).keys()].map(() => {
return factory.create('Post', {
author: peterLustig,
image: faker.image.unsplash.buildings(),
})
}),
)
await Promise.all(
[...Array(3).keys()].map(() => {
return factory.create('Comment', {
author: peterLustig,
postId: 'p4',
})
}),
)
await Promise.all(
[...Array(5).keys()].map(() => {
return factory.create('Comment', {
author: peterLustig,
postId: 'p14',
})
}),
)
await Promise.all(
[...Array(6).keys()].map(() => {
return factory.create('Comment', {
author: peterLustig,
postId: 'p0',
})
}),
)
await Promise.all(
[...Array(11).keys()].map(() => {
return factory.create('Post', {
author: dewey,
image: faker.image.unsplash.food(),
})
}),
)
await Promise.all(
[...Array(7).keys()].map(() => {
return factory.create('Comment', {
author: dewey,
postId: 'p2',
})
}),
)
await Promise.all(
[...Array(5).keys()].map(() => {
return factory.create('Comment', {
author: dewey,
postId: 'p6',
})
}),
)
await Promise.all(
[...Array(2).keys()].map(() => {
return factory.create('Comment', {
author: dewey,
postId: 'p9',
})
}),
)
await Promise.all(
[...Array(16).keys()].map(() => {
return factory.create('Post', {
author: louie,
image: faker.image.unsplash.technology(),
})
}),
)
await Promise.all(
[...Array(4).keys()].map(() => {
return factory.create('Comment', {
author: louie,
postId: 'p1',
})
}),
)
await Promise.all(
[...Array(8).keys()].map(() => {
return factory.create('Comment', {
author: louie,
postId: 'p10',
})
}),
)
await Promise.all(
[...Array(5).keys()].map(() => {
return factory.create('Comment', {
author: louie,
postId: 'p13',
})
}),
)
await Promise.all(
[...Array(45).keys()].map(() => {
return factory.create('Post', {
author: bobDerBaumeister,
image: faker.image.unsplash.people(),
})
}),
)
await Promise.all(
[...Array(2).keys()].map(() => {
return factory.create('Comment', {
author: bobDerBaumeister,
postId: 'p2',
})
}),
)
await Promise.all(
[...Array(3).keys()].map(() => {
return factory.create('Comment', {
author: bobDerBaumeister,
postId: 'p12',
})
}),
)
await Promise.all(
[...Array(7).keys()].map(() => {
return factory.create('Comment', {
author: bobDerBaumeister,
postId: 'p13',
})
}),
)
await Promise.all(
[...Array(8).keys()].map(() => {
return factory.create('Post', {
author: huey,
image: faker.image.unsplash.nature(),
})
}),
)
await Promise.all(
[...Array(6).keys()].map(() => {
return factory.create('Comment', {
author: huey,
postId: 'p0',
})
}),
)
await Promise.all(
[...Array(8).keys()].map(() => {
return factory.create('Comment', {
author: huey,
postId: 'p13',
})
}),
)
await Promise.all(
[...Array(9).keys()].map(() => {
return factory.create('Comment', {
author: huey,
postId: 'p15',
})
}),
)

View File

@ -14,10 +14,10 @@
resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz#3ce939cb127fb8aaa3ffc1e90dff9b8af9f2e3dc"
integrity sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ==
"@babel/cli@~7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.2.tgz#4ce8b5b4b2e4b4c1b7bd841cec62085e2dfc4465"
integrity sha512-JDZ+T/br9pPfT2lmAMJypJDTTTHM9ePD/ED10TRjRzJVdEVy+JB3iRlhzYmTt5YkNgHvxWGlUVnLtdv6ruiDrQ==
"@babel/cli@~7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.6.4.tgz#9b35a4e15fa7d8f487418aaa8229c8b0bc815f20"
integrity sha512-tqrDyvPryBM6xjIyKKUwr3s8CzmmYidwgdswd7Uc/Cv0ogZcuS1TYQTLx/eWKP3UbJ6JxZAiYlBZabXm/rtRsQ==
dependencies:
commander "^2.8.1"
convert-source-map "^1.1.0"
@ -38,18 +38,18 @@
dependencies:
"@babel/highlight" "^7.0.0"
"@babel/core@^7.1.0", "@babel/core@~7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91"
integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==
"@babel/core@^7.1.0", "@babel/core@~7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff"
integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==
dependencies:
"@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.2"
"@babel/generator" "^7.6.4"
"@babel/helpers" "^7.6.2"
"@babel/parser" "^7.6.2"
"@babel/parser" "^7.6.4"
"@babel/template" "^7.6.0"
"@babel/traverse" "^7.6.2"
"@babel/types" "^7.6.0"
"@babel/traverse" "^7.6.3"
"@babel/types" "^7.6.3"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
@ -58,12 +58,12 @@
semver "^5.4.1"
source-map "^0.5.0"
"@babel/generator@^7.4.0", "@babel/generator@^7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.2.tgz#dac8a3c2df118334c2a29ff3446da1636a8f8c03"
integrity sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==
"@babel/generator@^7.4.0", "@babel/generator@^7.6.3", "@babel/generator@^7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671"
integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==
dependencies:
"@babel/types" "^7.6.0"
"@babel/types" "^7.6.3"
jsesc "^2.5.1"
lodash "^4.17.13"
source-map "^0.5.0"
@ -241,10 +241,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
"@babel/node@~7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.6.2.tgz#a94479f95ee2008342f4847346c8bb8ff2770f44"
integrity sha512-59UxvVtRpVpL5i0KTcw41FqLNPT/Jc9k/48Rq00wfN49lAIQeRKGwZ6xX1FWlCfcIGP+5l4rfZajORvmYkhfGg==
"@babel/node@~7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/node/-/node-7.6.3.tgz#f175ab6718dde55431cbd4d9dee95f65c38be527"
integrity sha512-+nHje5AcE9TPlB/TRGYyOSQyTfhfU/WXniG6SkVf+V5+ibAjEqkH79lYdiEcytBTH4KeSf25IriySXs6TjaLjg==
dependencies:
"@babel/register" "^7.6.2"
commander "^2.8.1"
@ -254,10 +254,10 @@
regenerator-runtime "^0.13.3"
v8flags "^3.1.1"
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1"
integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.6.0", "@babel/parser@^7.6.3", "@babel/parser@^7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81"
integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
@ -382,10 +382,10 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-block-scoping@^7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz#96c33ab97a9ae500cc6f5b19e04a7e6553360a79"
integrity sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ==
"@babel/plugin-transform-block-scoping@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz#6e854e51fbbaa84351b15d4ddafe342f3a5d542a"
integrity sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
lodash "^4.17.13"
@ -507,10 +507,10 @@
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-named-capturing-groups-regex@^7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz#c1ca0bb84b94f385ca302c3932e870b0fb0e522b"
integrity sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g==
"@babel/plugin-transform-named-capturing-groups-regex@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.3.tgz#aaa6e409dd4fb2e50b6e2a91f7e3a3149dbce0cf"
integrity sha512-jTkk7/uE6H2s5w6VlMHeWuH+Pcy2lmdwFoeWCVnvIrDUnB5gQqTVI8WfmEAhF2CDEarGrknZcmSFg1+bkfCoSw==
dependencies:
regexpu-core "^4.6.0"
@ -605,18 +605,10 @@
"@babel/helper-regex" "^7.4.4"
regexpu-core "^4.6.0"
"@babel/polyfill@^7.2.3":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.6.0.tgz#6d89203f8b6cd323e8d946e47774ea35dc0619cc"
integrity sha512-q5BZJI0n/B10VaQQvln1IlDK3BTBJFbADx7tv+oXDPIDZuTo37H5Adb9jhlXm/fEN4Y7/64qD9mnrJJG7rmaTw==
dependencies:
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
"@babel/preset-env@~7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.2.tgz#abbb3ed785c7fe4220d4c82a53621d71fc0c75d3"
integrity sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q==
"@babel/preset-env@~7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.3.tgz#9e1bf05a2e2d687036d24c40e4639dc46cef2271"
integrity sha512-CWQkn7EVnwzlOdR5NOm2+pfgSNEZmvGjOhlCHBDq0J8/EStr+G+FvPEiz9B56dR6MoiUFjXhfE4hjLoAKKJtIQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
@ -634,7 +626,7 @@
"@babel/plugin-transform-arrow-functions" "^7.2.0"
"@babel/plugin-transform-async-to-generator" "^7.5.0"
"@babel/plugin-transform-block-scoped-functions" "^7.2.0"
"@babel/plugin-transform-block-scoping" "^7.6.2"
"@babel/plugin-transform-block-scoping" "^7.6.3"
"@babel/plugin-transform-classes" "^7.5.5"
"@babel/plugin-transform-computed-properties" "^7.2.0"
"@babel/plugin-transform-destructuring" "^7.6.0"
@ -649,7 +641,7 @@
"@babel/plugin-transform-modules-commonjs" "^7.6.0"
"@babel/plugin-transform-modules-systemjs" "^7.5.0"
"@babel/plugin-transform-modules-umd" "^7.2.0"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.6.2"
"@babel/plugin-transform-named-capturing-groups-regex" "^7.6.3"
"@babel/plugin-transform-new-target" "^7.4.4"
"@babel/plugin-transform-object-super" "^7.5.5"
"@babel/plugin-transform-parameters" "^7.4.4"
@ -662,7 +654,7 @@
"@babel/plugin-transform-template-literals" "^7.4.4"
"@babel/plugin-transform-typeof-symbol" "^7.2.0"
"@babel/plugin-transform-unicode-regex" "^7.6.2"
"@babel/types" "^7.6.0"
"@babel/types" "^7.6.3"
browserslist "^4.6.0"
core-js-compat "^3.1.1"
invariant "^2.2.2"
@ -688,7 +680,7 @@
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.5.5":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.2.tgz#c3d6e41b304ef10dcf13777a33e7694ec4a9a6dd"
integrity sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==
@ -704,25 +696,25 @@
"@babel/parser" "^7.6.0"
"@babel/types" "^7.6.0"
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.2":
version "7.6.2"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.2.tgz#b0e2bfd401d339ce0e6c05690206d1e11502ce2c"
integrity sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.2", "@babel/traverse@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9"
integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw==
dependencies:
"@babel/code-frame" "^7.5.5"
"@babel/generator" "^7.6.2"
"@babel/generator" "^7.6.3"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4"
"@babel/parser" "^7.6.2"
"@babel/types" "^7.6.0"
"@babel/parser" "^7.6.3"
"@babel/types" "^7.6.3"
debug "^4.1.0"
globals "^11.1.0"
lodash "^4.17.13"
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0":
version "7.6.1"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648"
integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==
"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0", "@babel/types@^7.6.3":
version "7.6.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09"
integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA==
dependencies:
esutils "^2.0.2"
lodash "^4.17.13"
@ -766,10 +758,10 @@
"@hapi/hoek" "8.x.x"
"@hapi/topo" "3.x.x"
"@hapi/joi@^16.1.4":
version "16.1.4"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.4.tgz#b039fe474a0ab838c1a90620c53a208fcef75d99"
integrity sha512-m7ctezhxjob+dSpXnCNlgAj6rrEpdSsaWu3GWL3g1AybQCU36mlAo9IwGFJwIxD+oHgdO6mYyviYlaejX+qN6g==
"@hapi/joi@^16.1.7":
version "16.1.7"
resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-16.1.7.tgz#360857223a87bb1f5f67691537964c1b4908ed93"
integrity sha512-anaIgnZhNooG3LJLrTFzgGALTiO97zRA1UkvQHm9KxxoSiIzCozB3RCNCpDnfhTJD72QlrHA8nwGmNgpFFCIeg==
dependencies:
"@hapi/address" "^2.1.2"
"@hapi/formula" "^1.2.0"
@ -1050,60 +1042,60 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@sentry/core@5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.6.2.tgz#8c5477654a83ebe41a72e86a79215deb5025e418"
integrity sha512-grbjvNmyxP5WSPR6UobN2q+Nss7Hvz+BClBT8QTr7VTEG5q89TwNddn6Ej3bGkaUVbct/GpVlI3XflWYDsnU6Q==
"@sentry/core@5.7.0":
version "5.7.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.7.0.tgz#c2aa5341e703ec7cf2acc69e51971a0b1f7d102a"
integrity sha512-gQel0d7LBSWJGHc7gfZllYAu+RRGD9GcYGmkRfemurmDyDGQDf/sfjiBi8f9QxUc2iFTHnvIR5nMTyf0U3yl3Q==
dependencies:
"@sentry/hub" "5.6.1"
"@sentry/minimal" "5.6.1"
"@sentry/types" "5.6.1"
"@sentry/utils" "5.6.1"
"@sentry/hub" "5.7.0"
"@sentry/minimal" "5.7.0"
"@sentry/types" "5.7.0"
"@sentry/utils" "5.7.0"
tslib "^1.9.3"
"@sentry/hub@5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.6.1.tgz#9f355c0abcc92327fbd10b9b939608aa4967bece"
integrity sha512-m+OhkIV5yTAL3R1+XfCwzUQka0UF/xG4py8sEfPXyYIcoOJ2ZTX+1kQJLy8QQJ4RzOBwZA+DzRKP0cgzPJ3+oQ==
"@sentry/hub@5.7.0":
version "5.7.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.7.0.tgz#f7c356202a9db1daae82ce7f48ebf1139e4e9d02"
integrity sha512-qNdYheJ6j4P9Sk0eqIINpJohImmu/+trCwFb4F8BGLQth5iGMVQD6D0YUrgjf4ZaQwfhw9tv4W6VEfF5tyASoA==
dependencies:
"@sentry/types" "5.6.1"
"@sentry/utils" "5.6.1"
"@sentry/types" "5.7.0"
"@sentry/utils" "5.7.0"
tslib "^1.9.3"
"@sentry/minimal@5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.6.1.tgz#09d92b26de0b24555cd50c3c33ba4c3e566009a1"
integrity sha512-ercCKuBWHog6aS6SsJRuKhJwNdJ2oRQVWT2UAx1zqvsbHT9mSa8ZRjdPHYOtqY3DoXKk/pLUFW/fkmAnpdMqRw==
"@sentry/minimal@5.7.0":
version "5.7.0"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.7.0.tgz#832d26bcd862c6ea628d48ad199ac7f966a2d907"
integrity sha512-0sizE2prS9nmfLyVUKmVzFFFqRNr9iorSCCejwnlRe3crqKqjf84tuRSzm6NkZjIyYj9djuuo9l9XN12NLQ/4A==
dependencies:
"@sentry/hub" "5.6.1"
"@sentry/types" "5.6.1"
"@sentry/hub" "5.7.0"
"@sentry/types" "5.7.0"
tslib "^1.9.3"
"@sentry/node@^5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.6.2.tgz#4b62f056031da65cad78220d48c546b8bfbfaed7"
integrity sha512-A9CELco6SjF4zt8iS1pO3KdUVI2WVhtTGhSH6X04OVf2en1fimPR+Vs8YVY/04udwd7o+3mI6byT+rS9+/Qzow==
"@sentry/node@^5.7.0":
version "5.7.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-5.7.0.tgz#153777f06b2fcd346edbff9adbb6b231c7e5fa0a"
integrity sha512-iqQbGAJDBlpQkp1rl9RkDCIfnukr4cOtHPgJPmLY19m/KXIHD2cdKhvbqoCvIPBTIAeSGQIvDT9jD5zT46eoqQ==
dependencies:
"@sentry/core" "5.6.2"
"@sentry/hub" "5.6.1"
"@sentry/types" "5.6.1"
"@sentry/utils" "5.6.1"
cookie "0.3.1"
https-proxy-agent "2.2.1"
lru_map "0.3.3"
"@sentry/core" "5.7.0"
"@sentry/hub" "5.7.0"
"@sentry/types" "5.7.0"
"@sentry/utils" "5.7.0"
cookie "^0.3.1"
https-proxy-agent "^3.0.0"
lru_map "^0.3.3"
tslib "^1.9.3"
"@sentry/types@5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.6.1.tgz#5915e1ee4b7a678da3ac260c356b1cb91139a299"
integrity sha512-Kub8TETefHpdhvtnDj3kKfhCj0u/xn3Zi2zIC7PB11NJHvvPXENx97tciz4roJGp7cLRCJsFqCg4tHXniqDSnQ==
"@sentry/types@5.7.0":
version "5.7.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.7.0.tgz#e8677e57b40c2c63cad42c02add12b238e647c10"
integrity sha512-bFRVortg713dE2yJXNFgNe6sNBVVSkpoELLkGPatdVQi0dYc6OggIIX4UZZvkynFx72GwYqO1NOrtUcJY2gmMg==
"@sentry/utils@5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.6.1.tgz#69d9e151e50415bc91f2428e3bcca8beb9bc2815"
integrity sha512-rfgha+UsHW816GqlSRPlniKqAZylOmQWML2JsujoUP03nPu80zdN43DK9Poy/d9OxBxv0gd5K2n+bFdM2kqLQQ==
"@sentry/utils@5.7.0":
version "5.7.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.7.0.tgz#a6850aa4f5476fa26517cd5c6248f871d8d9939b"
integrity sha512-XmwQpLqea9mj8x1N7P/l4JvnEb0Rn5Py5OtBgl0ctk090W+GB1uM8rl9mkMf6698o1s1Z8T/tI/QY0yFA5uZXg==
dependencies:
"@sentry/types" "5.6.1"
"@sentry/types" "5.7.0"
tslib "^1.9.3"
"@sindresorhus/is@^0.14.0":
@ -1452,7 +1444,7 @@ acorn@^7.0.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a"
integrity sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==
agent-base@^4.1.0:
agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
@ -1531,13 +1523,13 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
apollo-cache-control@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.8.4.tgz#a3650d5e4173953e2a3af995bea62147f1ffe4d7"
integrity sha512-IZ1d3AXZtkZhLYo0kWqTbZ6nqLFaeUvLdMESs+9orMadBZ7mvzcAfBwrhKyCWPGeAAZ/jKv8FtYHybpchHgFAg==
apollo-cache-control@^0.8.5:
version "0.8.5"
resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.8.5.tgz#d4b34691f6ca1cefac9d82b99a94a0815a85a5a8"
integrity sha512-2yQ1vKgJQ54SGkoQS/ZLZrDX3La6cluAYYdruFYJMJtL4zQrSdeOCy11CQliCMYEd6eKNyE70Rpln51QswW2Og==
dependencies:
apollo-server-env "^2.4.3"
graphql-extensions "^0.10.3"
graphql-extensions "^0.10.4"
apollo-cache-inmemory@~1.6.3:
version "1.6.3"
@ -1580,27 +1572,27 @@ apollo-datasource@^0.6.3:
apollo-server-caching "^0.5.0"
apollo-server-env "^2.4.3"
apollo-engine-reporting-protobuf@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.4.0.tgz#e34c192d86493b33a73181fd6be75721559111ec"
integrity sha512-cXHZSienkis8v4RhqB3YG3DkaksqLpcxApRLTpRMs7IXNozgV7CUPYGFyFBEra1ZFgUyHXx4G9MpelV+n2cCfA==
apollo-engine-reporting-protobuf@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.4.1.tgz#c0a35bcf28487f87dcbc452b03277f575192f5d2"
integrity sha512-d7vFFZ2oUrvGaN0Hpet8joe2ZG0X0lIGilN+SwgVP38dJnOuadjsaYMyrD9JudGQJg0bJA5wVQfYzcCVy0slrw==
dependencies:
protobufjs "^6.8.6"
apollo-engine-reporting@^1.4.6:
version "1.4.6"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.6.tgz#83af6689c4ab82d1c62c3f5dde7651975508114f"
integrity sha512-acfb7oFnru/8YQdY4x6+7WJbZfzdVETI8Cl+9ImgUrvUnE8P+f2SsGTKXTC1RuUvve4c56PAvaPgE+z8X1a1Mw==
apollo-engine-reporting@^1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/apollo-engine-reporting/-/apollo-engine-reporting-1.4.7.tgz#6ca69ebdc1c17200969e2e4e07a0be64d748c27e"
integrity sha512-qsKDz9VkoctFhojM3Nj3nvRBO98t8TS2uTgtiIjUGs3Hln2poKMP6fIQ37Nm2Q2B3JJst76HQtpPwXmRJd1ZUg==
dependencies:
apollo-engine-reporting-protobuf "^0.4.0"
apollo-graphql "^0.3.3"
apollo-engine-reporting-protobuf "^0.4.1"
apollo-graphql "^0.3.4"
apollo-server-caching "^0.5.0"
apollo-server-env "^2.4.3"
apollo-server-types "^0.2.4"
apollo-server-types "^0.2.5"
async-retry "^1.2.1"
graphql-extensions "^0.10.3"
graphql-extensions "^0.10.4"
apollo-env@0.5.1:
apollo-env@0.5.1, apollo-env@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/apollo-env/-/apollo-env-0.5.1.tgz#b9b0195c16feadf0fe9fd5563edb0b9b7d9e97d3"
integrity sha512-fndST2xojgSdH02k5hxk1cbqA9Ti8RX4YzzBoAB4oIe1Puhq7+YlhXGXfXB5Y4XN0al8dLg+5nAkyjNAR2qZTw==
@ -1617,12 +1609,12 @@ apollo-errors@^1.9.0:
assert "^1.4.1"
extendable-error "^0.1.5"
apollo-graphql@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.3.tgz#ce1df194f6e547ad3ce1e35b42f9c211766e1658"
integrity sha512-t3CO/xIDVsCG2qOvx2MEbuu4b/6LzQjcBBwiVnxclmmFyAxYCIe7rpPlnLHSq7HyOMlCWDMozjoeWfdqYSaLqQ==
apollo-graphql@^0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/apollo-graphql/-/apollo-graphql-0.3.4.tgz#c1f68591a4775945441d049eff9323542ab0401f"
integrity sha512-w+Az1qxePH4oQ8jvbhQBl5iEVvqcqynmU++x/M7MM5xqN1C7m1kyIzpN17gybXlTJXY4Oxej2WNURC2/hwpfYw==
dependencies:
apollo-env "0.5.1"
apollo-env "^0.5.1"
lodash.sortby "^4.7.0"
apollo-link-context@~1.0.19:
@ -1668,26 +1660,26 @@ apollo-server-caching@^0.5.0:
dependencies:
lru-cache "^5.0.0"
apollo-server-core@^2.9.4:
version "2.9.4"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.4.tgz#0404455884951804d23ea64e45514c73afd34e5e"
integrity sha512-6mzipnn9woJxgo/JQFWTlY13svS7HCr0ZsN035eRmKOsXzROfB9ugXcTuc6MP94ICM7TlB/DtJOP+bLX53mijw==
apollo-server-core@^2.9.6:
version "2.9.6"
resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.9.6.tgz#b6dc087200633f47ca4f08244d3e606b4d616320"
integrity sha512-2tHAWQxP7HrETI/BZvg2fem6YlahF9HUp4Y6SSL95WP3uNMOJBlN12yM1y+O2u5K5e4jwdPNaLjoL2A/26XrLw==
dependencies:
"@apollographql/apollo-tools" "^0.4.0"
"@apollographql/graphql-playground-html" "1.6.24"
"@types/graphql-upload" "^8.0.0"
"@types/ws" "^6.0.0"
apollo-cache-control "^0.8.4"
apollo-cache-control "^0.8.5"
apollo-datasource "^0.6.3"
apollo-engine-reporting "^1.4.6"
apollo-engine-reporting "^1.4.7"
apollo-server-caching "^0.5.0"
apollo-server-env "^2.4.3"
apollo-server-errors "^2.3.3"
apollo-server-plugin-base "^0.6.4"
apollo-server-types "^0.2.4"
apollo-tracing "^0.8.4"
apollo-server-plugin-base "^0.6.5"
apollo-server-types "^0.2.5"
apollo-tracing "^0.8.5"
fast-json-stable-stringify "^2.0.0"
graphql-extensions "^0.10.3"
graphql-extensions "^0.10.4"
graphql-tag "^2.9.2"
graphql-tools "^4.0.0"
graphql-upload "^8.0.2"
@ -1708,10 +1700,10 @@ apollo-server-errors@^2.3.3:
resolved "https://registry.yarnpkg.com/apollo-server-errors/-/apollo-server-errors-2.3.3.tgz#83763b00352c10dc68fbb0d41744ade66de549ff"
integrity sha512-MO4oJ129vuCcbqwr5ZwgxqGGiLz3hCyowz0bstUF7MR+vNGe4oe3DWajC9lv4CxrhcqUHQOeOPViOdIo1IxE3g==
apollo-server-express@^2.9.4:
version "2.9.4"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.4.tgz#ae7ca0b70a644ba9fa5e3ac395d1e2d9a4b23522"
integrity sha512-diX9n81E0tIJ0Sy2bHvDGPM9QsFBsZ76Nx/dszinY00ViyWG0yIAYEYWeRbsoKTeNDWWTvlMrh/3Eu2oaCIEhQ==
apollo-server-express@^2.9.6:
version "2.9.6"
resolved "https://registry.yarnpkg.com/apollo-server-express/-/apollo-server-express-2.9.6.tgz#eec2ec43b829b059278e14994d06bd23e43266f9"
integrity sha512-j80azBeXvLvyZsbqCnus7GH+w8vk+2IOnYzROZu/f0D2roDZtsu1XZkn+aplDJZXMcEXtqB6t4qNpyvV4zY0XQ==
dependencies:
"@apollographql/graphql-playground-html" "1.6.24"
"@types/accepts" "^1.3.5"
@ -1719,8 +1711,8 @@ apollo-server-express@^2.9.4:
"@types/cors" "^2.8.4"
"@types/express" "4.17.1"
accepts "^1.3.5"
apollo-server-core "^2.9.4"
apollo-server-types "^0.2.4"
apollo-server-core "^2.9.6"
apollo-server-types "^0.2.5"
body-parser "^1.18.3"
cors "^2.8.4"
express "^4.17.1"
@ -1730,47 +1722,47 @@ apollo-server-express@^2.9.4:
subscriptions-transport-ws "^0.9.16"
type-is "^1.6.16"
apollo-server-plugin-base@^0.6.4:
version "0.6.4"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.4.tgz#63ea4fd0bbb6c4510bc8d0d2ad0a0684c8d0da8c"
integrity sha512-4rY+cBAIpQomGWYBtk8hHkLQWHrh5hgIBPQqmhXh00YFdcY+Ob1/cU2/2iqTcIzhtcaezsc8OZ63au6ahSBQqg==
apollo-server-plugin-base@^0.6.5:
version "0.6.5"
resolved "https://registry.yarnpkg.com/apollo-server-plugin-base/-/apollo-server-plugin-base-0.6.5.tgz#eebe27734c51bf6a45b6a9ec8738750b132ffde7"
integrity sha512-z2ve7HEPWmZI3EzL0iiY9qyt1i0hitT+afN5PzssCw594LB6DfUQWsI14UW+W+gcw8hvl8VQUpXByfUntAx5vw==
dependencies:
apollo-server-types "^0.2.4"
apollo-server-types "^0.2.5"
apollo-server-testing@~2.9.4:
version "2.9.4"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.4.tgz#421783573bdc5cef70dfe574b5193db38a33b5fb"
integrity sha512-qvnA9cXRKqizfYPHBli4LeSKYXwFVsQkGF3eHgofN/RbTqnEBqW7I5L14qDYAjGZg9/Z4alJf69hFE8KPHbT0Q==
apollo-server-testing@~2.9.6:
version "2.9.6"
resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.9.6.tgz#1cae51c93a8865b85e877e2c9927964cf32625e6"
integrity sha512-pbURQD5VjNFk4GMVVxyCds9rY4/NIqjvjE4tyf1k89RHwMdk+zuVggt/DGudteorZtqAqtsOIHWojMBU4s2klA==
dependencies:
apollo-server-core "^2.9.4"
apollo-server-core "^2.9.6"
apollo-server-types@^0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.2.4.tgz#28864900ffc7f9711a859297c143a833fdb6aa43"
integrity sha512-G4FvBVgGQcTW6ZBS2+hvcDQkSfdOIKV+cHADduXA275v+5zl42g+bCaGd/hCCKTDRjmQvObLiMxH/BJ6pDMQgA==
apollo-server-types@^0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.2.5.tgz#2d63924706ffc1a59480cbbc93e9fe86655a57a5"
integrity sha512-6iJQsPh59FWu4K7ABrVmpnQVgeK8Ockx8BcawBh+saFYWTlVczwcLyGSZPeV1tPSKwFwKZutyEslrYSafcarXQ==
dependencies:
apollo-engine-reporting-protobuf "^0.4.0"
apollo-engine-reporting-protobuf "^0.4.1"
apollo-server-caching "^0.5.0"
apollo-server-env "^2.4.3"
apollo-server@~2.9.4:
version "2.9.4"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.4.tgz#564a0d0ec6dbefc86dbabe15bd23a83e48f58314"
integrity sha512-huAgQizkmzUkREixsSJHNM4ZnJ08plkwK70dm36mX9j+yYbc0h9J5b5o4E2Fb9U5PMR8kEVto1dz2rOJ0XPApA==
apollo-server@~2.9.6:
version "2.9.6"
resolved "https://registry.yarnpkg.com/apollo-server/-/apollo-server-2.9.6.tgz#11b6f1128ddb674d2651bb289e0c0fc28aa18653"
integrity sha512-sDvrGpMQsTGQ9FTkFm3xracrSUi8nFoh3svlD98pe6qb75UDDrXAZgxwQCSOwZ3BkaJ7UkdndfhnruhFstTeMw==
dependencies:
apollo-server-core "^2.9.4"
apollo-server-express "^2.9.4"
apollo-server-core "^2.9.6"
apollo-server-express "^2.9.6"
express "^4.0.0"
graphql-subscriptions "^1.0.0"
graphql-tools "^4.0.0"
apollo-tracing@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.8.4.tgz#0117820c3f0ad3aa6daf7bf13ddbb923cbefa6de"
integrity sha512-DjbFW0IvHicSlTVG+vK+1WINfBMRCdPPHJSW/j65JMir9Oe56WGeqL8qz8hptdUUmLYEb+azvcyyGsJsiR3zpQ==
apollo-tracing@^0.8.5:
version "0.8.5"
resolved "https://registry.yarnpkg.com/apollo-tracing/-/apollo-tracing-0.8.5.tgz#f07c4584d95bcf750e44bfe9845e073b03774941"
integrity sha512-lZn10/GRBZUlMxVYLghLMFsGcLN0jTYDd98qZfBtxw+wEWUx+PKkZdljDT+XNoOm/kDvEutFGmi5tSLhArIzWQ==
dependencies:
apollo-server-env "^2.4.3"
graphql-extensions "^0.10.3"
graphql-extensions "^0.10.4"
apollo-utilities@1.3.2, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.2:
version "1.3.2"
@ -1880,12 +1872,12 @@ assert@^1.4.1:
object-assign "^4.1.1"
util "0.10.3"
assertion-error-formatter@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/assertion-error-formatter/-/assertion-error-formatter-2.0.1.tgz#6bbdffaec8e2fa9e2b0eb158bfe353132d7c0a9b"
integrity sha512-cjC3jUCh9spkroKue5PDSKH5RFQ/KNuZJhk3GwHYmB/8qqETxLOmMdLH+ohi/VukNzxDlMvIe7zScvLoOdhb6Q==
assertion-error-formatter@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/assertion-error-formatter/-/assertion-error-formatter-3.0.0.tgz#be9c8825dee6a8a6c72183d915912d9b57d5d265"
integrity sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==
dependencies:
diff "^3.0.0"
diff "^4.0.1"
pad-right "^0.2.2"
repeat-string "^1.6.1"
@ -2073,7 +2065,7 @@ bcryptjs@~2.4.3:
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
becke-ch--regex--s0-0-v1--base--pl--lib@^1.2.0:
becke-ch--regex--s0-0-v1--base--pl--lib@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/becke-ch--regex--s0-0-v1--base--pl--lib/-/becke-ch--regex--s0-0-v1--base--pl--lib-1.4.0.tgz#429ceebbfa5f7e936e78d73fbdc7da7162b20e20"
integrity sha1-Qpzuu/pffpNueNc/vcfacWKyDiA=
@ -2328,7 +2320,7 @@ cheerio@~1.0.0-rc.2, cheerio@~1.0.0-rc.3:
lodash "^4.15.0"
parse5 "^3.0.1"
chokidar@^2.1.5, chokidar@^2.1.8:
chokidar@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
@ -2487,11 +2479,16 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.8.1, commander@^2.9.0, commander@~2.20.0:
commander@^2.8.1, commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
commander@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@ -2568,16 +2565,16 @@ cookie-signature@1.0.6:
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
cookie@0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cookie@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
cookiejar@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
@ -2626,12 +2623,11 @@ create-error-class@^3.0.0:
dependencies:
capture-stack-trace "^1.0.0"
cross-env@~6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.2.tgz#2ec3c4b9b90cc074fed375415fc229cb7d6ce612"
integrity sha512-lA44HlqWCzrv7/l2pY3sfLDvMhXXhx8oztvD6rg34PdCIcP0yk77UwOL2nacsZXlrzPUMtbfagVbK6Itx8pwng==
cross-env@~6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.3.tgz#4256b71e49b3a40637a0ce70768a6ef5c72ae941"
integrity sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==
dependencies:
"@babel/runtime" "^7.6.2"
cross-spawn "^7.0.0"
cross-fetch@2.2.2:
@ -2711,49 +2707,48 @@ cssstyle@^1.0.0:
dependencies:
cssom "0.3.x"
cucumber-expressions@^6.0.0:
version "6.6.2"
resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-6.6.2.tgz#d89640eccc72a78380b6c210eae36a64e7462b81"
integrity sha512-WcFSVBiWNLJbIcAAC3t/ACU46vaOKfe1UIF5H3qveoq+Y4XQm9j3YwHurQNufRKBBg8nCnpU7Ttsx7egjS3hwA==
cucumber-expressions@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-8.0.1.tgz#47eb87dcb626e90a4672986da1130f3c470b9e3d"
integrity sha512-g+A+tUEafNofe6ErwvOkqaMvDj9NuOr0GouGotpw4r5yK2d4144o9/6sQpXBr2YXbRy5ItmER/2bzAyDAzhPyQ==
dependencies:
becke-ch--regex--s0-0-v1--base--pl--lib "^1.2.0"
becke-ch--regex--s0-0-v1--base--pl--lib "^1.4.0"
xregexp "^4.2.4"
cucumber-tag-expressions@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/cucumber-tag-expressions/-/cucumber-tag-expressions-1.1.1.tgz#7f5c7b70009bc2b666591bfe64854578bedee85a"
integrity sha1-f1x7cACbwrZmWRv+ZIVFeL7e6Fo=
cucumber-tag-expressions@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/cucumber-tag-expressions/-/cucumber-tag-expressions-2.0.2.tgz#aac27aae3690818ec15235bd056282dad8a2d2b8"
integrity sha512-DohmT4X641KX/sb96bdb7J2kXNcQBPrYmf3Oc5kiHCLfzFMWx/o2kB4JvjvQPZnYuA9lRt6pqtArM5gvUn4uzw==
cucumber@~5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cucumber/-/cucumber-5.1.0.tgz#7b166812c255bec7eac4b0df7007a40d089c895d"
integrity sha512-zrl2VYTBRgvxucwV2GKAvLqcfA1Naeax8plPvWgPEzl3SCJiuPPv3WxBHIRHtPYcEdbHDR6oqLpZP4bJ8UIdmA==
cucumber@~6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/cucumber/-/cucumber-6.0.2.tgz#3c4fbf1f76e60ddee79ab58f137a62c897a4d7f0"
integrity sha512-yEwPYGvgS2KG6ODdUXQwWcxjyr/l31dmpGJsZSkJIXNLNNmieKVefTpf8zLj6+0V2TCPwkmUZt4+OIXv97duEw==
dependencies:
"@babel/polyfill" "^7.2.3"
assertion-error-formatter "^2.0.1"
assertion-error-formatter "^3.0.0"
bluebird "^3.4.1"
cli-table3 "^0.5.1"
colors "^1.1.2"
commander "^2.9.0"
cross-spawn "^6.0.5"
cucumber-expressions "^6.0.0"
cucumber-tag-expressions "^1.1.1"
commander "^3.0.1"
cucumber-expressions "^8.0.1"
cucumber-tag-expressions "^2.0.2"
duration "^0.2.1"
escape-string-regexp "^1.0.5"
figures "2.0.0"
gherkin "^5.0.0"
escape-string-regexp "^2.0.0"
figures "^3.0.0"
gherkin "5.0.0"
glob "^7.1.3"
indent-string "^3.1.0"
indent-string "^4.0.0"
is-generator "^1.0.2"
is-stream "^1.1.0"
is-stream "^2.0.0"
knuth-shuffle-seeded "^1.0.6"
lodash "^4.17.10"
lodash "^4.17.14"
mz "^2.4.0"
progress "^2.0.0"
resolve "^1.3.3"
serialize-error "^3.0.0"
serialize-error "^4.1.0"
stack-chain "^2.0.0"
stacktrace-js "^2.0.0"
string-argv "0.1.1"
string-argv "^0.3.0"
title-case "^2.1.1"
util-arity "^1.0.2"
verror "^1.9.0"
@ -2787,10 +2782,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
date-fns@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.4.1.tgz#b53f9bb65ae6bd9239437035710e01cf383b625e"
integrity sha512-2RhmH/sjDSCYW2F3ZQxOUx/I7PvzXpi89aQL2d3OAxSTwLx6NilATeUbe0menFE3Lu5lFkOFci36ivimwYHHxw==
date-fns@2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.5.0.tgz#b939f17c2902ce81cffe449702ba22c0781b38ec"
integrity sha512-I6Tkis01//nRcmvMQw/MRE1HAtcuA5Ie6jGPb8bJZJub7494LGOObqkV3ParnsSVviAjk5C8mNKDqYVBzCopWg==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
@ -2938,10 +2933,10 @@ diff-sequences@^24.9.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
diff@^3.0.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
diff@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
dns-prefetch-control@0.2.0:
version "0.2.0"
@ -3036,10 +3031,10 @@ dotenv@^4.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
integrity sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=
dotenv@~8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.1.0.tgz#d811e178652bfb8a1e593c6dd704ec7e90d85ea2"
integrity sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==
dotenv@~8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
duplexer3@^0.1.4:
version "0.1.4"
@ -3194,6 +3189,11 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
escape-string-regexp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
escodegen@^1.9.1:
version "1.12.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.12.0.tgz#f763daf840af172bb3a2b6dd7219c0e17f7ff541"
@ -3206,10 +3206,10 @@ escodegen@^1.9.1:
optionalDependencies:
source-map "~0.6.1"
eslint-config-prettier@~6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.3.0.tgz#e73b48e59dc49d950843f3eb96d519e2248286a3"
integrity sha512-EWaGjlDAZRzVFveh2Jsglcere2KK5CJBhkNSa1xs3KfMUGdRiT7lG089eqPdvlzWHpAqaekubOsOMu8W8Yk71A==
eslint-config-prettier@~6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.4.0.tgz#0a04f147e31d33c6c161b2dd0971418ac52d0477"
integrity sha512-YrKucoFdc7SEko5Sxe4r6ixqXPDP1tunGw91POeZTTRKItf/AMFYt/YLEQtZMkR2LVpAVhcAcZgcWpm1oGPW7w==
dependencies:
get-stdin "^6.0.0"
@ -3259,10 +3259,10 @@ eslint-plugin-import@~2.18.2:
read-pkg-up "^2.0.0"
resolve "^1.11.0"
eslint-plugin-jest@~22.17.0:
version "22.17.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.17.0.tgz#dc170ec8369cd1bff9c5dd8589344e3f73c88cf6"
integrity sha512-WT4DP4RoGBhIQjv+5D0FM20fAdAUstfYAf/mkufLNTojsfgzc5/IYW22cIg/Q4QBavAZsROQlqppiWDpFZDS8Q==
eslint-plugin-jest@~22.19.0:
version "22.19.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.19.0.tgz#0cf90946a8c927d40a2c64458c89bb635d0f2a0b"
integrity sha512-4zUc3rh36ds0SXdl2LywT4YWA3zRe8sfLhz8bPp8qQPIKvynTTkNGwmSCMpl5d9QiZE2JxSinGF+WD8yU+O0Lg==
dependencies:
"@typescript-eslint/experimental-utils" "^1.13.0"
@ -3628,13 +3628,6 @@ feature-policy@0.3.0:
resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069"
integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==
figures@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
dependencies:
escape-string-regexp "^1.0.5"
figures@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.0.0.tgz#756275c964646163cc6f9197c7a0295dbfd04de9"
@ -3893,10 +3886,10 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
gherkin@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.1.0.tgz#684bbb03add24eaf7bdf544f58033eb28fb3c6d5"
integrity sha1-aEu7A63STq9731RPWAM+so+zxtU=
gherkin@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.0.0.tgz#96def41198ec3908258b511af74f655a2764d2a1"
integrity sha1-lt70EZjsOQgli1Ea909lWidk0qE=
glob-parent@^3.1.0:
version "3.1.0"
@ -3995,14 +3988,14 @@ graphql-custom-directives@~0.2.14:
moment "^2.22.2"
numeral "^2.0.6"
graphql-extensions@^0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.10.3.tgz#9e37f3bd26309c40b03a0be0e63e02b3f99d52ea"
integrity sha512-kwU0gUe+Qdfr8iZYT91qrPSwQNgPhB/ClF1m1LEPdxlptk5FhFmjpxAcbMZ8q7j0kjfnbp2IeV1OhRDCEPqz2w==
graphql-extensions@^0.10.4:
version "0.10.4"
resolved "https://registry.yarnpkg.com/graphql-extensions/-/graphql-extensions-0.10.4.tgz#af851b0d44ea6838cf54de9df3cfc6a8e575e571"
integrity sha512-lE6MroluEYocbR/ICwccv39w+Pz4cBPadJ11z1rJkbZv5wstISEganbDOwl9qN21rcZGiWzh7QUNxUiFUXXEDw==
dependencies:
"@apollographql/apollo-tools" "^0.4.0"
apollo-server-env "^2.4.3"
apollo-server-types "^0.2.4"
apollo-server-types "^0.2.5"
graphql-import@0.7.1:
version "0.7.1"
@ -4339,12 +4332,12 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
https-proxy-agent@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
https-proxy-agent@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz#0106efa5d63d6d6f3ab87c999fa4877a3fd1ff97"
integrity sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==
dependencies:
agent-base "^4.1.0"
agent-base "^4.3.0"
debug "^3.1.0"
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
@ -4412,11 +4405,16 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
indent-string@^3.1.0, indent-string@^3.2.0:
indent-string@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -4772,6 +4770,11 @@ is-stream@^1.0.0, is-stream@^1.1.0:
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
is-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
is-symbol@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
@ -5591,7 +5594,7 @@ lodash.unescape@4.0.1:
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=
lodash@4.17.15, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.11, lodash@~4.17.14, lodash@~4.17.15:
lodash@4.17.15, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.11, lodash@~4.17.14, lodash@~4.17.15:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@ -5638,7 +5641,7 @@ lru-cache@^5.0.0:
dependencies:
yallist "^3.0.2"
lru_map@0.3.3:
lru_map@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
@ -6179,23 +6182,23 @@ nodemailer-html-to-text@^3.1.0:
dependencies:
html-to-text "^5.1.1"
nodemailer@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.0.tgz#a89b0c62d3937bdcdeecbf55687bd7911b627e12"
integrity sha512-TEHBNBPHv7Ie/0o3HXnb7xrPSSQmH1dXwQKRaMKDBGt/ZN54lvDVujP6hKkO/vjkIYL9rK8kHSG11+G42Nhxuw==
nodemailer@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.1.tgz#2784beebac6b9f014c424c54dbdcc5c4d1221346"
integrity sha512-j0BsSyaMlyadEDEypK/F+xlne2K5m6wzPYMXS/yxKI0s7jmT1kBx6GEKRVbZmyYfKOsjkeC/TiMVDJBI/w5gMQ==
nodemon@~1.19.3:
version "1.19.3"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.3.tgz#db71b3e62aef2a8e1283a9fa00164237356102c0"
integrity sha512-TBNKRmJykEbxpTniZBusqRrUTHIEqa2fpecbTQDQj1Gxjth7kKAPP296ztR0o5gPUWsiYbuEbt73/+XMYab1+w==
nodemon@~1.19.4:
version "1.19.4"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.4.tgz#56db5c607408e0fdf8920d2b444819af1aae0971"
integrity sha512-VGPaqQBNk193lrJFotBU8nvWZPqEZY2eIzymy2jjY0fJ9qIsxA0sxQ8ATPl0gZC645gijYEc1jtZvpS8QWzJGQ==
dependencies:
chokidar "^2.1.5"
debug "^3.1.0"
chokidar "^2.1.8"
debug "^3.2.6"
ignore-by-default "^1.0.1"
minimatch "^3.0.4"
pstree.remy "^1.1.6"
semver "^5.5.0"
supports-color "^5.2.0"
pstree.remy "^1.1.7"
semver "^5.7.1"
supports-color "^5.5.0"
touch "^3.1.0"
undefsafe "^2.0.2"
update-notifier "^2.5.0"
@ -6892,7 +6895,7 @@ psl@^1.1.24, psl@^1.1.28:
resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd"
integrity sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==
pstree.remy@^1.1.6:
pstree.remy@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3"
integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==
@ -7404,7 +7407,7 @@ semver-diff@^2.0.0:
dependencies:
semver "^5.0.3"
"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0:
"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@ -7438,10 +7441,12 @@ send@0.17.1:
range-parser "~1.2.1"
statuses "~1.5.0"
serialize-error@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-3.0.0.tgz#80100282b09be33c611536f50033481cb9cc87cf"
integrity sha512-+y3nkkG/go1Vdw+2f/+XUXM1DXX1XcxTl99FfiD/OEPUNw4uo0i6FKABfTAN5ZcgGtjTRZcEbxcE/jtXbEY19A==
serialize-error@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-4.1.0.tgz#63e1e33ede20bcd89d9f0528ea4c15fbf0f2b78a"
integrity sha512-5j9GgyGsP9vV9Uj1S0lDCvlsd+gc2LEPVK7HHHte7IyPwOD4lVQFeaX143gx3U5AnoCi+wbcb3mvaxVysjpxEw==
dependencies:
type-fest "^0.3.0"
serve-static@1.14.1:
version "1.14.1"
@ -7731,10 +7736,10 @@ streamsearch@0.1.2:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
string-argv@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.1.1.tgz#66bd5ae3823708eaa1916fa5412703150d4ddfaf"
integrity sha512-El1Va5ehZ0XTj3Ekw4WFidXvTmt9SrC0+eigdojgtJMVtPkF0qbBe9fyNSl9eQf+kUHnTSQxdQYzuHfZy8V+DQ==
string-argv@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-length@^2.0.0:
version "2.0.0"
@ -7885,7 +7890,7 @@ supports-color@^4.0.0:
dependencies:
has-flag "^2.0.0"
supports-color@^5.2.0, supports-color@^5.3.0:
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==

View File

@ -107,6 +107,11 @@ Then(`I can't see the moderation menu item`, () => {
When(/^I confirm the reporting dialog .*:$/, message => {
cy.contains(message) // wait for element to become visible
cy.get('.ds-modal').within(() => {
cy.get('.ds-radio-option-label')
.first()
.click({
force: true
})
cy.get('button')
.contains('Report')
.click()
@ -114,21 +119,22 @@ When(/^I confirm the reporting dialog .*:$/, message => {
})
Given('somebody reported the following posts:', table => {
table.hashes().forEach(({ id }) => {
table.hashes().forEach(({ submitterEmail, resourceId, reasonCategory, reasonDescription }) => {
const submitter = {
email: `submitter${id}@example.org`,
email: submitterEmail,
password: '1234'
}
cy.factory()
.create('User', submitter)
.authenticateAs(submitter)
.mutate(`mutation($id: ID!, $description: String!) {
report(description: $description, id: $id) {
id
.mutate(`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
type
}
}`, {
id,
description: 'Offensive content'
resourceId,
reasonCategory,
reasonDescription
})
})
})

View File

@ -50,8 +50,8 @@ Feature: Report and Moderate
Scenario: Review reported content
Given somebody reported the following posts:
| id |
| p1 |
| submitterEmail | resourceId | reasonCategory | reasonDescription |
| p1.submitter@example.org | p1 | discrimination_etc | Offensive content |
And I am logged in with a "moderator" role
When I click on the avatar menu in the top right corner
And I click on "Moderation"
@ -60,8 +60,8 @@ Feature: Report and Moderate
Scenario: Review reported posts of a user who's blocked a moderator
Given somebody reported the following posts:
| id |
| p2 |
| submitterEmail | resourceId | reasonCategory | reasonDescription |
| p2.submitter@example.org | p2 | other | Offensive content |
And my user account has the role "moderator"
And there is an annoying user who has blocked me
And I am logged in

View File

@ -0,0 +1,57 @@
# Backup (online)
## Online backups are only avaible with a Neo4j Enterprise and a license, see https://neo4j.com/licensing/ for the different licenses available
This tutorial explains how to carry out an online backup of your Neo4J
database in a kubernetes cluster.
One of the benefits of doing an online backup is that the Neo4j database does not need to be stopped, so there is no downtime. Read [the docs](https://neo4j.com/docs/operations-manual/current/backup/performing/)
To use Neo4j Enterprise you must add this line to your configmap, if using, or your deployment `nitro-neo4j` env.
```
NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes"
```
## Create a Backup in Kubernetes
```sh
# Backup the database with one command, this will get the nitro-neo4j pod, ssh into it, and run the backup command
kubectl -n=human-connection exec -it $(kubectl -n=human-connection get pods | grep nitro-neo4j | awk '{ print $1 }') -- neo4j-admin backup --backup-dir=/var/lib/neo4j --name=neo4j-backup
# Download the file from the pod to your computer.
kubectl cp human-connection/$(kubectl -n=human-connection get pods | grep nitro-neo4j | awk '{ print $1 }'):/var/lib/neo4j/neo4j-backup ./neo4j-backup/
```
You should now have a backup of the database locally. If you want, you can simulate disaster recovery by sshing into the nitro-neo4j pod, deleting all data and restoring from backup
## Disaster where database data is gone somehow
```sh
kubectl -n=human-connection exec -it $(kubectl -n=human-connection get pods | grep nitro-neo4j |awk '{ print $1 }') bash
# Enter cypher-shell
cypher-shell
# Delete all data
> MATCH (n) DETACH DELETE (n);
exit
```
## Restore a backup in Kubernetes
Restoration must be done while the database is not running, see [our docs](https://docs.human-connection.org/human-connection/deployment/volumes/neo4j-offline-backup#stop-and-restart-neo-4-j-database-in-kubernetes) for how to stop the database, but keep the container running
After, you have stopped the database, and have the pod running, you can restore the database by running these commands:
```sh
kubectl --namespace=human-connection get pods
# Copy the ID of the pod running Neo4J.
# Then upload your local backup to the pod. Note that once the pod gets deleted
# e.g. if you change the deployment, the backup file is gone with it.
kubectl cp ./neo4j-backup/ human-connection/<POD-ID>:/root/
kubectl --namespace=human-connection exec -it <POD-ID> bash
# Once you're in the pod restore the backup and overwrite the default database
# called `graph.db` with `--force`.
# This will delete all existing data in database `graph.db`!
neo4j-admin restore --from=/root/neo4j-backup --force
exit
```
Revert your changes to deployment `nitro-neo4j` which will restart the database.

View File

@ -15,6 +15,7 @@ services:
- ./webapp:/nitro-web
environment:
- NUXT_BUILD=/tmp/nuxt # avoid file permission issues when `rm -rf .nuxt/`
- PUBLIC_REGISTRATION=true
command: yarn run dev
backend:
build:
@ -28,6 +29,7 @@ services:
- SMTP_PORT=25
- SMTP_IGNORE_TLS=true
- "DEBUG=${DEBUG}"
- PUBLIC_REGISTRATION=true
maintenance:
image: humanconnection/maintenance:latest
build:

View File

@ -57,6 +57,7 @@ services:
environment:
- NEO4J_AUTH=none
- NEO4J_dbms_security_procedures_unrestricted=algo.*,apoc.*
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
ports:
- 7687:7687
- 7474:7474

View File

@ -1,4 +1,4 @@
FROM neo4j:3.5.11
FROM neo4j:3.5.11-enterprise
LABEL Description="Neo4J database of the Social Network Human-Connection.org with preinstalled database constraints and indices" Vendor="Human Connection gGmbH" Version="0.0.1" Maintainer="Human Connection gGmbH (developer@human-connection.org)"
ARG BUILD_COMMIT

View File

@ -0,0 +1,26 @@
#!/usr/bin/env bash
ENV_FILE=$(dirname "$0")/.env
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
if [ -z "$NEO4J_USERNAME" ] || [ -z "$NEO4J_PASSWORD" ]; then
echo "Please set NEO4J_USERNAME and NEO4J_PASSWORD environment variables."
echo "Database manipulation is not possible without connecting to the database."
echo "E.g. you could \`cp .env.template .env\` unless you run the script in a docker container"
fi
until echo 'RETURN "Connection successful" as info;' | cypher-shell
do
echo "Connecting to neo4j failed, trying again..."
sleep 1
done
echo "
MATCH (submitter:User)-[:REPORTED]->(report:Report)-[:REPORTED]->(resource)
DETACH DELETE report
CREATE (submitter)-[reported:REPORTED]->(resource)
SET reported.createdAt = toString(datetime())
SET reported.reasonCategory = 'other'
SET reported.reasonDescription = '!!! Created automatically to ensure database consistency! Creation date is when the database manipulation happened.'
RETURN reported;
" | cypher-shell

View File

@ -21,17 +21,21 @@
"devDependencies": {
"bcryptjs": "^2.4.3",
"codecov": "^3.6.1",
"cross-env": "^6.0.2",
"cross-env": "^6.0.3",
"cypress": "^3.4.1",
"cypress-cucumber-preprocessor": "^1.16.0",
"cypress-cucumber-preprocessor": "^1.16.2",
"cypress-file-upload": "^3.3.4",
"cypress-plugin-retries": "^1.3.0",
"dotenv": "^8.1.0",
"date-fns": "^2.5.0",
"dotenv": "^8.2.0",
"faker": "Marak/faker.js#master",
"graphql-request": "^1.8.2",
"neo4j-driver": "^1.7.6",
"neode": "^0.3.3",
"neode": "^0.3.6",
"npm-run-all": "^4.1.5",
"slug": "^1.1.0"
},
"resolutions": {
"set-value": "^2.0.1"
}
}

View File

@ -1,3 +1,4 @@
MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"
SENTRY_DSN_WEBAPP=
COMMIT=
PUBLIC_REGISTRATION=false

View File

@ -1,4 +1,4 @@
FROM node:12.10.0-alpine as base
FROM node:12.12.0-alpine as base
LABEL Description="Web Frontend of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)"
EXPOSE 3000

View File

@ -1,4 +1,4 @@
FROM node:12.10.0-alpine as build
FROM node:12.12.0-alpine as build
LABEL Description="Web Frontend of the Social Network Human-Connection.org" Vendor="Human-Connection gGmbH" Version="0.0.1" Maintainer="Human-Connection gGmbH (developer@human-connection.org)"
EXPOSE 3000

View File

@ -10,10 +10,15 @@
</ds-card>
</div>
<div v-else :class="{ comment: true, 'disabled-content': comment.deleted || comment.disabled }">
<ds-card :id="anchor">
<ds-card :id="anchor" :class="{ 'comment--target': isTarget }">
<ds-space margin-bottom="small" margin-top="small">
<hc-user :user="author" :date-time="comment.createdAt" />
<!-- Content Menu (can open Modals) -->
<hc-user :user="author" :date-time="comment.createdAt">
<template v-slot:dateTime>
<ds-text v-if="comment.createdAt !== comment.updatedAt">
({{ $t('comment.edited') }})
</ds-text>
</template>
</hc-user>
<client-only>
<content-menu
v-show="!openEditCommentMenu"
@ -38,35 +43,15 @@
/>
</div>
<div v-else>
<content-viewer
v-if="$filters.removeHtml(comment.content).length < 180"
:content="comment.content"
class="padding-left"
/>
<div v-else class="show-more-or-less-div">
<content-viewer
v-if="isCollapsed"
:content="$filters.truncate(comment.content, 180)"
class="padding-left text-align-left"
/>
<span class="show-more-or-less">
<a v-if="isCollapsed" class="padding-left" @click="isCollapsed = !isCollapsed">
{{ $t('comment.show.more') }}
</a>
</span>
</div>
<content-viewer
v-if="!isCollapsed"
:content="comment.content"
class="padding-left text-align-left"
/>
<div class="show-more-or-less-div">
<span class="show-more-or-less">
<a v-if="!isCollapsed" class="padding-left" @click="isCollapsed = !isCollapsed">
{{ $t('comment.show.less') }}
</a>
</span>
</div>
<content-viewer :content="commentContent" class="comment-content" />
<button
v-if="isLongComment"
type="button"
class="collapse-button"
@click="isCollapsed = !isCollapsed"
>
{{ isCollapsed ? $t('comment.show.more') : $t('comment.show.less') }}
</button>
</div>
<ds-space margin-bottom="small" />
</ds-card>
@ -75,6 +60,7 @@
<script>
import { mapGetters } from 'vuex'
import { COMMENT_MAX_UNTRUNCATED_LENGTH, COMMENT_TRUNCATE_TO_LENGTH } from '~/constants/comment'
import HcUser from '~/components/User/User'
import ContentMenu from '~/components/ContentMenu'
import ContentViewer from '~/components/Editor/ContentViewer'
@ -84,9 +70,14 @@ import scrollToAnchor from '~/mixins/scrollToAnchor.js'
export default {
mixins: [scrollToAnchor],
data: function() {
data() {
const anchor = `commentId-${this.comment.id}`
const isTarget = this.routeHash === `#${anchor}`
return {
isCollapsed: true,
anchor,
isTarget,
isCollapsed: !isTarget,
openEditCommentMenu: false,
}
},
@ -97,13 +88,9 @@ export default {
HcCommentForm,
},
props: {
post: { type: Object, default: () => {} },
comment: {
type: Object,
default() {
return {}
},
},
routeHash: { type: String, default: () => '' },
post: { type: Object, default: () => ({}) },
comment: { type: Object, default: () => ({}) },
dateTime: { type: [Date, String], default: null },
},
computed: {
@ -111,8 +98,15 @@ export default {
user: 'auth/user',
isModerator: 'auth/isModerator',
}),
anchor() {
return `commentId-${this.comment.id}`
isLongComment() {
return this.$filters.removeHtml(this.comment.content).length > COMMENT_MAX_UNTRUNCATED_LENGTH
},
commentContent() {
if (this.isLongComment && this.isCollapsed) {
return this.$filters.truncate(this.comment.content, COMMENT_TRUNCATE_TO_LENGTH)
}
return this.comment.content
},
displaysComment() {
return !this.unavailable || this.isModerator
@ -177,26 +171,37 @@ export default {
}
</script>
<style lang="scss" scoped>
.collapse-button {
// TODO: move this to css resets
font-family: inherit;
font-size: inherit;
border: none;
background-color: transparent;
float: right;
padding: 0 16px 16px 16px;
color: $color-primary;
cursor: pointer;
}
.comment-content {
padding-left: 40px;
}
.float-right {
float: right;
}
.padding-left {
padding-left: 40px;
@keyframes highlight {
0% {
border: 1px solid $color-primary;
}
100% {
border: 1px solid transparent;
}
}
.text-align-left {
text-align: left;
}
div.show-more-or-less-div {
text-align: right;
margin-right: 20px;
}
span.show-more-or-less {
display: block;
margin: 0px 20px;
cursor: pointer;
.comment--target {
animation: highlight 4s ease;
}
</style>

View File

@ -22,6 +22,7 @@
:key="comment.id"
:comment="comment"
:post="post"
:routeHash="routeHash"
@deleteComment="updateCommentList"
@updateComment="updateCommentList"
/>
@ -38,6 +39,7 @@ export default {
Comment,
},
props: {
routeHash: { type: String, default: () => '' },
post: { type: Object, default: () => {} },
},
methods: {

View File

@ -32,7 +32,7 @@ describe('ContributionForm.vue', () => {
const postTitle = 'this is a title for a post'
const postTitleTooShort = 'xx'
let postTitleTooLong = ''
for (let i = 0; i < 65; i++) {
for (let i = 0; i < 101; i++) {
postTitleTooLong += 'x'
}
const postContent = 'this is a post'

View File

@ -10,9 +10,17 @@
</hc-teaser-image>
<ds-card>
<ds-space />
<hc-user :user="currentUser" :trunc="35" />
<client-only>
<hc-user :user="currentUser" :trunc="35" />
</client-only>
<ds-space />
<ds-input model="title" class="post-title" placeholder="Title" name="title" autofocus />
<ds-input
model="title"
class="post-title"
:placeholder="$t('contribution.title')"
name="title"
autofocus
/>
<small class="smallTag">{{ form.title.length }}/{{ formSchema.title.max }}</small>
<client-only>
<hc-editor
@ -104,7 +112,7 @@ export default {
categoryIds: [],
},
formSchema: {
title: { required: true, min: 3, max: 64 },
title: { required: true, min: 3, max: 100 },
content: [{ required: true }],
},
id: null,
@ -262,7 +270,7 @@ export default {
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.smallTag {
width: 100%;
position: relative;

View File

@ -1,5 +1,5 @@
<template>
<editor-content :editor="editor" />
<editor-content :editor="editor" :key="content" />
</template>
<script>
@ -31,6 +31,9 @@ export default {
}),
}
},
beforeUpdate() {
this.editor.setContent(this.content)
},
beforeDestroy() {
this.editor.destroy()
},

View File

@ -5,8 +5,12 @@ import helpers from '~/storybook/helpers'
import Vue from 'vue'
const embed = {
image: 'https://i.ytimg.com/vi/ptCcgLM-p8k/maxresdefault_live.jpg',
title: 'Video Titel',
// html: null,
description: 'Video Description',
html:
'<iframe width="480" height="270" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
'<iframe width="auto" height="250" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
}
const plugins = [
@ -114,15 +118,12 @@ storiesOf('Editor', module)
}),
template: `<hc-editor :users="users" :value="content" />`,
}))
.add('Embeds', () => ({
.add('Embeds with iframe', () => ({
components: { HcEditor },
store: helpers.store,
data: () => ({
users,
content: `
<p>
The following link should render a youtube video in addition to the link.
</p>
<a class="embed" href="https://www.youtube.com/watch?v=qkdXAtO40Fo">
<em>https://www.youtube.com/watch?v=qkdXAtO40Fo</em>
</a>
@ -130,3 +131,16 @@ storiesOf('Editor', module)
}),
template: `<hc-editor :users="users" :value="content" />`,
}))
.add('Embeds with plain link', () => ({
components: { HcEditor },
store: helpers.store,
data: () => ({
users,
content: `
<a class="embed" href="https://telegram.org/">
<em>https://telegram.org/</em>
</a>
`,
}),
template: `<hc-editor :users="users" :value="content" />`,
}))

View File

@ -342,4 +342,84 @@ li > p {
margin: 0 0 $space-x-small;
}
}
.ProseMirror[contenteditable='false'] {
.embed-close-button {
display: none;
}
}
.embed-container {
position: relative;
padding: 0;
margin: $space-small auto;
overflow: hidden;
border-radius: $border-radius-base;
border: 1px solid $color-neutral-70;
background-color: $color-neutral-90;
}
.embed-content {
width: 100%;
height: 100%;
h4 {
margin: $space-small 0 0 $space-small;
}
p,
a {
display: block;
margin: 0 0 0 $space-small;
}
}
.embed-preview-image {
width: 100%;
height: auto;
}
.embed-preview-image--clickable {
cursor: pointer;
}
.embed-html {
width: 100%;
iframe {
width: 100%;
}
}
.embed-overlay {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: $space-large;
background-color: $color-neutral-100;
}
.embed-buttons {
button {
margin-right: $space-small;
}
}
.embed-checkbox {
display: flex;
input {
margin-right: $space-small;
}
}
.embed-close-button {
position: absolute;
top: $space-x-small;
right: $space-x-small;
background-color: rgba(250, 249, 250, 0.6);
}
</style>

View File

@ -35,7 +35,7 @@ describe('defaultExtensions', () => {
it('renders mentioning as link', () => {
const editor = createEditor()
const expected =
'<p>This is a post content mentioning <a href="/profile/f0628376-e692-4167-bdb4-d521de5a014f" rel="noopener noreferrer nofollow">@alicia-luettgen</a>.</p>'
'<p>This is a post content mentioning <a href="/profile/f0628376-e692-4167-bdb4-d521de5a014f" rel="noopener noreferrer nofollow" target="_blank">@alicia-luettgen</a>.</p>'
expect(editor.getHTML()).toEqual(expected)
})
})
@ -49,7 +49,7 @@ describe('defaultExtensions', () => {
it('renders hashtag as link', () => {
const editor = createEditor()
const expected =
'<p>This is a post content with a hashtag <a href="/search/hashtag/metoo" rel="noopener noreferrer nofollow">#metoo</a>.</p>'
'<p>This is a post content with a hashtag <a href="/search/hashtag/metoo" rel="noopener noreferrer nofollow" target="_blank">#metoo</a>.</p>'
expect(editor.getHTML()).toEqual(expected)
})
})

View File

@ -1,13 +1,12 @@
import { Node } from 'tiptap'
import pasteRule from '../commands/pasteRule'
import { compileToFunctions } from 'vue-template-compiler'
import Vue from 'vue'
import EmbedComponent from '~/components/Embed/EmbedComponent'
Vue.component(EmbedComponent)
const template = `<component :dataEmbedUrl="dataEmbedUrl" :embedData="embedData" :is="componentType" />`
const template = `
<a class="embed" :href="dataEmbedUrl" rel="noopener noreferrer nofollow" target="_blank">
<div v-if="embedHtml" v-html="embedHtml" />
<em> {{ dataEmbedUrl }} </em>
</a>
`
const compiledTemplate = compileToFunctions(template)
export default class Embed extends Node {
@ -67,16 +66,13 @@ export default class Embed extends Node {
embedData: {},
}),
async created() {
if (!this.options) return {}
this.embedData = await this.options.onEmbed({ url: this.dataEmbedUrl })
if (this.options) {
this.embedData = await this.options.onEmbed({ url: this.dataEmbedUrl })
}
},
computed: {
embedClass() {
return this.embedHtml ? 'embed' : ''
},
embedHtml() {
const { html = '' } = this.embedData
return html
componentType() {
return EmbedComponent
},
dataEmbedUrl: {
get() {

View File

@ -1,31 +1,30 @@
import { shallowMount } from '@vue/test-utils'
import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
import Embed from './Embed'
let Wrapper
let propsData
let Wrapper, propsData, component
const someUrl = 'https://www.youtube.com/watch?v=qkdXAtO40Fo'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
describe('Embed.vue', () => {
beforeEach(() => {
propsData = {}
const component = new Embed()
Wrapper = ({ mocks, propsData }) => {
component = new Embed()
Wrapper = ({ propsData }) => {
return shallowMount(component.view, { propsData })
}
})
it('renders anchor', () => {
propsData = {
node: { attrs: { href: someUrl } },
}
expect(Wrapper({ propsData }).is('a')).toBe(true)
})
describe('given a href', () => {
describe('onEmbed returned embed data', () => {
beforeEach(() => {
propsData.options = {
onEmbed: () => ({
__typename: 'Embed',
type: 'video',
title: 'Baby Loves Cat',
author: 'Merkley Family',
@ -49,9 +48,7 @@ describe('Embed.vue', () => {
propsData.node = { attrs: { href: 'https://www.youtube.com/watch?v=qkdXAtO40Fo' } }
const wrapper = Wrapper({ propsData })
await wrapper.html()
expect(wrapper.find('div iframe').attributes('src')).toEqual(
'https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed',
)
expect(wrapper.contains('embed-component-stub')).toBe(true)
})
})

View File

@ -27,6 +27,7 @@ export default class Link extends TipTapLink {
{
...node.attrs,
rel: 'noopener noreferrer nofollow',
target: '_blank',
},
0,
],

View File

@ -10,7 +10,6 @@ export default class EventHandler extends Extension {
new Plugin({
props: {
transformPastedText(text) {
// console.log('#### transformPastedText', text)
return text.trim()
},
transformPastedHTML(html) {
@ -33,7 +32,6 @@ export default class EventHandler extends Extension {
.replace(/<p>(\s*<br ?\/?>\s*)+/gim, '<p>')
// remove additional linebreaks when last child inside p tags
.replace(/(\s*<br ?\/?>\s*)+<\/p>/gim, '</p>')
// console.log('#### transformPastedHTML', html)
return html
},
// transformPasted(slice) {

View File

@ -0,0 +1,206 @@
import { mount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Styleguide from '@human-connection/styleguide'
import EmbedComponent from './EmbedComponent'
let wrapper, propsData, getters, mocks
const someUrl = 'https://www.youtube.com/watch?v=qkdXAtO40Fo'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Styleguide)
describe('EmbedComponent.vue', () => {
const Wrapper = () => {
const store = new Vuex.Store({
getters,
})
return mount(EmbedComponent, { propsData, localVue, store, mocks })
}
beforeEach(() => {
mocks = {
$t: a => a,
$apollo: {
mutate: jest
.fn()
.mockResolvedValueOnce({ data: { UpdateUser: { allowEmbedIframes: true } } }),
},
$toast: {
success: jest.fn(),
error: jest.fn(),
},
}
propsData = {}
getters = {
'auth/user': () => {
return { id: 'u5', allowEmbedIframes: false }
},
}
})
describe('given a href only for a link ', () => {
beforeEach(() => {
propsData.embedData = {
__typename: 'Embed',
type: 'link',
title: '👻 ✉️ Bruno... le ciel sur répondeur ! 🔮 🧠 - Clément FREZE',
author: null,
publisher: 'PeerTube.social',
date: null,
description:
'Salut tout le monde ! Aujourdhui, une vidéo sur le scepticisme, nous allons parler médiumnité avec le cas de Bruno CHARVET : « Bruno, un nouveau message ». Merci de rester respectueux dans les commentaires : SOURCES : Les sources des vi...',
url: 'https://peertube.social/videos/watch/f3cb1945-a8f7-481f-a465-946c6f884e50',
image: 'https://peertube.social/static/thumbnails/f3cb1945-a8f7-481f-a465-946c6f884e50.jpg',
audio: null,
video: null,
lang: 'fr',
sources: ['resource', 'oembed'],
html: null,
}
wrapper = Wrapper()
})
it('shows the title', () => {
expect(wrapper.find('h4').text()).toBe(
'👻 ✉️ Bruno... le ciel sur répondeur ! 🔮 🧠 - Clément FREZE',
)
})
it('shows the description', () => {
expect(wrapper.find('.embed-content p').text()).toBe(
'Salut tout le monde ! Aujourdhui, une vidéo sur le scepticisme, nous allons parler médiumnité avec le cas de Bruno CHARVET : « Bruno, un nouveau message ». Merci de rester respectueux dans les commentaires : SOURCES : Les sources des vi...',
)
})
it('shows preview Images for link', () => {
expect(wrapper.find('.embed-preview-image').exists()).toBe(true)
})
})
describe('given a href with embed html', () => {
describe('onEmbed returned title and description', () => {
beforeEach(() => {
propsData.embedData = {
__typename: 'Embed',
title: 'Baby Loves Cat',
description:
'Shes incapable of controlling her limbs when her kitty is around. The obsession grows every day. Ps. Thats a sleep sack shes in. Not a starfish outfit. Al...',
}
wrapper = Wrapper()
})
it('show the title', () => {
expect(wrapper.find('h4').text()).toBe('Baby Loves Cat')
})
it('show the desciption', () => {
expect(wrapper.find('.embed-content p').text()).toBe(
'Shes incapable of controlling her limbs when her kitty is around. The obsession grows every day. Ps. Thats a sleep sack shes in. Not a starfish outfit. Al...',
)
})
describe('onEmbed returned embed data with html', () => {
beforeEach(() => {
propsData.embedData = {
__typename: 'Embed',
type: 'video',
title: 'Baby Loves Cat',
author: 'Merkley Family',
publisher: 'YouTube',
date: '2015-08-16T00:00:00.000Z',
description:
'Shes incapable of controlling her limbs when her kitty is around. The obsession grows every day. Ps. Thats a sleep sack shes in. Not a starfish outfit. Al...',
url: someUrl,
image: 'https://i.ytimg.com/vi/qkdXAtO40Fo/maxresdefault.jpg',
audio: null,
video: null,
lang: 'de',
sources: ['resource', 'oembed'],
html:
'<iframe width="480" height="270" src="https://www.youtube.com/embed/qkdXAtO40Fo?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>',
}
wrapper = Wrapper()
})
it('shows a simple link when a user closes the embed preview', () => {
wrapper.find('.embed-close-button').trigger('click')
expect(wrapper.vm.showLinkOnly).toBe(true)
})
it('opens the data privacy overlay when a user clicks on the preview image', () => {
wrapper.find('.embed-preview-image--clickable').trigger('click')
expect(wrapper.vm.showOverlay).toBe(true)
})
describe('shows iframe', () => {
beforeEach(() => {
wrapper.setData({ showOverlay: true })
})
it('when user agress', () => {
wrapper.find('.ds-button-primary').trigger('click')
expect(wrapper.vm.showEmbed).toBe(true)
})
it('does not show iframe when user clicks to cancel', () => {
wrapper.find('.ds-button-ghost').trigger('click')
expect(wrapper.vm.showEmbed).toBe(false)
})
describe("doesn't set permanently", () => {
beforeEach(() => {
wrapper.find('.ds-button-primary').trigger('click')
})
it("if user doesn't give consent", () => {
expect(wrapper.vm.checkedAlwaysAllowEmbeds).toBe(false)
})
it("doesn't update the user's profile", () => {
expect(mocks.$apollo.mutate).not.toHaveBeenCalled()
})
})
describe('sets permanently', () => {
beforeEach(() => {
wrapper.find('input[type=checkbox]').trigger('click')
wrapper.find('.ds-button-primary').trigger('click')
})
it('changes setting permanetly when user requests', () => {
expect(wrapper.vm.checkedAlwaysAllowEmbeds).toBe(true)
})
it("updates the user's profile", () => {
expect(mocks.$apollo.mutate).toHaveBeenCalledTimes(1)
})
})
})
describe('immediately shows', () => {
beforeEach(() => {
getters = {
'auth/user': () => {
return { id: 'u5', allowEmbedIframes: true }
},
}
wrapper = Wrapper()
})
it('sets showEmbed to true', () => {
expect(wrapper.vm.showEmbed).toBe(true)
})
it('the iframe returned from oEmbed', () => {
expect(wrapper.find('iframe').html()).toEqual(propsData.embedData.html)
})
it('does not display image to click', () => {
expect(wrapper.find('.embed-preview-image--clickable').exists()).toBe(false)
})
})
})
})
})
})

View File

@ -0,0 +1,153 @@
<template>
<a v-if="showLinkOnly" :href="dataEmbedUrl" rel="noopener noreferrer nofollow" target="_blank">
{{ dataEmbedUrl }}
</a>
<ds-container v-else width="small" class="embed-container">
<section class="embed-content">
<div v-if="showEmbed" v-html="embedHtml" class="embed-html" />
<template v-else>
<img
v-if="embedHtml && embedImage"
:src="embedImage"
class="embed-preview-image embed-preview-image--clickable"
@click.prevent="openOverlay()"
/>
<img v-else-if="embedImage" :src="embedImage" class="embed-preview-image" />
</template>
<h4 v-if="embedTitle">{{ embedTitle }}</h4>
<p v-if="embedDescription">{{ embedDescription }}</p>
<a class="embed" :href="dataEmbedUrl" rel="noopener noreferrer nofollow" target="_blank">
{{ dataEmbedUrl }}
</a>
</section>
<aside v-if="showOverlay" class="embed-overlay">
<h3>{{ $t('editor.embed.data_privacy_warning') }}</h3>
<ds-text>{{ $t('editor.embed.data_privacy_info') }} {{ embedPublisher }}</ds-text>
<div class="embed-buttons">
<ds-button primary @click.prevent="allowEmbed()">
{{ $t('editor.embed.play_now') }}
</ds-button>
<ds-button ghost @click.prevent="closeOverlay()">{{ $t('actions.cancel') }}</ds-button>
</div>
<label class="embed-checkbox">
<input type="checkbox" v-model="checkedAlwaysAllowEmbeds" />
<span>{{ $t('editor.embed.always_allow') }}</span>
</label>
</aside>
<ds-button
icon="close"
ghost
size="small"
class="embed-close-button"
@click.prevent="removeEmbed()"
/>
</ds-container>
</template>
<script>
import { mapGetters, mapMutations } from 'vuex'
import { allowEmbedIframesMutation } from '~/graphql/User.js'
export default {
name: 'embed-component',
props: {
dataEmbedUrl: {
type: String,
default: '',
},
embedData: {
type: Object,
default() {
return {}
},
},
},
data() {
return {
checkedAlwaysAllowEmbeds: false,
showEmbed: false,
showOverlay: false,
showLinkOnly: false,
}
},
created() {
if (this.currentUser.allowEmbedIframes) {
this.showEmbed = this.currentUser.allowEmbedIframes
this.checkedAlwaysAllowEmbeds = this.currentUser.allowEmbedIframes
}
},
computed: {
...mapGetters({
currentUser: 'auth/user',
}),
embedHtml() {
const { html = '' } = this.embedData
return html
},
embedImage() {
const { image = '' } = this.embedData
return image
},
embedPublisher() {
const { publisher = '' } = this.embedData
return publisher
},
embedTitle() {
const { title = '' } = this.embedData
return title
},
embedAuthor() {
const { author = '' } = this.embedData
return author
},
embedDescription() {
const { description = '' } = this.embedData
return description
},
},
methods: {
...mapMutations({
setCurrentUser: 'auth/SET_USER',
}),
openOverlay() {
this.showOverlay = true
},
closeOverlay() {
this.showOverlay = false
},
allowEmbed() {
this.showEmbed = true
this.closeOverlay()
if (this.checkedAlwaysAllowEmbeds !== this.currentUser.allowEmbedIframes) {
this.updateEmbedSettings(this.checkedAlwaysAllowEmbeds)
}
},
removeEmbed() {
this.showLinkOnly = true
},
async updateEmbedSettings(allowEmbedIframes) {
try {
await this.$apollo.mutate({
mutation: allowEmbedIframesMutation(),
variables: {
id: this.currentUser.id,
allowEmbedIframes,
},
update: (store, { data: { UpdateUser } }) => {
const { allowEmbedIframes } = UpdateUser
this.setCurrentUser({
...this.currentUser,
allowEmbedIframes,
})
},
})
this.$toast.success(this.$t('contribution.success'))
this.showEmbed = this.currentUser.allowEmbedIframes
} catch (err) {
this.$toast.error(err.message)
}
},
},
}
</script>

View File

@ -1,12 +1,12 @@
import { mount, createLocalVue } from '@vue/test-utils'
import VerifyNonce from './VerifyNonce.vue'
import EnterNonce from './EnterNonce.vue'
import Styleguide from '@human-connection/styleguide'
const localVue = createLocalVue()
localVue.use(Styleguide)
describe('VerifyNonce ', () => {
describe('EnterNonce ', () => {
let wrapper
let Wrapper
let mocks
@ -25,28 +25,28 @@ describe('VerifyNonce ', () => {
beforeEach(jest.useFakeTimers)
Wrapper = () => {
return mount(VerifyNonce, {
return mount(EnterNonce, {
mocks,
localVue,
propsData,
})
}
it('renders a verify nonce form', () => {
it('renders an enter nonce form', () => {
wrapper = Wrapper()
expect(wrapper.find('.verify-nonce').exists()).toBe(true)
expect(wrapper.find('form').exists()).toBe(true)
})
describe('after verification nonce given', () => {
describe('after nonce entered', () => {
beforeEach(() => {
wrapper = Wrapper()
wrapper.find('input#nonce').setValue('123456')
wrapper.find('form').trigger('submit')
})
it('emits `verification`', () => {
it('emits `nonceEntered`', () => {
const expected = [[{ nonce: '123456', email: 'mail@example.org' }]]
expect(wrapper.emitted('verification')).toEqual(expected)
expect(wrapper.emitted('nonceEntered')).toEqual(expected)
})
})
})

View File

@ -0,0 +1,66 @@
<template>
<ds-space margin-top="large" margin-bottom="xxx-small">
<ds-form
v-model="formData"
:schema="formSchema"
@submit="handleSubmitVerify"
@input="handleInput"
@input-valid="handleInputValid"
>
<ds-input
:placeholder="$t('components.enter-nonce.form.nonce')"
model="nonce"
name="nonce"
id="nonce"
icon="question-circle"
/>
<ds-space margin-botton="large">
<ds-text>
{{ $t('components.enter-nonce.form.description') }}
</ds-text>
</ds-space>
<ds-button :disabled="disabled" primary fullwidth name="submit" type="submit">
{{ $t('components.enter-nonce.form.next') }}
</ds-button>
</ds-form>
<slot></slot>
</ds-space>
</template>
<script>
export default {
props: {
email: { type: String, required: true },
},
data() {
return {
formData: {
nonce: '',
},
formSchema: {
nonce: {
type: 'string',
min: 6,
max: 6,
required: true,
message: this.$t('components.enter-nonce.form.validations.length'),
},
},
disabled: true,
}
},
methods: {
async handleInput() {
this.disabled = true
},
async handleInputValid() {
this.disabled = false
},
handleSubmitVerify() {
const { nonce } = this.formData
const email = this.email
this.$emit('nonceEntered', { email, nonce })
},
},
}
</script>

View File

@ -36,6 +36,7 @@
import Dropdown from '~/components/Dropdown'
import find from 'lodash/find'
import orderBy from 'lodash/orderBy'
import locales from '~/locales'
export default {
components: {
@ -47,7 +48,7 @@ export default {
},
data() {
return {
locales: orderBy(process.env.locales, 'name'),
locales: orderBy(locales, 'name'),
}
},
computed: {

View File

@ -20,7 +20,7 @@ describe('ReportModal.vue', () => {
id: 'c43',
}
mocks = {
$t: jest.fn(),
$t: jest.fn(a => a),
$filters: {
truncate: a => a,
},
@ -29,7 +29,9 @@ describe('ReportModal.vue', () => {
error: () => {},
},
$apollo: {
mutate: jest.fn().mockResolvedValue(),
mutate: jest.fn().mockResolvedValue({
data: {},
}),
},
}
})
@ -154,6 +156,7 @@ describe('ReportModal.vue', () => {
describe('click confirm button', () => {
beforeEach(() => {
wrapper.find('.ds-radio-option-label').trigger('click')
wrapper.find('button.confirm').trigger('click')
})

View File

@ -8,14 +8,36 @@
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-html="message" />
<template slot="footer">
<ds-button class="cancel" icon="close" @click="cancel">{{ $t('report.cancel') }}</ds-button>
<ds-radio
v-model="form.reasonCategory"
:schema="formSchema.reasonCategory"
:label="$t('report.reason.category.label')"
:options="form.reasonCategoryOptions"
labelProp="label"
/>
<ds-input
class="reason-description"
v-model="form.reasonDescription"
:schema="formSchema.reasonDescription"
:label="$t('report.reason.description.label')"
:placeholder="$t('report.reason.description.placeholder')"
type="textarea"
rows="5"
/>
<small class="smallTag">
{{ form.reasonDescription.length }}/{{ formSchema.reasonDescription.max }}
</small>
<ds-space />
<template #footer>
<ds-button class="cancel" icon="close" @click="cancel">
{{ $t('report.cancel') }}
</ds-button>
<ds-button
danger
class="confirm"
icon="exclamation-circle"
:disabled="!form.reasonCategory"
:loading="loading"
@click="confirm"
>
@ -26,8 +48,10 @@
</template>
<script>
import gql from 'graphql-tag'
import { SweetalertIcon } from 'vue-sweetalert-icons'
import { reportMutation } from '~/graphql/Moderation.js'
import { valuesReasonCategoryOptions } from '~/constants/modals.js'
import validReport from '~/components/utils/ReportModal'
export default {
name: 'ReportModal',
@ -44,8 +68,21 @@ export default {
isOpen: true,
success: false,
loading: false,
form: {
reasonCategory: null,
reasonCategoryOptions: [],
reasonDescription: '',
},
}
},
created() {
this.form.reasonCategoryOptions = valuesReasonCategoryOptions.map(reasonCategory => {
return {
label: this.$t('report.reason.category.options.' + reasonCategory),
value: reasonCategory,
}
})
},
computed: {
title() {
return this.$t(`report.${this.type}.title`)
@ -54,6 +91,12 @@ export default {
const name = this.$filters.truncate(this.name, 30)
return this.$t(`report.${this.type}.message`, { name })
},
formSchema() {
const validReportSchema = validReport({ translate: this.$t })
return {
...validReportSchema.formSchema,
}
},
},
methods: {
async cancel() {
@ -65,52 +108,74 @@ export default {
}, 1000)
},
async confirm() {
const { reasonCategory, reasonDescription } = this.form
this.loading = true
try {
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
// await this.modalData.buttons.confirm.callback()
await this.$apollo.mutate({
mutation: gql`
mutation($id: ID!) {
report(id: $id) {
id
}
}
`,
variables: { id: this.id },
// TODO: Use the "modalData" structure introduced in "ConfirmModal" and refactor this here. Be aware that all the Jest tests have to be refactored as well !!!
// await this.modalData.buttons.confirm.callback()
this.$apollo
.mutate({
mutation: reportMutation(),
variables: {
resourceId: this.id,
reasonCategory: reasonCategory.value,
reasonDescription,
},
})
this.success = true
this.$toast.success(this.$t('report.success'))
setTimeout(() => {
this.isOpen = false
.then(({ _data }) => {
this.success = true
this.$toast.success(this.$t('report.success'))
setTimeout(() => {
this.success = false
this.$emit('close')
}, 500)
}, 1500)
} catch (err) {
this.$emit('close')
this.success = false
switch (err.message) {
case 'GraphQL error: User':
this.$toast.error(this.$t('report.user.error'))
break
case 'GraphQL error: Post':
this.$toast.error(this.$t('report.contribution.error'))
break
case 'GraphQL error: Comment':
this.$toast.error(this.$t('report.comment.error'))
break
}
} finally {
this.loading = false
}
this.isOpen = false
setTimeout(() => {
this.success = false
this.$emit('close')
}, 500)
}, 1500)
this.loading = false
})
.catch(err => {
this.$emit('close')
this.success = false
switch (err.message) {
case 'GraphQL error: User':
this.$toast.error(this.$t('report.user.error'))
break
case 'GraphQL error: Post':
this.$toast.error(this.$t('report.contribution.error'))
break
case 'GraphQL error: Comment':
this.$toast.error(this.$t('report.comment.error'))
break
default:
this.$toast.error(err.message)
}
this.loading = false
})
},
},
}
</script>
<style lang="scss">
.ds-modal {
max-width: 600px !important;
}
.ds-radio-option:not(.ds-button) {
width: 100% !important;
}
.ds-radio-option-label {
margin: 5px 20px 5px 5px !important;
width: 100% !important;
}
.reason-description {
margin-top: $space-x-small !important;
margin-bottom: $space-xx-small !important;
}
.smallTag {
width: 100%;
position: relative;
left: 90%;
}
.hc-modal-success {
pointer-events: none;
position: absolute;

View File

@ -39,7 +39,7 @@ describe('ChangePassword ', () => {
})
}
describe('given email and verification nonce', () => {
describe('given email and nonce', () => {
beforeEach(() => {
propsData.email = 'mail@example.org'
propsData.nonce = '123456'
@ -66,7 +66,7 @@ describe('ChangePassword ', () => {
describe('password reset successful', () => {
it('displays success message', () => {
const expected = 'verify-nonce.form.change-password.success'
const expected = 'components.password-reset.change-password.success'
expect(mocks.$t).toHaveBeenCalledWith(expected)
})

View File

@ -1,54 +1,62 @@
<template>
<ds-card class="verify-nonce">
<ds-space margin="large">
<ds-form
v-if="!changePasswordResult"
v-model="formData"
:schema="formSchema"
@submit="handleSubmitPassword"
class="change-password"
>
<template slot-scope="{ errors }">
<ds-input
id="password"
model="password"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password')"
/>
<ds-input
id="passwordConfirmation"
model="passwordConfirmation"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password-confirm')"
/>
<password-strength :password="formData.password" />
<ds-space margin-top="base">
<ds-button :loading="$apollo.loading" :disabled="errors" primary>
{{ $t('settings.security.change-password.button') }}
</ds-button>
</ds-space>
</template>
</ds-form>
<ds-text v-else>
<template v-if="changePasswordResult === 'success'">
<ds-space margin-top="base" margin-bottom="xxx-small">
<ds-form
v-if="!changePasswordResult"
v-model="formData"
:schema="formSchema"
@submit="handleSubmitPassword"
class="change-password"
>
<template slot-scope="{ errors }">
<ds-input
id="password"
model="password"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password')"
/>
<ds-input
id="passwordConfirmation"
model="passwordConfirmation"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password-confirm')"
/>
<password-strength :password="formData.password" />
<ds-space margin-top="base" margin-bottom="xxx-small">
<ds-button :loading="$apollo.loading" :disabled="errors" primary>
{{ $t('settings.security.change-password.button') }}
</ds-button>
</ds-space>
</template>
</ds-form>
<ds-space v-else>
<template v-if="changePasswordResult === 'success'">
<transition name="ds-transition-fade">
<sweetalert-icon icon="success" />
<ds-text>
{{ $t(`verify-nonce.form.change-password.success`) }}
</ds-text>
</template>
<template v-else>
</transition>
<ds-text>
{{ $t('components.password-reset.change-password.success') }}
</ds-text>
</template>
<template v-else>
<transition name="ds-transition-fade">
<sweetalert-icon icon="error" />
<ds-text align="left">
{{ $t(`verify-nonce.form.change-password.error`) }}
{{ $t('verify-nonce.form.change-password.help') }}
</ds-text>
<a href="mailto:support@human-connection.org">support@human-connection.org</a>
</template>
</ds-text>
</transition>
<ds-text>
<p>
{{ $t(`components.password-reset.change-password.error`) }}
</p>
<p>
{{ $t('components.password-reset.change-password.help') }}
<br />
<a href="mailto:support@human-connection.org">support@human-connection.org</a>
</p>
</ds-text>
</template>
<slot></slot>
</ds-space>
</ds-card>
</ds-space>
</template>
<script>

View File

@ -18,7 +18,7 @@ describe('Request', () => {
success: jest.fn(),
error: jest.fn(),
},
$t: jest.fn(),
$t: jest.fn(t => t),
$apollo: {
loading: false,
mutate: jest.fn().mockResolvedValue({ data: { reqestPasswordReset: true } }),
@ -45,7 +45,7 @@ describe('Request', () => {
it('renders a password reset form', () => {
wrapper = Wrapper()
expect(wrapper.find('.password-reset').exists()).toBe(true)
expect(wrapper.find('form').exists()).toBe(true)
})
describe('submit', () => {
@ -69,7 +69,10 @@ describe('Request', () => {
})
it('displays a message that a password email was requested', () => {
const expected = ['password-reset.form.submitted', { email: 'mail@example.org' }]
const expected = [
'components.password-reset.request.form.submitted',
{ email: 'mail@example.org' },
]
expect(mocks.$t).toHaveBeenCalledWith(...expected)
})

View File

@ -1,77 +1,55 @@
<template>
<ds-card class="password-reset">
<ds-flex gutter="small">
<ds-flex-item :width="{ base: '100%', sm: '50%' }" centered>
<client-only>
<locale-switch class="login-locale-switch" offset="5" />
</client-only>
<ds-space margin-top="small" margin-bottom="xxx-small" centered>
<img class="login-image" alt="Human Connection" src="/img/sign-up/humanconnection.svg" />
</ds-space>
</ds-flex-item>
<ds-flex-item :width="{ base: '100%', sm: '50%' }" centered>
<ds-space margin="small">
<ds-text size="small" align="left">{{ $t('login.copy') }}</ds-text>
</ds-space>
<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 align="left">{{ $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" align="left" />
</div>
<ds-space margin-bottom="small" />
<div>
<nuxt-link to="/login">{{ $t('site.login') }}</nuxt-link>
</div>
</ds-flex-item>
</ds-flex>
<ds-space margin="x-small"></ds-space>
</ds-card>
<ds-form
v-if="!submitted"
@input="handleInput"
@input-valid="handleInputValid"
v-model="formData"
:schema="formSchema"
@submit="handleSubmit"
>
<ds-space margin="small">
<ds-input
:placeholder="$t('login.email')"
type="email"
id="email"
model="email"
name="email"
icon="envelope"
/>
</ds-space>
<ds-space margin-botton="large">
<ds-text align="left">{{ $t('components.password-reset.request.form.description') }}</ds-text>
</ds-space>
<ds-button
:disabled="disabled"
:loading="$apollo.loading"
primary
fullwidth
name="submit"
type="submit"
icon="envelope"
>
{{ $t('components.password-reset.request.form.submit') }}
</ds-button>
<slot></slot>
</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" align="left" />
</div>
</template>
<script>
import gql from 'graphql-tag'
import { SweetalertIcon } from 'vue-sweetalert-icons'
import LocaleSwitch from '../LocaleSwitch/LocaleSwitch'
export default {
components: {
SweetalertIcon,
LocaleSwitch,
},
data() {
return {
@ -92,7 +70,7 @@ export default {
computed: {
submitMessage() {
const { email } = this.formData
return this.$t('password-reset.form.submitted', { email })
return this.$t('components.password-reset.request.form.submitted', { email })
},
},
methods: {
@ -124,17 +102,3 @@ export default {
},
}
</script>
<style>
.login-image {
width: 90%;
max-width: 200px;
}
.password-reset {
position: relative;
}
.login-locale-switch {
position: absolute;
top: 1em;
left: 1em;
}
</style>

View File

@ -1,67 +0,0 @@
<template>
<ds-card class="verify-nonce">
<ds-space margin="large">
<ds-form
v-model="formData"
:schema="formSchema"
@submit="handleSubmitVerify"
@input="handleInput"
@input-valid="handleInputValid"
>
<ds-input
:placeholder="$t('verify-nonce.form.nonce')"
model="nonce"
name="nonce"
id="nonce"
icon="question-circle"
/>
<ds-space margin-botton="large">
<ds-text>
{{ $t('verify-nonce.form.description') }}
</ds-text>
</ds-space>
<ds-button :disabled="disabled" primary fullwidth name="submit" type="submit">
{{ $t('verify-nonce.form.next') }}
</ds-button>
</ds-form>
</ds-space>
</ds-card>
</template>
<script>
export default {
props: {
email: { type: String, required: true },
},
data() {
return {
formData: {
nonce: '',
},
formSchema: {
nonce: {
type: 'string',
min: 6,
max: 6,
required: true,
message: this.$t('common.validations.verification-nonce'),
},
},
disabled: true,
}
},
methods: {
async handleInput() {
this.disabled = true
},
async handleInputValid() {
this.disabled = false
},
handleSubmitVerify() {
const { nonce } = this.formData
const email = this.email
this.$emit('verification', { email, nonce })
},
},
}
</script>

View File

@ -131,10 +131,11 @@ export default {
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.ds-card-image img {
width: 100%;
max-height: 300px;
max-height: 2000px;
object-fit: contain;
-o-object-fit: cover;
object-fit: cover;
-o-object-position: center;

View File

@ -8,6 +8,7 @@ const localVue = createLocalVue()
localVue.use(Styleguide)
config.stubs['sweetalert-icon'] = '<span><slot /></span>'
config.stubs['client-only'] = '<span><slot /></span>'
config.stubs['nuxt-link'] = '<span><slot /></span>'
describe('CreateUserAccount', () => {
let wrapper, Wrapper, mocks, propsData, stubs
@ -60,7 +61,9 @@ describe('CreateUserAccount', () => {
wrapper.find('input#password').setValue('hellopassword')
wrapper.find('textarea#about').setValue('Hello I am the `about` attribute')
wrapper.find('input#passwordConfirmation').setValue('hellopassword')
wrapper.find('input#checkbox').setChecked()
wrapper.find('input#checkbox0').setChecked()
wrapper.find('input#checkbox1').setChecked()
wrapper.find('input#checkbox2').setChecked()
await wrapper.find('form').trigger('submit')
await wrapper.html()
}
@ -102,7 +105,9 @@ describe('CreateUserAccount', () => {
it('displays success', async () => {
await action()
expect(mocks.$t).toHaveBeenCalledWith('registration.create-user-account.success')
expect(mocks.$t).toHaveBeenCalledWith(
'components.registration.create-user-account.success',
)
})
describe('after timeout', () => {
@ -130,7 +135,9 @@ describe('CreateUserAccount', () => {
it('displays form errors', async () => {
await action()
expect(wrapper.find('.backendErrors').text()).toContain('Invalid nonce')
expect(mocks.$t).toHaveBeenCalledWith(
'components.registration.create-user-account.error',
)
})
})
})

View File

@ -1,117 +1,123 @@
<template>
<ds-container width="small">
<ds-card v-if="success" class="success">
<ds-space>
<sweetalert-icon icon="success" />
<ds-text align="center" bold color="success">
{{ $t('registration.create-user-account.success') }}
</ds-text>
</ds-space>
</ds-card>
<ds-card v-else class="create-account-card">
<client-only>
<locale-switch />
</client-only>
<ds-space centered>
<img
class="create-account-image"
alt="Create an account for Human Connection"
src="/img/sign-up/nicetomeetyou.svg"
<div v-if="response === 'success'">
<transition name="ds-transition-fade">
<sweetalert-icon icon="success" />
</transition>
<ds-text align="center" bold color="success">
{{ $t('components.registration.create-user-account.success') }}
</ds-text>
</div>
<div v-else-if="response === 'error'">
<transition name="ds-transition-fade">
<sweetalert-icon icon="error" />
</transition>
<ds-text align="center" bold color="danger">
{{ $t('components.registration.create-user-account.error') }}
</ds-text>
<ds-text align="center">
{{ $t('components.registration.create-user-account.help') }}
<a :href="supportEmail.href">{{ supportEmail.label }}</a>
</ds-text>
<ds-space centered>
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
</ds-space>
</div>
<div v-else class="create-account-card">
<ds-space margin-top="large">
<ds-heading size="h3">
{{ $t('components.registration.create-user-account.title') }}
</ds-heading>
</ds-space>
<ds-form class="create-user-account" v-model="formData" :schema="formSchema" @submit="submit">
<template v-slot="{ errors }">
<ds-input
id="name"
model="name"
icon="user"
:label="$t('settings.data.labelName')"
:placeholder="$t('settings.data.namePlaceholder')"
/>
</ds-space>
<ds-space>
<ds-heading size="h3">
{{ $t('registration.create-user-account.title') }}
</ds-heading>
</ds-space>
<ds-input
id="about"
model="about"
type="textarea"
rows="3"
:label="$t('settings.data.labelBio')"
:placeholder="$t('settings.data.labelBio')"
/>
<ds-input
id="password"
model="password"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password')"
/>
<ds-input
id="passwordConfirmation"
model="passwordConfirmation"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password-confirm')"
/>
<password-strength :password="formData.password" />
<ds-form class="create-user-account" v-model="formData" :schema="formSchema" @submit="submit">
<template v-slot="{ errors }">
<ds-flex gutter="base">
<ds-flex-item width="100%">
<ds-input
id="name"
model="name"
icon="user"
:label="$t('settings.data.labelName')"
:placeholder="$t('settings.data.namePlaceholder')"
/>
<ds-input
id="about"
model="about"
type="textarea"
rows="3"
:label="$t('settings.data.labelBio')"
:placeholder="$t('settings.data.labelBio')"
/>
<ds-input
id="password"
model="password"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password')"
/>
<ds-input
id="passwordConfirmation"
model="passwordConfirmation"
type="password"
autocomplete="off"
:label="$t('settings.security.change-password.label-new-password-confirm')"
/>
<password-strength :password="formData.password" />
<ds-text>
<input
id="checkbox"
type="checkbox"
v-model="termsAndConditionsConfirmed"
:checked="termsAndConditionsConfirmed"
/>
<label
for="checkbox"
v-html="$t('termsAndConditions.termsAndConditionsConfirmed')"
></label>
</ds-text>
</ds-flex-item>
<ds-flex-item width="100%">
<ds-space class="backendErrors" v-if="backendErrors">
<ds-text align="center" bold color="danger">{{ backendErrors.message }}</ds-text>
</ds-space>
<ds-button
style="float: right;"
icon="check"
type="submit"
:loading="$apollo.loading"
:disabled="errors || !termsAndConditionsConfirmed"
primary
>
{{ $t('actions.save') }}
</ds-button>
</ds-flex-item>
</ds-flex>
</template>
</ds-form>
</ds-card>
</ds-container>
<ds-text>
<input
id="checkbox0"
type="checkbox"
v-model="termsAndConditionsConfirmed"
:checked="termsAndConditionsConfirmed"
/>
<label
for="checkbox"
v-html="$t('termsAndConditions.termsAndConditionsConfirmed')"
></label>
</ds-text>
<p>
<label>
<input id="checkbox1" type="checkbox" v-model="dataPrivacy" :checked="dataPrivacy" />
<span v-html="$t('components.registration.signup.form.data-privacy')"></span>
</label>
</p>
<p>
<label>
<input id="checkbox2" type="checkbox" v-model="minimumAge" :checked="minimumAge" />
<span v-html="$t('components.registration.signup.form.minimum-age')"></span>
</label>
</p>
<ds-button
style="float: right;"
icon="check"
type="submit"
:loading="$apollo.loading"
:disabled="errors || !termsAndConditionsConfirmed || !dataPrivacy || !minimumAge"
primary
>
{{ $t('actions.save') }}
</ds-button>
</template>
</ds-form>
</div>
</template>
<script>
import PasswordStrength from '../Password/Strength'
import { SweetalertIcon } from 'vue-sweetalert-icons'
import PasswordForm from '~/components/utils/PasswordFormHelper'
import { SUPPORT_EMAIL } from '~/constants/emails.js'
import { VERSION } from '~/constants/terms-and-conditions-version.js'
import { SignupVerificationMutation } from '~/graphql/Registration.js'
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
export default {
components: {
PasswordStrength,
SweetalertIcon,
LocaleSwitch,
},
data() {
const passwordForm = PasswordForm({ translate: this.$t })
return {
supportEmail: SUPPORT_EMAIL,
formData: {
name: '',
about: '',
@ -130,12 +136,13 @@ export default {
...passwordForm.formSchema,
},
disabled: true,
success: null,
backendErrors: null,
response: null,
// TODO: Our styleguide does not support checkmarks.
// Integrate termsAndConditionsConfirmed into `this.formData` once we
// have checkmarks available.
termsAndConditionsConfirmed: false,
dataPrivacy: false,
minimumAge: false,
}
},
props: {
@ -152,7 +159,7 @@ export default {
mutation: SignupVerificationMutation,
variables: { name, password, about, email, nonce, termsAndConditionsAgreedVersion },
})
this.success = true
this.response = 'success'
setTimeout(() => {
this.$emit('userCreated', {
email,
@ -160,7 +167,7 @@ export default {
})
}, 3000)
} catch (err) {
this.backendErrors = err
this.response = 'error'
}
},
},

View File

@ -42,7 +42,7 @@ describe('Signup', () => {
describe('without invitation code', () => {
it('renders signup form', () => {
wrapper = Wrapper()
expect(wrapper.find('.signup').exists()).toBe(true)
expect(wrapper.find('form').exists()).toBe(true)
})
describe('submit', () => {
@ -69,15 +69,18 @@ describe('Signup', () => {
})
it('displays a message that a mail for email verification was sent', () => {
const expected = ['registration.signup.form.success', { email: 'mail@example.org' }]
const expected = [
'components.registration.signup.form.success',
{ 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' }]])
it('emits `submit`', () => {
expect(wrapper.emitted('submit')).toEqual([[{ email: 'mail@example.org' }]])
})
})
})
@ -121,7 +124,9 @@ describe('Signup', () => {
it('explains the error', async () => {
await action()
expect(mocks.$t).toHaveBeenCalledWith('registration.signup.form.errors.email-exists')
expect(mocks.$t).toHaveBeenCalledWith(
'components.registration.signup.form.errors.email-exists',
)
})
})
@ -137,7 +142,7 @@ describe('Signup', () => {
it('explains the error', async () => {
await action()
expect(mocks.$t).toHaveBeenCalledWith(
'registration.signup.form.errors.invalid-invitation-token',
'components.registration.signup.form.errors.invalid-invitation-token',
)
})
})

View File

@ -1,59 +1,63 @@
<template>
<ds-card class="signup">
<ds-space margin="large">
<ds-form
v-if="!success && !error"
@input="handleInput"
@input-valid="handleInputValid"
v-model="formData"
:schema="formSchema"
@submit="handleSubmit"
<ds-space v-if="!success && !error" margin="large">
<ds-form
@input="handleInput"
@input-valid="handleInputValid"
v-model="formData"
:schema="formSchema"
@submit="handleSubmit"
>
<h1>
{{ invitation ? $t('profile.invites.title') : $t('components.registration.signup.title') }}
</h1>
<ds-space v-if="token" margin-botton="large">
<ds-text v-html="$t('registration.signup.form.invitation-code', { code: token })" />
</ds-space>
<ds-space margin-botton="large">
<ds-text>
{{
invitation
? $t('profile.invites.description')
: $t('components.registration.signup.form.description')
}}
</ds-text>
</ds-space>
<ds-input
:placeholder="invitation ? $t('profile.invites.emailPlaceholder') : $t('login.email')"
type="email"
id="email"
model="email"
name="email"
icon="envelope"
/>
<ds-button
:disabled="disabled"
:loading="$apollo.loading"
primary
fullwidth
name="submit"
type="submit"
icon="envelope"
>
<h1>{{ invitation ? $t('profile.invites.title') : $t('registration.signup.title') }}</h1>
<ds-space v-if="token" margin-botton="large">
<ds-text v-html="$t('registration.signup.form.invitation-code', { code: token })" />
</ds-space>
<ds-space margin-botton="large">
<ds-text>
{{
invitation
? $t('profile.invites.description')
: $t('registration.signup.form.description')
}}
</ds-text>
</ds-space>
<ds-input
:placeholder="invitation ? $t('profile.invites.emailPlaceholder') : $t('login.email')"
type="email"
id="email"
model="email"
name="email"
icon="envelope"
/>
<ds-button
:disabled="disabled"
:loading="$apollo.loading"
primary
fullwidth
name="submit"
type="submit"
icon="envelope"
>
{{ $t('registration.signup.form.submit') }}
</ds-button>
</ds-form>
<div v-else>
<template v-if="!error">
<sweetalert-icon icon="info" />
<ds-text align="center" v-html="submitMessage" />
</template>
<template v-else>
<sweetalert-icon icon="error" />
<ds-text align="center">{{ error.message }}</ds-text>
</template>
</div>
</ds-space>
</ds-card>
{{ $t('components.registration.signup.form.submit') }}
</ds-button>
<slot></slot>
</ds-form>
</ds-space>
<div v-else margin="large">
<template v-if="!error">
<transition name="ds-transition-fade">
<sweetalert-icon icon="info" />
</transition>
<ds-text align="center" v-html="submitMessage" />
</template>
<template v-else>
<transition name="ds-transition-fade">
<sweetalert-icon icon="error" />
</transition>
<ds-text align="center">{{ error.message }}</ds-text>
</template>
</div>
</template>
<script>
@ -103,7 +107,7 @@ export default {
computed: {
submitMessage() {
const { email } = this.formData
return this.$t('registration.signup.form.success', { email })
return this.$t('components.registration.signup.form.success', { email })
},
},
methods: {
@ -123,7 +127,7 @@ export default {
this.success = true
setTimeout(() => {
this.$emit('handleSubmitted', { email })
this.$emit('submit', { email })
}, 3000)
} catch (err) {
const { message } = err
@ -133,7 +137,10 @@ export default {
}
for (const [pattern, key] of Object.entries(mapping)) {
if (message.includes(pattern))
this.error = { key, message: this.$t(`registration.signup.form.errors.${key}`) }
this.error = {
key,
message: this.$t(`components.registration.signup.form.errors.${key}`),
}
}
if (!this.error) {
this.$toast.error(message)

View File

@ -3,17 +3,8 @@
</template>
<script>
import { getDateFnsLocale } from '~/locales'
import formatRelative from 'date-fns/formatRelative'
import { enUS, de, nl, fr, pt, es } from 'date-fns/locale' // pl currently not working library wise
const locales = {
en: enUS,
de,
nl,
fr,
es,
pt,
// pl
}
export default {
name: 'HcRelativeDateTime',
@ -25,8 +16,7 @@ export default {
},
computed: {
relativeDateTime() {
let locale = locales[this.$i18n.locale() || 'en']
return formatRelative(new Date(this.dateTime), new Date(), { locale })
return formatRelative(new Date(this.dateTime), new Date(), { locale: getDateFnsLocale(this) })
},
},
}

View File

@ -25,17 +25,6 @@ describe('TeaserImage.vue', () => {
wrapper = Wrapper()
})
describe('File upload', () => {
const imageUpload = [
{ file: { filename: 'avataar.svg', previewElement: '' }, url: 'someUrlToImage' },
]
it('supports adding a teaser image', () => {
wrapper.vm.addTeaserImage(imageUpload)
expect(wrapper.emitted().addTeaserImage[0]).toEqual(imageUpload)
})
})
describe('handles errors', () => {
beforeEach(() => jest.useFakeTimers())
const message = 'File upload failed'

View File

@ -5,25 +5,24 @@
id="postdropzone"
class="ds-card-image"
:use-custom-slot="true"
@vdropzone-thumbnail="thumbnail"
@vdropzone-error="verror"
@vdropzone-thumbnail="transformImage"
@vdropzone-drop="dropzoneDrop"
>
<div class="dz-message">
<div
:class="{
'hc-attachments-upload-area-post': true,
'hc-attachments-upload-area-update-post': contribution,
}"
>
<slot></slot>
<div
:class="{
'hc-attachments-upload-area-post': true,
'hc-attachments-upload-area-update-post': contribution,
'hc-drag-marker-post': true,
'hc-drag-marker-update-post': contribution,
}"
>
<slot></slot>
<div
:class="{
'hc-drag-marker-post': true,
'hc-drag-marker-update-post': contribution,
}"
>
<ds-icon name="image" size="xxx-large" />
</div>
<ds-icon name="image" size="xxx-large" />
</div>
</div>
</vue-dropzone>
@ -31,6 +30,8 @@
<script>
import vueDropzone from 'nuxt-dropzone'
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.css'
export default {
components: {
@ -42,7 +43,7 @@ export default {
data() {
return {
dropzoneOptions: {
url: this.addTeaserImage,
url: () => '',
maxFilesize: 5.0,
previewTemplate: this.template(),
},
@ -70,27 +71,51 @@ export default {
this.error = true
this.$toast.error(file.status, message)
},
addTeaserImage(file) {
this.$emit('addTeaserImage', file[0])
return ''
transformImage(file) {
let thumbnailElement, editor, confirm, thumbnailPreview, contributionImage
// Create the image editor overlay
editor = document.createElement('div')
thumbnailElement = document.querySelectorAll('#postdropzone')[0]
thumbnailPreview = document.querySelectorAll('.thumbnail-preview')[0]
if (thumbnailPreview) thumbnailPreview.remove()
contributionImage = document.querySelectorAll('.contribution-image')[0]
if (contributionImage) contributionImage.remove()
editor.classList.add('crop-overlay')
thumbnailElement.appendChild(editor)
// Create the confirm button
confirm = document.createElement('button')
confirm.classList.add('crop-confirm', 'ds-button', 'ds-button-primary')
confirm.textContent = this.$t('contribution.teaserImage.cropperConfirm')
confirm.addEventListener('click', () => {
// Get the canvas with image data from Cropper.js
let canvas = cropper.getCroppedCanvas()
canvas.toBlob(blob => {
this.$refs.el.manuallyAddFile(blob, canvas.toDataURL(), null, null, {
dontSubstractMaxFiles: false,
addToFiles: true,
})
image = new Image()
image.src = canvas.toDataURL()
image.classList.add('thumbnail-preview')
thumbnailElement.appendChild(image)
// Remove the editor from view
editor.parentNode.removeChild(editor)
const croppedImageFile = new File([blob], file.name, { type: 'image/jpeg' })
this.$emit('addTeaserImage', croppedImageFile)
}, 'image/jpeg')
})
editor.appendChild(confirm)
// Load the image
let image = new Image()
image.src = URL.createObjectURL(file)
editor.appendChild(image)
// Create Cropper.js and pass image
let cropper = new Cropper(image, { zoomable: false })
},
thumbnail: (file, dataUrl) => {
let thumbnailElement, contributionImage, uploadArea, thumbnailPreview, image
if (file.previewElement) {
thumbnailElement = document.querySelectorAll('#postdropzone')[0]
contributionImage = document.querySelectorAll('.contribution-image')[0]
thumbnailPreview = document.querySelectorAll('.thumbnail-preview')[0]
if (contributionImage) {
uploadArea = document.querySelectorAll('.hc-attachments-upload-area-update-post')[0]
uploadArea.removeChild(contributionImage)
uploadArea.classList.remove('hc-attachments-upload-area-update-post')
}
image = new Image()
image.src = URL.createObjectURL(file)
image.classList.add('thumbnail-preview')
if (thumbnailPreview) return thumbnailElement.replaceChild(image, thumbnailPreview)
thumbnailElement.appendChild(image)
}
dropzoneDrop() {
let cropOverlay = document.querySelectorAll('.crop-overlay')[0]
if (cropOverlay) cropOverlay.remove()
},
},
}
@ -98,16 +123,10 @@ export default {
<style lang="scss">
#postdropzone {
width: 100%;
min-height: 300px;
min-height: 500px;
background-color: $background-color-softest;
}
@media only screen and (max-width: 960px) {
#postdropzone {
min-height: 200px;
}
}
.hc-attachments-upload-area-post {
position: relative;
display: flex;
@ -134,11 +153,10 @@ export default {
display: flex;
align-items: center;
justify-content: center;
margin: 180px 5px;
color: hsl(0, 0%, 25%);
transition: all 0.2s ease-out;
font-size: 60px;
margin: 80px 5px;
background-color: $background-color-softest;
opacity: 0.65;
@ -178,7 +196,17 @@ export default {
border-top: $border-size-base solid $border-color-softest;
}
.contribution-image {
max-height: 300px;
.crop-overlay {
max-height: 2000px;
position: relative;
width: 100%;
background-color: #000;
}
.crop-confirm {
position: absolute;
left: 10px;
top: 10px;
z-index: 1;
}
</style>

View File

@ -56,7 +56,7 @@ const user = {
storiesOf('User', module)
.addDecorator(withA11y)
.addDecorator(helpers.layout)
.add('available user', () => ({
.add('available', () => ({
components: { User },
store: helpers.store,
data: () => ({
@ -64,7 +64,21 @@ storiesOf('User', module)
}),
template: '<user :user="user" :trunc="35" :date-time="new Date()" />',
}))
.add('anonymous user', () => ({
.add('has edited something', () => ({
components: { User },
store: helpers.store,
data: () => ({
user,
}),
template: `
<user :user="user" :trunc="35" :date-time="new Date()">
<template v-slot:dateTime>
- HEY! I'm edited
</template>
</user>
`,
}))
.add('anonymous', () => ({
components: { User },
store: helpers.store,
data: () => ({

View File

@ -11,17 +11,17 @@
<div @mouseover="openMenu(true)" @mouseleave="closeMenu(true)">
<hc-avatar class="avatar" :user="user" />
<div>
<ds-text align="left">
<ds-text>
<b class="username">{{ userName | truncate(18) }}</b>
<ds-text v-if="dateTime" size="small" color="soft">
<ds-icon name="clock" />
<client-only>
<hc-relative-date-time :date-time="dateTime" />
</client-only>
<slot name="dateTime"></slot>
</ds-text>
</ds-text>
</div>
<!-- Time -->
<ds-text align="left" size="small" color="soft">
{{ userSlug }}
</ds-text>

View File

@ -0,0 +1,25 @@
import { valuesReasonCategoryOptions } from '~/constants/modals.js'
export default function validReport({ translate }) {
return {
formSchema: {
reasonCategory: {
type: 'object',
required: true,
fields: {
value: {
type: 'enum',
enum: valuesReasonCategoryOptions,
required: true,
message: translate('report.reason.category.invalid'),
},
},
},
reasonDescription: {
type: 'string',
min: 0,
max: 200,
},
},
}
}

View File

@ -0,0 +1,36 @@
import validReport from './ReportModal'
import Schema from 'async-validator'
let translate
beforeEach(() => {
translate = jest.fn(() => 'Validation error')
})
describe('validReport', () => {
let validate = object => {
const { formSchema } = validReport({ translate })
const validator = new Schema(formSchema)
return validator.validate(object, { suppressWarning: true }).catch(({ errors }) => {
throw new Error(errors[0].message)
})
}
describe('reasonCategory', () => {
describe('invalid enum', () => {
it('rejects', async () => {
await expect(validate({ reasonCategory: { value: 'invalid_enum' } })).rejects.toThrow(
'Validation error',
)
})
})
describe('valid enum', () => {
it('resolves', async () => {
await expect(
validate({ reasonCategory: { value: 'discrimination_etc' } }),
).resolves.toBeUndefined()
})
})
})
})

View File

@ -1 +1,3 @@
export const COMMENT_MIN_LENGTH = 1
export const COMMENT_MAX_UNTRUNCATED_LENGTH = 300
export const COMMENT_TRUNCATE_TO_LENGTH = 180

View File

@ -0,0 +1,4 @@
export const SUPPORT_EMAIL = {
href: 'mailto:support@human-connection.org',
label: 'support@human-connection.org',
}

View File

@ -0,0 +1,11 @@
// this list equals to enums in GraphQL schema file "backend/src/schema/types/type/REPORTED.gql"
export const valuesReasonCategoryOptions = [
'discrimination_etc',
'pornographic_content_links',
'glorific_trivia_of_cruel_inhuman_acts',
'doxing',
'intentional_intimidation_stalking_persecution',
'advert_products_services_commercial',
'criminal_behavior_violation_german_law',
'other',
]

View File

@ -10,6 +10,7 @@ export default i18n => {
contentExcerpt
content
createdAt
updatedAt
disabled
deleted
author {
@ -39,6 +40,7 @@ export default i18n => {
contentExcerpt
content
createdAt
updatedAt
disabled
deleted
author {

View File

@ -40,6 +40,7 @@ export const postFragment = lang => gql`
content
contentExcerpt
createdAt
updatedAt
disabled
deleted
slug
@ -64,6 +65,7 @@ export const commentFragment = lang => gql`
fragment comment on Comment {
id
createdAt
updatedAt
disabled
deleted
content

View File

@ -1,13 +1,14 @@
import gql from 'graphql-tag'
export default app => {
export const reportListQuery = () => {
// no limit vor the moment like before: "reports(first: 20, orderBy: createdAt_desc)"
return gql`
query {
Report(first: 20, orderBy: createdAt_desc) {
id
description
type
reports(orderBy: createdAt_desc) {
createdAt
reasonCategory
reasonDescription
type
submitter {
id
slug
@ -79,3 +80,17 @@ export default app => {
}
`
}
export const reportMutation = () => {
return gql`
mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
report(
resourceId: $resourceId
reasonCategory: $reasonCategory
reasonDescription: $reasonDescription
) {
type
}
}
`
}

View File

@ -135,6 +135,17 @@ export const unfollowUserMutation = i18n => {
`
}
export const allowEmbedIframesMutation = () => {
return gql`
mutation($id: ID!, $allowEmbedIframes: Boolean) {
UpdateUser(id: $id, allowEmbedIframes: $allowEmbedIframes) {
id
allowEmbedIframes
}
}
`
}
export const checkSlugAvailableQuery = gql`
query($slug: String!) {
User(slug: $slug) {

View File

@ -1,4 +1,55 @@
{
"components": {
"password-reset": {
"request": {
"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>"
}
},
"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:"
}
},
"enter-nonce": {
"form": {
"nonce": "Code eingeben",
"description": "Öffne dein E-Mail Postfach und gib den Code ein, den wir geschickt haben.",
"next": "Weiter",
"validations": {
"length": "muss genau 6 Buchstaben lang sein"
}
}
},
"registration": {
"signup": {
"unavailable": "Leider ist die öffentliche Registrierung von Benutzerkonten auf diesem Server derzeit nicht möglich.",
"title": "Mach mit bei Human Connection!",
"form": {
"description": "Um loszulegen, gib deine E-Mail Adresse ein:",
"terms-and-condition": "Ich stimme den <a href=\"/terms-and-conditions\"><ds-text bold color=\"primary\" > Nutzungsbedingungen</ds-text></a>zu.",
"data-privacy": "Ich habe die <a href=\"https://human-connection.org/datenschutz/\" target=\"_blank\"><ds-text bold color=\"primary\" >Datenschutzerklärung</ds-text></a> gelesen und verstanden",
"minimum-age": "Ich bin 18 Jahre oder älter.",
"invitation-code": "Dein Einladungscode lautet: <b>{code}</b>",
"errors": {
"email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail Adresse!",
"invalid-invitation-token": "Es sieht so aus, als ob der Einladungscode schon eingelöst wurde. Jeder Code kann nur einmalig benutzt werden."
},
"submit": "Konto erstellen",
"success": "Eine Mail mit einem Bestätigungslink für die Registrierung wurde an <b>{email}</b> geschickt"
}
},
"create-user-account": {
"title": "Benutzerkonto anlegen",
"success": "Dein Benutzerkonto wurde erstellt!",
"error": "Es konnte kein Benutzerkonto erstellt werden!",
"help": "Vielleicht war der Bestätigungscode falsch oder abgelaufen? Wenn das Problem weiterhin besteht, schick uns gerne eine E-Mail an:"
}
}
},
"maintenance": {
"title": "Human Connection befindet sich in der Wartung",
"explanation": "Zurzeit führen wir einige geplante Wartungsarbeiten durch, bitte versuch es später erneut.",
@ -42,7 +93,7 @@
"bank": "Bankverbindung",
"germany": "Deutschland",
"code-of-conduct": "Verhaltenscodex",
"login": "Zurück zum Anmeldung"
"back-to-login": "Zurück zur Anmeldung"
},
"sorting": {
"newest": "Neueste",
@ -55,50 +106,14 @@
"email": "Deine E-Mail",
"password": "Dein Passwort",
"forgotPassword": "Passwort vergessen?",
"no-account": "Du hast noch kein Benutzerkonto?",
"register": "Benutzerkonto erstellen",
"moreInfo": "Was ist Human Connection?",
"moreInfoURL": "https://human-connection.org",
"moreInfoHint": "zur Präsentationsseite",
"hello": "Hallo",
"success": "Du bist eingeloggt!"
},
"password-reset": {
"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>"
}
},
"registration": {
"signup": {
"title": "Mach mit bei Human Connection!",
"form": {
"description": "Um loszulegen, gib deine E-Mail Adresse ein:",
"invitation-code": "Dein Einladungscode lautet: <b>{code}</b>",
"errors": {
"email-exists": "Es gibt schon ein Benutzerkonto mit dieser E-Mail Adresse!",
"invalid-invitation-token": "Es sieht so aus, als ob der Einladungscode schon eingelöst wurde. Jeder Code kann nur einmalig benutzt werden."
},
"submit": "Konto erstellen",
"success": "Eine Mail mit einem Bestätigungslink für die Registrierung wurde an <b>{email}</b> geschickt"
}
},
"create-user-account": {
"title": "Benutzerkonto anlegen",
"success": "Dein Benutzerkonto wurde erstellt!"
}
},
"verify-nonce": {
"form": {
"nonce": "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 …",
"mention": {
@ -108,6 +123,12 @@
"noHashtagsFound": "Keine Hashtags gefunden",
"addHashtag": "Neuer Hashtag",
"addLetter": "Tippe einen Buchstaben"
},
"embed": {
"data_privacy_warning": "Achte auf deine Daten!",
"data_privacy_info": "Deine Daten wurden noch nicht an Drittanbieter weitergegeben. Wenn du dieses Video jetzt abspielst, registriert der folgende Anbieter wahrscheinlich deine Nutzerdaten:",
"play_now": "Jetzt ansehen",
"always_allow": "Inhalte von Drittanbietern immer anzeigen (diese Einstellung kannst du jederzeit ändern)"
}
},
"profile": {
@ -220,6 +241,22 @@
"success": "Konto erfolgreich gelöscht!",
"pleaseConfirm": "<b class='is-danger'>Zerstörerische Aktion!</b> Gib <b>{confirm}</b> ein, um zu bestätigen."
},
"embeds": {
"name": "Drittanbieter",
"info-description": "Hier ist die Liste an Drittanbietern, deren Inhalte als Fremdcode z.B. in Form von eingebetteten Videos angezeigt werden kann:",
"status": {
"description": "Als Grundeinstellung für dich wird eingebetter Fremdcode von Drittanbietern",
"disabled": {
"off": "zunächst nicht angezeigt",
"on": "sofort angezeigt"
},
"change": {
"question": "Soll eingebetter Fremdcode von Dritten für dich immer angezeigt werden?",
"allow": "Na klar",
"deny": "Lieber nicht"
}
}
},
"organizations": {
"name": "Meine Organisationen"
},
@ -280,6 +317,7 @@
"columns": {
"number": "Nr.",
"name": "Name",
"email": "E-mail",
"slug": "Slug",
"role": "Rolle",
"createdAt": "Erstellt am"
@ -335,7 +373,8 @@
"submit": "Kommentiere",
"submitted": "Kommentar Gesendet",
"updated": "Änderungen gespeichert"
}
},
"edited": "bearbeitet"
},
"comment": {
"content": {
@ -348,10 +387,11 @@
"show": {
"more": "mehr anzeigen",
"less": "weniger anzeigen"
}
},
"edited": "bearbeitet"
},
"quotes": {
"african": {
"african": {
"quote": "Viele kleine Leute an vielen kleinen Orten, die viele kleine Dinge tun, werden das Antlitz dieser Welt verändern.",
"author": "Afrikanisches Sprichwort"
}
@ -375,8 +415,7 @@
"reportContent": "Melden",
"validations": {
"email": "muss eine gültige E-Mail Adresse sein",
"url": "muss eine gültige URL sein",
"verification-nonce": "muss genau 6 Buchstaben lang sein"
"url": "muss eine gültige URL sein"
}
},
"actions": {
@ -393,8 +432,11 @@
"reports": {
"empty": "Glückwunsch, es gibt nichts zu moderieren.",
"name": "Meldungen",
"submitter": "gemeldet von",
"disabledBy": "deaktiviert von"
"reasonCategory": "Kategorie",
"reasonDescription": "Beschreibung",
"createdAt": "Datum",
"submitter": "Gemeldet von",
"disabledBy": "Deaktiviert von"
}
},
"disable": {
@ -454,6 +496,27 @@
"type": "Kommentar",
"message": "Bist du sicher, dass du den Kommentar von \"<b>{name}</b>\" melden möchtest?",
"error": "Du hast den Kommentar bereits gemeldet!"
},
"reason": {
"category": {
"label": "Wähle eine Kategorie:",
"placeholder": "Kategorie …",
"options": {
"discrimination_etc": "Diskriminierende Beiträge, Kommentare, Äußerungen oder Beleidigungen.",
"pornographic_content_links": "Das Posten oder Verlinken eindeutig pornografischen Materials.",
"glorific_trivia_of_cruel_inhuman_acts": "Verherrlichung oder Verharmlosung grausamer oder unmenschlicher Gewalttätigkeiten.",
"doxing": "Das Veröffentlichen von personenbezogenen Daten anderer ohne deren Einverständnis oder das Androhen dessen (\"Doxing\").",
"intentional_intimidation_stalking_persecution": "Absichtliche Einschüchterung, Stalking oder Verfolgung.",
"advert_products_services_commercial": "Bewerben von Produkten und Dienstleistungen mit kommerzieller Absicht.",
"criminal_behavior_violation_german_law": "Strafbares Verhalten bzw. Verstoß gegen deutsches Recht.",
"other": "Andere …"
},
"invalid": "Bitte wähle eine gültige Kategorie aus"
},
"description": {
"label": "Bitte erkläre: Warum möchtest du dies melden?",
"placeholder": "Zusätzliche Information …"
}
}
},
"followButton": {
@ -492,6 +555,7 @@
}
},
"contribution": {
"title": "Titel",
"newPost": "Erstelle einen neuen Beitrag",
"filterFollow": "Beiträge filtern von Usern denen ich folge",
"filterALL": "Alle Beiträge anzeigen",
@ -526,6 +590,9 @@
"it-internet-data-privacy": "IT, Internet & Datenschutz",
"art-culture-sport": "Kunst, Kultur & Sport"
}
},
"teaserImage": {
"cropperConfirm": "Bestätigen"
}
},
"code-of-conduct": {

View File

@ -1,8 +1,60 @@
{
"components": {
"password-reset": {
"request": {
"title": "Reset your password",
"form": {
"description": "A password reset e-mail will be sent to the given e-mail address.",
"submit": "Request e-mail",
"submitted": "An e-mail with further instructions has been sent to <b>{email}</b>"
}
},
"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:"
}
},
"enter-nonce": {
"form": {
"nonce": "Enter your code",
"description": "Open your inbox and enter the code that we've sent to you.",
"next": "Continue",
"validations": {
"length": "must be 6 characters long"
}
}
},
"registration": {
"signup": {
"unavailable": "Unfortunately, public registration of user accounts is not available right now on this server.",
"title": "Join Human Connection!",
"form": {
"description": "To get started, enter your e-mail address:",
"terms-and-condition": "I confirm to the <a href=\"/terms-and-conditions\"><ds-text bold color=\"primary\" > Terms and conditions</ds-text></a>.",
"data-privacy": " I have read and understood the <a href=\"https://human-connection.org/datenschutz/\" target=\"_blank\"><ds-text bold color=\"primary\" >Privacy Statement</ds-text></a> ",
"minimum-age": "I'm 18 years or older.",
"invitation-code": "Your invitation code is: <b>{code}</b>",
"errors": {
"email-exists": "There is already a user account with this e-mail address!",
"invalid-invitation-token": "It looks like as if the invitation has been used already. Invitation links can only be used once."
},
"submit": "Create an account",
"success": "A mail with a link to complete your registration has been sent to <b>{email}</b>"
}
},
"create-user-account": {
"title": "Create user account",
"success": "Your account has been created!",
"error": "No user account could be created!",
"help": " Maybe the confirmation was invalid? In case of problems, feel free to ask for help by sending us a mail to:"
}
}
},
"maintenance": {
"title": "Human Connection is under maintenance",
"explanation": "At the moment we are doing some scheduled maintenance, please try again later.",
"questions": "Any Questions or concerns, send an email to"
"questions": "Any Questions or concerns, send an e-mail to"
},
"index": {
"no-results": "No contributions found.",
@ -42,7 +94,7 @@
"bank": "bank account",
"germany": "Germany",
"code-of-conduct": "Code of Conduct",
"login": "Back to login"
"back-to-login": "Back to login page"
},
"sorting": {
"newest": "Newest",
@ -52,54 +104,17 @@
"copy": "If you already have a human-connection account, please login.",
"login": "Login",
"logout": "Logout",
"email": "Your Email",
"email": "Your E-mail",
"password": "Your Password",
"forgotPassword": "Forgot Password?",
"no-account": "Don't have an account?",
"register": "Sign up",
"moreInfo": "What is Human Connection?",
"moreInfoURL": "https://human-connection.org/en/",
"moreInfoHint": "to the presentation page",
"hello": "Hello",
"success": "You are logged in!"
},
"password-reset": {
"title": "Reset your password",
"form": {
"description": "A password reset email will be sent to the given email address.",
"submit": "Request email",
"submitted": "An email with further instructions has been sent to <b>{email}</b>"
}
},
"registration": {
"signup": {
"title": "Join Human Connection!",
"form": {
"description": "To get started, enter your email address:",
"invitation-code": "Your invitation code is: <b>{code}</b>",
"errors": {
"email-exists": "There is already a user account with this email address!",
"invalid-invitation-token": "It looks like as if the invitation has been used already. Invitation links can only be used once."
},
"submit": "Create an account",
"success": "A mail with a link to complete your registration has been sent to <b>{email}</b>"
}
},
"create-user-account": {
"title": "Create user account",
"success": "Your account has been created!"
}
},
"verify-nonce": {
"form": {
"nonce": "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 …",
"mention": {
@ -109,6 +124,12 @@
"noHashtagsFound": "No hashtags found",
"addHashtag": "New hashtag",
"addLetter": "Type a letter"
},
"embed": {
"data_privacy_warning": "Data Privacy Warning!",
"data_privacy_info": "Your data has not yet been shared with any third party providers. If you proceed to watch this video the following provider will likely collect user data:",
"play_now": "Watch now",
"always_allow": "Always allow embedded content by third party providers (this setting can be changed any time)"
}
},
"profile": {
@ -131,8 +152,8 @@
},
"invites": {
"title": "Invite somebody to Human Connection!",
"description": "Enter thier email address for invitation.",
"emailPlaceholder": "Email to invite"
"description": "Enter thier e-mail address for invitation.",
"emailPlaceholder": "E-mail to invite"
}
},
"notifications": {
@ -161,23 +182,23 @@
},
"email": {
"validation": {
"same-email": "This is your current email address"
"same-email": "This is your current e-mail address"
},
"name": "Your email",
"labelEmail": "Change your email address",
"labelNewEmail": "New email Address",
"name": "Your e-mail",
"labelEmail": "Change your e-mail address",
"labelNewEmail": "New e-mail Address",
"labelNonce": "Enter your code",
"success": "A new email address has been registered.",
"submitted": "An email to verify your address has been sent to <b>{email}</b>.",
"change-successful": "Your email address has been changed successfully.",
"success": "A new e-mail address has been registered.",
"submitted": "An e-mail to verify your address has been sent to <b>{email}</b>.",
"change-successful": "Your e-mail address has been changed successfully.",
"verification-error": {
"message": "Your email could not be changed.",
"message": "Your e-mail could not be changed.",
"explanation": "This can have different causes:",
"reason": {
"invalid-nonce": "Is the confirmation code invalid?",
"no-email-request": "Are you certain that you requested a change of your email address?"
"no-email-request": "Are you certain that you requested a change of your e-mail address?"
},
"support": "If the problem persists, please contact us by email at"
"support": "If the problem persists, please contact us by e-mail at"
}
},
"validation": {
@ -221,6 +242,22 @@
"success": "Account successfully deleted!",
"pleaseConfirm": "<b class='is-danger'>Destructive action!</b> Type <b>{confirm}</b> to confirm"
},
"embeds": {
"name": "Third party providers",
"info-description": "Here is the list of third-party providers whose content can be displayed as third-party code, e.g. in the form of embedded videos.",
"status": {
"description": "As a default for you, embedded code of third-party providers is",
"disabled": {
"off": "initially not displayed",
"on": "displayed immediately"
},
"change": {
"question": "Should embedded source code from third parties always be displayed to you?",
"allow": "Sure",
"deny": "No thanks"
}
}
},
"organizations": {
"name": "My Organizations"
},
@ -275,12 +312,13 @@
"users": {
"name": "Users",
"form": {
"placeholder": "email, name or description"
"placeholder": "e-mail, name or description"
},
"table": {
"columns": {
"number": "No.",
"name": "Name",
"email": "E-mail",
"slug": "Slug",
"role": "Role",
"createdAt": "Created at"
@ -336,7 +374,8 @@
"submit": "Comment",
"submitted": "Comment Submitted",
"updated": "Changes Saved"
}
},
"edited": "edited"
},
"comment": {
"content": {
@ -349,7 +388,8 @@
"show": {
"more": "show more",
"less": "show less"
}
},
"edited": "edited"
},
"quotes": {
"african": {
@ -375,9 +415,8 @@
"loading": "loading",
"reportContent": "Report",
"validations": {
"email": "must be a valid email address",
"url": "must be a valid URL",
"verification-nonce": "must be 6 characters long"
"email": "must be a valid e-mail address",
"url": "must be a valid URL"
}
},
"actions": {
@ -394,8 +433,11 @@
"reports": {
"empty": "Congratulations, nothing to moderate.",
"name": "Reports",
"submitter": "reported by",
"disabledBy": "disabled by"
"reasonCategory": "Category",
"reasonDescription": "Description",
"createdAt": "Date",
"submitter": "Reported by",
"disabledBy": "Disabled by"
}
},
"disable": {
@ -455,6 +497,27 @@
"type": "Comment",
"message": "Do you really want to report the comment from \"<b>{name}</b>\"?",
"error": "You have already reported the comment!"
},
"reason": {
"category": {
"label": "Select a category:",
"placeholder": "Category …",
"options": {
"discrimination_etc": "Discriminatory posts, comments, utterances or insults.",
"pornographic_content_links": "Posting or linking of clearly pornographic material.",
"glorific_trivia_of_cruel_inhuman_acts": "Glorification or trivialization of cruel or inhuman acts of violence.",
"doxing": "The disclosure of others' personal information without their consent or threat there of (\"doxing\").",
"intentional_intimidation_stalking_persecution": "Intentional intimidation, stalking or persecution.",
"advert_products_services_commercial": "Advertising products and services with commercial intent.",
"criminal_behavior_violation_german_law": "Criminal behavior or violation of German law.",
"other": "Other …"
},
"invalid": "Please select a valid category"
},
"description": {
"label": "Please explain: Why you like to report this?",
"placeholder": "Additional information …"
}
}
},
"followButton": {
@ -493,6 +556,7 @@
}
},
"contribution": {
"title": "Title",
"newPost": "Create a new Post",
"filterFollow": "Filter contributions from users I follow",
"filterALL": "View all contributions",
@ -527,6 +591,9 @@
"it-internet-data-privacy": "IT, Internet & Data Privacy",
"art-culture-sport": "Art, Culture, & Sport"
}
},
"teaserImage": {
"cropperConfirm": "Confirm"
}
},
"code-of-conduct": {

View File

@ -292,6 +292,11 @@
"message": "¿Realmente quieres liberar el comentario de \"<b>{name}</b>\"?"
}
},
"contribution": {
"teaserImage": {
"cropperConfirm": "Confirmar"
}
},
"user": {
"avatar": {
"submitted": "Carga con éxito"

View File

@ -287,6 +287,11 @@
"message": "Voulez-vous vraiment publier le commentaire de \"<b>{name}</b>\"?"
}
},
"contribution": {
"teaserImage": {
"cropperConfirm": "Confirmer"
}
},
"user": {
"avatar": {
"submitted": "Téléchargement réussi"

View File

@ -1,50 +1,67 @@
module.exports = [
import { enUS, de, nl, fr, es, it, pt, pl } from 'date-fns/locale'
import find from 'lodash/find'
const locales = [
{
name: 'English',
code: 'en',
iso: 'en-US',
enabled: true,
dateFnsLocale: enUS,
},
{
name: 'Deutsch',
code: 'de',
iso: 'de-DE',
enabled: true,
dateFnsLocale: de,
},
{
name: 'Nederlands',
code: 'nl',
iso: 'nl-NL',
enabled: true,
dateFnsLocale: nl,
},
{
name: 'Français',
code: 'fr',
iso: 'fr-FR',
enabled: true,
dateFnsLocale: fr,
},
{
name: 'Italiano',
code: 'it',
iso: 'it-IT',
enabled: true,
dateFnsLocale: it,
},
{
name: 'Español',
code: 'es',
iso: 'es-ES',
enabled: true,
dateFnsLocale: es,
},
{
name: 'Português',
code: 'pt',
iso: 'pt-PT',
enabled: true,
dateFnsLocale: pt,
},
{
name: 'Polski',
code: 'pl',
iso: 'pl-PL',
enabled: true,
dateFnsLocale: pl,
},
]
export default locales
export function getDateFnsLocale({ $i18n }) {
const { dateFnsLocale } = find(locales, { code: $i18n.locale() }) || {}
return dateFnsLocale || enUS
}

View File

@ -140,5 +140,10 @@
"save": "Salva",
"edit": "Modifica",
"delete": "Cancella"
},
"contribution": {
"teaserImage": {
"cropperConfirm": "Confermare"
}
}
}

View File

@ -158,7 +158,10 @@
},
"contribution": {
"edit": "Bijdrage bewerken",
"delete": "Bijdrage verwijderen"
"delete": "Bijdrage verwijderen",
"teaserImage": {
"cropperConfirm": "Bevestigen"
}
},
"comment": {
"edit": "Commentaar bewerken",

View File

@ -1,4 +1,31 @@
{
"components": {
"password-reset": {
"request": {
"title": "Zresetuj hasło",
"form": {
"description": "Na podany adres e-mail zostanie wysłany email z resetem hasła.",
"submit": "Poproś o wiadomość e-mail",
"submitted": "Na adres <b>{email}</b> została wysłana wiadomość z dalszymi instrukcjami."
}
},
"change-password": {
"success": "Zmiana hasła zakończyła się sukcesem!",
"error": "Zmiana hasła nie powiodła się. Może kod bezpieczeństwa nie był poprawny?",
"help": "W przypadku problemów, zachęcamy do zwrócenia się o pomoc, wysyłając do nas wiadomość e-mail:"
}
},
"enter-nonce": {
"form": {
"nonce": "Wprowadź swój kod",
"description": "Otwórz swoją skrzynkę odbiorczą i wpisz kod, który do Ciebie wysłaliśmy.",
"next": "Kontynuuj",
"validations": {
"length": "musi mieć długość 6 znaków."
}
}
}
},
"filter-menu": {
"title": "Twoja bańka filtrująca"
},
@ -29,26 +56,6 @@
"moreInfoHint": "idź po więcej szczegółów",
"hello": "Cześć"
},
"password-reset": {
"title": "Zresetuj hasło",
"form": {
"description": "Na podany adres e-mail zostanie wysłany email z resetem hasła.",
"submit": "Poproś o wiadomość e-mail",
"submitted": "Na adres <b>{email}</b> została wysłana wiadomość z dalszymi instrukcjami."
}
},
"verify-nonce": {
"form": {
"nonce": "Wprowadź swój kod",
"description": "Otwórz swoją skrzynkę odbiorczą i wpisz kod, który do Ciebie wysłaliśmy.",
"next": "Kontynuuj",
"change-password": {
"success": "Zmiana hasła zakończyła się sukcesem!",
"error": "Zmiana hasła nie powiodła się. Może kod bezpieczeństwa nie był poprawny?",
"help": "W przypadku problemów, zachęcamy do zwrócenia się o pomoc, wysyłając do nas wiadomość e-mail:"
}
}
},
"editor": {
"placeholder": "Napisz coś inspirującego..."
},
@ -236,8 +243,7 @@
"loading": "załadunek",
"reportContent": "Sprawozdanie",
"validations": {
"email": "musi być ważny adres e-mail.",
"verification-nonce": "musi mieć długość 6 znaków."
"email": "musi być ważny adres e-mail."
}
},
"actions": {
@ -356,6 +362,9 @@
"languageSelectLabel": "Język",
"categories": {
"infoSelectedNoOfMaxCategories": "{chosen} z {max} wybrane kategorie"
},
"teaserImage": {
"cropperConfirm": "Potwierdzać"
}
}
}

View File

@ -118,7 +118,11 @@
},
"takeAction": {
"name": "Tomar uma ação"
}
},
"comment": {
"submit": "Commentar"
},
"edited": "editado"
},
"quotes": {
"african": {
@ -198,12 +202,26 @@
}
},
"contribution": {
"title": "Título",
"edit": "Editar Contribuição",
"delete": "Apagar Contribuição"
"delete": "Apagar Contribuição",
"teaserImage": {
"cropperConfirm": "Confirmar"
}
},
"comment": {
"edit": "Editar Comentário",
"delete": "Apagar Comentário"
"content": {
"unavailable-placeholder": "… este commenttário não está disponível"
},
"menu": {
"edit": "Editar Comentário",
"delete": "Apagar Comentário"
},
"show": {
"more": "mostrar mais",
"less": "mostrar menos"
},
"edited": "editado"
},
"followButton": {
"follow": "Seguir",

View File

@ -1,16 +1,10 @@
import defaultConfig from './nuxt.config.js'
const {
css,
styleResources,
env: { locales },
manifest,
} = defaultConfig
const { css, styleResources, manifest } = defaultConfig
export default {
css,
styleResources,
env: { locales },
manifest,
head: {

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