mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of github.com:Ocelot-Social-Community/Ocelot-Social into 6179-add-script-to-set-neo4j-in-offline-mode-and-back
This commit is contained in:
commit
202353bcb4
157
.codecov.yml
157
.codecov.yml
@ -1,157 +0,0 @@
|
||||
codecov:
|
||||
#token: uuid # Your private repository token
|
||||
#url: "http" # for Codecov Enterprise customers
|
||||
#slug: "owner/repo" # for Codecov Enterprise customers
|
||||
#branch: master # override the default branch
|
||||
#bot: username # set user whom will be the consumer of oauth requests
|
||||
#ci: # Custom CI domains if Codecov does not identify them automatically
|
||||
# - ci.domain.com
|
||||
# - !provider # ignore these providers when checking if CI passed
|
||||
# # ex. You may test on Travis, Circle, and AppVeyor, but only need
|
||||
# # to check if Travis passes. Therefore add: !circle and !appveyor
|
||||
notify:
|
||||
#after_n_builds: null # number of expected builds to recieve before sending notifications
|
||||
# # after: check ci status unless disabled via require_ci_to_pass
|
||||
require_ci_to_pass: yes # yes: will delay sending notifications until all ci is finished
|
||||
# no: will send notifications without checking ci status and wait till "after_n_builds" are uploaded
|
||||
#countdown: null # number of seconds to wait before first ci build check
|
||||
#delay: null # number of seconds to wait between ci build checks
|
||||
|
||||
coverage:
|
||||
precision: 2 # 2 = xx.xx%, 0 = xx%
|
||||
round: nearest # down|up|nearest - default down
|
||||
# range: 50...60 # default 70...90. red...green
|
||||
|
||||
#notify:
|
||||
# irc:
|
||||
# default:
|
||||
# server: "chat.freenode.net"|encrypted
|
||||
# branches: null # all branches by default
|
||||
# threshold: 1%
|
||||
# message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message
|
||||
# flags: null
|
||||
# paths: null
|
||||
#
|
||||
# slack:
|
||||
# default:
|
||||
# url: "http"|encrypted
|
||||
# threshold: 1%
|
||||
# branches: null # all branches by default
|
||||
# message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message
|
||||
# attachments: "sunburst, diff"
|
||||
# only_pulls: false
|
||||
# flags: null
|
||||
# paths: null
|
||||
#
|
||||
# email:
|
||||
# default:
|
||||
# to:
|
||||
# - example@domain.com
|
||||
# - &author
|
||||
# threshold: 1%
|
||||
# only_pulls: false
|
||||
# layout: header, diff, trends
|
||||
# flags: null
|
||||
# paths: null
|
||||
#
|
||||
# hipchat:
|
||||
# default:
|
||||
# url: "http"|encrypted
|
||||
# room: name|id
|
||||
# threshold: 1%
|
||||
# token: encrypted
|
||||
# branches: null # all branches by default
|
||||
# notify: false # if the hipchat message is silent or loud (default false)
|
||||
# message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message
|
||||
# flags: null
|
||||
# paths: null
|
||||
#
|
||||
# gitter:
|
||||
# url: "http"|encrypted
|
||||
# threshold: 1%
|
||||
# branches: null # all branches by default
|
||||
# message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message
|
||||
#
|
||||
# webhooks:
|
||||
# _name_:
|
||||
# url: "http"|encrypted
|
||||
# threshold: 1%
|
||||
# branches: null # all branches by default
|
||||
|
||||
status:
|
||||
project:
|
||||
default: false # disable the default status that measures entire project
|
||||
backend: # declare a new status context "backend"
|
||||
against: parent
|
||||
target: auto
|
||||
threshold: null
|
||||
#threshold: 1%
|
||||
base: auto
|
||||
if_no_uploads: error
|
||||
if_not_found: success
|
||||
if_ci_failed: error
|
||||
only_pulls: false
|
||||
#branches:
|
||||
# - master
|
||||
#flags:
|
||||
# - integration
|
||||
paths:
|
||||
- backend/ # only include coverage in "backend/" folder
|
||||
webapp: # declare a new status context "frontend"
|
||||
against: parent
|
||||
target: auto
|
||||
threshold: null
|
||||
#threshold: 1%
|
||||
base: auto
|
||||
if_no_uploads: error
|
||||
if_not_found: success
|
||||
if_ci_failed: error
|
||||
only_pulls: false
|
||||
#branches:
|
||||
# - master
|
||||
#flags:
|
||||
# - integration
|
||||
paths:
|
||||
- webapp/ # only include coverage in "webapp/" folder
|
||||
|
||||
patch:
|
||||
default: false
|
||||
# against: parent
|
||||
# target: 80%
|
||||
# branches: null
|
||||
# if_no_uploads: success
|
||||
# if_not_found: success
|
||||
# if_ci_failed: error
|
||||
# only_pulls: false
|
||||
# flags:
|
||||
# - integration
|
||||
# paths:
|
||||
# - folder
|
||||
|
||||
#changes:
|
||||
# default:
|
||||
# against: parent
|
||||
# branches: null
|
||||
# if_no_uploads: error
|
||||
# if_not_found: success
|
||||
# if_ci_failed: error
|
||||
# only_pulls: false
|
||||
# flags:
|
||||
# - integration
|
||||
# paths:
|
||||
# - folder
|
||||
|
||||
#flags:
|
||||
# integration:
|
||||
# branches:
|
||||
# - master
|
||||
# ignore:
|
||||
# - app/ui
|
||||
|
||||
#ignore: # files and folders for processing
|
||||
# - tests/*
|
||||
|
||||
#fixes:
|
||||
# - "old_path::new_path"
|
||||
|
||||
comment: off
|
||||
13
.github/file-filters.yml
vendored
Normal file
13
.github/file-filters.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
backend: &backend
|
||||
- '.github/workflows/test-backend.yml'
|
||||
- 'backend/**/*'
|
||||
- 'neo4j/**/*'
|
||||
- 'package.json'
|
||||
|
||||
docker: &docker
|
||||
- 'docker-compose.*'
|
||||
|
||||
webapp: &webapp
|
||||
- '.github/workflows/test-webapp.yml'
|
||||
- 'webapp/**/*'
|
||||
- 'package.json'
|
||||
42
.github/workflows/cleanup-cache-at-pr-closing.yml
vendored
Normal file
42
.github/workflows/cleanup-cache-at-pr-closing.yml
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
###############################################################################
|
||||
# A Github repo has max 10 GB of cache.
|
||||
# https://github.blog/changelog/2021-11-23-github-actions-cache-size-is-now-increased-to-10gb-per-repository/
|
||||
#
|
||||
# To avoid "cache thrashing" by their cache eviction policy it is recommended
|
||||
# to apply a cache cleanup workflow at PR closing to dele cache leftovers of
|
||||
# the current branch:
|
||||
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
|
||||
###############################################################################
|
||||
|
||||
name: ocelot.social cache cleanup on pr closing
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
clean-branch-cache:
|
||||
name: Cleanup branch cache
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
REPO=${{ github.repository }}
|
||||
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
|
||||
echo "Fetching list of cache key"
|
||||
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
|
||||
set +e
|
||||
echo "Deleting caches..."
|
||||
for cacheKey in $cacheKeysForPR
|
||||
do
|
||||
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
|
||||
done
|
||||
echo "Done"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
20
.github/workflows/publish.yml
vendored
20
.github/workflows/publish.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
@ -47,7 +47,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
@ -96,7 +96,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
@ -145,7 +145,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup env
|
||||
run: |
|
||||
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
|
||||
@ -243,7 +243,7 @@ jobs:
|
||||
needs: [upload_to_dockerhub]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch full History for changelog
|
||||
- name: Setup env
|
||||
@ -283,7 +283,7 @@ jobs:
|
||||
needs: [github_tag]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch full History for changelog
|
||||
- name: Setup env
|
||||
@ -306,4 +306,12 @@ jobs:
|
||||
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
|
||||
event-type: trigger-ocelot-build-success
|
||||
repository: 'Ocelot-Social-Community/stage.ocelot.social'
|
||||
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
|
||||
- name: Repository Dispatch stage.yunite.me
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
|
||||
event-type: trigger-ocelot-build-success
|
||||
repository: 'Yunite-Net/stage.yunite.me'
|
||||
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
|
||||
141
.github/workflows/test-backend.yml
vendored
Normal file
141
.github/workflows/test-backend.yml
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
name: ocelot.social backend test CI
|
||||
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Detect File Changes - Backend
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
backend: ${{ steps.changes.outputs.backend }}
|
||||
docker: ${{ steps.changes.outputs.docker }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for backend file changes
|
||||
uses: dorny/paths-filter@v2.11.1
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-filters.yml
|
||||
list-files: shell
|
||||
|
||||
build_test_neo4j:
|
||||
name: Docker Build Test - Neo4J
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.docker == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Neo4J | Build 'community' image
|
||||
run: |
|
||||
docker build --target community -t "ocelotsocialnetwork/neo4j-community:test" neo4j/
|
||||
docker save "ocelotsocialnetwork/neo4j-community:test" > /tmp/neo4j.tar
|
||||
|
||||
- name: Cache docker images
|
||||
id: cache-neo4j
|
||||
uses: actions/cache/save@v3.3.2
|
||||
with:
|
||||
path: /tmp/neo4j.tar
|
||||
key: ${{ github.run_id }}-backend-neo4j-cache
|
||||
|
||||
build_test_backend:
|
||||
name: Docker Build Test - Backend
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.docker == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: backend | Build 'test' image
|
||||
run: |
|
||||
docker build --target test -t "ocelotsocialnetwork/backend:test" backend/
|
||||
docker save "ocelotsocialnetwork/backend:test" > /tmp/backend.tar
|
||||
|
||||
- name: Cache docker images
|
||||
id: cache-backend
|
||||
uses: actions/cache/save@v3.3.2
|
||||
with:
|
||||
path: /tmp/backend.tar
|
||||
key: ${{ github.run_id }}-backend-cache
|
||||
|
||||
lint_backend:
|
||||
name: Lint Backend
|
||||
if: needs.files-changed.outputs.backend == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: backend | Lint
|
||||
run: cd backend && yarn && yarn run lint
|
||||
|
||||
unit_test_backend:
|
||||
name: Unit tests - Backend
|
||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.docker == 'true'
|
||||
needs: [files-changed, build_test_neo4j, build_test_backend]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore Neo4J cache
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
with:
|
||||
path: /tmp/neo4j.tar
|
||||
key: ${{ github.run_id }}-backend-neo4j-cache
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Restore Backend cache
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
with:
|
||||
path: /tmp/backend.tar
|
||||
key: ${{ github.run_id }}-backend-cache
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Load Docker Images
|
||||
run: |
|
||||
docker load < /tmp/neo4j.tar
|
||||
docker load < /tmp/backend.tar
|
||||
|
||||
- name: backend | copy env files
|
||||
run: |
|
||||
cp webapp/.env.template webapp/.env
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- name: backend | docker-compose
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps neo4j backend
|
||||
|
||||
- name: backend | Initialize Database
|
||||
run: docker-compose exec -T backend yarn db:migrate init
|
||||
|
||||
- name: backend | Migrate Database Up
|
||||
run: docker-compose exec -T backend yarn db:migrate up
|
||||
|
||||
- name: backend | Unit test incl. coverage check
|
||||
run: docker-compose exec -T backend yarn test
|
||||
|
||||
cleanup:
|
||||
name: Cleanup
|
||||
if: ${{ needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.docker == 'true' }}
|
||||
needs: [files-changed, unit_test_backend]
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Delete cache
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
KEY="${{ github.run_id }}-backend-neo4j-cache"
|
||||
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm
|
||||
KEY="${{ github.run_id }}-backend-cache"
|
||||
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm
|
||||
113
.github/workflows/test-e2e.yml
vendored
Normal file
113
.github/workflows/test-e2e.yml
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
name: ocelot.social end-to-end test CI
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
docker_preparation:
|
||||
name: Fullstack test preparation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Copy env files
|
||||
run: |
|
||||
cp webapp/.env.template webapp/.env
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- name: Build docker images
|
||||
run: |
|
||||
mkdir /tmp/images
|
||||
docker build --target community -t "ocelotsocialnetwork/neo4j-community:test" neo4j/
|
||||
docker save "ocelotsocialnetwork/neo4j-community:test" > /tmp/images/neo4j.tar
|
||||
docker build --target test -t "ocelotsocialnetwork/backend:test" backend/
|
||||
docker save "ocelotsocialnetwork/backend:test" > /tmp/images/backend.tar
|
||||
docker build --target test -t "ocelotsocialnetwork/webapp:test" webapp/
|
||||
docker save "ocelotsocialnetwork/webapp:test" > /tmp/images/webapp.tar
|
||||
|
||||
- name: Install cypress requirements
|
||||
run: |
|
||||
wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386"
|
||||
cd backend
|
||||
yarn install
|
||||
yarn build
|
||||
cd ..
|
||||
yarn install
|
||||
|
||||
- name: Cache docker images
|
||||
id: cache
|
||||
uses: actions/cache/save@v3.3.2
|
||||
with:
|
||||
path: |
|
||||
/opt/cucumber-json-formatter
|
||||
/home/runner/.cache/Cypress
|
||||
/home/runner/work/Ocelot-Social/Ocelot-Social
|
||||
/tmp/images/
|
||||
key: ${{ github.run_id }}-e2e-preparation-cache
|
||||
|
||||
fullstack_tests:
|
||||
name: Fullstack tests
|
||||
if: success()
|
||||
needs: docker_preparation
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
jobs: 8
|
||||
strategy:
|
||||
matrix:
|
||||
# run copies of the current job in parallel
|
||||
job: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
steps:
|
||||
- name: Restore cache
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
id: cache
|
||||
with:
|
||||
path: |
|
||||
/opt/cucumber-json-formatter
|
||||
/home/runner/.cache/Cypress
|
||||
/home/runner/work/Ocelot-Social/Ocelot-Social
|
||||
/tmp/images/
|
||||
key: ${{ github.run_id }}-e2e-preparation-cache
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Boot up test system | docker-compose
|
||||
run: |
|
||||
chmod +x /opt/cucumber-json-formatter
|
||||
sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter
|
||||
docker load < /tmp/images/neo4j.tar
|
||||
docker load < /tmp/images/backend.tar
|
||||
docker load < /tmp/images/webapp.tar
|
||||
docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend
|
||||
sleep 90s
|
||||
|
||||
- name: Full stack tests | run tests
|
||||
id: e2e-tests
|
||||
run: yarn run cypress:run --spec $(cypress/parallel-features.sh ${{ matrix.job }} ${{ env.jobs }} )
|
||||
|
||||
- name: Full stack tests | if tests failed, compile html report
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
run: |
|
||||
cd cypress/
|
||||
node create-cucumber-html-report.js
|
||||
|
||||
- name: Full stack tests | if tests failed, upload report
|
||||
id: e2e-report
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ocelot-e2e-test-report-pr${{ needs.docker_preparation.outputs.pr-number }}
|
||||
path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report
|
||||
|
||||
cleanup:
|
||||
name: Cleanup
|
||||
needs: [docker_preparation, fullstack_tests]
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Delete cache
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
KEY="${{ github.run_id }}-e2e-preparation-cache"
|
||||
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm
|
||||
116
.github/workflows/test-webapp.yml
vendored
Normal file
116
.github/workflows/test-webapp.yml
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
name: ocelot.social webapp test CI
|
||||
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
files-changed:
|
||||
name: Detect File Changes - Webapp
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
docker: ${{ steps.changes.outputs.docker }}
|
||||
webapp: ${{ steps.changes.outputs.webapp }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Check for frontend file changes
|
||||
uses: dorny/paths-filter@v2.11.1
|
||||
id: changes
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
filters: .github/file-filters.yml
|
||||
list-files: shell
|
||||
|
||||
prepare:
|
||||
name: Prepare
|
||||
if: needs.files-changed.outputs.webapp == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check translation files
|
||||
run: |
|
||||
scripts/translations/sort.sh
|
||||
scripts/translations/missing-keys.sh
|
||||
|
||||
build_test_webapp:
|
||||
name: Docker Build Test - Webapp
|
||||
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp == 'true'
|
||||
needs: [files-changed, prepare]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Webapp | Build 'test' image
|
||||
run: |
|
||||
docker build --target test -t "ocelotsocialnetwork/webapp:test" webapp/
|
||||
docker save "ocelotsocialnetwork/webapp:test" > /tmp/webapp.tar
|
||||
|
||||
- name: Cache docker image
|
||||
uses: actions/cache/save@v3.3.2
|
||||
with:
|
||||
path: /tmp/webapp.tar
|
||||
key: ${{ github.run_id }}-webapp-cache
|
||||
|
||||
lint_webapp:
|
||||
name: Lint Webapp
|
||||
if: needs.files-changed.outputs.webapp == 'true'
|
||||
needs: files-changed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: webapp | Lint
|
||||
run: cd webapp && yarn && yarn run lint
|
||||
|
||||
unit_test_webapp:
|
||||
name: Unit Tests - Webapp
|
||||
if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp == 'true'
|
||||
needs: [files-changed, build_test_webapp]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore webapp cache
|
||||
uses: actions/cache/restore@v3.3.2
|
||||
with:
|
||||
path: /tmp/webapp.tar
|
||||
key: ${{ github.run_id }}-webapp-cache
|
||||
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/webapp.tar
|
||||
|
||||
- name: Copy env files
|
||||
run: |
|
||||
cp webapp/.env.template webapp/.env
|
||||
cp backend/.env.template backend/.env
|
||||
|
||||
- name: backend | docker-compose
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp
|
||||
|
||||
- name: webapp | Unit tests incl. coverage check
|
||||
run: docker-compose exec -T webapp yarn test
|
||||
|
||||
cleanup:
|
||||
name: Cleanup
|
||||
if: ${{ needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.webapp == 'true' }}
|
||||
needs: [files-changed, unit_test_webapp]
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Delete cache
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
KEY="${{ github.run_id }}-webapp-cache"
|
||||
gh actions-cache delete $KEY -R Ocelot-Social-Community/Ocelot-Social --confirm
|
||||
|
||||
344
.github/workflows/test.yml
vendored
344
.github/workflows/test.yml
vendored
@ -1,344 +0,0 @@
|
||||
name: ocelot.social test CI
|
||||
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
##############################################################################
|
||||
# JOB: PREPARE #####################################################
|
||||
##############################################################################
|
||||
prepare:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
# needs: [nothing]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# TODO: DO STUFF ??? #####################################################
|
||||
##########################################################################
|
||||
- name: Check translation files
|
||||
run: |
|
||||
scripts/translations/sort.sh
|
||||
scripts/translations/missing-keys.sh
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD TEST NEO4J ###############################################
|
||||
##############################################################################
|
||||
build_test_neo4j:
|
||||
name: Docker Build Test - Neo4J
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# NEO4J ##################################################################
|
||||
##########################################################################
|
||||
- name: Neo4J | Build `community` image
|
||||
run: |
|
||||
docker build --target community -t "ocelotsocialnetwork/neo4j-community:test" neo4j/
|
||||
docker save "ocelotsocialnetwork/neo4j-community:test" > /tmp/neo4j.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-neo4j-image
|
||||
path: /tmp/neo4j.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD TEST BACKEND #############################################
|
||||
##############################################################################
|
||||
build_test_backend:
|
||||
name: Docker Build Test - Backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# BUILD BACKEND DOCKER IMAGE (build) #####################################
|
||||
##########################################################################
|
||||
- name: backend | Build `test` image
|
||||
run: |
|
||||
docker build --target test -t "ocelotsocialnetwork/backend:test" backend/
|
||||
docker save "ocelotsocialnetwork/backend:test" > /tmp/backend.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-backend-test
|
||||
path: /tmp/backend.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: DOCKER BUILD TEST WEBAPP ##############################################
|
||||
##############################################################################
|
||||
build_test_webapp:
|
||||
name: Docker Build Test - WebApp
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# BUILD WEBAPP DOCKER IMAGE (build) ######################################
|
||||
##########################################################################
|
||||
- name: webapp | Build `test` image
|
||||
run: |
|
||||
docker build --target test -t "ocelotsocialnetwork/webapp:test" webapp/
|
||||
docker save "ocelotsocialnetwork/webapp:test" > /tmp/webapp.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker-webapp-test
|
||||
path: /tmp/webapp.tar
|
||||
|
||||
##############################################################################
|
||||
# JOB: LINT BACKEND ##########################################################
|
||||
##############################################################################
|
||||
lint_backend:
|
||||
name: Lint backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_backend]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-backend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/backend.tar
|
||||
##########################################################################
|
||||
# LINT BACKEND ###########################################################
|
||||
##########################################################################
|
||||
- name: backend | Lint
|
||||
run: docker run --rm ocelotsocialnetwork/backend:test yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: LINT WEBAPP ###########################################################
|
||||
##############################################################################
|
||||
lint_webapp:
|
||||
name: Lint webapp
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_webapp]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Webapp)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-webapp-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/webapp.tar
|
||||
##########################################################################
|
||||
# LINT WEBAPP ############################################################
|
||||
##########################################################################
|
||||
- name: webapp | Lint
|
||||
run: docker run --rm ocelotsocialnetwork/webapp:test yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: UNIT TEST BACKEND #####################################################
|
||||
##############################################################################
|
||||
unit_test_backend:
|
||||
name: Unit tests - backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_neo4j,build_test_backend]
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGES #################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Neo4J)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-neo4j-image
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/neo4j.tar
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-backend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/backend.tar
|
||||
##########################################################################
|
||||
# UNIT TESTS BACKEND #####################################################
|
||||
##########################################################################
|
||||
- name: backend | copy env files webapp
|
||||
run: cp webapp/.env.template webapp/.env
|
||||
- name: backend | copy env files backend
|
||||
run: cp backend/.env.template backend/.env
|
||||
- name: backend | docker-compose
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps neo4j backend
|
||||
- name: backend | Initialize Database
|
||||
run: docker-compose exec -T backend yarn db:migrate init
|
||||
- name: backend | Migrate Database Up
|
||||
run: docker-compose exec -T backend yarn db:migrate up
|
||||
- name: backend | Unit test
|
||||
run: docker-compose exec -T backend yarn test
|
||||
##########################################################################
|
||||
# COVERAGE CHECK BACKEND #################################################
|
||||
##########################################################################
|
||||
- name: backend | Coverage check
|
||||
uses: webcraftmedia/coverage-check-action@master
|
||||
with:
|
||||
report_name: Coverage Backend
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 57
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
# JOB: UNIT TEST WEBAPP ######################################################
|
||||
##############################################################################
|
||||
unit_test_webapp:
|
||||
name: Unit tests - webapp
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_webapp]
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGES #################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Webapp)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-webapp-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/webapp.tar
|
||||
##########################################################################
|
||||
# UNIT TESTS WEBAPP ######################################################
|
||||
##########################################################################
|
||||
- name: backend | copy env files webapp
|
||||
run: cp webapp/.env.template webapp/.env
|
||||
- name: backend | copy env files backend
|
||||
run: cp backend/.env.template backend/.env
|
||||
- name: backend | docker-compose
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp
|
||||
- name: webapp | Unit tests
|
||||
run: docker-compose exec -T webapp yarn test
|
||||
##########################################################################
|
||||
# COVERAGE REPORT FRONTEND ################################################
|
||||
##########################################################################
|
||||
#- name: frontend | Coverage report
|
||||
# uses: romeovs/lcov-reporter-action@v0.2.21
|
||||
# with:
|
||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# lcov-file: ./coverage/lcov.info
|
||||
##########################################################################
|
||||
# COVERAGE CHECK WEBAPP ##################################################
|
||||
##########################################################################
|
||||
- name: webapp | Coverage check
|
||||
uses: webcraftmedia/coverage-check-action@master
|
||||
with:
|
||||
report_name: Coverage Webapp
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 83
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
# JOB: FULLSTACK TESTS #######################################################
|
||||
##############################################################################
|
||||
fullstack_tests:
|
||||
name: Fullstack tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_webapp, build_test_backend, build_test_neo4j]
|
||||
env:
|
||||
jobs: 8
|
||||
strategy:
|
||||
matrix:
|
||||
# run copies of the current job in parallel
|
||||
job: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
##########################################################################
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGES #################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Neo4J)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-neo4j-image
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/neo4j.tar
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-backend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/backend.tar
|
||||
- name: Download Docker Image (Webapp)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-webapp-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/webapp.tar
|
||||
##########################################################################
|
||||
# FULLSTACK TESTS CYPRESS ################################################
|
||||
##########################################################################
|
||||
- name: webapp | copy env files webapp
|
||||
run: cp webapp/.env.template webapp/.env
|
||||
- name: backend | copy env files backend
|
||||
run: cp backend/.env.template backend/.env
|
||||
- name: backend | docker-compose
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend
|
||||
- name: cypress | Fullstack tests
|
||||
id: e2e-tests
|
||||
run: |
|
||||
yarn install
|
||||
yarn run cypress:run --spec $(cypress/parallel-features.sh ${{ matrix.job }} ${{ env.jobs }} )
|
||||
##########################################################################
|
||||
# UPLOAD SCREENSHOTS - IF TESTS FAIL #####################################
|
||||
##########################################################################
|
||||
- name: Full stack tests | if any test failed, upload screenshots
|
||||
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cypress-screenshots
|
||||
path: cypress/screenshots/
|
||||
84
.travis.yml
84
.travis.yml
@ -1,84 +0,0 @@
|
||||
dist: xenial
|
||||
language: node_js
|
||||
node_js: lts/*
|
||||
cache:
|
||||
yarn: false
|
||||
npm: false
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libgconf-2-4
|
||||
snaps:
|
||||
- docker
|
||||
firefox: "latest-esr"
|
||||
|
||||
install:
|
||||
- yarn global add wait-on
|
||||
# Install Codecov
|
||||
- yarn install --frozen-lockfile
|
||||
- cp backend/.env.template backend/.env
|
||||
|
||||
before_script:
|
||||
- docker-compose -f docker-compose.yml build --parallel
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml build # just tagging, just be quite fast
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml up -d
|
||||
- wait-on http://localhost:7474
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.build-and-test.yml exec backend yarn run db:migrate init
|
||||
|
||||
script:
|
||||
- export CYPRESS_RETRIES=1
|
||||
- export BRANCH=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then echo $TRAVIS_BRANCH; else echo $TRAVIS_PULL_REQUEST_BRANCH; fi)
|
||||
- echo "TRAVIS_BRANCH=$TRAVIS_BRANCH, PR=$PR, BRANCH=$BRANCH"
|
||||
# Miscellaneous
|
||||
- ./scripts/translations/sort.sh
|
||||
- ./scripts/translations/missing-keys.sh
|
||||
# Backend
|
||||
- docker-compose exec backend yarn run lint
|
||||
- docker-compose exec backend yarn run test --ci --verbose=false --coverage
|
||||
- docker-compose exec backend yarn run db:seed
|
||||
- docker-compose exec backend yarn run db:reset
|
||||
# Frontend
|
||||
- docker-compose exec webapp yarn run lint
|
||||
- docker-compose exec webapp yarn run test --ci --verbose=false --coverage
|
||||
# Fullstack
|
||||
- docker-compose down
|
||||
- docker-compose -f docker-compose.yml up -d
|
||||
- wait-on http://localhost:7474
|
||||
# disable for last deploy, because of flakiness!
|
||||
# - yarn run cypress:run --record
|
||||
# - yarn run cucumber
|
||||
# Coverage
|
||||
# disable this uneffective thing for last deploy, because of easyness!
|
||||
# - yarn run codecov
|
||||
|
||||
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:
|
||||
- go get -u github.com/tcnksm/ghr
|
||||
# stop deployment to kubernetes until we have set it up
|
||||
# - ./scripts/setup_kubernetes.sh
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
script: bash scripts/docker_push.sh
|
||||
on:
|
||||
branch: master
|
||||
# stop deployment to kubernetes until we have set it up
|
||||
# - provider: script
|
||||
# script: bash scripts/deploy.sh
|
||||
# on:
|
||||
# branch: master
|
||||
302
CHANGELOG.md
302
CHANGELOG.md
@ -4,8 +4,310 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [3.1.2](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.1.0...3.1.2)
|
||||
|
||||
- fix(other): kubernetes error by degrading node version from v20.7.0 to v20.2.0 [`#6779`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6779)
|
||||
- fix(backend): cypher statement in user locales unit test [`#6780`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6780)
|
||||
- chore(release): release v3.1.1 – move dkim to secrets and fix search etc. [`#6763`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6763)
|
||||
- chore(other): move kubrnetes dkim from confmap to secret yaml [`#6761`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6761)
|
||||
- build(other): bump @babel/preset-env from 7.22.9 to 7.22.20 in /backend [`#6741`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6741)
|
||||
- build(other): bump @babel/register from 7.22.5 to 7.22.15 [`#6745`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6745)
|
||||
- chore(other): add tail command as template comment to `docker-compose.yml` [`#6755`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6755)
|
||||
- chore(other): upgrade node version in '.nvmrc' files to v20.6.0 [`#6758`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6758)
|
||||
- build(other): bump @babel/plugin-proposal-throw-expressions from 7.8.3 to 7.22.5 in /backend [`#6742`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6742)
|
||||
- build(other): bump @faker-js/faker from 8.0.2 to 8.1.0 [`#6743`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6743)
|
||||
- fix(webapp): chat-only show counter if unread messages exist [`#6752`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6752)
|
||||
- build(other): bump node from 20.6.0-alpine3.17 to 20.7.0-alpine3.17 in /webapp [`#6749`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6749)
|
||||
- build(other): bump node from 20.6.0-alpine3.17 to 20.7.0-alpine3.17 in /backend [`#6748`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6748)
|
||||
- fix(backend): post type as array in search posts query [`#6756`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6756)
|
||||
- refactor(backend): remove redundant package metascraper-audio from backend [`#6724`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6724)
|
||||
- test(other): update cypress related packages [`#6382`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6382)
|
||||
- build(other): bump @storybook/vue from 6.3.6 to 7.4.0 in /webapp [`#6708`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6708)
|
||||
- build(other): bump chai from 4.2.0 to 4.3.8 in /backend [`#6738`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6738)
|
||||
- build(other): bump eslint-config-prettier from 8.8.0 to 9.0.0 in /backend [`#6737`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6737)
|
||||
- build(other): bump slug from 6.1.0 to 8.2.3 [`#6716`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6716)
|
||||
- build(other): bump jsonwebtoken from 8.5.1 to 9.0.2 [`#6707`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6707)
|
||||
- build(other): bump expect from 29.6.1 to 29.6.4 [`#6699`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6699)
|
||||
- build(other): bump node from 20.2.0-alpine3.17 to 20.6.0-alpine3.17 in /webapp [`#6709`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6709)
|
||||
- build(other): bump node from 20.2.0-alpine3.17 to 20.6.0-alpine3.17 in /backend [`#6710`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6710)
|
||||
- build(other): bump actions/cache from 3.3.1 to 3.3.2 [`#6714`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6714)
|
||||
- build(other): bump actions/checkout from 3 to 4 [`#6713`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6713)
|
||||
- Bump @storybook/vue from 6.3.6 to 7.4.0 in /webapp [`8113d47`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/8113d47dfcbfa0a63ae1f035d9d24a4c548d2089)
|
||||
- Revert "update cypress packages" [`2229baf`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/2229baff6fb74d92ed395fb90a1421f994368a20)
|
||||
- update cypress packages [`dbedcf9`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/dbedcf901d3040d1a6a8db62170bdea86d020bb1)
|
||||
|
||||
#### [3.1.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.0.4...3.1.0)
|
||||
|
||||
> 13 September 2023
|
||||
|
||||
- chore(release): release v3.1.0 – use of dkim in deployment [`#6733`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6733)
|
||||
- chore(other): use dkim values in helm script of backend [`#6731`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6731)
|
||||
- fix(other): add global package.json to workflow file filters [`#6706`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6706)
|
||||
- Release v3.1.0 [`6276d87`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/6276d87c6a427438c5fbf56c0f7bc414293e671d)
|
||||
- Fix super fluid spaces [`ecdaf83`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/ecdaf83ac57bd4186f064d891d2570ce2438d47f)
|
||||
- Use DKIM values in Helm script of backend [`2ea98b1`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/2ea98b1900c1b33712a2456dde6ba3dfb6d95b00)
|
||||
|
||||
#### [3.0.4](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.0.3...3.0.4)
|
||||
|
||||
> 12 September 2023
|
||||
|
||||
- chore(other): release v3.0.4 – fix group link on map, embed backend shot down and bug on post language detect [`#6725`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6725)
|
||||
- fix(other): fix backup script for neo4j v4 [`#6662`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6662)
|
||||
- fix(other): fix workflow cache cleanup fail [`#6722`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6722)
|
||||
- fix(backend): fix post language detection [`#6720`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6720)
|
||||
- fix(webapp): add jsconfig.json to navigate in webapp. [`#6645`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6645)
|
||||
- fix(backend): metascraper crash [`#6704`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6704)
|
||||
- build(other): bump aws-sdk from 2.652.0 to 2.1425.0 in /backend [`#6651`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6651)
|
||||
- build(other): bump @babel/preset-env from 7.9.5 to 7.22.9 in /backend [`#6578`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6578)
|
||||
- build(other): bump @babel/preset-env from 7.22.7 to 7.22.9 [`#6572`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6572)
|
||||
- build(other): bump dotenv from 8.6.0 to 16.3.1 [`#6483`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6483)
|
||||
- Bump @babel/preset-env from 7.9.5 to 7.22.9 in /backend [`ae0c6f1`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/ae0c6f12bd86c39b33a2b48c2eeb0eccb880821c)
|
||||
- Revert "Revert "Revert "update metascraper packages""" [`7fdc5e8`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/7fdc5e8f5ed8e191763acfcd74c510138145f612)
|
||||
- Revert "Revert "update metascraper packages"" [`d5c1421`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/d5c142129291264d5508b3f32b8500c2451a5f39)
|
||||
|
||||
#### [3.0.3](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.0.2...3.0.3)
|
||||
|
||||
> 4 September 2023
|
||||
|
||||
- chore(release): release v3.0.3 – fix chat avatar error, adjust layout of filter and enable e-mail dkim [`#6702`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6702)
|
||||
- build(other): bump @babel/core from 7.22.8 to 7.22.9 [`#6573`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6573)
|
||||
- feat(backend): implement dkim config for nodemailer [`#6692`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6692)
|
||||
- fix(webapp): filtermenu mobile bug [`#6694`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6694)
|
||||
- fix(webapp): fix proxyapiurl in chat rooms for the avatars [`#6693`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6693)
|
||||
- fix(webapp): fix the group link in the map [`#6698`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6698)
|
||||
- Bump @babel/core from 7.22.8 to 7.22.9 [`209390a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/209390a7e026b03ee92f7b1ecb3b1a7b3b2e0232)
|
||||
- Write documentation for DKIM e-mail setting [`9f5d32e`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/9f5d32e527151854f1888614c59e0aa5f2b504d0)
|
||||
- Release v3.0.3 [`7d761c2`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/7d761c262a6e7bf6382cc49148c55ac8037db12a)
|
||||
|
||||
#### [3.0.2](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.0.1...3.0.2)
|
||||
|
||||
> 11 August 2023
|
||||
|
||||
- chore(release): release v3.0.2 – fix chat avatar error and wrong font in network [`#6674`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6674)
|
||||
- fix(webapp): fix wrong font in whole network comming from chat component [`#6672`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6672)
|
||||
- fix(webapp): fix 'm.avatar is null' error message [`#6671`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6671)
|
||||
- Release v3.0.2 [`7bcad81`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/7bcad81785de39115ba03aa37fb26caeff7775f2)
|
||||
- Remove font 'Quicksand' from the chat [`5b95419`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5b954197dc93bd5715f657539ca1b9b32d7d557a)
|
||||
- Fix 'm.avatar is null' error message [`2c12331`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/2c123313ed6267bb1c88470ba7ae3f4f693cfc15)
|
||||
|
||||
#### [3.0.1](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/3.0.0...3.0.1)
|
||||
|
||||
> 9 August 2023
|
||||
|
||||
- chore(release): release v3.0.1 – fix chat avatars [`#6667`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6667)
|
||||
- fix(webapp): try to fix avatars [`#6660`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6660)
|
||||
- Releasde v3.0.1 – fix chat avatars [`0daeb5b`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/0daeb5b957e11d206ceddc3e8b275929e1850d18)
|
||||
- conditional url replacement [`5c1ab88`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5c1ab880127a5f4c1b457598fafd3ae9fe1a0d12)
|
||||
- try to fix avatars [`6f43321`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/6f43321abad945602529b962e536546ab541cd70)
|
||||
|
||||
### [3.0.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/2.7.0...3.0.0)
|
||||
|
||||
> 7 August 2023
|
||||
|
||||
- chore(other): release v3.0.0 [`#6658`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6658)
|
||||
- fix(webapp): add missing locales [`#6652`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6652)
|
||||
- fix(webapp): fix wrong labels in filter menu buttons [`#6656`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6656)
|
||||
- refactor(webapp): refactor filter menu [`#6535`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6535)
|
||||
- fix(webapp): correct chat usertag profile link [`#6646`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6646)
|
||||
- fix(webapp): fix create event typing issues [`#6643`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6643)
|
||||
- fix(webapp): mini chat: dynamic header buttons [`#6641`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6641)
|
||||
- fix(webapp): start search of user after three characters (chat new room, add group member) [`#6639`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6639)
|
||||
- refactor(webapp): updated/refactored ui of create post page [`#6559`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6559)
|
||||
- fix(webapp): fix code in editor component [`#6624`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6624)
|
||||
- fix(backend): security subscriptions [`#6621`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6621)
|
||||
- fix(webapp): change the background color of my messages to the same color as side menu [`#6620`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6620)
|
||||
- feat(webapp): change general search hint [`#6619`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6619)
|
||||
- feat(webapp): chat component + button open search [`#6618`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6618)
|
||||
- feat(webapp): change color red to green on cancel buttons of post and comments [`#6565`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6565)
|
||||
- fix(webapp): groups highlighting [`#6608`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6608)
|
||||
- feat(webapp): chat link to chat page [`#6617`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6617)
|
||||
- fix(webapp): fix lastMessage to contain proper values [`#6615`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6615)
|
||||
- fix(backend): chat create message - only take 2000 chat message characters [`#6613`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6613)
|
||||
- feat(webapp): chat component can now show clickable urls [`#6614`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6614)
|
||||
- fix(webapp): highlight username functionality for chat [`#6609`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6609)
|
||||
- fix(webapp): fix chat behaviour [`#6611`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6611)
|
||||
- feat(webapp): chat seen check style [`#6612`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6612)
|
||||
- feat(webapp): on open chat room page close or on change profile and click chat change small chat [`#6605`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6605)
|
||||
- feat(webapp): change text for chat room search to `Filter chat rooms` [`#6601`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6601)
|
||||
- fix(backend): convert indexId to string (neo4j number problem) [`#6603`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6603)
|
||||
- fix(other): github cache in workflow [`#6604`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6604)
|
||||
- feat(webapp): show last message and unread count in room list [`#6595`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6595)
|
||||
- refactor(other): cache docker images for backend webapp e2e testing [`#6585`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6585)
|
||||
- feat(backend): chat message added subscription [`#6586`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6586)
|
||||
- refactor(backend): removed promise all from seed [`#6599`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6599)
|
||||
- feat(webapp): add headline to chat page [`#6563`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6563)
|
||||
- fix(other): set workflow not to fail while deleting cache [`#6594`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6594)
|
||||
- feat(backend): room count subscription [`#6584`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6584)
|
||||
- fix(backend): profile query fix [`#6587`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6587)
|
||||
- feat(webapp): mark messages as seen [`#6567`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6567)
|
||||
- feat(webapp): changed usertag in chat messages to user-slug [`#6555`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6555)
|
||||
- feat(backend): more room properties [`#6564`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6564)
|
||||
- refactor(backend): chat messages - order by indexId instead of createdAt [`#6562`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6562)
|
||||
- feat(webapp): chat language is reactive [`#6560`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6560)
|
||||
- feat(backend): unread rooms query [`#6566`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6566)
|
||||
- feat(webapp): chat last seen in webapp [`#6580`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6580)
|
||||
- feat(webapp): chat paginate rooms [`#6579`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6579)
|
||||
- refactor(other): use cached docker images in github e2e flow to decrease run time of e2e test job runs [`#6582`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6582)
|
||||
- fix(webapp): show room header images. [`#6581`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6581)
|
||||
- feat(backend): chat seed [`#6561`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6561)
|
||||
- feat(webapp): chat message pagination [`#6549`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6549)
|
||||
- feat(webapp): chat remove reply smileys [`#6553`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6553)
|
||||
- feat(webapp): remove message actions for chat [`#6552`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6552)
|
||||
- feat(backend): more message props [`#6547`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6547)
|
||||
- refactor(backend): fix seed to not use promise all where easily refactored [`#6520`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6520)
|
||||
- refactor(other): add proper reporting to cypress tests [`#6540`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6540)
|
||||
- refactor(other): cypress - migrate from cypress-file-upload to .selectFile() [`#6528`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6528)
|
||||
- feat(webapp): change chat style and lang [`#6517`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6517)
|
||||
- feat(webapp): show events with locations on the map [`#6518`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6518)
|
||||
- feat(webapp): refine chat notification in header menu and chat button on user profile [`#6550`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6550)
|
||||
- refactor(other): disable cypress test retries [`#6527`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6527)
|
||||
- feat(webapp): event creation page -better placeholder texts for venue and city inputs [`#6525`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6525)
|
||||
- refactor(other): refine webapp github workflow conditions [`#6526`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6526)
|
||||
- docs(other): update TODO-next-update.md for 2.7.0-470 [`#6532`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6532)
|
||||
- fix(backend): chat - do not allow to create room with self [`#6530`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6530)
|
||||
- feat(webapp): chat message capability [`#6524`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6524)
|
||||
- chore(backend): bump @faker-js/faker from 5.1.0 to 8.0.2 [`#6368`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6368)
|
||||
- chore(other): bump cypress from 12.14.0 to 12.17.0 [`#6509`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6509)
|
||||
- feat(webapp): chat rooms [`#6519`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6519)
|
||||
- fix(other): fix branded build - rename files to js for the webapp [`#6516`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6516)
|
||||
- fix(backend): fix branded build (migration) [`#6508`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6508)
|
||||
- fix(backend): fix branded build [`#6507`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6507)
|
||||
- feat(webapp): create a component chat scaffholding [`#6503`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6503)
|
||||
- fix(webapp): fix create post & notifications [`#6505`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6505)
|
||||
- feat(backend): typescript eslint backend [`#6478`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6478)
|
||||
- fix(backend): subscriptions [`#6477`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6477)
|
||||
- fix(backend): seed reports [`#6472`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6472)
|
||||
- refactor(backend): remove activity pub [`#6470`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6470)
|
||||
- fix(webapp): change registration `terms and conditions`, `data privacy` links to use `page-params-link` [`#6441`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6441)
|
||||
- feat(backend): message properties [`#6471`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6471)
|
||||
- feat(backend): room properties [`#6451`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6451)
|
||||
- fix(backend): create message mutation [`#6469`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6469)
|
||||
- feat(backend): messages [`#6450`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6450)
|
||||
- feat(backend): rooms [`#6444`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6444)
|
||||
- fix(other): neo4j config map & node version [`#6464`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6464)
|
||||
- Bump @babel/core from 7.22.1 to 7.22.5 [`#6413`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6413)
|
||||
- refactor(database): update neo4j to 4.4 [`#6306`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6306)
|
||||
- Bump cypress from 12.13.0 to 12.14.0 [`#6414`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6414)
|
||||
- Bump node from 20.2.0-alpine3.17 to 20.3.0-alpine3.17 in /backend [`#6409`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6409)
|
||||
- Bump node from 20.2.0-alpine3.17 to 20.3.0-alpine3.17 in /webapp [`#6408`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6408)
|
||||
- fix(backend): typescript fix [`#6448`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6448)
|
||||
- refactor(backend): migrate completely to typescript [`#6434`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6434)
|
||||
- remove package cypress-file-upload from e2e testing [`73f6bc6`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/73f6bc642194b0c73769d4e8d8e53645b6e80adf)
|
||||
- fixed cypress [`5f545f3`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5f545f3b8fc6927954e036b27ad2e123bcd36149)
|
||||
- fix seed to not use promise all where easily refactored [`1b0f512`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/1b0f5124159033214f99bfbc4cebe9dfaa7dd76e)
|
||||
|
||||
#### [2.7.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/2.6.0...2.7.0)
|
||||
|
||||
> 14 June 2023
|
||||
|
||||
- chore(release): v2.7.0 [`#6440`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6440)
|
||||
- fix(webapp): fix event teaser date from start to end by new components [`#6385`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6385)
|
||||
- refactor(webapp): optimize create and update event form [`#6381`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6381)
|
||||
- feat(backend): show events not ended yet [`#6405`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6405)
|
||||
- feat(backend): migration to add postType property to existing posts [`#6396`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6396)
|
||||
- feat(backend): seed events [`#6391`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6391)
|
||||
- feat(backend): seed posts as article [`#6227`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6227)
|
||||
- refactor(webapp): fix coverage [`#6361`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6361)
|
||||
- test(other): migrate cypress to v12 [`#6008`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6008)
|
||||
- refactor(backend): copy files in external script [`#6364`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6364)
|
||||
- feat(webapp): alternative solution for filter and order posts [`#6367`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6367)
|
||||
- refactor(webapp): changed color for event-ribbon. [`#6362`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6362)
|
||||
- fix(webapp): warnings in unit tests [`#6359`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6359)
|
||||
- Bump metascraper-title from 5.33.5 to 5.34.7 in /backend [`#6372`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6372)
|
||||
- Bump @babel/preset-env from 7.21.5 to 7.22.4 [`#6369`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6369)
|
||||
- fix(other): typescript fix regarding dist/build folder 2 [`#6366`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6366)
|
||||
- fix(backend): corrected path in branded images for backend build folder(former dist) [`#6365`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6365)
|
||||
- chore(other): upgrade node version in '.nvmrc' files to v20.2.0 [`#6331`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6331)
|
||||
- feat(backend): typescript [`#6321`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6321)
|
||||
- fix(webapp): fix group list number to six [`#6319`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6319)
|
||||
- fix(webapp): fix notification menu comment hash [`#6335`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6335)
|
||||
- feat(other): 🍰 epic events – master [`#6199`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6199)
|
||||
- fix(other): fix avatar seeding [`#6260`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6260)
|
||||
- chore(other): set 'DEBUG=true' in backend '.env.template' to use GraphQL Playground [`#6333`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6333)
|
||||
- refactor(other): unused packages ocelot [`#6326`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6326)
|
||||
- refactor(backend): unused packages backend [`#6325`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6325)
|
||||
- docs(other): add description for script usage in deployment readme [`#6329`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6329)
|
||||
- fix(webapp): fix newsfeed layout [`#6154`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6154)
|
||||
- fix(webapp): adds white space after user handle in comment editor [`#6308`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6308)
|
||||
- Bump validator from 13.0.0 to 13.9.0 in /backend [`#6076`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6076)
|
||||
- Bump metascraper-soundcloud from 5.34.2 to 5.34.4 in /backend [`#6312`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6312)
|
||||
- Bump metascraper-audio from 5.33.5 to 5.34.4 in /backend [`#6287`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6287)
|
||||
- Bump node from 19.9.0-alpine3.17 to 20.2.0-alpine3.17 in /backend [`#6310`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6310)
|
||||
- docs(other): add missing todo in deployment readme 'TODO-next-update.md' [`#6324`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6324)
|
||||
- Bump node from 20.1.0-alpine3.17 to 20.2.0-alpine3.17 in /webapp [`#6309`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6309)
|
||||
- fix(backend): helmet fix [`#6318`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6318)
|
||||
- fix(backend): helmet + graphiql [`#6303`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6303)
|
||||
- Bump @babel/preset-env from 7.21.4 to 7.21.5 [`#6273`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6273)
|
||||
- Bump date-fns from 2.25.0 to 2.30.0 [`#6283`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6283)
|
||||
- Bump node from 19.9.0-alpine3.17 to 20.1.0-alpine3.17 in /webapp [`#6280`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6280)
|
||||
- Bump @babel/core from 7.21.4 to 7.21.8 [`#6284`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6284)
|
||||
- Bump helmet from 3.22.0 to 7.0.0 in /backend [`#6296`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6296)
|
||||
- fix(webapp): fix z layer of header elements [`#6279`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6279)
|
||||
- fix(webapp): properly render avatars in group settings [`#6289`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6289)
|
||||
- fix(backend): post type on notifications [`#6257`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6257)
|
||||
- fix(backend): recover missing commit [`#6262`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6262)
|
||||
- feat(backend): filter posts by post type [`#6255`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6255)
|
||||
- feat(backend): save location address [`#6240`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6240)
|
||||
- feat(backend): add further event params [`#6231`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6231)
|
||||
- feat(backend): event parameters [`#6198`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6198)
|
||||
- feat(backend): create and update posts with labels [`#6197`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6197)
|
||||
- feat(backend): add article label to posts [`#6196`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6196)
|
||||
- Cypress: update packaage info [`b38769b`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/b38769b048e9cb9ca07862a61ea810f21b4ce82a)
|
||||
- update cypress related packageges in package.json [`692ec2a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/692ec2a11555600647ec8d95b8296c9869948b02)
|
||||
- fixed coverage reporting [`540cd40`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/540cd40e10ec0461ef17379cb93d914839f3a84f)
|
||||
|
||||
#### [2.6.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/2.5.1...2.6.0)
|
||||
|
||||
> 27 April 2023
|
||||
|
||||
- chore(release): v2.6.0 [`#6271`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6271)
|
||||
- fix(other): docker-compose for rebranding deployment [`#6265`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6265)
|
||||
- feat(webapp): default categories of group for posts in group [`#6259`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6259)
|
||||
- refactor(webapp): make action radius select in group form a reusable component [`#6244`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6244)
|
||||
- fix(webapp): show avatar for group members [`#6258`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6258)
|
||||
- fix(webapp): fix search for 3 chars [`#6256`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6256)
|
||||
- fix(backend): group posts cannot be pinned [`#6242`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6242)
|
||||
- Bump metascraper-soundcloud from 5.33.5 to 5.34.2 in /backend [`#6213`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6213)
|
||||
- refactor(other): refactor test workflows [`#6151`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6151)
|
||||
- fix(other): deployment fix typo, update stage.ocelot.social reference [`#6230`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6230)
|
||||
- fix(other): bump metascraper-url from 5.33.5 to 5.34.2 in /backend [`#6217`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6217)
|
||||
- fix(other): workflow typo [`#6235`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6235)
|
||||
- feat(other): publish stage.yunite.me hook [`#6234`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6234)
|
||||
- fix(other): reduce kubernetes memory limits [`#6229`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6229)
|
||||
- fix(other): deployment for branded image with custom names [`#6228`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6228)
|
||||
- Bump node from 19.8.1-alpine3.17 to 19.9.0-alpine3.17 in /backend [`#6219`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6219)
|
||||
- Bump node from 19.8.1-alpine3.17 to 19.9.0-alpine3.17 in /webapp [`#6220`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6220)
|
||||
- feat(other): deployment pod resources [`#6132`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6132)
|
||||
- Bump @babel/core from 7.12.17 to 7.21.4 in /webapp [`#6215`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6215)
|
||||
- Bump jsonwebtoken from 8.5.1 to 9.0.0 in /webapp [`#6079`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6079)
|
||||
- Bump jest from 29.4.2 to 29.5.0 in /webapp [`#6094`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6094)
|
||||
- Bump babel-jest from 29.4.2 to 29.5.0 in /webapp [`#6095`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6095)
|
||||
- Bump neode from 0.4.8 to 0.4.9 in /backend [`#6075`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6075)
|
||||
- Bump node from 19.4.0-alpine3.17 to 19.8.1-alpine3.17 in /webapp [`#6155`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6155)
|
||||
- Bump node from 19.4.0-alpine3.17 to 19.8.1-alpine3.17 in /backend [`#6156`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6156)
|
||||
- Bump @babel/core from 7.9.0 to 7.21.4 [`#6200`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6200)
|
||||
- Bump @babel/preset-env from 7.12.7 to 7.21.4 [`#6204`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6204)
|
||||
- Bump expect from 25.3.0 to 29.5.0 [`#6098`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6098)
|
||||
- separate test workflows [`3533a36`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/3533a36cdc811c0e1dae218fbc2184f7c4bc3951)
|
||||
- get it working [`8df7d5d`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/8df7d5d265b0c5ba16f167a213631d765d2f985e)
|
||||
- feat(webapp): group categories on posts [`3244f3f`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/3244f3f86d1e8c09e0fd49f43c49f0a3aa8b85ab)
|
||||
|
||||
#### [2.5.1](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/2.5.0...2.5.1)
|
||||
|
||||
> 23 March 2023
|
||||
|
||||
- chore(other): release v2.5.1 – fix filter menu width [`#6180`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6180)
|
||||
- feat(webapp): add tooltips to all menu icons [`#6185`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6185)
|
||||
- fix(webapp): popup filter max-width [`#6177`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6177)
|
||||
- Add tooltip to header notifications menu [`28505a5`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/28505a5b181008ebcde6fa58b7a4a8459a492018)
|
||||
- Add tooltip to header avatar menu [`4c0469f`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/4c0469f61a3c2fae23e50c6a5a2a91b63fac149a)
|
||||
- Release v2.5.1 - fix filter menu width [`08def14`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/08def14cafef7816d8e43f1896430400bda9635d)
|
||||
|
||||
#### [2.5.0](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/2.4.0...2.5.0)
|
||||
|
||||
> 21 March 2023
|
||||
|
||||
- chore(other): release v2.5.0 [`#6172`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6172)
|
||||
- fix(other): publish transmit github run number properly [`#6171`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6171)
|
||||
- feat(other): deployment include GitHub run number on branded images [`#6170`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6170)
|
||||
- fix(backend): new migration to create search indexes [`#6167`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/6167)
|
||||
|
||||
@ -8,9 +8,11 @@
|
||||
|
||||
[ocelot.social](https://ocelot.social) is free and open source software program code to run social networks. Its development is supported by a community of programmers and interested network operators.
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<p align="center">
|
||||
<a href="https://ocelot.social" target="_blank"><img src="webapp/static/img/custom/logo-squared.svg" alt="ocelot.social" width="40%" height="40%"></a>
|
||||
</p>
|
||||
<!-- markdownlint-enable MD033 -->
|
||||
|
||||
Our goal is to enable people to participate fairly and equally in online social networks. The equality of opportunity applies both to the fundamental equality of all people and to the possibility of letting their diverse voices be heard.
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
* [Frontend tests](webapp/testing.md)
|
||||
* [Backend tests](backend/testing.md)
|
||||
* [Docker More Closely](DOCKER_MORE_CLOSELY.md)
|
||||
* [Deployment](https://github.com/Ocelot-Social-Community/Ocelot-Social-Deploy-Rebranding/blob/master/deployment/README.md)
|
||||
* [Deployment](deployment/README.md)
|
||||
* [Contributing](CONTRIBUTING.md)
|
||||
* [Feature Specification](cypress/features.md)
|
||||
* [Code of conduct](CODE_OF_CONDUCT.md)
|
||||
|
||||
@ -12,8 +12,7 @@ docker-compose*.yml
|
||||
./*.log
|
||||
|
||||
node_modules/
|
||||
scripts/
|
||||
dist/
|
||||
build/
|
||||
|
||||
maintenance-worker/
|
||||
neo4j/
|
||||
|
||||
@ -1,13 +1,23 @@
|
||||
DEBUG=true
|
||||
|
||||
NEO4J_URI=bolt://localhost:7687
|
||||
NEO4J_USERNAME=neo4j
|
||||
NEO4J_PASSWORD=letmein
|
||||
GRAPHQL_URI=http://localhost:4000
|
||||
CLIENT_URI=http://localhost:3000
|
||||
|
||||
# EMail
|
||||
EMAIL_SUPPORT="devops@ocelot.social"
|
||||
EMAIL_DEFAULT_SENDER="devops@ocelot.social"
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_IGNORE_TLS=true
|
||||
SMTP_USERNAME=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_SECURE="false" # true for 465, false for other ports
|
||||
SMTP_DKIM_DOMAINNAME=
|
||||
SMTP_DKIM_KEYSELECTOR=
|
||||
SMTP_DKIM_PRIVATKEY=
|
||||
|
||||
JWT_SECRET="b/&&7b78BF&fv/Vd"
|
||||
JWT_EXPIRES="2y"
|
||||
@ -26,7 +36,4 @@ AWS_ENDPOINT=
|
||||
AWS_REGION=
|
||||
AWS_BUCKET=
|
||||
|
||||
EMAIL_DEFAULT_SENDER="devops@ocelot.social"
|
||||
EMAIL_SUPPORT="devops@ocelot.social"
|
||||
|
||||
CATEGORIES_ACTIVE=false
|
||||
|
||||
@ -1,25 +1,219 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
es6: true,
|
||||
// es6: true,
|
||||
node: true,
|
||||
jest: true
|
||||
},
|
||||
parserOptions: {
|
||||
/* parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},
|
||||
},*/
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['prettier', '@typescript-eslint' /*, 'import', 'n', 'promise'*/],
|
||||
extends: [
|
||||
'standard',
|
||||
'plugin:prettier/recommended'
|
||||
// 'eslint:recommended',
|
||||
'plugin:prettier/recommended',
|
||||
// 'plugin:import/recommended',
|
||||
// 'plugin:import/typescript',
|
||||
// 'plugin:security/recommended',
|
||||
// 'plugin:@eslint-community/eslint-comments/recommended',
|
||||
],
|
||||
plugins: [
|
||||
'jest'
|
||||
],
|
||||
rules: {
|
||||
settings: {
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
/* rules: {
|
||||
//'indent': [ 'error', 2 ],
|
||||
//'quotes': [ "error", "single"],
|
||||
// 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-console': ['error'],
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'prettier/prettier': ['error'],
|
||||
> 'no-console': ['error'],
|
||||
> 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
> 'prettier/prettier': ['error'],
|
||||
}, */
|
||||
rules: {
|
||||
'no-console': 'error',
|
||||
camelcase: 'error',
|
||||
'no-debugger': 'error',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
htmlWhitespaceSensitivity: 'ignore',
|
||||
},
|
||||
],
|
||||
// import
|
||||
// 'import/export': 'error',
|
||||
// 'import/no-deprecated': 'error',
|
||||
// 'import/no-empty-named-blocks': 'error',
|
||||
// 'import/no-extraneous-dependencies': 'error',
|
||||
// 'import/no-mutable-exports': 'error',
|
||||
// 'import/no-unused-modules': 'error',
|
||||
// 'import/no-named-as-default': 'error',
|
||||
// 'import/no-named-as-default-member': 'error',
|
||||
// 'import/no-amd': 'error',
|
||||
// 'import/no-commonjs': 'error',
|
||||
// 'import/no-import-module-exports': 'error',
|
||||
// 'import/no-nodejs-modules': 'off',
|
||||
// 'import/unambiguous': 'error',
|
||||
// 'import/default': 'error',
|
||||
// 'import/named': 'error',
|
||||
// 'import/namespace': 'error',
|
||||
// 'import/no-absolute-path': 'error',
|
||||
// 'import/no-cycle': 'error',
|
||||
// 'import/no-dynamic-require': 'error',
|
||||
// 'import/no-internal-modules': 'off',
|
||||
// 'import/no-relative-packages': 'error',
|
||||
// 'import/no-relative-parent-imports': ['error', { ignore: ['@/*'] }],
|
||||
// 'import/no-self-import': 'error',
|
||||
// 'import/no-unresolved': 'error',
|
||||
// 'import/no-useless-path-segments': 'error',
|
||||
// 'import/no-webpack-loader-syntax': 'error',
|
||||
// 'import/consistent-type-specifier-style': 'error',
|
||||
// 'import/exports-last': 'off',
|
||||
// 'import/extensions': 'error',
|
||||
// 'import/first': 'error',
|
||||
// 'import/group-exports': 'off',
|
||||
// 'import/newline-after-import': 'error',
|
||||
// 'import/no-anonymous-default-export': 'error',
|
||||
// 'import/no-default-export': 'error',
|
||||
// 'import/no-duplicates': 'error',
|
||||
// 'import/no-named-default': 'error',
|
||||
// 'import/no-namespace': 'error',
|
||||
// 'import/no-unassigned-import': 'error',
|
||||
// 'import/order': [
|
||||
// 'error',
|
||||
// {
|
||||
// groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
// 'newlines-between': 'always',
|
||||
// pathGroups: [
|
||||
// {
|
||||
// pattern: '@?*/**',
|
||||
// group: 'external',
|
||||
// position: 'after',
|
||||
// },
|
||||
// {
|
||||
// pattern: '@/**',
|
||||
// group: 'external',
|
||||
// position: 'after',
|
||||
// },
|
||||
// ],
|
||||
// alphabetize: {
|
||||
// order: 'asc' /* sort in ascending order. Options: ['ignore', 'asc', 'desc'] */,
|
||||
// caseInsensitive: true /* ignore case. Options: [true, false] */,
|
||||
// },
|
||||
// distinctGroup: true,
|
||||
// },
|
||||
// ],
|
||||
// 'import/prefer-default-export': 'off',
|
||||
// n
|
||||
// 'n/handle-callback-err': 'error',
|
||||
// 'n/no-callback-literal': 'error',
|
||||
// 'n/no-exports-assign': 'error',
|
||||
// 'n/no-extraneous-import': 'error',
|
||||
// 'n/no-extraneous-require': 'error',
|
||||
// 'n/no-hide-core-modules': 'error',
|
||||
// 'n/no-missing-import': 'off', // not compatible with typescript
|
||||
// 'n/no-missing-require': 'error',
|
||||
// 'n/no-new-require': 'error',
|
||||
// 'n/no-path-concat': 'error',
|
||||
// 'n/no-process-exit': 'error',
|
||||
// 'n/no-unpublished-bin': 'error',
|
||||
// 'n/no-unpublished-import': 'off', // TODO need to exclude seeds
|
||||
// 'n/no-unpublished-require': 'error',
|
||||
// 'n/no-unsupported-features': ['error', { ignores: ['modules'] }],
|
||||
// 'n/no-unsupported-features/es-builtins': 'error',
|
||||
// 'n/no-unsupported-features/es-syntax': 'error',
|
||||
// 'n/no-unsupported-features/node-builtins': 'error',
|
||||
// 'n/process-exit-as-throw': 'error',
|
||||
// 'n/shebang': 'error',
|
||||
// 'n/callback-return': 'error',
|
||||
// 'n/exports-style': 'error',
|
||||
// 'n/file-extension-in-import': 'off',
|
||||
// 'n/global-require': 'error',
|
||||
// 'n/no-mixed-requires': 'error',
|
||||
// 'n/no-process-env': 'error',
|
||||
// 'n/no-restricted-import': 'error',
|
||||
// 'n/no-restricted-require': 'error',
|
||||
// 'n/no-sync': 'error',
|
||||
// 'n/prefer-global/buffer': 'error',
|
||||
// 'n/prefer-global/console': 'error',
|
||||
// 'n/prefer-global/process': 'error',
|
||||
// 'n/prefer-global/text-decoder': 'error',
|
||||
// 'n/prefer-global/text-encoder': 'error',
|
||||
// 'n/prefer-global/url': 'error',
|
||||
// 'n/prefer-global/url-search-params': 'error',
|
||||
// 'n/prefer-promises/dns': 'error',
|
||||
// 'n/prefer-promises/fs': 'error',
|
||||
// promise
|
||||
// 'promise/catch-or-return': 'error',
|
||||
// 'promise/no-return-wrap': 'error',
|
||||
// 'promise/param-names': 'error',
|
||||
// 'promise/always-return': 'error',
|
||||
// 'promise/no-native': 'off',
|
||||
// 'promise/no-nesting': 'warn',
|
||||
// 'promise/no-promise-in-callback': 'warn',
|
||||
// 'promise/no-callback-in-promise': 'warn',
|
||||
// 'promise/avoid-new': 'warn',
|
||||
// 'promise/no-new-statics': 'error',
|
||||
// 'promise/no-return-in-finally': 'warn',
|
||||
// 'promise/valid-params': 'warn',
|
||||
// 'promise/prefer-await-to-callbacks': 'error',
|
||||
// 'promise/no-multiple-resolved': 'error',
|
||||
// eslint comments
|
||||
// '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
|
||||
// '@eslint-community/eslint-comments/no-restricted-disable': 'error',
|
||||
// '@eslint-community/eslint-comments/no-use': 'off',
|
||||
// '@eslint-community/eslint-comments/require-description': 'off',
|
||||
},
|
||||
overrides: [
|
||||
// only for ts files
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
extends: [
|
||||
// 'plugin:@typescript-eslint/recommended',
|
||||
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
// 'plugin:@typescript-eslint/strict',
|
||||
],
|
||||
rules: {
|
||||
// allow explicitly defined dangling promises
|
||||
// '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||
'no-void': ['error', { allowAsStatement: true }],
|
||||
// ignore prefer-regexp-exec rule to allow string.match(regex)
|
||||
'@typescript-eslint/prefer-regexp-exec': 'off',
|
||||
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
|
||||
'import/unambiguous': 'off',
|
||||
// this is not compatible with typeorm, due to joined tables can be null, but are not defined as nullable
|
||||
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||
},
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
// this is to properly reference the referenced project database without requirement of compiling it
|
||||
// eslint-disable-next-line camelcase
|
||||
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.spec.ts'],
|
||||
plugins: ['jest'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'jest/no-disabled-tests': 'error',
|
||||
'jest/no-focused-tests': 'error',
|
||||
'jest/no-identical-title': 'error',
|
||||
'jest/prefer-to-have-length': 'error',
|
||||
'jest/valid-expect': 'error',
|
||||
'@typescript-eslint/unbound-method': 'off',
|
||||
// 'jest/unbound-method': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
@ -3,7 +3,7 @@ node_modules/
|
||||
.vscode
|
||||
.idea
|
||||
yarn-error.log
|
||||
dist/*
|
||||
build/*
|
||||
coverage.lcov
|
||||
.nyc_output/
|
||||
public/uploads/*
|
||||
|
||||
@ -1 +1 @@
|
||||
v19.4.0
|
||||
v20.2.0
|
||||
@ -1,7 +1,7 @@
|
||||
##################################################################################
|
||||
# BASE (Is pushed to DockerHub for rebranding) ###################################
|
||||
##################################################################################
|
||||
FROM node:19.4.0-alpine3.17 as base
|
||||
FROM node:20.2.0-alpine3.17 as base
|
||||
|
||||
# ENVs
|
||||
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
||||
@ -74,7 +74,7 @@ FROM code as build
|
||||
# yarn install
|
||||
RUN yarn install --production=false --frozen-lockfile --non-interactive
|
||||
# yarn build
|
||||
RUN yarn run build
|
||||
RUN /bin/sh -c "yarn run build"
|
||||
|
||||
##################################################################################
|
||||
# TEST ###########################################################################
|
||||
@ -90,7 +90,7 @@ CMD /bin/sh -c "yarn run dev"
|
||||
FROM base as production
|
||||
|
||||
# Copy "binary"-files from build image
|
||||
COPY --from=build ${DOCKER_WORKDIR}/dist ./dist
|
||||
COPY --from=build ${DOCKER_WORKDIR}/build ./build
|
||||
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
|
||||
# Copy static files
|
||||
# TODO - externalize the uploads so we can copy the whole folder
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
preset: 'ts-jest',
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'**/*.js',
|
||||
'**/*.ts',
|
||||
'!**/node_modules/**',
|
||||
'!**/test/**',
|
||||
'!**/dist/**',
|
||||
'!**/src/**/?(*.)+(spec|test).js?(x)'
|
||||
'!**/build/**',
|
||||
'!**/src/**/?(*.)+(spec|test).ts?(x)'
|
||||
],
|
||||
coverageReporters: ['lcov', 'text'],
|
||||
testMatch: ['**/src/**/?(*.)+(spec|test).js?(x)'],
|
||||
setupFilesAfterEnv: ['<rootDir>/test/setup.js']
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 67,
|
||||
},
|
||||
},
|
||||
testMatch: ['**/src/**/?(*.)+(spec|test).ts?(x)'],
|
||||
setupFilesAfterEnv: ['<rootDir>/test/setup.ts']
|
||||
}
|
||||
|
||||
@ -1,35 +1,34 @@
|
||||
{
|
||||
"name": "ocelot-social-backend",
|
||||
"version": "2.5.0",
|
||||
"version": "3.1.2",
|
||||
"description": "GraphQL Backend for ocelot.social",
|
||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||
"author": "ocelot.social Community",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"main": "src/index.js",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"__migrate": "migrate --compiler 'js:@babel/register' --migrations-dir ./src/db/migrations",
|
||||
"prod:migrate": "migrate --migrations-dir ./dist/db/migrations --store ./dist/db/migrate/store.js",
|
||||
"start": "node dist/",
|
||||
"build": "babel src/ -d dist/ --copy-files",
|
||||
"dev": "nodemon --exec babel-node src/ -e js,gql",
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,gql",
|
||||
"lint": "eslint src --config .eslintrc.js",
|
||||
"__migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations",
|
||||
"prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js",
|
||||
"start": "node build/src/",
|
||||
"build": "tsc && ./scripts/build.copy.files.sh",
|
||||
"dev": "nodemon --exec ts-node src/ -e js,ts,gql",
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/ -e js,ts,gql",
|
||||
"lint": "eslint --max-warnings=0 --ext .js,.ts ./src",
|
||||
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||
"db:clean": "babel-node src/db/clean.js",
|
||||
"db:clean": "ts-node src/db/clean.ts",
|
||||
"db:reset": "yarn run db:clean",
|
||||
"db:seed": "babel-node src/db/seed.js",
|
||||
"db:migrate": "yarn run __migrate --store ./src/db/migrate/store.js",
|
||||
"db:migrate:create": "yarn run __migrate --template-file ./src/db/migrate/template.js --date-format 'yyyymmddHHmmss' create"
|
||||
"db:seed": "ts-node src/db/seed.ts",
|
||||
"db:migrate": "yarn run __migrate --store ./src/db/migrate/store.ts",
|
||||
"db:migrate:create": "yarn run __migrate --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/cli": "~7.8.4",
|
||||
"@babel/core": "~7.9.0",
|
||||
"@babel/node": "~7.8.7",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.8.3",
|
||||
"@babel/preset-env": "~7.9.5",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.22.5",
|
||||
"@babel/preset-env": "~7.22.20",
|
||||
"@babel/register": "^7.9.0",
|
||||
"@hapi/joi": "^17.1.1",
|
||||
"@sentry/node": "^5.15.4",
|
||||
"apollo-cache-inmemory": "~1.6.5",
|
||||
"apollo-client": "~2.6.8",
|
||||
@ -37,7 +36,7 @@
|
||||
"apollo-link-http": "~1.5.17",
|
||||
"apollo-server": "~2.14.2",
|
||||
"apollo-server-express": "^2.14.2",
|
||||
"aws-sdk": "^2.652.0",
|
||||
"aws-sdk": "^2.1425.0",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.1.0",
|
||||
"babel-jest": "~25.2.6",
|
||||
@ -46,19 +45,15 @@
|
||||
"cheerio": "~1.0.0-rc.3",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~7.0.3",
|
||||
"date-fns": "2.22.1",
|
||||
"debug": "~4.1.1",
|
||||
"dotenv": "~8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^14.6.0",
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
"graphql-iso-date": "~3.6.1",
|
||||
"graphql-middleware": "~4.0.2",
|
||||
"graphql-middleware-sentry": "^3.2.1",
|
||||
"graphql-redis-subscriptions": "^2.2.1",
|
||||
"graphql-shield": "~7.2.2",
|
||||
"graphql-tag": "~2.10.3",
|
||||
"helmet": "~3.22.0",
|
||||
"helmet": "~7.0.0",
|
||||
"ioredis": "^4.16.1",
|
||||
"jsonwebtoken": "~8.5.1",
|
||||
"languagedetect": "^2.0.0",
|
||||
@ -66,9 +61,7 @@
|
||||
"lodash": "~4.17.14",
|
||||
"merge-graphql-schemas": "^1.7.8",
|
||||
"metascraper": "^5.33.5",
|
||||
"metascraper-audio": "^5.33.5",
|
||||
"metascraper-author": "^5.33.5",
|
||||
"metascraper-clearbit-logo": "^5.3.0",
|
||||
"metascraper-date": "^5.33.5",
|
||||
"metascraper-description": "^5.33.5",
|
||||
"metascraper-image": "^5.33.5",
|
||||
@ -76,51 +69,56 @@
|
||||
"metascraper-lang-detector": "^4.10.2",
|
||||
"metascraper-logo": "^5.33.5",
|
||||
"metascraper-publisher": "^5.33.5",
|
||||
"metascraper-soundcloud": "^5.33.5",
|
||||
"metascraper-title": "^5.33.5",
|
||||
"metascraper-url": "^5.33.5",
|
||||
"metascraper-soundcloud": "^5.34.4",
|
||||
"metascraper-title": "^5.34.7",
|
||||
"metascraper-url": "^5.34.2",
|
||||
"metascraper-video": "^5.33.5",
|
||||
"metascraper-youtube": "^5.33.5",
|
||||
"migrate": "^1.7.0",
|
||||
"migrate": "^2.0.0",
|
||||
"mime-types": "^2.1.26",
|
||||
"minimatch": "^3.0.4",
|
||||
"mustache": "^4.2.0",
|
||||
"neo4j-driver": "^4.0.2",
|
||||
"neo4j-graphql-js": "^2.11.5",
|
||||
"neode": "^0.4.8",
|
||||
"neode": "^0.4.9",
|
||||
"node-fetch": "~2.6.1",
|
||||
"nodemailer": "^6.4.4",
|
||||
"nodemailer-html-to-text": "^3.2.0",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"request": "~2.88.2",
|
||||
"sanitize-html": "~1.22.0",
|
||||
"slug": "~6.0.0",
|
||||
"subscriptions-transport-ws": "^0.9.19",
|
||||
"trunc-html": "~1.1.2",
|
||||
"uuid": "~8.3.2",
|
||||
"validator": "^13.0.0",
|
||||
"wait-on": "~4.0.1",
|
||||
"validator": "^13.9.0",
|
||||
"xregexp": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "5.1.0",
|
||||
"@faker-js/faker": "7.6.0",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/node": "^20.2.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"apollo-server-testing": "~2.11.0",
|
||||
"chai": "~4.2.0",
|
||||
"chai": "~4.3.8",
|
||||
"cucumber": "~6.0.5",
|
||||
"eslint": "~6.8.0",
|
||||
"eslint-config-prettier": "~6.15.0",
|
||||
"eslint-config-standard": "~14.1.1",
|
||||
"eslint-plugin-import": "~2.20.2",
|
||||
"eslint-plugin-jest": "~23.8.2",
|
||||
"eslint-plugin-node": "~11.1.0",
|
||||
"eslint-plugin-prettier": "~3.4.1",
|
||||
"eslint-plugin-promise": "~4.3.1",
|
||||
"eslint-plugin-standard": "~4.0.1",
|
||||
"jest": "29.4",
|
||||
"eslint": "^8.37.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.4",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"eslint-plugin-n": "^15.7.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-security": "^1.7.1",
|
||||
"jest": "^27.2.4",
|
||||
"nodemon": "~2.0.2",
|
||||
"prettier": "~2.3.2",
|
||||
"prettier": "^2.8.7",
|
||||
"rosie": "^2.0.1",
|
||||
"supertest": "~4.0.2"
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/**/fs-capacitor": "^6.2.0",
|
||||
|
||||
24
backend/scripts/build.copy.files.sh
Executable file
24
backend/scripts/build.copy.files.sh
Executable file
@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
|
||||
# html files
|
||||
mkdir -p build/src/middleware/helpers/email/templates/
|
||||
cp -r src/middleware/helpers/email/templates/*.html build/src/middleware/helpers/email/templates/
|
||||
|
||||
mkdir -p build/src/middleware/helpers/email/templates/en/
|
||||
cp -r src/middleware/helpers/email/templates/en/*.html build/src/middleware/helpers/email/templates/en/
|
||||
|
||||
mkdir -p build/src/middleware/helpers/email/templates/de/
|
||||
cp -r src/middleware/helpers/email/templates/de/*.html build/src/middleware/helpers/email/templates/de/
|
||||
|
||||
# gql files
|
||||
mkdir -p build/src/schema/types/
|
||||
cp -r src/schema/types/*.gql build/src/schema/types/
|
||||
|
||||
mkdir -p build/src/schema/types/enum/
|
||||
cp -r src/schema/types/enum/*.gql build/src/schema/types/enum/
|
||||
|
||||
mkdir -p build/src/schema/types/scalar/
|
||||
cp -r src/schema/types/scalar/*.gql build/src/schema/types/scalar/
|
||||
|
||||
mkdir -p build/src/schema/types/type/
|
||||
cp -r src/schema/types/type/*.gql build/src/schema/types/type/
|
||||
@ -1,240 +0,0 @@
|
||||
// import { extractDomainFromUrl, signAndSend } from './utils'
|
||||
import { extractNameFromId, signAndSend } from './utils'
|
||||
import { isPublicAddressed } from './utils/activity'
|
||||
// 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 Collections from './Collections'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import CONFIG from '../config'
|
||||
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) {
|
||||
activityPub = new ActivityPub(CONFIG.CLIENT_URI, CONFIG.GRAPHQL_URI)
|
||||
|
||||
// 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') // eslint-disable-line no-console
|
||||
} else {
|
||||
console.log('-> ActivityPub middleware already added to the graphql express server') // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
// handleFollowActivity(activity) {
|
||||
// debug(`inside FOLLOW ${activity.actor}`)
|
||||
// const toActorName = extractNameFromId(activity.object)
|
||||
// const 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)
|
||||
|
||||
// const 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleCreateActivity(activity) {
|
||||
debug('inside create')
|
||||
switch (activity.object.type) {
|
||||
case 'Note': {
|
||||
const articleObject = activity.object
|
||||
if (articleObject.inReplyTo) {
|
||||
return this.dataSource.createComment(activity)
|
||||
} else {
|
||||
return this.dataSource.createPost(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -1,575 +0,0 @@
|
||||
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: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 {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
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.slug,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
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
|
||||
@ -1,29 +0,0 @@
|
||||
import user from './user'
|
||||
import inbox from './inbox'
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import verify from './verify'
|
||||
|
||||
export default function () {
|
||||
const router = express.Router()
|
||||
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,
|
||||
)
|
||||
return router
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -1,92 +0,0 @@
|
||||
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
|
||||
@ -1,20 +0,0 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
import express from 'express'
|
||||
import CONFIG from '../../config/'
|
||||
import cors from 'cors'
|
||||
|
||||
const debug = require('debug')('ea:webfinger')
|
||||
const regex = /acct:([a-z0-9_-]*)@([a-z0-9_-]*)/
|
||||
|
||||
const createWebFinger = (name) => {
|
||||
const { host } = new URL(CONFIG.CLIENT_URI)
|
||||
return {
|
||||
subject: `acct:${name}@${host}`,
|
||||
links: [
|
||||
{
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
href: `${CONFIG.CLIENT_URI}/activitypub/users/${name}`,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export async function handler(req, res) {
|
||||
const { resource = '' } = req.query
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [_, name, domain] = resource.match(regex) || []
|
||||
if (!(name && domain))
|
||||
return res.status(400).json({
|
||||
error: 'Query parameter "?resource=acct:<USER>@<DOMAIN>" is missing.',
|
||||
})
|
||||
|
||||
const session = req.app.get('driver').session()
|
||||
try {
|
||||
const [slug] = await session.readTransaction(async (t) => {
|
||||
const result = await t.run('MATCH (u:User {slug: $slug}) RETURN u.slug AS slug', {
|
||||
slug: name,
|
||||
})
|
||||
return result.records.map((record) => record.get('slug'))
|
||||
})
|
||||
if (!slug)
|
||||
return res.status(404).json({
|
||||
error: `No record found for "${name}@${domain}".`,
|
||||
})
|
||||
const webFinger = createWebFinger(name)
|
||||
return res.contentType('application/jrd+json').json(webFinger)
|
||||
} catch (error) {
|
||||
debug(error)
|
||||
return res.status(500).json({
|
||||
error: `Something went terribly wrong. Please visit ${CONFIG.SUPPORT_URL}`,
|
||||
})
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const router = express.Router()
|
||||
router.use('/webfinger', cors(), express.urlencoded({ extended: true }), handler)
|
||||
return router
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
import { handler } from './webfinger'
|
||||
import Factory, { cleanDatabase } from '../../db/factories'
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
import CONFIG from '../../config'
|
||||
|
||||
let resource, res, json, status, contentType
|
||||
|
||||
const driver = getDriver()
|
||||
|
||||
const request = () => {
|
||||
json = jest.fn()
|
||||
status = jest.fn(() => ({ json }))
|
||||
contentType = jest.fn(() => ({ json }))
|
||||
res = { status, contentType }
|
||||
const req = {
|
||||
app: {
|
||||
get: (key) => {
|
||||
return {
|
||||
driver,
|
||||
}[key]
|
||||
},
|
||||
},
|
||||
query: {
|
||||
resource,
|
||||
},
|
||||
}
|
||||
return handler(req, res)
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
})
|
||||
|
||||
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
|
||||
afterEach(async () => {
|
||||
await cleanDatabase()
|
||||
})
|
||||
|
||||
describe('webfinger', () => {
|
||||
describe('no ressource', () => {
|
||||
beforeEach(() => {
|
||||
resource = undefined
|
||||
})
|
||||
|
||||
it('sends HTTP 400', async () => {
|
||||
await request()
|
||||
expect(status).toHaveBeenCalledWith(400)
|
||||
expect(json).toHaveBeenCalledWith({
|
||||
error: 'Query parameter "?resource=acct:<USER>@<DOMAIN>" is missing.',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('?resource query param', () => {
|
||||
describe('is missing acct:', () => {
|
||||
beforeEach(() => {
|
||||
resource = 'some-user@domain'
|
||||
})
|
||||
|
||||
it('sends HTTP 400', async () => {
|
||||
await request()
|
||||
expect(status).toHaveBeenCalledWith(400)
|
||||
expect(json).toHaveBeenCalledWith({
|
||||
error: 'Query parameter "?resource=acct:<USER>@<DOMAIN>" is missing.',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('has no domain', () => {
|
||||
beforeEach(() => {
|
||||
resource = 'acct:some-user@'
|
||||
})
|
||||
|
||||
it('sends HTTP 400', async () => {
|
||||
await request()
|
||||
expect(status).toHaveBeenCalledWith(400)
|
||||
expect(json).toHaveBeenCalledWith({
|
||||
error: 'Query parameter "?resource=acct:<USER>@<DOMAIN>" is missing.',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with acct:', () => {
|
||||
beforeEach(() => {
|
||||
resource = 'acct:some-user@domain'
|
||||
})
|
||||
|
||||
it('returns error as json', async () => {
|
||||
await request()
|
||||
expect(status).toHaveBeenCalledWith(404)
|
||||
expect(json).toHaveBeenCalledWith({
|
||||
error: 'No record found for "some-user@domain".',
|
||||
})
|
||||
})
|
||||
|
||||
describe('given a user for acct', () => {
|
||||
beforeEach(async () => {
|
||||
await Factory.build('user', { slug: 'some-user' })
|
||||
})
|
||||
|
||||
it('returns user object', async () => {
|
||||
await request()
|
||||
expect(contentType).toHaveBeenCalledWith('application/jrd+json')
|
||||
expect(json).toHaveBeenCalledWith({
|
||||
links: [
|
||||
{
|
||||
href: `${CONFIG.CLIENT_URI}/activitypub/users/some-user`,
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
},
|
||||
],
|
||||
subject: `acct:some-user@${new URL(CONFIG.CLIENT_URI).host}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,104 +0,0 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,172 +0,0 @@
|
||||
// import dotenv from 'dotenv'
|
||||
// import { resolve } from 'path'
|
||||
import crypto from 'crypto'
|
||||
import request from 'request'
|
||||
import CONFIG from './../../config'
|
||||
const debug = require('debug')('ea:security')
|
||||
|
||||
// TODO Does this reference a local config? Why?
|
||||
// dotenv.config({ path: resolve('src', 'activitypub', '.env') })
|
||||
|
||||
export function generateRsaKeyPair(options = {}) {
|
||||
const { passphrase = CONFIG.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 = CONFIG.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)
|
||||
const 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',
|
||||
]
|
||||
@ -1,117 +0,0 @@
|
||||
import { activityPub } from '../ActivityPub'
|
||||
import { throwErrorIfApolloErrorOccurred } from './index'
|
||||
// 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')
|
||||
)
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
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()
|
||||
})
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
import { activityPub } from '../ActivityPub'
|
||||
import gql from 'graphql-tag'
|
||||
import { createSignature } from '../security'
|
||||
import request from 'request'
|
||||
import CONFIG from './../../config'
|
||||
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 = ${CONFIG.PRIVATE_KEY_PASSPHRASE}`)
|
||||
return new Promise((resolve, reject) => {
|
||||
debug('inside signAndSend')
|
||||
// get the private key
|
||||
activityPub.dataSource.client
|
||||
.query({
|
||||
query: gql`
|
||||
query {
|
||||
User(slug: "${fromName}") {
|
||||
privateKey
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.then((result) => {
|
||||
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()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import dotenv from 'dotenv'
|
||||
import emails from './emails.js'
|
||||
import metadata from './metadata.js'
|
||||
import emails from './emails'
|
||||
import metadata from './metadata'
|
||||
|
||||
// Load env file
|
||||
if (require.resolve) {
|
||||
@ -15,10 +15,11 @@ if (require.resolve) {
|
||||
}
|
||||
|
||||
// Use Cypress env or process.env
|
||||
declare let Cypress: any | undefined
|
||||
const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env // eslint-disable-line no-undef
|
||||
|
||||
const environment = {
|
||||
NODE_ENV: env.NODE_ENV || process.NODE_ENV,
|
||||
NODE_ENV: env.NODE_ENV || process.env.NODE_ENV,
|
||||
DEBUG: env.NODE_ENV !== 'production' && env.DEBUG,
|
||||
TEST: env.NODE_ENV === 'test',
|
||||
PRODUCTION: env.NODE_ENV === 'production',
|
||||
@ -39,6 +40,8 @@ const server = {
|
||||
JWT_EXPIRES: env.JWT_EXPIRES || '2y',
|
||||
}
|
||||
|
||||
const hasDKIMData = env.SMTP_DKIM_DOMAINNAME && env.SMTP_DKIM_KEYSELECTOR && env.SMTP_DKIM_PRIVATKEY
|
||||
|
||||
const smtp = {
|
||||
SMTP_HOST: env.SMTP_HOST,
|
||||
SMTP_PORT: env.SMTP_PORT,
|
||||
@ -46,6 +49,10 @@ const smtp = {
|
||||
SMTP_SECURE: env.SMTP_SECURE === 'true',
|
||||
SMTP_USERNAME: env.SMTP_USERNAME,
|
||||
SMTP_PASSWORD: env.SMTP_PASSWORD,
|
||||
SMTP_DKIM_DOMAINNAME: hasDKIMData && env.SMTP_DKIM_DOMAINNAME,
|
||||
SMTP_DKIM_KEYSELECTOR: hasDKIMData && env.SMTP_DKIM_KEYSELECTOR,
|
||||
// PEM format: https://docs.progress.com/bundle/datadirect-hybrid-data-pipeline-installation-46/page/PEM-file-format.html
|
||||
SMTP_DKIM_PRIVATKEY: hasDKIMData && env.SMTP_DKIM_PRIVATKEY.replace(/\\n/g, '\n'), // replace all "\n" in .env string by real line break
|
||||
}
|
||||
|
||||
const neo4j = {
|
||||
@ -90,14 +97,12 @@ const options = {
|
||||
}
|
||||
|
||||
// Check if all required configs are present
|
||||
if (require.resolve) {
|
||||
// are we in a nodejs environment?
|
||||
Object.entries(required).map((entry) => {
|
||||
if (!entry[1]) {
|
||||
throw new Error(`ERROR: "${entry[0]}" env variable is missing.`)
|
||||
}
|
||||
})
|
||||
}
|
||||
Object.entries(required).map((entry) => {
|
||||
if (!entry[1]) {
|
||||
throw new Error(`ERROR: "${entry[0]}" env variable is missing.`)
|
||||
}
|
||||
return entry
|
||||
})
|
||||
|
||||
export default {
|
||||
...environment,
|
||||
@ -1,4 +1,4 @@
|
||||
// this file is duplicated in `backend/src/config/logos.js` and `webapp/constants/logos.js` and replaced on rebranding
|
||||
// this file is duplicated in `backend/src/config/logos` and `webapp/constants/logos.js` and replaced on rebranding
|
||||
// this are the paths in the webapp
|
||||
export default {
|
||||
LOGO_HEADER_PATH: '/img/custom/logo-horizontal.svg',
|
||||
@ -1,4 +1,4 @@
|
||||
// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js` and replaced on rebranding
|
||||
// this file is duplicated in `backend/src/config/metadata` and `webapp/constants/metadata.js` and replaced on rebranding
|
||||
export default {
|
||||
APPLICATION_NAME: 'ocelot.social',
|
||||
APPLICATION_SHORT_NAME: 'ocelot',
|
||||
@ -1,4 +1,4 @@
|
||||
// this file is duplicated in `backend/src/constants/metadata.js` and `webapp/constants/metadata.js`
|
||||
// this file is duplicated in `backend/src/constants/metadata` and `webapp/constants/metadata.js`
|
||||
export const CATEGORIES_MIN = 1
|
||||
export const CATEGORIES_MAX = 3
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
// this file is duplicated in `backend/src/constants/group.js` and `webapp/constants/group.js`
|
||||
// this file is duplicated in `backend/src/constants/group` and `webapp/constants/group.js`
|
||||
export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 50 // with removed HTML tags
|
||||
export const DESCRIPTION_EXCERPT_HTML_LENGTH = 250 // with removed HTML tags
|
||||
@ -1,5 +0,0 @@
|
||||
// this file is duplicated in `backend/src/config/metadata.js` and `webapp/constants/metadata.js`
|
||||
export default {
|
||||
NONCE_LENGTH: 5,
|
||||
INVITE_CODE_LENGTH: 6,
|
||||
}
|
||||
5
backend/src/constants/registration.ts
Normal file
5
backend/src/constants/registration.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// this file is duplicated in `backend/src/config/metadata` and `webapp/constants/metadata.js`
|
||||
export default {
|
||||
NONCE_LENGTH: 5,
|
||||
INVITE_CODE_LENGTH: 6,
|
||||
}
|
||||
2
backend/src/db/compiler.ts
Normal file
2
backend/src/db/compiler.ts
Normal file
@ -0,0 +1,2 @@
|
||||
const tsNode = require('ts-node')
|
||||
module.exports = tsNode.register
|
||||
@ -2,10 +2,10 @@ import { v4 as uuid } from 'uuid'
|
||||
import slugify from 'slug'
|
||||
import { hashSync } from 'bcryptjs'
|
||||
import { Factory } from 'rosie'
|
||||
import faker from '@faker-js/faker'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { getDriver, getNeode } from './neo4j'
|
||||
import CONFIG from '../config/index.js'
|
||||
import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode.js'
|
||||
import CONFIG from '../config/index'
|
||||
import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode'
|
||||
|
||||
const neode = getNeode()
|
||||
|
||||
@ -15,7 +15,7 @@ const uniqueImageUrl = (imageUrl) => {
|
||||
return newUrl.toString()
|
||||
}
|
||||
|
||||
export const cleanDatabase = async (options = {}) => {
|
||||
export const cleanDatabase = async (options: any = {}) => {
|
||||
const { driver = getDriver() } = options
|
||||
const session = driver.session()
|
||||
try {
|
||||
@ -63,7 +63,7 @@ Factory.define('basicUser')
|
||||
.option('password', '1234')
|
||||
.attrs({
|
||||
id: uuid,
|
||||
name: faker.name.findName,
|
||||
name: faker.name.fullName,
|
||||
password: '1234',
|
||||
role: 'user',
|
||||
termsAndConditionsAgreedVersion: '0.0.1',
|
||||
@ -165,7 +165,7 @@ Factory.define('post')
|
||||
})
|
||||
.after(async (buildObject, options) => {
|
||||
const [post, author, image, /* categories, */ tags] = await Promise.all([
|
||||
neode.create('Post', buildObject),
|
||||
neode.create('Article', buildObject),
|
||||
options.author,
|
||||
options.image,
|
||||
// options.categories,
|
||||
@ -18,13 +18,13 @@ export function up(next) {
|
||||
rxSession
|
||||
.beginTransaction()
|
||||
.pipe(
|
||||
flatMap((txc) =>
|
||||
flatMap((txc: any) =>
|
||||
concat(
|
||||
txc
|
||||
.run('MATCH (email:EmailAddress) RETURN email {.email}')
|
||||
.records()
|
||||
.pipe(
|
||||
map((record) => {
|
||||
map((record: any) => {
|
||||
const { email } = record.get('email')
|
||||
const normalizedEmail = normalizeEmail(email)
|
||||
return { email, normalizedEmail }
|
||||
@ -45,7 +45,7 @@ export function up(next) {
|
||||
)
|
||||
.records()
|
||||
.pipe(
|
||||
map((r) => ({
|
||||
map((r: any) => ({
|
||||
oldEmail: email,
|
||||
email: r.get('email'),
|
||||
user: r.get('user'),
|
||||
@ -12,7 +12,7 @@ export function up(next) {
|
||||
rxSession
|
||||
.beginTransaction()
|
||||
.pipe(
|
||||
flatMap((transaction) =>
|
||||
flatMap((transaction: any) =>
|
||||
concat(
|
||||
transaction
|
||||
.run(
|
||||
@ -23,7 +23,7 @@ export function up(next) {
|
||||
)
|
||||
.records()
|
||||
.pipe(
|
||||
map((record) => {
|
||||
map((record: any) => {
|
||||
const { id: locationId } = record.get('location')
|
||||
return { locationId }
|
||||
}),
|
||||
@ -40,7 +40,7 @@ export function up(next) {
|
||||
)
|
||||
.records()
|
||||
.pipe(
|
||||
map((record) => ({
|
||||
map((record: any) => ({
|
||||
location: record.get('location'),
|
||||
updatedLocation: record.get('updatedLocation'),
|
||||
})),
|
||||
@ -3,7 +3,7 @@ import { existsSync, createReadStream } from 'fs'
|
||||
import path from 'path'
|
||||
import { S3 } from 'aws-sdk'
|
||||
import mime from 'mime-types'
|
||||
import { s3Configs } from '../../config'
|
||||
import s3Configs from '../../config'
|
||||
import https from 'https'
|
||||
|
||||
export const description = `
|
||||
@ -11,13 +11,13 @@ export async function up(next) {
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
// Implement your migration here.
|
||||
await transaction.run(`
|
||||
CREATE CONSTRAINT ON ( group:Group ) ASSERT group.id IS UNIQUE
|
||||
`)
|
||||
await transaction.run(`
|
||||
CREATE CONSTRAINT ON ( group:Group ) ASSERT group.slug IS UNIQUE
|
||||
`)
|
||||
// Those two indexes already exist
|
||||
// await transaction.run(`
|
||||
// CREATE CONSTRAINT ON ( group:Group ) ASSERT group.id IS UNIQUE
|
||||
// `)
|
||||
// await transaction.run(`
|
||||
// CREATE CONSTRAINT ON ( group:Group ) ASSERT group.slug IS UNIQUE
|
||||
// `)
|
||||
await transaction.run(`
|
||||
CALL db.index.fulltext.createNodeIndex("group_fulltext_search",["Group"],["name", "slug", "about", "description"])
|
||||
`)
|
||||
@ -10,7 +10,7 @@ export async function up(next) {
|
||||
try {
|
||||
// Drop indexes if they exist because due to legacy code they might be set already
|
||||
const indexesResponse = await transaction.run(`CALL db.indexes()`)
|
||||
const indexes = indexesResponse.records.map((record) => record.get('indexName'))
|
||||
const indexes = indexesResponse.records.map((record) => record.get('name'))
|
||||
if (indexes.indexOf('user_fulltext_search') > -1) {
|
||||
await transaction.run(`CALL db.index.fulltext.drop("user_fulltext_search")`)
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
|
||||
export const description = 'Add to all existing posts the Article label'
|
||||
|
||||
export async function up(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
await transaction.run(`
|
||||
MATCH (post:Post)
|
||||
SET post:Article
|
||||
RETURN post
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
await transaction.run(`
|
||||
MATCH (post:Post)
|
||||
REMOVE post:Article
|
||||
RETURN post
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
import { getDriver } from '../../db/neo4j'
|
||||
|
||||
export const description = 'Add postType property Article to all posts'
|
||||
|
||||
export async function up(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
await transaction.run(`
|
||||
MATCH (post:Post)
|
||||
SET post.postType = 'Article'
|
||||
RETURN post
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(next) {
|
||||
const driver = getDriver()
|
||||
const session = driver.session()
|
||||
const transaction = session.beginTransaction()
|
||||
|
||||
try {
|
||||
await transaction.run(`
|
||||
MATCH (post:Post)
|
||||
REMOVE post.postType
|
||||
RETURN post
|
||||
`)
|
||||
await transaction.commit()
|
||||
next()
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
await transaction.rollback()
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('rolled back')
|
||||
throw new Error(error)
|
||||
} finally {
|
||||
session.close()
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,7 @@ export function getNeode(options = {}) {
|
||||
if (!neodeInstance) {
|
||||
const { uri, username, password } = { ...defaultOptions, ...options }
|
||||
neodeInstance = new Neode(uri, username, password).with(models)
|
||||
neodeInstance.extend('Post', 'Article', {})
|
||||
return neodeInstance
|
||||
}
|
||||
return neodeInstance
|
||||
File diff suppressed because it is too large
Load Diff
1574
backend/src/db/seed.ts
Normal file
1574
backend/src/db/seed.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
||||
# GraphQL Playground
|
||||
|
||||
***Attention:** For using the GraphQL Playground set `DEBUG=true` in your backend `.env`, see `.env.template`!*
|
||||
|
||||
To use GraphQL Playground, we need to know some basics:
|
||||
|
||||
## How To Login?
|
||||
|
||||
50
backend/src/graphql/messages.ts
Normal file
50
backend/src/graphql/messages.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const createMessageMutation = () => {
|
||||
return gql`
|
||||
mutation ($roomId: ID!, $content: String!) {
|
||||
CreateMessage(roomId: $roomId, content: $content) {
|
||||
id
|
||||
content
|
||||
senderId
|
||||
username
|
||||
avatar
|
||||
date
|
||||
saved
|
||||
distributed
|
||||
seen
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const messageQuery = () => {
|
||||
return gql`
|
||||
query ($roomId: ID!, $first: Int, $offset: Int) {
|
||||
Message(roomId: $roomId, first: $first, offset: $offset, orderBy: indexId_desc) {
|
||||
_id
|
||||
id
|
||||
indexId
|
||||
content
|
||||
senderId
|
||||
author {
|
||||
id
|
||||
}
|
||||
username
|
||||
avatar
|
||||
date
|
||||
saved
|
||||
distributed
|
||||
seen
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const markMessagesAsSeen = () => {
|
||||
return gql`
|
||||
mutation ($messageIds: [String!]) {
|
||||
MarkMessagesAsSeen(messageIds: $messageIds)
|
||||
}
|
||||
`
|
||||
}
|
||||
@ -11,6 +11,8 @@ export const createPostMutation = () => {
|
||||
$content: String!
|
||||
$categoryIds: [ID]
|
||||
$groupId: ID
|
||||
$postType: PostType
|
||||
$eventInput: _EventInput
|
||||
) {
|
||||
CreatePost(
|
||||
id: $id
|
||||
@ -19,11 +21,31 @@ export const createPostMutation = () => {
|
||||
content: $content
|
||||
categoryIds: $categoryIds
|
||||
groupId: $groupId
|
||||
postType: $postType
|
||||
eventInput: $eventInput
|
||||
) {
|
||||
id
|
||||
slug
|
||||
title
|
||||
content
|
||||
disabled
|
||||
deleted
|
||||
postType
|
||||
author {
|
||||
name
|
||||
}
|
||||
categories {
|
||||
id
|
||||
}
|
||||
eventStart
|
||||
eventEnd
|
||||
eventLocationName
|
||||
eventVenue
|
||||
eventIsOnline
|
||||
eventLocation {
|
||||
lng
|
||||
lat
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -50,6 +72,7 @@ export const filterPosts = () => {
|
||||
id
|
||||
title
|
||||
content
|
||||
eventStart
|
||||
}
|
||||
}
|
||||
`
|
||||
67
backend/src/graphql/rooms.ts
Normal file
67
backend/src/graphql/rooms.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const createRoomMutation = () => {
|
||||
return gql`
|
||||
mutation ($userId: ID!) {
|
||||
CreateRoom(userId: $userId) {
|
||||
id
|
||||
roomId
|
||||
roomName
|
||||
lastMessageAt
|
||||
unreadCount
|
||||
#avatar
|
||||
users {
|
||||
_id
|
||||
id
|
||||
name
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const roomQuery = () => {
|
||||
return gql`
|
||||
query Room($first: Int, $offset: Int, $id: ID) {
|
||||
Room(first: $first, offset: $offset, id: $id, orderBy: lastMessageAt_desc) {
|
||||
id
|
||||
roomId
|
||||
roomName
|
||||
avatar
|
||||
lastMessageAt
|
||||
unreadCount
|
||||
lastMessage {
|
||||
_id
|
||||
id
|
||||
content
|
||||
senderId
|
||||
username
|
||||
avatar
|
||||
date
|
||||
saved
|
||||
distributed
|
||||
seen
|
||||
}
|
||||
users {
|
||||
_id
|
||||
id
|
||||
name
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
export const unreadRoomsQuery = () => {
|
||||
return gql`
|
||||
query {
|
||||
UnreadRooms
|
||||
}
|
||||
`
|
||||
}
|
||||
@ -5,14 +5,13 @@
|
||||
* @property fieldName String
|
||||
* @property callback Function
|
||||
*/
|
||||
function walkRecursive(data, fields, fieldName, callback, _key) {
|
||||
function walkRecursive(data, fields, fieldName, 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
|
||||
const key = _key.split('!')
|
||||
if (key.length === 1 || key[1] !== fieldName) data = callback(data, key[0])
|
||||
const fieldDef = fields.find((f) => f.field === _key)
|
||||
if (data && typeof data === 'string' && fieldDef) {
|
||||
if (!fieldDef.excludes?.includes(fieldName)) data = callback(data, _key)
|
||||
} else if (data && Array.isArray(data)) {
|
||||
// go into the rabbit hole and dig through that array
|
||||
data.forEach((res, index) => {
|
||||
@ -1,56 +0,0 @@
|
||||
import { generateRsaKeyPair } from '../activitypub/security'
|
||||
import { activityPub } from '../activitypub/ActivityPub'
|
||||
// import as from 'activitystrea.ms'
|
||||
|
||||
// const debug = require('debug')('backend:schema')
|
||||
|
||||
export default {
|
||||
Mutation: {
|
||||
// CreatePost: async (resolve, root, args, context, info) => {
|
||||
// args.activityId = activityPub.generateStatusId(context.user.slug)
|
||||
// args.objectId = activityPub.generateStatusId(context.user.slug)
|
||||
|
||||
// const post = await resolve(root, args, context, info)
|
||||
|
||||
// const { user: author } = context
|
||||
// const actorId = author.actorId
|
||||
// debug(`actorId = ${actorId}`)
|
||||
// const createActivity = await new Promise((resolve, reject) => {
|
||||
// as.create()
|
||||
// .id(`${actorId}/status/${args.activityId}`)
|
||||
// .actor(`${actorId}`)
|
||||
// .object(
|
||||
// as
|
||||
// .article()
|
||||
// .id(`${actorId}/status/${post.id}`)
|
||||
// .content(post.content)
|
||||
// .to('https://www.w3.org/ns/activitystreams#Public')
|
||||
// .publishedNow()
|
||||
// .attributedTo(`${actorId}`),
|
||||
// )
|
||||
// .prettyWrite((err, doc) => {
|
||||
// if (err) {
|
||||
// reject(err)
|
||||
// } else {
|
||||
// debug(doc)
|
||||
// const parsedDoc = JSON.parse(doc)
|
||||
// parsedDoc.send = true
|
||||
// resolve(JSON.stringify(parsedDoc))
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// try {
|
||||
// await activityPub.sendActivity(createActivity)
|
||||
// } catch (e) {
|
||||
// debug(`error sending post activity\n${e}`)
|
||||
// }
|
||||
// return post
|
||||
// },
|
||||
SignupVerification: async (resolve, root, args, context, info) => {
|
||||
const keys = generateRsaKeyPair()
|
||||
Object.assign(args, keys)
|
||||
args.actorId = `${activityPub.host}/activitypub/users/${args.slug}`
|
||||
return resolve(root, args, context, info)
|
||||
},
|
||||
},
|
||||
}
|
||||
60
backend/src/middleware/chatMiddleware.ts
Normal file
60
backend/src/middleware/chatMiddleware.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { isArray } from 'lodash'
|
||||
|
||||
const setRoomProps = (room) => {
|
||||
if (room.users) {
|
||||
room.users.forEach((user) => {
|
||||
user._id = user.id
|
||||
})
|
||||
}
|
||||
if (room.lastMessage) {
|
||||
room.lastMessage._id = room.lastMessage.id
|
||||
}
|
||||
}
|
||||
|
||||
const setMessageProps = (message, context) => {
|
||||
message._id = message.id
|
||||
if (message.senderId !== context.user.id) {
|
||||
message.distributed = true
|
||||
}
|
||||
}
|
||||
|
||||
const roomProperties = async (resolve, root, args, context, info) => {
|
||||
const resolved = await resolve(root, args, context, info)
|
||||
if (resolved) {
|
||||
if (isArray(resolved)) {
|
||||
resolved.forEach((room) => {
|
||||
setRoomProps(room)
|
||||
})
|
||||
} else {
|
||||
setRoomProps(resolved)
|
||||
}
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
const messageProperties = async (resolve, root, args, context, info) => {
|
||||
const resolved = await resolve(root, args, context, info)
|
||||
if (resolved) {
|
||||
if (isArray(resolved)) {
|
||||
resolved.forEach((message) => {
|
||||
setMessageProps(message, context)
|
||||
})
|
||||
} else {
|
||||
setMessageProps(resolved, context)
|
||||
}
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Room: roomProperties,
|
||||
Message: messageProperties,
|
||||
},
|
||||
Mutation: {
|
||||
CreateRoom: roomProperties,
|
||||
},
|
||||
Subscription: {
|
||||
chatMessageAdded: messageProperties,
|
||||
},
|
||||
}
|
||||
@ -8,7 +8,7 @@ import { exec, build } from 'xregexp/xregexp-all.js'
|
||||
// 2. If it starts with a digit '0-9' than a unicode letter has to follow.
|
||||
const regX = build('^((\\pL+[\\pL0-9]*)|([0-9]+\\pL+[\\pL0-9]*))$')
|
||||
|
||||
export default function (content) {
|
||||
export default function (content?) {
|
||||
if (!content) return []
|
||||
const $ = cheerio.load(content)
|
||||
// We can not search for class '.hashtag', because the classes are removed at the 'xss' middleware.
|
||||
@ -18,7 +18,7 @@ export default function (content) {
|
||||
return $(el).attr('data-hashtag-id')
|
||||
})
|
||||
.get()
|
||||
const hashtags = []
|
||||
const hashtags: any = []
|
||||
ids.forEach((id) => {
|
||||
const match = exec(id, regX)
|
||||
if (match != null) {
|
||||
@ -30,6 +30,7 @@ const standardSanitizeHtmlOptions = {
|
||||
'strike',
|
||||
'span',
|
||||
'blockquote',
|
||||
'usertag',
|
||||
],
|
||||
allowedAttributes: {
|
||||
a: ['href', 'class', 'target', 'data-*', 'contenteditable'],
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user