Merge master

This commit is contained in:
Wolfgang Huß 2025-05-25 19:35:38 +02:00
commit a067470b5e
909 changed files with 53996 additions and 21835 deletions

View File

@ -57,19 +57,10 @@ updates:
applies-to: version-updates
patterns:
- "*apollo-server*"
babel:
applies-to: version-updates
patterns:
- "@babel*"
metascraper:
applies-to: version-updates
patterns:
- "metascraper*"
typescript:
applies-to: version-updates
patterns:
- "ts*"
- "*types?"
# webapp
- package-ecosystem: docker

View File

@ -30,11 +30,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Remove old documentation files
run: rm -rf ./deployment/src/old/ ./CHANGELOG.md # workaround until https://github.com/gaurav-nelson/github-action-markdown-link-check/pull/183 has been done
- name: Remove uncheckable documentation files
run: rm -rf ./CHANGELOG.md # workaround until https://github.com/gaurav-nelson/github-action-markdown-link-check/pull/183 has been done
- name: Check Markdown Links
uses: gaurav-nelson/github-action-markdown-link-check@7d83e59a57f3c201c76eed3d33dff64ec4452d27 # 1.0.15
uses: gaurav-nelson/github-action-markdown-link-check@1b916f2cf6c36510a6059943104e3c42ce6c16bc # 1.0.15
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'no'
@ -54,7 +54,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup Node 20
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.0.3
with:
node-version: '20'

View File

@ -30,7 +30,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup Node 20
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.0.3
with:
node-version: 20
@ -38,7 +38,7 @@ jobs:
run: npm install && npm run docs:build
- name: Deploy Vuepress to Github Pages
uses: crazy-max/ghaction-github-pages@fbf0a4fa4e00f45accd6cf3232368436ec06ed59 # v4.0.0
uses: crazy-max/ghaction-github-pages@df5cc2bfa78282ded844b354faee141f06b41865 # v4.0.0
with:
target_branch: gh-pages
build_dir: .vuepress/dist

91
.github/workflows/docker-push.yml vendored Normal file
View File

@ -0,0 +1,91 @@
name: docker-push
on: push
jobs:
build-and-push-images:
strategy:
matrix:
app:
- name: neo4j
context: neo4j
file: neo4j/Dockerfile
target: community
- name: backend-base
context: backend
file: backend/Dockerfile
target: base
- name: backend-build
context: backend
file: backend/Dockerfile
target: build
- name: backend
context: backend
file: backend/Dockerfile
target: production
- name: webapp-base
context: webapp
file: webapp/Dockerfile
target: base
- name: webapp-build
context: webapp
file: webapp/Dockerfile
target: build
- name: webapp
context: webapp
file: webapp/Dockerfile
target: production
- name: maintenance-base
context: webapp
file: webapp/Dockerfile.maintenance
target: base
- name: maintenance-build
context: webapp
file: webapp/Dockerfile.maintenance
target: build
- name: maintenance
context: webapp
file: webapp/Dockerfile.maintenance
target: production
runs-on: ubuntu-latest
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}/${{ matrix.app.name }}
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Log in to the Container registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=schedule
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=ref,event=branch
type=ref,event=pr
type=sha
- name: Build and push Docker images
id: push
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0
with:
context: ${{ matrix.app.context }}
target: ${{ matrix.app.target }}
file: ${{ matrix.app.file }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@ -6,241 +6,12 @@ on:
- master
jobs:
##############################################################################
# JOB: DOCKER BUILD COMMUNITY NEO4J ##########################################
##############################################################################
build_production_neo4j:
name: Docker Build Production - Neo4J
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup env
run: |
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
- name: Neo4J | Build `community` image
run: |
docker build --target community \
--tag "ocelotsocialnetwork/neo4j-community:latest" \
--tag "ocelotsocialnetwork/neo4j-community:${VERSION}" \
--tag "ocelotsocialnetwork/neo4j-community:${BUILD_VERSION}" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
neo4j/
- name: Neo4J | Save docker image
run: docker save "ocelotsocialnetwork/neo4j-community" > /tmp/neo4j.tar
- name: Upload Artifact
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: docker-neo4j-community
path: /tmp/neo4j.tar
##############################################################################
# JOB: DOCKER BUILD PRODUCTION BACKEND #######################################
##############################################################################
build_production_backend:
name: Docker Build Production - Backend
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup env
run: |
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
- name: Backend | Build `production` image
run: |
docker build --target base \
--tag "ocelotsocialnetwork/backend:latest-base" \
--tag "ocelotsocialnetwork/backend:${VERSION}-base" \
--tag "ocelotsocialnetwork/backend:${BUILD_VERSION}-base" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
backend/
docker build --target code \
--tag "ocelotsocialnetwork/backend:latest-code" \
--tag "ocelotsocialnetwork/backend:${VERSION}-code" \
--tag "ocelotsocialnetwork/backend:${BUILD_VERSION}-code" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
backend/
docker build --target production \
--tag "ocelotsocialnetwork/backend:latest" \
--tag "ocelotsocialnetwork/backend:${VERSION}" \
--tag "ocelotsocialnetwork/backend:${BUILD_VERSION}" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
backend/
- name: Backend | Save docker image
run: docker save "ocelotsocialnetwork/backend" > /tmp/backend.tar
- name: Upload Artifact
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: docker-backend-production
path: /tmp/backend.tar
##############################################################################
# JOB: DOCKER BUILD PRODUCTION WEBAPP ########################################
##############################################################################
build_production_webapp:
name: Docker Build Production - WebApp
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup env
run: |
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
- name: Webapp | Build `production` image
run: |
docker build --target base \
--tag "ocelotsocialnetwork/webapp:latest-base" \
--tag "ocelotsocialnetwork/webapp:${VERSION}-base" \
--tag "ocelotsocialnetwork/webapp:${BUILD_VERSION}-base" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
webapp/
docker build --target code \
--tag "ocelotsocialnetwork/webapp:latest-code" \
--tag "ocelotsocialnetwork/webapp:${VERSION}-code" \
--tag "ocelotsocialnetwork/webapp:${BUILD_VERSION}-code" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
webapp/
docker build --target production \
--tag "ocelotsocialnetwork/webapp:latest" \
--tag "ocelotsocialnetwork/webapp:${VERSION}" \
--tag "ocelotsocialnetwork/webapp:${BUILD_VERSION}" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
webapp/
- name: Webapp | Save docker image
run: docker save "ocelotsocialnetwork/webapp" > /tmp/webapp.tar
- name: Upload Artifact
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: docker-webapp-production
path: /tmp/webapp.tar
##############################################################################
# JOB: DOCKER BUILD PRODUCTION MAINTENANCE ###################################
##############################################################################
build_production_maintenance:
name: Docker Build Production - Maintenance
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Setup env
run: |
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
- name: Maintenance | Build `production` image
run: |
docker build --target base \
--tag "ocelotsocialnetwork/maintenance:latest-base" \
--tag "ocelotsocialnetwork/maintenance:${VERSION}-base" \
--tag "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-base" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
-f webapp/Dockerfile.maintenance \
webapp/
docker build --target code \
--tag "ocelotsocialnetwork/maintenance:latest-code" \
--tag "ocelotsocialnetwork/maintenance:${VERSION}-code" \
--tag "ocelotsocialnetwork/maintenance:${BUILD_VERSION}-code" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
-f webapp/Dockerfile.maintenance \
webapp/
docker build --target production \
--tag "ocelotsocialnetwork/maintenance:latest" \
--tag "ocelotsocialnetwork/maintenance:${VERSION}" \
--tag "ocelotsocialnetwork/maintenance:${BUILD_VERSION}" \
--build-arg BBUILD_DATE=$BUILD_DATE \
--build-arg BBUILD_VERSION=$BUILD_VERSION \
--build-arg BBUILD_COMMIT=$BUILD_COMMIT \
-f webapp/Dockerfile.maintenance \
webapp/
- name: Maintenance | Save docker image
run: docker save "ocelotsocialnetwork/maintenance" > /tmp/maintenance.tar
- name: Upload Artifact
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: docker-maintenance-production
path: /tmp/maintenance.tar
##############################################################################
# JOB: UPLOAD TO DOCKERHUB ###################################################
##############################################################################
upload_to_dockerhub:
name: Upload to Dockerhub
runs-on: ubuntu-latest
needs: [build_production_neo4j,build_production_backend,build_production_webapp,build_production_maintenance]
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
steps:
- name: Download Docker Image (Neo4J)
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: docker-neo4j-community
path: /tmp
- run: docker load < /tmp/neo4j.tar
- name: Download Docker Image (Backend)
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: docker-backend-production
path: /tmp
- run: docker load < /tmp/backend.tar
- name: Download Docker Image (WebApp)
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: docker-webapp-production
path: /tmp
- run: docker load < /tmp/webapp.tar
- name: Download Docker Image (Maintenance)
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: docker-maintenance-production
path: /tmp
- run: docker load < /tmp/maintenance.tar
- name: login to dockerhub
run: echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin
- name: Push images
run: |
docker push --all-tags ocelotsocialnetwork/neo4j-community
docker push --all-tags ocelotsocialnetwork/backend
docker push --all-tags ocelotsocialnetwork/webapp
docker push --all-tags ocelotsocialnetwork/maintenance
##############################################################################
# JOB: GITHUB TAG LATEST VERSION #############################################
##############################################################################
github_tag:
name: Tag latest version on Github
runs-on: ubuntu-latest
needs: [upload_to_dockerhub]
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
@ -293,7 +64,7 @@ jobs:
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
#- name: Repository Dispatch
# uses: peter-evans/repository-dispatch@39d2331fbbe4be56c4434ca745a23633155f9cdf # v3.0.0
# uses: peter-evans/repository-dispatch@342b4dee76f7e22ff463b5e5d632b75319eb411e # v3.0.0
# with:
# token: ${{ github.token }}
# event-type: trigger-ocelot-build-success
@ -301,7 +72,7 @@ jobs:
# client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
- name: Repository Dispatch stage.ocelot.social
uses: peter-evans/repository-dispatch@39d2331fbbe4be56c4434ca745a23633155f9cdf # v3.0.0
uses: peter-evans/repository-dispatch@342b4dee76f7e22ff463b5e5d632b75319eb411e # v3.0.0
with:
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
event-type: trigger-ocelot-build-success
@ -309,7 +80,7 @@ jobs:
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@39d2331fbbe4be56c4434ca745a23633155f9cdf # v3.0.0
uses: peter-evans/repository-dispatch@342b4dee76f7e22ff463b5e5d632b75319eb411e # v3.0.0
with:
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
event-type: trigger-ocelot-build-success

View File

@ -37,7 +37,7 @@ jobs:
- name: Cache docker images
id: cache-neo4j
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/neo4j.tar
key: ${{ github.run_id }}-backend-neo4j-cache
@ -58,7 +58,7 @@ jobs:
- name: Cache docker images
id: cache-backend
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/backend.tar
key: ${{ github.run_id }}-backend-cache
@ -87,14 +87,14 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Restore Neo4J cache
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.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@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/backend.tar
key: ${{ github.run_id }}-backend-cache
@ -112,7 +112,8 @@ jobs:
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
# doesn't work without the --build flag - this either means we should not load the cached images or cache the correct image
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps neo4j backend --build
- name: backend | Initialize Database
run: docker compose exec -T backend yarn db:migrate init

View File

@ -3,8 +3,62 @@ name: ocelot.social end-to-end test CI
on: push
jobs:
docker_preparation:
name: Fullstack test preparation
prepare_neo4j_image:
name: Fullstack | prepare neo4j image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Build docker image
run: |
docker build --target community -t "ocelotsocialnetwork/neo4j-community:test" neo4j/
docker save "ocelotsocialnetwork/neo4j-community:test" > /tmp/neo4j.tar
- name: Cache docker image
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/neo4j.tar
key: ${{ github.run_id }}-e2e-neo4j-cache
prepare_backend_image:
name: Fullstack | prepare backend image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Build docker image
run: |
docker build --target test -t "ocelotsocialnetwork/backend:test" backend/
docker save "ocelotsocialnetwork/backend:test" > /tmp/backend.tar
- name: Cache docker image
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/backend.tar
key: ${{ github.run_id }}-e2e-backend-cache
prepare_webapp_image:
name: Fullstack | prepare webapp image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Build docker 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@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/webapp.tar
key: ${{ github.run_id }}-e2e-webapp-cache
prepare_cypress:
name: Fullstack | prepare cypress
runs-on: ubuntu-latest
steps:
- name: Checkout code
@ -13,18 +67,7 @@ jobs:
- name: Copy env files
run: |
cp webapp/.env.template webapp/.env
cp frontend/.env.dist frontend/.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
cp backend/.env.test_e2e backend/.env
- name: Install cypress requirements
run: |
@ -35,21 +78,20 @@ jobs:
cd ..
yarn install
- name: Cache docker images
id: cache
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
- name: Cache docker image
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.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
key: ${{ github.run_id }}-e2e-cypress
fullstack_tests:
name: Fullstack tests
name: Fullstack | tests
if: success()
needs: docker_preparation
needs: [prepare_neo4j_image, prepare_backend_image, prepare_webapp_image, prepare_cypress]
runs-on: ubuntu-latest
env:
jobs: 8
@ -58,26 +100,42 @@ jobs:
# run copies of the current job in parallel
job: [1, 2, 3, 4, 5, 6, 7, 8]
steps:
- name: Restore cache
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
id: cache
- name: Restore cypress cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.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
fail-on-cache-miss: true
key: ${{ github.run_id }}-e2e-cypress
restore-keys: ${{ github.run_id }}-e2e-cypress
- name: Restore neo4j cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/neo4j.tar
key: ${{ github.run_id }}-e2e-neo4j-cache
- name: Restore backend cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/backend.tar
key: ${{ github.run_id }}-e2e-backend-cache
- name: Restore webapp cache
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/webapp.tar
key: ${{ github.run_id }}-e2e-webapp-cache
- 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
docker load < /tmp/neo4j.tar
docker load < /tmp/backend.tar
docker load < /tmp/webapp.tar
docker compose -f docker-compose.yml -f docker-compose.test.yml up --build --detach --no-deps webapp neo4j backend mailserver
sleep 90s
- name: Full stack tests | run tests
@ -93,22 +151,29 @@ jobs:
- name: Full stack tests | if tests failed, upload report
id: e2e-report
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
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]
cleanup_cache:
name: Cleanup Cache
needs: fullstack_tests
runs-on: ubuntu-latest
permissions: write-all
continue-on-error: true
steps:
- name: Delete cache
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Full stack tests | cleanup cache
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
cacheKeys=$(gh cache list --json key --jq '.[] | select(.key | startswith("${{ github.run_id }}-e2e-")) | .key')
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeys
do
gh cache delete "$cacheKey"
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -50,7 +50,7 @@ jobs:
docker save "ocelotsocialnetwork/webapp:test" > /tmp/webapp.tar
- name: Cache docker image
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/webapp.tar
key: ${{ github.run_id }}-webapp-cache
@ -79,7 +79,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- name: Restore webapp cache
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.0.2
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.0.2
with:
path: /tmp/webapp.tar
key: ${{ github.run_id }}-webapp-cache
@ -94,7 +94,8 @@ jobs:
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
# doesn't work without the --build flag - this either means we should not load the cached images or cache the correct image
run: docker compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp --build
- name: webapp | Unit tests incl. coverage check
run: docker compose exec -T webapp yarn test

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.actor != 'dependabot[bot]' }}
steps:
- uses: amannn/action-semantic-pull-request@40166f00814508ec3201fc8595b393d451c8cd80 # v5.5.3
- uses: amannn/action-semantic-pull-request@335288255954904a41ddda8947c8f2c844b8bfeb # v5.5.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

2
.gitignore vendored
View File

@ -10,11 +10,13 @@ yarn-error.log*
kubeconfig.yaml
backup-cron-job.log
.vscode
.nuxt
node_modules/
cypress/videos
cypress/screenshots/
cypress.env.json
deployment/configurations/
.vuepress/.cache/
.vuepress/.temp/

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
nodejs 20.12.1

File diff suppressed because it is too large Load Diff

View File

@ -186,7 +186,6 @@ $ cp .env.template .env
# in folder backend/
$ cp .env.template .env
```
For Development:

View File

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

View File

@ -6,18 +6,28 @@ NEO4J_PASSWORD=letmein
GRAPHQL_URI=http://localhost:4000
CLIENT_URI=http://localhost:3000
# EMail
EMAIL_SUPPORT="devops@ocelot.social"
# E-Mail default settings
SUPPORT_EMAIL="devops@ocelot.social"
EMAIL_DEFAULT_SENDER="devops@ocelot.social"
SMTP_HOST=
SMTP_PORT=
SMTP_IGNORE_TLS=true
SMTP_MAX_CONNECTIONS=5
SMTP_MAX_MESSAGES=Infinity
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_SECURE="false" # true for 465, false for other ports
SMTP_DKIM_DOMAINNAME=
SMTP_DKIM_KEYSELECTOR=
SMTP_DKIM_PRIVATKEY=
# E-Mail settings for our 'docker compose up mailserver'
# SMTP_HOST=localhost
# SMTP_PORT=1025
# SMTP_IGNORE_TLS=true
# SMTP_USERNAME=
# SMTP_PASSWORD=
# SMTP_MAX_CONNECTIONS=1
# SMTP_MAX_MESSAGES= 10
JWT_SECRET="b/&&7b78BF&fv/Vd"
JWT_EXPIRES="2y"

41
backend/.env.test_e2e Normal file
View File

@ -0,0 +1,41 @@
DEBUG=true
NEO4J_URI=bolt://localhost:7687
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=letmein
GRAPHQL_URI=http://localhost:4000
CLIENT_URI=http://localhost:3000
# E-Mail default settings
SUPPORT_EMAIL="devops@ocelot.social"
EMAIL_DEFAULT_SENDER="devops@ocelot.social"
SMTP_HOST=mailserver
SMTP_PORT=1025
SMTP_IGNORE_TLS=true
SMTP_MAX_CONNECTIONS=5
SMTP_MAX_MESSAGES=Infinity
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"
MAPBOX_TOKEN="pk.eyJ1IjoiYnVzZmFrdG9yIiwiYSI6ImNraDNiM3JxcDBhaWQydG1uczhpZWtpOW4ifQ.7TNRTO-o9aK1Y6MyW_Nd4g"
PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
SENTRY_DSN_BACKEND=
COMMIT=
PUBLIC_REGISTRATION=false
INVITE_REGISTRATION=true
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_ENDPOINT=
AWS_REGION=
AWS_BUCKET=
CATEGORIES_ACTIVE=false

3
backend/.eslintignore Normal file
View File

@ -0,0 +1,3 @@
node_modules/
build/
coverage/

232
backend/.eslintrc.cjs Normal file
View File

@ -0,0 +1,232 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
root: true,
env: {
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint', 'import', 'n', 'promise', 'security', 'no-catch-all'],
extends: [
'standard',
'eslint:recommended',
'plugin:n/recommended',
'plugin:prettier/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:promise/recommended',
'plugin:security/recommended-legacy',
'plugin:@eslint-community/eslint-comments/recommended',
'prettier',
],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
typescript: {
project: ['./tsconfig.json', './backend/tsconfig.json'],
},
node: true,
},
},
rules: {
'no-catch-all/no-catch-all': 'error',
'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': 'off', // not compatible with .eslintrc.cjs
'import/default': 'error',
'import/named': 'off', // has false positives
'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': 'off', // not compatible with neode
'import/no-default-export': 'off', // not compatible with neode
'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/callback-return': 'error',
'n/exports-style': 'error',
'n/file-extension-in-import': ['error', 'never'],
'n/global-require': 'error',
'n/handle-callback-err': 'error',
// 'n/hashbang': 'error', // part of n/recommended
'n/no-callback-literal': 'error',
// 'n/no-deprecated-api': 'error', // part of n/recommended
// 'n/no-exports-assign': 'error', // part of n/recommended
'n/no-extraneous-import': 'off', // duplicate of import/no-extraneous-dependencies // part of n/recommended
// 'n/no-extraneous-require': 'error', // part of n/recommended
'n/no-hide-core-modules': 'error',
'n/no-missing-import': 'off', // not compatible with typescript // part of n/recommended
// 'n/no-missing-require': 'error', // part of n/recommended
'n/no-mixed-requires': 'error',
'n/no-new-require': 'error',
'n/no-path-concat': 'error',
'n/no-process-env': 'error',
// 'n/no-process-exit': 'error', // part of n/recommended
'n/no-restricted-import': 'error',
'n/no-restricted-require': 'error',
'n/no-sync': 'error',
// 'n/no-unpublished-bin': 'error', // part of n/recommended
'n/no-unpublished-import': [
'error',
{ allowModules: ['apollo-server-testing', 'rosie', '@faker-js/faker', 'ts-jest'] },
], // part of n/recommended
'n/no-unpublished-require': ['error', { allowModules: ['ts-jest', 'require-json5'] }], // part of n/recommended
// 'n/no-unsupported-features/es-builtins': 'error', // part of n/recommended
// 'n/no-unsupported-features/es-syntax': 'error', // part of n/recommended
// 'n/no-unsupported-features/node-builtins': 'error', // part of n/recommended
'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-node-protocol': 'error',
'n/prefer-promises/dns': 'error',
'n/prefer-promises/fs': 'error',
// 'n/process-exit-as-throw': 'error', // part of n/recommended
'n/shebang': 'error',
// promise
// 'promise/always-return': 'error', // part of promise/recommended
'promise/avoid-new': 'error',
// 'promise/catch-or-return': 'error', // part of promise/recommended
// 'promise/no-callback-in-promise': 'warn', // part of promise/recommended
'promise/no-multiple-resolved': 'error',
'promise/no-native': 'off', // ES5 only
// 'promise/no-nesting': 'warn', // part of promise/recommended
// 'promise/no-new-statics': 'error', // part of promise/recommended
// 'promise/no-promise-in-callback': 'warn', // part of promise/recommended
// 'promise/no-return-in-finally': 'warn', // part of promise/recommended
// 'promise/no-return-wrap': 'error', // part of promise/recommended
// 'promise/param-names': 'error', // part of promise/recommended
'promise/prefer-await-to-callbacks': 'error',
'promise/prefer-catch': 'error',
'promise/spec-only': 'error',
// 'promise/valid-params': 'error', // part of promise/recommended
// 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',
'prettier',
],
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',
// respect underscore as acceptable unused variable
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
// this is to properly reference the referenced project database without requirement of compiling it
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',
},
},
{
extends: ['plugin:jsonc/recommended-with-jsonc'],
files: ['*.json', '*.json5', '*.jsonc'],
parser: 'jsonc-eslint-parser',
},
],
}

View File

@ -1,219 +0,0 @@
module.exports = {
root: true,
env: {
// es6: true,
node: true,
},
/* parserOptions: {
parser: 'babel-eslint'
},*/
parser: '@typescript-eslint/parser',
plugins: ['prettier', '@typescript-eslint' /*, 'import', 'n', 'promise'*/],
extends: [
'standard',
// 'eslint:recommended',
'plugin:prettier/recommended',
// 'plugin:import/recommended',
// 'plugin:import/typescript',
// 'plugin:security/recommended',
// 'plugin:@eslint-community/eslint-comments/recommended',
],
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'],
}, */
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',
},
},
],
};

View File

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

View File

@ -1,103 +1,47 @@
##################################################################################
# BASE (Is pushed to DockerHub for rebranding) ###################################
##################################################################################
FROM node:20.12.1-alpine3.19 AS base
# ENVs
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
ENV DOCKER_WORKDIR="/app"
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
ARG BBUILD_DATE="1970-01-01T00:00:00.00Z"
ENV BUILD_DATE=$BBUILD_DATE
## We cannot do $(yarn run version)-${BUILD_NUMBER} here so we default to 0.0.0-0
ARG BBUILD_VERSION="0.0.0-0"
ENV BUILD_VERSION=$BBUILD_VERSION
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
ARG BBUILD_COMMIT="0000000"
ENV BUILD_COMMIT=$BBUILD_COMMIT
## SET NODE_ENV
ENV NODE_ENV="production"
## App relevant Envs
ENV PORT="4000"
# Labels
LABEL org.label-schema.build-date="${BUILD_DATE}"
FROM node:24.0.2-alpine AS base
LABEL org.label-schema.name="ocelot.social:backend"
LABEL org.label-schema.description="Backend of the Social Network Software ocelot.social"
LABEL org.label-schema.usage="https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/README.md"
LABEL org.label-schema.url="https://ocelot.social"
LABEL org.label-schema.vcs-url="https://github.com/Ocelot-Social-Community/Ocelot-Social/tree/master/backend"
LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}"
LABEL org.label-schema.vendor="ocelot.social Community"
LABEL org.label-schema.version="${BUILD_VERSION}"
LABEL org.label-schema.schema-version="1.0"
LABEL maintainer="devops@ocelot.social"
# Install Additional Software
## install: git
RUN apk --no-cache add git python3 make g++
# Settings
## Expose Container Port
ENV NODE_ENV="production"
ENV PORT="4000"
EXPOSE ${PORT}
RUN apk --no-cache add git python3 make g++ bash linux-headers
RUN mkdir -p /app
WORKDIR /app
CMD ["/bin/bash", "-c", "yarn run start"]
## Workdir
RUN mkdir -p ${DOCKER_WORKDIR}
WORKDIR ${DOCKER_WORKDIR}
##################################################################################
# DEVELOPMENT (Connected to the local environment, to reload on demand) ##########
##################################################################################
FROM base AS development
CMD ["/bin/sh", "-c", "yarn install && yarn run dev"]
# We don't need to copy or build anything since we gonna bind to the
# local filesystem which will need a rebuild anyway
# Run command
# (for development we need to execute yarn install since the
# node_modules are on another volume and need updating)
CMD /bin/sh -c "yarn install && yarn run dev"
##################################################################################
# CODE (Does contain all code files and is pushed to DockerHub for rebranding) ###
##################################################################################
FROM base AS code
# copy everything, but do not build.
FROM base AS build
COPY . .
ONBUILD COPY ./branding/constants/ src/config/tmp
# copy categories to brand them (use yarn prod:db:data:categories)
ONBUILD COPY ./branding/constants/ src/constants/
ONBUILD RUN tools/replace-constants.sh
ONBUILD COPY ./branding/email/ src/middleware/helpers/email/
ONBUILD COPY ./branding/middlewares/ src/middleware/branding/
ONBUILD COPY ./branding/data/ src/db/data
ONBUILD COPY ./branding/public/ public/
ONBUILD RUN yarn install --production=false --frozen-lockfile --non-interactive
ONBUILD RUN yarn run build
ONBUILD RUN mkdir /build
ONBUILD RUN cp -r ./build /build
ONBUILD RUN cp -r ./public /build
ONBUILD RUN cp -r ./package.json yarn.lock /build
ONBUILD RUN cd /build && yarn install --production=true --frozen-lockfile --non-interactive
##################################################################################
# BUILD (Does contain all files and the compilate and is therefore bloated) ######
##################################################################################
FROM code AS build
# yarn install
RUN yarn install --production=false --frozen-lockfile --non-interactive
# yarn build
RUN /bin/sh -c "yarn run build"
##################################################################################
# TEST ###########################################################################
##################################################################################
FROM build AS test
# required for the migrations
# ONBUILD RUN cp -r ./src /src
CMD ["/bin/bash", "-c", "yarn run dev"]
# Run command
CMD /bin/sh -c "yarn run dev"
FROM build AS production_build
##################################################################################
# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) #
##################################################################################
FROM base AS production
# Copy "binary"-files from build image
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
COPY --from=build ${DOCKER_WORKDIR}/public/img/ ./public/img/
COPY --from=build ${DOCKER_WORKDIR}/public/providers.json ./public/providers.json
# Copy package.json for script definitions (lock file should not be needed)
COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
# Run command
CMD /bin/sh -c "yarn run start"
COPY --from=production_build /build .

View File

@ -6,12 +6,12 @@ Run the following command to install everything through docker.
The installation takes a bit longer on the first pass or on rebuild ...
```bash
```sh
# in main folder
$ docker-compose up
$ docker compose up
# or
# rebuild the containers for a cleanup
$ docker-compose up --build
$ docker compose up --build
```
Wait a little until your backend is up and running at [http://localhost:4000/](http://localhost:4000/).
@ -26,7 +26,7 @@ some known problems with more recent node versions). You can use the
[node version manager](https://github.com/nvm-sh/nvm) `nvm` to switch
between different local Node versions:
```bash
```sh
# install Node
$ cd backend
$ nvm install v20.12.1
@ -35,7 +35,7 @@ $ nvm use v20.12.1
Install node dependencies with [yarn](https://yarnpkg.com/en/):
```bash
```sh
# in main folder
$ cd backend
$ yarn install
@ -47,7 +47,7 @@ $ nvm use && yarn
Copy Environment Variables:
```bash
```sh
# in backend/
$ cp .env.template .env
```
@ -57,14 +57,14 @@ a [local Neo4J](http://localhost:7474) instance is up and running.
Start the backend for development with:
```bash
```sh
# in backend/
$ yarn run dev
```
or start the backend in production environment with:
```bash
```sh
# in backend/
$ yarn run start
```
@ -79,154 +79,141 @@ More details about our GraphQL playground and how to use it with ocelot.social c
![GraphQL Playground](../.gitbook/assets/graphql-playground.png)
### Database Indexes and Constraints
## Database
Database indexes and constraints need to be created and upgraded when the database and the backend are running:
A fresh database needs to be initialized and migrated.
::: tabs
@tab:active Docker
```bash
# in main folder while docker-compose is running
$ docker exec backend yarn run db:migrate init
# only once: init admin user and create indexes and constraints in Neo4j database
# for development
$ docker compose exec backend yarn prod:migrate init
# in production mode use command
$ docker compose exec backend /bin/sh -c "yarn prod:migrate init"
```sh
# in folder backend while database is running
yarn db:migrate init
# for docker environments:
docker exec ocelot-social-backend-1 yarn db:migrate init
# for docker production:
docker exec ocelot-social-backend-1 yarn prod:migrate init
```
```bash
# in main folder with docker compose running
$ docker exec backend yarn run db:migrate up
```sh
# in backend with database running (In docker or local)
yarn db:migrate up
# for docker development:
docker exec ocelot-social-backend-1 yarn db:migrate up
# for docker production
docker exec ocelot-social-backend-1 yarn prod:migrate up
```
@tab Without Docker
### Optional Data
```bash
# in folder backend/ while database is running
# make sure your database is running on http://localhost:7474/browser/
yarn run db:migrate init
You can seed some optional data into the database.
To create the default admin <admin@example.org> with password `1234` use:
```sh
# in backend with database running (In docker or local)
yarn db:data:admin
```
```bash
# in backend/ with database running (In docker or local)
yarn run db:migrate up
When using `CATEGORIES_ACTIVE=true` you also want to seed the categories with:
```sh
# in backend with database running (In docker or local)
yarn db:data:categories
```
:::
### Branding Data
#### Seed Database
You might need to seed some branding specific data into the database.
If you want your backend to return anything else than an empty response, you
need to seed your database:
To do so, run:
::: tabs
@tab:active Docker
```sh
# in backend with database running (In docker or local)
yarn db:data:branding
In another terminal run:
```bash
# in main folder while docker-compose is running
$ docker exec backend yarn run db:seed
# for docker
docker exec ocelot-social-backend-1 yarn db:data:branding
```
To reset the database run:
### Seed Data
```bash
# in main folder while docker-compose is running
$ docker exec backend yarn run db:reset
For a predefined set of test data you can seed the database with:
```sh
# in backend with database running (In docker or local)
yarn db:seed
# for docker
docker exec ocelot-social-backend-1 yarn db:seed
```
### Reset Data
In order to reset the database you can run:
```sh
# in backend with database running (In docker or local)
yarn db:reset
# or deleting the migrations as well
yarn db:reset:withmigrations
# for docker
docker exec ocelot-social-backend-1 yarn db:reset
# or deleting the migrations as well
docker exec ocelot-social-backend-1 yarn db:reset:withmigrations
# you could also wipe out your neo4j database and delete all volumes with:
$ docker-compose down -v
# if container is not running, run this command to set up your database indexes and constraints
$ docker exec backend yarn run db:migrate init
# And then upgrade the indexes and const
$ docker exec backend yarn run db:migrate up
docker compose down -v
```
@tab Without Docker
Run:
```bash
# in backend/ while database is running
$ yarn run db:seed
```
To reset the database run:
```bash
# in backend/ while database is running
$ yarn run db:reset
```
:::
> Note: This just deletes the data and not the constraints, hence you do not need to rerun `yarn db:migrate init` or `yarn db:migrate up`.
### Data migrations
Although Neo4J is schema-less,you might find yourself in a situation in which
you have to migrate your data e.g. because your data modeling has changed.
::: tabs
@tab:active Docker
Generate a data migration file:
```bash
# in main folder while docker-compose is running
$ docker-compose exec backend yarn run db:migrate:create your_data_migration
# Edit the file in ./src/db/migrations/
```
To run the migration:
```bash
# in main folder while docker-compose is running
$ docker exec backend yarn run db:migrate up
```
@tab Without Docker
Generate a data migration file:
```bash
# in backend/
```sh
# in backend
$ yarn run db:migrate:create your_data_migration
# Edit the file in ./src/db/migrations/
# for docker
# in main folder while docker compose is running
$ docker compose exec ocelot-social-backend-1 yarn run db:migrate:create your_data_migration
# Edit the file in ./src/db/migrations/
```
To run the migration:
```bash
```sh
# in backend/ while database is running
$ yarn run db:migrate up
```
:::
# for docker
# in main folder while docker compose is running
$ docker exec backend yarn run db:migrate up
```
## Testing
**Beware**: We have no multiple database setup at the moment. We clean the
database after each test, running the tests will wipe out all your data!
::: tabs
@tab:active Docker
Run the unit tests:
```bash
# in main folder while docker-compose is running
$ docker exec backend yarn run test
```
@tab Without Docker
Run the unit tests:
```bash
```sh
# in backend/ while database is running
$ yarn run test
# for docker
# in main folder while docker compose is running
$ docker exec ocelot-social-backend-1 yarn run test
```
:::
If the snapshots of the emails must be updated, you have to run the tests in docker! Otherwise the CI will fail.
```sh
# in main folder while docker compose is running
$ docker exec ocelot-social-backend-1 yarn run test -u src/emails/
```

View File

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

27
backend/jest.config.cjs Normal file
View File

@ -0,0 +1,27 @@
/* eslint-disable import/no-commonjs */
const requireJSON5 = require('require-json5')
const { pathsToModuleNameMapper } = require('ts-jest')
const { compilerOptions } = requireJSON5('./tsconfig.json')
module.exports = {
verbose: true,
preset: 'ts-jest',
collectCoverage: true,
collectCoverageFrom: [
'**/*.ts',
'!**/node_modules/**',
'!**/test/**',
'!**/build/**',
'!**/src/**/?(*.)+(spec|test).ts?(x)',
'!**/src/db/**',
],
coverageThreshold: {
global: {
lines: 90,
},
},
testMatch: ['**/src/**/?(*.)+(spec|test).ts?(x)'],
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
}

View File

@ -1,20 +0,0 @@
module.exports = {
verbose: true,
preset: 'ts-jest',
collectCoverage: true,
collectCoverageFrom: [
'**/*.ts',
'!**/node_modules/**',
'!**/test/**',
'!**/build/**',
'!**/src/**/?(*.)+(spec|test).ts?(x)',
'!**/src/db/**'
],
coverageThreshold: {
global: {
lines: 90,
},
},
testMatch: ['**/src/**/?(*.)+(spec|test).ts?(x)'],
setupFilesAfterEnv: ['<rootDir>/test/setup.ts']
}

View File

@ -1,6 +1,6 @@
{
"name": "ocelot-social-backend",
"version": "3.2.0",
"version": "3.6.1",
"description": "GraphQL Backend for ocelot.social",
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
"author": "ocelot.social Community",
@ -8,120 +8,132 @@
"private": false,
"main": "src/index.ts",
"scripts": {
"__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",
"build": "tsc && tsc-alias && ./scripts/build.copy.files.sh",
"dev": "nodemon --exec ts-node --require tsconfig-paths/register src/index.ts -e js,ts,gql",
"dev:debug": "nodemon --exec node --inspect=0.0.0.0:9229 build/src/index.js -e js,ts,gql",
"lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts,.cjs,.json,.json5,.jsonc .",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles",
"db:clean": "ts-node src/db/clean.ts",
"db:reset": "yarn run db:clean",
"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"
"db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts",
"db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts",
"db:seed": "ts-node --require tsconfig-paths/register src/db/seed.ts",
"db:data:admin": "ts-node --require tsconfig-paths/register src/db/admin.ts",
"db:data:badges": "ts-node --require tsconfig-paths/register src/db/badges.ts",
"db:data:branding": "ts-node --require tsconfig-paths/register src/db/data-branding.ts",
"db:data:categories": "ts-node --require tsconfig-paths/register src/db/categories.ts",
"db:migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --store ./src/db/migrate/store.ts",
"db:migrate:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create",
"prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js",
"prod:db:data:branding": "node build/src/db/data-branding.js",
"prod:db:data:categories": "node build/src/db/categories.js"
},
"dependencies": {
"@babel/cli": "~7.26.4",
"@babel/core": "^7.26.0",
"@babel/node": "~7.26.0",
"@babel/plugin-proposal-throw-expressions": "^7.25.9",
"@babel/preset-env": "~7.26.0",
"@babel/register": "^7.23.7",
"@aws-sdk/client-s3": "^3.817.0",
"@aws-sdk/lib-storage": "^3.797.0",
"@sentry/node": "^5.15.4",
"apollo-cache-inmemory": "~1.6.6",
"apollo-client": "~2.6.10",
"apollo-link-context": "~1.0.20",
"apollo-link-http": "~1.5.17",
"@types/mime-types": "^2.1.4",
"apollo-server": "~2.14.2",
"apollo-server-express": "^2.14.2",
"aws-sdk": "^2.1692.0",
"babel-core": "~7.0.0-0",
"babel-eslint": "~10.1.0",
"babel-jest": "~29.7.0",
"babel-plugin-transform-runtime": "^6.23.0",
"bcryptjs": "~2.4.3",
"bcryptjs": "~3.0.2",
"body-parser": "^1.20.3",
"cheerio": "~1.0.0",
"cors": "~2.8.5",
"cross-env": "~7.0.3",
"dotenv": "~16.4.7",
"express": "^4.21.2",
"dotenv": "~16.5.0",
"email-templates": "^12.0.2",
"express": "^5.1.0",
"graphql": "^14.6.0",
"graphql-middleware": "~4.0.2",
"graphql-middleware-sentry": "^3.2.1",
"graphql-redis-subscriptions": "^2.7.0",
"graphql-shield": "~7.2.2",
"graphql-subscriptions": "^1.1.0",
"graphql-tag": "~2.10.3",
"helmet": "~8.0.0",
"ioredis": "^4.16.1",
"graphql-upload": "^13.0.0",
"helmet": "~8.1.0",
"ioredis": "^5.6.1",
"jsonwebtoken": "~8.5.1",
"languagedetect": "^2.0.0",
"linkify-html": "^4.3.1",
"linkifyjs": "^4.2.0",
"linkify-html": "^4.2.0",
"lodash": "~4.17.21",
"merge-graphql-schemas": "^1.7.8",
"metascraper": "^5.33.5",
"metascraper-author": "^5.33.5",
"metascraper-date": "^5.33.5",
"metascraper-description": "^5.33.5",
"metascraper-image": "^5.33.5",
"metascraper-lang": "^5.33.5",
"metascraper": "^5.46.11",
"metascraper-author": "^5.46.11",
"metascraper-date": "^5.46.11",
"metascraper-description": "^5.46.11",
"metascraper-image": "^5.46.11",
"metascraper-lang": "^5.46.11",
"metascraper-lang-detector": "^4.10.2",
"metascraper-logo": "^5.33.5",
"metascraper-publisher": "^5.33.5",
"metascraper-logo": "^5.46.11",
"metascraper-publisher": "^5.46.11",
"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",
"metascraper-title": "^5.46.11",
"metascraper-url": "^5.46.11",
"metascraper-video": "^5.46.11",
"metascraper-youtube": "^5.46.11",
"migrate": "^2.1.0",
"mime-types": "^2.1.35",
"mime-types": "^3.0.1",
"minimatch": "^9.0.4",
"mustache": "^4.2.0",
"neo4j-driver": "^4.4.11",
"neo4j-graphql-js": "^2.11.5",
"neode": "^0.4.9",
"node-fetch": "^2.7.0",
"nodemailer": "^6.9.16",
"nodemailer": "^6.10.1",
"nodemailer-html-to-text": "^3.2.0",
"preview-email": "^3.1.0",
"pug": "^3.0.3",
"request": "~2.88.2",
"sanitize-html": "~2.14.0",
"sanitize-html": "~2.17.0",
"slug": "~9.1.0",
"subscriptions-transport-ws": "^0.9.19",
"trunc-html": "~1.1.2",
"uuid": "~9.0.1",
"validator": "^13.12.0",
"xregexp": "^4.3.0"
"validator": "^13.15.0",
"xregexp": "^5.1.2"
},
"devDependencies": {
"@faker-js/faker": "9.3.0",
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
"@faker-js/faker": "9.8.0",
"@types/email-templates": "^10.0.4",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.2",
"@types/lodash": "^4.17.17",
"@types/node": "^22.15.21",
"@types/slug": "^5.0.9",
"@types/uuid": "~9.0.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"apollo-server-testing": "~2.11.0",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-config-prettier": "^10.1.5",
"eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-import-resolver-typescript": "^4.3.4",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.10.0",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-jsonc": "^2.20.1",
"eslint-plugin-n": "^17.17.0",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-security": "^3.0.1",
"jest": "^29.7.0",
"nodemon": "~3.1.9",
"prettier": "^3.4.2",
"nodemon": "~3.1.10",
"prettier": "^3.5.3",
"require-json5": "^1.3.0",
"rosie": "^2.1.1",
"ts-jest": "^29.2.5",
"ts-jest": "^29.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
"tsc-alias": "^1.8.16",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.8.3"
},
"resolutions": {
"**/**/fs-capacitor": "^6.2.0",
"**/graphql-upload": "^11.0.0",
"nan": "2.17.0"
"**/strip-ansi": "6.0.1",
"**/string-width": "4.2.0",
"**/wrap-ansi": "7.0.0"
},
"engines": {
"node": ">=20.12.1"
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="400" height="346.67" version="1.1" viewBox="0 0 400 346.67" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="linearGradient4" x1="708.76" x2="493.17" y1="280.91" y2="65.326" gradientTransform="translate(-404.06 .215)" gradientUnits="userSpaceOnUse">
<stop stop-color="#c1c1c1" offset="0"/>
<stop stop-color="#fcfcfc" offset="1"/>
</linearGradient>
</defs>
<path d="m-0.21505 173.98 100.65-173.76h198.71l101.08 173.76-99.785 172.04-201.29 0.43011z" fill="#bebebe"/>
<path d="m22.482 173.91 89.236-154.07h176.18l89.617 154.07-88.473 152.54-178.47 0.38135z" fill="url(#linearGradient4)"/>
</svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="513"
height="444"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs3" />
<g
fill="none"
fill-rule="evenodd"
id="g3">
<path
fill="#333"
d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"
id="path1"
style="fill:#868383;fill-opacity:1" />
<g
fill="#ffffff"
id="g2"
transform="translate(92)">
<path
d="m 35.01,367.726 c -0.08,-21.169 -0.205,-53.162 21.257,-71.332 3.817,-3.253 9.93,-7.497 17.321,-9.224 2.575,-0.523 4.956,-0.756 7.262,-0.979 4.438,-0.431 8.27,-0.804 12.054,-2.9 l 4.954,-2.846 c 9.87,-5.655 19.194,-10.996 28.226,-17.377 5.085,-3.632 6.726,-15.73 6.095,-25.428 -0.214,-2.792 -1.893,-5.7 -3.67,-8.777 -1.097,-1.901 -2.232,-3.867 -3.065,-5.916 l -0.073,-0.199 a 56.976,56.976 0 0 1 -0.422,-1.443 c -1.195,-4.205 -1.933,-6.378 -2.386,-7.476 -7.029,-0.944 -11.8,-8.647 -12.888,-21.006 l -0.031,-0.557 c -0.645,-12.785 0.808,-16.13 2.316,-17.716 0.24,-0.254 0.505,-0.475 0.783,-0.666 -1.754,-16.051 3.115,-32.521 13.358,-44.704 9.314,-11.079 21.955,-17.18 35.592,-17.18 3.73,0 7.55,0.458 11.355,1.362 25.63,6.228 41.679,30.27 40.062,59.227 0.53,0.251 1.018,0.61 1.44,1.066 2.752,2.964 2.47,10.97 2.22,14.276 l -0.024,0.41 c -0.335,5.236 -0.684,10.65 -3.052,15.73 -1.739,3.918 -4.405,6.242 -6.76,8.29 -2.396,2.089 -4.288,3.735 -5.294,6.885 -0.7,2.416 -1.645,4.866 -2.559,7.235 -1.752,4.538 -3.407,8.827 -3.54,13.244 -0.427,10.222 1.17,18.391 4.172,21.359 5.097,5.163 13.003,9.391 19.978,13.121 1.6,0.855 3.166,1.692 4.654,2.517 9.28,5.052 16.07,7.915 25.309,8.557 9.118,0.849 18.056,5.193 24.754,11.97 0.736,0.641 1.82,1.744 3.694,3.648 4.416,4.492 4.416,4.492 4.426,5.852 l 0.007,0.758 c 10.783,17.702 11.14,40.656 11.415,58.169 l 0.05,3.28 -3.278,0.028 c -42.05,0.363 -84.058,0.677 -126.058,0.993 -42.12,0.314 -84.232,0.632 -126.367,0.994 L 35.024,371 Z"
id="path2" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 637 B

After

Width:  |  Height:  |  Size: 637 B

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 654 B

After

Width:  |  Height:  |  Size: 654 B

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

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

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="513"
height="444"
version="1.1"
id="svg5"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5" />
<g
fill="none"
fill-rule="evenodd"
id="g5">
<path
fill="#ad245d"
d="M 384.5,0.297 512.325,221.9 384.325,443.602 128.5,443.704 0.675,222.1 128.675,0.4 Z"
id="path1"
style="display:inline;fill:#e67919;fill-opacity:1" />
<path
d="m 83.051688,348.226 c -0.08,-21.169 -0.205,-53.162 21.257002,-71.332 3.817,-3.253 9.93,-7.497 17.321,-9.224 2.575,-0.523 4.956,-0.756 7.262,-0.979 4.438,-0.431 8.27,-0.804 12.054,-2.9 l 4.954,-2.846 c 9.87,-5.655 19.194,-10.996 28.226,-17.377 5.085,-3.632 6.726,-15.73 6.095,-25.428 -0.214,-2.792 -1.893,-5.7 -3.67,-8.777 -1.097,-1.901 -2.232,-3.867 -3.065,-5.916 l -0.073,-0.199 a 56.976,56.976 0 0 1 -0.422,-1.443 c -1.195,-4.205 -1.933,-6.378 -2.386,-7.476 -7.029,-0.944 -11.8,-8.647 -12.888,-21.006 l -0.031,-0.557 c -0.645,-12.785 0.808,-16.13 2.316,-17.716 0.24,-0.254 0.505,-0.475 0.783,-0.666 -1.754,-16.051 3.115,-32.521 13.358,-44.704 9.314,-11.079 21.955,-17.18 35.592,-17.18 3.73,0 7.55,0.458 11.355,1.362 25.63,6.228 41.679,30.27 40.062,59.227 0.53,0.251 1.018,0.61 1.44,1.066 2.752,2.964 2.47,10.97 2.22,14.276 l -0.024,0.41 c -0.335,5.236 -0.684,10.65 -3.052,15.73 -1.739,3.918 -4.405,6.242 -6.76,8.29 -2.396,2.089 -4.288,3.735 -5.294,6.885 -0.7,2.416 -1.645,4.866 -2.559,7.235 -1.752,4.538 -3.407,8.827 -3.54,13.244 -0.427,10.222 1.17,18.391 4.172,21.359 5.097,5.163 13.003,9.391 19.978,13.121 1.6,0.855 3.166,1.692 4.654,2.517 9.28,5.052 16.07,7.915 25.309,8.557 9.118,0.849 18.056,5.193 24.754,11.97 0.736,0.641 1.82,1.744 3.694,3.648 4.416,4.492 4.416,4.492 4.426,5.852 l 0.007,0.758 c 10.783,17.702 11.14,40.656 11.415,58.169 l 0.05,3.28 -3.278,0.028 c -42.05,0.363 -84.058,0.677 -126.058,0.993 -42.12,0.314 -84.232,0.632 -126.367002,0.994 l -3.273,0.029 z"
id="path2-5"
style="display:inline;fill:#ffffff" />
<path
d="m 351.97006,182.43811 c -2.88526,0.005 -5.2219,2.34474 -5.223,5.23 0.001,2.88526 2.33774,5.22504 5.223,5.23 2.88747,-0.003 5.22769,-2.34253 5.231,-5.23 -0.003,-2.88708 -2.34292,-5.22669 -5.23,-5.23 m 0,40.237 c -19.307,0 -35.012,-15.703 -35.012,-35.007 0,-19.295 15.706,-34.993 35.011,-34.993 19.292,0 34.989,15.698 34.989,34.993 0,19.304 -15.697,35.007 -34.989,35.007"
fill="#ffffff"
id="path2"
style="display:inline" />
<path
d="m 351.97006,147.67511 c -22.338,0 -40.511,18.164 -40.511,40.492 0,22.336 18.173,40.508 40.511,40.508 22.325,0 40.489,-18.172 40.489,-40.508 0,-22.328 -18.164,-40.492 -40.489,-40.492 m 0,51.022 c 5.823,0 10.531,-4.716 10.531,-10.53 a 10.517,10.517 0 0 0 -10.53,-10.529 10.51,10.51 0 0 0 -10.523,10.529 c 0,5.814 4.7,10.53 10.522,10.53 m 0,-40.496 c 16.563,0 29.963,13.406 29.963,29.966 0,16.555 -13.4,29.982 -29.963,29.982 -16.555,0 -29.985,-13.427 -29.985,-29.982 0,-16.56 13.43,-29.966 29.985,-29.966"
fill="#ad245d"
id="path3"
style="display:inline;fill:#e67919;fill-opacity:1" />
<path
d="m 351.95906,152.88411 c -20.305,0 -36.825,16.06 -36.825,35.799 0,19.747 16.52,35.813 36.825,35.813 20.306,0 36.827,-16.066 36.827,-35.813 0,-19.74 -16.521,-35.8 -36.827,-35.8 z m -16.713,108.791 -4.11,-21.345 c -0.324,-0.129 -0.648,-0.265 -0.972,-0.404 l -18.012,12.169 -23.607,-23.609 12.186,-18.009 a 63.31,63.31 0 0 1 -0.403,-0.968 l -21.369,-4.122 v -33.383 l 21.361,-4.13 c 0.131,-0.327 0.267,-0.652 0.407,-0.979 l -12.18,-18.025 23.608,-23.612 18.015,12.198 c 0.322,-0.137 0.643,-0.27 0.964,-0.4 l 4.112,-21.381 h 33.416 l 4.13,21.387 c 0.319,0.13 0.638,0.26 0.956,0.396 l 18.024,-12.2 23.608,23.612 -12.186,18.031 c 0.139,0.324 0.273,0.648 0.402,0.971 l 21.363,4.133 v 33.381 l -21.37,4.124 c -0.13,0.32 -0.262,0.64 -0.398,0.96 l 12.19,18.017 -23.606,23.609 -18.021,-12.171 c -0.32,0.137 -0.642,0.27 -0.964,0.402 l -4.13,21.348 z"
fill="#ffffff"
id="path4"
style="display:inline" />
<path
d="m 351.95956,218.12311 c -17.342,0 -31.45,-13.656 -31.45,-30.44 0,-16.778 14.108,-30.427 31.45,-30.427 17.341,0 31.449,13.649 31.449,30.426 0,16.785 -14.108,30.441 -31.45,30.441 z m 20.979,-108.448 h -41.97 l -1.64,8.517 -1.953,10.156 -8.55,-5.788 -7.18,-4.862 -6.132,6.132 -17.399,17.4 -6.126,6.126 4.85,7.178 5.8,8.583 -10.173,1.966 -8.506,1.646 v 41.932 l 8.51,1.643 10.16,1.962 -5.787,8.553 -4.858,7.179 6.13,6.13 17.399,17.399 6.126,6.126 7.177,-4.85 8.56,-5.782 1.954,10.141 1.64,8.513 h 41.964 l 1.645,-8.507 1.964,-10.15 8.566,5.787 7.178,4.846 6.124,-6.124 17.4,-17.399 6.13,-6.13 -4.858,-7.18 -5.788,-8.554 10.153,-1.96 8.51,-1.643 v -41.932 l -8.507,-1.646 -10.165,-1.965 5.8,-8.584 4.85,-7.178 -6.125,-6.126 -17.4,-17.4 -6.13,-6.13 -7.18,4.858 -8.558,5.792 -1.964,-10.166 -1.64,-8.509 z m -20.98,118.948 c 23.176,0 41.95,-18.318 41.95,-40.94 0,-22.607 -18.774,-40.927 -41.95,-40.927 -23.174,0 -41.948,18.32 -41.948,40.926 0,22.623 18.774,40.941 41.949,40.941 z m 12.314,-108.448 3.855,19.963 a 47.184,47.184 0 0 1 6.037,2.502 l 16.824,-11.386 17.4,17.4 -11.362,16.818 a 53.171,53.171 0 0 1 2.502,6.066 l 19.932,3.855 v 24.601 l -19.932,3.848 a 56.644,56.644 0 0 1 -2.502,6.066 l 11.362,16.795 -17.4,17.4 -16.824,-11.364 a 44.931,44.931 0 0 1 -6.037,2.504 l -3.855,19.932 h -24.633 l -3.84,-19.932 a 43.821,43.821 0 0 1 -6.043,-2.504 l -16.818,11.364 -17.4,-17.4 11.364,-16.795 a 53.759,53.759 0 0 1 -2.51,-6.066 l -19.933,-3.848 v -24.601 l 19.933,-3.855 a 50.617,50.617 0 0 1 2.51,-6.066 l -11.364,-16.818 17.4,-17.4 16.818,11.386 a 45.957,45.957 0 0 1 6.043,-2.502 l 3.84,-19.963 h 24.632 z"
fill="#ad245d"
id="path5"
style="display:inline;fill:#e67919;fill-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="513"
height="444"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs3" />
<g
fill="none"
fill-rule="evenodd"
id="g3">
<path
fill="#AD245D"
d="M384.5.297L512.325 221.9l-128 221.702-255.825.102L.675 222.1 128.675.4z"
id="path1"
style="fill:#e67919;fill-opacity:1" />
<path
d="m 80.877371,348.226 c -0.08,-21.169 -0.205,-53.162 21.256999,-71.332 3.817,-3.253 9.93,-7.497 17.321,-9.224 2.575,-0.523 4.956,-0.756 7.262,-0.979 4.438,-0.431 8.27,-0.804 12.054,-2.9 l 4.954,-2.846 c 9.87,-5.655 19.194,-10.996 28.226,-17.377 5.085,-3.632 6.726,-15.73 6.095,-25.428 -0.214,-2.792 -1.893,-5.7 -3.67,-8.777 -1.097,-1.901 -2.232,-3.867 -3.065,-5.916 l -0.073,-0.199 a 56.976,56.976 0 0 1 -0.422,-1.443 c -1.195,-4.205 -1.933,-6.378 -2.386,-7.476 -7.029,-0.944 -11.8,-8.647 -12.888,-21.006 l -0.031,-0.557 c -0.645,-12.785 0.808,-16.13 2.316,-17.716 0.24,-0.254 0.505,-0.475 0.783,-0.666 -1.754,-16.051 3.115,-32.521 13.358,-44.704 9.314,-11.079 21.955,-17.18 35.592,-17.18 3.73,0 7.55,0.458 11.355,1.362 25.63,6.228 41.679,30.27 40.062,59.227 0.53,0.251 1.018,0.61 1.44,1.066 2.752,2.964 2.47,10.97 2.22,14.276 l -0.024,0.41 c -0.335,5.236 -0.684,10.65 -3.052,15.73 -1.739,3.918 -4.405,6.242 -6.76,8.29 -2.396,2.089 -4.288,3.735 -5.294,6.885 -0.7,2.416 -1.645,4.866 -2.559,7.235 -1.752,4.538 -3.407,8.827 -3.54,13.244 -0.427,10.222 1.17,18.391 4.172,21.359 5.097,5.163 13.003,9.391 19.978,13.121 1.6,0.855 3.166,1.692 4.654,2.517 9.28,5.052 16.07,7.915 25.309,8.557 9.118,0.849 18.056,5.193 24.754,11.97 0.736,0.641 1.82,1.744 3.694,3.648 4.416,4.492 4.416,4.492 4.426,5.852 l 0.007,0.758 c 10.783,17.702 11.14,40.656 11.415,58.169 l 0.05,3.28 -3.278,0.028 c -42.05,0.363 -84.058,0.677 -126.058,0.993 -42.12,0.314 -84.232,0.632 -126.366999,0.994 l -3.273,0.029 z"
id="path2-5"
style="display:inline;fill:#ffffff" />
<text
font-family="Impact"
font-size="118px"
font-style="condensed"
font-weight="700"
id="text2"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;font-family:Monospace;-inkscape-font-specification:Monospace;display:inline;fill:#ffffff"
x="6.8672509"
y="17"><tspan
x="262.86725"
y="225"
id="tspan2"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;font-family:Monospace;-inkscape-font-specification:Monospace;fill:#ffffff">&lt;/&gt;</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="512"
height="444"
version="1.1"
id="svg3"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs3" />
<g
fill="none"
fill-rule="evenodd"
id="g3">
<path
fill="#AD245D"
d="M384 .297L511.392 221.65l-128 221.702-255.392.352L.608 222.35 128.608.65z"
id="path1"
style="fill:#e67919;fill-opacity:1" />
<path
d="m 85.310542,348.226 c -0.08,-21.169 -0.205,-53.162 21.256998,-71.332 3.817,-3.253 9.93,-7.497 17.321,-9.224 2.575,-0.523 4.956,-0.756 7.262,-0.979 4.438,-0.431 8.27,-0.804 12.054,-2.9 l 4.954,-2.846 c 9.87,-5.655 19.194,-10.996 28.226,-17.377 5.085,-3.632 6.726,-15.73 6.095,-25.428 -0.214,-2.792 -1.893,-5.7 -3.67,-8.777 -1.097,-1.901 -2.232,-3.867 -3.065,-5.916 l -0.073,-0.199 a 56.976,56.976 0 0 1 -0.422,-1.443 c -1.195,-4.205 -1.933,-6.378 -2.386,-7.476 -7.029,-0.944 -11.8,-8.647 -12.888,-21.006 l -0.031,-0.557 c -0.645,-12.785 0.808,-16.13 2.316,-17.716 0.24,-0.254 0.505,-0.475 0.783,-0.666 -1.754,-16.051 3.115,-32.521 13.358,-44.704 9.314,-11.079 21.955,-17.18 35.592,-17.18 3.73,0 7.55,0.458 11.355,1.362 25.63,6.228 41.679,30.27 40.062,59.227 0.53,0.251 1.018,0.61 1.44,1.066 2.752,2.964 2.47,10.97 2.22,14.276 l -0.024,0.41 c -0.335,5.236 -0.684,10.65 -3.052,15.73 -1.739,3.918 -4.405,6.242 -6.76,8.29 -2.396,2.089 -4.288,3.735 -5.294,6.885 -0.7,2.416 -1.645,4.866 -2.559,7.235 -1.752,4.538 -3.407,8.827 -3.54,13.244 -0.427,10.222 1.17,18.391 4.172,21.359 5.097,5.163 13.003,9.391 19.978,13.121 1.6,0.855 3.166,1.692 4.654,2.517 9.28,5.052 16.07,7.915 25.309,8.557 9.118,0.849 18.056,5.193 24.754,11.97 0.736,0.641 1.82,1.744 3.694,3.648 4.416,4.492 4.416,4.492 4.426,5.852 l 0.007,0.758 c 10.783,17.702 11.14,40.656 11.415,58.169 l 0.05,3.28 -3.278,0.028 c -42.05,0.363 -84.058,0.677 -126.058,0.993 -42.12,0.314 -84.232,0.632 -126.366998,0.994 l -3.273,0.029 z"
id="path2-5"
style="display:inline;fill:#ffffff" />
<path
d="m 349.88573,158.65792 h 15.318 v 86.734 h -15.318 v 0.513 l -22.127,-17.64 v 0.12 h -10.21 v 25.51 h -22.128 v -25.51 h -20.424 v -52.722 h 52.762 v -0.508 l 22.127,-17.065 z m 34.313,72.093 -7.988,-4.591 c 15.803,-27.453 1.717,-46.642 1.106,-47.443 l 7.304,-5.607 c 0.774,0.993 18.547,24.675 -0.422,57.641 z m 27.361,21.216 -13.866,-7.975 c 27.437,-47.66 2.98,-80.973 1.918,-82.36 l 12.682,-9.737 c 1.343,1.723 32.2,42.839 -0.734,100.072 z"
id="path2-1"
style="display:inline;fill:#ffffff" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,257 +1,234 @@
[
{
"provider_name": "Codepen",
"provider_url": "https:\/\/codepen.io",
"endpoints": [
{
"schemes": [
"http:\/\/codepen.io\/*",
"https:\/\/codepen.io\/*"
],
"url": "http:\/\/codepen.io\/api\/oembed"
}
]
},
{
"provider_name": "DTube",
"provider_url": "https:\/\/d.tube\/",
"endpoints": [
{
"schemes": [
"https:\/\/d.tube\/v\/*"
],
"url": "https:\/\/api.d.tube\/oembed",
"discovery": true
}
]
},
{
"provider_name": "Facebook (Post)",
"provider_url": "https:\/\/www.facebook.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/www.facebook.com\/*\/posts\/*",
"https:\/\/www.facebook.com\/photos\/*",
"https:\/\/www.facebook.com\/*\/photos\/*",
"https:\/\/www.facebook.com\/photo.php*",
"https:\/\/www.facebook.com\/photo.php",
"https:\/\/www.facebook.com\/*\/activity\/*",
"https:\/\/www.facebook.com\/permalink.php",
"https:\/\/www.facebook.com\/media\/set?set=*",
"https:\/\/www.facebook.com\/questions\/*",
"https:\/\/www.facebook.com\/notes\/*\/*\/*"
],
"url": "https:\/\/www.facebook.com\/plugins\/post\/oembed.json",
"discovery": true
}
]
},
{
"provider_name": "Facebook (Video)",
"provider_url": "https:\/\/www.facebook.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/www.facebook.com\/*\/videos\/*",
"https:\/\/www.facebook.com\/video.php"
],
"url": "https:\/\/www.facebook.com\/plugins\/video\/oembed.json",
"discovery": true
}
]
},
{
"provider_name": "Flickr",
"provider_url": "https:\/\/www.flickr.com\/",
"endpoints": [
{
"schemes": [
"http:\/\/*.flickr.com\/photos\/*",
"http:\/\/flic.kr\/p\/*",
"https:\/\/*.flickr.com\/photos\/*",
"https:\/\/flic.kr\/p\/*"
],
"url": "https:\/\/www.flickr.com\/services\/oembed\/",
"discovery": true
}
]
},
{
"provider_name": "GIPHY",
"provider_url": "https:\/\/giphy.com",
"endpoints": [
{
"schemes": [
"https:\/\/giphy.com\/gifs\/*",
"http:\/\/gph.is\/*",
"https:\/\/media.giphy.com\/media\/*\/giphy.gif"
],
"url": "https:\/\/giphy.com\/services\/oembed",
"discovery": true
}
]
},
{
"provider_name": "Instagram",
"provider_url": "https:\/\/instagram.com",
"endpoints": [
{
"schemes": [
"http:\/\/instagram.com\/p\/*",
"http:\/\/instagr.am\/p\/*",
"http:\/\/www.instagram.com\/p\/*",
"http:\/\/www.instagr.am\/p\/*",
"https:\/\/instagram.com\/p\/*",
"https:\/\/instagr.am\/p\/*",
"https:\/\/www.instagram.com\/p\/*",
"https:\/\/www.instagr.am\/p\/*"
],
"url": "https:\/\/api.instagram.com\/oembed",
"formats": [
"json"
]
}
]
},
{
"provider_name": "Meetup",
"provider_url": "http:\/\/www.meetup.com",
"endpoints": [
{
"schemes": [
"http:\/\/meetup.com\/*",
"https:\/\/www.meetup.com\/*",
"https:\/\/meetup.com\/*",
"http:\/\/meetu.ps\/*"
],
"url": "https:\/\/api.meetup.com\/oembed",
"formats": [
"json"
]
}
]
},
{
"provider_name": "MixCloud",
"provider_url": "https:\/\/mixcloud.com\/",
"endpoints": [
{
"schemes": [
"http:\/\/www.mixcloud.com\/*\/*\/",
"https:\/\/www.mixcloud.com\/*\/*\/"
],
"url": "https:\/\/www.mixcloud.com\/oembed\/"
}
]
},
{
"provider_name": "Reddit",
"provider_url": "https:\/\/reddit.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/reddit.com\/r\/*\/comments\/*\/*",
"https:\/\/www.reddit.com\/r\/*\/comments\/*\/*"
],
"url": "https:\/\/www.reddit.com\/oembed"
}
]
},
{
"provider_name": "SlideShare",
"provider_url": "http:\/\/www.slideshare.net\/",
"endpoints": [
{
"schemes": [
"http:\/\/www.slideshare.net\/*\/*",
"http:\/\/fr.slideshare.net\/*\/*",
"http:\/\/de.slideshare.net\/*\/*",
"http:\/\/es.slideshare.net\/*\/*",
"http:\/\/pt.slideshare.net\/*\/*"
],
"url": "http:\/\/www.slideshare.net\/api\/oembed\/2",
"discovery": true
}
]
},
{
"provider_name": "SoundCloud",
"provider_url": "http:\/\/soundcloud.com\/",
"endpoints": [
{
"schemes": [
"http:\/\/soundcloud.com\/*",
"https:\/\/soundcloud.com\/*"
],
"url": "https:\/\/soundcloud.com\/oembed"
}
]
},
{
"provider_name": "Twitch",
"provider_url": "https:\/\/www.twitch.tv",
"endpoints": [
{
"schemes": [
"http:\/\/clips.twitch.tv\/*",
"https:\/\/clips.twitch.tv\/*",
"http:\/\/www.twitch.tv\/*",
"https:\/\/www.twitch.tv\/*",
"http:\/\/twitch.tv\/*",
"https:\/\/twitch.tv\/*"
],
"url": "https:\/\/api.twitch.tv\/v4\/oembed",
"formats": [
"json"
]
}
]
},
{
"provider_name": "Twitter",
"provider_url": "http:\/\/www.twitter.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/twitter.com\/*\/status\/*",
"https:\/\/*.twitter.com\/*\/status\/*"
],
"url": "https:\/\/publish.twitter.com\/oembed"
}
]
},
{
"provider_name": "Vimeo",
"provider_url": "https:\/\/vimeo.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/vimeo.com\/*",
"https:\/\/vimeo.com\/album\/*\/video\/*",
"https:\/\/vimeo.com\/channels\/*\/*",
"https:\/\/vimeo.com\/groups\/*\/videos\/*",
"https:\/\/vimeo.com\/ondemand\/*\/*",
"https:\/\/player.vimeo.com\/video\/*"
],
"url": "https:\/\/vimeo.com\/api\/oembed.{format}",
"discovery": true
}
]
},
{
"provider_name": "YouTube",
"provider_url": "https:\/\/www.youtube.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/*.youtube.com\/watch*",
"https:\/\/*.youtube.com\/v\/*",
"https:\/\/youtu.be\/*"
],
"url": "https:\/\/www.youtube.com\/oembed",
"discovery": true
}
]
}
]
{
"provider_name": "Codepen",
"provider_url": "https://codepen.io",
"endpoints": [
{
"schemes": ["http://codepen.io/*", "https://codepen.io/*"],
"url": "http://codepen.io/api/oembed"
}
]
},
{
"provider_name": "DTube",
"provider_url": "https://d.tube/",
"endpoints": [
{
"schemes": ["https://d.tube/v/*"],
"url": "https://api.d.tube/oembed",
"discovery": true
}
]
},
{
"provider_name": "Facebook (Post)",
"provider_url": "https://www.facebook.com/",
"endpoints": [
{
"schemes": [
"https://www.facebook.com/*/posts/*",
"https://www.facebook.com/photos/*",
"https://www.facebook.com/*/photos/*",
"https://www.facebook.com/photo.php*",
"https://www.facebook.com/photo.php",
"https://www.facebook.com/*/activity/*",
"https://www.facebook.com/permalink.php",
"https://www.facebook.com/media/set?set=*",
"https://www.facebook.com/questions/*",
"https://www.facebook.com/notes/*/*/*"
],
"url": "https://www.facebook.com/plugins/post/oembed.json",
"discovery": true
}
]
},
{
"provider_name": "Facebook (Video)",
"provider_url": "https://www.facebook.com/",
"endpoints": [
{
"schemes": ["https://www.facebook.com/*/videos/*", "https://www.facebook.com/video.php"],
"url": "https://www.facebook.com/plugins/video/oembed.json",
"discovery": true
}
]
},
{
"provider_name": "Flickr",
"provider_url": "https://www.flickr.com/",
"endpoints": [
{
"schemes": [
"http://*.flickr.com/photos/*",
"http://flic.kr/p/*",
"https://*.flickr.com/photos/*",
"https://flic.kr/p/*"
],
"url": "https://www.flickr.com/services/oembed/",
"discovery": true
}
]
},
{
"provider_name": "GIPHY",
"provider_url": "https://giphy.com",
"endpoints": [
{
"schemes": [
"https://giphy.com/gifs/*",
"http://gph.is/*",
"https://media.giphy.com/media/*/giphy.gif"
],
"url": "https://giphy.com/services/oembed",
"discovery": true
}
]
},
{
"provider_name": "Instagram",
"provider_url": "https://instagram.com",
"endpoints": [
{
"schemes": [
"http://instagram.com/p/*",
"http://instagr.am/p/*",
"http://www.instagram.com/p/*",
"http://www.instagr.am/p/*",
"https://instagram.com/p/*",
"https://instagr.am/p/*",
"https://www.instagram.com/p/*",
"https://www.instagr.am/p/*"
],
"url": "https://api.instagram.com/oembed",
"formats": ["json"]
}
]
},
{
"provider_name": "Meetup",
"provider_url": "http://www.meetup.com",
"endpoints": [
{
"schemes": [
"http://meetup.com/*",
"https://www.meetup.com/*",
"https://meetup.com/*",
"http://meetu.ps/*"
],
"url": "https://api.meetup.com/oembed",
"formats": ["json"]
}
]
},
{
"provider_name": "MixCloud",
"provider_url": "https://mixcloud.com/",
"endpoints": [
{
"schemes": ["http://www.mixcloud.com/*/*/", "https://www.mixcloud.com/*/*/"],
"url": "https://www.mixcloud.com/oembed/"
}
]
},
{
"provider_name": "Reddit",
"provider_url": "https://reddit.com/",
"endpoints": [
{
"schemes": [
"https://reddit.com/r/*/comments/*/*",
"https://www.reddit.com/r/*/comments/*/*"
],
"url": "https://www.reddit.com/oembed"
}
]
},
{
"provider_name": "SlideShare",
"provider_url": "http://www.slideshare.net/",
"endpoints": [
{
"schemes": [
"http://www.slideshare.net/*/*",
"http://fr.slideshare.net/*/*",
"http://de.slideshare.net/*/*",
"http://es.slideshare.net/*/*",
"http://pt.slideshare.net/*/*"
],
"url": "http://www.slideshare.net/api/oembed/2",
"discovery": true
}
]
},
{
"provider_name": "SoundCloud",
"provider_url": "http://soundcloud.com/",
"endpoints": [
{
"schemes": ["http://soundcloud.com/*", "https://soundcloud.com/*"],
"url": "https://soundcloud.com/oembed"
}
]
},
{
"provider_name": "Twitch",
"provider_url": "https://www.twitch.tv",
"endpoints": [
{
"schemes": [
"http://clips.twitch.tv/*",
"https://clips.twitch.tv/*",
"http://www.twitch.tv/*",
"https://www.twitch.tv/*",
"http://twitch.tv/*",
"https://twitch.tv/*"
],
"url": "https://api.twitch.tv/v4/oembed",
"formats": ["json"]
}
]
},
{
"provider_name": "Twitter",
"provider_url": "http://www.twitter.com/",
"endpoints": [
{
"schemes": ["https://twitter.com/*/status/*", "https://*.twitter.com/*/status/*"],
"url": "https://publish.twitter.com/oembed"
}
]
},
{
"provider_name": "Vimeo",
"provider_url": "https://vimeo.com/",
"endpoints": [
{
"schemes": [
"https://vimeo.com/*",
"https://vimeo.com/album/*/video/*",
"https://vimeo.com/channels/*/*",
"https://vimeo.com/groups/*/videos/*",
"https://vimeo.com/ondemand/*/*",
"https://player.vimeo.com/video/*"
],
"url": "https://vimeo.com/api/oembed.{format}",
"discovery": true
}
]
},
{
"provider_name": "YouTube",
"provider_url": "https://www.youtube.com/",
"endpoints": [
{
"schemes": [
"https://*.youtube.com/watch*",
"https://*.youtube.com/v/*",
"https://youtu.be/*"
],
"url": "https://www.youtube.com/oembed",
"discovery": true
}
]
}
]

View File

@ -1,24 +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/
# public
cp -r public/ build/public/
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/
# email files
mkdir -p build/src/emails/templates/
cp -r src/emails/templates/ build/src/emails/
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/
mkdir -p build/src/emails/locales/
cp -r src/emails/locales/ build/src/emails/
# gql files
mkdir -p build/src/schema/types/
cp -r src/schema/types/*.gql build/src/schema/types/
mkdir -p build/src/graphql/types/
cp -r src/graphql/types/*.gql build/src/graphql/types/
mkdir -p build/src/schema/types/enum/
cp -r src/schema/types/enum/*.gql build/src/schema/types/enum/
mkdir -p build/src/graphql/types/enum/
cp -r src/graphql/types/enum/*.gql build/src/graphql/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/graphql/types/scalar/
cp -r src/graphql/types/scalar/*.gql build/src/graphql/types/scalar/
mkdir -p build/src/schema/types/type/
cp -r src/schema/types/type/*.gql build/src/schema/types/type/
mkdir -p build/src/graphql/types/type/
cp -r src/graphql/types/type/*.gql build/src/graphql/types/type/

View File

@ -1,8 +1,5 @@
// this file is duplicated in `backend/src/config/` and `webapp/constants/` and replaced on rebranding by https://github.com/Ocelot-Social-Community/Ocelot-Social-Deploy-Rebranding/tree/master/branding/constants/
export default {
SUPPORT_EMAIL: 'support@changemedia.at',
MODERATION_EMAIL: 'support@changemedia.at',
// ATTENTION: the following links have to be defined even for internal pages with full URLs as example like 'https://staging.ocelot.social/support', because they are used in e-mails!
ORGANIZATION_LINK: 'https://changemedia.club',
SUPPORT_LINK: 'https://changemedia.club/impressum',
}

View File

@ -1,31 +1,34 @@
import dotenv from 'dotenv'
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable n/no-process-env */
import { config } from 'dotenv'
// eslint-disable-next-line import/no-namespace
import * as SMTPTransport from 'nodemailer/lib/smtp-pool'
import emails from './emails'
import metadata from './metadata'
// Load env file
if (require.resolve) {
try {
dotenv.config({ path: require.resolve('../../.env') })
} catch (error) {
// This error is thrown when the .env is not found
if (error.code !== 'MODULE_NOT_FOUND') {
throw error
}
}
}
config()
// Use Cypress env or process.env
// eslint-disable-next-line @typescript-eslint/no-explicit-any
declare let Cypress: any | undefined
const env = typeof Cypress !== 'undefined' ? Cypress.env() : process.env // eslint-disable-line no-undef
const env = (typeof Cypress !== 'undefined' ? Cypress.env() : process.env) as typeof process.env
const environment = {
NODE_ENV: env.NODE_ENV || process.env.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',
// used for staging enviroments if 'PRODUCTION=true' and 'PRODUCTION_DB_CLEAN_ALLOW=true'
PRODUCTION_DB_CLEAN_ALLOW: env.PRODUCTION_DB_CLEAN_ALLOW === 'true' || false, // default = false
DISABLED_MIDDLEWARES: (env.NODE_ENV !== 'production' && env.DISABLED_MIDDLEWARES) || false,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
DISABLED_MIDDLEWARES: ['test', 'development'].includes(env.NODE_ENV!)
? (env.DISABLED_MIDDLEWARES?.split(',') ?? [])
: [],
SEND_MAIL: env.NODE_ENV !== 'test',
}
const required = {
@ -35,30 +38,51 @@ const required = {
}
const server = {
CLIENT_URI: env.CLIENT_URI || 'http://localhost:3000',
GRAPHQL_URI: env.GRAPHQL_URI || 'http://localhost:4000',
JWT_EXPIRES: env.JWT_EXPIRES || '2y',
CLIENT_URI: env.CLIENT_URI ?? 'http://localhost:3000',
GRAPHQL_URI: env.GRAPHQL_URI ?? 'http://localhost:4000',
JWT_EXPIRES: env.JWT_EXPIRES ?? '2y',
}
const hasDKIMData = env.SMTP_DKIM_DOMAINNAME && env.SMTP_DKIM_KEYSELECTOR && env.SMTP_DKIM_PRIVATKEY
const SMTP_HOST = env.SMTP_HOST
const SMTP_PORT = (env.SMTP_PORT && parseInt(env.SMTP_PORT)) || undefined
const SMTP_IGNORE_TLS = env.SMTP_IGNORE_TLS !== 'false' // default = true
const SMTP_SECURE = env.SMTP_SECURE === 'true'
const SMTP_USERNAME = env.SMTP_USERNAME
const SMTP_PASSWORD = env.SMTP_PASSWORD
const SMTP_DKIM_DOMAINNAME = env.SMTP_DKIM_DOMAINNAME
const SMTP_DKIM_KEYSELECTOR = env.SMTP_DKIM_KEYSELECTOR
// PEM format = https://docs.progress.com/bundle/datadirect-hybrid-data-pipeline-installation-46/page/PEM-file-format.html
const SMTP_DKIM_PRIVATKEY = env.SMTP_DKIM_PRIVATKEY?.replace(/\\n/g, '\n') // replace all "\n" in .env string by real line break
const SMTP_MAX_CONNECTIONS = (env.SMTP_MAX_CONNECTIONS && parseInt(env.SMTP_MAX_CONNECTIONS)) || 5
const SMTP_MAX_MESSAGES = (env.SMTP_MAX_MESSAGES && parseInt(env.SMTP_MAX_MESSAGES)) || 100
const smtp = {
SMTP_HOST: env.SMTP_HOST,
SMTP_PORT: env.SMTP_PORT,
SMTP_IGNORE_TLS: env.SMTP_IGNORE_TLS !== 'false', // default = true
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 nodemailerTransportOptions: SMTPTransport.Options = {
host: SMTP_HOST,
port: SMTP_PORT,
ignoreTLS: SMTP_IGNORE_TLS,
secure: SMTP_SECURE, // true for 465, false for other ports
pool: true,
maxConnections: SMTP_MAX_CONNECTIONS,
maxMessages: SMTP_MAX_MESSAGES,
}
if (SMTP_USERNAME && SMTP_PASSWORD) {
nodemailerTransportOptions.auth = {
user: SMTP_USERNAME,
pass: SMTP_PASSWORD,
}
}
if (SMTP_DKIM_DOMAINNAME && SMTP_DKIM_KEYSELECTOR && SMTP_DKIM_PRIVATKEY) {
nodemailerTransportOptions.dkim = {
domainName: SMTP_DKIM_DOMAINNAME,
keySelector: SMTP_DKIM_KEYSELECTOR,
privateKey: SMTP_DKIM_PRIVATKEY,
}
}
const neo4j = {
NEO4J_URI: env.NEO4J_URI || 'bolt://localhost:7687',
NEO4J_USERNAME: env.NEO4J_USERNAME || 'neo4j',
NEO4J_PASSWORD: env.NEO4J_PASSWORD || 'neo4j',
NEO4J_URI: env.NEO4J_URI ?? 'bolt://localhost:7687',
NEO4J_USERNAME: env.NEO4J_USERNAME ?? 'neo4j',
NEO4J_PASSWORD: env.NEO4J_PASSWORD ?? 'neo4j',
}
const sentry = {
@ -68,7 +92,7 @@ const sentry = {
const redis = {
REDIS_DOMAIN: env.REDIS_DOMAIN,
REDIS_PORT: env.REDIS_PORT,
REDIS_PORT: (env.REDIS_PORT && parseInt(env.REDIS_PORT)) || undefined,
REDIS_PASSWORD: env.REDIS_PASSWORD,
}
@ -88,14 +112,23 @@ const s3 = {
const options = {
EMAIL_DEFAULT_SENDER: env.EMAIL_DEFAULT_SENDER,
SUPPORT_EMAIL: env.SUPPORT_EMAIL,
SUPPORT_URL: emails.SUPPORT_LINK,
APPLICATION_NAME: metadata.APPLICATION_NAME,
ORGANIZATION_URL: emails.ORGANIZATION_LINK,
PUBLIC_REGISTRATION: env.PUBLIC_REGISTRATION === 'true' || false,
INVITE_REGISTRATION: env.INVITE_REGISTRATION !== 'false', // default = true
INVITE_CODES_PERSONAL_PER_USER:
(env.INVITE_CODES_PERSONAL_PER_USER && parseInt(env.INVITE_CODES_PERSONAL_PER_USER)) || 7,
INVITE_CODES_GROUP_PER_USER:
(env.INVITE_CODES_GROUP_PER_USER && parseInt(env.INVITE_CODES_GROUP_PER_USER)) || 7,
CATEGORIES_ACTIVE: process.env.CATEGORIES_ACTIVE === 'true' || false,
}
const language = {
LANGUAGE_DEFAULT: process.env.LANGUAGE_DEFAULT ?? 'en',
}
// Check if all required configs are present
Object.entries(required).map((entry) => {
if (!entry[1]) {
@ -108,10 +141,12 @@ export default {
...environment,
...server,
...required,
...smtp,
...neo4j,
...sentry,
...redis,
...s3,
...options,
...language,
}
export { nodemailerTransportOptions }

View File

@ -1,10 +1,3 @@
// this file is duplicated in `backend/src/config/logos` and `webapp/constants/logos.js` and replaced on rebranding
// this file is duplicated in `backend/src/config/logos.ts` 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',
LOGO_SIGNUP_PATH: '/img/custom/logo-squared.svg',
LOGO_WELCOME_PATH: '/img/custom/logo-squared.svg',
LOGO_LOGOUT_PATH: '/img/custom/logo-squared.svg',
LOGO_PASSWORD_RESET_PATH: '/img/custom/logo-squared.svg',
LOGO_MAINTENACE_RESET_PATH: '/img/custom/logo-squared.svg',
}
export default {}

View File

@ -0,0 +1,32 @@
// this file is duplicated in `backend/src/config/logos.ts` and `webapp/constants/logos.js` and replaced on rebranding
// this are the paths in the webapp
import { merge } from 'lodash'
import logos from '@config/logos'
const defaultLogos = {
LOGO_HEADER_PATH: '/img/custom/logo-horizontal.svg',
LOGO_HEADER_MOBILE_PATH: '/img/custom/logo-horizontal.svg',
LOGO_HEADER_WIDTH: '130px',
LOGO_HEADER_MOBILE_WIDTH: '100px',
LOGO_HEADER_CLICK: {
// externalLink: {
// url: 'https://ocelot.social',
// target: '_blank',
// },
externalLink: null,
internalPath: {
to: {
name: 'index',
},
scrollTo: '.main-navigation',
},
},
LOGO_SIGNUP_PATH: '/img/custom/logo-squared.svg',
LOGO_WELCOME_PATH: '/img/custom/logo-squared.svg',
LOGO_LOGOUT_PATH: '/img/custom/logo-squared.svg',
LOGO_PASSWORD_RESET_PATH: '/img/custom/logo-squared.svg',
LOGO_MAINTENACE_RESET_PATH: '/img/custom/logo-squared.svg',
}
export default merge(defaultLogos, logos)

View File

@ -0,0 +1,2 @@
// this file is duplicated in `backend/src/constants/badges` and `webapp/constants/badges.js`
export const TROPHY_BADGES_SELECTED_MAX = 9

View File

@ -5,98 +5,116 @@ export const CATEGORIES_MAX = 3
export const categories = [
{
icon: 'networking',
id: 'cat0',
slug: 'networking',
name: 'networking',
description: 'Kooperation, Aktionsbündnisse, Solidarität, Hilfe',
},
{
icon: 'home',
id: 'cat1',
slug: 'home',
name: 'home',
description: 'Bauen, Lebensgemeinschaften, Tiny Houses, Gemüsegarten',
},
{
icon: 'energy',
id: 'cat2',
slug: 'energy',
name: 'energy',
description: 'Öl, Gas, Kohle, Wind, Wasserkraft, Biogas, Atomenergie, ...',
},
{
icon: 'psyche',
id: 'cat3',
slug: 'psyche',
name: 'psyche',
description: 'Seele, Gefühle, Glück',
},
{
icon: 'movement',
id: 'cat4',
slug: 'body-and-excercise',
name: 'body-and-excercise',
description: 'Sport, Yoga, Massage, Tanzen, Entspannung',
},
{
icon: 'balance-scale',
id: 'cat5',
slug: 'law',
name: 'law',
description: 'Menschenrechte, Gesetze, Verordnungen',
},
{
icon: 'finance',
id: 'cat6',
slug: 'finance',
name: 'finance',
description: 'Geld, Finanzsystem, Alternativwährungen, ...',
},
{
icon: 'child',
id: 'cat7',
slug: 'children',
name: 'children',
description: 'Familie, Pädagogik, Schule, Prägung',
},
{
icon: 'mobility',
id: 'cat8',
slug: 'mobility',
name: 'mobility',
description: 'Reise, Verkehr, Elektromobilität',
},
{
icon: 'shopping-cart',
id: 'cat9',
slug: 'economy',
name: 'economy',
description: 'Handel, Konsum, Marketing, Lebensmittel, Lieferketten, ...',
},
{
icon: 'peace',
id: 'cat10',
slug: 'peace',
name: 'peace',
description: 'Krieg, Militär, soziale Verteidigung, Waffen, Cyberattacken',
},
{
icon: 'politics',
id: 'cat11',
slug: 'politics',
name: 'politics',
description: 'Demokratie, Mitbestimmung, Wahlen, Korruption, Parteien',
},
{
icon: 'nature',
id: 'cat12',
slug: 'nature',
name: 'nature',
description: 'Tiere, Pflanzen, Landwirtschaft, Ökologie, Artenvielfalt',
},
{
icon: 'science',
id: 'cat13',
slug: 'science',
name: 'science',
description: 'Bildung, Hochschule, Publikationen, ...',
},
{
icon: 'health',
id: 'cat14',
slug: 'health',
name: 'health',
description: 'Medizin, Ernährung, WHO, Impfungen, Schadstoffe, ...',
},
{
icon: 'media',
id: 'cat15',
slug: 'it-and-media',
name: 'it-and-media',
description:
'Nachrichten, Manipulation, Datenschutz, Überwachung, Datenkraken, AI, Software, Apps',
},
{
icon: 'spirituality',
id: 'cat16',
slug: 'spirituality',
name: 'spirituality',
description: 'Religion, Werte, Ethik',
},
{
icon: 'culture',
id: 'cat17',
slug: 'culture',
name: 'culture',
description: 'Kunst, Theater, Musik, Fotografie, Film',
},
{
icon: 'miscellaneous',
id: 'cat18',
slug: 'miscellaneous',
name: 'miscellaneous',
description: '',
},
]

View File

@ -1,5 +1,2 @@
// this file is duplicated in `backend/src/config/metadata` and `webapp/constants/metadata.js`
export default {
NONCE_LENGTH: 5,
INVITE_CODE_LENGTH: 6,
}
// this file is duplicated in `backend/src/config/registration.ts` and `webapp/constants/registration.js`
export default {}

View File

@ -0,0 +1,12 @@
// this file is duplicated in `backend/src/config/registrationBranded.ts` and `webapp/constants/registrationBranded.js`
import { merge } from 'lodash'
import registration from '@constants/registration'
const defaultRegistration = {
NONCE_LENGTH: 5,
INVITE_CODE_LENGTH: 6,
LAYOUT: 'no-header',
}
export default merge(defaultRegistration, registration)

View File

@ -0,0 +1,3 @@
export const NOTIFICATION_ADDED = 'NOTIFICATION_ADDED'
export const CHAT_MESSAGE_ADDED = 'CHAT_MESSAGE_ADDED'
export const ROOM_COUNT_UPDATED = 'ROOM_COUNT_UPDATED'

View File

@ -0,0 +1,49 @@
import { getDriver, getNeode } from '@db/neo4j'
import type { Driver } from 'neo4j-driver'
export const query =
(driver: Driver) =>
async ({ query, variables = {} }: { query: string; variables?: object }) => {
const session = driver.session()
const result = session.readTransaction(async (transaction) => {
const response = await transaction.run(query, variables)
return response
})
try {
return await result
} finally {
await session.close()
}
}
export const write =
(driver: Driver) =>
async ({ query, variables = {} }: { query: string; variables?: object }) => {
const session = driver.session()
const result = session.writeTransaction(async (transaction) => {
const response = await transaction.run(query, variables)
return response
})
try {
return await result
} finally {
await session.close()
}
}
export default () => {
const driver = getDriver()
const neode = getNeode()
return {
driver,
neode,
query: query(driver),
write: write(driver),
}
}

View File

@ -0,0 +1,25 @@
import { RedisPubSub } from 'graphql-redis-subscriptions'
import { PubSub } from 'graphql-subscriptions'
import Redis from 'ioredis'
import CONFIG from '@config/index'
export default () => {
const { REDIS_DOMAIN, REDIS_PORT, REDIS_PASSWORD } = CONFIG
if (!(REDIS_DOMAIN && REDIS_PORT && REDIS_PASSWORD)) {
return new PubSub()
}
const options = {
host: REDIS_DOMAIN,
port: REDIS_PORT,
password: REDIS_PASSWORD,
retryStrategy: (times) => {
return Math.min(times * 50, 2000)
},
}
return new RedisPubSub({
publisher: new Redis(options),
subscriber: new Redis(options),
})
}

54
backend/src/db/admin.ts Normal file
View File

@ -0,0 +1,54 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/require-await */
import { hashSync } from 'bcryptjs'
import { v4 as uuid } from 'uuid'
import { getDriver } from './neo4j'
const defaultAdmin = {
email: 'admin@example.org',
// eslint-disable-next-line n/no-sync
password: hashSync('1234', 10),
name: 'admin',
id: uuid(),
slug: 'admin',
}
const createDefaultAdminUser = async () => {
const driver = getDriver()
const session = driver.session()
const createAdminTxResultPromise = session.writeTransaction(async (txc) => {
txc.run(
`MERGE (e:EmailAddress {
email: "${defaultAdmin.email}",
createdAt: toString(datetime())
})-[:BELONGS_TO]->(u:User {
name: "${defaultAdmin.name}",
encryptedPassword: "${defaultAdmin.password}",
role: "admin",
id: "${defaultAdmin.id}",
slug: "${defaultAdmin.slug}",
createdAt: toString(datetime()),
allowEmbedIframes: false,
showShoutsPublicly: false,
deleted: false,
disabled: false
})-[:PRIMARY_EMAIL]->(e)`,
)
})
try {
await createAdminTxResultPromise
console.log('Successfully created default admin user!') // eslint-disable-line no-console
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
} finally {
session.close()
driver.close()
}
}
;(async function () {
await createDefaultAdminUser()
})()

14
backend/src/db/badges.ts Normal file
View File

@ -0,0 +1,14 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { getNeode } from './neo4j'
import { trophies, verification } from './seed/badges'
// eslint-disable-next-line import/newline-after-import
;(async function () {
const neode = getNeode()
try {
await trophies()
await verification()
} finally {
neode.close()
}
})()

View File

@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { categories } from '@constants/categories'
import databaseContext from '@context/database'
const { query, write, driver } = databaseContext()
const createCategories = async () => {
const result = await query({
query: 'MATCH (category:Category) RETURN category { .* }',
})
const categoryIds = categories.map((c) => c.id)
const categorySlugs = categories.map((c) => c.slug)
await write({
query: `MATCH (category:Category)
WHERE NOT category.id IN $categoryIds
DETACH DELETE category`,
variables: {
categoryIds,
categorySlugs,
},
})
const existingCategories = result.records.map((r) => r.get('category'))
const newCategories = categories.filter((c) => !existingCategories.some((cat) => c.id === cat.id))
await write({
query: `UNWIND $newCategories AS map
CREATE (category:Category)
SET category = map
SET category.createdAt = toString(datetime())`,
variables: {
newCategories,
},
})
// eslint-disable-next-line no-console
console.log('Successfully created categories!')
await driver.close()
}
;(async function () {
await createCategories()
})()

View File

@ -1,2 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable import/no-commonjs */
// eslint-disable-next-line n/no-unpublished-require, @typescript-eslint/no-var-requires
const tsNode = require('ts-node')
// eslint-disable-next-line import/no-unassigned-import, n/no-unpublished-require
require('tsconfig-paths/register')
module.exports = tsNode.register

View File

@ -0,0 +1,27 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-floating-promises */
import { readdir } from 'node:fs/promises'
import path from 'node:path'
import { getNeode } from './neo4j'
const dataFolder = path.join(__dirname, 'data/')
const neode = getNeode()
;(async function () {
const files = await readdir(dataFolder)
for await (const file of files) {
if (file.slice(0, -3).endsWith('-branding')) {
const importedModule = await import(path.join(dataFolder, file))
if (!importedModule.default) {
throw new Error('Your data file must export a default function')
}
await importedModule.default()
}
}
// close database connection
neode.close()
})()

View File

@ -1,35 +1,43 @@
import { v4 as uuid } from 'uuid'
import slugify from 'slug'
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { faker } from '@faker-js/faker'
import { hashSync } from 'bcryptjs'
import { Factory } from 'rosie'
import { faker } from '@faker-js/faker'
import slugify from 'slug'
import { v4 as uuid } from 'uuid'
import { generateInviteCode } from '@graphql/resolvers/inviteCodes'
import { getDriver, getNeode } from './neo4j'
import CONFIG from '../config/index'
import generateInviteCode from '../schema/resolvers/helpers/generateInviteCode'
const neode = getNeode()
const uniqueImageUrl = (imageUrl) => {
const newUrl = new URL(imageUrl, CONFIG.CLIENT_URI)
const newUrl = new URL(imageUrl)
newUrl.search = `random=${uuid()}`
return newUrl.toString()
}
export const cleanDatabase = async (options: any = {}) => {
const { driver = getDriver() } = options
export const cleanDatabase = async ({ withMigrations } = { withMigrations: false }) => {
const driver = getDriver()
const session = driver.session()
const clean = `
MATCH (everything)
${withMigrations ? '' : "WHERE NOT 'Migration' IN labels(everything)"}
DETACH DELETE everything
`
try {
await session.writeTransaction((transaction) => {
return transaction.run(
`
MATCH (everything)
WHERE NOT 'Migration' IN labels(everything)
DETACH DELETE everything
`,
)
return transaction.run(clean)
})
} finally {
session.close()
await session.close()
}
}
@ -37,25 +45,34 @@ Factory.define('category')
.attr('id', uuid)
.attr('icon', 'globe')
.attr('name', 'Global Peace & Nonviolence')
.after((buildObject, options) => {
.after((buildObject, _options) => {
return neode.create('Category', buildObject)
})
Factory.define('badge')
.attr('type', 'crowdfunding')
.attr('status', 'permanent')
.after((buildObject, options) => {
.after((buildObject, _options) => {
return neode.create('Badge', buildObject)
})
Factory.define('image')
.attr('url', faker.image.url)
.attr('aspectRatio', 1.3333333333333333)
.attr('width', 400)
.attr('height', 300)
.attr('blur', 0)
.attr('alt', faker.lorem.sentence)
.attr('type', 'image/jpeg')
.after((buildObject, options) => {
const { url: imageUrl } = buildObject
if (imageUrl) buildObject.url = uniqueImageUrl(imageUrl)
.attr('url', null)
.after((buildObject, _options) => {
if (!buildObject.url) {
buildObject.url = faker.image.urlPicsumPhotos({
width: buildObject.width,
height: buildObject.height,
blur: buildObject.blur,
})
}
buildObject.url = uniqueImageUrl(buildObject.url)
buildObject.aspectRatio = buildObject.width / buildObject.height
return neode.create('Image', buildObject)
})
@ -70,34 +87,34 @@ Factory.define('basicUser')
termsAndConditionsAgreedAt: '2019-08-01T10:47:19.212Z',
allowEmbedIframes: false,
showShoutsPublicly: false,
sendNotificationEmails: true,
locale: 'en',
})
.attr('slug', ['slug', 'name'], (slug, name) => {
return slug || slugify(name, { lower: true })
})
.attr('encryptedPassword', ['password'], (password) => {
// eslint-disable-next-line n/no-sync
return hashSync(password, 10)
})
Factory.define('userWithoutEmailAddress')
.extend('basicUser')
.option('about', faker.lorem.paragraph)
.after(async (buildObject, options) => {
.after(async (buildObject, _options) => {
return neode.create('User', buildObject)
})
Factory.define('userWithAboutNull')
.extend('basicUser')
.option('about', null)
.after(async (buildObject, options) => {
.after(async (buildObject, _options) => {
return neode.create('User', buildObject)
})
Factory.define('userWithAboutEmpty')
.extend('basicUser')
.option('about', '')
.after(async (buildObject, options) => {
.after(async (buildObject, _options) => {
return neode.create('User', buildObject)
})
@ -173,6 +190,7 @@ Factory.define('post')
])
await Promise.all([
post.relateTo(author, 'author'),
post.relateTo(author, 'observes'),
// Promise.all(categories.map((c) => c.relateTo(post, 'post'))),
Promise.all(tags.map((t) => t.relateTo(post, 'post'))),
])
@ -208,7 +226,11 @@ Factory.define('comment')
options.author,
options.post,
])
await Promise.all([comment.relateTo(author, 'author'), comment.relateTo(post, 'post')])
await Promise.all([
comment.relateTo(author, 'author'),
comment.relateTo(post, 'post'),
post.relateTo(author, 'observes'),
])
return comment
})
@ -217,7 +239,7 @@ Factory.define('donations')
.attr('showDonations', true)
.attr('goal', 15000)
.attr('progress', 7000)
.after((buildObject, options) => {
.after((buildObject, _options) => {
return neode.create('Donations', buildObject)
})
@ -228,13 +250,13 @@ const emailDefaults = {
Factory.define('emailAddress')
.attrs(emailDefaults)
.after((buildObject, options) => {
.after((buildObject, _options) => {
return neode.create('EmailAddress', buildObject)
})
Factory.define('unverifiedEmailAddress')
.attr(emailDefaults)
.after((buildObject, options) => {
.after((buildObject, _options) => {
return neode.create('UnverifiedEmailAddress', buildObject)
})
@ -246,17 +268,27 @@ const inviteCodeDefaults = {
Factory.define('inviteCode')
.attrs(inviteCodeDefaults)
.option('groupId', null)
.option('group', ['groupId'], (groupId) => {
if (groupId) {
return neode.find('Group', groupId)
}
})
.option('generatedById', null)
.option('generatedBy', ['generatedById'], (generatedById) => {
if (generatedById) return neode.find('User', generatedById)
return Factory.build('user')
})
.after(async (buildObject, options) => {
const [inviteCode, generatedBy] = await Promise.all([
const [inviteCode, generatedBy, group] = await Promise.all([
neode.create('InviteCode', buildObject),
options.generatedBy,
options.group,
])
await Promise.all([inviteCode.relateTo(generatedBy, 'generated')])
await inviteCode.relateTo(generatedBy, 'generated')
if (group) {
await inviteCode.relateTo(group, 'invitesTo')
}
return inviteCode
})
@ -274,11 +306,11 @@ Factory.define('location')
id: 'country.10743216036480410',
type: 'country',
})
.after((buildObject, options) => {
.after((buildObject, _options) => {
return neode.create('Location', buildObject)
})
Factory.define('report').after((buildObject, options) => {
Factory.define('report').after((buildObject, _options) => {
return neode.create('Report', buildObject)
})
@ -286,7 +318,7 @@ Factory.define('tag')
.attrs({
name: '#human-connection',
})
.after((buildObject, options) => {
.after((buildObject, _options) => {
return neode.create('Tag', buildObject)
})
@ -294,7 +326,7 @@ Factory.define('socialMedia')
.attrs({
url: 'https://mastodon.social/@Gargron',
})
.after((buildObject, options) => {
.after((buildObject, _options) => {
return neode.create('SocialMedia', buildObject)
})

View File

@ -1,104 +1,52 @@
import { getDriver, getNeode } from '../../db/neo4j'
import { hashSync } from 'bcryptjs'
import { v4 as uuid } from 'uuid'
import { categories } from '../../constants/categories'
import CONFIG from '../../config'
const defaultAdmin = {
email: 'admin@example.org',
password: hashSync('1234', 10),
name: 'admin',
id: uuid(),
slug: 'admin',
}
const createCategories = async (session) => {
const createCategoriesTxResultPromise = session.writeTransaction(async (txc) => {
categories.forEach(({ icon, name }, index) => {
const id = `cat${index + 1}`
txc.run(
`MERGE (c:Category {
icon: "${icon}",
slug: "${name}",
name: "${name}",
id: "${id}",
createdAt: toString(datetime())
})`,
)
})
})
try {
await createCategoriesTxResultPromise
console.log('Successfully created categories!') // eslint-disable-line no-console
} catch (error) {
console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console
}
}
const createDefaultAdminUser = async (session) => {
const readTxResultPromise = session.readTransaction(async (txc) => {
const result = await txc.run('MATCH (user:User) RETURN count(user) AS userCount')
return result.records.map((r) => r.get('userCount'))
})
let createAdmin = false
try {
const userCount = parseInt(String(await readTxResultPromise))
if (userCount === 0) createAdmin = true
} catch (error) {
console.log(error) // eslint-disable-line no-console
}
if (createAdmin) {
const createAdminTxResultPromise = session.writeTransaction(async (txc) => {
txc.run(
`MERGE (e:EmailAddress {
email: "${defaultAdmin.email}",
createdAt: toString(datetime())
})-[:BELONGS_TO]->(u:User {
name: "${defaultAdmin.name}",
encryptedPassword: "${defaultAdmin.password}",
role: "admin",
id: "${defaultAdmin.id}",
slug: "${defaultAdmin.slug}",
createdAt: toString(datetime()),
allowEmbedIframes: false,
showShoutsPublicly: false,
sendNotificationEmails: true,
deleted: false,
disabled: false
})-[:PRIMARY_EMAIL]->(e)`,
)
})
try {
await createAdminTxResultPromise
console.log('Successfully created default admin user!') // eslint-disable-line no-console
} catch (error) {
console.log(error) // eslint-disable-line no-console
}
}
}
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { getDriver, getNeode } from '@db/neo4j'
class Store {
async init(next) {
async init(errFn) {
const neode = getNeode()
const { driver } = neode
const session = driver.session()
await createDefaultAdminUser(session)
if (CONFIG.CATEGORIES_ACTIVE) await createCategories(session)
const writeTxResultPromise = session.writeTransaction(async (txc) => {
await txc.run('CALL apoc.schema.assert({},{},true)') // drop all indices and constraints
const session = neode.session()
const txFreshIndicesConstrains = session.writeTransaction(async (txc) => {
// drop all indices and constraints
await txc.run('CALL apoc.schema.assert({},{},true)')
/*
#############################################
# ADD YOUR CUSTOM INDICES & CONSTRAINS HERE #
#############################################
*/
// Search indexes (also part of migration 20230320130345-fulltext-search-indexes)
await txc.run(
`CALL db.index.fulltext.createNodeIndex("user_fulltext_search",["User"],["name", "slug"])`,
)
await txc.run(
`CALL db.index.fulltext.createNodeIndex("post_fulltext_search",["Post"],["title", "content"])`,
)
await txc.run(`CALL db.index.fulltext.createNodeIndex("tag_fulltext_search",["Tag"],["id"])`) // also part of migration 20200207080200-fulltext_index_for_tags
// Search indexes (also part of migration 20220803060819-create_fulltext_indices_and_unique_keys_for_groups)
await txc.run(`
CALL db.index.fulltext.createNodeIndex("group_fulltext_search",["Group"],["name", "slug", "about", "description"])
`)
})
try {
await writeTxResultPromise
// Due to limitations of neode in combination with the limitations of the community version of neo4j
// we need to have all constraints and indexes defined here. They can not be properly migrated
await txFreshIndicesConstrains
// You have to wait for the schema to install, else the constraints will not be present.
// This is a type error of the library
// eslint-disable-next-line @typescript-eslint/await-thenable
await getNeode().schema.install()
// eslint-disable-next-line no-console
console.log('Successfully created database indices and constraints!')
next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
next(error, null)
errFn(error)
} finally {
session.close()
driver.close()
await session.close()
neode.close()
}
}
@ -122,11 +70,12 @@ class Store {
}
const [{ title: lastRun }] = migrations
next(null, { lastRun, migrations })
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
next(error)
} finally {
session.close()
await session.close()
}
}
@ -157,13 +106,14 @@ class Store {
try {
await writeTxResultPromise
next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
console.log(error) // eslint-disable-line no-console
next(error)
} finally {
session.close()
await session.close()
}
}
}
module.exports = Store
export default Store

View File

@ -1,8 +1,10 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { getDriver } from '@db/neo4j'
export const description = ''
export async function up(next) {
export async function up(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
@ -11,7 +13,6 @@ export async function up(next) {
// Implement your migration here.
await transaction.run(``)
await transaction.commit()
next()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
@ -20,11 +21,11 @@ export async function up(next) {
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
await session.close()
}
}
export async function down(next) {
export async function down(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
@ -33,7 +34,6 @@ export async function down(next) {
// Implement your migration here.
await transaction.run(``)
await transaction.commit()
next()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
@ -42,6 +42,6 @@ export async function down(next) {
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
await session.close()
}
}

View File

@ -1,7 +1,16 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable promise/prefer-await-to-callbacks */
import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError, filter } from 'rxjs/operators'
import { getDriver } from '../neo4j'
import normalizeEmail from '../../schema/resolvers//helpers/normalizeEmail'
import { getDriver } from '@db/neo4j'
import normalizeEmail from '@graphql/resolvers/helpers/normalizeEmail'
export const description = `
This migration merges duplicate :User and :EmailAddress nodes. It became
@ -14,16 +23,19 @@ export const description = `
`
export function up(next) {
const driver = getDriver()
const rxSession = driver.rxSession()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const rxSession = driver.rxSession() as any
rxSession
.beginTransaction()
.pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
flatMap((txc: any) =>
concat(
txc
.run('MATCH (email:EmailAddress) RETURN email {.email}')
.records()
.pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map((record: any) => {
const { email } = record.get('email')
const normalizedEmail = normalizeEmail(email)
@ -45,6 +57,7 @@ export function up(next) {
)
.records()
.pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map((r: any) => ({
oldEmail: email,
email: r.get('email'),
@ -58,7 +71,7 @@ export function up(next) {
),
)
.subscribe({
next: ({ user, email, oldUser, oldEmail }) =>
next: ({ user, email, _oldUser, oldEmail }) =>
// eslint-disable-next-line no-console
console.log(`
Merged:

View File

@ -1,6 +1,15 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable promise/prefer-await-to-callbacks */
import { throwError, concat } from 'rxjs'
import { flatMap, mergeMap, map, catchError } from 'rxjs/operators'
import { getDriver } from '../neo4j'
import { getDriver } from '@db/neo4j'
export const description = `
This migration merges duplicate :Location nodes. It became
@ -8,10 +17,12 @@ export const description = `
`
export function up(next) {
const driver = getDriver()
const rxSession = driver.rxSession()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const rxSession = driver.rxSession() as any
rxSession
.beginTransaction()
.pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
flatMap((transaction: any) =>
concat(
transaction
@ -23,6 +34,7 @@ export function up(next) {
)
.records()
.pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map((record: any) => {
const { id: locationId } = record.get('location')
return { locationId }
@ -40,6 +52,7 @@ export function up(next) {
)
.records()
.pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map((record: any) => ({
location: record.get('location'),
updatedLocation: record.get('updatedLocation'),

View File

@ -1,4 +1,6 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { getDriver } from '@db/neo4j'
export const description = `
This migration creates a MUTED relationship between two edges(:User) that have a pre-existing BLOCKED relationship.
@ -8,7 +10,7 @@ export const description = `
A blocked user will still be able to see your contributions, but will not be able to interact with them and vice versa.
`
export async function up(next) {
export async function up(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
@ -21,6 +23,7 @@ export async function up(next) {
`,
)
await transaction.commit()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
@ -28,19 +31,20 @@ export async function up(next) {
// eslint-disable-next-line no-console
console.log('rolled back')
} finally {
session.close()
await session.close()
}
}
export function down(next) {
export async function down(next) {
const driver = getDriver()
const session = driver.session()
try {
// Rollback your migration here.
next()
// next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (err) {
next(err)
} finally {
session.close()
await session.close()
}
}

View File

@ -1,4 +1,9 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { getDriver } from '@db/neo4j'
export const description = `
This migration swaps the value stored in Location.lat with the value

View File

@ -1,4 +1,8 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { getDriver } from '@db/neo4j'
export const description =
'This migration adds a fulltext index for the tags in order to search for Hasthags.'
@ -9,10 +13,13 @@ export async function up(next) {
const transaction = session.beginTransaction()
try {
// We do do this in /src/db/migrate/store.ts
/*
await transaction.run(`
CALL db.index.fulltext.createNodeIndex("tag_fulltext_search",["Tag"],["id"])
`)
await transaction.commit()
*/
next()
} catch (error) {
const { message } = error
@ -28,7 +35,7 @@ export async function up(next) {
throw new Error(error)
}
} finally {
session.close()
await session.close()
}
}
@ -39,10 +46,13 @@ export async function down(next) {
try {
// Implement your migration here.
// We do do this in /src/db/migrate/store.ts
/*
await transaction.run(`
CALL db.index.fulltext.drop("tag_fulltext_search")
`)
await transaction.commit()
*/
next()
} catch (error) {
// eslint-disable-next-line no-console
@ -52,6 +62,6 @@ export async function down(next) {
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
await session.close()
}
}

View File

@ -1,4 +1,8 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { getDriver } from '@db/neo4j'
export const description = `
We introduced a new node label 'Image' and we need a primary key for it. Best
@ -32,7 +36,7 @@ export async function up(next) {
throw new Error(error)
}
} finally {
session.close()
await session.close()
}
}
@ -48,6 +52,7 @@ export async function down(next) {
`)
await transaction.commit()
next()
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
@ -55,6 +60,6 @@ export async function down(next) {
// eslint-disable-next-line no-console
console.log('rolled back')
} finally {
session.close()
await session.close()
}
}

View File

@ -1,107 +0,0 @@
import { getDriver } from '../../db/neo4j'
import { existsSync, createReadStream } from 'fs'
import path from 'path'
import { S3 } from 'aws-sdk'
import mime from 'mime-types'
import s3Configs from '../../config'
import https from 'https'
export const description = `
Upload all image files to a S3 compatible object storage in order to reduce
load on our backend.
`
export async function up(next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
const agent = new https.Agent({
maxSockets: 5,
})
const {
AWS_ENDPOINT: endpoint,
AWS_REGION: region,
AWS_BUCKET: Bucket,
S3_CONFIGURED,
} = s3Configs
if (!S3_CONFIGURED) {
// eslint-disable-next-line no-console
console.log('No S3 given, cannot upload image files')
return
}
const s3 = new S3({ region, endpoint, httpOptions: { agent } })
try {
// Implement your migration here.
const { records } = await transaction.run('MATCH (image:Image) RETURN image.url as url')
let urls = records.map((r) => r.get('url'))
urls = urls.filter((url) => url.startsWith('/uploads'))
const locations = await Promise.all(
urls
.map((url) => {
return async () => {
const { pathname } = new URL(url, 'http://example.org')
const fileLocation = path.join(__dirname, `../../../public/${pathname}`)
const s3Location = `original${pathname}`
if (existsSync(fileLocation)) {
const mimeType = mime.lookup(fileLocation)
const params = {
Bucket,
Key: s3Location,
ACL: 'public-read',
ContentType: mimeType || 'image/jpeg',
Body: createReadStream(fileLocation),
}
const data = await s3.upload(params).promise()
const { Location: spacesUrl } = data
const updatedRecord = await transaction.run(
'MATCH (image:Image {url: $url}) SET image.url = $spacesUrl RETURN image.url as url',
{ url, spacesUrl },
)
const [updatedUrl] = updatedRecord.records.map((record) => record.get('url'))
return updatedUrl
}
}
})
.map((p) => p()),
)
// eslint-disable-next-line no-console
console.log('this is locations', locations)
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 {
// Implement your migration here.
await transaction.run(``)
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')
} finally {
session.close()
}
}

View File

@ -1,5 +1,8 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { getDriver } from '../../db/neo4j'
import { getDriver } from '@db/neo4j'
export const description = `
Refactor all our image properties on posts and users to a dedicated type
@ -58,7 +61,7 @@ export async function up() {
console.log('Created image nodes from all user avatars and post images.')
printSummaries(stats)
} finally {
session.close()
await session.close()
}
}
@ -96,6 +99,6 @@ export async function down() {
console.log('UNDO: Split images from users and posts.')
printSummaries(stats)
} finally {
session.close()
await session.close()
}
}

View File

@ -1,4 +1,9 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { getDriver } from '@db/neo4j'
export const description =
'We should not maintain obsolete attributes for users who have been deleted.'
@ -20,7 +25,7 @@ export async function up(next) {
`)
try {
// Implement your migration here.
const users = await updateDeletedUserAttributes.records.map((record) => record.get('user'))
const users = updateDeletedUserAttributes.records.map((record) => record.get('user'))
// eslint-disable-next-line no-console
console.log(users)
await transaction.commit()
@ -33,7 +38,7 @@ export async function up(next) {
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
await session.close()
}
}

View File

@ -1,4 +1,9 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { getDriver } from '@db/neo4j'
export const description =
'We should not maintain obsolete attributes for posts which have been deleted.'
@ -22,7 +27,7 @@ export async function up(next) {
`)
try {
// Implement your migration here.
const posts = await updateDeletedPostsAttributes.records.map((record) => record.get('post'))
const posts = updateDeletedPostsAttributes.records.map((record) => record.get('post'))
// eslint-disable-next-line no-console
console.log(posts)
await transaction.commit()
@ -35,7 +40,7 @@ export async function up(next) {
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
await session.close()
}
}

View File

@ -1,5 +1,13 @@
import { getDriver } from '../../db/neo4j'
import { existsSync } from 'fs'
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable security/detect-non-literal-fs-filename */
import { existsSync } from 'node:fs'
import { getDriver } from '@db/neo4j'
export const description = `
In this review:
@ -24,6 +32,7 @@ export async function up(next) {
const urls = records.map((record) => record.get('url'))
const danglingUrls = urls.filter((url) => {
const fileLocation = `public${url}`
// eslint-disable-next-line n/no-sync
return !existsSync(fileLocation)
})
await transaction.run(
@ -52,7 +61,7 @@ export async function up(next) {
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
await session.close()
}
}

View File

@ -1,9 +1,6 @@
/* eslint-disable @typescript-eslint/no-empty-function */
'use strict'
module.exports.up = function (next) {
next()
}
export async function up(_next) {}
module.exports.down = function (next) {
next()
}
export async function down(_next) {}

View File

@ -1,10 +1,12 @@
import { getDriver } from '../../db/neo4j'
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { getDriver } from '@db/neo4j'
export const description = `
This migration adds the clickedCount property to all posts, setting it to 0.
`
module.exports.up = async function (next) {
export async function up(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
@ -15,7 +17,6 @@ module.exports.up = async function (next) {
SET p.clickedCount = 0
`)
await transaction.commit()
next()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
@ -24,11 +25,11 @@ module.exports.up = async function (next) {
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
await session.close()
}
}
module.exports.down = async function (next) {
export async function down(_next) {
const driver = getDriver()
const session = driver.session()
const transaction = session.beginTransaction()
@ -39,7 +40,6 @@ module.exports.down = async function (next) {
REMOVE p.clickedCount
`)
await transaction.commit()
next()
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
@ -48,6 +48,6 @@ module.exports.down = async function (next) {
console.log('rolled back')
throw new Error(error)
} finally {
session.close()
await session.close()
}
}

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