├── .babelrc
├── .codeclimate.yml
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .gitmodules
├── .npmignore
├── .travis.yml
├── AUTHORS
├── LICENSE
├── README.md
├── base.tsconfig.json
├── browser.tsconfig.json
├── database.json
├── docker
└── Dockerfile
├── jest.config.js
├── migrations
├── 20180401000000-persons.js
├── 20180402000000-cookies.js
├── 20180403000000-notes.js
├── 20180404000000-follows.js
├── 20180405000000-local_accounts.js
├── 20180406000000-remote_accounts.js
├── 20180407000000-insert_local_account.js
├── 20180408000000-insert_remote_account.js
├── 20180409000000-uris.js
├── 20180503000000-attributed_to_id.js
├── 20180504000000-actor_id.js
├── 20180505000000-object_id.js
├── 20180506000000-remote_account_uris.js
├── 20180507000000-local_accounts_id.js
├── 20180508000000-notes_id.js
├── 20180509000000-mentions.js
├── 20180510000000-insert_note.js
├── 20180601000000-statuses.js
├── 20180602000000-tombstones.js
├── 20180603000000-delete_status.js
├── 20180604000000-cookies_change_person_id.js
├── 20180605000000-cookies_add_account_id.js
├── 20180606000000-cookies_remove_person_id.js
├── 20180607000000-cookies_rename_person_id_to_account_id.js
├── 20180608000000-likes.js
├── 20180609000000-notes_summary.js
├── 20180610000000-notes_in_reply_to_id.js
├── 20180620000000-statuses_published.js
├── 20180621000000-hashtags.js
├── 20180622000000-persons_summary.js
├── 20180623000000-persons_name.js
├── 20180630000000-actors.js
├── 20180630000000-uris_allocated.js
├── 20180640000000-announces_object_id.js
├── 20180720000000-documents.js
├── 20190408000000-der.js
├── 20190606000000-dirty_documents.js
└── 20190606000000-insert_document_without_url.js
├── mini-migrate.js
├── mini-serve.js
├── node-session.tsconfig.json
├── node.tsconfig.json
├── package-lock.json
├── package.d.ts
├── package.json
├── src
├── bull-arena.d.ts
├── client.ts
├── http-signature.d.ts
├── lib
│ ├── generate_uuid.test.ts
│ ├── generate_uuid.ts
│ ├── generated_activitystreams.ts
│ ├── generated_webfinger.ts
│ ├── isomorphism
│ │ ├── browser
│ │ │ └── session
│ │ │ │ └── event_source.ts
│ │ └── node
│ │ │ └── session
│ │ │ └── event_source.ts
│ ├── parsed_activitystreams
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── resolver.test.ts
│ │ └── resolver.ts
│ ├── repository
│ │ ├── actors.test.ts
│ │ ├── actors.ts
│ │ ├── announces.test.ts
│ │ ├── announces.ts
│ │ ├── challenges.test.ts
│ │ ├── challenges.ts
│ │ ├── cookies.ts
│ │ ├── dirty_documents.ts
│ │ ├── documents.test.ts
│ │ ├── documents.ts
│ │ ├── follows.test.ts
│ │ ├── follows.ts
│ │ ├── hashtags.test.ts
│ │ ├── hashtags.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── likes.test.ts
│ │ ├── likes.ts
│ │ ├── local_accounts.test.ts
│ │ ├── local_accounts.ts
│ │ ├── mentions.test.ts
│ │ ├── mentions.ts
│ │ ├── notes.test.ts
│ │ ├── notes.ts
│ │ ├── pg.test.ts
│ │ ├── pg.ts
│ │ ├── remote_accounts.test.ts
│ │ ├── remote_accounts.ts
│ │ ├── statuses.test.ts
│ │ ├── statuses.ts
│ │ ├── subscribers.ts
│ │ ├── syslog.ts
│ │ ├── unlinked_documents.ts
│ │ ├── uris.test.ts
│ │ └── uris.ts
│ ├── session
│ │ ├── actor.ts
│ │ ├── announce.ts
│ │ ├── fetch.ts
│ │ ├── identity.ts
│ │ ├── like.ts
│ │ ├── note.ts
│ │ └── types.ts
│ ├── test
│ │ ├── fabricator.ts
│ │ ├── repository.ts
│ │ └── types.ts
│ ├── transfer.test.ts
│ ├── transfer.ts
│ └── tuples
│ │ ├── accept.test.ts
│ │ ├── accept.ts
│ │ ├── actor
│ │ ├── base.ts
│ │ ├── from_parsed_activitystreams.test.ts
│ │ ├── from_parsed_activitystreams.ts
│ │ ├── index.test.ts
│ │ ├── index.ts
│ │ ├── resolver.test.ts
│ │ └── resolver.ts
│ │ ├── announce.test.ts
│ │ ├── announce.ts
│ │ ├── challenge.test.ts
│ │ ├── challenge.ts
│ │ ├── cookie.test.ts
│ │ ├── cookie.ts
│ │ ├── create.test.ts
│ │ ├── create.ts
│ │ ├── delete.d.ts
│ │ ├── delete.test.ts
│ │ ├── delete.ts
│ │ ├── dirty_document.ts
│ │ ├── document.test.ts
│ │ ├── document.ts
│ │ ├── follow.test.ts
│ │ ├── follow.ts
│ │ ├── hashtag.test.ts
│ │ ├── hashtag.ts
│ │ ├── key.test.ts
│ │ ├── key.ts
│ │ ├── like.test.ts
│ │ ├── like.ts
│ │ ├── local_account.test.ts
│ │ ├── local_account.ts
│ │ ├── mention.test.ts
│ │ ├── mention.ts
│ │ ├── note.test.ts
│ │ ├── note.ts
│ │ ├── ordered_collection.test.ts
│ │ ├── ordered_collection.ts
│ │ ├── ordered_collection_page.test.ts
│ │ ├── ordered_collection_page.ts
│ │ ├── relation.test.ts
│ │ ├── relation.ts
│ │ ├── remote_account.test.ts
│ │ ├── remote_account.ts
│ │ ├── status.test.ts
│ │ ├── status.ts
│ │ ├── undo.test.ts
│ │ ├── undo.ts
│ │ ├── uri.test.ts
│ │ └── uri.ts
├── options.ts
├── processor.ts
├── routes
│ ├── .well-known
│ │ └── webfinger.ts
│ ├── @[acct]
│ │ ├── [id].ts
│ │ ├── inbox.ts
│ │ ├── index.svelte
│ │ ├── index.ts
│ │ └── outbox.ts
│ ├── [uuid].ts
│ ├── _dashboard.svelte
│ ├── _error.svelte
│ ├── _landing.svelte
│ ├── _layout.svelte
│ ├── _secure.ts
│ ├── _send_activitystreams.ts
│ ├── api
│ │ ├── _cookie.ts
│ │ ├── events.ts
│ │ ├── proxy.ts
│ │ ├── signin.ts
│ │ ├── signup.ts
│ │ └── uploadMedia.ts
│ └── index.svelte
├── sapper.d.ts
├── server.ts
├── service-worker.js
├── subsystems
│ ├── client.ts
│ ├── processor
│ │ ├── handlers
│ │ │ ├── accept.test.ts
│ │ │ ├── accept.ts
│ │ │ ├── index.ts
│ │ │ ├── post_follow.test.ts
│ │ │ ├── post_follow.ts
│ │ │ ├── post_like.test.ts
│ │ │ ├── post_like.ts
│ │ │ ├── post_status.test.ts
│ │ │ ├── post_status.ts
│ │ │ ├── process_inbox.test.ts
│ │ │ └── process_inbox.ts
│ │ └── index.ts
│ └── server.ts
├── svelte.d.ts
└── template.html
├── static
└── agpl-3.0-standalone.html
├── test.tsconfig.json
├── travis
└── create-bucket.js
├── ts.eslintrc.json
├── tsconfig.json
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "test": {
4 | "plugins": ["transform-es2015-modules-commonjs"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | checks:
3 | argument-count:
4 | enabled: false
5 | complex-logic:
6 | enabled: false
7 | file-lines:
8 | enabled: false
9 | method-complexity:
10 | enabled: false
11 | method-count:
12 | enabled: false
13 | method-lines:
14 | enabled: false
15 | nested-control-flow:
16 | enabled: false
17 | return-statements:
18 | enabled: false
19 | similar-code:
20 | enabled: false
21 | identical-code:
22 | enabled: false
23 | plugins:
24 | eslint:
25 | enabled: true
26 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2018 Miniverse authors
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU Affero General Public License as published by
5 | # the Free Software Foundation, version 3 of the License.
6 | #
7 | # This program is distributed in the hope that it will be useful,
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | # GNU Affero General Public License for more details.
11 | #
12 | # You should have received a copy of the GNU Affero General Public License
13 | # along with this program. If not, see .
14 |
15 | /__sapper__
16 | /src/node_modules
17 | /src/service-worker.js
18 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": { "browser": true, "es6": true, "jest": true, "node": true },
3 | "extends": "eslint:recommended",
4 | "globals": { "ga": false, "__non_webpack_require__": false },
5 | "parserOptions": { "ecmaVersion": 8, "sourceType": "module" }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2018 Miniverse authors
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU Affero General Public License as published by
5 | # the Free Software Foundation, version 3 of the License.
6 | #
7 | # This program is distributed in the hope that it will be useful,
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | # GNU Affero General Public License for more details.
11 | #
12 | # You should have received a copy of the GNU Affero General Public License
13 | # along with this program. If not, see .
14 |
15 | node_modules
16 | /__sapper__
17 | /docker/miniverse-*.tgz
18 | /static/license.html
19 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "DefinitelyTyped"]
2 | path = DefinitelyTyped
3 | url = https://github.com/miniorg/DefinitelyTyped.git
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2018 Miniverse authors
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU Affero General Public License as published by
5 | # the Free Software Foundation, version 3 of the License.
6 | #
7 | # This program is distributed in the hope that it will be useful,
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | # GNU Affero General Public License for more details.
11 | #
12 | # You should have received a copy of the GNU Affero General Public License
13 | # along with this program. If not, see .
14 |
15 | *
16 | !/__sapper__/**
17 | !/AUTHORS
18 | !/LICENSE
19 | !/README.md
20 | !/database.json
21 | !/migrations/**
22 | !/mini-migrate.js
23 | !/mini-serve.js
24 | !/static/**
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: 11
3 | stages:
4 | - test
5 | - name: deploy
6 | if: branch = master
7 | jobs:
8 | include:
9 | - &job_test_linux_100
10 | stage: test
11 | os: linux
12 | dist: xenial
13 | sudo: false
14 | addons:
15 | postgresql: 10
16 | env:
17 | - AWS_ENDPOINT=http://127.0.0.1:9000
18 | - AWS_ACCESS_KEY_ID=minio
19 | - AWS_SECRET_ACCESS_KEY=miniostorage
20 | - AWS_SIGNATURE_VERSION=v4
21 | - AWS_S3_BUCKET=miniverse
22 | - AWS_S3_FORCE_PATH_STYLE=1
23 | - MINIO_ACCESS_KEY=minio
24 | - MINIO_SECRET_KEY=miniostorage
25 | - <<: *job_test_linux_100
26 | addons:
27 | postgresql: 9.6
28 | - <<: *job_test_linux_100
29 | addons:
30 | postgresql: 9.5
31 | - <<: *job_test_linux_100
32 | addons:
33 | postgresql: 9.6
34 | dist: trusty
35 | - <<: *job_test_linux_100
36 | addons:
37 | postgresql: 9.5
38 | dist: trusty
39 | - <<: *job_test_linux_100
40 | os: osx
41 | before_install:
42 | - initdb -D "$HOME/postgres"
43 | - pg_ctl -D "$HOME/postgres" start
44 | - brew install minio redis
45 | - minio server travis/minio.RELEASE.2018-07-23T18-34-49Z/srv/data &
46 | - redis-server --daemonize yes
47 | - pg_isready
48 | cache:
49 | directories:
50 | - node_modules
51 | - "$HOME/Library/Caches/Homebrew"
52 | - stage: deploy
53 | os: linux
54 | dist: xenial
55 | sudo: false
56 | addons:
57 | apt:
58 | packages:
59 | - awscli
60 | script:
61 | - npm pack
62 | - mv miniverse-0.0.1.tgz docker
63 | - docker build -t miniorg/miniverse docker
64 | - docker login -p "$DOCKER_PASSWORD" -u "$DOCKER_USER"
65 | - docker push miniorg/miniverse
66 | - pip install --user awscli
67 | - aws ecs update-service --force-new-deployment --service server
68 | services:
69 | - redis-server
70 | cache:
71 | directories:
72 | - node_modules
73 | - travis/minio.RELEASE.2019-04-04T18-31-46Z/bin
74 | before_install:
75 | - |
76 | if [ ! -f travis/minio.RELEASE.2019-04-04T18-31-46Z/bin/minio ]
77 | then
78 | mkdir -p travis/minio.RELEASE.2019-04-04T18-31-46Z/bin
79 | wget https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2019-04-04T18-31-46Z -O travis/minio.RELEASE.2019-04-04T18-31-46Z/bin/minio
80 | sha1sum -c <<< '06ad2f55f370d2ad1d1310cbdcae8889637a8eb1 travis/minio.RELEASE.2019-04-04T18-31-46Z/bin/minio'
81 | chmod a+x travis/minio.RELEASE.2019-04-04T18-31-46Z/bin/minio
82 | fi
83 | - ./travis/minio.RELEASE.2019-04-04T18-31-46Z/bin/minio server travis/minio.RELEASE.2019-04-04T18-31-46Z/srv/data &
84 | script:
85 | - npx eslint .
86 | - npx eslint -c ts.eslintrc.json --ext .ts .
87 | - createdb travis_test_1
88 | - PGDATABASE=travis_test_1 npx db-migrate up
89 | - node travis/create-bucket.js
90 | - npm test -- -w 1
91 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Akihiko Odaki (https://pub.miniverse.social/@aki)
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Miniverse
2 |
3 | Miniverse is an experimental microblogging software capbable to perform
4 | server-to-server interaction with recent Mastodon versions.
5 |
6 | # Some database caveats
7 | ## Do not delete any record of `local_accounts`
8 | References from `local_accounts` has `ON DELETE RESTRICT` because every records
9 | of `local_accounts` includes a precious private key. You should follow the
10 | principle when making a new reference.
11 |
12 | ## Do not update `id` column of any record
13 | The application tracks references by `id` column and therefore
14 | `ON UPDATE CASCADE`, which is effective only on the database, is not sufficient.
15 | References to `id` column should have `ON UPDATE RESTRICT`.
16 |
17 | # License
18 |
19 | `app/service-worker.js` is copied from:
20 | https://github.com/sveltejs/sapper-template/blob/master/app/service-worker.js
21 |
22 | It is written by Sapper authors, and licensed under LIL.
23 |
24 | The other portions are licensed under AGPL-3.0 by Akihiko Odaki.
25 |
--------------------------------------------------------------------------------
/base.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "baseUrl": ".",
5 | "downlevelIteration": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "noUnusedLocals": true,
8 | "noUnusedParameters": true,
9 | "strict": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/browser.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./base.tsconfig",
3 | "compilerOptions": {
4 | "lib": ["es2017", "dom"],
5 | "types": ["webpack-env"]
6 | },
7 | "include": [
8 | "src/client.ts",
9 | "src/lib/session",
10 | "src/sapper.d.ts",
11 | "src/svelte.d.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/database.json:
--------------------------------------------------------------------------------
1 | {
2 | "default": "universal",
3 | "universal": {
4 | "driver": "pg",
5 | "user": { "ENV": "PGUSER" },
6 | "database": { "ENV": "PGDATABASE" },
7 | "port": { "ENV": "PGPORT" },
8 | "host": { "ENV": "PGHOST" },
9 | "password": { "ENV": "PGPASSWORD" },
10 | "binary": { "ENV": "PGBINARY" },
11 | "client_encoding": { "ENV": "PGCLIENT_ENCODING" },
12 | "replication": { "ENV": "PGREPLICATION" },
13 | "application_name": { "ENV": "PGAPPNAME" }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2018 Miniverse authors
2 | #
3 | # This program is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU Affero General Public License as published by
5 | # the Free Software Foundation, version 3 of the License.
6 | #
7 | # This program is distributed in the hope that it will be useful,
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | # GNU Affero General Public License for more details.
11 | #
12 | # You should have received a copy of the GNU Affero General Public License
13 | # along with this program. If not, see .
14 |
15 | FROM node:11-alpine as base
16 | RUN apk add expat fftw giflib glib lcms2 libexif libjpeg-turbo libpng libwebp orc tiff
17 |
18 | FROM base
19 | RUN apk add --no-cache --virtual build \
20 | build-base expat-dev fftw-dev giflib-dev glib-dev lcms2-dev libexif-dev \
21 | libjpeg-turbo-dev libpng-dev libwebp-dev pkgconfig python2 orc-dev tiff-dev
22 | RUN wget https://github.com/libvips/libvips/releases/download/v8.7.4/vips-8.7.4.tar.gz && \
23 | echo 'c2ba4455e477ab707fb0abf946fbae010e266364 vips-8.7.4.tar.gz' | sha1sum -c
24 | COPY miniverse-0.0.1.tgz /
25 | RUN tar xf vips-8.7.4.tar.gz && \
26 | cd vips-8.7.4 && \
27 | ./configure && \
28 | make -j`getconf _NPROCESSORS_ONLN` install && \
29 | cd .. && \
30 | npm -g config set user root && npm -g install miniverse-0.0.1.tgz && \
31 | rm -r miniverse-0.0.1.tgz vips-8.7.4 vips-8.7.4.tar.gz && \
32 | apk del build
33 |
34 | FROM base
35 | CMD mini-migrate up && exec mini-serve
36 | ENV NO_UPDATE_NOTIFIER=1
37 | USER node
38 | COPY --from=1 / /
39 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | globals: { 'ts-jest': { tsConfig: 'base.tsconfig.json' } },
3 | testEnvironment: 'node',
4 | testPathIgnorePatterns: ['/DefinitelyTyped/', '/node_modules/'],
5 | transform: { '\\.ts$': 'ts-jest' }
6 | }
7 |
--------------------------------------------------------------------------------
/migrations/20180401000000-persons.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.createTable('persons', {
18 | id: { type: 'int', autoIncrement: true, notNull: true, primaryKey: true },
19 | username: { type: 'string', notNull: true },
20 | host: { type: 'string', notNull: true },
21 | }, error => {
22 | if (error) {
23 | callback(error);
24 | } else {
25 | db.runSql('CREATE UNIQUE INDEX persons_acct ON persons (lower(host), username)', callback);
26 | }
27 | });
28 |
29 | exports._meta = { version: 1 };
30 |
--------------------------------------------------------------------------------
/migrations/20180402000000-cookies.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.createTable('cookies', {
18 | digest: { type: 'bytea', notNull: true, primaryKey: true },
19 | person_id: {
20 | type: 'int',
21 | notNull: true,
22 | foreignKey: {
23 | name: 'person_id',
24 | table: 'persons',
25 | rules: { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
26 | mapping: 'id',
27 | }
28 | }
29 | }, callback);
30 |
31 | exports._meta = { version: 1 };
32 |
--------------------------------------------------------------------------------
/migrations/20180403000000-notes.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.createTable('notes', {
18 | id: { type: 'bigint', autoIncrement: true, notNull: true, primaryKey: true },
19 | attributed_to_id: {
20 | type: 'int',
21 | notNull: true,
22 | foreignKey: {
23 | name: 'attributed_to_id',
24 | table: 'persons',
25 | rules: { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
26 | mapping: 'id',
27 | }
28 | },
29 | content: { type: 'string', notNull: true }
30 | }, callback);
31 |
32 | exports._meta = { version: 1 };
33 |
--------------------------------------------------------------------------------
/migrations/20180404000000-follows.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.createTable('follows', {
18 | id: { type: 'int', autoIncrement: true, notNull: true, primaryKey: true },
19 | actor_id: {
20 | type: 'int',
21 | notNull: true,
22 | foreignKey: {
23 | name: 'actor_id',
24 | table: 'persons',
25 | rules: { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
26 | mapping: 'id',
27 | }
28 | },
29 | object_id: {
30 | type: 'int',
31 | notNull: true,
32 | foreignKey: {
33 | name: 'object_id',
34 | table: 'persons',
35 | rules: { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
36 | mapping: 'id',
37 | }
38 | }
39 | }, error => {
40 | if (error) {
41 | callback(error);
42 | } else {
43 | db.addIndex(
44 | 'follows',
45 | 'follows_actor_id_object_id',
46 | ['actor_id', 'object_id'],
47 | true,
48 | callback);
49 | }
50 | });
51 |
52 | exports._meta = { version: 1 };
53 |
--------------------------------------------------------------------------------
/migrations/20180405000000-local_accounts.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.createTable('local_accounts', {
18 | person_id: {
19 | type: 'int',
20 | notNull: true,
21 | primaryKey: true,
22 | foreignKey: {
23 | name: 'person_id',
24 | table: 'persons',
25 |
26 | // local_accounts should not be deleted as it contains precious private
27 | // keys.
28 | rules: { onDelete: 'RESTRICT', onUpdate: 'CASCADE' },
29 |
30 | mapping: 'id',
31 | }
32 | },
33 | admin: { type: 'boolean', notNull: true },
34 | private_key_pem: { type: 'string', notNull: true },
35 | salt: { type: 'bytea', notNull: true },
36 | server_key: { type: 'bytea', notNull: true },
37 | stored_key: { type: 'bytea', notNull: true }
38 | }, callback);
39 |
40 | exports._meta = { version: 1 };
41 |
--------------------------------------------------------------------------------
/migrations/20180406000000-remote_accounts.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.createTable('remote_accounts', {
18 | uri: { type: 'string', notNull: true, unique: true },
19 | inbox_uri: { type: 'string', notNull: true },
20 | key_uri: { type: 'string', notNull: true, unique: true },
21 | public_key_pem: { type: 'string', notNull: true },
22 | person_id: {
23 | type: 'int',
24 | notNull: true,
25 | primaryKey: true,
26 | foreignKey: {
27 | name: 'person_id',
28 | table: 'persons',
29 | rules: { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
30 | mapping: 'id',
31 | }
32 | },
33 | }, callback);
34 |
35 | exports._meta = { version: 1 };
36 |
--------------------------------------------------------------------------------
/migrations/20180407000000-insert_local_account.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`CREATE FUNCTION insert_local_account(username TEXT, admin BOOLEAN, private_key_pem TEXT, salt BYTEA, server_key BYTEA, stored_key BYTEA)
18 | RETURNS INTEGER AS $$
19 | DECLARE person_id INTEGER;
20 | BEGIN
21 | INSERT INTO persons (username, host) VALUES ($1, '') RETURNING id INTO person_id;
22 | INSERT INTO local_accounts (person_id, admin, private_key_pem, salt, server_key, stored_key) VALUES (person_id, $2, $3, $4, $5, $6);
23 | RETURN person_id;
24 | END
25 | $$ LANGUAGE plpgsql`, callback);
26 |
--------------------------------------------------------------------------------
/migrations/20180408000000-insert_remote_account.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`CREATE FUNCTION insert_remote_account(username TEXT, host TEXT, uri TEXT, inbox_uri TEXT, key_uri TEXT, public_key_pem TEXT)
18 | RETURNS INTEGER AS $$
19 | DECLARE person_id INTEGER;
20 | BEGIN
21 | INSERT INTO persons (username, host) VALUES ($1, $2) RETURNING id INTO person_id;
22 | INSERT INTO remote_accounts(person_id, uri, inbox_uri, key_uri, public_key_pem) VALUES (person_id, $3, $4, $5, $6);
23 | RETURN person_id;
24 | END
25 | $$ LANGUAGE plpgsql`, callback);
26 |
--------------------------------------------------------------------------------
/migrations/20180503000000-attributed_to_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.changeColumn(
18 | 'notes', 'attributed_to_id', { type: 'bigint', notNull: true }, callback);
19 |
--------------------------------------------------------------------------------
/migrations/20180504000000-actor_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.changeColumn(
18 | 'follows', 'actor_id', { type: 'bigint', notNull: true }, callback);
19 |
--------------------------------------------------------------------------------
/migrations/20180505000000-object_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.changeColumn(
18 | 'follows', 'object_id', { type: 'bigint', notNull: true }, callback);
19 |
--------------------------------------------------------------------------------
/migrations/20180506000000-remote_account_uris.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`ALTER TABLE remote_accounts RENAME person_id TO id;
18 | INSERT INTO uris (id, uri) SELECT id, uri FROM remote_accounts;
19 | ALTER TABLE remote_accounts DROP uri;
20 |
21 | CREATE OR REPLACE FUNCTION insert_remote_account(
22 | username TEXT,
23 | host TEXT,
24 | uri TEXT,
25 | inbox_uri TEXT,
26 | key_uri TEXT,
27 | public_key_pem TEXT
28 | ) RETURNS RECORD AS $$
29 | DECLARE id BIGINT;
30 | DECLARE inbox_uri_id BIGINT;
31 | DECLARE key_uri_id BIGINT;
32 | DECLARE result RECORD;
33 | BEGIN
34 | INSERT INTO persons (username, host) VALUES ($1, $2)
35 | RETURNING persons.id INTO id;
36 |
37 | INSERT INTO uris (id, uri) VALUES (id, $3);
38 |
39 | SELECT INTO inbox_uri_id uris.id FROM uris WHERE uris.uri = $4;
40 | IF inbox_uri_id IS NULL
41 | THEN
42 | INSERT INTO uris (uri) VALUES ($4) RETURNING uris.id INTO inbox_uri_id;
43 | END IF;
44 |
45 | SELECT INTO key_uri_id uris.id FROM uris WHERE uris.uri = $5;
46 | IF key_uri_id IS NULL
47 | THEN
48 | INSERT INTO uris (uri) VALUES ($5) RETURNING uris.id INTO key_uri_id;
49 | END IF;
50 |
51 | INSERT INTO remote_accounts (
52 | id,
53 | inbox_uri_id,
54 | key_uri_id,
55 | public_key_pem
56 | ) VALUES (id, inbox_uri_id, key_uri_id, $6);
57 |
58 | result := (id, inbox_uri_id, key_uri_id);
59 | RETURN result;
60 | END
61 | $$ LANGUAGE plpgsql;
62 | `, callback);
63 |
--------------------------------------------------------------------------------
/migrations/20180507000000-local_accounts_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`ALTER TABLE local_accounts RENAME person_id TO id;
18 |
19 | CREATE OR REPLACE FUNCTION insert_local_account(
20 | username TEXT,
21 | admin BOOLEAN,
22 | private_key_pem TEXT,
23 | salt BYTEA,
24 | server_key BYTEA,
25 | stored_key BYTEA
26 | ) RETURNS BIGINT AS $$
27 | DECLARE id BIGINT;
28 | BEGIN
29 | INSERT INTO persons (username, host) VALUES ($1, '')
30 | RETURNING persons.id INTO id;
31 |
32 | INSERT INTO local_accounts
33 | (id, admin, private_key_pem, salt, server_key, stored_key)
34 | VALUES (id, $2, $3, $4, $5, $6);
35 |
36 | RETURN id;
37 | END
38 | $$ LANGUAGE plpgsql;
39 | `, callback);
40 |
--------------------------------------------------------------------------------
/migrations/20180508000000-notes_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) =>
18 | db.runSql('ALTER TABLE notes ALTER id TYPE BIGINT', callback);
19 |
--------------------------------------------------------------------------------
/migrations/20180509000000-mentions.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.createTable('mentions', {
18 | note_id: {
19 | type: 'bigint',
20 | notNull: true,
21 | foreignKey: {
22 | name: 'note_id',
23 | table: 'notes',
24 | rules: { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
25 | mapping: 'id',
26 | }
27 | },
28 | href_id: {
29 | type: 'bigint',
30 | notNull: true,
31 | foreignKey: {
32 | name: 'href_id',
33 | table: 'persons',
34 | rules: { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
35 | mapping: 'id',
36 | }
37 | }
38 | }, callback);
39 |
--------------------------------------------------------------------------------
/migrations/20180510000000-insert_note.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`CREATE FUNCTION insert_note(
18 | uri VARCHAR,
19 | attributed_to_id BIGINT,
20 | content TEXT,
21 | mentions BIGINT[]
22 | ) RETURNS BIGINT AS $$
23 | DECLARE id BIGINT;
24 | BEGIN
25 | INSERT INTO notes (attributed_to_id, content) VALUES ($2, $3)
26 | RETURNING notes.id INTO id;
27 |
28 | IF uri IS NOT NULL THEN
29 | INSERT INTO uris (id, uri) VALUES (id, $1);
30 | END IF;
31 |
32 | INSERT INTO mentions (note_id, href_id)
33 | SELECT id, unnest FROM unnest(mentions);
34 |
35 | RETURN id;
36 | END
37 | $$ LANGUAGE plpgsql;
38 |
39 | DROP FUNCTION insert_note_with_uri(VARCHAR, BIGINT, TEXT);
40 | `, callback);
41 |
--------------------------------------------------------------------------------
/migrations/20180601000000-statuses.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`CREATE TABLE statuses (
18 | id BIGINT PRIMARY KEY DEFAULT nextval('id_seq'),
19 | person_id BIGINT REFERENCES persons (id) ON DELETE CASCADE ON UPDATE CASCADE
20 | );
21 |
22 | CREATE TABLE announces (
23 | id BIGINT PRIMARY KEY REFERENCES statuses (id)
24 | ON DELETE CASCADE ON UPDATE CASCADE,
25 | object_id BIGINT REFERENCES notes (id)
26 | ON DELETE RESTRICT ON UPDATE CASCADE
27 | );
28 |
29 | CREATE FUNCTION delete_status(
30 | id BIGINT,
31 | attributed_to_id BIGINT
32 | ) RETURNS VOID AS $$
33 | BEGIN
34 | IF EXISTS (SELECT TRUE FROM statuses WHERE statuses.id = $1 AND attributed_to_id = $2) THEN
35 | DELETE FROM statuses USING announces
36 | WHERE statuses.id = announces.id AND announces.object_id = $1;
37 | DELETE FROM statuses WHERE statuses.id = $1;
38 | END IF;
39 | END
40 | $$ LANGUAGE plpgsql;
41 |
42 | CREATE FUNCTION insert_announce(
43 | uri VARCHAR,
44 | actor_id BIGINT,
45 | object_id BIGINT
46 | ) RETURNS BIGINT AS $$
47 | DECLARE id BIGINT;
48 | BEGIN
49 | INSERT INTO statuses (person_id) VALUES ($2) RETURNING statuses.id INTO id;
50 | INSERT INTO announces (id, object_id) VALUES (id, $3);
51 |
52 | IF uri IS NOT NULL THEN
53 | INSERT INTO uris (id, uri) VALUES (id, $1);
54 | END IF;
55 |
56 | RETURN id;
57 | END
58 | $$ LANGUAGE plpgsql;
59 |
60 | CREATE OR REPLACE FUNCTION insert_note(
61 | uri VARCHAR,
62 | attributed_to_id BIGINT,
63 | content TEXT,
64 | mentions BIGINT[]
65 | ) RETURNS BIGINT AS $$
66 | DECLARE id BIGINT;
67 | BEGIN
68 | INSERT INTO statuses (person_id) VALUES ($2) RETURNING statuses.id INTO id;
69 | INSERT INTO notes (id, content) VALUES (id, $3);
70 |
71 | IF uri IS NOT NULL THEN
72 | INSERT INTO uris (id, uri) VALUES (id, $1);
73 | END IF;
74 |
75 | INSERT INTO mentions (note_id, href_id)
76 | SELECT id, unnest FROM unnest(mentions);
77 |
78 | RETURN id;
79 | END
80 | $$ LANGUAGE plpgsql;
81 |
82 | INSERT INTO statuses (id, person_id) SELECT id, attributed_to_id FROM notes;
83 | ALTER TABLE notes ADD CONSTRAINT id FOREIGN KEY (id) REFERENCES statuses (id)
84 | ON DELETE CASCADE ON UPDATE CASCADE;
85 | ALTER TABLE notes DROP COLUMN attributed_to_id;
86 | `, callback);
87 |
--------------------------------------------------------------------------------
/migrations/20180602000000-tombstones.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.createTable('tombstones', {
18 | id: {
19 | type: 'bigint',
20 | primaryKey: true,
21 | foreignKey: {
22 | name: 'id',
23 | table: 'uris',
24 | rules: { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
25 | mapping: 'id',
26 | }
27 | }
28 | }, callback);
29 |
30 | exports._meta = { version: 1 };
31 |
--------------------------------------------------------------------------------
/migrations/20180603000000-delete_status.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`CREATE OR REPLACE FUNCTION delete_status(
18 | id BIGINT,
19 | attributed_to_id BIGINT
20 | ) RETURNS VOID AS $$
21 | DECLARE announce_id BIGINT;
22 | BEGIN
23 | IF EXISTS (SELECT TRUE FROM statuses WHERE statuses.id = $1 AND attributed_to_id = $2) THEN
24 | INSERT INTO tombstones (id) VALUES ($1);
25 |
26 | FOR announce_id IN SELECT announces.id FROM announces WHERE object_id = $1 LOOP
27 | INSERT INTO tombstones (id) VALUES (announce_id);
28 | DELETE FROM statuses WHERE statuses.id = announce_id;
29 | END LOOP;
30 |
31 | DELETE FROM statuses WHERE statuses.id = $1;
32 | END IF;
33 | END
34 | $$ LANGUAGE plpgsql;
35 | `, callback);
36 |
37 | exports._meta = { version: 1 };
38 |
--------------------------------------------------------------------------------
/migrations/20180604000000-cookies_change_person_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.changeColumn(
18 | 'cookies', 'person_id', { type: 'bigint', notNull: true }, callback);
19 |
20 | exports._meta = { version: 1 };
21 |
--------------------------------------------------------------------------------
/migrations/20180605000000-cookies_add_account_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.addForeignKey(
18 | 'cookies',
19 | 'local_accounts',
20 | 'account_id',
21 | { person_id: 'id' },
22 | { onDelete: 'CASCADE', onUpdate: 'CASCADE' },
23 | callback);
24 |
25 | exports._meta = { version: 1 };
26 |
--------------------------------------------------------------------------------
/migrations/20180606000000-cookies_remove_person_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) =>
18 | db.removeForeignKey('cookies', 'person_id', callback);
19 |
20 | exports._meta = { version: 1 };
21 |
--------------------------------------------------------------------------------
/migrations/20180607000000-cookies_rename_person_id_to_account_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) =>
18 | db.renameColumn('cookies', 'person_id', 'account_id', callback);
19 |
20 | exports._meta = { version: 1 };
21 |
--------------------------------------------------------------------------------
/migrations/20180608000000-likes.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`CREATE TABLE likes (
18 | id BIGSERIAL PRIMARY KEY,
19 | actor_id BIGINT REFERENCES persons (id) ON DELETE CASCADE ON UPDATE CASCADE,
20 | object_id BIGINT REFERENCES notes (id) ON DELETE CASCADE ON UPDATE CASCADE,
21 | UNIQUE (actor_id, object_id)
22 | )`, callback);
23 |
24 | exports._meta = { version: 1 };
25 |
--------------------------------------------------------------------------------
/migrations/20180609000000-notes_summary.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`ALTER TABLE notes ADD summary TEXT;
18 | UPDATE notes SET summary = '';
19 | ALTER TABLE notes ALTER summary SET NOT NULL;
20 |
21 | DROP FUNCTION insert_note(VARCHAR, BIGINT, TEXT, BIGINT[]);
22 | CREATE OR REPLACE FUNCTION insert_note(
23 | uri VARCHAR,
24 | attributed_to_id BIGINT,
25 | summary TEXT,
26 | content TEXT,
27 | mentions BIGINT[]
28 | ) RETURNS BIGINT AS $$
29 | DECLARE id BIGINT;
30 | BEGIN
31 | INSERT INTO statuses (person_id) VALUES ($2) RETURNING statuses.id INTO id;
32 | INSERT INTO notes (id, summary, content) VALUES (id, $3, $4);
33 |
34 | IF $1 IS NOT NULL THEN
35 | INSERT INTO uris (id, uri) VALUES (id, $1);
36 | END IF;
37 |
38 | INSERT INTO mentions (note_id, href_id) SELECT id, unnest FROM unnest($5);
39 |
40 | RETURN id;
41 | END
42 | $$ LANGUAGE plpgsql;
43 | `, callback);
44 |
45 | exports._meta = { version: 1 };
46 |
--------------------------------------------------------------------------------
/migrations/20180640000000-announces_object_id.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.changeColumn(
18 | 'announces', 'object_id', { type: 'bigint', notNull: true }, callback);
19 |
20 | exports._meta = { version: 1 };
21 |
--------------------------------------------------------------------------------
/migrations/20190606000000-dirty_documents.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`
18 | CREATE TABLE dirty_documents (
19 | id serial PRIMARY KEY,
20 | uuid uuid NOT NULL,
21 | format varchar NOT NULL);
22 |
23 | DROP FUNCTION insert_document_with_url(uuid, varchar, varchar);
24 |
25 | CREATE FUNCTION insert_document_with_url(dirty_id integer, url varchar)
26 | RETURNS bigint LANGUAGE plpgsql AS $_$
27 | DECLARE uri_id bigint;
28 | BEGIN
29 | INSERT INTO uris (uri, allocated) VALUES ($2, TRUE)
30 | ON CONFLICT (uri) DO UPDATE SET allocated = TRUE WHERE NOT uris.allocated
31 | RETURNING uris.id INTO uri_id;
32 |
33 | INSERT INTO documents (id, uuid, format) SELECT uri_id, uuid, format
34 | FROM dirty_documents WHERE dirty_documents.id = $1;
35 |
36 | DELETE FROM dirty_documents WHERE id = $1;
37 |
38 | RETURN uri_id;
39 | END
40 | $_$;`, callback);
41 |
42 | exports._meta = { version: 1 };
43 |
--------------------------------------------------------------------------------
/migrations/20190606000000-insert_document_without_url.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | exports.up = (db, callback) => db.runSql(`
18 | CREATE FUNCTION insert_document_without_url(dirty_id integer)
19 | RETURNS bigint LANGUAGE plpgsql AS $_$
20 | DECLARE id bigint;
21 | BEGIN
22 | INSERT INTO documents (uuid, format) SELECT uuid, format
23 | FROM dirty_documents WHERE dirty_documents.id = $1
24 | RETURNING documents.id INTO id;
25 |
26 | DELETE FROM dirty_documents WHERE dirty_documents.id = $1;
27 |
28 | RETURN id;
29 | END
30 | $_$;`, callback);
31 |
32 | exports._meta = { version: 1 };
33 |
--------------------------------------------------------------------------------
/mini-migrate.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /*
4 | Copyright (C) 2018 Miniverse authors
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, version 3 of the License.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Affero General Public License for more details.
14 |
15 | You should have received a copy of the GNU Affero General Public License
16 | along with this program. If not, see .
17 | */
18 |
19 | const { getInstance } = require('db-migrate');
20 |
21 | if (process.argv.indexOf('--verbose') >= 0 || process.argv.indexOf('-v') >= 0) {
22 | global.verbose = true;
23 | }
24 |
25 | process.title = 'db-migrate';
26 | const dbmigrate = getInstance(false, { cwd: __dirname, noPlugins: true });
27 | dbmigrate.registerAPIHook().then(() => dbmigrate.run());
28 |
--------------------------------------------------------------------------------
/mini-serve.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /*
4 | Copyright (C) 2018 Miniverse authors
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as published by
8 | the Free Software Foundation, version 3 of the License.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Affero General Public License for more details.
14 |
15 | You should have received a copy of the GNU Affero General Public License
16 | along with this program. If not, see .
17 | */
18 |
19 | process.chdir(__dirname);
20 | require('./__sapper__/build');
21 |
--------------------------------------------------------------------------------
/node-session.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./base.tsconfig",
3 | "compilerOptions": {
4 | "module": "CommonJS",
5 | "target": "ES2017",
6 | "types": []
7 | },
8 | "include": [
9 | "src/lib/session",
10 | "src/svelte.d.ts"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/node.tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./base.tsconfig",
3 | "compilerOptions": {
4 | "lib": ["es2017"],
5 | "module": "CommonJS",
6 | "target": "ES2017",
7 | "types": []
8 | },
9 | "include": [
10 | "migrations",
11 | "src/bull-arena.d.ts",
12 | "src/http-signature.d.ts",
13 | "src/processor.ts",
14 | "src/routes",
15 | "src/sapper.d.ts",
16 | "src/server.ts",
17 | "src/svelte.d.ts",
18 | "webpack.config.js"
19 | ],
20 | "exclude": ["**/*.test.ts"]
21 | }
22 |
--------------------------------------------------------------------------------
/package.d.ts:
--------------------------------------------------------------------------------
1 | export const dependencies: { [package: string]: string };
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "miniverse",
3 | "version": "0.0.1",
4 | "description": "Miniverse is a microblogging software.",
5 | "bin": {
6 | "mini-migrate": "mini-migrate.js",
7 | "mini-process": "__sapper__/build/server/processor.js",
8 | "mini-serve": "mini-serve.js"
9 | },
10 | "scripts": {
11 | "dev": "sapper dev",
12 | "build": "sapper build",
13 | "prepare": "sapper build",
14 | "start": "node __sapper__/build",
15 | "test": "jest"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/miniorg/miniverse.git"
20 | },
21 | "license": "AGPL-3.0",
22 | "bugs": {
23 | "url": "https://github.com/miniorg/miniverse/issues"
24 | },
25 | "homepage": "https://github.com/miniorg/miniverse#readme",
26 | "dependencies": {
27 | "@hapi/accept": "^3.2.2",
28 | "abort-controller": "^3.0.0",
29 | "aws-sdk": "^2.485.0",
30 | "body-parser": "^1.19.0",
31 | "bull": "^3.10.0",
32 | "bull-arena": "^2.6.3",
33 | "busboy": "^0.3.1",
34 | "cookie": "^0.4.0",
35 | "db-migrate": "^0.11.6",
36 | "db-migrate-pg": "^1.0.0",
37 | "http-signature": "^1.2.0",
38 | "ioredis": "^4.10.0",
39 | "node-fetch": "https://github.com/miniorg/node-fetch/releases/download/v2.3.0-miniverse-r0/node-fetch-2.3.0.tgz",
40 | "pg": "^7.11.0",
41 | "sanitize-html": "^1.20.1",
42 | "sapper": "^0.27.4",
43 | "sharp": "^0.22.1",
44 | "svelte": "^3.6.1",
45 | "type-is": "^1.6.18"
46 | },
47 | "devDependencies": {
48 | "@jetbrains/ring-ui-license-checker": "^1.5.2",
49 | "@types/bull": "^3.5.15",
50 | "@types/busboy": "^0.2.3",
51 | "@types/cookie": "^0.3.3",
52 | "@types/express": "^4.17.0",
53 | "@types/hapi__accept": "^3.2.0",
54 | "@types/ioredis": "^4.0.12",
55 | "@types/jest": "^24.0.15",
56 | "@types/nock": "^10.0.3",
57 | "@types/node": "https://github.com/miniorg/DefinitelyTyped/releases/download/miniverse-r0/node.tgz",
58 | "@types/node-fetch": "https://github.com/miniorg/DefinitelyTyped/releases/download/miniverse-r0/node-fetch.tgz",
59 | "@types/pg": "^7.4.14",
60 | "@types/sanitize-html": "^1.20.0",
61 | "@types/sharp": "^0.22.2",
62 | "@types/type-is": "^1.6.2",
63 | "@types/webpack-env": "^1.13.9",
64 | "@typescript-eslint/eslint-plugin": "^1.11.0",
65 | "@typescript-eslint/parser": "^1.11.0",
66 | "babel-core": "^6.26.3",
67 | "babel-jest": "^24.8.0",
68 | "eslint": "^5.16.0",
69 | "express": "^4.17.1",
70 | "jest": "^24.8.0",
71 | "nock": "^10.0.6",
72 | "svelte-loader": "^2.13.4",
73 | "ts-jest": "^24.0.2",
74 | "ts-loader": "^6.0.4",
75 | "typescript": "^3.5.2",
76 | "webpack": "^4.35.0"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/bull-arena.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'bull-arena' {
2 | import { RequestHandler } from 'express';
3 | import { RedisOptions } from 'ioredis';
4 |
5 | function Arena(
6 | config: {
7 | queues: ({
8 | host: string;
9 | port?: string;
10 | password?: string;
11 | db?: number;
12 | } | {
13 | url?: string;
14 | } | {
15 | redis?: RedisOptions;
16 | } & {
17 | name: string;
18 | hostId: string;
19 | type?: null | string;
20 | prefix?: string;
21 | })[];
22 | },
23 | listenOpts: {
24 | port?: number;
25 | host?: string;
26 | basePath?: string;
27 | disableListen?: boolean;
28 | useCdn?: boolean;
29 | }): RequestHandler;
30 |
31 | export = Arena;
32 | }
33 |
--------------------------------------------------------------------------------
/src/client.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import start from './subsystems/client';
18 |
19 | const root = document.getElementById('root');
20 |
21 | if (root) {
22 | start(root);
23 | } else {
24 | // eslint-disable-next-line no-console
25 | console.error('failed to get the root element');
26 | }
27 |
28 | if (module.hot) {
29 | module.hot.accept();
30 | }
31 |
--------------------------------------------------------------------------------
/src/http-signature.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'http-signature' {
2 | import { ClientRequest, IncomingMessage } from 'http';
3 |
4 | export function parseRequest(request: IncomingMessage): {};
5 |
6 | export function sign(request: ClientRequest, options: {
7 | readonly authorizationHeaderName?: 'Signature';
8 | readonly key: string;
9 | readonly keyId: string;
10 | }): boolean;
11 |
12 | export function verifySignature(parsedSignature: {}, pubkey: string): boolean;
13 | }
14 |
15 | declare module 'http-signature/lib/utils' {
16 | export class HttpSignatureError extends Error {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/lib/generate_uuid.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { randomBytes } from 'crypto';
18 | import { promisify } from 'util';
19 |
20 | const promisifiedRandomBytes = promisify(randomBytes);
21 |
22 | export default async function() {
23 | const timestamp = (Date.now() + 12219292800000) * 10000;
24 | const timestampQuotient = timestamp / 0x100000000;
25 | const timestampRemainder = timestamp % 0x100000000;
26 | const random = await promisifiedRandomBytes(8);
27 |
28 | random[0] = (random[0] & 0x3f) | 0x80;
29 | random[2] |= 1;
30 |
31 | const timeLow = Math.floor(timestampRemainder);
32 | const timeMid = timestampQuotient & 0xffff;
33 | const timeHighAndVersion = (timestampQuotient >> 16) | 0x1000;
34 | const clockSeq = random.slice(0, 2);
35 | const node = random.slice(2);
36 |
37 | return [
38 | ('0000000' + timeLow.toString(16)).slice(-8),
39 | ('000' + timeMid.toString(16)).slice(-4),
40 | timeHighAndVersion.toString(16),
41 | clockSeq.toString('hex'),
42 | node.toString('hex')
43 | ].join('-');
44 | }
45 |
--------------------------------------------------------------------------------
/src/lib/generated_activitystreams.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | export interface Accept {
18 | type: string;
19 | object: Follow;
20 | }
21 |
22 | export interface Announce {
23 | type: string;
24 | id?: string;
25 | published: Date;
26 | object: string;
27 | }
28 |
29 | export type Any = Accept | Actor | Announce | Create |
30 | Follow | Key | LocalActor | Note |
31 | OrderedCollection | OrderedCollectionPage;
32 |
33 | export interface Actor {
34 | id: string;
35 | preferredUsername: string;
36 | name: string;
37 | summary: string;
38 | inbox: string;
39 | outbox: string;
40 | }
41 |
42 | export interface Create {
43 | type: string;
44 | id?: string;
45 | object: Document | Note;
46 | }
47 |
48 | export interface Document {
49 | type: string;
50 | mediaType: string;
51 | url: string;
52 | }
53 |
54 | export interface Endpoints {
55 | proxyUrl: string;
56 | uploadMedia: string;
57 | }
58 |
59 | export interface Follow {
60 | type: string;
61 | actor: string;
62 | object: string;
63 | }
64 |
65 | export interface Hashtag {
66 | type: string;
67 | name: string;
68 | }
69 |
70 | export interface Key {
71 | id: string;
72 | type: string;
73 | owner: string;
74 | publicKeyPem: string;
75 | }
76 |
77 | export interface Like {
78 | type: string;
79 | object: string;
80 | }
81 |
82 | export interface LocalActor extends Actor {
83 | type: string;
84 | endpoints: Endpoints;
85 | publicKey: Key;
86 | 'miniverse:salt': string;
87 | }
88 |
89 | export interface Mention {
90 | type: string;
91 | href: string;
92 | }
93 |
94 | export interface Note {
95 | type: string;
96 | id: string;
97 | published: Date;
98 | attributedTo: string;
99 | inReplyTo: string | null;
100 | to: string;
101 | summary: string | null;
102 | content: string;
103 | attachment: Document[];
104 | tag: (Hashtag | Mention)[];
105 | }
106 |
107 | export interface OrderedCollection {
108 | type: string;
109 | orderedItems: Any[];
110 | }
111 |
112 | export interface OrderedCollectionPage {
113 | type: string;
114 | orderedItems: Any[];
115 | }
116 |
--------------------------------------------------------------------------------
/src/lib/generated_webfinger.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | export interface Account {
18 | subject: string;
19 | links: [
20 | {
21 | rel: 'self';
22 | type: 'application/activity+json';
23 | href: string;
24 | }
25 | ];
26 | }
27 |
--------------------------------------------------------------------------------
/src/lib/isomorphism/browser/session/event_source.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { readable } from 'svelte/store';
18 |
19 | export function listenEventSource() {
20 | const eventSource = new EventSource(`https://${location.host}/api/events`);
21 | let inbox: unknown[] = [];
22 |
23 | return readable(inbox, set => {
24 | eventSource.onmessage = ({ data }) => {
25 | const { type, orderedItems } = JSON.parse(data);
26 |
27 | if (type == 'OrderedCollectionPage') {
28 | inbox = orderedItems.reverse().concat(inbox);
29 | set(inbox);
30 | }
31 | };
32 |
33 | return () => eventSource.close();
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/src/lib/isomorphism/node/session/event_source.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { readable } from 'svelte/store';
18 |
19 | export function listenEventSource() {
20 | return readable([], () => () => {});
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/repository/announces.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import Announce, { Seed } from '../tuples/announce';
19 | import Status from '../tuples/status';
20 | import URI from '../tuples/uri';
21 | import Repository, { conflict } from '.';
22 |
23 | export default class {
24 | async insertAnnounce(
25 | this: Repository,
26 | { status, object }: Seed,
27 | signal: AbortSignal,
28 | recover: (error: Error & { name?: string; [conflict]?: boolean }) => unknown
29 | ) {
30 | const { rows: [{ insert_announce }]} = await this.pg.query({
31 | name: 'insertAnnounce',
32 | text: 'SELECT insert_announce($1, $2, $3, $4)',
33 | values: [
34 | status.published,
35 | status.uri,
36 | status.actor.id,
37 | object.id
38 | ]
39 | }, signal, error => {
40 | if (error.name == 'AbortError') {
41 | return recover(error);
42 | }
43 |
44 | if (error.code == '23502') {
45 | return recover(Object.assign(
46 | new Error('uri conflicts.'),
47 | { [conflict]: true }));
48 | }
49 |
50 | return error;
51 | });
52 |
53 | return new Announce({
54 | repository: this,
55 | status: new Status({
56 | repository: this,
57 | id: insert_announce,
58 | published: status.published,
59 | actor: status.actor,
60 | uri: status.uri == null ? null : new URI({
61 | repository: this,
62 | id: insert_announce,
63 | uri: status.uri,
64 | allocated: true
65 | })
66 | }),
67 | object
68 | });
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/lib/repository/challenges.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import repository from '../test/repository';
18 |
19 | test('inserts challenge and allows to query with digest', async () => {
20 | await repository.insertChallenge(Buffer.from('digest'));
21 |
22 | const digest = Buffer.from('digest');
23 | await expect(repository.selectChallengeByDigest(digest))
24 | .resolves
25 | .toHaveProperty('digest', digest);
26 | });
27 |
28 | test('does not return a challenge if not present', () =>
29 | expect(repository.selectChallengeByDigest(Buffer.from('digest')))
30 | .resolves
31 | .toBe(null));
32 |
--------------------------------------------------------------------------------
/src/lib/repository/challenges.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Challenge from '../tuples/challenge';
18 | import Repository from '.';
19 |
20 | function createKey(this: Repository, digest: Buffer) {
21 | const repositoryPrefixLength = Buffer.byteLength(this.redis.prefix);
22 | const challengesPrefixLength = Buffer.byteLength('challenges:');
23 | const buffer = Buffer.allocUnsafe(
24 | repositoryPrefixLength + challengesPrefixLength + digest.byteLength);
25 |
26 | buffer.write(this.redis.prefix);
27 | buffer.write('challenges:', repositoryPrefixLength);
28 | digest.copy(buffer, repositoryPrefixLength + challengesPrefixLength);
29 |
30 | return buffer;
31 | }
32 |
33 | export default class {
34 | async insertChallenge(this: Repository, digest: Buffer) {
35 | const key = createKey.call(this, digest);
36 | await this.redis.client.setex(key, 1048576, '');
37 | return new Challenge({ digest });
38 | }
39 |
40 | async selectChallengeByDigest(this: Repository, digest: Buffer) {
41 | if (await this.redis.client.get(createKey.call(this, digest)) == null) {
42 | return null;
43 | }
44 |
45 | return new Challenge({ digest });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/lib/repository/cookies.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import Cookie from '../tuples/cookie';
19 | import LocalAccount from '../tuples/local_account';
20 | import Repository from '.';
21 |
22 | export default class {
23 | async insertCookie(this: Repository, { account, digest }: {
24 | readonly account: LocalAccount;
25 | readonly digest: Buffer;
26 | }, signal: AbortSignal, recover: (error: Error & { name: string }) => unknown) {
27 | await this.pg.query({
28 | name: 'insertCookie',
29 | text: 'INSERT INTO cookies (digest, account_id) VALUES ($1, $2)',
30 | values: [digest, account.id]
31 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
32 |
33 | return new Cookie({ repository: this, account, digest });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/lib/repository/dirty_documents.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import DirtyDocument from '../tuples/dirty_document';
19 | import Repository from '.';
20 |
21 | export default class {
22 | async deleteDirtyDocument(
23 | this: Repository,
24 | { id }: DirtyDocument,
25 | signal: AbortSignal,
26 | recover: (error: Error & { name: string }) => unknown
27 | ) {
28 | await this.pg.query({
29 | name: 'deleteDirtyDocument',
30 | text: 'DELETE FROM dirty_documents WHERE id = $1',
31 | values: [id]
32 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
33 | }
34 |
35 | async insertDirtyDocument(
36 | this: Repository,
37 | uuid: string,
38 | format: string,
39 | signal: AbortSignal,
40 | recover: (error: Error & { name: string }) => unknown
41 | ) {
42 | const { rows } = await this.pg.query({
43 | name: 'insertDirtyDocument',
44 | text: 'INSERT INTO dirty_documents (uuid, format) VALUES ($1, $2) RETURNING id',
45 | values: [uuid, format]
46 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
47 |
48 | return new DirtyDocument({ repository: this, id: rows[0].id, uuid, format });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/lib/repository/hashtags.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import { fabricateNote } from '../test/fabricator';
19 | import repository from '../test/repository';
20 |
21 | test('inserts note and allows to query its hashtags', async () => {
22 | const { id } = await fabricateNote({ hashtags: ['名前'] });
23 | const recover = jest.fn();
24 | const { signal } = new AbortController;
25 | const hashtags = await repository.selectHashtagsByNoteId(id, signal, recover);
26 | expect(recover).not.toHaveBeenCalled();
27 | expect(hashtags[0]).toHaveProperty('name', '名前');
28 | });
29 |
--------------------------------------------------------------------------------
/src/lib/repository/hashtags.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import Hashtag from '../tuples/hashtag';
19 | import Repository from '.';
20 |
21 | export default class {
22 | async selectHashtagsByNoteId(
23 | this: Repository,
24 | noteId: string,
25 | signal: AbortSignal,
26 | recover: (error: Error & { name: string }) => unknown
27 | ) {
28 | const { rows } = await this.pg.query({
29 | name: 'selectHashtagsByNoteId',
30 | text: 'SELECT * FROM hashtags JOIN hashtags_notes ON hashtags.id = hashtags_notes.hashtag_id WHERE hashtags_notes.note_id = $1',
31 | values: [noteId]
32 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
33 |
34 | return (rows as any[]).map(properties => new Hashtag(properties));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/lib/repository/index.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Repository from '.';
18 | import S3 = require('aws-sdk/clients/s3');
19 |
20 | test('defaults finger host to host', async () => {
21 | const repository = new Repository({
22 | analytics: {},
23 | console,
24 | content: { frame: {}, image: {}, script: { sources: [] } },
25 | host: 'إختبار',
26 | pg: { host: 'localhost', port: 5432 },
27 | redis: {},
28 | s3: { service: new S3, bucket: '', keyPrefix: '', urlPrefix: '' }
29 | });
30 |
31 | try {
32 | expect(repository).toHaveProperty('fingerHost', 'إختبار');
33 | } finally {
34 | await repository.end();
35 | }
36 | });
37 |
38 | test('allows to override finger host', async () => {
39 | const repository = new Repository({
40 | analytics: {},
41 | console,
42 | content: { frame: {}, image: {}, script: { sources: [] } },
43 | fingerHost: 'إختبار',
44 | host: '',
45 | pg: { host: 'localhost', port: 5432 },
46 | redis: {},
47 | s3: { service: new S3, bucket: '', keyPrefix: '', urlPrefix: '' }
48 | });
49 |
50 | try {
51 | expect(repository).toHaveProperty('fingerHost', 'إختبار');
52 | } finally {
53 | await repository.end();
54 | }
55 | });
56 |
--------------------------------------------------------------------------------
/src/lib/repository/likes.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import Actor from '../tuples/actor';
19 | import Like, { Seed } from '../tuples/like';
20 | import Note from '../tuples/note';
21 | import Repository from '.';
22 |
23 | export default class {
24 | async deleteLikeByActorAndObject(
25 | this: Repository,
26 | actor: Actor,
27 | object: Note,
28 | signal: AbortSignal,
29 | recover: (error: Error & { name: string }) => unknown
30 | ) {
31 | await this.pg.query({
32 | name: 'deleteLikeByActorAndObject',
33 | text: 'DELETE FROM likes WHERE actor_id = $1 AND object_id = $2',
34 | values: [actor.id, object.id]
35 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
36 | }
37 |
38 | async insertLike(
39 | this: Repository,
40 | { actor, object }: Seed,
41 | signal: AbortSignal,
42 | recover: (error: Error & { name?: string }) => unknown
43 | ) {
44 | const { rows: [{ id }] } = await this.pg.query({
45 | name: 'insertLike',
46 | text: 'INSERT INTO likes (actor_id, object_id) VALUES ($1, $2) RETURNING id',
47 | values: [actor.id, object.id]
48 | }, signal, error => {
49 | if (error.name == 'AbortError') {
50 | return recover(error);
51 | }
52 |
53 | if (error.code == '23505') {
54 | return recover(new Error('Already liked.'));
55 | }
56 |
57 | return error;
58 | });
59 |
60 | return new Like({ repository: this, id, actor, object });
61 | }
62 |
63 | async selectLikeById(
64 | this: Repository,
65 | id: string,
66 | signal: AbortSignal,
67 | recover: (error: Error & { name: string }) => unknown
68 | ): Promise {
69 | const { rows } = await this.pg.query({
70 | name: 'selectLikeById',
71 | text: 'SELECT * FROM likes WHERE id = $1',
72 | values: [id]
73 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
74 |
75 | return rows[0] ? new Like({
76 | repository: this,
77 | id,
78 | actorId: rows[0].actor_id,
79 | objectId: rows[0].object_id
80 | }) : null;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/lib/repository/mentions.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import { fabricateLocalAccount, fabricateNote } from '../test/fabricator';
19 | import repository from '../test/repository';
20 | import { unwrap } from '../test/types';
21 |
22 | test('inserts note and allows to query its mentions', async () => {
23 | const recover = jest.fn();
24 | const { signal } = new AbortController;
25 | const account = await fabricateLocalAccount();
26 | const actor = unwrap(await account.select('actor', signal, recover));
27 | const note = await fabricateNote({ mentions: [actor] });
28 |
29 | const mentions = await repository.selectMentionsIncludingActorsByNoteId(
30 | note.id, signal, recover);
31 |
32 | expect(recover).not.toHaveBeenCalled();
33 | expect(mentions[0]).toHaveProperty('hrefId', actor.id);
34 | });
35 |
--------------------------------------------------------------------------------
/src/lib/repository/mentions.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import Mention from '../tuples/mention';
19 | import Repository from '.';
20 |
21 | export default class {
22 | async selectMentionsIncludingActorsByNoteId(
23 | this: Repository,
24 | id: string,
25 | signal: AbortSignal,
26 | recover: (error: Error & { name: string }) => unknown
27 | ): Promise {
28 | const hrefs = await this.selectActorsMentionedByNoteId(id, signal, recover);
29 | return hrefs.map(href => new Mention({ repository: this, href }));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/lib/repository/pg.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import repository from '../test/repository';
19 |
20 | describe('query', () => {
21 | test('aborts if AbortSignal says already aborted', () => {
22 | const controller = new AbortController;
23 | const { pg } = repository;
24 | const recovery = {};
25 | const { signal } = controller;
26 |
27 | controller.abort();
28 |
29 | return expect(pg.query('SELECT pg_wait(9)', signal, ({ name }) => {
30 | expect(name).toBe('AbortError');
31 | return recovery;
32 | })).rejects.toBe(recovery);
33 | });
34 |
35 | test('aborts query', () => {
36 | const controller = new AbortController;
37 | const { pg } = repository;
38 | const recovery = {};
39 | const { signal } = controller;
40 |
41 | const promise = pg.query('SELECT pg_wait(9)', signal, ({ name }) => {
42 | expect(name).toBe('AbortError');
43 | return recovery;
44 | });
45 |
46 | controller.abort();
47 | return expect(promise).rejects.toBe(recovery);
48 | });
49 |
50 | test('queries', async () => {
51 | const { pg } = repository;
52 | const recover = jest.fn();
53 | const { signal } = new AbortController;
54 |
55 | await expect(pg.query('SELECT', signal, recover))
56 | .resolves.toHaveProperty('command', 'SELECT');
57 |
58 | expect(recover).not.toHaveBeenCalled();
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/src/lib/repository/subscribers.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Repository from '.';
18 |
19 | export type Listen = (channel: string, message: string) => unknown;
20 |
21 | export default class {
22 | async subscribe(this: Repository, channel: string, listen: Listen) {
23 | if (this.listeners[channel]) {
24 | this.listeners[channel].add(listen);
25 | } else {
26 | this.listeners[channel] = new Set([listen]);
27 |
28 | const subscription = this.redis.subscriber.subscribe(channel);
29 |
30 | subscription.catch(() => {
31 | if (this.listeners[channel].size <= 1) {
32 | delete this.listeners[channel];
33 | } else {
34 | this.listeners[channel].delete(listen);
35 | }
36 | });
37 |
38 | return subscription;
39 | }
40 | }
41 |
42 | async unsubscribe(this: Repository, channel: string, listen: Listen) {
43 | if (this.listeners[channel].size <= 1) {
44 | delete this.listeners[channel];
45 |
46 | return this.redis.subscriber.unsubscribe(channel);
47 | }
48 |
49 | this.listeners[channel].delete(listen);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/lib/repository/syslog.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { format } from 'util';
18 |
19 | /*
20 | RFC 5424 - The Syslog Protocol
21 | 6.2.1. PRI
22 | https://tools.ietf.org/html/rfc5424#section-6.2.1
23 | */
24 |
25 | export default class Syslog {
26 | private readonly console: Console;
27 |
28 | constructor(console: Console) {
29 | this.console = console;
30 | }
31 |
32 | error(string: unknown, ...args: unknown[]) {
33 | this.console.error(format(string, ...args).replace(/^/gm, '<3>'));
34 | }
35 |
36 | warn(string: unknown, ...args: unknown[]) {
37 | this.console.warn(format(string, ...args).replace(/^/gm, '<4>'));
38 | }
39 |
40 | info(string: unknown, ...args: unknown[]) {
41 | this.console.info(format(string, ...args).replace(/^/gm, '<6>'));
42 | }
43 |
44 | debug(string: unknown, ...args: unknown[]) {
45 | this.console.debug(format(string, ...args).replace(/^/gm, '<7>'));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/lib/repository/unlinked_documents.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import Repository from '.';
19 |
20 | export default class {
21 | async deleteUnlinkedDocumentsByIds(
22 | this: Repository,
23 | ids: string[],
24 | signal: AbortSignal,
25 | recover: (error: Error & { name: string }) => unknown
26 | ) {
27 | await this.pg.query({
28 | name: 'deleteUnlinkedDocumentsByIds',
29 | text: 'DELETE FROM unlinked_documents WHERE id = ANY($1)',
30 | values: [ids]
31 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
32 | }
33 |
34 | async selectUnlinkedDocuments(
35 | this: Repository,
36 | signal: AbortSignal,
37 | recover: (error: Error & { name: string }) => unknown
38 | ) {
39 | const { rows } = await this.pg.query({
40 | name: 'selectUnlinkedDocuments',
41 | text: 'SELECT * FROM unlinked_documents'
42 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
43 |
44 | return rows as { id: string; uuid: string; format: string }[];
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/lib/repository/uris.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import { fabricateRemoteAccount } from '../test/fabricator';
19 | import repository from '../test/repository';
20 | import { unwrap } from '../test/types';
21 |
22 | const { signal } = new AbortController;
23 |
24 | describe('selectURIById', () => {
25 | test('resolves with null if not found', async () => {
26 | const recover = jest.fn();
27 | await expect(repository.selectURIById('0', signal, recover))
28 | .resolves.toBe(null);
29 | expect(recover).not.toHaveBeenCalled();
30 | });
31 | });
32 |
33 | describe('selectAllocatedURI', () => {
34 | test('resolves with null if not found', async () => {
35 | const recover = jest.fn();
36 | await expect(repository.selectAllocatedURI('', signal, recover))
37 | .resolves.toBe(null);
38 | expect(recover).not.toHaveBeenCalled();
39 | });
40 | });
41 |
42 | test('inserts remote account and allows to query the URI of its inbox by ID', async () => {
43 | const recover = jest.fn();
44 |
45 | const account = await fabricateRemoteAccount(
46 | { inbox: { uri: 'https://ReMoTe.إختبار/inbox' } });
47 |
48 | const { id } = unwrap(await account.select('inboxURI', signal, recover));
49 |
50 | await expect(repository.selectURIById(id, signal, recover))
51 | .resolves
52 | .toHaveProperty('uri', 'https://ReMoTe.إختبار/inbox');
53 |
54 | expect(recover).not.toHaveBeenCalled();
55 | });
56 |
57 | test('inserts remote account and allows to query the URI of its inbox', async () => {
58 | const recover = jest.fn();
59 |
60 | const account = await fabricateRemoteAccount(
61 | { inbox: { uri: 'https://ReMoTe.إختبار/inbox' } });
62 |
63 | const { id } = unwrap(await account.select('inboxURI', signal, recover));
64 |
65 | const uri = await repository.selectAllocatedURI(
66 | 'https://ReMoTe.إختبار/inbox', signal, recover);
67 |
68 | expect(recover).not.toHaveBeenCalled();
69 | expect(uri).toHaveProperty('id', id);
70 | expect(uri).toHaveProperty('uri', 'https://ReMoTe.إختبار/inbox');
71 | });
72 |
--------------------------------------------------------------------------------
/src/lib/repository/uris.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import URI from '../tuples/uri';
19 | import Repository from '.';
20 |
21 | export default class {
22 | async selectURIById(
23 | this: Repository,
24 | id: string,
25 | signal: AbortSignal,
26 | recover: (error: Error & { name: string }) => unknown
27 | ): Promise {
28 | const { rows } = await this.pg.query({
29 | name: 'selectURIById',
30 | text: 'SELECT * FROM uris WHERE id = $1',
31 | values: [id]
32 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
33 |
34 | return rows[0] ? new URI({
35 | repository: this,
36 | id,
37 | uri: rows[0].uri,
38 | allocated: rows[0].allocated
39 | }) : null;
40 | }
41 |
42 | async selectAllocatedURI(
43 | this: Repository,
44 | uri: string,
45 | signal: AbortSignal,
46 | recover: (error: Error & { name: string }) => unknown
47 | ): Promise {
48 | const { rows } = await this.pg.query({
49 | name: 'selectAllocatedURI',
50 | text: 'SELECT * FROM uris WHERE uri = $1 AND allocated',
51 | values: [uri]
52 | }, signal, error => error.name == 'AbortError' ? recover(error) : error);
53 |
54 | return rows[0] ? new URI({
55 | repository: this,
56 | id: rows[0].id,
57 | uri,
58 | allocated: rows[0].allocated
59 | }) : null;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/lib/session/actor.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { OrderedCollection } from '../generated_activitystreams';
18 | import { Account } from '../generated_webfinger';
19 | import { postOutbox } from './fetch';
20 | import Session from './types';
21 |
22 | const headers = {
23 | Accept: 'application/activity+json;q=0.9,application/ld+json;q=0.8'
24 | };
25 |
26 | interface Actor {
27 | id: string;
28 | preferredUsername: string;
29 | name: string;
30 | summary: string;
31 | inbox: string;
32 | outbox: OrderedCollection & { readonly id: string } | string;
33 | }
34 |
35 | async function fetchActor({ endpoints, fingerHost }: Session, givenFetch: typeof fetch, acct: string) {
36 | const remote = acct.includes('@');
37 | const encodedAcct = encodeURIComponent(remote ?
38 | acct : `${acct}@${fingerHost}`);
39 |
40 | const finger = await givenFetch(
41 | '/.well-known/webfinger?resource=acct:' + encodedAcct);
42 |
43 | if ([404, 410].includes(finger.status)) {
44 | return null;
45 | }
46 |
47 | const { links } = await finger.json() as Account;
48 | const link = links.find(({ rel }) => rel == 'self');
49 | if (!link) {
50 | return null;
51 | }
52 |
53 | const activityStreams = await (remote ?
54 | givenFetch(endpoints.proxyUrl,
55 | { method: 'POST', headers, body: new URLSearchParams({ id: link.href }) }) :
56 | givenFetch(link.href, { headers }));
57 |
58 | return [404, 410].includes(activityStreams.status) ? null : activityStreams.json();
59 | }
60 |
61 | export { fetchActor as fetch }
62 |
63 | export async function fetchOutbox(givenFetch: typeof fetch, actor: Actor) {
64 | const id = typeof actor.outbox == 'string' ? actor.outbox : actor.outbox.id;
65 | const fetched = await givenFetch(id, {
66 | headers: { Accept: 'application/activity+json;q=0.9,application/ld+json;q=0.8' }
67 | });
68 |
69 | const outbox = await fetched.json();
70 |
71 | outbox.id = id;
72 | actor.outbox = outbox;
73 | }
74 |
75 | export async function follow(session: Session, givenFetch: typeof fetch, { id }: Actor) {
76 | await postOutbox(session, givenFetch, {
77 | '@context': 'https://www.w3.org/ns/activitystreams',
78 | type: 'Follow',
79 | object: id,
80 | });
81 | }
82 |
--------------------------------------------------------------------------------
/src/lib/session/announce.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { postOutbox } from './fetch';
18 | import Session from './types';
19 |
20 | export async function announce(session: Session, givenFetch: typeof fetch, object: string) {
21 | await postOutbox(session, givenFetch, {
22 | '@context': 'https://www.w3.org/ns/activitystreams',
23 | type: 'Announce',
24 | published: new Date,
25 | to: 'https://www.w3.org/ns/activitystreams#Public',
26 | object
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/src/lib/session/fetch.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Session from './types';
18 |
19 | export function postOutbox({ user }: Session, givenFetch: typeof fetch, body: unknown) {
20 | if (!user) {
21 | throw new Error('Not signed in.');
22 | }
23 |
24 | return givenFetch(user.outbox, {
25 | mode: 'cors',
26 | method: 'POST',
27 | credentials: 'same-origin',
28 | headers: { 'Content-Type': 'application/activity+json' },
29 | body: JSON.stringify(body)
30 | });
31 | }
32 |
33 | export function uploadMedia({ user }: Session, givenFetch: typeof fetch, body: FormData) {
34 | if (!user) {
35 | throw new Error('Not signed in.');
36 | }
37 |
38 | return givenFetch(user.endpoints.uploadMedia, {
39 | mode: 'cors',
40 | method: 'POST',
41 | credentials: 'same-origin',
42 | body
43 | });
44 | }
45 |
--------------------------------------------------------------------------------
/src/lib/session/like.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { postOutbox } from './fetch';
18 | import Session from './types';
19 |
20 | export async function like(session: Session, givenFetch: typeof fetch, object: string) {
21 | await postOutbox(session, givenFetch, {
22 | '@context': 'https://www.w3.org/ns/activitystreams',
23 | type: 'Like',
24 | object
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/src/lib/session/note.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { postOutbox, uploadMedia } from './fetch';
18 | import Session from './types';
19 |
20 | export async function create(session: Session, givenFetch: typeof fetch, content: string, document?: Blob) {
21 | const attachment = [];
22 |
23 | if (document) {
24 | const data = new FormData;
25 | data.set('file', document);
26 | const { headers } = await uploadMedia(session, givenFetch, data);
27 | const location = headers.get('Location');
28 |
29 | if (location) {
30 | attachment.push((await (await fetch(location)).json()).object);
31 | }
32 | }
33 |
34 | await postOutbox(session, givenFetch, {
35 | '@context': 'https://www.w3.org/ns/activitystreams',
36 | type: 'Note',
37 | published: new Date,
38 | to: 'https://www.w3.org/ns/activitystreams#Public',
39 | content,
40 | attachment,
41 | tag: []
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/src/lib/session/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { Announce, Endpoints, Key, Note } from '../generated_activitystreams';
18 |
19 | export interface Analytics {
20 | readonly trackingId?: string;
21 | }
22 |
23 | export interface User {
24 | id: string;
25 | preferredUsername: string;
26 | name: string;
27 | summary: string;
28 | inbox: string | (Announce | Note)[];
29 | outbox: string;
30 | type: string;
31 | endpoints: Endpoints;
32 | publicKey: Key;
33 | 'miniverse:salt': string;
34 | }
35 |
36 | export default interface Session {
37 | readonly analytics: Analytics;
38 | readonly endpoints: { readonly proxyUrl: string };
39 | readonly fingerHost: string;
40 | readonly nonce: string;
41 | readonly user: User | null;
42 | }
43 |
--------------------------------------------------------------------------------
/src/lib/test/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | export function unwrap(value: T | null | undefined): T {
18 | expect(value).not.toBe(null);
19 | expect(value).not.toBeUndefined();
20 |
21 | if (value == null) {
22 | throw new Error('Unexpected null.');
23 | }
24 |
25 | return value;
26 | }
27 |
--------------------------------------------------------------------------------
/src/lib/tuples/accept.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Accept as ActivityStreams } from '../generated_activitystreams';
19 | import Follow from './follow';
20 | import Relation, { Reference } from './relation';
21 | import Repository from '../repository';
22 |
23 | interface References {
24 | object: Follow | null;
25 | }
26 |
27 | type Properties = { objectId: string } | { objectId?: string; object: Follow };
28 |
29 | export default class Accept extends Relation {
30 | readonly object?: Reference;
31 | readonly objectId!: string;
32 |
33 | async toActivityStreams(
34 | signal: AbortSignal,
35 | recover: (error: Error & { name?: string }) => unknown
36 | ): Promise {
37 | const object = await this.select('object', signal, recover);
38 |
39 | if (!object) {
40 | throw recover(new Error('object not found.'));
41 | }
42 |
43 | return {
44 | type: 'Accept',
45 | object: await object.toActivityStreams(signal, recover)
46 | };
47 | }
48 |
49 | static async create(
50 | repository: Repository,
51 | object: Follow,
52 | signal: AbortSignal,
53 | recover: (error: Error & { name?: string }) => unknown
54 | ) {
55 | const accept = new this({ object, repository });
56 | const [objectActor, objectObject] = await Promise.all([
57 | object.select('actor', signal, recover),
58 | object.select('object', signal, recover)
59 | ]);
60 |
61 | if (!objectActor) {
62 | throw recover(new Error('object\'s actor not found.'));
63 | }
64 |
65 | if (!objectObject) {
66 | throw recover(new Error('object\'s object not found.'));
67 | }
68 |
69 | if (!objectObject.host && objectActor.host) {
70 | await repository.queue.add({ type: 'accept', objectId: object.id }, { timeout: 16384 });
71 | }
72 |
73 | return accept;
74 | }
75 | }
76 |
77 | Accept.references = {
78 | object: {
79 | query: Accept.withRepository('selectFollowIncludingActorAndObjectById'),
80 | id: 'objectId'
81 | }
82 | };
83 |
--------------------------------------------------------------------------------
/src/lib/tuples/actor/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Base from './base';
18 | import FromParsedActivityStreams from './from_parsed_activitystreams';
19 | import Resolver from './resolver';
20 |
21 | export default class Actor extends Base {}
22 |
23 | for (const Constructor of [FromParsedActivityStreams, Resolver]) {
24 | for (const key of Object.getOwnPropertyNames(Constructor)) {
25 | if (!['length', 'name', 'prototype'].includes(key)) {
26 | (Actor as any)[key] = (Constructor as any)[key];
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/tuples/challenge.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Challenge, { digest } from './challenge';
18 | import repository from '../test/repository';
19 |
20 | const secret = Buffer.from('secret');
21 | const expectedDigest = Buffer.from([
22 | 229, 233, 250, 27, 163, 30, 205, 26, 232, 79, 117, 202, 170, 71, 79, 58,
23 | 102, 63, 5, 244
24 | ]);
25 |
26 | describe('create', () => {
27 | test('creates challenge', async () => {
28 | const challenge = await Challenge.create(repository, secret);
29 |
30 | expect(expectedDigest.equals(challenge.digest)).toBe(true);
31 |
32 | expect(repository.selectChallengeByDigest(expectedDigest))
33 | .resolves
34 | .toBeInstanceOf(Challenge);
35 | });
36 | });
37 |
38 | describe('digest', () => {
39 | test('digests secret', () =>
40 | expect(expectedDigest.equals(digest(secret))).toBe(true));
41 | });
42 |
--------------------------------------------------------------------------------
/src/lib/tuples/challenge.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { createHash } from 'crypto';
18 | import Repository from '../repository';
19 |
20 | export function digest(secret: Buffer): Buffer {
21 | const hash = createHash('sha1');
22 | hash.update(secret);
23 | return hash.digest();
24 | }
25 |
26 | export default class Challenge {
27 | readonly digest: Buffer;
28 |
29 | constructor({ digest }: { readonly digest: Buffer }) {
30 | this.digest = digest;
31 | }
32 |
33 | static create(repository: Repository, secret: Buffer) {
34 | return repository.insertChallenge(digest(secret));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/lib/tuples/cookie.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import { fabricateLocalAccount } from '../test/fabricator';
19 | import repository from '../test/repository';
20 | import Cookie, { digestToken } from './cookie';
21 | import LocalAccount from './local_account';
22 |
23 | const secret = Buffer.from('secret');
24 | const expectedDigest = Buffer.from([
25 | 229, 233, 250, 27, 163, 30, 205, 26, 232, 79, 117, 202, 170, 71, 79, 58,
26 | 102, 63, 5, 244
27 | ]);
28 | const token = 'c2VjcmV0';
29 |
30 | describe('create', () => {
31 | test('creates cookie', async () => {
32 | const account = await fabricateLocalAccount();
33 | const { signal } = new AbortController;
34 | const recover = jest.fn();
35 | const cookie = await Cookie.create(
36 | repository, account, secret, signal, recover);
37 |
38 | expect(expectedDigest.equals(cookie.digest)).toBe(true);
39 |
40 | await expect(repository.selectLocalAccountByDigestOfCookie(
41 | expectedDigest,
42 | signal,
43 | recover
44 | )).resolves.toBeInstanceOf(LocalAccount);
45 |
46 | expect(recover).not.toHaveBeenCalled();
47 | });
48 | });
49 |
50 | describe('digestToken', () => {
51 | test('parses and digests token', () =>
52 | expect(expectedDigest.equals(digestToken(token))).toBe(true));
53 | });
54 |
--------------------------------------------------------------------------------
/src/lib/tuples/cookie.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { BinaryLike, createHash } from 'crypto';
19 | import Repository from '../repository';
20 | import LocalAccount from './local_account';
21 | import Relation, { Reference } from './relation';
22 |
23 | function digest(secret: BinaryLike) {
24 | const hash = createHash('sha1');
25 | hash.update(secret);
26 | return hash.digest();
27 | }
28 |
29 | interface BaseProperties {
30 | digest: Buffer;
31 | }
32 |
33 | interface AccountIdProperties {
34 | accountId: string;
35 | }
36 |
37 | interface AccountProperties {
38 | accountId?: string;
39 | account: LocalAccount;
40 | }
41 |
42 | interface References {
43 | account: LocalAccount | null;
44 | }
45 |
46 | type Properties = BaseProperties & (AccountIdProperties | AccountProperties);
47 |
48 | export function digestToken(token: string) {
49 | return digest(Buffer.from(token, 'base64'));
50 | }
51 |
52 | export function getToken(secret: Buffer) {
53 | return secret.toString('base64');
54 | }
55 |
56 | export default class Cookie extends Relation {
57 | readonly account?: Reference;
58 | readonly accountId!: string;
59 | readonly digest!: Buffer;
60 |
61 | static create(
62 | repository: Repository,
63 | account: LocalAccount,
64 | secret: Buffer,
65 | signal: AbortSignal,
66 | recover: (error: Error & { name: string }) => unknown
67 | ) {
68 | return repository.insertCookie(
69 | { account, digest: digest(secret) },
70 | signal,
71 | recover);
72 | }
73 | }
74 |
75 | Cookie.references = {
76 | account: {
77 | query: Cookie.withRepository('selectLocalAccountById'),
78 | id: 'accountId'
79 | }
80 | };
81 |
--------------------------------------------------------------------------------
/src/lib/tuples/delete.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import ParsedActivityStreams from '../parsed_activitystreams';
18 | import Repository from '../repository';
19 | import Actor from './actor';
20 |
21 | export default class Delete {
22 | static createFromParsedActivityStreams(
23 | repository: Repository,
24 | activity: ParsedActivityStreams,
25 | actor: Actor): Promise;
26 | }
27 |
--------------------------------------------------------------------------------
/src/lib/tuples/delete.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController, AbortSignal } from 'abort-controller';
18 | import ParsedActivityStreams from '../parsed_activitystreams';
19 | import Repository from '../repository';
20 | import { temporaryError } from '../transfer';
21 | import Actor from './actor';
22 |
23 | export const unexpectedType = Symbol();
24 |
25 | export default class Delete {
26 | static async createFromParsedActivityStreams(
27 | repository: Repository,
28 | activity: ParsedActivityStreams,
29 | actor: Actor,
30 | signal: AbortSignal,
31 | recover: (error: Error & {
32 | [temporaryError]?: boolean;
33 | [unexpectedType]?: boolean;
34 | }) => unknown
35 | ) {
36 | const type = await activity.getType(signal, recover);
37 |
38 | if (!type || !type.has('Delete')) {
39 | throw recover(Object.assign(
40 | new Error('Unsupported type. Expected Delete.'),
41 | { [unexpectedType]: true }));
42 | }
43 |
44 | const object = await activity.getObject(signal, recover);
45 | if (!object) {
46 | throw recover(new Error('object unspecified.'));
47 | }
48 |
49 | const id = await object.getId(recover);
50 | if (typeof id != 'string') {
51 | throw recover(new Error('Unsupported id type. Expected string.'));
52 | }
53 |
54 | const uri = await repository.selectAllocatedURI(id, signal, recover);
55 |
56 | if (uri) {
57 | await repository.deleteStatusByUriAndAttributedTo(
58 | uri, actor, signal, recover);
59 |
60 | const documents =
61 | await repository.selectUnlinkedDocuments(signal, recover);
62 | const documentIds = documents.map(({ id }) => id);
63 | const controller = new AbortController;
64 |
65 | await repository.s3.service.deleteObjects({
66 | Bucket: repository.s3.bucket,
67 | Delete: {
68 | Objects: documents.map(({ uuid, format }) =>
69 | ({ Key: `${repository.s3.keyPrefix}${uuid}.${format}` }))
70 | }
71 | }).promise();
72 |
73 | await repository.deleteUnlinkedDocumentsByIds(
74 | documentIds, controller.signal, recover);
75 | }
76 |
77 | return new this;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/lib/tuples/dirty_document.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Repository from '../repository';
18 |
19 | interface Properties {
20 | readonly repository: Repository;
21 | readonly id: number;
22 | readonly uuid: string;
23 | readonly format: string;
24 | }
25 |
26 | export default class DirtyDocument {
27 | readonly repository: Repository;
28 | readonly id: number;
29 | readonly uuid: string;
30 | readonly format: string;
31 |
32 | constructor({ repository, id, uuid, format }: Properties) {
33 | this.repository = repository;
34 | this.id = id;
35 | this.uuid = uuid;
36 | this.format = format;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/lib/tuples/hashtag.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import repository from '../test/repository';
18 | import Hashtag from './hashtag';
19 |
20 | describe('toActivityStreams', () => {
21 | test('resolves with ActivityStreams representation', () => {
22 | const hashtag = new Hashtag({ repository, name: '名前' });
23 |
24 | return expect(hashtag.toActivityStreams())
25 | .resolves
26 | .toEqual({ type: 'Hashtag', name: '名前' });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/lib/tuples/hashtag.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { Hashtag as ActivityStreams } from '../generated_activitystreams';
18 | import Repository from '../repository';
19 |
20 | interface Properties {
21 | readonly repository: Repository;
22 | readonly name: string;
23 | }
24 |
25 | export default class Hashtag {
26 | readonly repository: Repository;
27 | readonly name: string;
28 |
29 | constructor({ repository, name }: Properties) {
30 | this.repository = repository;
31 | this.name = name;
32 | }
33 |
34 | async toActivityStreams(): Promise {
35 | return { type: 'Hashtag', name: this.name };
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/lib/tuples/mention.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import { fabricateLocalAccount } from '../test/fabricator';
19 | import repository from '../test/repository';
20 | import { unwrap } from '../test/types';
21 | import Mention from './mention';
22 |
23 | describe('toActivityStreams', () => {
24 | test('resolves with ActivityStreams representation', async () => {
25 | const recover = jest.fn();
26 | const { signal } = new AbortController;
27 |
28 | const account =
29 | await fabricateLocalAccount({ actor: { username: '行動者' } });
30 |
31 | const mention = new Mention({
32 | repository,
33 | href: unwrap(await account.select('actor', signal, recover))
34 | });
35 |
36 | await expect(mention.toActivityStreams(signal, recover)).resolves.toEqual({
37 | type: 'Mention',
38 | href: 'https://xn--kgbechtv/@%E8%A1%8C%E5%8B%95%E8%80%85'
39 | });
40 |
41 | expect(recover).not.toHaveBeenCalled();
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/src/lib/tuples/mention.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Mention as ActivityStreams } from '../generated_activitystreams';
19 | import Actor from './actor';
20 | import Relation, { Reference } from './relation';
21 |
22 | type Properties = { hrefId: string } | { hrefId?: string; href: Actor };
23 |
24 | interface References {
25 | href: Actor | null;
26 | }
27 |
28 | export default class Mention extends Relation {
29 | readonly href?: Reference;
30 | readonly hrefId!: string;
31 |
32 | async toActivityStreams(
33 | signal: AbortSignal,
34 | recover: (error: Error & { name?: string }) => unknown
35 | ): Promise {
36 | const actor = await this.select('href', signal, recover);
37 | if (!actor) {
38 | throw recover(new Error('href not found.'));
39 | }
40 |
41 | const href = await actor.getUri(signal, recover);
42 | if (!href) {
43 | throw recover(new Error('href\'s uri not found.'));
44 | }
45 |
46 | return { type: 'Mention', href };
47 | }
48 | }
49 |
50 | Mention.references = {
51 | href: { query: Mention.withRepository('selectActorById'), id: 'hrefId' }
52 | };
53 |
--------------------------------------------------------------------------------
/src/lib/tuples/ordered_collection.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import OrderedCollection from './ordered_collection';
19 |
20 | describe('toActivityStreams', () => {
21 | const { signal } = new AbortController;
22 |
23 | test('catches an error which is not AbortError', async () => {
24 | const collection = new OrderedCollection({
25 | orderedItems: [
26 | {
27 | async toActivityStreams(_signal, recover) {
28 | throw recover(new Error);
29 | }
30 | }
31 | ]
32 | });
33 |
34 | const recover = jest.fn();
35 |
36 | await expect(collection.toActivityStreams(signal, recover)).resolves.toEqual({
37 | type: 'OrderedCollection',
38 | orderedItems: []
39 | })
40 |
41 | expect(recover).not.toHaveBeenCalled();
42 | });
43 |
44 | test('propagates AbortError', async () => {
45 | const collection = new OrderedCollection({
46 | orderedItems: [
47 | {
48 | async toActivityStreams(_signal, recover) {
49 | throw recover(Object.assign(new Error, { name: 'AbortError' }));
50 | }
51 | }
52 | ]
53 | });
54 |
55 | const recovery = {};
56 |
57 | await expect(collection.toActivityStreams(signal, () => recovery))
58 | .rejects.toBe(recovery);
59 | });
60 |
61 | test('returns ActivityStreams representation', async () => {
62 | const item = {
63 | type: 'Announce',
64 | id: 'https://Id.إختبار/',
65 | published: new Date,
66 | object: 'https://ObJeCt.إختبار/'
67 | };
68 |
69 | const collection = new OrderedCollection({
70 | orderedItems: [
71 | {
72 | async toActivityStreams() {
73 | return item;
74 | }
75 | }
76 | ]
77 | });
78 |
79 | const recover = jest.fn();
80 | const body = collection.toActivityStreams(signal, recover);
81 |
82 | await Promise.all([
83 | expect(body).resolves.toHaveProperty('type', 'OrderedCollection'),
84 | expect(body).resolves.toHaveProperty('orderedItems', [item])
85 | ]);
86 |
87 | expect(recover).not.toHaveBeenCalled();
88 | });
89 | });
90 |
--------------------------------------------------------------------------------
/src/lib/tuples/ordered_collection.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Any, OrderedCollection } from '../generated_activitystreams';
19 |
20 | const recovery = {};
21 |
22 | interface ToActivityStreams {
23 | toActivityStreams(
24 | signal: AbortSignal,
25 | recover: (error: Error) => unknown
26 | ): Promise;
27 | }
28 |
29 | export default class {
30 | readonly orderedItems: ToActivityStreams[];
31 |
32 | constructor({ orderedItems }: { readonly orderedItems: ToActivityStreams[] }) {
33 | this.orderedItems = orderedItems;
34 | }
35 |
36 | async toActivityStreams(
37 | signal: AbortSignal,
38 | recover: (error: Error & { name: string }) => unknown
39 | ): Promise {
40 | return {
41 | type: 'OrderedCollection',
42 | orderedItems: (await Promise.all(this.orderedItems.map(
43 | item => item.toActivityStreams(
44 | signal,
45 | error => error.name == 'AbortError' ? recover(error) : recovery
46 | ).catch(error => {
47 | if (error != recovery) {
48 | throw error;
49 | }
50 |
51 | return recovery;
52 | })
53 | ))).filter(item => item != recovery) as Any[]
54 | };
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/lib/tuples/ordered_collection_page.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import OrderedCollectionPage from './ordered_collection_page';
19 |
20 | describe('toActivityStreams', () => {
21 | const { signal } = new AbortController;
22 |
23 | test('catches an error which is not AbortError', async () => {
24 | const collection = new OrderedCollectionPage({
25 | orderedItems: [
26 | {
27 | async toActivityStreams(_signal, recover) {
28 | throw recover(new Error);
29 | }
30 | }
31 | ]
32 | });
33 |
34 | const recover = jest.fn();
35 |
36 | await expect(collection.toActivityStreams(signal, recover)).resolves.toEqual({
37 | type: 'OrderedCollectionPage',
38 | orderedItems: []
39 | })
40 |
41 | expect(recover).not.toHaveBeenCalled();
42 | });
43 |
44 | test('propagates AbortError', async () => {
45 | const collection = new OrderedCollectionPage({
46 | orderedItems: [
47 | {
48 | async toActivityStreams(_signal, recover) {
49 | throw recover(Object.assign(new Error, { name: 'AbortError' }));
50 | }
51 | }
52 | ]
53 | });
54 |
55 | const recovery = {};
56 |
57 | await expect(collection.toActivityStreams(signal, () => recovery))
58 | .rejects.toBe(recovery);
59 | });
60 |
61 | test('returns ActivityStreams representation', async () => {
62 | const item = {
63 | type: 'Announce',
64 | id: 'https://Id.إختبار/',
65 | published: new Date,
66 | object: 'https://ObJeCt.إختبار/'
67 | };
68 |
69 | const collection = new OrderedCollectionPage({
70 | orderedItems: [
71 | {
72 | async toActivityStreams() {
73 | return item;
74 | }
75 | }
76 | ]
77 | });
78 |
79 | const recover = jest.fn();
80 | const body = collection.toActivityStreams(signal, recover);
81 |
82 | await Promise.all([
83 | expect(body).resolves.toHaveProperty('type', 'OrderedCollectionPage'),
84 | expect(body).resolves.toHaveProperty('orderedItems', [item])
85 | ]);
86 | });
87 | });
88 |
--------------------------------------------------------------------------------
/src/lib/tuples/ordered_collection_page.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Any, OrderedCollectionPage } from '../generated_activitystreams';
19 |
20 | const recovery = {};
21 |
22 | interface ToActivityStreams {
23 | toActivityStreams(
24 | signal: AbortSignal,
25 | recover: (error: Error & { name?: string }) => unknown
26 | ): Promise;
27 | }
28 |
29 | export default class {
30 | readonly orderedItems: ToActivityStreams[];
31 |
32 | constructor({ orderedItems }: { readonly orderedItems: ToActivityStreams[] }) {
33 | this.orderedItems = orderedItems;
34 | }
35 |
36 | async toActivityStreams(
37 | signal: AbortSignal,
38 | recover: (error: Error & { name: string }) => unknown
39 | ): Promise {
40 | return {
41 | type: 'OrderedCollectionPage',
42 | orderedItems: (await Promise.all(this.orderedItems.map(
43 | item => item.toActivityStreams(
44 | signal,
45 | error => error.name == 'AbortError' ? recover(error) : recovery
46 | ).catch(error => {
47 | if (error != recovery) {
48 | throw error;
49 | }
50 |
51 | return recovery;
52 | })
53 | ))).filter(item => item != recovery) as Any[]
54 | };
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/lib/tuples/status.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import { fabricateLocalAccount, fabricateNote } from '../test/fabricator';
19 | import { unwrap } from '../test/types';
20 |
21 | describe('getUri', () => {
22 | const { signal } = new AbortController;
23 |
24 | test('resolves with local URI', async () => {
25 | const recover = jest.fn();
26 |
27 | const account =
28 | await fabricateLocalAccount({ actor: { username: 'attributed to' } });
29 |
30 | const actor = unwrap(await account.select('actor', signal, recover));
31 | const note = await fabricateNote({ status: { actor } });
32 | const status = unwrap(await note.select('status', signal, recover));
33 |
34 | await expect(status.getUri(signal, recover)).resolves.toMatch(/^https:\/\/xn--kgbechtv\/@attributed%20to\//);
35 | expect(recover).not.toHaveBeenCalled();
36 | });
37 |
38 | test('resolves with remote URI when it is resolved remote status', async () => {
39 | const recover = jest.fn();
40 |
41 | const note = await fabricateNote(
42 | { status: { uri: 'https://NoTe.xn--kgbechtv/' } });
43 |
44 | const status = unwrap(await note.select('status', signal, recover));
45 |
46 | await expect(status.getUri(signal, recover)).resolves.toBe('https://NoTe.xn--kgbechtv/');
47 | expect(recover).not.toHaveBeenCalled();
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/lib/tuples/status.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { domainToASCII } from 'url';
19 | import Actor from './actor';
20 | import Announce from './announce';
21 | import Note from './note';
22 | import Relation, { Reference } from './relation';
23 | import URI, { encodeSegment } from './uri';
24 |
25 | interface BaseProperties {
26 | id: string;
27 | published: Date;
28 | }
29 |
30 | interface ActorIdProperties {
31 | actorId: string;
32 | }
33 |
34 | interface ActorProperties {
35 | actorId?: string;
36 | actor: Actor;
37 | }
38 |
39 | interface References {
40 | extension: Announce | Note | null;
41 | actor: Actor | null;
42 | uri: URI | null;
43 | }
44 |
45 | type Properties = BaseProperties & (ActorIdProperties | ActorProperties);
46 |
47 | export default class Status extends Relation {
48 | readonly id!: string;
49 | readonly uri?: Reference;
50 | readonly actorId!: string;
51 | readonly actor?: Reference;
52 | readonly extension?: Reference;
53 | readonly published!: Date;
54 |
55 | async getUri(
56 | signal: AbortSignal,
57 | recover: (error: Error & { name?: string }) => unknown
58 | ) {
59 | const actor = await this.select('actor', signal, recover);
60 | if (!actor) {
61 | throw recover(new Error('actor not found.'));
62 | }
63 |
64 | if (actor.host) {
65 | const uri = await this.select('uri', signal, recover);
66 | if (!uri) {
67 | throw recover(new Error('uri not found.'));
68 | }
69 |
70 | return uri.uri;
71 | }
72 |
73 | const repositoryHost = domainToASCII(this.repository.host);
74 | const encodedUsername = encodeSegment(actor.username);
75 |
76 | return `https://${repositoryHost}/@${encodedUsername}/${this.id}`;
77 | }
78 | }
79 |
80 | Status.references = {
81 | extension: { id: 'id', inverseOf: 'status' },
82 | actor: { id: 'actorId', query: Status.withRepository('selectActorById') },
83 | uri: { id: 'id', query: Status.withRepository('selectURIById') }
84 | };
85 |
--------------------------------------------------------------------------------
/src/lib/tuples/uri.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { encodeSegment, encodeAcctUserpart, normalizeHost } from './uri';
18 |
19 | /*
20 | RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax
21 | 3.3 Path
22 | https://tools.ietf.org/html/rfc3986#section-3.3
23 | */
24 | describe('encodeSegment', () => {
25 | for (const [decoded, encoded] of [
26 | ['//', '%2F%2F'], ['??', '%3F%3F'], ['##', '%23%23']
27 | ]) {
28 | test(`encodes ${decoded} to ${encoded}`, () =>
29 | expect(encodeSegment(decoded)).toBe(encoded));
30 | }
31 |
32 | for (const character of 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&\'()*+,;=:@') {
33 | test(`does not encode ${character}`, () =>
34 | expect(encodeSegment(character)).toBe(character));
35 | }
36 | });
37 |
38 | /*
39 | RFC 7565 - The 'acct' URI Scheme
40 | 7. IANA Considerations
41 | https://tools.ietf.org/html/rfc7565#section-7
42 | */
43 | describe('encodeAcctUserpart', () => {
44 | for (const [decoded, encoded] of [[':', '%3A'], ['@', '%40']]) {
45 | test(`encodes ${decoded} to ${encoded}`, () =>
46 | expect(encodeAcctUserpart(decoded)).toBe(encoded));
47 | }
48 |
49 | for (const character of 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~!$&\'()*+,;=') {
50 | test(`does not encode ${character}`, () =>
51 | expect(encodeAcctUserpart(character)).toBe(character));
52 | }
53 | });
54 |
55 | describe('normalizeHost', () => {
56 | test('encodes in IDNA', () =>
57 | expect(normalizeHost('إختبار')).toBe('xn--kgbechtv'));
58 |
59 | test('replaces uppercase ASCII characters with lowercase characters', () =>
60 | expect(normalizeHost('EXAMPLE.COM')).toBe('example.com'));
61 | });
62 |
--------------------------------------------------------------------------------
/src/lib/tuples/uri.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { domainToASCII } from 'url';
18 | import Repository from '../repository';
19 |
20 | interface Properties {
21 | readonly repository: Repository;
22 | readonly id: string;
23 | readonly uri: string;
24 | readonly allocated: boolean;
25 | }
26 |
27 | export function encodeSegment(segment: string) {
28 | return encodeURI(segment)
29 | .replace(/\//g, '%2F')
30 | .replace(/\?/g, '%3F')
31 | .replace(/#/g, '%23');
32 | }
33 |
34 | export function encodeAcctUserpart(userpart: string) {
35 | return encodeSegment(userpart).replace(/:/g, '%3A').replace(/@/g, '%40');
36 | }
37 |
38 | export function normalizeHost(host: string) {
39 | return domainToASCII(host).toLowerCase();
40 | }
41 |
42 | export default class {
43 | readonly id!: string;
44 | readonly uri!: string;
45 | readonly allocated!: boolean;
46 |
47 | constructor(properties: Properties) {
48 | Object.assign(this, properties);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/options.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import S3 = require('aws-sdk/clients/s3');
18 |
19 | if (!process.env.AWS_S3_BUCKET) {
20 | throw new Error('AWS_S3_BUCKET environment variable not specified.');
21 | }
22 |
23 | if (!process.env.AWS_S3_URL_PREFIX) {
24 | throw new Error('AWS_S3_URL_PREFIX environment variable not specified.');
25 | }
26 |
27 | if (!process.env.HOST) {
28 | throw new Error('HOST environment variable not specified.');
29 | }
30 |
31 | export default {
32 | analytics: { trackingId: process.env.ANALYTICS_TRACKING_ID },
33 | console,
34 | content: {
35 | frame: { sourceList: process.env.CONTENT_FRAME_SOURCE_LIST },
36 | image: { sourceList: process.env.CONTENT_IMAGE_SOURCE_LIST },
37 | script: {
38 | sourceList: process.env.CONTENT_SCRIPT_SOURCE_LIST,
39 | sources: process.env.CONTENT_SCRIPT_SOURCES ?
40 | process.env.CONTENT_SCRIPT_SOURCES.split(';') : []
41 | }
42 | },
43 | fingerHost: process.env.FINGER_HOST,
44 | host: process.env.HOST,
45 | pg: {
46 | host: process.env.PGHOST || 'localhost',
47 | port: process.env.PGPORT && parseInt(process.env.PGPORT) || 5432
48 | },
49 | redis: {
50 | prefix: process.env.REDIS_PREFIX,
51 | url: process.env.REDIS_URL
52 | },
53 | s3: {
54 | service: new S3({
55 | endpoint: process.env.AWS_ENDPOINT,
56 | accessKeyId: process.env.AWS_ACCESS_KEY_ID,
57 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
58 | region: process.env.AWS_REGION,
59 | signatureVersion: process.env.AWS_SIGNATURE_VERSION,
60 | s3BucketEndpoint: Boolean(process.env.AWS_S3_BUCKET_ENDPOINT),
61 | s3ForcePathStyle: Boolean(process.env.AWS_S3_FORCE_PATH_STYLE),
62 | }),
63 | bucket: process.env.AWS_S3_BUCKET,
64 | keyPrefix: process.env.AWS_S3_KEY_PREFIX || '',
65 | urlPrefix: process.env.AWS_S3_URL_PREFIX
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/src/processor.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Repository from './lib/repository';
18 | import options from './options';
19 | import processJobs from './subsystems/processor';
20 |
21 | const repository = new Repository(options);
22 |
23 | function terminate() {
24 | repository.end();
25 | }
26 |
27 | process.on('SIGINT', terminate);
28 | process.on('SIGTERM', terminate);
29 |
30 | processJobs(repository);
31 |
--------------------------------------------------------------------------------
/src/routes/.well-known/webfinger.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Actor from '../../lib/tuples/actor';
18 | import { normalizeHost } from '../../lib/tuples/uri';
19 | import secure from '../_secure';
20 |
21 | const recovery = {};
22 |
23 | export const get = secure(async ({ query }, response) => {
24 | const lowerResource = query.resource;
25 | const parsed = /(?:acct:)?(.*)@(.*)/.exec(lowerResource);
26 |
27 | if (!parsed) {
28 | response.sendStatus(404);
29 | return;
30 | }
31 |
32 | const [, userpart, host] = parsed;
33 | const { repository } = response.app.locals;
34 | const { signal } = response.locals;
35 | let actor;
36 |
37 | try {
38 | if (host == normalizeHost(repository.fingerHost)) {
39 | actor = await repository.selectActorByUsernameAndNormalizedHost(
40 | decodeURI(userpart), null, signal, () => recovery);
41 | } else {
42 | actor = await Actor.fromUsernameAndNormalizedHost(
43 | repository,
44 | decodeURI(userpart),
45 | host,
46 | signal,
47 | () => recovery);
48 | }
49 |
50 | if (!actor) {
51 | response.sendStatus(404);
52 | return;
53 | }
54 |
55 | const account = await actor.select('account', signal, () => recovery);
56 | if (!account) {
57 | response.sendStatus(404);
58 | return;
59 | }
60 |
61 | response.json(await account.toWebFinger(signal, _error => recovery));
62 | } catch (error) {
63 | if (error == recovery) {
64 | response.sendStatus(500);
65 | return;signal
66 | }
67 |
68 | throw error;
69 | }
70 | });
71 |
--------------------------------------------------------------------------------
/src/routes/@[acct]/[id].ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { normalizeHost } from '../../lib/tuples/uri';
18 | import secure from '../_secure';
19 | import sendActivityStreams from '../_send_activitystreams';
20 |
21 | const recovery = {};
22 |
23 | export const get = secure(async (request, response) => {
24 | const { params } = request;
25 | const { repository } = response.app.locals;
26 | const { signal } = response.locals;
27 | const acct = decodeURIComponent(params.acct);
28 | const atIndex = acct.lastIndexOf('@');
29 | let extension;
30 | let actor;
31 | let username;
32 | let normalizedHost;
33 |
34 | if (atIndex < 0) {
35 | username = acct;
36 | normalizedHost = null;
37 | } else {
38 | username = acct.slice(0, atIndex);
39 | normalizedHost = normalizeHost(acct.slice(atIndex + 1));
40 | }
41 |
42 | try {
43 | const status = await repository.selectStatusIncludingExtensionById(params.id, signal, () => recovery);
44 | if (!status) {
45 | response.sendStatus(404);
46 | return;
47 | }
48 |
49 | [extension, actor] = await Promise.all([
50 | status.select('extension', signal, () => recovery),
51 | status.select('actor', signal, () => recovery)
52 | ]);
53 | if (!extension || !actor ||
54 | actor.username != username ||
55 | (actor.host != normalizedHost &&
56 | actor.host &&
57 | normalizeHost(actor.host) != normalizedHost)) {
58 | response.sendStatus(404);
59 | return;
60 | }
61 | } catch (error) {
62 | if (error == recovery) {
63 | response.sendStatus(422);
64 | return;
65 | }
66 |
67 | throw error;
68 | }
69 |
70 | await sendActivityStreams(response, extension);
71 | });
72 |
--------------------------------------------------------------------------------
/src/routes/@[acct]/inbox.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { text } from 'body-parser';
18 | import { Request, Response } from 'express';
19 | import { parseRequest } from 'http-signature';
20 | import { HttpSignatureError } from 'http-signature/lib/utils';
21 | import { promisify } from 'util';
22 | import secure from '../_secure';
23 |
24 | const setBody = promisify(text({
25 | type: ['application/activity+json', 'application/ld+json']
26 | })) as unknown as (request: Request, response: Response) => Promise;
27 |
28 | /*
29 | ActivityPub
30 | 5.2 Inbox
31 | https://www.w3.org/TR/activitypub/#inbox
32 | > The inboxes of actors on federated servers accepts HTTP POST requests, with
33 | > behaviour described in Delivery.
34 | */
35 | export const post = secure(async (request, response) => {
36 | let signature;
37 |
38 | request.headers.authorization = 'Signature ' + request.headers.signature;
39 |
40 | try {
41 | signature = parseRequest(request);
42 | } catch (error) {
43 | if (error instanceof HttpSignatureError) {
44 | response.sendStatus(403);
45 | return;
46 | }
47 |
48 | throw error;
49 | }
50 |
51 | await setBody(request, response);
52 |
53 | await response.app.locals.repository.queue.add({
54 | type: 'processInbox',
55 | signature,
56 | body: request.body
57 | }, { timeout: 16384 });
58 |
59 | response.sendStatus(202);
60 | });
61 |
--------------------------------------------------------------------------------
/src/routes/@[acct]/index.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
24 | {/each}
25 |
26 |
46 |
54 |
--------------------------------------------------------------------------------
/src/routes/@[acct]/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { mediaType } from '@hapi/accept';
18 | import Actor from '../../lib/tuples/actor';
19 | import { normalizeHost } from '../../lib/tuples/uri';
20 | import secure from '../_secure';
21 | import sendActivityStreams from '../_send_activitystreams';
22 |
23 | const recovery = {};
24 |
25 | export const get = secure(async ({ params, headers }, response, next) => {
26 | /*
27 | ActivityPub
28 | https://www.w3.org/TR/activitypub/
29 | > Servers MAY use HTTP content negotiation as defined in [RFC7231] to select
30 | > the type of data to return in response to a request, but MUST present the
31 | > ActivityStreams object representation in response to
32 | > application/ld+json; profile="https://www.w3.org/ns/activitystreams",
33 | > and SHOULD also present the ActivityStreams representation in response to
34 | > application/activity+json as well.
35 | */
36 | /*
37 | Avoid accepts method of express.Response because parameters such as
38 | "profile" confuses it.
39 | */
40 | const accepted = mediaType(headers.accept, [
41 | 'text/html',
42 | 'application/activity+json',
43 | 'application/ld+json'
44 | ]);
45 |
46 | if (!accepted || accepted.startsWith('text/html')) {
47 | next();
48 | return;
49 | }
50 |
51 | const acct = decodeURIComponent(params.acct);
52 | const atIndex = acct.lastIndexOf('@');
53 | let actor;
54 | let username;
55 | let normalizedHost;
56 |
57 | if (atIndex < 0) {
58 | username = acct;
59 | normalizedHost = null;
60 | } else {
61 | username = acct.slice(0, atIndex);
62 | normalizedHost = normalizeHost(acct.slice(atIndex + 1));
63 | }
64 |
65 | try {
66 | actor = await Actor.fromUsernameAndNormalizedHost(
67 | response.app.locals.repository,
68 | username,
69 | normalizedHost,
70 | response.locals.signal,
71 | () => recovery);
72 | } catch (error) {
73 | if (error == recovery) {
74 | response.sendStatus(422);
75 | return;
76 | }
77 |
78 | throw error;
79 | }
80 |
81 | if (actor) {
82 | await sendActivityStreams(response, actor);
83 | } else {
84 | response.sendStatus(404);
85 | }
86 | });
87 |
--------------------------------------------------------------------------------
/src/routes/[uuid].ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2019 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Create from '../lib/tuples/create';
18 | import secure from './_secure';
19 | import sendActivityStreams from './_send_activitystreams';
20 |
21 | const recovery = {};
22 |
23 | export const get = secure(async ({ params }, response) => {
24 | const { repository } = response.app.locals;
25 | let object;
26 |
27 | try {
28 | object = await repository.selectDocumentByUUID(
29 | params.uuid, response.locals.signal, () => recovery);
30 | } catch (error) {
31 | if (error != recovery) {
32 | throw error;
33 | }
34 |
35 | response.sendStatus(500);
36 | return;
37 | }
38 |
39 | if (!object) {
40 | response.sendStatus(404);
41 | return;
42 | }
43 |
44 | await sendActivityStreams(response, new Create({ repository, object }));
45 | });
46 |
--------------------------------------------------------------------------------
/src/routes/_dashboard.svelte:
--------------------------------------------------------------------------------
1 |
16 |
17 |
This site sends various data potentially private to third party. Check
23 | loaded scripts and block them if you don't want.
24 | {/if}
25 |
39 |
--------------------------------------------------------------------------------
/src/routes/_secure.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { Request, NextFunction } from 'express';
18 | import { Response } from '../subsystems/server';
19 |
20 | interface Handler {
21 | (request: Request, response: Response, next: NextFunction): unknown;
22 | }
23 |
24 | export default (handle: Handler) => async (request: Request, response: Response, next: NextFunction) => {
25 | try {
26 | await handle(request, response, next);
27 | } catch (error) {
28 | response.app.locals.repository.console.error(error);
29 | response.sendStatus(500);
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/src/routes/_send_activitystreams.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Response } from 'express';
19 |
20 | const recovery = {};
21 |
22 | export default async (response: Response, object: {
23 | toActivityStreams(
24 | signal: AbortSignal,
25 | recover: (error: Error) => unknown
26 | ): Promise<{ [key: string]: unknown }>;
27 | }) => {
28 | let message;
29 |
30 | try {
31 | message = await object.toActivityStreams(response.locals.signal, () => recovery);
32 | } catch (error) {
33 | if (error == recovery) {
34 | response.sendStatus(500);
35 | return;
36 | }
37 |
38 | throw error;
39 | }
40 |
41 | message['@context'] = [
42 | 'https://miniverse.social/ns',
43 | 'https://w3id.org/security/v1',
44 | 'https://www.w3.org/ns/activitystreams'
45 | ];
46 |
47 | /*
48 | Mastodon requires Content-Type to be application/activity+json:
49 | Hook up URL-based resource look-up to ActivityPub (#4589) · tootsuite/mastodon@4e75f0d
50 | https://github.com/tootsuite/mastodon/commit/4e75f0d88932511ad154773f4c77a485367ed36c#diff-328eab87b2768b5bd2b03f27e89a7dceR32
51 | Be aware that it does not accept the following type because of a bug:
52 | application/ld+json; profile="https://www.w3.org/ns/activitystreams"
53 | */
54 | response.set('Content-Type', 'application/activity+json');
55 |
56 | response.send(message);
57 | };
58 |
--------------------------------------------------------------------------------
/src/routes/api/_cookie.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { randomBytes } from 'crypto';
18 | import { Response } from 'express';
19 | import { promisify } from 'util';
20 | import Cookie, { getToken } from '../../lib/tuples/cookie';
21 | import LocalAccount from '../../lib/tuples/local_account';
22 | import Repository from '../../lib/repository';
23 |
24 | const promisifiedRandomBytes = promisify(randomBytes);
25 | const recovery = {};
26 |
27 | export default async function(repository: Repository, account: LocalAccount, response: Response) {
28 | const secret = await promisifiedRandomBytes(64);
29 |
30 | await Cookie.create(
31 | repository,
32 | account,
33 | secret,
34 | response.locals.signal,
35 | () => recovery
36 | ).catch(error => {
37 | if (error == recovery) {
38 | response.sendStatus(500);
39 | return;
40 | }
41 |
42 | throw error;
43 | });
44 |
45 | response.cookie('miniverse', getToken(secret), {
46 | httpOnly: true,
47 | sameSite: 'Lax',
48 | secure: true
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/src/routes/api/events.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import OrderedCollectionPage from '../../lib/tuples/ordered_collection_page';
18 | import secure from '../_secure';
19 |
20 | const recovery = {};
21 |
22 | export const get = secure(async (request, response) => {
23 | const { repository } = response.app.locals;
24 | const { signal, user } = response.locals;
25 | let resolved;
26 |
27 | if (!user) {
28 | response.sendStatus(403);
29 | return;
30 | }
31 |
32 | response.setHeader('Content-Type', 'text/event-stream');
33 | response.setHeader('Transfer-Encoding', 'chunked');
34 |
35 | try {
36 | const statuses = await user.select('inbox', signal, () => recovery);
37 |
38 | const initialCollection = new OrderedCollectionPage({
39 | orderedItems: (await Promise.all(statuses
40 | .reverse()
41 | .map(status => status.select('extension', signal, () => recovery))))
42 | .filter(Boolean as unknown as (t: T | null) => t is T)
43 | });
44 |
45 | resolved = await initialCollection.toActivityStreams(
46 | signal, () => recovery) as { [key: string]: unknown };
47 | } catch (error) {
48 | if (error == recovery) {
49 | response.sendStatus(500);
50 | return;
51 | }
52 |
53 | throw error;
54 | }
55 |
56 | const subscribedChannel = repository.getInboxChannel(user);
57 |
58 | function listen(_publishedChannel: string, message: string) {
59 | response.write(`data:{"@context":"https://www.w3.org/ns/activitystreams","type":"OrderedCollectionPage","orderedItems":[${message}]}\n\n`);
60 | }
61 |
62 | resolved['@context'] = 'https://www.w3.org/ns/activitystreams';
63 | response.write(`data:${JSON.stringify(resolved)}\n\n`);
64 |
65 | await repository.subscribe(subscribedChannel, listen);
66 | const heartbeat = setInterval(() => response.write(':\n'), 16384);
67 |
68 | request.on('close', () => {
69 | clearInterval(heartbeat);
70 | repository.unsubscribe(subscribedChannel, listen);
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/src/routes/api/proxy.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { Request, Response, urlencoded } from 'express';
18 | import { promisify } from 'util';
19 | import ParsedActivityStreams, {
20 | noHost
21 | } from '../../lib/parsed_activitystreams';
22 | import Actor from '../../lib/tuples/actor';
23 | import secure from '../_secure';
24 | import sendActivityStreams from '../_send_activitystreams';
25 |
26 | const recovery = {};
27 | const setBody = promisify(urlencoded({ extended: false })) as
28 | unknown as
29 | (request: Request, response: Response) => Promise;
30 |
31 | export const post = secure(async (request, response) => {
32 | await setBody(request, response);
33 |
34 | const { body } = request;
35 | const { repository } = response.app.locals;
36 | const parsed = new ParsedActivityStreams(repository, body.id, noHost);
37 | let actor;
38 |
39 | try {
40 | actor = await Actor.fromParsedActivityStreams(repository, parsed, response.locals.signal, () => recovery);
41 | } catch (error) {
42 | if (error == recovery) {
43 | response.sendStatus(500);
44 | return;
45 | }
46 |
47 | throw error;
48 | }
49 |
50 | if (actor) {
51 | await sendActivityStreams(response, actor);
52 | } else {
53 | response.sendStatus(404);
54 | }
55 | });
56 |
--------------------------------------------------------------------------------
/src/routes/api/signup.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { Request, Response } from 'express';
18 | import { raw } from 'body-parser';
19 | import { promisify } from 'util';
20 | import LocalAccount from '../../lib/tuples/local_account';
21 | import secure from '../_secure';
22 | import cookie from './_cookie';
23 |
24 | const recovery = {};
25 | const setBody = promisify(raw()) as
26 | unknown as
27 | (request: Request, response: Response) => Promise;
28 |
29 | export const post = secure(async (request, response) => {
30 | await setBody(request, response);
31 |
32 | const { body } = request;
33 | const { repository } = response.app.locals;
34 | const salt = body.slice(0, 64);
35 | const serverKey = body.slice(64, 96);
36 | const storedKey = body.slice(96, 128);
37 | const username = body.slice(128).toString();
38 | let account;
39 |
40 | try {
41 | account = await LocalAccount.create(repository, {
42 | actor: {
43 | username,
44 | name: '',
45 | summary: ''
46 | },
47 | admin: false,
48 | salt,
49 | serverKey,
50 | storedKey
51 | }, response.locals.signal, () => recovery);
52 | } catch (error) {
53 | if (error == recovery) {
54 | response.sendStatus(422);
55 | return;
56 | }
57 |
58 | throw error;
59 | }
60 |
61 | await cookie(repository, account, response);
62 | response.sendStatus(204);
63 | });
64 |
--------------------------------------------------------------------------------
/src/routes/index.svelte:
--------------------------------------------------------------------------------
1 |
16 | {#if $session.user}
17 |
18 | {:else}
19 |
20 | {/if}
21 |
28 |
--------------------------------------------------------------------------------
/src/sapper.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@sapper/app' {
2 | function start(options: { target: HTMLElement }): void;
3 | }
4 |
5 | declare module '@sapper/server' {
6 | import { Handler, Request, Response } from 'express';
7 | import Session from 'src/lib/session/types';
8 |
9 | function middleware(options: {
10 | session(request: Request, response: Response): Session;
11 | }): Handler;
12 | }
13 |
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import Repository from './lib/repository';
18 | import options from './options';
19 | import processJobs from './subsystems/processor';
20 | import serve from './subsystems/server';
21 |
22 | const repository = new Repository(options);
23 |
24 | if (!process.env.NO_PROCESSOR) {
25 | processJobs(repository);
26 | }
27 |
28 | const server = serve(repository).listen(Number(process.env.PORT));
29 | server.on('error', repository.console.error);
30 |
31 | function terminate() {
32 | const paused = repository.queue.pause(true);
33 | server.close(() => paused.then(() => repository.end()));
34 | }
35 |
36 | process.on('SIGINT', terminate);
37 | process.on('SIGTERM', terminate);
38 |
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | import { timestamp, files, shell, routes } from '@sapper/service-worker';
2 |
3 | const ASSETS = `cache${timestamp}`;
4 |
5 | // `shell` is an array of all the files generated by the bundler,
6 | // `files` is an array of everything in the `static` directory
7 | const to_cache = shell.concat(files);
8 | const cached = new Set(to_cache);
9 |
10 | self.addEventListener('install', event => {
11 | event.waitUntil(
12 | caches
13 | .open(ASSETS)
14 | .then(cache => cache.addAll(to_cache))
15 | .then(() => {
16 | self.skipWaiting();
17 | })
18 | );
19 | });
20 |
21 | self.addEventListener('activate', event => {
22 | event.waitUntil(
23 | caches.keys().then(async keys => {
24 | // delete old caches
25 | for (const key of keys) {
26 | if (key !== ASSETS) await caches.delete(key);
27 | }
28 |
29 | self.clients.claim();
30 | })
31 | );
32 | });
33 |
34 | self.addEventListener('fetch', event => {
35 | if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
36 |
37 | const url = new URL(event.request.url);
38 |
39 | // don't try to handle e.g. data: URIs
40 | if (!url.protocol.startsWith('http')) return;
41 |
42 | // ignore dev server requests
43 | if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
44 |
45 | if (url.pathname === '/api/events' || url.origin !== self.origin) return;
46 |
47 | // always serve static files and bundler-generated assets from cache
48 | if (url.host === self.location.host && cached.has(url.pathname)) {
49 | event.respondWith(caches.match(event.request));
50 | return;
51 | }
52 |
53 | // for pages, you might want to serve a shell `service-worker-index.html` file,
54 | // which Sapper has generated for you. It's not right for every
55 | // app, but if it's right for yours then uncomment this section
56 | /*
57 | if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
58 | event.respondWith(caches.match('/service-worker-index.html'));
59 | return;
60 | }
61 | */
62 |
63 | if (event.request.cache === 'only-if-cached') return;
64 |
65 | // for everything else, try the network first, falling back to
66 | // cache if the user is offline. (If the pages never change, you
67 | // might prefer a cache-first approach to a network-first one.)
68 | event.respondWith(
69 | caches
70 | .open(`offline${timestamp}`)
71 | .then(async cache => {
72 | try {
73 | const response = await fetch(event.request);
74 | cache.put(event.request, response.clone());
75 | return response;
76 | } catch(err) {
77 | const response = await cache.match(event.request);
78 | if (response) return response;
79 |
80 | throw err;
81 | }
82 | })
83 | );
84 | });
85 |
--------------------------------------------------------------------------------
/src/subsystems/client.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { start } from '@sapper/app';
18 |
19 | type GA = ((...a: unknown[]) => void) & { q: unknown[] };
20 |
21 | declare global {
22 | interface Window {
23 | ga: GA;
24 | }
25 |
26 | const ga: GA;
27 | }
28 |
29 | window.ga = function() {
30 | window.ga.q.push(arguments);
31 | } as GA;
32 |
33 | ga.q = [];
34 |
35 | addEventListener('error', ({ message, filename }) => {
36 | ga('send', 'event', 'Runtime script error', filename, message);
37 | });
38 |
39 | addEventListener('unhandledrejection', ({ reason }) => {
40 | ga('send', 'event', 'Unhandled promise rejection', null, reason);
41 | });
42 |
43 | document.addEventListener('securitypolicyviolation', ({ blockedURI, violatedDirective }) => {
44 | ga(
45 | 'send',
46 | 'event',
47 | 'Security Policy Violation',
48 | violatedDirective,
49 | blockedURI,
50 | { nonInteraction: true });
51 | });
52 |
53 | export default function(target: HTMLElement) {
54 | return start({ target });
55 | }
56 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/accept.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import {
19 | fabricateFollow,
20 | fabricateLocalAccount,
21 | fabricateRemoteAccount
22 | } from '../../../lib/test/fabricator';
23 | import repository from '../../../lib/test/repository';
24 | import { unwrap } from '../../../lib/test/types';
25 | import accept from './accept';
26 | import nock = require('nock');
27 |
28 | test('delivers to remote account', async () => {
29 | const recover = jest.fn();
30 | const { signal } = new AbortController;
31 |
32 | const [actor, object] = await Promise.all([
33 | fabricateRemoteAccount({ inbox: { uri: 'https://AcToR.إختبار/?inbox' } })
34 | .then(account => account.select('actor', signal, recover))
35 | .then(unwrap),
36 | fabricateLocalAccount()
37 | .then(account => account.select('actor', signal, recover))
38 | .then(unwrap)
39 | ]);
40 |
41 | const { id } = await fabricateFollow({ actor, object });
42 | const job = await repository.queue.add({ objectId: id });
43 |
44 | const post = nock('https://AcToR.إختبار').post('/?inbox').reply(200);
45 |
46 | try {
47 | await accept(repository, job, signal, recover);
48 | expect(post.isDone()).toBe(true);
49 | } finally {
50 | nock.cleanAll();
51 | }
52 |
53 | expect(recover).not.toHaveBeenCalled();
54 | });
55 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/accept.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Job } from 'bull';
19 | import Repository from '../../../lib/repository';
20 | import { postToInbox, temporaryError } from '../../../lib/transfer';
21 | import Accept from '../../../lib/tuples/accept';
22 | import LocalAccount from '../../../lib/tuples/local_account';
23 | import RemoteAccount from '../../../lib/tuples/remote_account';
24 |
25 | interface Data {
26 | readonly objectId: string;
27 | }
28 |
29 | export default async function(repository: Repository, { data }: Job, signal: AbortSignal, recover: (error: Error & { [temporaryError]?: boolean }) => unknown) {
30 | const accept = new Accept({ objectId: data.objectId, repository });
31 |
32 | const object = await accept.select('object', signal, recover);
33 | if (!object) {
34 | throw recover(new Error('object not found.'));
35 | }
36 |
37 | const [sender, inboxURI] = await Promise.all([
38 | object.select('object', signal, recover).then(actor => {
39 | if (actor) {
40 | return actor.select('account', signal, recover);
41 | }
42 |
43 | throw recover(new Error('object\'s object not found.'));
44 | }),
45 | object.select('actor', signal, recover).then(async actor => {
46 | if (!actor) {
47 | throw recover(new Error('object\'s actor not found.'));
48 | }
49 |
50 | const account = await actor.select('account', signal, recover);
51 | if (!(account instanceof RemoteAccount)) {
52 | throw recover(new Error('object\'s actor\'s account invalid.'));
53 | }
54 |
55 | return account.select('inboxURI', signal, recover);
56 | })
57 | ]);
58 |
59 | if (!(sender instanceof LocalAccount)) {
60 | throw recover(new Error('object\'s object\'s account invalid.'));
61 | }
62 |
63 | if (!inboxURI) {
64 | throw recover(new Error('object\'s actor\'s inboxURI not found.'));
65 | }
66 |
67 | await postToInbox(repository, sender, inboxURI, accept as any, signal, recover);
68 | }
69 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import accept from './accept';
18 | import postFollow from './post_follow';
19 | import postLike from './post_like';
20 | import postStatus from './post_status';
21 | import processInbox from './process_inbox';
22 |
23 | export default { accept, postFollow, postLike, postStatus, processInbox };
24 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/post_follow.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import {
19 | fabricateFollow,
20 | fabricateLocalAccount,
21 | fabricateRemoteAccount
22 | } from '../../../lib/test/fabricator';
23 | import repository from '../../../lib/test/repository';
24 | import { unwrap } from '../../../lib/test/types';
25 | import postFollow from './post_follow';
26 | import nock = require('nock');
27 |
28 | test('delivers to remote account', async () => {
29 | const recover = jest.fn();
30 | const { signal } = new AbortController;
31 |
32 | const [actor, object] = await Promise.all([
33 | fabricateLocalAccount()
34 | .then(account => account.select('actor', signal, recover))
35 | .then(unwrap),
36 | fabricateRemoteAccount({ inbox: { uri: 'https://ObJeCt.إختبار/?inbox' } })
37 | .then(account => account.select('actor', signal, recover))
38 | .then(unwrap)
39 | ]);
40 |
41 | const follow = await fabricateFollow({ actor, object });
42 | const job = await repository.queue.add({ id: follow.id });
43 | const post = nock('https://ObJeCt.إختبار').post('/?inbox').reply(200);
44 |
45 | try {
46 | await postFollow(repository, job, signal, recover);
47 | expect(post.isDone()).toBe(true);
48 | } finally {
49 | nock.cleanAll();
50 | }
51 |
52 | expect(recover).not.toHaveBeenCalled();
53 | });
54 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/post_follow.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Job } from 'bull';
19 | import Repository from '../../../lib/repository';
20 | import { postToInbox, temporaryError } from '../../../lib/transfer';
21 | import LocalAccount from '../../../lib/tuples/local_account';
22 | import RemoteAccount from '../../../lib/tuples/remote_account';
23 |
24 | interface Data {
25 | readonly id: string;
26 | }
27 |
28 | export default async function (repository: Repository, { data }: Job, signal: AbortSignal, recover: (error: Error & { [temporaryError]?: boolean }) => unknown) {
29 | const follow = await repository.selectFollowIncludingActorAndObjectById(data.id, signal, recover);
30 |
31 | if (!follow) {
32 | throw recover(new Error('Follow not found.'));
33 | }
34 |
35 | const [sender, inboxURI] = await Promise.all([
36 | follow.select('actor', signal, recover).then(actor => {
37 | if (!actor) {
38 | throw new Error('actor not found.');
39 | }
40 |
41 | return actor.select('account', signal, recover);
42 | }),
43 | follow.select('object', signal, recover).then(async actor => {
44 | if (!actor) {
45 | throw new Error('object not found.');
46 | }
47 |
48 | const account = await actor.select('account', signal, recover);
49 | if (!(account instanceof RemoteAccount)) {
50 | throw recover(new Error('object\'s account invalid'));
51 | }
52 |
53 | return account.select('inboxURI', signal, recover);
54 | })
55 | ]);
56 |
57 | if (!(sender instanceof LocalAccount)) {
58 | throw recover(new Error('actor\'s account invalid'));
59 | }
60 |
61 | if (!inboxURI) {
62 | throw recover(new Error('object\'s inbox not found.'));
63 | }
64 |
65 | await postToInbox(repository, sender, inboxURI, follow as any, signal, recover);
66 | }
67 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/post_like.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import {
19 | fabricateLike,
20 | fabricateLocalAccount,
21 | fabricateNote,
22 | fabricateRemoteAccount
23 | } from '../../../lib/test/fabricator';
24 | import repository from '../../../lib/test/repository';
25 | import { unwrap } from '../../../lib/test/types';
26 | import postLike from './post_like';
27 | import nock = require('nock');
28 |
29 | test('delivers to remote account', async () => {
30 | const recover = jest.fn();
31 | const { signal } = new AbortController;
32 |
33 | const [actor, object] = await Promise.all([
34 | fabricateLocalAccount()
35 | .then(account => account.select('actor', signal, recover))
36 | .then(unwrap),
37 | fabricateRemoteAccount({ inbox: { uri: 'https://ObJeCt.إختبار/?inbox' } })
38 | .then(account => account.select('actor', signal, recover))
39 | .then(unwrap)
40 | .then(actor => fabricateNote({ status: { actor } }))
41 | ]);
42 |
43 | const { id } = await fabricateLike({ actor, object });
44 | const job = await repository.queue.add({ id });
45 |
46 | const post = nock('https://ObJeCt.إختبار').post('/?inbox').reply(200);
47 |
48 | try {
49 | await postLike(repository, job, signal, recover);
50 | expect(post.isDone()).toBe(true);
51 | } finally {
52 | nock.cleanAll();
53 | }
54 |
55 | expect(recover).not.toHaveBeenCalled();
56 | });
57 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/post_like.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Job } from 'bull';
19 | import Repository from '../../../lib/repository';
20 | import { postToInbox, temporaryError } from '../../../lib/transfer';
21 | import LocalAccount from '../../../lib/tuples/local_account';
22 | import RemoteAccount from '../../../lib/tuples/remote_account';
23 |
24 | interface Data {
25 | readonly id: string;
26 | }
27 |
28 | export default async function(repository: Repository, { data }: Job, signal: AbortSignal, recover: (error: Error & { [temporaryError]?: boolean }) => unknown) {
29 | const like = await repository.selectLikeById(data.id, signal, recover);
30 | if (!like) {
31 | throw recover(new Error('Like not found.'));
32 | }
33 |
34 | const [sender, inboxURI] = await Promise.all([
35 | like.select('actor', signal, recover).then(actor => {
36 | if (!actor) {
37 | throw recover(new Error('actor not found.'));
38 | }
39 |
40 | return actor.select('account', signal, recover);
41 | }),
42 | like.select('object', signal, recover).then(async note => {
43 | if (!note) {
44 | throw recover(new Error('object not found.'));
45 | }
46 |
47 | const status = await note.select('status', signal, recover);
48 | if (!status) {
49 | throw recover(new Error('object\'s status not found.'));
50 | }
51 |
52 | const actor = await status.select('actor', signal, recover);
53 | if (!actor) {
54 | throw recover(new Error('object\'s actor not found.'));
55 | }
56 |
57 | const account = await actor.select('account', signal, recover);
58 | if (!(account instanceof RemoteAccount)) {
59 | throw recover(new Error('object\'s actor\'s account invalid.'));
60 | }
61 |
62 | return account.select('inboxURI', signal, recover);
63 | })
64 | ]);
65 |
66 | if (!(sender instanceof LocalAccount)) {
67 | throw recover(new Error('actor\'s account invalid.'));
68 | }
69 |
70 | if (!inboxURI) {
71 | throw recover(new Error('object\'s actor\'s inboxURI not found.'));
72 | }
73 |
74 | await postToInbox(repository, sender, inboxURI, like as any, signal, recover);
75 | }
76 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/post_status.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController } from 'abort-controller';
18 | import {
19 | fabricateAnnounce,
20 | fabricateLocalAccount,
21 | fabricateNote,
22 | fabricateRemoteAccount
23 | } from '../../../lib/test/fabricator';
24 | import repository from '../../../lib/test/repository';
25 | import { unwrap } from '../../../lib/test/types';
26 | import postStatus from './post_status';
27 | import nock = require('nock');
28 |
29 | const { signal } = new AbortController;
30 |
31 | test('delivers announce to remote account', async () => {
32 | const recover = jest.fn();
33 |
34 | const [recipient, actor] = await Promise.all([
35 | fabricateRemoteAccount(
36 | { inbox: { uri: 'https://ReCiPiEnT.إختبار/?inbox' } }),
37 | fabricateLocalAccount()
38 | .then(account => account.select('actor', signal, recover))
39 | .then(unwrap)
40 | ]);
41 |
42 | const announce = await fabricateAnnounce({ status: { actor } });
43 |
44 | const job = await repository.queue.add({
45 | statusId: announce.id,
46 | inboxURIId: recipient.inboxURIId
47 | });
48 |
49 | const post = nock('https://ReCiPiEnT.إختبار').post('/?inbox').reply(200);
50 |
51 | try {
52 | await postStatus(repository, job, signal, recover);
53 | expect(post.isDone()).toBe(true);
54 | } finally {
55 | nock.cleanAll();
56 | }
57 |
58 | expect(recover).not.toHaveBeenCalled();
59 | });
60 |
61 | test('delivers note to remote account', async () => {
62 | const recover = jest.fn();
63 |
64 | const [recipient, actor] = await Promise.all([
65 | fabricateRemoteAccount(
66 | { inbox: { uri: 'https://ReCiPiEnT.إختبار/?inbox' } }),
67 | fabricateLocalAccount()
68 | .then(account => account.select('actor', signal, recover))
69 | .then(unwrap)
70 | ]);
71 |
72 | const note = await fabricateNote({ status: { actor } });
73 |
74 | const job = await repository.queue.add({
75 | statusId: note.id,
76 | inboxURIId: recipient.inboxURIId
77 | });
78 |
79 | const post = nock('https://ReCiPiEnT.إختبار').post('/?inbox').reply(200);
80 |
81 | try {
82 | await postStatus(repository, job, signal, recover);
83 | expect(post.isDone()).toBe(true);
84 | } finally {
85 | nock.cleanAll();
86 | }
87 |
88 | expect(recover).not.toHaveBeenCalled();
89 | });
90 |
--------------------------------------------------------------------------------
/src/subsystems/processor/handlers/post_status.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortSignal } from 'abort-controller';
18 | import { Job } from 'bull';
19 | import Repository from '../../../lib/repository';
20 | import { postToInbox, temporaryError } from '../../../lib/transfer';
21 | import Create from '../../../lib/tuples/create';
22 | import LocalAccount from '../../../lib/tuples/local_account';
23 | import Note from '../../../lib/tuples/note';
24 |
25 | interface Data {
26 | readonly statusId: string;
27 | readonly inboxURIId: string;
28 | }
29 |
30 | export default async function(repository: Repository, { data }: Job, signal: AbortSignal, recover: (error: Error & { [temporaryError]?: boolean }) => unknown) {
31 | const [[activity, sender], inboxURI] = await Promise.all([
32 | repository.selectStatusIncludingExtensionById(data.statusId, signal, recover).then(status => {
33 | if (!status) {
34 | throw recover(new Error('Status not found.'));
35 | }
36 |
37 | return Promise.all([
38 | status.select('extension', signal, recover).then(object =>
39 | object instanceof Note ? new Create({ repository, object }) : object),
40 | status.select('actor', signal, recover).then(actor => {
41 | if (!actor) {
42 | throw recover(new Error('actor not found.'));
43 | }
44 |
45 | return actor.select('account', signal, recover);
46 | })
47 | ]);
48 | }),
49 | repository.selectURIById(data.inboxURIId, signal, recover)
50 | ]);
51 |
52 | if (!activity) {
53 | throw new Error('Status extension not found.');
54 | }
55 |
56 | if (!(sender instanceof LocalAccount)) {
57 | throw recover(new Error('actor\'s account invalid.'));
58 | }
59 |
60 | if (!inboxURI) {
61 | throw recover(new Error('actor\'s inboxURI not found.'));
62 | }
63 |
64 | await postToInbox(repository, sender, inboxURI, activity as any, signal, recover);
65 | }
66 |
--------------------------------------------------------------------------------
/src/subsystems/processor/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2018 Miniverse authors
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU Affero General Public License as published by
6 | the Free Software Foundation, version 3 of the License.
7 |
8 | This program is distributed in the hope that it will be useful,
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | GNU Affero General Public License for more details.
12 |
13 | You should have received a copy of the GNU Affero General Public License
14 | along with this program. If not, see .
15 | */
16 |
17 | import { AbortController, AbortSignal } from 'abort-controller';
18 | import { Job } from 'bull';
19 | import { globalAgent } from 'https';
20 | import Repository from '../../lib/repository';
21 | import { temporaryError } from '../../lib/transfer';
22 | import handlers from './handlers';
23 |
24 | export default function(repository: Repository) {
25 | repository.queue.process(globalAgent.maxFreeSockets, job => {
26 | const indexableHandlers = handlers as {
27 | readonly [key: string]: (
28 | (
29 | repository: Repository,
30 | job: Job,
31 | signal: AbortSignal,
32 | recover: (error: Error & { [temporaryError]?: boolean }) => unknown
33 | ) => Promise
34 | ) | undefined;
35 | };
36 |
37 | const handle = indexableHandlers[job.data.type];
38 | if (!handle) {
39 | repository.console.error(`Unrecoverable error caused by job ${job.id}.`);
40 | throw new Error('Invalid data type.');
41 | }
42 |
43 | const controller = new AbortController;
44 | const recovery: { error?: Error } = {};
45 | let discard = Promise.resolve();
46 |
47 | const promise: Promise & {
48 | cancel?: () => unknown;
49 | } = handle(repository, job, controller.signal, error => {
50 | if (!error[temporaryError]) {
51 | discard = job.discard();
52 | }
53 |
54 | recovery.error = error;
55 | return recovery;
56 | }).catch(error => discard.then(() => {
57 | if (error == recovery) {
58 | throw recovery.error;
59 | }
60 |
61 | repository.console.error(`Unrecoverable error caused by job ${job.id}.`);
62 | throw error;
63 | }));
64 |
65 | promise.cancel = () => controller.abort();
66 | return promise;
67 | });
68 | }
69 |
--------------------------------------------------------------------------------
/src/svelte.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'svelte/store' {
2 | export function readable(value: T, subscribe: (set: (value: T) => void) => () => void): {
3 | subscribe(): () => void;
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 | %sapper.base%
22 | %sapper.styles%
23 | %sapper.head%
24 |
25 |
26 |
%sapper.html%
27 |
Copyright (C) 2018 Miniverse authors. Licensed under AGPL-3.0.