diff --git a/.gitignore b/.gitignore index 92377b4b6..5e221b038 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ ansible/inventory ansible/inventory_dev ansible/passwords/ -ansible/vars/ # docker build files docker/lemmy_mine.hjson diff --git a/.travis.yml b/.travis.yml index d765ecb4f..602a8613d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ before_cache: - rm -rfv target/debug/build/lemmy_server-* - rm -rfv target/debug/deps/lemmy_server-* - rm -rfv target/debug/lemmy_server.d - - cargo clean before_script: - psql -c "create user lemmy with password 'password' superuser;" -U postgres - psql -c 'create database lemmy with owner lemmy;' -U postgres diff --git a/ansible/lemmy_dev.yml b/ansible/lemmy_dev.yml index 1d8e40aee..e9b8364f3 100644 --- a/ansible/lemmy_dev.yml +++ b/ansible/lemmy_dev.yml @@ -16,32 +16,6 @@ - setup: # gather facts tasks: - # TODO: this task is running on all hosts at the same time so there is a race condition - - name: xxx - shell: | - mkdir -p "vars/{{ inventory_hostname }}/" - if [ ! -f "vars/{{ inventory_hostname }}/port_counter" ]; then - if [ -f "vars/max_port_counter" ]; then - MAX_PORT=$(cat vars/max_port_counter) - else - MAX_PORT=8000 - fi - OUR_PORT=$(expr $MAX_PORT + 10) - echo $OUR_PORT > "vars/{{ inventory_hostname }}/port_counter" - echo $OUR_PORT > "vars/max_port_counter" - fi - cat "vars/{{ inventory_hostname }}/port_counter" - args: - executable: /bin/bash - delegate_to: localhost - register: lemmy_port - - - set_fact: "lemmy_port={{ lemmy_port.stdout_lines[0] }}" - - set_fact: "pictshare_port={{ lemmy_port|int + 1 }}" - - set_fact: "iframely_port={{ lemmy_port|int + 2 }}" - - debug: - msg: "lemmy_port={{ lemmy_port }} pictshare_port={{pictshare_port}} iframely_port={{iframely_port}}" - - name: install dependencies apt: pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot', 'python-certbot-nginx'] @@ -51,29 +25,25 @@ args: creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' - # TODO: need to use different path per domain - name: create lemmy folder file: path={{item.path}} state=directory with_items: - - { path: '/lemmy/{{ domain }}/' } - - { path: '/lemmy/{{ domain }}/volumes/' } - - { path: '/var/cache/lemmy/{{ domain }}/' } + - { path: '/lemmy/' } + - { path: '/lemmy/volumes/' } - block: - name: add template files template: src={{item.src}} dest={{item.dest}} mode={{item.mode}} with_items: - - { src: 'templates/docker-compose.yml', dest: '/lemmy/{{domain}}/docker-compose.yml', mode: '0600' } - - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy-{{ domain }}.conf', mode: '0644' } - - { src: '../docker/iframely.config.local.js', dest: '/lemmy/{{ domain }}/iframely.config.local.js', mode: '0600' } + - { src: 'templates/docker-compose.yml', dest: '/lemmy/docker-compose.yml', mode: '0600' } + - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf', mode: '0644' } + - { src: '../docker/iframely.config.local.js', dest: '/lemmy/iframely.config.local.js', mode: '0600' } - name: add config file (only during initial setup) - template: src='templates/config.hjson' dest='/lemmy/{{domain}}/lemmy.hjson' mode='0600' force='no' owner='1000' group='1000' + template: src='templates/config.hjson' dest='/lemmy/lemmy.hjson' mode='0600' force='no' owner='1000' group='1000' vars: - # TODO: these paths are changed, need to move the files - # TODO: not sure what to call the local var folder, its not mentioned in the ansible docs - postgres_password: "{{ lookup('password', 'vars/{{ inventory_hostname }}/postgres_password chars=ascii_letters,digits') }}" - jwt_password: "{{ lookup('password', 'vars/{{ inventory_hostname }}/jwt_password chars=ascii_letters,digits') }}" + postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" + jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}" - name: build the dev docker image local_action: shell cd .. && sudo docker build . -f docker/dev/Dockerfile -t lemmy:dev @@ -115,7 +85,7 @@ # be a problem for testing - name: start docker-compose docker_compose: - project_src: "/lemmy/{{ domain }}/" + project_src: /lemmy/ state: present recreate: always ignore_errors: yes @@ -126,6 +96,6 @@ - name: certbot renewal cronjob cron: special_time=daily - name=certbot-renew-lemmy-{{ domain }} + name=certbot-renew-lemmy user=root job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'nginx -s reload'" diff --git a/ansible/templates/docker-compose.yml b/ansible/templates/docker-compose.yml index d6afd2532..a4d54f6d8 100644 --- a/ansible/templates/docker-compose.yml +++ b/ansible/templates/docker-compose.yml @@ -4,7 +4,7 @@ services: lemmy: image: {{ lemmy_docker_image }} ports: - - "127.0.0.1:{{ lemmy_port }}:8536" + - "127.0.0.1:8536:8536" restart: always environment: - RUST_LOG=error @@ -28,7 +28,7 @@ services: pictshare: image: shtripok/pictshare:latest ports: - - "127.0.0.1:{{ pictshare_port }}:80" + - "127.0.0.1:8537:80" volumes: - ./volumes/pictshare:/usr/share/nginx/html/data restart: always @@ -36,7 +36,7 @@ services: iframely: image: dogbin/iframely:latest ports: - - "127.0.0.1:{{ iframely_port }}:80" + - "127.0.0.1:8061:80" volumes: - ./iframely.config.local.js:/iframely/config.local.js:ro restart: always diff --git a/ansible/templates/nginx.conf b/ansible/templates/nginx.conf index 003720bcb..04e5a6436 100644 --- a/ansible/templates/nginx.conf +++ b/ansible/templates/nginx.conf @@ -1,4 +1,4 @@ -proxy_cache_path /var/cache/lemmy/{{ domain }} levels=1:2 keys_zone=lemmy_frontend_cache_{{ domain }}:10m max_size=100m use_temp_path=off; +proxy_cache_path /var/cache/lemmy_frontend levels=1:2 keys_zone=lemmy_frontend_cache:10m max_size=100m use_temp_path=off; server { listen 80; @@ -52,7 +52,7 @@ server { client_max_body_size 50M; location / { - proxy_pass http://0.0.0.0:{{ lemmy_port }}; + proxy_pass http://0.0.0.0:8536; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -63,7 +63,7 @@ server { proxy_set_header Connection "upgrade"; # Proxy Cache - proxy_cache lemmy_frontend_cache_{{ domain }}; + proxy_cache lemmy_frontend_cache; proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504; proxy_cache_revalidate on; proxy_cache_lock on; @@ -71,7 +71,7 @@ server { } location /pictshare/ { - proxy_pass http://0.0.0.0:{{ pictshare_port }}/; + proxy_pass http://0.0.0.0:8537/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -82,7 +82,7 @@ server { } location /iframely/ { - proxy_pass http://0.0.0.0:{{ iframely_port }}/; + proxy_pass http://0.0.0.0:8061/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -98,6 +98,6 @@ map $remote_addr $remote_addr_anon { ::1 $remote_addr; default 0.0.0.0; } -log_format main_{{ domain }} '$remote_addr_anon - $remote_user [$time_local] "$request" ' +log_format main '$remote_addr_anon - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" "$http_user_agent"'; -access_log /var/log/nginx/access.log main_{{ domain }}; +access_log /var/log/nginx/access.log main; diff --git a/ansible/uninstall.yml b/ansible/uninstall.yml index 08d5316c3..252c5bd1f 100644 --- a/ansible/uninstall.yml +++ b/ansible/uninstall.yml @@ -22,14 +22,24 @@ - name: stop docker-compose docker_compose: - project_src: /lemmy/{{domain}}/ + project_src: /lemmy/ state: absent - name: delete data file: path={{item.path}} state=absent with_items: - - { path: '/lemmy/{{domain}}/' } - - { path: '/etc/nginx/sites-enabled/lemmy-{{ domain }}.conf' } + - { path: '/lemmy/' } + - { path: '/etc/nginx/sites-enabled/lemmy.conf' } + + - name: Remove a volume + docker_volume: name={{item.name}} state=absent + with_items: + - { name: 'lemmy_lemmy_db' } + - { name: 'lemmy_lemmy_pictshare' } + + - name: delete entire ecloud folder + file: path='/mnt/repo-base/' state=absent + when: delete_certs|bool - name: remove certbot cronjob cron: diff --git a/docker/lemmy.hjson b/docker/lemmy.hjson index 5a6d1ff11..b61ea8261 100644 --- a/docker/lemmy.hjson +++ b/docker/lemmy.hjson @@ -41,7 +41,16 @@ # interval length for registration limit register_per_second: 3600 } -# # email sending configuration +# # optional: parameters for automatic configuration of new instance (only used at first start) +# setup: { +# # username for the admin user +# admin_username: "lemmy" +# # password for the admin user +# admin_password: "lemmy" +# # name of the site (can be changed later) +# site_name: "Lemmy Test" +# } +# # optional: email sending configuration # email: { # # hostname of the smtp server # smtp_server: "" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 10a6153ea..70c423c79 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -13,6 +13,7 @@ - [Contributing](contributing.md) - [Docker Development](contributing_docker_development.md) - [Local Development](contributing_local_development.md) + - [Federation Development](contributing_federation_development.md) - [Websocket/HTTP API](contributing_websocket_http_api.md) - [ActivityPub API Outline](contributing_apub_api_outline.md) - [Theming Guide](contributing_theming.md) diff --git a/docs/src/about_goals.md b/docs/src/about_goals.md index b78c78182..caa6948a0 100644 --- a/docs/src/about_goals.md +++ b/docs/src/about_goals.md @@ -50,3 +50,4 @@ - [Federation.md](https://github.com/dariusk/gathio/blob/7fc93dbe9d4d99457a0e85c6c532112f415b7af2/FEDERATION.md) - [Activitypub implementers guide](https://socialhub.activitypub.rocks/t/draft-guide-for-new-activitypub-implementers/479) - [Data storage questions](https://socialhub.activitypub.rocks/t/data-storage-questions/579/3) +- [Activitypub as it has been understood](https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood) diff --git a/docs/src/administration_install_ansible.md b/docs/src/administration_install_ansible.md index 875dae6a1..bf5e67493 100644 --- a/docs/src/administration_install_ansible.md +++ b/docs/src/administration_install_ansible.md @@ -1,5 +1,7 @@ # Ansible Installation +This is the same as the [Docker installation](administration_install_docker.md), except that Ansible handles all of it automatically. It also does some extra things like setting up TLS and email for your Lemmy instance. + First, you need to [install Ansible on your local computer](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) (e.g. using `sudo apt install ansible`) or the equivalent for you platform. Then run the following commands on your local computer: @@ -11,3 +13,10 @@ cp inventory.example inventory nano inventory # enter your server, domain, contact email ansible-playbook lemmy.yml --become ``` + +To update to a new version, just run the following in your local Lemmy repo: +```bash +git pull origin master +cd ansible +ansible-playbook lemmy.yml --become +``` \ No newline at end of file diff --git a/docs/src/administration_install_docker.md b/docs/src/administration_install_docker.md index 992049839..391299b3d 100644 --- a/docs/src/administration_install_docker.md +++ b/docs/src/administration_install_docker.md @@ -1,29 +1,33 @@ # Docker Installation -Make sure you have both docker and docker-compose(>=`1.24.0`) installed: +Make sure you have both docker and docker-compose(>=`1.24.0`) installed. On Ubuntu, just run `apt install docker-compose docker.io`. Next, ```bash -mkdir lemmy/ -cd lemmy/ +# create a folder for the lemmy files. the location doesnt matter, you can put this anywhere you want +mkdir /lemmy +cd /lemmy +# download default config files wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/lemmy.hjson wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/iframely.config.local.js -# Edit lemmy.hjson, and docker-compose.yml to do more configuration (like adding a custom password) docker-compose up -d ``` -and go to http://localhost:8536. +After this, have a look at the [config file](administration_configuration.md) named `lemmy.hjson`, and adjust it, in particular the hostname. -[A sample nginx config](/ansible/templates/nginx.conf) (Note: Avatar / Image uploading won't work without this), could be setup with: +To make Lemmy available outside the server, you need to setup a reverse proxy, like Nginx. [A sample nginx config](/ansible/templates/nginx.conf), could be setup with: ```bash wget https://raw.githubusercontent.com/dessalines/lemmy/master/ansible/templates/nginx.conf # Replace the {{ vars }} sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf ``` + +You will also need to setup TLS, for example with [Let's Encrypt](https://letsencrypt.org/). After this you need to restart Nginx to reload the config. + ## Updating -To update to the newest version, run: +To update to the newest version, you can manually change the version in `docker-compose.yml`. Alternatively, fetch the latest version from our git repo: ```bash wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml diff --git a/docs/src/contributing_federation_development.md b/docs/src/contributing_federation_development.md new file mode 100644 index 000000000..13a047d08 --- /dev/null +++ b/docs/src/contributing_federation_development.md @@ -0,0 +1,37 @@ +# Federation Development + +## Setup + +If you don't have a local clone of the Lemmy repo yet, just run the following command: + +```bash +git clone https://yerbamate.dev/nutomic/lemmy.git -b federation +``` + +If you already have the Lemmy repo cloned, you need to add a new remote: +```bash +git remote add federation https://yerbamate.dev/nutomic/lemmy.git +git checkout federation +git pull federation federation +``` + +## Running + +You need to have the following packages installed, the Docker service needs to be running. + +- docker +- docker-compose +- cargo +- yarn + +Then run the following +```bash +cd dev/federation-test +./run-federation-test.bash +``` + +After the build is finished and the docker-compose setup is running, open [127.0.0.1:8540](http://127.0.0.1:8540) and +[127.0.0.1:8541](http://127.0.0.1:8541) in your browser to use the test instances. You can login as admin with +username `lemmy` and password `lemmy`, or create new accounts. + +Please get in touch if you want to contribute to this, so we can coordinate things and avoid duplicate work. \ No newline at end of file diff --git a/server/Cargo.lock b/server/Cargo.lock index 78e7ee1e8..ceb09b755 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "activitystreams" -version = "0.5.0-alpha.11" +version = "0.5.0-alpha.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0fb876395ae7a1dd1c7d7de38f2cdb583918db16b46ee5b75d2e9bf7af1ef9f" +checksum = "e7173513c9d586a1157f375835777e3b50498b6b7aab4411a7098b455ba995f0" dependencies = [ "activitystreams-derive", "chrono", @@ -17,9 +17,9 @@ dependencies = [ [[package]] name = "activitystreams-derive" -version = "0.5.0-alpha.4" +version = "0.5.0-alpha.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2bc640808dceb2efac81e6bcb77a7f4e2e76af7fb60e88f966b48123b625d2f" +checksum = "c7ff4a2be3b67d763e78794f622ef2d53da077521229774837f61963c4067b36" dependencies = [ "proc-macro2", "quote", @@ -46,7 +46,7 @@ dependencies = [ "pin-project", "smallvec", "tokio", - "tokio-util", + "tokio-util 0.2.0", "trust-dns-proto", "trust-dns-resolver", ] @@ -63,7 +63,7 @@ dependencies = [ "futures-sink", "log", "tokio", - "tokio-util", + "tokio-util 0.2.0", ] [[package]] @@ -378,6 +378,15 @@ dependencies = [ "memchr 2.3.3", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "arc-swap" version = "0.4.5" @@ -401,9 +410,9 @@ checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" [[package]] name = "async-trait" -version = "0.1.26" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a03abb7c9b93ae229356151a083d26218c0358866a2a59d4280c856e9482e6" +checksum = "bab5c215748dc1ad11a145359b1067107ae0f8ca5e99844fa64067ed5bf198e3" dependencies = [ "proc-macro2", "quote", @@ -621,9 +630,9 @@ checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" [[package]] name = "bytestring" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc267467f58ef6cc8874064c62a0423eb0d099362c8a23edd1c6d044f46eead4" +checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" dependencies = [ "bytes", ] @@ -652,6 +661,21 @@ dependencies = [ "time", ] +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -661,6 +685,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "comrak" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c" +dependencies = [ + "clap", + "entities", + "lazy_static 1.4.0", + "pest", + "pest_derive", + "regex 1.3.6", + "twoway", + "typed-arena", + "unicode_categories", +] + [[package]] name = "config" version = "0.10.1" @@ -999,6 +1040,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + [[package]] name = "enum-as-inner" version = "0.3.2" @@ -1238,9 +1285,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7938e6aa2a31df4e21f224dc84704bd31c089a6d1355c535b03667371cccc843" +checksum = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42" dependencies = [ "bytes", "fnv", @@ -1252,7 +1299,7 @@ dependencies = [ "log", "slab", "tokio", - "tokio-util", + "tokio-util 0.3.1", ] [[package]] @@ -1266,9 +1313,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" dependencies = [ "libc", ] @@ -1483,6 +1530,7 @@ dependencies = [ "actix-web-actors", "bcrypt", "chrono", + "comrak", "config", "diesel", "diesel_migrations", @@ -1625,6 +1673,12 @@ dependencies = [ "linked-hash-map 0.5.2", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -1931,6 +1985,49 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1", +] + [[package]] name = "pin-project" version = "0.4.8" @@ -1986,9 +2083,9 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" [[package]] name = "proc-macro-nested" @@ -1998,9 +2095,9 @@ checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" [[package]] name = "proc-macro2" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" dependencies = [ "unicode-xid", ] @@ -2363,21 +2460,22 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "security-framework" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97bbedbe81904398b6ebb054b3e912f99d55807125790f3198ac990d98def5b0" +checksum = "572dfa3a0785509e7a44b5b4bebcf94d41ba34e9ed9eb9df722545c3b3c4144a" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", + "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06fd2f23e31ef68dd2328cc383bd493142e46107a3a0e24f7d734e3f3b80fe4c" +checksum = "8ddb15a5fec93b7021b8a9e96009c5d8d51c15673569f7c0f6b7204e5b7b404f" dependencies = [ "core-foundation-sys", "libc", @@ -2495,6 +2593,18 @@ dependencies = [ "url", ] +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + [[package]] name = "sha1" version = "0.6.0" @@ -2560,9 +2670,9 @@ checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" [[package]] name = "socket2" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" +checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" dependencies = [ "cfg-if", "libc", @@ -2588,6 +2698,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "strsim" version = "0.9.3" @@ -2659,19 +2775,28 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "1.0.13" +name = "textwrap" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3711fd1c4e75b3eff12ba5c40dba762b6b65c5476e8174c1a664772060c49bf" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0570dc61221295909abdb95c739f2e74325e14293b2026b0a7e195091ec54ae" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae2b85ba4c9aa32dd3343bd80eb8d22e9b54b7688c17ea3907f236885353b233" +checksum = "227362df41d566be41a28f64401e07a043157c21c14b9785a0d8e256f940a8fd" dependencies = [ "proc-macro2", "quote", @@ -2728,9 +2853,9 @@ dependencies = [ [[package]] name = "tokio" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616" +checksum = "619cdb2245c40c42d563089b72e80c5df659513d667a017598439ef7a7b1ffe1" dependencies = [ "bytes", "fnv", @@ -2761,6 +2886,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.6" @@ -2809,12 +2948,40 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "twoway" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc" +dependencies = [ + "memchr 2.3.3", + "unchecked-index", +] + +[[package]] +name = "typed-arena" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" + [[package]] name = "typenum" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + [[package]] name = "unicase" version = "2.6.0" @@ -2848,12 +3015,24 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "untrusted" version = "0.7.0" @@ -2923,6 +3102,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + [[package]] name = "version_check" version = "0.1.5" @@ -3041,9 +3226,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" +checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" dependencies = [ "winapi 0.3.8", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index 83dfb43ef..1455428d7 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2", "64-column-tables"] } diesel_migrations = "1.4.0" dotenv = "0.15.0" -activitystreams = "0.5.0-alpha.10" +activitystreams = "0.5.0-alpha.16" bcrypt = "0.6.2" chrono = { version = "0.4.7", features = ["serde"] } failure = "0.1.5" @@ -37,3 +37,4 @@ hjson = "0.8.2" url = "2.1.1" percent-encoding = "2.1.0" isahc = "0.9" +comrak = "0.7" diff --git a/server/config/defaults.hjson b/server/config/defaults.hjson index e048ced89..8603e49a8 100644 --- a/server/config/defaults.hjson +++ b/server/config/defaults.hjson @@ -1,4 +1,15 @@ { +# # optional: parameters for automatic configuration of new instance (only used at first start) +# setup: { +# # username for the admin user +# admin_username: "" +# # password for the admin user +# admin_password: "" +# # optional: email for the admin user (can be omitted and set later through the website) +# admin_email: "" +# # name of the site (can be changed later) +# site_name: "" +# } # settings related to the postgresql database database: { # username to connect to postgres diff --git a/server/src/api/site.rs b/server/src/api/site.rs index ef1a28287..6bd90149b 100644 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@ -1,5 +1,9 @@ use super::*; +use crate::api::user::Register; +use crate::api::{Oper, Perform}; +use crate::settings::Settings; use diesel::PgConnection; +use log::info; use std::str::FromStr; #[derive(Serialize, Deserialize)] @@ -53,12 +57,12 @@ pub struct GetModlogResponse { #[derive(Serialize, Deserialize)] pub struct CreateSite { - name: String, - description: Option, - enable_downvotes: bool, - open_registration: bool, - enable_nsfw: bool, - auth: String, + pub name: String, + pub description: Option, + pub enable_downvotes: bool, + pub open_registration: bool, + pub enable_nsfw: bool, + pub auth: String, } #[derive(Serialize, Deserialize)] @@ -277,10 +281,34 @@ impl Perform for Oper { fn perform(&self, conn: &PgConnection) -> Result { let _data: &GetSite = &self.data; - // It can return a null site in order to redirect - let site_view = match Site::read(&conn, 1) { - Ok(_site) => Some(SiteView::read(&conn)?), - Err(_e) => None, + let site = Site::read(&conn, 1); + let site_view = if site.is_ok() { + Some(SiteView::read(&conn)?) + } else if let Some(setup) = Settings::get().setup.as_ref() { + let register = Register { + username: setup.admin_username.to_owned(), + email: setup.admin_email.to_owned(), + password: setup.admin_password.to_owned(), + password_verify: setup.admin_password.to_owned(), + admin: true, + show_nsfw: true, + }; + let login_response = Oper::new(register).perform(&conn)?; + info!("Admin {} created", setup.admin_username); + + let create_site = CreateSite { + name: setup.site_name.to_owned(), + description: None, + enable_downvotes: false, + open_registration: false, + enable_nsfw: false, + auth: login_response.jwt, + }; + Oper::new(create_site).perform(&conn)?; + info!("Site {} created", setup.site_name); + Some(SiteView::read(&conn)?) + } else { + None }; let mut admins = UserView::admins(&conn)?; diff --git a/server/src/api/user.rs b/server/src/api/user.rs index 333fd9494..056a2a846 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -14,12 +14,12 @@ pub struct Login { #[derive(Serialize, Deserialize)] pub struct Register { - username: String, - email: Option, - password: String, - password_verify: String, - admin: bool, - show_nsfw: bool, + pub username: String, + pub email: Option, + pub password: String, + pub password_verify: String, + pub admin: bool, + pub show_nsfw: bool, } #[derive(Serialize, Deserialize)] @@ -42,7 +42,7 @@ pub struct SaveUserSettings { #[derive(Serialize, Deserialize)] pub struct LoginResponse { - jwt: String, + pub jwt: String, } #[derive(Serialize, Deserialize)] diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index 2f533b977..d914c08df 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -107,7 +107,7 @@ pub async fn get_apub_community_outbox( .set_id(base_url)?; collection .collection_props - .set_many_items_object_boxs( + .set_many_items_base_boxes( community_posts .iter() .map(|c| c.as_page().unwrap()) diff --git a/server/src/apub/puller.rs b/server/src/apub/puller.rs index 43cc2f8c8..a9ac10861 100644 --- a/server/src/apub/puller.rs +++ b/server/src/apub/puller.rs @@ -9,8 +9,8 @@ use crate::settings::Settings; use activitystreams::actor::{properties::ApActorProperties, Group}; use activitystreams::collection::{OrderedCollection, UnorderedCollection}; use activitystreams::ext::Ext; -use activitystreams::object::ObjectBox; use activitystreams::object::Page; +use activitystreams::BaseBox; use failure::Error; use isahc::prelude::*; use log::warn; @@ -75,11 +75,11 @@ pub fn get_remote_community_posts(identifier: &str) -> Result>(&get_remote_community_uri(identifier))?; let outbox_uri = &community.extension.get_outbox().to_string(); let outbox = fetch_remote_object::(outbox_uri)?; - let items = outbox.collection_props.get_many_items_object_boxs(); + let items = outbox.collection_props.get_many_items_base_boxes(); let posts: Vec = items .unwrap() - .map(|obox: &ObjectBox| { + .map(|obox: &BaseBox| { let page: Page = obox.clone().to_concrete::().unwrap(); PostView { id: -1, diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index dacdb6f6a..e0d358ffe 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -1,4 +1,3 @@ -extern crate lazy_static; use crate::settings::Settings; use diesel::dsl::*; use diesel::result::Error; diff --git a/server/src/lib.rs b/server/src/lib.rs index 0012c73fe..9f882b7f8 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -11,6 +11,7 @@ pub extern crate actix; pub extern crate actix_web; pub extern crate bcrypt; pub extern crate chrono; +pub extern crate comrak; pub extern crate dotenv; pub extern crate jsonwebtoken; pub extern crate lettre; @@ -18,6 +19,7 @@ pub extern crate lettre_email; extern crate log; pub extern crate rand; pub extern crate regex; +pub extern crate rss; pub extern crate serde; pub extern crate serde_json; pub extern crate sha2; @@ -220,6 +222,10 @@ fn fetch_iframely_and_pictshare_data( ) } +pub fn markdown_to_html(text: &str) -> String { + comrak::markdown_to_html(text, &comrak::ComrakOptions::default()) +} + #[cfg(test)] mod tests { use crate::{extract_usernames, is_email_regex, remove_slurs, slur_check, slurs_vec_to_str}; diff --git a/server/src/routes/feeds.rs b/server/src/routes/feeds.rs index ad0f28d5a..6826cf5cb 100644 --- a/server/src/routes/feeds.rs +++ b/server/src/routes/feeds.rs @@ -1,5 +1,3 @@ -extern crate rss; - use super::*; use crate::db::comment_view::{ReplyQueryBuilder, ReplyView}; use crate::db::community::Community; @@ -8,9 +6,9 @@ use crate::db::site_view::SiteView; use crate::db::user::{Claims, User_}; use crate::db::user_mention_view::{UserMentionQueryBuilder, UserMentionView}; use crate::db::{ListingType, SortType}; -use crate::Settings; +use crate::{markdown_to_html, Settings}; use actix_web::{web, HttpResponse, Result}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, NaiveDateTime, Utc}; use diesel::r2d2::{ConnectionManager, Pool}; use diesel::PgConnection; use failure::Error; @@ -34,7 +32,6 @@ enum RequestType { pub fn config(cfg: &mut web::ServiceConfig) { cfg .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed)) - .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)) .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)); } @@ -44,9 +41,7 @@ async fn get_all_feed( ) -> Result { let res = web::block(move || { let conn = db.get()?; - - let sort_type = get_sort_type(info)?; - get_feed_all_data(&conn, &sort_type) + get_feed_all_data(&conn, &get_sort_type(info)?) }) .await .map(|rss| { @@ -58,6 +53,29 @@ async fn get_all_feed( Ok(res) } +fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result { + let site_view = SiteView::read(&conn)?; + + let posts = PostQueryBuilder::create(&conn) + .listing_type(ListingType::All) + .sort(sort_type) + .list()?; + + let items = create_post_items(posts); + + let mut channel_builder = ChannelBuilder::default(); + channel_builder + .title(&format!("{} - All", site_view.name)) + .link(format!("https://{}", Settings::get().hostname)) + .items(items); + + if let Some(site_desc) = site_view.description { + channel_builder.description(&site_desc); + } + + Ok(channel_builder.build().unwrap().to_string()) +} + async fn get_feed( path: web::Path<(String, String)>, info: web::Query, @@ -86,6 +104,7 @@ async fn get_feed( } }) .await + .map(|builder| builder.build().unwrap().to_string()) .map(|rss| { HttpResponse::Ok() .content_type("application/rss+xml") @@ -103,34 +122,11 @@ fn get_sort_type(info: web::Query) -> Result { SortType::from_str(&sort_query) } -fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result { - let site_view = SiteView::read(&conn)?; - - let posts = PostQueryBuilder::create(&conn) - .listing_type(ListingType::All) - .sort(sort_type) - .list()?; - - let items = create_post_items(posts); - - let mut channel_builder = ChannelBuilder::default(); - channel_builder - .title(&format!("{} - All", site_view.name)) - .link(format!("https://{}", Settings::get().hostname)) - .items(items); - - if let Some(site_desc) = site_view.description { - channel_builder.description(&site_desc); - } - - Ok(channel_builder.build().unwrap().to_string()) -} - fn get_feed_user( conn: &PgConnection, sort_type: &SortType, user_name: String, -) -> Result { +) -> Result { let site_view = SiteView::read(&conn)?; let user = User_::find_by_username(&conn, &user_name)?; let user_url = user.get_profile_url(); @@ -149,14 +145,14 @@ fn get_feed_user( .link(user_url) .items(items); - Ok(channel_builder.build().unwrap().to_string()) + Ok(channel_builder) } fn get_feed_community( conn: &PgConnection, sort_type: &SortType, community_name: String, -) -> Result { +) -> Result { let site_view = SiteView::read(&conn)?; let community = Community::read_from_name(&conn, community_name)?; let community_url = community.get_url(); @@ -179,10 +175,14 @@ fn get_feed_community( channel_builder.description(&community_desc); } - Ok(channel_builder.build().unwrap().to_string()) + Ok(channel_builder) } -fn get_feed_front(conn: &PgConnection, sort_type: &SortType, jwt: String) -> Result { +fn get_feed_front( + conn: &PgConnection, + sort_type: &SortType, + jwt: String, +) -> Result { let site_view = SiteView::read(&conn)?; let user_id = Claims::decode(&jwt)?.claims.id; @@ -204,10 +204,10 @@ fn get_feed_front(conn: &PgConnection, sort_type: &SortType, jwt: String) -> Res channel_builder.description(&site_desc); } - Ok(channel_builder.build().unwrap().to_string()) + Ok(channel_builder) } -fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result { +fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result { let site_view = SiteView::read(&conn)?; let user_id = Claims::decode(&jwt)?.claims.id; @@ -233,86 +233,61 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result { channel_builder.description(&site_desc); } - Ok(channel_builder.build().unwrap().to_string()) + Ok(channel_builder) } fn create_reply_and_mention_items( replies: Vec, mentions: Vec, ) -> Vec { - let mut items: Vec = Vec::new(); + let mut reply_items: Vec = replies + .iter() + .map(|r| { + let reply_url = format!( + "https://{}/post/{}/comment/{}", + Settings::get().hostname, + r.post_id, + r.id + ); + build_item(&r.creator_name, &r.published, &reply_url, &r.content) + }) + .collect(); - for r in replies { - let mut i = ItemBuilder::default(); + let mut mention_items: Vec = mentions + .iter() + .map(|m| { + let mention_url = format!( + "https://{}/post/{}/comment/{}", + Settings::get().hostname, + m.post_id, + m.id + ); + build_item(&m.creator_name, &m.published, &mention_url, &m.content) + }) + .collect(); - i.title(format!("Reply from {}", r.creator_name)); + reply_items.append(&mut mention_items); + reply_items +} - let author_url = format!("https://{}/u/{}", Settings::get().hostname, r.creator_name); - i.author(format!( - "/u/{} (link)", - r.creator_name, author_url - )); - - let dt = DateTime::::from_utc(r.published, Utc); - i.pub_date(dt.to_rfc2822()); - - let reply_url = format!( - "https://{}/post/{}/comment/{}", - Settings::get().hostname, - r.post_id, - r.id - ); - i.comments(reply_url.to_owned()); - let guid = GuidBuilder::default() - .permalink(true) - .value(&reply_url) - .build(); - i.guid(guid.unwrap()); - - i.link(reply_url); - - // TODO find a markdown to html parser here, do images, etc - i.description(r.content); - - items.push(i.build().unwrap()); - } - - for m in mentions { - let mut i = ItemBuilder::default(); - - i.title(format!("Mention from {}", m.creator_name)); - - let author_url = format!("https://{}/u/{}", Settings::get().hostname, m.creator_name); - i.author(format!( - "/u/{} (link)", - m.creator_name, author_url - )); - - let dt = DateTime::::from_utc(m.published, Utc); - i.pub_date(dt.to_rfc2822()); - - let mention_url = format!( - "https://{}/post/{}/comment/{}", - Settings::get().hostname, - m.post_id, - m.id - ); - i.comments(mention_url.to_owned()); - let guid = GuidBuilder::default() - .permalink(true) - .value(&mention_url) - .build(); - i.guid(guid.unwrap()); - - i.link(mention_url); - - // TODO find a markdown to html parser here, do images, etc - i.description(m.content); - - items.push(i.build().unwrap()); - } - - items +fn build_item(creator_name: &str, published: &NaiveDateTime, url: &str, content: &str) -> Item { + let mut i = ItemBuilder::default(); + i.title(format!("Reply from {}", creator_name)); + let author_url = format!("https://{}/u/{}", Settings::get().hostname, creator_name); + i.author(format!( + "/u/{} (link)", + creator_name, author_url + )); + let dt = DateTime::::from_utc(*published, Utc); + i.pub_date(dt.to_rfc2822()); + i.comments(url.to_owned()); + let guid = GuidBuilder::default().permalink(true).value(url).build(); + i.guid(guid.unwrap()); + i.link(url.to_owned()); + // TODO add images + let html = markdown_to_html(&content.to_string()); + i.description(html); + i.build().unwrap() } fn create_post_items(posts: Vec) -> Vec { @@ -359,9 +334,8 @@ fn create_post_items(posts: Vec) -> Vec { i.link(url); } - // TODO find a markdown to html parser here, do images, etc - let mut description = format!(" - submitted by {} to {}
{} points | {} comments", + // TODO add images + let mut description = format!("submitted by {} to {}
{} points | {} comments", author_url, p.creator_name, community_url, @@ -371,7 +345,8 @@ fn create_post_items(posts: Vec) -> Vec { p.number_of_comments); if let Some(body) = p.body { - description.push_str(&format!("

{}", body)); + let html = markdown_to_html(&body); + description.push_str(&html); } i.description(description); diff --git a/server/src/routes/nodeinfo.rs b/server/src/routes/nodeinfo.rs index abfae1eda..662089b30 100644 --- a/server/src/routes/nodeinfo.rs +++ b/server/src/routes/nodeinfo.rs @@ -1,4 +1,3 @@ -extern crate lazy_static; use crate::apub::get_apub_protocol_string; use crate::db::site_view::SiteView; use crate::version; diff --git a/server/src/settings.rs b/server/src/settings.rs index 3928f74e1..29d5966ba 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -1,4 +1,3 @@ -extern crate lazy_static; use config::{Config, ConfigError, Environment, File}; use serde::Deserialize; use std::env; @@ -9,6 +8,7 @@ static CONFIG_FILE: &str = "config/config.hjson"; #[derive(Debug, Deserialize)] pub struct Settings { + pub setup: Option, pub database: Database, pub hostname: String, pub bind: IpAddr, @@ -20,6 +20,14 @@ pub struct Settings { pub federation: Federation, } +#[derive(Debug, Deserialize)] +pub struct Setup { + pub admin_username: String, + pub admin_password: String, + pub admin_email: Option, + pub site_name: String, +} + #[derive(Debug, Deserialize)] pub struct RateLimitConfig { pub message: i32, diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index ea9b619b8..d7f3b5a8a 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -375,12 +375,13 @@ export class Navbar extends Component { let link = isCommentType(reply) ? `/post/${reply.post_id}/comment/${reply.id}` : `/inbox`; - let body = md.render(reply.content); + let htmlBody = md.render(reply.content); + let body = reply.content; // Unfortunately the notifications API can't do html messageToastify( creator_name, creator_avatar, - body, + htmlBody, link, this.context.router ); diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx index edb858ddf..ff863dcb0 100644 --- a/ui/src/components/post-listing.tsx +++ b/ui/src/components/post-listing.tsx @@ -250,14 +250,14 @@ export class PostListing extends Component {
{
{WebSocketService.Instance.site.enable_downvotes && ( )} diff --git a/ui/src/components/symbols.tsx b/ui/src/components/symbols.tsx index 1220413cb..16deec3eb 100644 --- a/ui/src/components/symbols.tsx +++ b/ui/src/components/symbols.tsx @@ -97,6 +97,12 @@ export class Symbols extends Component { + + + + + + diff --git a/ui/src/utils.ts b/ui/src/utils.ts index 48bd175e2..8ecef19b6 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -12,6 +12,7 @@ import 'moment/locale/ca'; import 'moment/locale/fa'; import 'moment/locale/pt-br'; import 'moment/locale/ja'; +import 'moment/locale/ka'; import { UserOperation, @@ -59,6 +60,7 @@ export const languages = [ { code: 'eo', name: 'Esperanto' }, { code: 'es', name: 'Español' }, { code: 'de', name: 'Deutsch' }, + { code: 'ka', name: 'ქართული ენა' }, { code: 'fa', name: 'فارسی' }, { code: 'ja', name: '日本語' }, { code: 'pt_BR', name: 'Português Brasileiro' }, @@ -353,6 +355,8 @@ export function getMomentLanguage(): string { lang = 'pt-br'; } else if (lang.startsWith('ja')) { lang = 'ja'; + } else if (lang.startsWith('ka')) { + lang = 'ka'; } else { lang = 'en'; } diff --git a/ui/translations/ka.json b/ui/translations/ka.json new file mode 100644 index 000000000..2c63c0851 --- /dev/null +++ b/ui/translations/ka.json @@ -0,0 +1,2 @@ +{ +} diff --git a/ui/translations/pl.json b/ui/translations/pl.json index bfd870e45..9da7dc94a 100644 --- a/ui/translations/pl.json +++ b/ui/translations/pl.json @@ -1,3 +1,257 @@ { + "remove_post": "Usuń Post", + "no_posts": "Brak Postów.", + "create_a_post": "Stwórz post", + "create_post": "Stwórz Post", + "number_of_posts_0": "Post", + "number_of_posts_1": "Posty/ów", + "number_of_posts_2": "Posty/ów", + "posts": "Posty", + "related_posts": "Te posty mogą być powiązane", + "cross_posts": "Ten link został też zapostowany na:", + "comments": "Komentarze", + "number_of_comments_0": "Komentarz", + "number_of_comments_1": "Komentarzy/e", + "number_of_comments_2": "Komentarzy/e", + "post": "post", + "cross_post": "post zewnętrzny", + "cross_posted_to": "post wrzucony na zewnątrz: ", + "remove_comment": "Usuń Komentarz", + "communities": "Społeczności", + "users": "Użytkownicy", + "create_a_community": "Stwórz społeczność", + "create_community": "Stwórz Społeczność", + "remove_community": "Usuń Społeczność", + "subscribed_to_communities": "Subskrybowane <1>społeczności", + "trending_communities": "Popularne <1>społeczności", + "list_of_communities": "Lista społeczności", + "community_reqs": "małe litery, podkreślniki, bez spacji.", + "create_private_message": "Stwórz Prywatną Wiadomość", + "send_secure_message": "Wyślij Bezpieczną Wiadomość", + "send_message": "Wyślij Wiadomość", + "message": "Wiadomość", + "edit": "edytuj", + "reply": "odpowiedz", + "more": "więcej", + "cancel": "Anuluj", + "preview": "Podgląd", + "upload_image": "prześlij obraz", + "avatar": "Awatar", + "upload_avatar": "Prześlij Awatar", + "show_avatars": "Pokaż Awatary", + "formatting_help": "poradnik formatowania", + "lock": "zablokuj", + "messages": "Wiadomości", + "yes": "tak", + "number_of_communities_0": "Społeczność", + "number_of_communities_1": "Społeczności", + "number_of_communities_2": "Społeczności", + "sorting_help": "poradnik sortowania", + "view_source": "pokaż źródło", + "unlock": "odblokuj", + "sticky": "przyklej", + "unsticky": "odklej", + "link": "link", + "archive_link": "link archiwalny", + "mod": "moderator", + "mods": "moderatorzy", + "moderates": "Moderuje", + "settings": "Ustawienia", + "remove_as_mod": "zabierz uprawnienia moderatora", + "appoint_as_mod": "przyznaj uprawnienia moderatora", + "modlog": "Log moderatorski", + "admin": "administrator", + "admins": "administratorzy", + "remove_as_admin": "wycofaj uprawnienia administratora", + "appoint_as_admin": "przyznaj uprawnienia administratora", + "remove": "usuń", + "removed": "usunięte", + "locked": "zablokowane", + "stickied": "przyklejone", + "reason": "Powód", + "mark_as_read": "zaznacz jako przeczytane", + "mark_as_unread": "zaznacz jako nieprzeczytane", + "delete": "usuń", + "deleted": "usunięte", + "delete_account": "Usuń Konto", + "delete_account_confirm": "Ostrzeżenie: twoje dane zostaną bezpowrotnie usunięte. Wpisz swoje hasło aby potwierdzić.", + "restore": "przywróć", + "ban": "zbanuj", + "ban_from_site": "zbanuj ze strony", + "unban": "odbanuj", + "unban_from_site": "odbanuj ze strony", + "banned": "zbanowano", + "save": "zapisz", + "unsave": "cofnij zapis", + "create": "stwórz", + "creator": "autor", + "username": "Nazwa użytkownika", + "email_or_username": "Email lub Nazwa Użytkownika", + "number_of_users_0": "Użytkownik", + "number_of_users_1": "Użytkownicy/ków", + "number_of_users_2": "Użytkownicy/ków", + "number_of_subscribers_0": "Subskrybent", + "number_of_subscribers_1": "Subskrybenci/tów", + "number_of_subscribers_2": "Subskrybenci/tów", + "number_of_points_0": "Punkt", + "number_of_points_1": "Punkty/ów", + "number_of_points_2": "Punkty/ów", + "number_online_0": "Użytkownik Online", + "number_online_1": "Użytkowników Online", + "number_online_2": "Użytkowników Online", + "name": "Nazwa", + "title": "Tytuł", + "category": "Kategoria", + "subscribers": "Subskrybenci", + "both": "Obydwa", + "saved": "Zapisane", + "unsubscribe": "Odsubskrybuj", + "subscribe": "Subskrybuj", + "subscribed": "Zasubskrybowane", + "prev": "Wstecz", + "next": "Dalej", + "sidebar": "Pasek boczny", + "sort_type": "Sortuj typ", + "hot": "Popularne", + "new": "Nowe", + "old": "Stare", + "top_day": "Popularne dzisiaj", + "week": "Tydzień", + "month": "Miesiąc", + "year": "Rok", + "all": "Wszystko", + "top": "Popularne", + "api": "API", + "docs": "Dokumentacja", + "inbox": "Skrzynka odbiorcza", + "inbox_for": "Skrzynka odbiorcza <1>{{user}}", + "mark_all_as_read": "zaznacz wszystko jako przeczytane", + "type": "Rodzaj", + "unread": "Nieprzeczytane", + "replies": "Odpowiedzi", + "mentions": "Wzmianki", + "reply_sent": "Odpowiedź wysłana", + "message_sent": "Wiadomość wysłana", + "search": "Szukaj", + "overview": "Podgląd", + "view": "Widok", + "logout": "Wyloguj", + "login_sign_up": "Zaloguj / Zarejestruj", + "login": "Zaloguj", + "sign_up": "Zarejestruj", + "notifications_error": "Powiadomienia na pulpicie są niedostępne w Twojej przeglądarce. Spróbuj przeglądarkę Firefox lub Chrome.", + "unread_messages": "Nieprzeczytane Wiadomości", + "password": "Hasło", + "verify_password": "Zweryfikuj Hasło", + "old_password": "Stare Hasło", + "forgot_password": "nie pamiętam hasła", + "reset_password_mail_sent": "Wysłano email w celu zresetowania hasła.", + "password_change": "Zmiana Hasła", + "new_password": "Nowe Hasło", + "no_email_setup": "Email nie został poprawnie ustawiony na tym serwerze.", + "email": "Email", + "matrix_user_id": "Użytkownik Matrixa", + "private_message_disclaimer": "Ostrzeżenie: Prywatne wiadomości w Lemmym nie są bezpieczne. Jeśli chcesz wysyłać i odbierać bezpieczne wiadomości załóż konto na <1>Riot.im.", + "send_notifications_to_email": "Wysyłaj powiadomienia na Email", + "optional": "Opcjonalne", + "expires": "Wygasa", + "language": "Język", + "browser_default": "Domyślna wartość przeglądarki", + "downvotes_disabled": "Wdółgłosy wyłączone", + "enable_downvotes": "Włącz Wdółgłosy", + "upvote": "Wgóręgłos", + "number_of_upvotes_0": "Wgóręgłos", + "number_of_upvotes_1": "Wgóręgłosy/ów", + "number_of_upvotes_2": "Wgóręgłosy/ów", + "downvote": "Wdółgłos", + "number_of_downvotes_0": "Wdółgłos", + "number_of_downvotes_1": "Wdółgłosy/ów", + "number_of_downvotes_2": "Wdółgłosy/ów", + "open_registration": "Rejestracja Otwarta", + "registration_closed": "Rejestracja Zamknięta", + "enable_nsfw": "Włącz NSFW", + "url": "URL", + "body": "Treść", + "copy_suggested_title": "skopiuj sugerowany tytuł: {{title}}", + "community": "Społeczność", + "expand_here": "Rozwiń tutaj", + "subscribe_to_communities": "Zasubskrybuj kilka <1>społeczności.", + "chat": "Czat", + "recent_comments": "Najnowsze Komentarze", + "no_results": "Brak wyników.", + "setup": "Instalacja", + "lemmy_instance_setup": "Instalacja Instancji Lemmy", + "setup_admin": "Ustaw Administratora Strony", + "your_site": "twoja witryna", + "modified": "zmodyfikowane", + "nsfw": "NSFW", + "show_nsfw": "Pokaż treści NSFW (+18)", + "theme": "Motyw", + "sponsors": "Sponsorzy", + "sponsors_of_lemmy": "Sponsorzy projektu Lemmy", + "sponsor_message": "Lemmy jest wolnym, <1>otwartoźródłowym oprogramowaniem, co oznacza zero reklam, opłat, czy innych form kapitalizacji, od zawsze na zawsze. Twoje darowizny idą bezpośrednio na rozwój projektu w pełno-etatowym wymiarze. Specjalne wyrazy podziękowania dla następujących osób:", + "support_on_patreon": "Wspieraj w serwisie Patreon", + "support_on_liberapay": "Wspieraj na Liberapay", + "donate_to_lemmy": "Przekaż datek na Lemmiego", + "donate": "Przekaż datek", + "general_sponsors": "Główni Sponsorzy to osoby które wsparły Lemmiego kwotą od $10 do $39.", + "crypto": "Kryptowaluta", + "bitcoin": "Bitcoin", + "ethereum": "Ethereum", + "monero": "Monero", + "code": "Kod", + "joined": "Dołączono", + "by": "przez", + "to": "do", + "from": "od", + "transfer_community": "transfer społeczności", + "transfer_site": "transfer witryny", + "are_you_sure": "na pewno?", + "no": "nie", + "powered_by": "Powered by", + "landing": "Lemmy jest <1>agregatorem linków / alternatywą dla reddita. Jest przeznaczony do działania w ramach cyfrowej przestrzeni nazywanej <2>fediverse<2>. <3>Opiera się na samodzielnym hostingu, posiada aktualizowane na żywo wątki z komentarzami, i zajmuje bardzo mało miejsce (<4>~80kB). Federacja w ramach sieci ActivityPub jest w planach. <5>Ta wersja jest <6>bardzo wczesną wersją beta, co oznacza, że wiele funkcji nadal nie działa tak jak powinny. <7><8>Pod tym adresem można sugerować nową funkcjonalność i zgłaszać błędy.<9>Stworzono z wykorzystaniem <10>Rust, <11>Actix, <12>Inferno, <13>Typescript.", + "not_logged_in": "Nie jesteś zalogowana/y.", + "logged_in": "Zalogowano.", + "community_ban": "Zostałaś/eś zbanowana/y z tej społeczności.", + "site_ban": "Zostałaś/eś zbanowana/y z tej witryny", + "couldnt_create_comment": "Nie udało się stworzyć komentarza.", + "couldnt_like_comment": "Polubienie komentarza nie powiodło się.", + "couldnt_update_comment": "Zaktualizowanie komentarza nie powiodło się.", + "couldnt_save_comment": "Zapisanie komentarza nie powiodło się.", + "couldnt_get_comments": "Pobranie komentarzy nie powiodło się.", + "no_comment_edit_allowed": "Nie masz uprawnień do edycji komentarza.", + "no_post_edit_allowed": "Nie masz uprawnień do edycji posta.", + "no_community_edit_allowed": "Nie masz uprawnień do edycji społeczności.", + "couldnt_find_community": "Nie udało się znaleźć społeczności.", + "couldnt_update_community": "Nie udało się zaktualizować Społeczności.", + "community_already_exists": "Społeczność już istnieje.", + "community_moderator_already_exists": "Moderator społeczności już istnieje.", + "community_follower_already_exists": "Osoba obserwująca społeczność już istnieje.", + "community_user_already_banned": "Użytkownik społeczności jest już zbanowany.", + "couldnt_create_post": "Nie udało się stworzyć posta.", + "post_title_too_long": "Tytuł posta zbyt długi.", + "couldnt_like_post": "Nie udało się polubić posta.", + "couldnt_find_post": "Nie udało się znaleźć posta.", + "couldnt_update_post": "Nie udało się zaktualizować postów", + "couldnt_get_posts": "Nie udało się pobrać postów", + "couldnt_save_post": "Nie udało się zapisać posta.", + "no_slurs": "Bez obelg.", + "not_an_admin": "Nie jest administratorem.", + "site_already_exists": "Witryna już istnieje.", + "couldnt_update_site": "Nie udało się zaktualizować witryny.", + "couldnt_find_that_username_or_email": "Nie udało się znaleźć takiej nazwy użytkownika lub adresu email.", + "password_incorrect": "Hasło niepoprawne.", + "passwords_dont_match": "Hasła nie pasują do siebie.", + "admin_already_created": "Wybacz, funkcja administratora jest już przypisana.", + "user_already_exists": "Użytkownik już istnieje.", + "email_already_exists": "Email już istnieje.", + "couldnt_update_user": "Nie udało się zaktualizować użytkownika.", + "system_err_login": "Błąd systemu. Spróbuj wylogować się i następnie zalogować ponownie.", + "couldnt_create_private_message": "Nie udało się stworzyć prywatnej wiadomości.", + "no_private_message_edit_allowed": "Brak uprawnień do edycji prywatnej wiadomości.", + "couldnt_update_private_message": "Nie udało się zaktualizować prywatnej wiadomości.", + "time": "Czas", + "action": "Akcja", + "block_leaving": "Czy na pewno chcesz wyjść?", + "show_context": "Pokaż kontekst" } - diff --git a/ui/translations/pt_BR.json b/ui/translations/pt_BR.json index a9b36b481..c7cce2021 100644 --- a/ui/translations/pt_BR.json +++ b/ui/translations/pt_BR.json @@ -243,5 +243,6 @@ "number_of_upvotes": "{{count}} voto positivo", "number_of_upvotes_plural": "{{count}} votos positivos", "number_of_downvotes": "{{count}} voto negativo", - "number_of_downvotes_plural": "{{count}} votos negativos" + "number_of_downvotes_plural": "{{count}} votos negativos", + "show_context": "Mostrar contexto" }