Merging origin to 342-merge_documentation

This commit is contained in:
Robert Schäfer 2019-04-04 00:48:14 +02:00
commit 138d9a0011
434 changed files with 46548 additions and 0 deletions

11
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,11 @@
<!--
Please take a look at the issue templates at https://github.com/Human-Connection/Human-Connection/issues/new/choose
before submitting a new issue. Following one of the issue templates will ensure maintainers can route your request efficiently.
Thanks!
-->
## Issue
<!-- Describe your Issue in detail. -->
<!-- Attach screenshots and drawings if needed. -->

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,31 @@
---
name: 🐛 Bug report
about: Create a report to help us improve
---
## 🐛 Bugreport
<!-- Describe your issue in detail. Include screenshots if needed. Give us as much information as possible. Use a clear and concise description of what the bug is.-->
### Steps to reproduce the behavior
1.
2.
3.
4. ...
5. Profit
### Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
### Version & Environment
Type: [] <!-- [Desktop|Smartphone] -->
- OS: [] <!-- [e.g. iOS8.1 or Windows] -->
- Browser: [] <!-- [e.g. stock browser, safari, chrome] -->
- Version [] <!-- [e.g. 22] -->
- Device: [] <!-- [e.g. iPhone6] -->
### Additional context
<!-- Add any other context about the problem here. -->

View File

@ -0,0 +1,26 @@
---
name: 🚀 Feature request
about: Suggest an idea for this project
---
## 🚀 Feature
<!-- Describe the Feature. -->
### Is your feature request related to a problem? Please describe.
<!-- A clear and concise description of what the problem is.
Ex. I'm always frustrated when [...] -->
### Describe the prefered solution and alternatives you've considered
<!-- A clear and concise description of what you want to happen.
Are there any alternative solutions or features you've considered? -->
### Design & Layout
<!-- Attach Screenshots and Drawings. -->
### Additional context
<!-- Add any other context or screenshots about the feature request here.-->

10
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: 💬 Question
about: If you need help understanding HumanConnection.
---
<!-- Chat with Team HumanConnection -->
<!-- If you need an answer right away, visit the HumanConnection Discord:
https://discord.gg/Q3mpcgr -->
## 💬 Question
<!-- Describe your Question in detail. Include screenshots and drawings if needed. -->

28
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,28 @@
## Pullrequest
<!-- Describe the Pullrequest. -->
### Issues
<!-- Which Issues does this fix, which are related?
- fixes #XXX
- relates #XXX
-->
- [X] None
### Checklist
<!-- Anything important to be thought of when deploying?
- [ ] Env-Variables adjustment needed
- [ ] Breaking/critical change
-->
- [X] None
### How2Test
<!-- Give a detailed description how to test your PR and confirm it is working as expected. -->
<!-- Maintainers will check the Tests
- [ ] Test1
- [ ] Test2
-->
- [X] None
### Todo
<!-- In case some parts are still missing, list them here. -->
- [X] None

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
.env
.idea
*.iml
.vscode
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.yarn-integrity
.eslintcache
kubeconfig.yaml
node_modules/
cypress/videos
cypress/screenshots/
cypress.env.json
!.gitkeep

61
.travis.yml Normal file
View File

@ -0,0 +1,61 @@
language: generic
services:
- docker
addons:
chrome: stable
apt:
sources:
- google-chrome
packages:
- google-chrome-stable
before_install:
- yarn global add wait-on
- yarn install
- cp cypress.env.template.json cypress.env.json
install:
- docker-compose -f docker-compose.yml -f docker-compose.travis.yml up --build -d
- wait-on http://localhost:7474 && docker-compose exec neo4j migrate
script:
- docker-compose exec backend yarn run lint
- docker-compose exec backend yarn run test --ci --verbose=false
- docker-compose exec backend yarn run db:reset
- docker-compose exec backend yarn run db:seed
- docker-compose exec backend yarn run test:cucumber
- docker-compose exec backend yarn run test:coverage
- docker-compose exec backend yarn run db:reset
- docker-compose exec backend yarn run db:seed
- docker-compose exec webapp yarn run lint
- docker-compose exec webapp yarn run test --ci --verbose=false
- docker-compose exec -d backend yarn run test:cypress
- yarn run cypress:run --record --key $CYPRESS_TOKEN
after_success:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh success $WEBHOOK_URL
- if [ $TRAVIS_BRANCH == "master" ] && [ $TRAVIS_EVENT_TYPE == "push" ]; then
wget https://raw.githubusercontent.com/Human-Connection/Discord-Bot/develop/tester.sh &&
chmod +x tester.sh &&
./tester.sh staging $WEBHOOK_URL;
fi
after_failure:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh
- chmod +x send.sh
- ./send.sh failure $WEBHOOK_URL
before_deploy:
- ./scripts/setup_kubernetes.sh
deploy:
- provider: script
script: scripts/docker_push.sh
on:
branch: master
- provider: script
script: scripts/deploy.sh
on:
branch: master

46
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at developer@human-connection.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

72
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,72 @@
Thanks so much for thinking of contributing to the Human Connection project, we really appreciate it! :-)
### Getting Set Up
Instructions for how to install all the necessary software can be found in our [documentation](https://docs.human-connection.org/nitro/)
We recommend that new folks should ideally work together with an existing developer. Please join our discord instance to chat with developers or just ask them in tickets in [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f/boards?repos=152252353):
![](https://dl.dropbox.com/s/vbmcihkduy9dhko/Screenshot%202019-01-03%2015.50.11.png?dl=0)
Here are some general notes on our development flow:
### Development
* Currently operating in two week sprints
* We are using ZenHub to coordinate
- estimating time per issue is the crucial feature of [Zenhub](https://app.zenhub.com/workspaces/human-connection-nitro-5c0154ecc699f60fc92cf11f) that Github does not have
- "up-for-grabs" links to [Github project](https://github.com/orgs/Human-Connection/projects/10?card_filter_query=label%3A%22good+first+issue)
- ordering on ZenHub not necessarily reflected on github projects
* AgileVentures run open pairing sessions at 10:30am UTC each week on Tuesdays and Thursdays
* Core team
- all the people who are hired by HC non-profit corporation
- you can Meet-the-team [every two weeks in German](https://human-connection.org/veranstaltungen/) and [every month in English](https://human-connection.org/en/events/).
- 9 people
- 2 core developers (Robert [@roschaefer](https://github.com/roschaefer) and Greg [@appinteractive](https://github.com/appinteractive))
- 3 marketeers Jasi, Dennis and Sensi
- Hardy doing business development
- Martin head of IT and previously data protection officer
- Victor doing accounting and controlling
- Nicolas is the community manager (reviews content in the network) reflects community opinion back to the core team
* when can folks pair with Robert
- 10am UTC until 5pm UTC every working day
### Philosophy
We practise [collective code ownership](http://www.extremeprogramming.org/rules/collective.html) rather than strong code ownership, which means that:
* anyone can start working on anyone elses code
* we avoid blocking because someone else isn't working on something
* however it's sometimes good to leave something in order to create successful education experience
* everyone should always push their code to branches so others can see it
Everyone feel free to request merges or answers to issues from the project managers
But what do we do when waiting for merge into master (wanting to keep PRs small)
--> Robert recommends creating a pull request for each step
- programming is also about thinking about other people - empathy for your co-workers
- but what about when you are waiting for merge?
- solutions
- 1) put 2nd PR into branch that the first PR is hitting - but requires update after merging
- 2) prefer to leave exiting PR until it can be reviewed, and instead go and work on some other part of the codebase that is not impacted by the first PR
### Notes
question: when you want to pick a task - (find out priority) - is it in discord? is it in AV slack? --> Robert says you can always ask in discord - group channels are the best
Robert shares: [Zenhub board](https://app.zenhub.com/workspaces/nitro-embed-5c0154ecc699f60fc92cf11f/boards?repos=112590397,152252353,152252578,157710732,163305928)
Robert says the order of tickets are preserved in ZenHub and reflect their priority (most important at the top) and so check out the current milestones
Matt - question about who can work on [ticket 100](https://app.zenhub.com/workspaces/nitro-embed-5c0154ecc699f60fc92cf11f/issues/human-connection/human-connection/100) --> Robert - in rare occasions it might be exclusive to someone with admin permissions
Robert: notes greg just pushed this today: https://github.com/Human-Connection/Nitro-Deployment
Matt makes point that new stories will have to be taken off the "New Issues" and Robert says that's fine, if you don't like the first one, then you can take the next one. Volunteeers have no commitment except their own self development and their awesomeness by contributing to free and open-source software projects.
Robert notes that everyone is invited to join the kickoff meetings
Robert - difference between "important" (creates a lot of value) and "beginner friendly" (easy to implement)

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Human-Connection gGmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

39
README.md Normal file
View File

@ -0,0 +1,39 @@
<p align="center">
<a href="https://human-connection.org"><img align="center" src="https://github.com/Human-Connection/Human-Connection/blob/master/lets_get_together_2.png" alt="Human-Connection" /></a>
</p>
# Human-Connection
[![Build Status](https://travis-ci.com/Human-Connection/Human-Connection.svg?branch=master)](https://travis-ci.com/Human-Connection/Human-Connection)
Human Connection is a free and open-source social network for active citizenship.
**Technology Stack**
- [VueJS](https://vuejs.org/)
- [NuxtJS](https://nuxtjs.org/)
- [GraphQL](https://graphql.org/)
- [NodeJS](https://nodejs.org/en/)
- [Neo4J](https://neo4j.com/)
## Live demo
Try out our deployed [staging environment](https://nitro-staging.human-connection.org/).
Logins:
| email | password | role |
| --- | --- | --- |
| `user@example.org` | 1234 | user |
| `moderator@example.org` | 1234 | moderator |
| `admin@example.org` | 1234 | admin |
## Documentation
Learn how to set up a local development environment in our [Docs](https://docs.human-connection.org/nitro).
## Translations
Contributre translations on [lokalise.co](https://lokalise.co/public/556252725c18dd752dd546.13222042/).
## Developer Chat
Join the open-source community on [Discord](https://discord.gg/6ub73U3).

15
backend/.babelrc Normal file
View File

@ -0,0 +1,15 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "10"
}
}
]
],
"plugins": [
"@babel/plugin-proposal-throw-expressions"
]
}

2
backend/.codecov.yml Normal file
View File

@ -0,0 +1,2 @@
coverage:
range: "60...100"

22
backend/.dockerignore Normal file
View File

@ -0,0 +1,22 @@
.vscode/
.nyc_output/
.github/
.travis.yml
.graphqlconfig
.env
Dockerfile
docker-compose*.yml
./*.png
./*.log
node_modules/
scripts/
dist/
db-migration-worker/
neo4j/
public/uploads/*
!.gitkeep

12
backend/.env.template Normal file
View File

@ -0,0 +1,12 @@
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=letmein
GRAPHQL_PORT=4000
GRAPHQL_URI=http://localhost:4000
CLIENT_URI=http://localhost:3000
MOCK=false
JWT_SECRET="b/&&7b78BF&fv/Vd"
MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"
PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"

20
backend/.eslintrc.js Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
"extends": "standard",
"parser": "babel-eslint",
"env": {
"es6": true,
"node": true,
"jest/globals": true
},
"rules": {
"indent": [
"error",
2
],
"quotes": [
"error",
"single"
]
},
"plugins": ["jest"]
};

13
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
node_modules/
.env
.vscode
.idea
yarn-error.log
dist/*
coverage.lcov
.nyc_output/
public/uploads/*
!.gitkeep
# Apple macOS folder attribute file
.DS_Store

3
backend/.graphqlconfig Normal file
View File

@ -0,0 +1,3 @@
{
"schemaPath": "./src/schema.graphql"
}

24
backend/Dockerfile Normal file
View File

@ -0,0 +1,24 @@
FROM node:10-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
ARG BUILD_COMMIT
ENV BUILD_COMMIT=$BUILD_COMMIT
ARG WORKDIR=/nitro-backend
RUN mkdir -p $WORKDIR
WORKDIR $WORKDIR
COPY package.json yarn.lock ./
COPY .env.template .env
CMD ["yarn", "run", "start"]
FROM base as builder
RUN yarn install --frozen-lockfile --non-interactive
COPY . .
RUN cp .env.template .env
RUN yarn run build
# reduce image size with a multistage build
FROM base as production
ENV NODE_ENV=production
COPY --from=builder /nitro-backend/dist ./dist
RUN yarn install --frozen-lockfile --non-interactive

196
backend/README.md Normal file
View File

@ -0,0 +1,196 @@
<p align="center">
<a href="https://human-connection.org"><img align="center" src="humanconnection.png" height="200" alt="Human Connection" /></a>
</p>
# NITRO Backend
[![Build Status](https://img.shields.io/travis/com/Human-Connection/Nitro-Backend/master.svg)](https://travis-ci.com/Human-Connection/Nitro-Backend)
[![MIT License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/Human-Connection/Nitro-Backend/blob/backend/LICENSE.md)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Backend.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Backend?ref=badge_shield)
[![Discord Channel](https://img.shields.io/discord/489522408076738561.svg)](https://discord.gg/6ub73U3)
> This Prototype tries to resolve the biggest hurdle of connecting
> our services together. This is not possible in a sane way using
> our current approach.
>
> With this Prototype we can explore using the combination of
> GraphQL and the Neo4j Graph Database for achieving the connected
> nature of a social graph with better development experience as we
> do not need to connect data by our own any more through weird table
> structures etc.
>
> #### Advantages:
> - easer data structure
> - better connected data
> - easy to achieve "recommendations" based on actions (relations)
> - more performant and better to understand API
> - better API client that uses caching
>
> We still need to evaluate the drawbacks and estimate the development
> cost of such an approach
## How to get in touch
Connect with other developers over [Discord](https://discord.gg/6ub73U3)
## Quick Start
### Requirements
Node >= `v10.12.0`
```
node --version
```
### Forking the repository
Before you start, fork the repository using the fork button above, then clone it to your local machine using `git clone https://github.com/your-username/Nitro-Backend.git`
### Installation with Docker
Run:
```sh
docker-compose up
# create indices etc.
docker-compose exec neo4j migrate
# if you want seed data
# open another terminal and run
docker-compose exec backend yarn run db:seed
```
App is [running on port 4000](http://localhost:4000/)
To wipe out your neo4j database run:
```sh
docker-compose down -v
```
### Installation without Docker
Install dependencies:
Download [Neo4j Community Edition](https://neo4j.com/download-center/#releases) and unpack the files.
Download [Neo4j Apoc](https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases) and drop the file into the `plugins` folder of the just extracted Neo4j-Server
Start Neo4j
```
neo4j\bin\neo4j start
```
and confirm it's running [here](http://localhost:7474)
```bash
yarn install
# -or-
npm install
```
Copy:
```
cp .env.template .env
```
Configure the file `.env` according to your needs and your local setup.
Start the GraphQL service:
```bash
yarn dev
# -or-
npm dev
```
And on the production machine run following:
```bash
yarn start
# -or-
npm start
```
This will start the GraphQL service (by default on localhost:4000)
where you can issue GraphQL requests or access GraphQL Playground in the browser:
![GraphQL Playground](graphql-playground.png)
## Configure
Set your Neo4j connection string and credentials in `.env`.
For example:
_.env_
```yaml
NEO4J_URI=bolt://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=letmein
```
> You need to install APOC as a plugin for the graph you create in the neo4j desktop app!
Note that grand-stack-starter does not currently bundle a distribution
of Neo4j. You can download [Neo4j Desktop](https://neo4j.com/download/)
and run locally for development, spin up a [hosted Neo4j Sandbox instance](https://neo4j.com/download/),
run Neo4j in one of the [many cloud options](https://neo4j.com/developer/guide-cloud-deployment/),
[spin up Neo4j in a Docker container](https://neo4j.com/developer/docker/) or on Debian-based systems install [Neo4j from the Debian Repository](http://debian.neo4j.org/).
Just be sure to update the Neo4j connection string and credentials accordingly in `.env`.
## Mock API Results
Alternatively you can just mock all responses from the api which let
you build a frontend application without running a neo4j instance.
Just set `MOCK=true` inside `.env` or pass it on application start.
## Seed and Reset the Database
Optionally you can seed the GraphQL service by executing mutations that
will write sample data to the database:
```bash
yarn run db:seed
# -or-
npm run db:seed
```
For a reset you can use the reset script:
```bash
yarn db:reset
# -or-
npm run db:reset
```
## Run Tests
**Beware**: We have no multiple database setup at the moment. We clean the database after each test, running the tests will wipe out all your data!
Run the **_jest_** tests:
```bash
yarn run test
# -or-
npm run test
```
Run the **_cucumber_** features:
```bash
yarn run test:cucumber
# -or-
npm run test:cucumber
```
When some tests fail, try `yarn db:reset` and after that `yarn db:seed`. Then run the tests again
## Todo`s
- [x] add jwt authentication
- [ ] get directives working correctly (@toLower, @auth, @role, etc.)
- [x] check if search is working
- [x] check if sorting is working
- [x] check if pagination is working
- [ ] check if upload is working (using graphql-yoga?)
- [x] evaluate middleware
- [ ] ignore Posts and Comments by blacklisted Users
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Backend.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Backend?ref=badge_large)

View File

@ -0,0 +1 @@
.ssh/

View File

@ -0,0 +1 @@
.ssh/

View File

@ -0,0 +1,3 @@
|1|GuOYlVEhTowidPs18zj9p5F2j3o=|sDHJYLz9Ftv11oXeGEjs7SpVyg0= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM5N29bI5CeKu1/RBPyM2fwyf7fuajOO+tyhKe1+CC2sZ1XNB5Ff6t6MtCLNRv2mUuvzTbW/HkisDiA5tuXUHOk=
|1|2KP9NV+Q5g2MrtjAeFSVcs8YeOI=|nf3h4wWVwC4xbBS1kzgzE2tBldk= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhRK6BeIEUxXlS0z/pOfkUkSPfn33g4J1U3L+MyUQYHm+7agT08799ANJhnvELKE1tt4Vx80I9UR81oxzZcy3E=
|1|HonYIRNhKyroUHPKU1HSZw0+Qzs=|5T1btfwFBz2vNSldhqAIfTbfIgQ= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNhRK6BeIEUxXlS0z/pOfkUkSPfn33g4J1U3L+MyUQYHm+7agT08799ANJhnvELKE1tt4Vx80I9UR81oxzZcy3E=

View File

@ -0,0 +1,13 @@
FROM mongo:4
RUN apt-get update && apt-get -y install --no-install-recommends wget apt-transport-https \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN wget -O - https://debian.neo4j.org/neotechnology.gpg.key | apt-key add -
RUN echo 'deb https://debian.neo4j.org/repo stable/' | tee /etc/apt/sources.list.d/neo4j.list
RUN apt-get update && apt-get -y install --no-install-recommends openjdk-8-jre openssh-client neo4j rsync \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
COPY migration ./migration
COPY migrate.sh /usr/local/bin/migrate
COPY sync_uploads.sh /usr/local/bin/sync_uploads

View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
for var in "SSH_USERNAME" "SSH_HOST" "MONGODB_USERNAME" "MONGODB_PASSWORD" "MONGODB_DATABASE" "MONGODB_AUTH_DB" "NEO4J_URI"
do
if [[ -z "${!var}" ]]; then
echo "${var} is undefined"
exit 1
fi
done
/migration/mongo/import.sh
/migration/neo4j/import.sh

View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -e
echo "SSH_USERNAME ${SSH_USERNAME}"
echo "SSH_HOST ${SSH_HOST}"
echo "MONGODB_USERNAME ${MONGODB_USERNAME}"
echo "MONGODB_PASSWORD ${MONGODB_PASSWORD}"
echo "MONGODB_DATABASE ${MONGODB_DATABASE}"
echo "MONGODB_AUTH_DB ${MONGODB_AUTH_DB}"
echo "-------------------------------------------------"
mongo ${MONGODB_DATABASE} --eval "db.dropDatabase();"
rm -rf /mongo-export/*
ssh -4 -M -S my-ctrl-socket -fnNT -L 27018:localhost:27017 -l ${SSH_USERNAME} ${SSH_HOST}
mongodump --host localhost -d ${MONGODB_DATABASE} --port 27018 --username ${MONGODB_USERNAME} --password ${MONGODB_PASSWORD} --authenticationDatabase ${MONGODB_AUTH_DB} --gzip --archive=/tmp/mongodump.archive
mongorestore --gzip --archive=/tmp/mongodump.archive
ssh -S my-ctrl-socket -O check -l ${SSH_USERNAME} ${SSH_HOST}
ssh -S my-ctrl-socket -O exit -l ${SSH_USERNAME} ${SSH_HOST}
for collection in "categories" "badges" "users" "contributions" "comments" "follows" "shouts"
do
mongoexport --db ${MONGODB_DATABASE} --collection $collection --out "/mongo-export/$collection.json"
done

View File

@ -0,0 +1,10 @@
CALL apoc.load.json('file:/mongo-export/badges.json') YIELD value as badge
MERGE(b:Badge {id: badge._id["$oid"]})
ON CREATE SET
b.key = badge.key,
b.type = badge.type,
b.icon = badge.image.path,
b.status = badge.status,
b.createdAt = badge.createdAt.`$date`,
b.updatedAt = badge.updatedAt.`$date`
;

View File

@ -0,0 +1,89 @@
CALL apoc.load.json('file:/mongo-export/categories.json') YIELD value as category
MERGE(c:Category {id: category._id["$oid"]})
ON CREATE SET
c.name = category.title,
c.slug = category.slug,
c.icon = category.icon,
c.createdAt = category.createdAt.`$date`,
c.updatedAt = category.updatedAt.`$date`
;
MATCH (c:Category)
WHERE (c.icon = "categories-justforfun")
SET c.icon = 'smile'
;
MATCH (c:Category)
WHERE (c.icon = "categories-luck")
SET c.icon = 'heart-o'
;
MATCH (c:Category)
WHERE (c.icon = "categories-health")
SET c.icon = 'medkit'
;
MATCH (c:Category)
WHERE (c.icon = "categories-environment")
SET c.icon = 'tree'
;
MATCH (c:Category)
WHERE (c.icon = "categories-animal-justice")
SET c.icon = 'paw'
;
MATCH (c:Category)
WHERE (c.icon = "categories-human-rights")
SET c.icon = 'balance-scale'
;
MATCH (c:Category)
WHERE (c.icon = "categories-education")
SET c.icon = 'graduation-cap'
;
MATCH (c:Category)
WHERE (c.icon = "categories-cooperation")
SET c.icon = 'users'
;
MATCH (c:Category)
WHERE (c.icon = "categories-politics")
SET c.icon = 'university'
;
MATCH (c:Category)
WHERE (c.icon = "categories-economy")
SET c.icon = 'money'
;
MATCH (c:Category)
WHERE (c.icon = "categories-technology")
SET c.icon = 'flash'
;
MATCH (c:Category)
WHERE (c.icon = "categories-internet")
SET c.icon = 'mouse-pointer'
;
MATCH (c:Category)
WHERE (c.icon = "categories-art")
SET c.icon = 'paint-brush'
;
MATCH (c:Category)
WHERE (c.icon = "categories-freedom-of-speech")
SET c.icon = 'bullhorn'
;
MATCH (c:Category)
WHERE (c.icon = "categories-sustainability")
SET c.icon = 'shopping-cart'
;
MATCH (c:Category)
WHERE (c.icon = "categories-peace")
SET c.icon = 'angellist'
;

View File

@ -0,0 +1,14 @@
CALL apoc.load.json('file:/mongo-export/comments.json') YIELD value as json
MERGE (comment:Comment {id: json._id["$oid"]})
ON CREATE SET
comment.content = json.content,
comment.contentExcerpt = json.contentExcerpt,
comment.deleted = json.deleted,
comment.disabled = false
WITH comment, json, json.contributionId as postId
MATCH (post:Post {id: postId})
WITH comment, post, json.userId as userId
MATCH (author:User {id: userId})
MERGE (comment)-[:COMMENTS]->(post)
MERGE (author)-[:WROTE]->(comment)
;

View File

@ -0,0 +1,25 @@
CALL apoc.load.json('file:/mongo-export/contributions.json') YIELD value as post
MERGE (p:Post {id: post._id["$oid"]})
ON CREATE SET
p.title = post.title,
p.slug = post.slug,
p.image = post.teaserImg,
p.content = post.content,
p.contentExcerpt = post.contentExcerpt,
p.visibility = toLower(post.visibility),
p.createdAt = post.createdAt.`$date`,
p.updatedAt = post.updatedAt.`$date`,
p.deleted = post.deleted,
p.disabled = NOT post.isEnabled
WITH p, post
MATCH (u:User {id: post.userId})
MERGE (u)-[:WROTE]->(p)
WITH p, post, post.categoryIds as categoryIds
UNWIND categoryIds AS categoryId
MATCH (c:Category {id: categoryId})
MERGE (p)-[:CATEGORIZED]->(c)
WITH p, post.tags AS tags
UNWIND tags AS tag
MERGE (t:Tag {id: apoc.create.uuid(), name: tag})
MERGE (p)-[:TAGGED]->(t)
;

View File

@ -0,0 +1,4 @@
CALL apoc.load.json('file:/mongo-export/follows.json') YIELD value as follow
MATCH (u1:User {id: follow.userId}), (u2:User {id: follow.foreignId})
MERGE (u1)-[:FOLLOWS]->(u2)
;

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIRECTORY="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
echo "MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r;" | cypher-shell -a $NEO4J_URI
for collection in "badges" "categories" "users" "follows" "contributions" "shouts" "comments"
do
echo "Import ${collection}..." && cypher-shell -a $NEO4J_URI < $SCRIPT_DIRECTORY/$collection.cql
done

View File

@ -0,0 +1,4 @@
CALL apoc.load.json('file:/mongo-export/shouts.json') YIELD value as shout
MATCH (u:User {id: shout.userId}), (p:Post {id: shout.foreignId})
MERGE (u)-[:SHOUTED]->(p)
;

View File

@ -0,0 +1,20 @@
CALL apoc.load.json('file:/mongo-export/users.json') YIELD value as user
MERGE(u:User {id: user._id["$oid"]})
ON CREATE SET
u.name = user.name,
u.slug = user.slug,
u.email = user.email,
u.password = user.password,
u.avatar = user.avatar,
u.coverImg = user.coverImg,
u.wasInvited = user.wasInvited,
u.role = toLower(user.role),
u.createdAt = user.createdAt.`$date`,
u.updatedAt = user.updatedAt.`$date`,
u.deleted = user.deletedAt IS NOT NULL,
u.disabled = false
WITH u, user, user.badgeIds AS badgeIds
UNWIND badgeIds AS badgeId
MATCH (b:Badge {id: badgeId})
MERGE (b)-[:REWARDED]->(u)
;

View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
for var in "SSH_USERNAME" "SSH_HOST" "UPLOADS_DIRECTORY"
do
if [[ -z "${!var}" ]]; then
echo "${var} is undefined"
exit 1
fi
done
rsync --archive --update --verbose ${SSH_USERNAME}@${SSH_HOST}:${UPLOADS_DIRECTORY}/* /uploads/

View File

@ -0,0 +1,18 @@
version: "3.7"
services:
neo4j:
environment:
- NEO4J_AUTH=none
ports:
- 7687:7687
- 7474:7474
backend:
ports:
- 4001:4001
- 4123:4123
image: humanconnection/nitro-backend:builder
build:
context: .
target: builder
command: yarn run test:cypress

View File

@ -0,0 +1,36 @@
version: "3.7"
services:
backend:
volumes:
- uploads:/nitro-backend/public/uploads
neo4j:
volumes:
- mongo-export:/mongo-export
environment:
- NEO4J_apoc_import_file_enabled=true
db-migration-worker:
build:
context: db-migration-worker
volumes:
- mongo-export:/mongo-export
- uploads:/uploads
- ./db-migration-worker/migration/:/migration
- ./db-migration-worker/.ssh/:/root/.ssh/
networks:
- hc-network
depends_on:
- backend
environment:
- NEO4J_URI=bolt://neo4j:7687
- "SSH_USERNAME=${SSH_USERNAME}"
- "SSH_HOST=${SSH_HOST}"
- "MONGODB_USERNAME=${MONGODB_USERNAME}"
- "MONGODB_PASSWORD=${MONGODB_PASSWORD}"
- "MONGODB_AUTH_DB=${MONGODB_AUTH_DB}"
- "MONGODB_DATABASE=${MONGODB_DATABASE}"
- "UPLOADS_DIRECTORY=${UPLOADS_DIRECTORY}"
volumes:
mongo-export:
uploads:

View File

@ -0,0 +1,23 @@
version: "3.7"
services:
backend:
image: humanconnection/nitro-backend:builder
build:
context: .
target: builder
volumes:
- .:/nitro-backend
- /nitro-backend/node_modules
command: yarn run dev
neo4j:
environment:
- NEO4J_AUTH=none
ports:
- 7687:7687
- 7474:7474
volumes:
- neo4j-data:/data
volumes:
neo4j-data:

View File

@ -0,0 +1,9 @@
version: "3.7"
services:
neo4j:
environment:
- NEO4J_PASSWORD=letmein
backend:
environment:
- NEO4J_PASSWORD=letmein

View File

@ -0,0 +1,14 @@
version: "3.7"
services:
neo4j:
environment:
- NEO4J_AUTH=none
ports:
- 7687:7687
- 7474:7474
backend:
image: humanconnection/nitro-backend:builder
build:
context: .
target: builder

View File

@ -0,0 +1,34 @@
version: "3.7"
services:
backend:
image: humanconnection/nitro-backend:latest
build:
context: .
target: production
networks:
- hc-network
depends_on:
- neo4j
ports:
- 4000:4000
environment:
- NEO4J_URI=bolt://neo4j:7687
- GRAPHQL_PORT=4000
- GRAPHQL_URI=http://localhost:4000
- CLIENT_URI=http://localhost:3000
- JWT_SECRET=b/&&7b78BF&fv/Vd
- MOCK=false
- MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ
- PRIVATE_KEY_PASSPHRASE=a7dsf78sadg87ad87sfagsadg78
neo4j:
image: humanconnection/neo4j:latest
build:
context: neo4j
networks:
- hc-network
networks:
hc-network:
name: hc-network

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
backend/humanconnection.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

3
backend/neo4j/Dockerfile Normal file
View File

@ -0,0 +1,3 @@
FROM neo4j:3.5.0
RUN wget https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.5.0.1/apoc-3.5.0.1-all.jar -P plugins/
COPY migrate.sh /usr/local/bin/migrate

18
backend/neo4j/migrate.sh Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
# If the user has the password `neo4j` this is a strong indicator, that we are
# the initial default user. Before we can create constraints, we have to change
# the default password. This is a security feature of neo4j.
if echo ":exit" | cypher-shell --password neo4j 2> /dev/null ; then
echo "CALL dbms.security.changePassword('${NEO4J_PASSWORD}');" | cypher-shell --password neo4j
fi
set -e
echo '
CALL db.index.fulltext.createNodeIndex("full_text_search",["Post"],["title", "content"]);
CREATE CONSTRAINT ON (p:Post) ASSERT p.slug IS UNIQUE;
CREATE CONSTRAINT ON (c:Category) ASSERT c.slug IS UNIQUE;
CREATE CONSTRAINT ON (u:User) ASSERT u.slug IS UNIQUE;
CREATE CONSTRAINT ON (o:Organization) ASSERT o.slug IS UNIQUE;
' | cypher-shell

100
backend/package.json Normal file
View File

@ -0,0 +1,100 @@
{
"name": "human-connection-backend",
"version": "0.0.1",
"description": "GraphQL Backend for Human Connection",
"main": "src/index.js",
"scripts": {
"build": "babel src/ -d dist/ --copy-files",
"start": "node dist/",
"dev": "nodemon --exec babel-node src/ -e js,graphql",
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js -e js,graphql",
"lint": "eslint src --config .eslintrc.js",
"test": "nyc --reporter=text-lcov yarn test:jest",
"test:cypress": "run-p --race test:before:*",
"test:before:server": "cross-env CLIENT_URI=http://localhost:4123 GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 babel-node src/ 2> /dev/null",
"test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions,activityPub babel-node src/ 2> /dev/null",
"test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand",
"test:cucumber:cmd": "wait-on tcp:4001 tcp:4123 && cucumber-js --require-module @babel/register --exit test/",
"test:jest:cmd:debug": "wait-on tcp:4001 tcp:4123 && node --inspect-brk ./node_modules/.bin/jest -i --forceExit --detectOpenHandles --runInBand",
"test:jest": "run-p --race test:before:* 'test:jest:cmd {@}' --",
"test:cucumber": "run-p --race test:before:* 'test:cucumber:cmd {@}' --",
"test:jest:debug": "run-p --race test:before:* 'test:jest:cmd:debug {@}' --",
"test:coverage": "nyc report --reporter=text-lcov > coverage.lcov",
"db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js",
"db:reset": "babel-node src/seed/reset-db.js",
"db:seed": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 DISABLED_MIDDLEWARES=permissions run-p --race dev db:script:seed"
},
"author": "Human Connection gGmbH",
"license": "MIT",
"jest": {
"verbose": true,
"testMatch": [
"**/src/**/?(*.)+(spec|test).js?(x)"
]
},
"dependencies": {
"activitystrea.ms": "~2.1.3",
"apollo-cache-inmemory": "~1.5.1",
"apollo-client": "~2.5.1",
"apollo-link-context": "~1.0.14",
"apollo-link-http": "~1.5.14",
"apollo-server": "~2.4.8",
"bcryptjs": "~2.4.3",
"cheerio": "~1.0.0-rc.2",
"cors": "~2.8.5",
"cross-env": "~5.2.0",
"date-fns": "2.0.0-alpha.27",
"debug": "~4.1.1",
"dotenv": "~7.0.0",
"express": "~4.16.4",
"faker": "~4.1.0",
"graphql": "~14.2.1",
"graphql-custom-directives": "~0.2.14",
"graphql-iso-date": "~3.6.1",
"graphql-middleware": "~3.0.2",
"graphql-shield": "~5.3.1",
"graphql-tag": "~2.10.1",
"graphql-yoga": "~1.17.4",
"helmet": "~3.16.0",
"jsonwebtoken": "~8.5.1",
"linkifyjs": "~2.1.8",
"lodash": "~4.17.11",
"ms": "~2.1.1",
"neo4j-driver": "~1.7.3",
"neo4j-graphql-js": "~2.4.2",
"node-fetch": "~2.3.0",
"npm-run-all": "~4.1.5",
"request": "~2.88.0",
"sanitize-html": "~1.20.0",
"slug": "~1.1.0",
"trunc-html": "~1.1.2",
"uuid": "~3.3.2",
"wait-on": "~3.2.0"
},
"devDependencies": {
"@babel/cli": "~7.2.3",
"@babel/core": "~7.4.0",
"@babel/node": "~7.2.2",
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/preset-env": "~7.4.3",
"@babel/register": "~7.4.0",
"apollo-server-testing": "~2.4.8",
"babel-core": "~7.0.0-0",
"babel-eslint": "~10.0.1",
"babel-jest": "~24.5.0",
"chai": "~4.2.0",
"cucumber": "~5.1.0",
"eslint": "~5.16.0",
"eslint-config-standard": "~12.0.0",
"eslint-plugin-import": "~2.16.0",
"eslint-plugin-jest": "~22.4.1",
"eslint-plugin-node": "~8.0.1",
"eslint-plugin-promise": "~4.1.1",
"eslint-plugin-standard": "~4.0.0",
"graphql-request": "~1.8.2",
"jest": "~24.7.0",
"nodemon": "~1.18.10",
"nyc": "~13.3.0",
"supertest": "~4.0.2"
}
}

0
backend/public/.gitkeep Normal file
View File

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#84A939" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)" fill="#FEFEFE"><path d="M126.284 211.948c102.42-35.097 185.118-48.813 243.947 3.76-87.197-24.763-166.785-19.12-243.947-3.76zm56.557 37.817c83.2 8.086 125.85 7.018 185.85-18.187-78.351 41.912-114.88 34.814-185.85 18.187zm102.729 20.872c0-1.67 1.563-3.027 3.492-3.027 1.927 0 3.493 1.357 3.493 3.027 0 1.67-1.566 3.026-3.493 3.026-1.93 0-3.492-1.355-3.492-3.026zm-12.265.16c0-1.67 1.562-3.027 3.49-3.027 1.93 0 3.493 1.356 3.493 3.026 0 1.671-1.564 3.026-3.493 3.026-1.928 0-3.49-1.355-3.49-3.026zm-12.426.158c0-1.671 1.567-3.026 3.492-3.026 1.93 0 3.493 1.355 3.493 3.026 0 1.672-1.563 3.026-3.493 3.026-1.925 0-3.492-1.354-3.492-3.026zm59.115-4.762l-4.274 14.591-55.034-.141 44.12-5.849 15.188-8.601zm-186.512-44.348v-5.807c13.51-1.104 33.205-1.49 52.186 2.728l-52.186 3.08zm248.469-4.061c0-26.937-46.004-48.784-112.812-48.784-29.018 0-62.094 4.123-90.233 10.995l-46.092-5.762 3.41 20.454C122.504 201.562 114 209.429 114 217.784c0 8.419 8.628 16.34 22.526 23.251l-3.47 20.823 47.352-5.918a373.092 373.092 0 0 0 24.043 4.893v6.96h20.215l1.573-3.841a400.647 400.647 0 0 0 22.102 1.965l4.608 11.551.326 6.43h66.102l7.206-20.662h-14.52c42.795-7.105 69.888-24.776 69.888-45.452z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/><path id="c" d="M0 .056h291.913v103.538H0z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#84A939" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)"><path d="M247.532 256.288c26.553 0 49.62 4.608 61.301 11.379-10-8.244-33.676-14.039-61.3-14.039-27.625 0-51.301 5.795-61.302 14.039 11.684-6.77 34.75-11.379 61.301-11.379" fill="#E5ECD6"/><path d="M248.467 272.148c28.174 0 52.65 6.146 65.046 15.173-10.611-10.993-35.735-18.718-65.046-18.718-29.31 0-54.433 7.725-65.044 18.718 12.395-9.027 36.872-15.173 65.044-15.173" fill="#B5CB88"/><path d="M248.467 291.044c30.201 0 56.439 6.45 69.725 15.93-11.372-11.54-38.304-19.653-69.725-19.653-31.419 0-58.35 8.113-69.723 19.653 13.286-9.48 39.524-15.93 69.723-15.93" fill="#E5ECD6"/><path d="M248.424 312.164c33.088 0 61.833 6.994 76.39 17.272-12.46-12.512-41.964-21.31-76.39-21.31-34.423 0-63.929 8.798-76.39 21.31 14.558-10.278 43.303-17.272 76.39-17.272" fill="#B5CB88"/><g transform="translate(102 146)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M145.956 28.306c-56.672 0-105.907 7.595-130.842 18.756C36.458 33.475 86.994 23.92 145.956 23.92c58.963 0 109.5 9.556 130.843 23.143-24.937-11.16-74.17-18.756-130.843-18.756m69.48 26.622c-4.413 2.168-9.05 1.749-10.362-.937-1.308-2.684 1.205-6.62 5.617-8.787 4.412-2.166 9.05-1.745 10.36.94 1.31 2.683-1.204 6.618-5.616 8.784m-69.479 36.038c-33.854 0-61.301-7.914-61.301-17.676 0-9.762 27.447-17.678 61.301-17.678 33.855 0 61.302 7.916 61.302 17.678s-27.447 17.676-61.302 17.676m54.659 2.678c-.498 1.453-2.839 1.968-5.228 1.145-2.39-.822-3.925-2.668-3.43-4.122.498-1.455 2.839-1.966 5.23-1.145 2.389.821 3.924 2.665 3.428 4.122m-100.66 0c-.497 1.453-2.838 1.968-5.227 1.145-2.39-.822-3.926-2.668-3.43-4.122.5-1.455 2.837-1.966 5.228-1.145 2.39.821 3.927 2.665 3.43 4.122M77.101 55.188c-4.647-1.597-7.631-5.186-6.667-8.015.967-2.828 5.517-3.826 10.163-2.228 4.648 1.597 7.632 5.187 6.666 8.014-.966 2.828-5.515 3.828-10.162 2.23m148.591-28.937C214.793 11.05 183.223.056 145.956.056 108.689.056 77.12 11.05 66.22 26.252 26.35 33.765 0 46.746 0 61.505c0 23.244 65.347 42.089 145.956 42.089 80.61 0 145.957-18.845 145.957-42.09 0-14.758-26.351-27.739-66.22-35.252" fill="#FEFEFE" mask="url(#d)"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1 @@
<svg width="512" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255-33L476 95v256L255 479 34 351V95z"/><path id="c" d="M0 0h132.6v218.216H0z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#84A939" transform="rotate(30 255 223)" xlink:href="#a"/><g mask="url(#b)"><g transform="translate(181.645 120)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M11.221 66.902C6.636 33.096 34.24 9.57 62.304 9.57c.985 0 1.966.033 2.945.091-26.822 5.83-50.335 27.206-54.028 57.242M67.378.001C30.569 0-5.633 32.784.733 79.817c3.475 25.675 37.881 81.413 65.736 83.034a.02.02 0 0 1-.004.01c-.257.111-.492.482-.524.857-.001-.006-.005-.01-.005-.017a1.132 1.132 0 0 0 .003.205.84.84 0 0 0 .04.173c-.582 1.261-1.33 2.487-1.804 4.159-.67 2.366 3.474 5.2 7.465 2.42 2.523-1.76-1.758-3.08-1.977-4.84a9.117 9.117 0 0 0-.116-.674c3.67 2.972 10.917 10.466 7.945 21.534-1.91 7.111-5.973 11.194-9.56 14.794-4.17 4.19-8.109 8.146-6.837 15.37a1.662 1.662 0 0 0 1.924 1.349 1.664 1.664 0 0 0 1.346-1.927c-.972-5.52 1.918-8.423 5.919-12.442 3.687-3.702 8.275-8.311 10.415-16.28 3.49-12.99-5.024-21.714-9.236-25.105 27.033-5.322 57.021-57.103 60.365-81.804C138.19 33.6 104.183.001 67.378.001" fill="#FEFEFE" mask="url(#d)"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#84A939" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)" fill="#FEFEFE"><path d="M230.035 250.385c.86.402 1.793.598 2.712.593a6.087 6.087 0 0 0 2.66-.63c1.828-.903 3.288-2.82 3.642-5.787-.02-.147-.024-.306-.039-.458h-12.88c-.013.143-.01.303-.026.445.408 2.984 1.992 4.939 3.931 5.837M237.234 99h-12.187c-23.28 13.57-56.759 56.364.884 141.217h11.366c-30.487-66-23.202-120.912-.063-141.217zM259.063 99h12.187c23.28 13.57 56.891 56.364-.751 141.217h-12.167c30.487-66 23.87-120.912.73-141.217zM273.348 244.102c-.01.12-.007.25-.022.363.407 2.987 1.993 4.944 3.93 5.842.863.402 1.794.601 2.713.594a6.115 6.115 0 0 0 2.662-.628 5.906 5.906 0 0 0 1.59-1.179c.157-.176.314-.36.472-.538.85-1.094 1.448-2.571 1.607-4.454h-12.952zM223.388 244.34l-.02-.237h-12.902c.04.311.1.603.165.89a728.456 728.456 0 0 0 4.557 5.28c.63.199 1.277.333 1.924.329a6.091 6.091 0 0 0 2.662-.628c1.795-.887 3.228-2.76 3.614-5.633M245.652 250.096a6.352 6.352 0 0 0 2.711.593 6.138 6.138 0 0 0 2.666-.63c1.859-.92 3.337-2.896 3.654-5.957h-12.897c-.009.129-.013.261-.022.387.455 2.85 2.003 4.73 3.888 5.607M284.012 96.298c-10.502-4.528-22.225-7.073-34.6-7.073-12.373 0-24.095 2.545-34.597 7.073h69.197z"/><path d="M220.209 240.285c-70.207-81.191-29.736-127.722-2.779-141.217h-9.395C184.08 112.061 168 135.971 168 163.328c0 25.886 19.326 53.366 39.087 76.957h13.122zM277 240.285c70.207-81.191 29.736-127.722 2.778-141.217h9.395c23.957 12.993 40.036 36.903 40.036 64.26 0 25.886-19.326 53.366-39.087 76.957H277zM237.045 278.261l2.188-15.376h2.898v4.38h14.439v-4.38h3.45l2.183 15.376h-25.158zm-.603 27.055l2.329-20.592h3.614l2.059 16.025 14.905 5.014-22.907-.447zm43.804-51.73c-.09.002-.177.016-.267.016a9.026 9.026 0 0 1-3.852-.839c-1.689-.784-3.168-2.126-4.188-3.978-.942 1.62-2.257 2.798-3.746 3.535a8.84 8.84 0 0 1-3.846.903 9.036 9.036 0 0 1-3.853-.835c-1.71-.795-3.205-2.163-4.225-4.051-.95 1.929-2.381 3.323-4.049 4.146a8.76 8.76 0 0 1-3.843.906 9.04 9.04 0 0 1-3.857-.838c-1.587-.739-2.997-1.963-4.008-3.646-.95 1.785-2.326 3.083-3.91 3.867a8.782 8.782 0 0 1-3.845.905 9.006 9.006 0 0 1-3.855-.836c-1.687-.785-3.166-2.13-4.186-3.98-.94 1.62-2.256 2.8-3.747 3.536a8.74 8.74 0 0 1-3.173.862c2.917 3.326 5.77 6.544 8.484 9.626h10.363l-2.185 15.376h-4.61v6.463h3.426l-3.516 21.696h.03l3.911 4.67h31.626l3.913-4.67h.008v-.009l.01-.012h-.015l-3.508-21.675h2.891v-6.463h-3.833l-2.186-15.376h9.33c2.664-2.983 5.457-6.092 8.311-9.3zM248.011 99h5.029c16.71 35.173 10.995 57.883 1.544 141.217h-12.886c-10.733-70.609-14.48-113.26 1.544-141.217h4.769z"/><path d="M261.625 249.933a6.384 6.384 0 0 0 2.71.592 6.17 6.17 0 0 0 2.664-.628c1.793-.888 3.225-2.76 3.61-5.636l-.013-.159h-4.205c-.011.025-.02.05-.032.075l-.172-.075h-8.494c.412 2.978 1.995 4.93 3.932 5.831"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#84A939" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)" fill="#FEFEFE"><path d="M126 146l94.405 71.42-38.172-25.45-27.501-3.282 95.226 49.254-28.733 91.941 37.763-51.306-17.24 4.515 87.017-41.867 8.209-41.045 2.463 10.67 30.373-3.282-29.553-20.111-40.225 15.186 18.881 26.269-64.852-66.494z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/><path id="c" d="M0 0h218.936v174.69H0z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#84A939" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)"><g transform="translate(124 138)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M104.134 27.903L5.11 0l-.592.604 97.681 29.448 87.491 33.51L4.473.651 0 5.219l100.81 83.795-39.555 15.02 46.152-9.598L218.08 70.762l.02.055-72.984 20.65-6.912 36.332 17.764 10.131c.265-6.47-6.641-9.97-5.288-17.874a31.598 31.598 0 0 0 1.546-2.977l.12.018a8.186 8.186 0 0 0 2.182-16.225 8.147 8.147 0 0 0-6.094 1.646 8.12 8.12 0 0 1 6.565-2.006c.27.035.532.087.794.139l-1.385-11.538 2.93-.353 1.561 12.991c6.621 3.448 8.733 12.552 1.896 15.793l3.072 25.581.35 2.918-2.55-1.456-7.14-4.072-.178.06c-.01-.006-.019-.015-.029-.021l.173-.059-18.673-10.648-.916-.524.197-1.033 6.836-35.923-31.345 8.87 10.75 16.354c-6.477 5.957-3.536 3.9-10.95 9.05l-15.685 4.519c-1.989 5.116-9.56 10.71 3.271 13.775 14.892-5.856 18.646-6.024 32.718-13.112l27.452 41.764 12.621 1.104 21.208-35.327 26.96-68.784-114.803-42.677z" fill="#FEFEFE" mask="url(#d)"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#84A939" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)" fill="#FEFEFE"><path d="M235.643 286.994c-7.337-3.551-12.277-8.112-15.098-13.57l-116.648-18.373-4.258-23.658 13.038 4.518 10.197 14.959-4.824 2.328 99.665 9.536c-.513-9.03 2.799-19.675 9.271-31.656-1.464 20.596 1.8 37.705 15.463 47.156 23.486 6.045 48.729 7.994 74.61 8.465-28.956 4.908-55.646 3.822-81.416.295zm69.532-27.415l-9.773-8.359-33.12-.45-10.792-3.35 2.282-14.323 57.564 4.523c-14.163-9.303-33.081-17.106-50.387-19.464v-5.573l.071-.34 147.07-6.467-19.661-7.346-126.49 9.388 2.19-10.52h-15.855l1.777 10.453-125.741-9.33-19.66 7.344 146.156 6.427.067.391v4.842c-.049 0-.098-.004-.145-.004-29.507 0-40.107 20.221-42.641 37.652l-79.63-4.934-9.978-19.48-29.413-6.494 4.3 32.913-4.3.502v9.3l42.598 6.01 86.112 18.034h10.441a78.766 78.766 0 0 0 5.452 2.24l1.33 8.006c-2.342 1.298-3.945 3.766-3.945 6.631a7.612 7.612 0 0 0 15.222 0c0-3.778-2.76-6.89-6.366-7.486l-.9-5.403c17.834 5.241 39.833 5.613 67.932 2.156l-.573 3.444c-3.433.736-6.01 3.781-6.01 7.435a7.611 7.611 0 1 0 15.22 0c0-2.995-1.747-5.563-4.265-6.803l.841-5.07 5.734-5.15h5.136c6.532-7.727 10.336-8.41 10.336-26.918 0-1.424-.303-2.907-.843-4.427h-27.343zM223.91 109.559c4.673-.533 9.103.431 13.206 2.575 1.986 1.039 2.466.165 2.88-1.414 2.241-3.537 4.34-7.236 8.198-9.306 11.677-6.263 26.247.19 30.135 13.27 1.77 5.957 1.12 11.827-.688 17.605-1.005 3.22-2.371 6.327-3.573 9.483-4.357 9.065-9.837 17.545-13.952 26.727-.854 1.906-1.6 1.957-3.322 1.064-2.773-1.438-5.53-2.964-8.534-3.923-4.288-1.848-8.602-3.644-12.86-5.56-6.696-3.015-13.344-6.187-18.823-11.203-7.401-6.774-10.928-14.974-7.87-25 2.305-7.555 7.88-11.94 15.204-14.318"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<svg width="512" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255-33L476 95v256L255 479 34 351V95z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#84A939" transform="rotate(30 255 223)" xlink:href="#a"/><g mask="url(#b)" fill="#FEFEFE"><path d="M128.748 228.578l61.936 31.273 188.772-121.85zM215.634 274.035l-14.938 27.141 24.659-22.233zM217.273 271.672l70.296 35.496L382.634 138z"/><path d="M217.495 267.595L358.438 153.66 190.882 264.248l5.072 37.632 16.98-30.852 4.563-3.434z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 654 B

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/><path id="c" d="M0 0h280.118v187.763H0z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#1E70B7" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)"><g transform="translate(108 134)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M163.39 149.606c-15.27-3.601-29.887-9.802-42.72-18.873 27.116 9.276 59.092 11.735 86.94 4.313 5.732 9.119 12.64 13.725 19.001 17.29 3.803 2.266 5.487 3.529 6.159 6.883l.958 4.714c.827 4.517-.43 6.509-3.67 8.897l-4.463 3.087c-5.162 3.798-5.112 11.852 2.063 11.846h21.003c7.333-.043 15.421-8.131 17.356-16.9l13.312-76.946c4.428-27.484-10.356-51.243-29.436-68.604C229.875 7.108 203.244-5.11 177.288 2.105 133.059 14.76 125.194 3.303 106.21 3.8c-12.501.33-18.655 3.835-29.956 8.434-9.583 3.902-19.977 6.67-29.195 11.133 4.37-5.153 9.852-9.154 15.82-12.316-3.005-4.873-11.102-6.727-14.773-3.955-4.665 3.508-8.828 13.264-7.669 19.958-3.305 2.128-6.357 4.614-9.042 7.629-6.095 6.848-8.07 13.003-8.703 16.488-.556 2.559-.892 3.309-2.648 4.437L2.633 66.225c-2.825 1.8-3.648 5.47-1.12 9.4 7.115 11.115 18.142 9.597 23.066 8.214 2.105-.551 2.823-.201 3.986 1.183 4.264 4.786 14.15 8.82 28.89 6.746 4.917-.653 8.087 2.192 7.681 7.185L60.73 154.9c-1.517 10.845-3.575 14.505-11.227 16.699l-15.68 4.317c-6.09 2.032-8.685 11.852-.744 11.846h34.72c6.472-.155 11.16-1.741 15.407-6.341 16.097-17.618 20.644-41.42 26.332-53.78 14.2 12.144 29.457 23.05 33.438 32.4 1.971 4.63.537 9.656-4.626 12.454-4.123 2.115-4.885 10.306 2.794 9.585l18.273-.01c5.423-.116 7.822-4.267 7.524-11.521-.305-6.631-1.65-13.762-3.55-20.944" fill="#FEFEFE" mask="url(#d)"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#1E70B7" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)" fill="#FEFEFE"><path d="M194.65 255.66c4.047 4.982 9.571 3.188 15.086-1.194 8.055-6.402 16.476-17.269 19.764-26.627 2.495-7.093.732-16.373-5.516-20.253-6.478-4.02-13.564.416-16.274 5.952-4.439 9.062-6.906 12.694-13.617 18.74-7.817 6.823-6.24 14.631.557 23.381zm107.461 0c6.797-8.75 8.374-16.559.558-23.382-6.712-6.046-9.178-9.678-13.614-18.74-2.713-5.536-9.796-9.972-16.277-5.952-6.248 3.88-8.011 13.16-5.517 20.253 3.288 9.358 11.709 20.225 19.765 26.627 5.515 4.382 11.041 6.176 15.085 1.193zm-27.558-26.665c4.408 4.596 13.593 2.09 12.798-5.04-.078-.677-.316-1.14-.681-1.583-5.136-6.53-10.59-4.72-12.941 2.797-.51 1.73.058 3.004.824 3.826zm-52.345 0c.766-.822 1.338-2.097.828-3.822-2.354-7.522-7.808-9.332-12.944-2.8-.366.442-.604.905-.682 1.581-.795 7.13 8.393 9.637 12.798 5.041zm-46.864-42.102a134.897 134.897 0 0 1 11.14-16.112c5.049-6.26 10.538-11.57 16.521-15.894l-3.069-3.96c-12.943-16.678-35.918 14.46-24.592 35.966zm118.413-32.006c5.982 4.323 11.472 9.633 16.523 15.894a135.717 135.717 0 0 1 11.14 16.112c11.326-21.507-11.652-52.644-24.595-35.966l-3.068 3.96zm-12.793 148.086c-9.533 6.024-22.681 7.08-32.584-.319-9.899 7.4-23.05 6.343-32.584.32 11.038 26.404 54.131 26.404 65.168 0zm-77.322-162.669c-17.905-23.078-50.233 22.52-30.874 51.153-6.196 11.49-10.812 24.013-13.066 36.701-1.673 4.842-5.068 10.247-8.702 12.538 0 0 3.541 1.435 7.232 1.614.023 3.57.214 6.986.558 10.262-.592 3.486-1.754 6.857-3.452 9.046 0 0 2.194.077 4.793-.356 1.887 9.088 5.145 16.903 9.583 23.556 1.129 3.366 1.695 6.9 1.225 9.64 0 0 2.069-1.012 4.214-2.693.309.336.619.67.936 1.001 2.25 4.433 4.814 11.088 2.906 15.704 0 0 6.777-1.493 9.759-5.2 1.319 2.512 2.001 5.115 1.247 7.41 0 0 2.882-1.73 5.193-4.288 5.679 2.733 11.894 4.871 18.563 6.465-1.526-3.76-2.325-8.128-2.168-13.093-4.717-4.3-7.891-10.057-8.221-16.612-.381-7.577 1.707-13.788 5.644-19.447-.755 2.826-1.166 5.664-1.121 8.508.147 11.827 6.973 20.626 15.905 24.124 8.105 3.18 17.946 1.991 23.146-5.26-.971-.228-1.944-.68-2.916-1.363-5.113-3.562-11.209-5.599-14.235-9.004-2.945-3.307-2.691-8.974 2.608-9.427 3.835-.332 9.649 1.588 15.981 1.588 6.334 0 12.144-1.92 15.984-1.588 5.298.453 5.548 6.12 2.606 9.427-3.029 3.405-9.124 5.442-14.235 9.004-.972.682-1.945 1.135-2.916 1.362 5.201 7.252 15.041 8.44 23.147 5.261 8.93-3.498 15.757-12.297 15.908-24.124.044-2.909-.388-5.813-1.179-8.704 3.995 5.714 6.083 11.978 5.698 19.643-.328 6.555-3.5 12.311-8.219 16.612.159 4.965-.643 9.33-2.17 13.09 6.669-1.594 12.884-3.729 18.564-6.462 2.31 2.558 5.196 4.287 5.196 4.287-.757-2.294-.072-4.897 1.243-7.407 2.986 3.705 9.759 5.198 9.759 5.198-1.907-4.616.657-11.27 2.907-15.704.317-.327.626-.662.936-1 2.141 1.68 4.213 2.692 4.213 2.692-.47-2.74.097-6.274 1.224-9.64 4.443-6.648 7.7-14.468 9.584-23.556 2.599.436 4.793.356 4.793.356-1.698-2.189-2.86-5.555-3.452-9.044.345-3.276.536-6.692.561-10.264 3.688-.179 7.229-1.612 7.229-1.612-3.634-2.293-7.029-7.698-8.702-12.54-2.254-12.688-6.87-25.21-13.065-36.7 19.358-28.634-12.97-74.232-30.875-51.154l-7.396 9.534c-10.869-5.877-23.226-8.89-37.343-8.89-14.113 0-26.473 3.013-37.341 8.89l-7.397-9.534z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/><path id="c" d="M0 0h170.423v190.223H0z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#1E70B7" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)"><g transform="translate(163 133)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M166.489 131.825c-1.473-1.058-3.205-1.59-5.055-1.64-.457 6.738-2.37 13.122-5.414 19.051.17-3.725.102-7.574.139-11.43.365-38.024-25.043-68.757-55.531-70.73-15.427-.998-24.246-2.62-32.522-17.258-4.209-7.476-8.694-13.01-14.506-15.995 5.49.17 9.962 1.896 13.843 4.935 7.902-9.387 12.119-22.148 11.271-34.633-.266-3.669-2.423-4.599-5.458-3.92C55.139 4.406 48.259 17.56 45.958 31.511c-2.137-.257-4.432-.262-6.922.011-18.57 2.036-29.837 12.815-38.007 30.464-6.125 13.231 16.172 29.308 38.519 26.05-4.534 3.588-11.775 5.888-19.235 6.687 3.513 34.395 5.22 55.208 23.367 73.087 2.47 2.45 1.558 5.335-1.846 5.937-7.459 1.338-16.863 4.724-16.792 13.26.079 2.416 1.348 3.198 3.909 3.216h30.665c6.359.03 9.516-1.617 12.461-6.109 5.481-9.447 7.894-13.456 21.287-15.267 4.308-.58 5.463-3.02 4.227-7.052-10.239-33.426 12.827-44.218 29.58-42.314-14.528 2.398-33.529 13.087-21.94 46.98 1.143 3.323.858 6.253-3.664 6.865-7.692.985-19.784 4.091-19.534 13.88.126 2.27 1.351 2.985 3.49 3.017h30.7c7-.122 9.536-2.118 12.4-6.023 5.81-8.472 13.706-8.113 19.675-13.87 3.003-2.897 4.851-6.236 5.996-9.864 4.231-.685 8.758-3.59 12.054-8.185 5.377-7.5 5.442-16.658.141-20.456" fill="#FEFEFE" mask="url(#d)"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/><path id="c" d="M96 134h304.563v187.156H96z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#1E70B7" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M233.572 271.01c-6.76-4.6-11.25-9.185-15.986-13.274 3.792 1.377 8.023 3.209 13.55 5.875 4.745 2.288 10.686 2.315 14.2-3.954 6.953-12.513 6.215-24.384 2.593-35.755 7.358 12.029 11.76 24.825 3.788 39.516 24.56 8.805 51.835 8.101 80.266-2.195-10.935-20.436-10.743-44.23-1.969-63.405-5.608 24.92.452 53.337 22.912 72.526 3.892 3.067 10.577 7.094 19.964 2.115 3.8-2.017 7.911-5.055 11.643-8.464-6.773 10.094-16.673 16.925-26.168 17.712.62 5.349 1.587 9.12 4.62 10.944 1.496.913 1.996 2.159 1.705 4.097l-1.14 7.26c-.613 3.047-1.516 4.663-4.944 5.458-13.168 3.534-10.77 11.695-3.248 11.69h15.154c5.555-.036 7.675-2.274 9.697-5.745l10.8-20.05c2.724-5.373 3.196-9.243 1.618-16.325l-3.724-12.733c-.969-3.3-.899-5.469 1.3-8.149 4.538-5.501 6.385-10.606 7.854-19.371 1.993-12.962 4.435-36.15.066-53.626-5.476-29.484-25.04-49.834-55.373-44.32-58.384 12.407-79.885-1.403-97.514-3.75-21.65-2.876-31.163 8.51-54.29 18.17 6.589-7.674 4.7-17.8 4.358-21.257-17.537.597-19.418 11.694-22.262 24.364-7.197-.895-12.441 2.044-14.888 10.181-6.707 19.633-21.913 21.532-29.577 20.886-1.769-.16-3.044-.723-4.335-2.073-3.484-3.843-5-9.245-4.752-17.793.144-2.482-2.28-2.809-4.11-1.32-4.522 3.993-9.938 13.102-11.465 27.708-.33 2.956-.517 3.703-2.258 6.166-3.195 4.77-5.135 10.542-5.635 16.787-.1 1.517.113 2.431 1.218 3.599 3.305 3.358 7.055 4.495 11.96 4.276 2.502-.089 3.088.005 4.891 1.84 12.791 13.16 20.004 6.143 42.201 8.222 22.875 2.137 39.078-5.802 46.26-22.102-1.034 12.313-6.938 22.012-17.133 28.431 2.766 4.267 6.674 7.578 13.054 9.51 1.803.57 2.582 1.412 2.535 3.635-.1 11.664 1.936 19.135 3.291 26.036 1.909 10.214 4.25 24.27-7.288 27.113-13.167 3.534-10.773 11.695-3.246 11.69h15.153c5.552-.094 9.04-1.625 9.691-5.745 1.826-11.108 3.626-14.96 7.916-24.43 2.928-6.469 5.201-12.769 7.047-19.97" fill="#FEFEFE" mask="url(#d)"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/><path id="c" d="M149 133h197.237v189.77H149z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#1E70B7" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M271.983 301.966c-8.364.88-17.497-1.828-24.363-9.108-6.87 7.276-16.007 9.988-24.373 9.113-9.277 7.49-8.469 20.8 4.571 20.8h39.6c13.044 0 13.847-13.317 4.565-20.805m-5.28-75.342c-.039 1.26 1.02 1.754 2.108 1.212 8.188-4.17 15.895-2.294 20.334-18.03.253-.933-.016-1.463-1.016-1.665-4.606-.938-11.352-2.32-15.98 1.017-4.78 3.45-5.047 11.206-5.446 17.466m-38.173 0c-.399-6.26-.663-14.016-5.447-17.466-4.624-3.337-11.37-1.955-15.975-1.017-1 .202-1.27.732-1.018 1.665 4.44 15.736 12.148 13.86 20.334 18.03 1.09.542 2.15.048 2.106-1.212m19.087-52.888c10.23 0 20.464 1.781 30.69 5.34 2.265.695 3.222-1.32 1.772-2.685-9.789-8.417-21.04-12.329-32.462-12.26-11.42-.069-22.672 3.843-32.46 12.26-1.449 1.365-.494 3.38 1.771 2.685 10.227-3.559 20.458-5.34 30.69-5.34m-25.682 22.118c14.401-3.159 36.964-3.159 51.363 0 2.24.462 2.76-1.326 1.487-2.249-14.989-10.87-39.347-10.87-54.335 0-1.274.923-.754 2.711 1.485 2.25m84.907 91.212c18.226-11.169 27.342-44.08 6.566-76.568-1.112-1.646-3.009-.658-2.366 1.69 5.31 18.81 9.856 44.55-6.773 72.111-1.37 2.442.575 3.904 2.573 2.767m-118.45 0c1.997 1.137 3.945-.325 2.572-2.767-16.632-27.562-12.082-53.302-6.77-72.112.642-2.347-1.256-3.335-2.368-1.689-20.778 32.487-11.66 65.4 6.565 76.568m109.691-72.166c-1.47-1.436-3.266-.266-2.617 1.638 4.863 13.282 5.382 21.047 2.34 34.861-.702 3.111 2.083 4.15 3.734 2.05 8.518-11.839 6.568-27.957-3.457-38.549m-100.929 0c-10.029 10.592-11.98 26.71-3.459 38.548 1.652 2.101 4.436 1.062 3.731-2.049-3.038-13.814-2.519-21.579 2.344-34.86.65-1.905-1.148-3.075-2.616-1.639m10.684 99.41c-.587-4.866 2.393-10.63 8.83-13.823-7.377-2.622-13.304-8.265-15.13-16.082-3.725-15.93 2.586-27.452 9.781-36.574-3.448 8.332-6.012 17.802-4.478 28.098 2.651 17.814 23.762 22.192 38.972 4.347a5.937 5.937 0 0 1-1.229-.718l-10.733-8.355c-4.53-3.792.198-11.802 7.68-8.432 1.526.658 2.075 1.026 3.837 1.01h4.5c1.761.016 2.31-.352 3.838-1.01 7.478-3.37 12.21 4.64 7.68 8.432l-10.738 8.355a5.855 5.855 0 0 1-1.224.718c15.205 17.85 36.32 13.472 38.971-4.347 1.525-10.234-1.002-19.658-4.42-27.952 7.173 9.168 13.435 20.724 9.721 36.428-1.847 7.811-7.772 13.453-15.142 16.076 6.448 3.19 9.431 8.96 8.84 13.83 13.604-4.805 25.07-12.445 33.402-23.119 3.515 2.727 7.033 8.127 8.982 16.097 7.267-11.59 5.723-25.319 2.934-33.122 2.974 1.391 6.955 5.561 9.13 12.083 4.117-12.372 1.098-24.299-4.144-31.306 3.438.722 6.39 3.253 8.212 6.202 1.823-13.045-4.316-26.077-10.38-32.4 2.431.021 5.142 1.17 6.873 2.732-3.695-15.869-13.086-25.616-16.297-28.566a100.124 100.124 0 0 0-5.95-10.615c-3.995-5.673-3.172-11.237 1.441-15.836 18.95-19.27-6.892-57.465-26.66-38.16l-10.595 10.768c-5.095 5.18-9.29 6.324-16.369 4.498a82.25 82.25 0 0 0-20.356-2.532c-7.098 0-13.9.887-20.354 2.532-7.079 1.826-11.274.682-16.37-4.498l-10.598-10.769c-19.765-19.304-45.61 18.89-26.657 38.161 4.615 4.6 5.436 10.163 1.443 15.836a98.73 98.73 0 0 0-5.95 10.615c-3.21 2.95-12.603 12.697-16.298 28.566 1.728-1.563 4.443-2.71 6.873-2.733-6.064 6.324-12.203 19.356-10.38 32.401 1.824-2.949 4.774-5.48 8.213-6.202-5.245 7.007-8.26 18.934-4.148 31.306 2.178-6.522 6.156-10.692 9.13-12.083-2.789 7.803-4.329 21.532 2.934 33.122 1.953-7.97 5.47-13.37 8.985-16.097 8.334 10.67 19.795 18.314 33.398 23.118" fill="#FEFEFE" mask="url(#d)"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#1E70B7" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)" fill="#FEFEFE"><path d="M188.74 245.033c-3.953 0-7.161 1.979-7.161 4.426 0 2.444 3.208 4.427 7.162 4.427 3.954 0 7.161-1.983 7.161-4.427 0-2.447-3.207-4.426-7.161-4.426m21.144 8.09c-7.708-.69-10.217 6.19-21.144 6.19-10.924 0-13.433-6.88-21.144-6.19 5.133 3.257 8.929 12.703 21.144 12.703 12.215 0 16.018-9.446 21.144-12.703m147.05-30.274c2.975-9.662 5.02-12.315 11.313-17.998 31.238-27.646 30.43-51.658 24.545-70.851-5.557 18.187-33.065 4.357-40.66 28.398-13.773-21.315-34.758.35-49.452-13.277 1.532 27.69 25.526 33.642 38.183 48.287 4.444 5.152 3.989 11.73-1.956 15.517-49.149 31.406-100.128 1.906-149.571 1.017-29.169-.523-58.305 6.71-79.76 14.678-9.511 3.825-11.391 10.684-8.035 19.454 13.923 32.119 49.884 52.038 90.795 58.184-16.337.539-32.45-1.076-47.332-4.852-5.706 5.575-12.046 11.423-19.477 17.533 32.45 7.447 57.723.387 78.558-11.304a189.342 189.342 0 0 0 40.502-.944c32.71 10.862 73.437 22.47 117.684-7.714-51.504-16.794-68.083-38.293-121.901-33.977 30.804-9.268 54.943-2.28 83.925 7.978 14.89-12.964 26.424-29.613 32.64-50.129"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M255.5-33L477 95v256L255.5 479 34 351V95z"/><path id="c" d="M0 0h197.01v186.749H0z"/></defs><g transform="translate(1 -1)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#1E70B7" transform="rotate(30 255.5 223)" xlink:href="#a"/><g mask="url(#b)"><g transform="translate(149 135)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M49.467 17.814c-5.418-4.322-11.367-7.31-17.501-7.373-4.208-.017-5.188-.143-7.916-2.47C19.304 3.894 16.122 1.23 11.564.16 9.108-.35 8.214.324 7.7 2.604c-1.15 5.252-2.128 7.936-.598 15.377.496 2.086-1.204 2.514-3.507 1.135-3.663-2.276-5.075 3.447-1.612 7.282 3.112 3.439 7.559 6.192 8.223 15.72.26 3.761.578 6.618.995 9.014.483 2.634.568 3.038.113 5.636-3.691 19.968 4.455 21.12 9.908 33.044 4.086 8.933 8.75 17.055 14.4 22.549 2.416 2.293 3.74 3.88 5.048 7.015 4.922 11.494 9.567 28.374 8.574 50.046-.3 6.38-3.776 10.275-10.251 8.8-3.782-.86-7.311.24-7.3 4.123.006 1.734.618 4.185 3.927 4.405h14.67c3.519-.014 6.028-1.376 7.004-4.792 5.766-21.198 7.343-36.803 8.017-52.548.4-6.025 2.717-9.076 9.089-9.544 15.108-.951 32.244-3.475 46.518-6.29-2.863-9.972-4.225-20.618-2.973-33.955 4.204 27.358 11.447 31.555 28.017 55.235 8.376 11.97 7.227 30.77-4.102 41.189-.913.84-1.938 1.534-3.082 1.988-2.16.862-2.996.672-5.11.189-3.78-.858-7.31.24-7.3 4.123.008 1.734.618 4.185 3.927 4.405h14.67c3.53-.053 5.39-1.316 6.736-3.811l13.022-24.778c3.375-6.477 2.747-11.292-.021-17.974-1.057-2.462-2.313-6.117-3.357-9.33-1.78-5.84-.413-9.097 2.269-13.242 4.059-6.362 10.075-10.92 14.302-20.597-.55 6.769-3.346 12.22-6.521 17.24-3.15 5.21-3.77 8.492-2.05 14.373 3.522 12.064 7.018 22.587 8.536 26.661 3.067 7.749 7.642 9.631 10.662 12.84 1.363 1.376 2.358 1.154 3.126-.572 1.67-4.08 5.263-7.486 5.34-15.825-.115-7.849-1.454-36.989-6.296-61.132-5.165-25.71-17.168-45.135-48.56-44.38-23.8.995-50.62 5.209-69.908-6.633-7.2 7.161-16.35 8.217-25.05 2.631 9.129 2.359 17.167-1.261 22.149-9.938 1.434-2.602 1.067-5.016-1.773-6.18l-13.266-5.427c-1.73-.726-3.018-1.351-4.84-2.862" fill="#FEFEFE" mask="url(#d)"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#AD245D" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><path d="M35.01 367.726c-.08-21.169-.205-53.162 21.257-71.332 3.817-3.253 9.93-7.497 17.321-9.224 2.575-.523 4.956-.756 7.262-.979 4.438-.431 8.27-.804 12.054-2.9l4.954-2.846c9.87-5.655 19.194-10.996 28.226-17.377 5.085-3.632 6.726-15.73 6.095-25.428-.214-2.792-1.893-5.7-3.67-8.777-1.097-1.901-2.232-3.867-3.065-5.916l-.073-.199a56.976 56.976 0 0 1-.422-1.443c-1.195-4.205-1.933-6.378-2.386-7.476-7.029-.944-11.8-8.647-12.888-21.006l-.031-.557c-.645-12.785.808-16.13 2.316-17.716.24-.254.505-.475.783-.666-1.754-16.051 3.115-32.521 13.358-44.704 9.314-11.079 21.955-17.18 35.592-17.18 3.73 0 7.55.458 11.355 1.362 25.63 6.228 41.679 30.27 40.062 59.227.53.251 1.018.61 1.44 1.066 2.752 2.964 2.47 10.97 2.22 14.276l-.024.41c-.335 5.236-.684 10.65-3.052 15.73-1.739 3.918-4.405 6.242-6.76 8.29-2.396 2.089-4.288 3.735-5.294 6.885-.7 2.416-1.645 4.866-2.559 7.235-1.752 4.538-3.407 8.827-3.54 13.244-.427 10.222 1.17 18.391 4.172 21.359 5.097 5.163 13.003 9.391 19.978 13.121 1.6.855 3.166 1.692 4.654 2.517 9.28 5.052 16.07 7.915 25.309 8.557 9.118.849 18.056 5.193 24.754 11.97.736.641 1.82 1.744 3.694 3.648 4.416 4.492 4.416 4.492 4.426 5.852l.007.758c10.783 17.702 11.14 40.656 11.415 58.169l.05 3.28-3.278.028c-42.05.363-84.058.677-126.058.993-42.12.314-84.232.632-126.367.994l-3.273.029-.014-3.274zM329.011 135.763a5.232 5.232 0 0 0-5.223 5.23 5.232 5.232 0 0 0 5.223 5.23 5.236 5.236 0 0 0 5.231-5.23 5.236 5.236 0 0 0-5.23-5.23m0 40.237C309.705 176 294 160.297 294 140.993 294 121.698 309.706 106 329.011 106 348.303 106 364 121.698 364 140.993 364 160.297 348.303 176 329.011 176" fill="#FFF"/><path d="M330.511 101C308.173 101 290 119.164 290 141.492 290 163.828 308.173 182 330.511 182 352.836 182 371 163.828 371 141.492 371 119.164 352.836 101 330.511 101m0 51.022c5.823 0 10.531-4.716 10.531-10.53a10.517 10.517 0 0 0-10.53-10.529 10.51 10.51 0 0 0-10.523 10.529c0 5.814 4.7 10.53 10.522 10.53m0-40.496c16.563 0 29.963 13.406 29.963 29.966 0 16.555-13.4 29.982-29.963 29.982-16.555 0-29.985-13.427-29.985-29.982 0-16.56 13.43-29.966 29.985-29.966" fill="#AD245D"/><path d="M331 106.209c-20.305 0-36.825 16.06-36.825 35.799 0 19.747 16.52 35.813 36.825 35.813 20.306 0 36.827-16.066 36.827-35.813 0-19.74-16.521-35.8-36.827-35.8zM314.287 215l-4.11-21.345c-.324-.129-.648-.265-.972-.404l-18.012 12.169-23.607-23.609 12.186-18.009a63.31 63.31 0 0 1-.403-.968L258 158.712v-33.383l21.361-4.13c.131-.327.267-.652.407-.979l-12.18-18.025 23.608-23.612 18.015 12.198c.322-.137.643-.27.964-.4L314.287 69h33.416l4.13 21.387c.319.13.638.26.956.396l18.024-12.2 23.608 23.612-12.186 18.031c.139.324.273.648.402.971L404 125.33v33.381l-21.37 4.124c-.13.32-.262.64-.398.96l12.19 18.017-23.606 23.609-18.021-12.171c-.32.137-.642.27-.964.402L347.701 215h-33.414z" fill="#FFF"/><path d="M330 171.448c-17.342 0-31.45-13.656-31.45-30.44 0-16.778 14.108-30.427 31.45-30.427 17.341 0 31.449 13.649 31.449 30.426 0 16.785-14.108 30.441-31.45 30.441zM350.979 63h-41.97l-1.64 8.517-1.953 10.156-8.55-5.788-7.18-4.862-6.132 6.132-17.399 17.4-6.126 6.126 4.85 7.178 5.8 8.583-10.173 1.966-8.506 1.646v41.932l8.51 1.643 10.16 1.962-5.787 8.553-4.858 7.179 6.13 6.13 17.399 17.399 6.126 6.126 7.177-4.85 8.56-5.782 1.954 10.141 1.64 8.513H350.975l1.645-8.507 1.964-10.15 8.566 5.787 7.178 4.846 6.124-6.124 17.4-17.399 6.13-6.13-4.858-7.18-5.788-8.554 10.153-1.96 8.51-1.643v-41.932l-8.507-1.646-10.165-1.965 5.8-8.584 4.85-7.178-6.125-6.126-17.4-17.4-6.13-6.13-7.18 4.858-8.558 5.792-1.964-10.166L350.98 63zm-20.98 118.948c23.176 0 41.95-18.318 41.95-40.94 0-22.607-18.774-40.927-41.95-40.927-23.174 0-41.948 18.32-41.948 40.926 0 22.623 18.774 40.941 41.949 40.941zM342.313 73.5l3.855 19.963a47.184 47.184 0 0 1 6.037 2.502l16.824-11.386 17.4 17.4-11.362 16.818a53.171 53.171 0 0 1 2.502 6.066l19.932 3.855v24.601l-19.932 3.848a56.644 56.644 0 0 1-2.502 6.066l11.362 16.795-17.4 17.4-16.824-11.364a44.931 44.931 0 0 1-6.037 2.504l-3.855 19.932H317.68l-3.84-19.932a43.821 43.821 0 0 1-6.043-2.504l-16.818 11.364-17.4-17.4 11.364-16.795a53.759 53.759 0 0 1-2.51-6.066l-19.933-3.848v-24.601l19.933-3.855a50.617 50.617 0 0 1 2.51-6.066l-11.364-16.818 17.4-17.4 16.818 11.386a45.957 45.957 0 0 1 6.043-2.502l3.84-19.963h24.632z" fill="#AD245D"/></g></svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#AD245D" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><g fill="#FFF"><path d="M35.01 367.726c-.08-21.169-.205-53.162 21.257-71.332 3.817-3.253 9.93-7.497 17.321-9.224 2.575-.523 4.956-.756 7.262-.979 4.438-.431 8.27-.804 12.054-2.9l4.954-2.846c9.87-5.655 19.194-10.996 28.226-17.377 5.085-3.632 6.726-15.73 6.095-25.428-.214-2.792-1.893-5.7-3.67-8.777-1.097-1.901-2.232-3.867-3.065-5.916l-.073-.199a56.976 56.976 0 0 1-.422-1.443c-1.195-4.205-1.933-6.378-2.386-7.476-7.029-.944-11.8-8.647-12.888-21.006l-.031-.557c-.645-12.785.808-16.13 2.316-17.716.24-.254.505-.475.783-.666-1.754-16.051 3.115-32.521 13.358-44.704 9.314-11.079 21.955-17.18 35.592-17.18 3.73 0 7.55.458 11.355 1.362 25.63 6.228 41.679 30.27 40.062 59.227.53.251 1.018.61 1.44 1.066 2.752 2.964 2.47 10.97 2.22 14.276l-.024.41c-.335 5.236-.684 10.65-3.052 15.73-1.739 3.918-4.405 6.242-6.76 8.29-2.396 2.089-4.288 3.735-5.294 6.885-.7 2.416-1.645 4.866-2.559 7.235-1.752 4.538-3.407 8.827-3.54 13.244-.427 10.222 1.17 18.391 4.172 21.359 5.097 5.163 13.003 9.391 19.978 13.121 1.6.855 3.166 1.692 4.654 2.517 9.28 5.052 16.07 7.915 25.309 8.557 9.118.849 18.056 5.193 24.754 11.97.736.641 1.82 1.744 3.694 3.648 4.416 4.492 4.416 4.492 4.426 5.852l.007.758c10.783 17.702 11.14 40.656 11.415 58.169l.05 3.28-3.278.028c-42.05.363-84.058.677-126.058.993-42.12.314-84.232.632-126.367.994l-3.273.029-.014-3.274z"/><text font-family="Impact" font-size="118" font-style="condensed" font-weight="700" transform="translate(1 -1)"><tspan x="256" y="208">&lt;/&gt;</tspan></text></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<svg width="512" height="444" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#AD245D" d="M384 .297L511.392 221.65l-128 221.702-255.392.352L.608 222.35 128.608.65z"/><g fill="#FFF"><path d="M34.944 367.726c-.081-21.169-.205-53.162 21.215-71.332 3.81-3.253 9.91-7.497 17.288-9.224 2.57-.523 4.946-.756 7.247-.979 4.43-.431 8.254-.804 12.03-2.9l4.945-2.846c9.851-5.655 19.157-10.996 28.171-17.377 5.075-3.632 6.713-15.73 6.082-25.428-.213-2.792-1.888-5.7-3.662-8.777-1.095-1.901-2.228-3.867-3.059-5.916l-.073-.199a57.061 57.061 0 0 1-.42-1.443c-1.194-4.205-1.93-6.378-2.382-7.476-7.015-.944-11.778-8.647-12.864-21.006l-.03-.557c-.644-12.785.806-16.13 2.31-17.716.241-.254.505-.475.783-.666-1.75-16.051 3.11-32.521 13.331-44.704 9.296-11.079 21.912-17.18 35.524-17.18 3.722 0 7.535.458 11.332 1.362 25.58 6.228 41.597 30.27 39.983 59.227.53.251 1.016.61 1.439 1.066 2.745 2.964 2.464 10.97 2.215 14.276l-.024.41c-.335 5.236-.683 10.65-3.046 15.73-1.736 3.918-4.397 6.242-6.747 8.29-2.391 2.089-4.28 3.735-5.284 6.885-.698 2.416-1.642 4.866-2.554 7.235-1.749 4.538-3.4 8.827-3.534 13.244-.425 10.222 1.17 18.391 4.165 21.359 5.087 5.163 12.977 9.391 19.939 13.121 1.597.855 3.16 1.692 4.645 2.517 9.262 5.052 16.038 7.915 25.259 8.557 9.1.849 18.02 5.193 24.706 11.97.735.641 1.817 1.744 3.687 3.648 4.408 4.492 4.408 4.492 4.417 5.852l.007.758c10.762 17.702 11.12 40.656 11.392 58.169l.05 3.28-3.271.028c-41.968.363-83.894.677-125.812.993-42.038.314-84.067.632-126.12.994l-3.267.029-.013-3.274zM332.387 115.763h15.318v86.734h-15.318v.513l-22.127-17.64v.12h-10.21V211h-22.128v-25.51h-20.424v-52.722h52.762v-.508l22.127-17.065v.568zm34.313 72.093l-7.988-4.591c15.803-27.453 1.717-46.642 1.106-47.443l7.304-5.607c.774.993 18.547 24.675-.422 57.641zm27.361 21.216l-13.866-7.975c27.437-47.66 2.98-80.973 1.918-82.36L394.795 109c1.343 1.723 32.2 42.839-.734 100.072z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M0 .89h265.71v170.52H0z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#84A939" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><path d="M210.84 346.697l2.17.412c3.654-5.4 7.304-10.797 11.167-16.514l-13.948-4.158c.216 7.17.415 13.715.61 20.26" fill="#FEFEFE"/><g transform="translate(124 96.872)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path d="M265.476 135.466c2.28-16.36-12.373-28.485-33.146-37.925-3.264-1.498-32.042-13.807-71.066-11.423-.493-.025-.99-.055-1.485-.077-4.622-.21-8.12-1.25-9.72-6.232 8.024-2.458 14.575-6.522 17.408-15.188 2.705-8.281.812-14.61-5.396-18.966 3.091-4.423 7.693-7.442 11.87-10.785 5.529-4.425 11.32-7.711 18.427-8.009.24.227.53.414.906.525 2.803.837 4.097-3.51 1.295-4.345-1.034-.309-1.896.11-2.368.801-6.4.293-11.767 2.709-17.154 6.329-5.428 3.635-11.634 8.205-15.427 13.712-.48-1.289-2.718-6.195-9.466-8.056-4.63-1.276-7.919-.39-9.835.541 1.488-12.829-8.348-26.169-18.146-33.456-.048-.826-.524-1.61-1.556-1.917-2.804-.835-4.097 3.502-1.295 4.337.41.121.772.127 1.11.054 9.044 6.741 19.157 19.93 16.758 31.839-.028.13-.005.243-.002.363-16.899-.786-23.471 21.104-10.53 35.477-4.684 2.295-5.616 2.748-8.613 1.177-32.087-24.754-64.257-30.463-67.89-31.01C27.605 39.755 8.707 41.88 1.662 56.82-2.182 65.082.903 73.342 8.21 80.396A46.714 46.714 0 0 0 10.342 95.9c13.312 41.39 67.248 17.494 97.338 5.981 1.043 2.698 2.299 5.946 3.627 9.388l1.733.513c-12.762 1.374-25.1 8.753-34.245 20.253 11.938-.127 26.762 2.062 42.317 6.698 15.322 4.568 28.756 10.738 38.637 17.268-1.399-14.62-7.72-27.531-17.685-35.634l.046.013 9.111-6.463c18.754 25.75 51.289 77.278 85.606 49.502a46.669 46.669 0 0 0 10.267-11.807c9.978-1.902 17.079-7.125 18.382-16.146" fill="#FEFEFE" mask="url(#b)"/></g><path d="M239.142 255.648c-17.848-5.32-34.719-7.408-47.378-6.481a74.024 74.024 0 0 0-.923 2.876 70.569 70.569 0 0 0-2.728 14.609c12.465-.643 28.687 1.501 45.785 6.598 16.902 5.04 31.502 12.025 41.573 19.298a70.533 70.533 0 0 0 6.48-16.67c-10.089-7.605-25.188-14.976-42.809-20.23" fill="#E5ECD6"/><path d="M189.62 286.635c3.972 16.568 14.648 29.797 29.836 34.325 15.22 4.537 31.434-.721 43.835-12.47-9.486-5.756-21.669-11.116-35.364-15.198-13.858-4.13-27.135-6.319-38.307-6.657" fill="#FEFEFE"/></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M.073.459h124.425v172.749H.073z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#84A939" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><path d="M232.409 254.848c.268-7.727 7.016-33.547 7.258-39.582.838-19.569-19.295-37.316-35.404-44.459-20.896-9.313-46.108-15.054-68.428-7.619-17.536 5.837-23.16 21.744-17.995 38.456 5.905 18.931 25.96 23.883 43.23 27.984-2.735 6.685-13.588 12.887-16.81 23.005-2.78 8.729-2.97 17.367-1.227 26.318.333 1.703 1.236 3.314 1.819 4.836 5.689 14.863 40.24 34.999 49.693 39.224 3.09 1.366 18.603 9.7 24.605 9.519.476-.016.962-.047 1.453-.096 12.115-1.246 17.577-9.799 19.811-20.828.36-1.791-8.229-49.896-8.005-56.758" fill="#E5ECD6"/><path d="M268.885 204.496c-.268-7.83-8.948-10.453-13.935-7.422-2.966.291-5.702 2.075-6.746 5.894-4.237 15.355-1.235 30.542-.916 46.19.358 17.416-.37 34.49 2.024 51.804 1.158 8.493 14.876 8.837 16.773.612 3.27-14.248 1.438-28.557 1.233-42.969-.291-18.067 2.216-36.006 1.567-54.11" fill="#FEFEFE"/><path d="M268.54 185.138c3.437-13.134 12.433-23.528 20.018-34.472 9.29-13.399 19.905-24.457 35.392-29.791.675.343 1.439.565 2.342.565 6.73 0 6.73-10.44 0-10.44-2.483 0-4.111 1.491-4.696 3.324-13.922 4.858-24.18 13.72-33.687 25.246-10.257 12.408-21.876 27.757-26.012 43.492-1.119 4.336 5.523 6.417 6.643 2.076M251.284 184.303c-2.824-31.263-34.996-55.942-62.636-66.026-.649-1.789-2.215-3.207-4.696-3.207-6.732 0-6.732 10.42 0 10.42.979 0 1.782-.23 2.481-.612 24.382 8.926 55.359 31.37 57.89 59.222.381 4.386 7.37 4.679 6.961.203" fill="#BBD094"/><g transform="translate(273.18 159.325)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path d="M8.09 95.524C7.822 87.797 1.074 61.977.832 55.94c-.838-19.568 19.294-37.316 35.404-44.458 20.896-9.313 46.106-15.055 68.427-7.62C122.2 9.702 127.824 25.608 122.66 42.32c-5.905 18.932-25.961 23.884-43.23 27.984 2.734 6.685 13.588 12.887 16.81 23.005 2.78 8.73 2.97 17.367 1.226 26.318-.334 1.704-1.236 3.315-1.818 4.836-5.69 14.863-40.24 35-49.695 39.225-3.088 1.366-18.601 9.7-24.604 9.518a21.536 21.536 0 0 1-1.453-.095C7.78 171.864 2.32 163.31.085 152.282c-.36-1.792 8.228-49.896 8.005-56.758" fill="#E5ECD6" mask="url(#b)"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M.474.589h222.082V155.85H.474z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#84A939" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><path d="M106 295.77c0-8.772 4.114-14.323 7.973-17.622 3.405-2.91 6.612-4.072 6.612-4.072s.991-13.569 10.898-22.434c.097-.086.18-.179.278-.265 3.804-3.31 8.891-5.92 15.692-6.854.112-.015.214-.039.33-.053.545-.07 1.087-.144 1.654-.193 3.884-.328 7.329-.025 10.403.706 18.218 4.333 22.977 24.233 22.977 24.233s9.721-6.152 19.125.324c9.397 6.48 7.454 15.87 7.454 15.87 17.507-2.266 26.092 4.209 26.092 18.623 0 14.412-17.015 17.001-17.015 17.001h-93.672s-.333-.157-.89-.452C120.203 318.614 106 310.138 106 295.771" fill="#FEFEFE"/><path d="M333.747 275.73c0-55.854-45.477-101.293-101.378-101.293-43.712 0-80.995 27.807-95.194 66.632a36.758 36.758 0 0 0-9.437 5.96l-.479.442c-5.63 5.087-8.934 11.15-10.906 16.39 5.97-58.709 55.707-104.681 116.016-104.681 64.32 0 116.648 52.284 116.648 116.55 0 16.978-3.683 33.105-10.245 47.668h-16.984a100.603 100.603 0 0 0 11.96-47.669" fill="#E5ECD6"/><path d="M320.29 275.73c0-48.44-39.44-87.85-87.923-87.85-34.819 0-64.929 20.35-79.154 49.752-.883.022-1.766.048-2.66.124-.625.051-1.223.127-1.818.205l-.773.112c-3.737.513-7.249 1.487-10.519 2.889 14.22-38.649 51.374-66.313 94.924-66.313 55.784 0 101.17 45.345 101.17 101.08a100.39 100.39 0 0 1-11.987 47.67h-15.386c8.92-13.742 14.127-30.106 14.127-47.67" fill="#CCDBAE"/><path d="M308.526 275.73c0-41.957-34.164-76.092-76.156-76.092-28.736 0-53.744 16.016-66.704 39.555-.62-.182-1.233-.366-1.877-.518-3.268-.778-6.708-1.125-10.244-1.06 14.209-29.225 44.179-49.44 78.825-49.44 48.315 0 87.626 39.278 87.626 87.554 0 17.574-5.227 33.94-14.183 47.67h-14.145c10.533-13.055 16.858-29.635 16.858-47.67" fill="#B5CB88"/><g transform="translate(182.963 98.98)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path d="M104.288 16.014c56.52-.178 102.65 45.62 102.829 102.093.04 13.318-2.485 26.05-7.103 37.743h16.444c3.993-11.874 6.14-24.585 6.097-37.791C222.35 53.079 169.273.382 104.241.588 59.286.73 20.245 26.132.474 63.257c7.3-3.05 14.945-5.433 22.867-7.06 18.668-24.318 47.967-40.079 80.947-40.183" fill="#E5ECD6" mask="url(#b)"/></g><path d="M376.475 217.131c.042 13.462-2.936 26.238-8.287 37.7h14.566c4.626-11.69 7.153-24.425 7.11-37.742-.178-56.354-46.21-102.058-102.612-101.88-32.822.104-61.988 15.75-80.621 39.905 6.557-1.328 13.306-2.117 20.192-2.376 15.83-14.881 37.068-24.076 60.472-24.15 49.019-.155 89.023 39.565 89.18 88.543" fill="#CCDBAE"/><path d="M364.578 217.169c.042 13.669-3.534 26.506-9.78 37.663h13.049a87.88 87.88 0 0 0 8.33-37.7c-.155-48.812-40.028-88.4-88.881-88.246-23.205.076-44.275 9.145-60.024 23.84 1.434-.048 2.867-.108 4.313-.108 4.568 0 9.074.26 13.516.738 12.112-8.07 26.607-12.831 42.23-12.881 42.461-.134 77.112 34.271 77.247 76.694" fill="#B5CB88"/><path d="M354.798 254.83h-.377c.03.174.072.345.102.519.098-.17.18-.349.275-.518" fill="#CABAA1"/></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="#84A939" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><path d="M238.234 309.762c-1.375.258-2.792 1.55-3.708 2.736-1.608 2.081-3.007 1.861-5.165.778-8.43-4.235-16.65-1.328-20.991 6.88-.792 1.496-2.614 2.848-4.257 3.43-6.343 2.24-10.08 6.638-10.719 13.498h98.273c-.897-5.657-3.359-9.528-8.075-12.241-2.12-1.22-4.18-3.107-5.416-5.194-4.232-7.157-10.743-9.897-18.71-7.382-3.109.983-9.193-2.98-10.331-6.279h-6.066c-.46 1.829-1.749 3.193-4.835 3.774" fill="#FEFEFE"/><path d="M252.144 200.674c-2.639 3.425-9.63 13.408-11.283 24.964l-.87 5.505c-2.211-5.534-6.18-10.496-11.796-14.408-11.918-8.303-25.534-11.544-39.636-13.142-6.885-.78-13.842-.944-21.559-1.436 4.949 20.969 11.08 40.049 25.452 55.231 6.342 6.698 14.362 10.992 23.85 10.166 5.9-.514 11.66-2.65 17.862-4.149 4.946 11.405 7.394 24.204 8.876 37.28.143 1.266.255 2.46.227 3.56h5.569c-.882-7.237-1.761-14.484-2.983-21.665-.397-2.335-.79-4.38-.575-5.91.299-1.79.748-1.79 1.582-3.072.884-1.193 2.198-2.26 4.092-3.402 2.143 1.826 3.998 3.807 6.216 5.223 4.423 2.827 7.262 5.105 14.637 5.803 2.418.234 5.006.276 7.187-.337 4.318-1.207 8.743-2.716 12.51-5.075 14.244-8.905 24.206-21.803 32.765-35.977-.592-.457-.912-.851-1.319-.994-14.417-5.058-29.212-8.309-44.538-6.92-8.774.796-17.394 3.209-23.143 10.763-3.56 4.676-6.985 9.634-5.063 16.138-.412 3.335-1.967 5.828-4.244 7.79-1.402-12.036-2.767-39.862 11.142-58.371" fill="#E5ECD6"/><path d="M258.188 206.85s7.083 18.602 21.241 17.487c0 0 20.87.371 21.987-27.161 0 0 21.198 8.399 32.42-4.093 0 0 15.653-18.974-8.567-34.6 0 0 18.63-8.93 11.921-30.137 0 0-6.707-19.347-35.028-7.812 0 0-.746-26.045-23.107-26.788 0 0-23.538.821-21.239 27.53 0 0-23.85-13.022-35.03 5.582 0 0-10.062 18.973 10.435 31.996 0 0-22.36 17.115-7.826 35.719 0 0 10.435 12.649 27.762 4.836m25.716-27.533c-6.827 0-12.36-5.525-12.36-12.342 0-6.816 5.533-12.342 12.36-12.342 6.826 0 12.36 5.526 12.36 12.342 0 6.817-5.534 12.342-12.36 12.342" fill="#FFF"/></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1 @@
<svg width="513" height="444" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M.318.89h249.647v250.352H.318z"/></defs><g fill="none" fill-rule="evenodd"><path fill="#84A939" d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"/><g transform="translate(135.784 96.866)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path d="M124.802 234.855c59.98 0 108.778-48.803 108.778-108.788 0-59.988-48.797-108.79-108.778-108.79-46.9 0-86.909 29.866-102.143 71.563a39.335 39.335 0 0 0-10.123 6.401l-.513.474C5.98 101.181 2.435 107.691.318 113.318 6.725 50.265 60.092.89 124.802.89c69.014 0 125.163 56.153 125.163 125.177 0 69.021-56.15 125.175-125.163 125.175-46.925 0-87.849-25.989-109.267-64.306l19.208-.021c19.576 28.89 52.615 47.94 90.059 47.94" fill="#E5ECD6" mask="url(#b)"/></g><path d="M260.586 114.37c59.852 0 108.55 48.701 108.55 108.562 0 59.86-48.698 108.561-108.55 108.561-37.305 0-70.211-18.96-89.753-47.711l17.75-.02c17.315 20.476 43.151 33.52 72.003 33.52 52.019 0 94.34-42.325 94.34-94.35 0-52.025-42.321-94.35-94.34-94.35-37.36 0-69.668 21.853-84.936 53.43-.947.027-1.892.055-2.851.136-.673.056-1.31.135-1.949.22l-.834.12c-4.005.55-7.774 1.598-11.285 3.104 15.257-41.51 55.121-71.222 101.855-71.222" fill="#CCDBAE"/><path d="M260.586 128.899c51.845 0 94.023 42.182 94.023 94.033s-42.178 94.033-94.023 94.033c-28.657 0-54.333-12.913-71.588-33.202l17.15-.02c14.463 12.977 33.527 20.913 54.438 20.913 45.057 0 81.715-36.662 81.715-81.724 0-45.062-36.658-81.724-81.715-81.724-30.832 0-57.664 17.202-71.57 42.483-.663-.194-1.324-.392-2.016-.556-3.508-.834-7.195-1.207-10.99-1.138 15.245-31.39 47.403-53.098 84.576-53.098" fill="#B5CB88"/><path d="M127 247.046c0-9.422 4.415-15.384 8.555-18.926 3.653-3.125 7.094-4.374 7.094-4.374s1.065-14.572 11.693-24.095c.104-.091.195-.192.299-.285 4.083-3.554 9.543-6.356 16.837-7.361.12-.016.231-.04.354-.057.584-.073 1.167-.154 1.773-.207 4.169-.351 7.867-.026 11.166.76 19.546 4.653 24.653 26.027 24.653 26.027s10.43-6.608 20.522.347c10.082 6.958 7.997 17.045 7.997 17.045 18.783-2.436 27.997 4.52 27.997 20 0 15.478-18.256 18.259-18.256 18.259H147.17s-.357-.167-.954-.485C142.24 271.58 127 262.478 127 247.046" fill="#FEFEFE"/></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,227 @@
import {
extractNameFromId,
extractDomainFromUrl,
signAndSend
} from './utils'
import {
isPublicAddressed,
sendAcceptActivity,
sendRejectActivity
} from './utils/activity'
import request from 'request'
import as from 'activitystrea.ms'
import NitroDataSource from './NitroDataSource'
import router from './routes'
import dotenv from 'dotenv'
import Collections from './Collections'
import uuid from 'uuid/v4'
const debug = require('debug')('ea')
let activityPub = null
export { activityPub }
export default class ActivityPub {
constructor (activityPubEndpointUri, internalGraphQlUri) {
this.endpoint = activityPubEndpointUri
this.dataSource = new NitroDataSource(internalGraphQlUri)
this.collections = new Collections(this.dataSource)
}
static init (server) {
if (!activityPub) {
dotenv.config()
activityPub = new ActivityPub(process.env.CLIENT_URI || 'http://localhost:3000', process.env.GRAPHQL_URI || 'http://localhost:4000')
// integrate into running graphql express server
server.express.set('ap', activityPub)
server.express.use(router)
console.log('-> ActivityPub middleware added to the graphql express server')
} else {
console.log('-> ActivityPub middleware already added to the graphql express server')
}
}
handleFollowActivity (activity) {
debug(`inside FOLLOW ${activity.actor}`)
let toActorName = extractNameFromId(activity.object)
let fromDomain = extractDomainFromUrl(activity.actor)
const dataSource = this.dataSource
return new Promise((resolve, reject) => {
request({
url: activity.actor,
headers: {
'Accept': 'application/activity+json'
}
}, async (err, response, toActorObject) => {
if (err) return reject(err)
// save shared inbox
toActorObject = JSON.parse(toActorObject)
await this.dataSource.addSharedInboxEndpoint(toActorObject.endpoints.sharedInbox)
let followersCollectionPage = await this.dataSource.getFollowersCollectionPage(activity.object)
const followActivity = as.follow()
.id(activity.id)
.actor(activity.actor)
.object(activity.object)
// add follower if not already in collection
if (followersCollectionPage.orderedItems.includes(activity.actor)) {
debug('follower already in collection!')
debug(`inbox = ${toActorObject.inbox}`)
resolve(sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
} else {
followersCollectionPage.orderedItems.push(activity.actor)
}
debug(`toActorObject = ${toActorObject}`)
toActorObject = typeof toActorObject !== 'object' ? JSON.parse(toActorObject) : toActorObject
debug(`followers = ${JSON.stringify(followersCollectionPage.orderedItems, null, 2)}`)
debug(`inbox = ${toActorObject.inbox}`)
debug(`outbox = ${toActorObject.outbox}`)
debug(`followers = ${toActorObject.followers}`)
debug(`following = ${toActorObject.following}`)
try {
await dataSource.saveFollowersCollectionPage(followersCollectionPage)
debug('follow activity saved')
resolve(sendAcceptActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
} catch (e) {
debug('followers update error!', e)
resolve(sendRejectActivity(followActivity, toActorName, fromDomain, toActorObject.inbox))
}
})
})
}
handleUndoActivity (activity) {
debug('inside UNDO')
switch (activity.object.type) {
case 'Follow':
const followActivity = activity.object
return this.dataSource.undoFollowActivity(followActivity.actor, followActivity.object)
case 'Like':
return this.dataSource.deleteShouted(activity)
default:
}
}
handleCreateActivity (activity) {
debug('inside create')
switch (activity.object.type) {
case 'Article':
case 'Note':
const articleObject = activity.object
if (articleObject.inReplyTo) {
return this.dataSource.createComment(activity)
} else {
return this.dataSource.createPost(activity)
}
default:
}
}
handleDeleteActivity (activity) {
debug('inside delete')
switch (activity.object.type) {
case 'Article':
case 'Note':
return this.dataSource.deletePost(activity)
default:
}
}
handleUpdateActivity (activity) {
debug('inside update')
switch (activity.object.type) {
case 'Note':
case 'Article':
return this.dataSource.updatePost(activity)
default:
}
}
handleLikeActivity (activity) {
// TODO differ if activity is an Article/Note/etc.
return this.dataSource.createShouted(activity)
}
handleDislikeActivity (activity) {
// TODO differ if activity is an Article/Note/etc.
return this.dataSource.deleteShouted(activity)
}
async handleAcceptActivity (activity) {
debug('inside accept')
switch (activity.object.type) {
case 'Follow':
const followObject = activity.object
const followingCollectionPage = await this.collections.getFollowingCollectionPage(followObject.actor)
followingCollectionPage.orderedItems.push(followObject.object)
await this.dataSource.saveFollowingCollectionPage(followingCollectionPage)
}
}
getActorObject (url) {
return new Promise((resolve, reject) => {
request({
url: url,
headers: {
'Accept': 'application/json'
}
}, (err, response, body) => {
if (err) {
reject(err)
}
resolve(JSON.parse(body))
})
})
}
generateStatusId (slug) {
return `https://${this.host}/activitypub/users/${slug}/status/${uuid()}`
}
async sendActivity (activity) {
delete activity.send
const fromName = extractNameFromId(activity.actor)
if (Array.isArray(activity.to) && isPublicAddressed(activity)) {
debug('is public addressed')
const sharedInboxEndpoints = await this.dataSource.getSharedInboxEndpoints()
// serve shared inbox endpoints
sharedInboxEndpoints.map((sharedInbox) => {
return this.trySend(activity, fromName, new URL(sharedInbox).host, sharedInbox)
})
activity.to = activity.to.filter((recipient) => {
return !(isPublicAddressed({ to: recipient }))
})
// serve the rest
activity.to.map(async (recipient) => {
debug('serve rest')
const actorObject = await this.getActorObject(recipient)
return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox)
})
} else if (typeof activity.to === 'string') {
debug('is string')
const actorObject = await this.getActorObject(activity.to)
return this.trySend(activity, fromName, new URL(activity.to).host, actorObject.inbox)
} else if (Array.isArray(activity.to)) {
activity.to.map(async (recipient) => {
const actorObject = await this.getActorObject(recipient)
return this.trySend(activity, fromName, new URL(recipient).host, actorObject.inbox)
})
}
}
async trySend (activity, fromName, host, url, tries = 5) {
try {
return await signAndSend(activity, fromName, host, url)
} catch (e) {
if (tries > 0) {
setTimeout(function () {
return this.trySend(activity, fromName, host, url, --tries)
}, 20000)
}
}
}
}

View File

@ -0,0 +1,28 @@
export default class Collections {
constructor (dataSource) {
this.dataSource = dataSource
}
getFollowersCollection (actorId) {
return this.dataSource.getFollowersCollection(actorId)
}
getFollowersCollectionPage (actorId) {
return this.dataSource.getFollowersCollectionPage(actorId)
}
getFollowingCollection (actorId) {
return this.dataSource.getFollowingCollection(actorId)
}
getFollowingCollectionPage (actorId) {
return this.dataSource.getFollowingCollectionPage(actorId)
}
getOutboxCollection (actorId) {
return this.dataSource.getOutboxCollection(actorId)
}
getOutboxCollectionPage (actorId) {
return this.dataSource.getOutboxCollectionPage(actorId)
}
}

View File

@ -0,0 +1,552 @@
import {
throwErrorIfApolloErrorOccurred,
extractIdFromActivityId,
extractNameFromId,
constructIdFromName
} from './utils'
import {
createOrderedCollection,
createOrderedCollectionPage
} from './utils/collection'
import {
createArticleObject,
isPublicAddressed
} from './utils/activity'
import crypto from 'crypto'
import gql from 'graphql-tag'
import { createHttpLink } from 'apollo-link-http'
import { setContext } from 'apollo-link-context'
import { InMemoryCache } from 'apollo-cache-inmemory'
import fetch from 'node-fetch'
import { ApolloClient } from 'apollo-client'
import trunc from 'trunc-html'
const debug = require('debug')('ea:nitro-datasource')
export default class NitroDataSource {
constructor (uri) {
this.uri = uri
const defaultOptions = {
query: {
fetchPolicy: 'network-only',
errorPolicy: 'all'
}
}
const link = createHttpLink({ uri: this.uri, fetch: fetch }) // eslint-disable-line
const cache = new InMemoryCache()
const authLink = setContext((_, { headers }) => {
// generate the authentication token (maybe from env? Which user?)
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJuYW1lIjoiUGV0ZXIgTHVzdGlnIiwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9qb2huY2FmYXp6YS8xMjguanBnIiwiaWQiOiJ1MSIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5vcmciLCJzbHVnIjoicGV0ZXItbHVzdGlnIiwiaWF0IjoxNTUyNDIwMTExLCJleHAiOjE2Mzg4MjAxMTEsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDAwMCIsInN1YiI6InUxIn0.G7An1yeQUViJs-0Qj-Tc-zm0WrLCMB3M02pfPnm6xzw'
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
Authorization: token ? `Bearer ${token}` : ''
}
}
})
this.client = new ApolloClient({
link: authLink.concat(link),
cache: cache,
defaultOptions
})
}
async getFollowersCollection (actorId) {
const slug = extractNameFromId(actorId)
debug(`slug= ${slug}`)
const result = await this.client.query({
query: gql`
query {
User(slug: "${slug}") {
followedByCount
}
}
`
})
debug('successfully fetched followers')
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const followersCount = actor.followedByCount
const followersCollection = createOrderedCollection(slug, 'followers')
followersCollection.totalItems = followersCount
return followersCollection
} else {
throwErrorIfApolloErrorOccurred(result)
}
}
async getFollowersCollectionPage (actorId) {
const slug = extractNameFromId(actorId)
debug(`getFollowersPage slug = ${slug}`)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
followedBy {
slug
}
followedByCount
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const followers = actor.followedBy
const followersCount = actor.followedByCount
const followersCollection = createOrderedCollectionPage(slug, 'followers')
followersCollection.totalItems = followersCount
debug(`followers = ${JSON.stringify(followers, null, 2)}`)
await Promise.all(
followers.map(async (follower) => {
followersCollection.orderedItems.push(constructIdFromName(follower.slug))
})
)
return followersCollection
} else {
throwErrorIfApolloErrorOccurred(result)
}
}
async getFollowingCollection (actorId) {
const slug = extractNameFromId(actorId)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
followingCount
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const followingCount = actor.followingCount
const followingCollection = createOrderedCollection(slug, 'following')
followingCollection.totalItems = followingCount
return followingCollection
} else {
throwErrorIfApolloErrorOccurred(result)
}
}
async getFollowingCollectionPage (actorId) {
const slug = extractNameFromId(actorId)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
following {
slug
}
followingCount
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const following = actor.following
const followingCount = actor.followingCount
const followingCollection = createOrderedCollectionPage(slug, 'following')
followingCollection.totalItems = followingCount
await Promise.all(
following.map(async (user) => {
followingCollection.orderedItems.push(await constructIdFromName(user.slug))
})
)
return followingCollection
} else {
throwErrorIfApolloErrorOccurred(result)
}
}
async getOutboxCollection (actorId) {
const slug = extractNameFromId(actorId)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
contributions {
title
slug
content
contentExcerpt
createdAt
}
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const posts = actor.contributions
const outboxCollection = createOrderedCollection(slug, 'outbox')
outboxCollection.totalItems = posts.length
return outboxCollection
} else {
throwErrorIfApolloErrorOccurred(result)
}
}
async getOutboxCollectionPage (actorId) {
const slug = extractNameFromId(actorId)
debug(`inside getting outbox collection page => ${slug}`)
const result = await this.client.query({
query: gql`
query {
User(slug:"${slug}") {
actorId
contributions {
id
activityId
objectId
title
slug
content
contentExcerpt
createdAt
author {
name
}
}
}
}
`
})
debug(result.data)
if (result.data) {
const actor = result.data.User[0]
const posts = actor.contributions
const outboxCollection = createOrderedCollectionPage(slug, 'outbox')
outboxCollection.totalItems = posts.length
await Promise.all(
posts.map(async (post) => {
outboxCollection.orderedItems.push(await createArticleObject(post.activityId, post.objectId, post.content, post.author.name, post.id, post.createdAt))
})
)
debug('after createNote')
return outboxCollection
} else {
throwErrorIfApolloErrorOccurred(result)
}
}
async undoFollowActivity (fromActorId, toActorId) {
const fromUserId = await this.ensureUser(fromActorId)
const toUserId = await this.ensureUser(toActorId)
const result = await this.client.mutate({
mutation: gql`
mutation {
RemoveUserFollowedBy(from: {id: "${fromUserId}"}, to: {id: "${toUserId}"}) {
from { name }
}
}
`
})
debug(`undoFollowActivity result = ${JSON.stringify(result, null, 2)}`)
throwErrorIfApolloErrorOccurred(result)
}
async saveFollowersCollectionPage (followersCollection, onlyNewestItem = true) {
debug('inside saveFollowers')
let orderedItems = followersCollection.orderedItems
const toUserName = extractNameFromId(followersCollection.id)
const toUserId = await this.ensureUser(constructIdFromName(toUserName))
orderedItems = onlyNewestItem ? [orderedItems.pop()] : orderedItems
return Promise.all(
orderedItems.map(async (follower) => {
debug(`follower = ${follower}`)
const fromUserId = await this.ensureUser(follower)
debug(`fromUserId = ${fromUserId}`)
debug(`toUserId = ${toUserId}`)
const result = await this.client.mutate({
mutation: gql`
mutation {
AddUserFollowedBy(from: {id: "${fromUserId}"}, to: {id: "${toUserId}"}) {
from { name }
}
}
`
})
debug(`addUserFollowedBy edge = ${JSON.stringify(result, null, 2)}`)
throwErrorIfApolloErrorOccurred(result)
debug('saveFollowers: added follow edge successfully')
})
)
}
async saveFollowingCollectionPage (followingCollection, onlyNewestItem = true) {
debug('inside saveFollowers')
let orderedItems = followingCollection.orderedItems
const fromUserName = extractNameFromId(followingCollection.id)
const fromUserId = await this.ensureUser(constructIdFromName(fromUserName))
orderedItems = onlyNewestItem ? [orderedItems.pop()] : orderedItems
return Promise.all(
orderedItems.map(async (following) => {
debug(`follower = ${following}`)
const toUserId = await this.ensureUser(following)
debug(`fromUserId = ${fromUserId}`)
debug(`toUserId = ${toUserId}`)
const result = await this.client.mutate({
mutation: gql`
mutation {
AddUserFollowing(from: {id: "${fromUserId}"}, to: {id: "${toUserId}"}) {
from { name }
}
}
`
})
debug(`addUserFollowing edge = ${JSON.stringify(result, null, 2)}`)
throwErrorIfApolloErrorOccurred(result)
debug('saveFollowing: added follow edge successfully')
})
)
}
async createPost (activity) {
// TODO how to handle the to field? Now the post is just created, doesn't matter who is the recipient
// createPost
const postObject = activity.object
if (!isPublicAddressed(postObject)) {
return debug('createPost: not send to public (sending to specific persons is not implemented yet)')
}
const title = postObject.summary ? postObject.summary : postObject.content.split(' ').slice(0, 5).join(' ')
const postId = extractIdFromActivityId(postObject.id)
debug('inside create post')
let result = await this.client.mutate({
mutation: gql`
mutation {
CreatePost(content: "${postObject.content}", contentExcerpt: "${trunc(postObject.content, 120)}", title: "${title}", id: "${postId}", objectId: "${postObject.id}", activityId: "${activity.id}") {
id
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
// ensure user and add author to post
const userId = await this.ensureUser(postObject.attributedTo)
debug(`userId = ${userId}`)
debug(`postId = ${postId}`)
result = await this.client.mutate({
mutation: gql`
mutation {
AddPostAuthor(from: {id: "${userId}"}, to: {id: "${postId}"}) {
from {
name
}
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
}
async deletePost (activity) {
const result = await this.client.mutate({
mutation: gql`
mutation {
DeletePost(id: "${extractIdFromActivityId(activity.object.id)}") {
title
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
}
async updatePost (activity) {
const postObject = activity.object
const postId = extractIdFromActivityId(postObject.id)
const date = postObject.updated ? postObject.updated : new Date().toISOString()
const result = await this.client.mutate({
mutation: gql`
mutation {
UpdatePost(content: "${postObject.content}", contentExcerpt: "${trunc(postObject.content, 120).html}", id: "${postId}", updatedAt: "${date}") {
title
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
}
async createShouted (activity) {
const userId = await this.ensureUser(activity.actor)
const postId = extractIdFromActivityId(activity.object)
const result = await this.client.mutate({
mutation: gql`
mutation {
AddUserShouted(from: {id: "${userId}"}, to: {id: "${postId}"}) {
from {
name
}
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
if (!result.data.AddUserShouted) {
debug('something went wrong shouting post')
throw Error('User or Post not exists')
}
}
async deleteShouted (activity) {
const userId = await this.ensureUser(activity.actor)
const postId = extractIdFromActivityId(activity.object)
const result = await this.client.mutate({
mutation: gql`
mutation {
RemoveUserShouted(from: {id: "${userId}"}, to: {id: "${postId}"}) {
from {
name
}
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
if (!result.data.AddUserShouted) {
debug('something went wrong disliking a post')
throw Error('User or Post not exists')
}
}
async getSharedInboxEndpoints () {
const result = await this.client.query({
query: gql`
query {
SharedInboxEndpoint {
uri
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
return result.data.SharedInboxEnpoint
}
async addSharedInboxEndpoint (uri) {
try {
const result = await this.client.mutate({
mutation: gql`
mutation {
CreateSharedInboxEndpoint(uri: "${uri}")
}
`
})
throwErrorIfApolloErrorOccurred(result)
return true
} catch (e) {
return false
}
}
async createComment (activity) {
const postObject = activity.object
let result = await this.client.mutate({
mutation: gql`
mutation {
CreateComment(content: "${postObject.content}", activityId: "${extractIdFromActivityId(activity.id)}") {
id
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
const toUserId = await this.ensureUser(activity.actor)
const result2 = await this.client.mutate({
mutation: gql`
mutation {
AddCommentAuthor(from: {id: "${result.data.CreateComment.id}"}, to: {id: "${toUserId}"}) {
id
}
}
`
})
throwErrorIfApolloErrorOccurred(result2)
const postId = extractIdFromActivityId(postObject.inReplyTo)
result = await this.client.mutate({
mutation: gql`
mutation {
AddCommentPost(from: { id: "${result.data.CreateComment.id}", to: { id: "${postId}" }}) {
id
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
}
/**
* This function will search for user existence and will create a disabled user with a random 16 bytes password when no user is found.
*
* @param actorId
* @returns {Promise<*>}
*/
async ensureUser (actorId) {
debug(`inside ensureUser = ${actorId}`)
const name = extractNameFromId(actorId)
const queryResult = await this.client.query({
query: gql`
query {
User(slug: "${name}") {
id
}
}
`
})
if (queryResult.data && Array.isArray(queryResult.data.User) && queryResult.data.User.length > 0) {
debug('ensureUser: user exists.. return id')
// user already exists.. return the id
return queryResult.data.User[0].id
} else {
debug('ensureUser: user not exists.. createUser')
// user does not exist.. create it
const pw = crypto.randomBytes(16).toString('hex')
const slug = name.toLowerCase().split(' ').join('-')
const result = await this.client.mutate({
mutation: gql`
mutation {
CreateUser(password: "${pw}", slug:"${slug}", actorId: "${actorId}", name: "${name}", email: "${slug}@test.org") {
id
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
return result.data.CreateUser.id
}
}
}

View File

@ -0,0 +1,54 @@
import express from 'express'
import { activityPub } from '../ActivityPub'
const debug = require('debug')('ea:inbox')
const router = express.Router()
// Shared Inbox endpoint (federated Server)
// For now its only able to handle Note Activities!!
router.post('/', async function (req, res, next) {
debug(`Content-Type = ${req.get('Content-Type')}`)
debug(`body = ${JSON.stringify(req.body, null, 2)}`)
debug(`Request headers = ${JSON.stringify(req.headers, null, 2)}`)
switch (req.body.type) {
case 'Create':
await activityPub.handleCreateActivity(req.body).catch(next)
break
case 'Undo':
await activityPub.handleUndoActivity(req.body).catch(next)
break
case 'Follow':
await activityPub.handleFollowActivity(req.body).catch(next)
break
case 'Delete':
await activityPub.handleDeleteActivity(req.body).catch(next)
break
/* eslint-disable */
case 'Update':
await activityPub.handleUpdateActivity(req.body).catch(next)
break
case 'Accept':
await activityPub.handleAcceptActivity(req.body).catch(next)
case 'Reject':
// Do nothing
break
case 'Add':
break
case 'Remove':
break
case 'Like':
await activityPub.handleLikeActivity(req.body).catch(next)
break
case 'Dislike':
await activityPub.handleDislikeActivity(req.body).catch(next)
break
case 'Announce':
debug('else!!')
debug(JSON.stringify(req.body, null, 2))
}
/* eslint-enable */
res.status(200).end()
})
export default router

View File

@ -0,0 +1,29 @@
import user from './user'
import inbox from './inbox'
import webFinger from './webFinger'
import express from 'express'
import cors from 'cors'
import verify from './verify'
const router = express.Router()
router.use('/.well-known/webFinger',
cors(),
express.urlencoded({ extended: true }),
webFinger
)
router.use('/activitypub/users',
cors(),
express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'] }),
express.urlencoded({ extended: true }),
user
)
router.use('/activitypub/inbox',
cors(),
express.json({ type: ['application/activity+json', 'application/ld+json', 'application/json'] }),
express.urlencoded({ extended: true }),
verify,
inbox
)
export default router

View File

@ -0,0 +1,43 @@
import { createActor } from '../utils/actor'
const gql = require('graphql-tag')
const debug = require('debug')('ea:serveUser')
export async function serveUser (req, res, next) {
let name = req.params.name
if (name.startsWith('@')) {
name = name.slice(1)
}
debug(`name = ${name}`)
const result = await req.app.get('ap').dataSource.client.query({
query: gql`
query {
User(slug: "${name}") {
publicKey
}
}
`
}).catch(reason => { debug(`serveUser User fetch error: ${reason}`) })
if (result.data && Array.isArray(result.data.User) && result.data.User.length > 0) {
const publicKey = result.data.User[0].publicKey
const actor = createActor(name, publicKey)
debug(`actor = ${JSON.stringify(actor, null, 2)}`)
debug(`accepts json = ${req.accepts(['application/activity+json', 'application/ld+json', 'application/json'])}`)
if (req.accepts(['application/activity+json', 'application/ld+json', 'application/json'])) {
return res.json(actor)
} else if (req.accepts('text/html')) {
// TODO show user's profile page instead of the actor object
/* const outbox = JSON.parse(result.outbox)
const posts = outbox.orderedItems.filter((el) => { return el.object.type === 'Note'})
const actor = result.actor
debug(posts) */
// res.render('user', { user: actor, posts: JSON.stringify(posts)})
return res.json(actor)
}
} else {
debug(`error getting publicKey for actor ${name}`)
next()
}
}

View File

@ -0,0 +1,92 @@
import { sendCollection } from '../utils/collection'
import express from 'express'
import { serveUser } from './serveUser'
import { activityPub } from '../ActivityPub'
import verify from './verify'
const router = express.Router()
const debug = require('debug')('ea:user')
router.get('/:name', async function (req, res, next) {
debug('inside user.js -> serveUser')
await serveUser(req, res, next)
})
router.get('/:name/following', (req, res) => {
debug('inside user.js -> serveFollowingCollection')
const name = req.params.name
if (!name) {
res.status(400).send('Bad request! Please specify a name.')
} else {
const collectionName = req.query.page ? 'followingPage' : 'following'
sendCollection(collectionName, req, res)
}
})
router.get('/:name/followers', (req, res) => {
debug('inside user.js -> serveFollowersCollection')
const name = req.params.name
if (!name) {
return res.status(400).send('Bad request! Please specify a name.')
} else {
const collectionName = req.query.page ? 'followersPage' : 'followers'
sendCollection(collectionName, req, res)
}
})
router.get('/:name/outbox', (req, res) => {
debug('inside user.js -> serveOutboxCollection')
const name = req.params.name
if (!name) {
return res.status(400).send('Bad request! Please specify a name.')
} else {
const collectionName = req.query.page ? 'outboxPage' : 'outbox'
sendCollection(collectionName, req, res)
}
})
router.post('/:name/inbox', verify, async function (req, res, next) {
debug(`body = ${JSON.stringify(req.body, null, 2)}`)
debug(`actorId = ${req.body.actor}`)
// const result = await saveActorId(req.body.actor)
switch (req.body.type) {
case 'Create':
await activityPub.handleCreateActivity(req.body).catch(next)
break
case 'Undo':
await activityPub.handleUndoActivity(req.body).catch(next)
break
case 'Follow':
await activityPub.handleFollowActivity(req.body).catch(next)
break
case 'Delete':
await activityPub.handleDeleteActivity(req.body).catch(next)
break
/* eslint-disable */
case 'Update':
await activityPub.handleUpdateActivity(req.body).catch(next)
break
case 'Accept':
await activityPub.handleAcceptActivity(req.body).catch(next)
case 'Reject':
// Do nothing
break
case 'Add':
break
case 'Remove':
break
case 'Like':
await activityPub.handleLikeActivity(req.body).catch(next)
break
case 'Dislike':
await activityPub.handleDislikeActivity(req.body).catch(next)
break
case 'Announce':
debug('else!!')
debug(JSON.stringify(req.body, null, 2))
}
/* eslint-enable */
res.status(200).end()
})
export default router

View File

@ -0,0 +1,15 @@
import { verifySignature } from '../security'
const debug = require('debug')('ea:verify')
export default async (req, res, next) => {
debug(`actorId = ${req.body.actor}`)
// TODO stop if signature validation fails
if (await verifySignature(`${req.protocol}://${req.hostname}:${req.app.get('port')}${req.originalUrl}`, req.headers)) {
debug('verify = true')
next()
} else {
// throw Error('Signature validation failed!')
debug('verify = false')
next()
}
}

View File

@ -0,0 +1,39 @@
import express from 'express'
import { createWebFinger } from '../utils/actor'
import gql from 'graphql-tag'
const router = express.Router()
router.get('/', async function (req, res) {
const resource = req.query.resource
if (!resource || !resource.includes('acct:')) {
return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.')
} else {
const nameAndDomain = resource.replace('acct:', '')
const name = nameAndDomain.split('@')[0]
let result
try {
result = await req.app.get('ap').dataSource.client.query({
query: gql`
query {
User(slug: "${name}") {
slug
}
}
`
})
} catch (error) {
return res.status(500).json({ error })
}
if (result.data && result.data.User.length > 0) {
const webFinger = createWebFinger(name)
return res.contentType('application/jrd+json').json(webFinger)
} else {
return res.status(404).json({ error: `No record found for ${nameAndDomain}.` })
}
}
})
export default router

View File

@ -0,0 +1,84 @@
import { generateRsaKeyPair, createSignature, verifySignature } from '.'
import crypto from 'crypto'
import request from 'request'
jest.mock('request')
let privateKey
let publicKey
let headers
const passphrase = 'a7dsf78sadg87ad87sfagsadg78'
describe('activityPub/security', () => {
beforeEach(() => {
const pair = generateRsaKeyPair({ passphrase })
privateKey = pair.privateKey
publicKey = pair.publicKey
headers = {
'Date': '2019-03-08T14:35:45.759Z',
'Host': 'democracy-app.de',
'Content-Type': 'application/json'
}
})
describe('createSignature', () => {
describe('returned http signature', () => {
let signatureB64
let httpSignature
beforeEach(() => {
const signer = crypto.createSign('rsa-sha256')
signer.update('(request-target): post /activitypub/users/max/inbox\ndate: 2019-03-08T14:35:45.759Z\nhost: democracy-app.de\ncontent-type: application/json')
signatureB64 = signer.sign({ key: privateKey, passphrase }, 'base64')
httpSignature = createSignature({ privateKey, keyId: 'https://human-connection.org/activitypub/users/lea#main-key', url: 'https://democracy-app.de/activitypub/users/max/inbox', headers, passphrase })
})
it('contains keyId', () => {
expect(httpSignature).toContain('keyId="https://human-connection.org/activitypub/users/lea#main-key"')
})
it('contains default algorithm "rsa-sha256"', () => {
expect(httpSignature).toContain('algorithm="rsa-sha256"')
})
it('contains headers', () => {
expect(httpSignature).toContain('headers="(request-target) date host content-type"')
})
it('contains signature', () => {
expect(httpSignature).toContain('signature="' + signatureB64 + '"')
})
})
})
describe('verifySignature', () => {
let httpSignature
beforeEach(() => {
httpSignature = createSignature({ privateKey, keyId: 'http://localhost:4001/activitypub/users/test-user#main-key', url: 'https://democracy-app.de/activitypub/users/max/inbox', headers, passphrase })
const body = {
'publicKey': {
'id': 'https://localhost:4001/activitypub/users/test-user#main-key',
'owner': 'https://localhost:4001/activitypub/users/test-user',
'publicKeyPem': publicKey
}
}
const mockedRequest = jest.fn((_, callback) => callback(null, null, JSON.stringify(body)))
request.mockImplementation(mockedRequest)
})
it('resolves false', async () => {
await expect(verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)).resolves.toEqual(false)
})
describe('valid signature', () => {
beforeEach(() => {
headers.Signature = httpSignature
})
it('resolves true', async () => {
await expect(verifySignature('https://democracy-app.de/activitypub/users/max/inbox', headers)).resolves.toEqual(true)
})
})
})
})

View File

@ -0,0 +1,154 @@
import dotenv from 'dotenv'
import { resolve } from 'path'
import crypto from 'crypto'
import request from 'request'
const debug = require('debug')('ea:security')
dotenv.config({ path: resolve('src', 'activitypub', '.env') })
export function generateRsaKeyPair (options = {}) {
const { passphrase = process.env.PRIVATE_KEY_PASSPHRASE } = options
return crypto.generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase
}
})
}
// signing
export function createSignature (options) {
const {
privateKey, keyId, url,
headers = {},
algorithm = 'rsa-sha256',
passphrase = process.env.PRIVATE_KEY_PASSPHRASE
} = options
if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) }
const signer = crypto.createSign(algorithm)
const signingString = constructSigningString(url, headers)
signer.update(signingString)
const signatureB64 = signer.sign({ key: privateKey, passphrase }, 'base64')
const headersString = Object.keys(headers).reduce((result, key) => { return result + ' ' + key.toLowerCase() }, '')
return `keyId="${keyId}",algorithm="${algorithm}",headers="(request-target)${headersString}",signature="${signatureB64}"`
}
// verifying
export function verifySignature (url, headers) {
return new Promise((resolve, reject) => {
const signatureHeader = headers['signature'] ? headers['signature'] : headers['Signature']
if (!signatureHeader) {
debug('No Signature header present!')
resolve(false)
}
debug(`Signature Header = ${signatureHeader}`)
const signature = extractKeyValueFromSignatureHeader(signatureHeader, 'signature')
const algorithm = extractKeyValueFromSignatureHeader(signatureHeader, 'algorithm')
const headersString = extractKeyValueFromSignatureHeader(signatureHeader, 'headers')
const keyId = extractKeyValueFromSignatureHeader(signatureHeader, 'keyId')
if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) {
debug('Unsupported hash algorithm specified!')
resolve(false)
}
const usedHeaders = headersString.split(' ')
const verifyHeaders = {}
Object.keys(headers).forEach((key) => {
if (usedHeaders.includes(key.toLowerCase())) {
verifyHeaders[key.toLowerCase()] = headers[key]
}
})
const signingString = constructSigningString(url, verifyHeaders)
debug(`keyId= ${keyId}`)
request({
url: keyId,
headers: {
'Accept': 'application/json'
}
}, (err, response, body) => {
if (err) reject(err)
debug(`body = ${body}`)
const actor = JSON.parse(body)
const publicKeyPem = actor.publicKey.publicKeyPem
resolve(httpVerify(publicKeyPem, signature, signingString, algorithm))
})
})
}
// private: signing
function constructSigningString (url, headers) {
const urlObj = new URL(url)
let signingString = `(request-target): post ${urlObj.pathname}${urlObj.search !== '' ? urlObj.search : ''}`
return Object.keys(headers).reduce((result, key) => {
return result + `\n${key.toLowerCase()}: ${headers[key]}`
}, signingString)
}
// private: verifying
function httpVerify (pubKey, signature, signingString, algorithm) {
if (!SUPPORTED_HASH_ALGORITHMS.includes(algorithm)) { throw Error(`SIGNING: Unsupported hashing algorithm = ${algorithm}`) }
const verifier = crypto.createVerify(algorithm)
verifier.update(signingString)
return verifier.verify(pubKey, signature, 'base64')
}
// private: verifying
// This function can be used to extract the signature,headers,algorithm etc. out of the Signature Header.
// Just pass what you want as key
function extractKeyValueFromSignatureHeader (signatureHeader, key) {
const keyString = signatureHeader.split(',').filter((el) => {
return !!el.startsWith(key)
})[0]
let firstEqualIndex = keyString.search('=')
// When headers are requested add 17 to the index to remove "(request-target) " from the string
if (key === 'headers') { firstEqualIndex += 17 }
return keyString.substring(firstEqualIndex + 2, keyString.length - 1)
}
// Obtained from invoking crypto.getHashes()
export const SUPPORTED_HASH_ALGORITHMS = [
'rsa-md4',
'rsa-md5',
'rsa-mdC2',
'rsa-ripemd160',
'rsa-sha1',
'rsa-sha1-2',
'rsa-sha224',
'rsa-sha256',
'rsa-sha384',
'rsa-sha512',
'blake2b512',
'blake2s256',
'md4',
'md4WithRSAEncryption',
'md5',
'md5-sha1',
'md5WithRSAEncryption',
'mdc2',
'mdc2WithRSA',
'ripemd',
'ripemd160',
'ripemd160WithRSA',
'rmd160',
'sha1',
'sha1WithRSAEncryption',
'sha224',
'sha224WithRSAEncryption',
'sha256',
'sha256WithRSAEncryption',
'sha384',
'sha384WithRSAEncryption',
'sha512',
'sha512WithRSAEncryption',
'ssl3-md5',
'ssl3-sha1',
'whirlpool']

View File

@ -0,0 +1,108 @@
import { activityPub } from '../ActivityPub'
import { signAndSend, throwErrorIfApolloErrorOccurred } from './index'
import crypto from 'crypto'
import as from 'activitystrea.ms'
import gql from 'graphql-tag'
const debug = require('debug')('ea:utils:activity')
export function createNoteObject (text, name, id, published) {
const createUuid = crypto.randomBytes(16).toString('hex')
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `${activityPub.endpoint}/activitypub/users/${name}/status/${createUuid}`,
'type': 'Create',
'actor': `${activityPub.endpoint}/activitypub/users/${name}`,
'object': {
'id': `${activityPub.endpoint}/activitypub/users/${name}/status/${id}`,
'type': 'Note',
'published': published,
'attributedTo': `${activityPub.endpoint}/activitypub/users/${name}`,
'content': text,
'to': 'https://www.w3.org/ns/activitystreams#Public'
}
}
}
export async function createArticleObject (activityId, objectId, text, name, id, published) {
const actorId = await getActorId(name)
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `${activityId}`,
'type': 'Create',
'actor': `${actorId}`,
'object': {
'id': `${objectId}`,
'type': 'Article',
'published': published,
'attributedTo': `${actorId}`,
'content': text,
'to': 'https://www.w3.org/ns/activitystreams#Public'
}
}
}
export async function getActorId (name) {
const result = await activityPub.dataSource.client.query({
query: gql`
query {
User(slug: "${name}") {
actorId
}
}
`
})
throwErrorIfApolloErrorOccurred(result)
if (Array.isArray(result.data.User) && result.data.User[0]) {
return result.data.User[0].actorId
} else {
throw Error(`No user with name: ${name}`)
}
}
export function sendAcceptActivity (theBody, name, targetDomain, url) {
as.accept()
.id(`${activityPub.endpoint}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex'))
.actor(`${activityPub.endpoint}/activitypub/users/${name}`)
.object(theBody)
.prettyWrite((err, doc) => {
if (!err) {
return signAndSend(doc, name, targetDomain, url)
} else {
debug(`error serializing Accept object: ${err}`)
throw new Error('error serializing Accept object')
}
})
}
export function sendRejectActivity (theBody, name, targetDomain, url) {
as.reject()
.id(`${activityPub.endpoint}/activitypub/users/${name}/status/` + crypto.randomBytes(16).toString('hex'))
.actor(`${activityPub.endpoint}/activitypub/users/${name}`)
.object(theBody)
.prettyWrite((err, doc) => {
if (!err) {
return signAndSend(doc, name, targetDomain, url)
} else {
debug(`error serializing Accept object: ${err}`)
throw new Error('error serializing Accept object')
}
})
}
export function isPublicAddressed (postObject) {
if (typeof postObject.to === 'string') {
postObject.to = [postObject.to]
}
if (typeof postObject === 'string') {
postObject.to = [postObject]
}
if (Array.isArray(postObject)) {
postObject.to = postObject
}
return postObject.to.includes('Public') ||
postObject.to.includes('as:Public') ||
postObject.to.includes('https://www.w3.org/ns/activitystreams#Public')
}

View File

@ -0,0 +1,41 @@
import { activityPub } from '../ActivityPub'
export function createActor (name, pubkey) {
return {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'id': `${activityPub.endpoint}/activitypub/users/${name}`,
'type': 'Person',
'preferredUsername': `${name}`,
'name': `${name}`,
'following': `${activityPub.endpoint}/activitypub/users/${name}/following`,
'followers': `${activityPub.endpoint}/activitypub/users/${name}/followers`,
'inbox': `${activityPub.endpoint}/activitypub/users/${name}/inbox`,
'outbox': `${activityPub.endpoint}/activitypub/users/${name}/outbox`,
'url': `${activityPub.endpoint}/activitypub/@${name}`,
'endpoints': {
'sharedInbox': `${activityPub.endpoint}/activitypub/inbox`
},
'publicKey': {
'id': `${activityPub.endpoint}/activitypub/users/${name}#main-key`,
'owner': `${activityPub.endpoint}/activitypub/users/${name}`,
'publicKeyPem': pubkey
}
}
}
export function createWebFinger (name) {
const { host } = new URL(activityPub.endpoint)
return {
'subject': `acct:${name}@${host}`,
'links': [
{
'rel': 'self',
'type': 'application/activity+json',
'href': `${activityPub.endpoint}/activitypub/users/${name}`
}
]
}
}

View File

@ -0,0 +1,70 @@
import { activityPub } from '../ActivityPub'
import { constructIdFromName } from './index'
const debug = require('debug')('ea:utils:collections')
export function createOrderedCollection (name, collectionName) {
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}`,
'summary': `${name}s ${collectionName} collection`,
'type': 'OrderedCollection',
'first': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}?page=true`,
'totalItems': 0
}
}
export function createOrderedCollectionPage (name, collectionName) {
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}?page=true`,
'summary': `${name}s ${collectionName} collection`,
'type': 'OrderedCollectionPage',
'totalItems': 0,
'partOf': `${activityPub.endpoint}/activitypub/users/${name}/${collectionName}`,
'orderedItems': []
}
}
export function sendCollection (collectionName, req, res) {
const name = req.params.name
const id = constructIdFromName(name)
switch (collectionName) {
case 'followers':
attachThenCatch(activityPub.collections.getFollowersCollection(id), res)
break
case 'followersPage':
attachThenCatch(activityPub.collections.getFollowersCollectionPage(id), res)
break
case 'following':
attachThenCatch(activityPub.collections.getFollowingCollection(id), res)
break
case 'followingPage':
attachThenCatch(activityPub.collections.getFollowingCollectionPage(id), res)
break
case 'outbox':
attachThenCatch(activityPub.collections.getOutboxCollection(id), res)
break
case 'outboxPage':
attachThenCatch(activityPub.collections.getOutboxCollectionPage(id), res)
break
default:
res.status(500).end()
}
}
function attachThenCatch (promise, res) {
return promise
.then((collection) => {
res.status(200).contentType('application/activity+json').send(collection)
})
.catch((err) => {
debug(`error getting a Collection: = ${err}`)
res.status(500).end()
})
}

View File

@ -0,0 +1,102 @@
import { activityPub } from '../ActivityPub'
import gql from 'graphql-tag'
import { createSignature } from '../security'
import request from 'request'
const debug = require('debug')('ea:utils')
export function extractNameFromId (uri) {
const urlObject = new URL(uri)
const pathname = urlObject.pathname
const splitted = pathname.split('/')
return splitted[splitted.indexOf('users') + 1]
}
export function extractIdFromActivityId (uri) {
const urlObject = new URL(uri)
const pathname = urlObject.pathname
const splitted = pathname.split('/')
return splitted[splitted.indexOf('status') + 1]
}
export function constructIdFromName (name, fromDomain = activityPub.endpoint) {
return `${fromDomain}/activitypub/users/${name}`
}
export function extractDomainFromUrl (url) {
return new URL(url).host
}
export function throwErrorIfApolloErrorOccurred (result) {
if (result.error && (result.error.message || result.error.errors)) {
throw new Error(`${result.error.message ? result.error.message : result.error.errors[0].message}`)
}
}
export function signAndSend (activity, fromName, targetDomain, url) {
// fix for development: replace with http
url = url.indexOf('localhost') > -1 ? url.replace('https', 'http') : url
debug(`passhprase = ${process.env.PRIVATE_KEY_PASSPHRASE}`)
return new Promise(async (resolve, reject) => {
debug('inside signAndSend')
// get the private key
const result = await activityPub.dataSource.client.query({
query: gql`
query {
User(slug: "${fromName}") {
privateKey
}
}
`
})
if (result.error) {
reject(result.error)
} else {
// add security context
const parsedActivity = JSON.parse(activity)
if (Array.isArray(parsedActivity['@context'])) {
parsedActivity['@context'].push('https://w3id.org/security/v1')
} else {
const context = [parsedActivity['@context']]
context.push('https://w3id.org/security/v1')
parsedActivity['@context'] = context
}
// deduplicate context strings
parsedActivity['@context'] = [...new Set(parsedActivity['@context'])]
const privateKey = result.data.User[0].privateKey
const date = new Date().toUTCString()
debug(`url = ${url}`)
request({
url: url,
headers: {
'Host': targetDomain,
'Date': date,
'Signature': createSignature({ privateKey,
keyId: `${activityPub.endpoint}/activitypub/users/${fromName}#main-key`,
url,
headers: {
'Host': targetDomain,
'Date': date,
'Content-Type': 'application/activity+json'
}
}),
'Content-Type': 'application/activity+json'
},
method: 'POST',
body: JSON.stringify(parsedActivity)
}, (error, response) => {
if (error) {
debug(`Error = ${JSON.stringify(error, null, 2)}`)
reject(error)
} else {
debug('Response Headers:', JSON.stringify(response.headers, null, 2))
debug('Response Body:', JSON.stringify(response.body, null, 2))
resolve()
}
})
}
})
}

View File

@ -0,0 +1,16 @@
import {
GraphQLLowerCaseDirective,
GraphQLTrimDirective,
GraphQLDefaultToDirective
} from 'graphql-custom-directives'
export default function applyDirectives (augmentedSchema) {
const directives = [
GraphQLLowerCaseDirective,
GraphQLTrimDirective,
GraphQLDefaultToDirective
]
augmentedSchema._directives.push.apply(augmentedSchema._directives, directives)
return augmentedSchema
}

View File

@ -0,0 +1,18 @@
import { v1 as neo4j } from 'neo4j-driver'
import dotenv from 'dotenv'
dotenv.config()
let driver
export function getDriver (options = {}) {
const {
uri = process.env.NEO4J_URI || 'bolt://localhost:7687',
username = process.env.NEO4J_USERNAME || 'neo4j',
password = process.env.NEO4J_PASSWORD || 'neo4j'
} = options
if (!driver) {
driver = neo4j.driver(uri, neo4j.auth.basic(username, password))
}
return driver
}

View File

@ -0,0 +1,13 @@
import {
GraphQLDate,
GraphQLTime,
GraphQLDateTime
} from 'graphql-iso-date'
export default function applyScalars (augmentedSchema) {
augmentedSchema._typeMap.Date = GraphQLDate
augmentedSchema._typeMap.Time = GraphQLTime
augmentedSchema._typeMap.DateTime = GraphQLDateTime
return augmentedSchema
}

View File

@ -0,0 +1,29 @@
import fs from 'fs'
import path from 'path'
import userManagement from './resolvers/user_management.js'
import statistics from './resolvers/statistics.js'
import reports from './resolvers/reports.js'
import posts from './resolvers/posts.js'
import moderation from './resolvers/moderation.js'
import rewards from './resolvers/rewards.js'
export const typeDefs = fs
.readFileSync(
process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')
)
.toString('utf-8')
export const resolvers = {
Query: {
...statistics.Query,
...userManagement.Query
},
Mutation: {
...userManagement.Mutation,
...reports.Mutation,
...posts.Mutation,
...moderation.Mutation,
...rewards.Mutation
}
}

View File

@ -0,0 +1,14 @@
/**
* Provide a way to iterate for each element in an array while waiting for async functions to finish
*
* @param array
* @param callback
* @returns {Promise<void>}
*/
async function asyncForEach (array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
export default asyncForEach

View File

@ -0,0 +1,28 @@
/**
* iterate through all fields and replace it with the callback result
* @property data Array
* @property fields Array
* @property callback Function
*/
function walkRecursive (data, fields, callback, _key) {
if (!Array.isArray(fields)) {
throw new Error('please provide an fields array for the walkRecursive helper')
}
if (data && typeof data === 'string' && fields.includes(_key)) {
// well we found what we searched for, lets replace the value with our callback result
data = callback(data, _key)
} else if (data && Array.isArray(data)) {
// go into the rabbit hole and dig through that array
data.forEach((res, index) => {
data[index] = walkRecursive(data[index], fields, callback, index)
})
} else if (data && typeof data === 'object') {
// lets get some keys and stir them
Object.keys(data).forEach(k => {
data[k] = walkRecursive(data[k], fields, callback, k)
})
}
return data
}
export default walkRecursive

17
backend/src/index.js Normal file
View File

@ -0,0 +1,17 @@
import createServer from './server'
import ActivityPub from './activitypub/ActivityPub'
const serverConfig = {
port: process.env.GRAPHQL_PORT || 4000
// cors: {
// credentials: true,
// origin: [process.env.CLIENT_URI] // your frontend url.
// }
}
const server = createServer()
server.start(serverConfig, options => {
/* eslint-disable-next-line no-console */
console.log(`GraphQLServer ready at ${process.env.GRAPHQL_URI} 🚀`)
ActivityPub.init(server)
})

View File

@ -0,0 +1,16 @@
import { request } from 'graphql-request'
// this is the to-be-tested server host
// not to be confused with the seeder host
export const host = 'http://127.0.0.1:4123'
export async function login ({ email, password }) {
const mutation = `
mutation {
login(email:"${email}", password:"${password}")
}`
const response = await request(host, mutation)
return {
authorization: `Bearer ${response.login}`
}
}

30
backend/src/jwt/decode.js Normal file
View File

@ -0,0 +1,30 @@
import jwt from 'jsonwebtoken'
export default async (driver, authorizationHeader) => {
if (!authorizationHeader) return null
const token = authorizationHeader.replace('Bearer ', '')
let id = null
try {
const decoded = await jwt.verify(token, process.env.JWT_SECRET)
id = decoded.sub
} catch {
return null
}
const session = driver.session()
const query = `
MATCH (user:User {id: {id} })
RETURN user {.id, .slug, .name, .avatar, .email, .role, .disabled, .actorId}
LIMIT 1
`
const result = await session.run(query, { id })
session.close()
const [currentUser] = await result.records.map((record) => {
return record.get('user')
})
if (!currentUser) return null
if (currentUser.disabled) return null
return {
token,
...currentUser
}
}

17
backend/src/jwt/encode.js Normal file
View File

@ -0,0 +1,17 @@
import jwt from 'jsonwebtoken'
import ms from 'ms'
// Generate an Access Token for the given User ID
export default function encode (user) {
const token = jwt.sign(user, process.env.JWT_SECRET, {
expiresIn: ms('1d'),
issuer: process.env.GRAPHQL_URI,
audience: process.env.CLIENT_URI,
subject: user.id.toString()
})
// jwt.verifySignature(token, process.env.JWT_SECRET, (err, data) => {
// console.log('token verification:', err, data)
// })
return token
}

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