diff --git a/.dockerignore b/.dockerignore index a29cd2ec..03466f0a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,5 @@ ui/node_modules ui/dist server/target -docker/dev/volumes -docker/federation/volumes +docs .git diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index be4c3f62..e2aa0547 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,4 +1,3 @@ # These are supported funding model platforms patreon: dessalines -liberapay: Lemmy diff --git a/.gitignore b/.gitignore index 9f7fa1e3..e36af129 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,5 @@ ansible/inventory -ansible/inventory_dev ansible/passwords/ -docker/lemmy_mine.hjson -docker/dev/env_deploy.sh build/ .idea/ -ui/src/translations -docker/dev/volumes -docker/federation-test/volumes +docker/dev/config/config.hjson diff --git a/.travis.yml b/.travis.yml index 602a8613..5fc26cde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,30 +5,21 @@ matrix: allow_failures: - rust: nightly fast_finish: true -cache: cargo +cache: + directories: + - /home/travis/.cargo before_cache: - - rm -rfv target/debug/incremental/lemmy_server-* - - rm -rfv target/debug/.fingerprint/lemmy_server-* - - rm -rfv target/debug/build/lemmy_server-* - - rm -rfv target/debug/deps/lemmy_server-* - - rm -rfv target/debug/lemmy_server.d + - rm -rf /home/travis/.cargo/registry before_script: - - psql -c "create user lemmy with password 'password' superuser;" -U postgres - - psql -c 'create database lemmy with owner lemmy;' -U postgres - - rustup component add clippy --toolchain stable-x86_64-unknown-linux-gnu + - psql -c "create user rrr with password 'rrr' superuser;" -U postgres + - psql -c 'create database rrr with owner rrr;' -U postgres before_install: - cd server script: - # Default checks, but fail if anything is detected - - cargo build - - cargo clippy -- -D clippy::style -D clippy::correctness -D clippy::complexity -D clippy::perf - - cargo install diesel_cli --no-default-features --features postgres --force - diesel migration run + - cargo build - cargo test env: - global: - - DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy - - RUST_TEST_THREADS=1 - + - DATABASE_URL=postgres://rrr:rrr@localhost/rrr addons: postgresql: "9.4" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index e0270d4c..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,35 +0,0 @@ -# Code of Conduct - -- We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. -- Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all. -- Please be kind and courteous. There’s no need to be mean or rude. -- Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer. -- Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works. -- We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term “harassment” as including the definition in the Citizen Code of Conduct; if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups. -- Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact one of the channel ops or any of the Lemmy moderation team immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back. -- Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome. - -[**Message the Moderation Team on Mastodon**](https://mastodon.social/@LemmyDev) - -[**Email The Moderation Team**](mailto:contact@lemmy.ml) - -## Moderation - -These are the policies for upholding our community’s standards of conduct. If you feel that a thread needs moderation, please contact the Lemmy moderation team . - -1. Remarks that violate the Lemmy standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner.) -2. Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed. -3. Moderators will first respond to such remarks with a warning, at the same time the offending content will likely be removed whenever possible. -4. If the warning is unheeded, the user will be “kicked,” i.e., kicked out of the communication channel to cool off. -5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded. -6. Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology. -7. If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, in private. Complaints about bans in-channel are not allowed. -8. Moderators are held to a higher standard than other community members. If a moderator creates an inappropriate situation, they should expect less leeway than others. - -In the Lemmy community we strive to go the extra step to look out for each other. Don’t just aim to be technically unimpeachable, try to be your best self. In particular, avoid flirting with offensive or sensitive issues, particularly if they’re off-topic; this all too often leads to unnecessary fights, hurt feelings, and damaged trust; worse, it can drive people away from the community entirely. - -And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could’ve communicated better — remember that it’s your responsibility to make others comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust. - -The enforcement policies listed above apply to all official Lemmy venues; including git repositories under [github.com/LemmyNet/lemmy](https://github.com/LemmyNet/lemmy) and [yerbamate.dev/dessalines/lemmy](https://yerbamate.dev/dessalines/lemmy), the [Matrix channel](https://matrix.to/#/!BZVTUuEiNmRcbFeLeI:matrix.org?via=matrix.org&via=privacytools.io&via=permaweb.io); and all instances under lemmy.ml. For other projects adopting the Rust Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion. - -Adapted from the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct), which is based on the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 7827d822..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -# Contributing - -See [here](https://dev.lemmy.ml/docs/contributing.html) for contributing Instructions. - diff --git a/README.md b/README.md index 09b2540f..fa35788d 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,99 @@ -
- -![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/LemmyNet/lemmy.svg) -[![Build Status](https://travis-ci.org/LemmyNet/lemmy.svg?branch=master)](https://travis-ci.org/LemmyNet/lemmy) -[![GitHub issues](https://img.shields.io/github/issues-raw/LemmyNet/lemmy.svg)](https://github.com/LemmyNet/lemmy/issues) -[![Docker Pulls](https://img.shields.io/docker/pulls/dessalines/lemmy.svg)](https://cloud.docker.com/repository/docker/dessalines/lemmy/) -[![Translation status](http://weblate.yerbamate.dev/widgets/lemmy/-/lemmy/svg-badge.svg)](http://weblate.yerbamate.dev/engage/lemmy/) -[![License](https://img.shields.io/github/license/LemmyNet/lemmy.svg)](LICENSE) -![GitHub stars](https://img.shields.io/github/stars/LemmyNet/lemmy?style=social) -
-

- + - -

Lemmy

-

- A link aggregator / reddit clone for the fediverse. -
-
- View Site - · - Documentation - · - Report Bug - · - Request Feature - · - Releases -

-## About The Project +

Lemmy

+ +
+ +[![Github](https://img.shields.io/badge/-Github-blue)](https://github.com/dessalines/lemmy) +[![Gitlab](https://img.shields.io/badge/-Gitlab-yellowgreen)](https://gitlab.com/dessalines/lemmy) +![Mastodon Follow](https://img.shields.io/mastodon/follow/810572?domain=https%3A%2F%2Fmastodon.social&style=social) +![GitHub stars](https://img.shields.io/github/stars/dessalines/lemmy?style=social) +[![Matrix](https://img.shields.io/matrix/rust-reddit-fediverse:matrix.org.svg?label=matrix-chat)](https://riot.im/app/#/room/#rust-reddit-fediverse:matrix.org) +![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/dessalines/lemmy.svg) +[![Build Status](https://travis-ci.org/dessalines/lemmy.svg?branch=master)](https://travis-ci.org/dessalines/lemmy) +[![GitHub issues](https://img.shields.io/github/issues-raw/dessalines/lemmy.svg)](https://github.com/dessalines/lemmy/issues) +[![Docker Pulls](https://img.shields.io/docker/pulls/dessalines/lemmy.svg)](https://cloud.docker.com/repository/docker/dessalines/lemmy/) +![GitHub commit activity](https://img.shields.io/github/commit-activity/m/dessalines/lemmy.svg) +![GitHub repo size](https://img.shields.io/github/repo-size/dessalines/lemmy.svg) +[![License](https://img.shields.io/github/license/dessalines/lemmy.svg)](LICENSE) +[![Patreon](https://img.shields.io/badge/-Support%20on%20Patreon-blueviolet.svg)](https://www.patreon.com/dessalines) +
+ +--- + +

A link aggregator / reddit clone for the fediverse. +
+

+ +[Lemmy Dev instance](https://dev.lemmy.ml) *for testing purposes only* + +This is a **very early beta version**, and a lot of features are currently broken or in active development, such as federation. Front Page|Post ---|--- -![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/chat_screen.png) +![main screen](https://i.imgur.com/kZSRcRu.png)|![chat screen](https://i.imgur.com/4XghNh6.png) -[Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse). +## 📝 Table of Contents + + + +- [Features](#features) +- [About](#about) + * [Why's it called Lemmy?](#whys-it-called-lemmy) +- [Install](#install) + * [Docker](#docker) + + [Updating](#updating) + * [Ansible](#ansible) + * [Kubernetes](#kubernetes) +- [Develop](#develop) + * [Docker Development](#docker-development) + * [Local Development](#local-development) + + [Requirements](#requirements) + + [Set up Postgres DB](#set-up-postgres-db) + + [Running](#running) +- [Configuration](#configuration) +- [Documentation](#documentation) +- [Support](#support) +- [Translations](#translations) +- [Credits](#credits) + + + +## Features + +- Open source, [AGPL License](/LICENSE). +- Self hostable, easy to deploy. + - Comes with [Docker](#docker), [Ansible](#ansible), [Kubernetes](#kubernetes). +- Clean, mobile-friendly interface. + - Live-updating Comment threads. + - Full vote scores `(+/-)` like old reddit. + - Themes, including light, dark, and solarized. + - Emojis with autocomplete support. Start typing `:` + - User tagging using `@`, Community tagging using `#`. + - Notifications, on comment replies and when you're tagged. + - i18n / internationalization support. + - RSS / Atom feeds for `All`, `Subscribed`, `Inbox`, `User`, and `Community`. +- Cross-posting support. + - A *similar post search* when creating new posts. Great for question / answer communities. +- Moderation abilities. + - Public Moderation Logs. + - Both site admins, and community moderators, who can appoint other moderators. + - Can lock, remove, and restore posts and comments. + - Can ban and unban users from communities and the site. + - Can transfer site and communities to others. +- Can fully erase your data, replacing all posts and comments. +- NSFW post / community support. +- High performance. + - Server is written in rust. + - Front end is `~80kB` gzipped. + - Supports arm64 / Raspberry Pi. + +## About + +[Lemmy](https://github.com/dessalines/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse). For a link aggregator, this means a user registered on one server can subscribe to forums on any other server, and can have discussions with users registered elsewhere. @@ -44,8 +101,6 @@ The overall goal is to create an easily self-hostable, decentralized alternative Each lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing. -*Note: Federation is still in active development and the WebSocket, as well as, HTTP API are currently unstable* - ### Why's it called Lemmy? - Lead singer from [Motörhead](https://invidio.us/watch?v=pWB5JZRGl0U). @@ -53,92 +108,176 @@ Each lemmy server can set its own moderation policy; appointing site-wide admins - The [Koopa from Super Mario](https://www.mariowiki.com/Lemmy_Koopa). - The [furry rodents](http://sunchild.fpwc.org/lemming-the-little-giant-of-the-north/). -### Built With +Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Inferno](https://www.infernojs.org), [Typescript](https://www.typescriptlang.org/) and [Diesel](http://diesel.rs/). -- [Rust](https://www.rust-lang.org) -- [Actix](https://actix.rs/) -- [Diesel](http://diesel.rs/) -- [Inferno](https://infernojs.org) -- [Typescript](https://www.typescriptlang.org/) +## Install -## Features +### Docker -- Open source, [AGPL License](/LICENSE). -- Self hostable, easy to deploy. - - Comes with [Docker](#docker), [Ansible](#ansible), [Kubernetes](#kubernetes). -- Clean, mobile-friendly interface. - - Only a minimum of a username and password is required to sign up! - - User avatar support. - - Live-updating Comment threads. - - Full vote scores `(+/-)` like old reddit. - - Themes, including light, dark, and solarized. - - Emojis with autocomplete support. Start typing `:` - - User tagging using `@`, Community tagging using `#`. - - Integrated image uploading in both posts and comments. - - A post can consist of a title and any combination of self text, a URL, or nothing else. - - Notifications, on comment replies and when you're tagged. - - Notifications can be sent via email. - - Private messaging support. - - i18n / internationalization support. - - RSS / Atom feeds for `All`, `Subscribed`, `Inbox`, `User`, and `Community`. -- Cross-posting support. - - A *similar post search* when creating new posts. Great for question / answer communities. -- Moderation abilities. - - Public Moderation Logs. - - Can sticky posts to the top of communities. - - Both site admins, and community moderators, who can appoint other moderators. - - Can lock, remove, and restore posts and comments. - - Can ban and unban users from communities and the site. - - Can transfer site and communities to others. -- Can fully erase your data, replacing all posts and comments. -- NSFW post / community support. -- OEmbed support via Iframely. -- High performance. - - Server is written in rust. - - Front end is `~80kB` gzipped. - - Supports arm64 / Raspberry Pi. +Make sure you have both docker and docker-compose(>=`1.24.0`) installed: -## Installation +```bash +mkdir lemmy/ +cd lemmy/ +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/.env +# Edit the .env if you want custom passwords +docker-compose up -d +``` -- [Docker](https://dev.lemmy.ml/docs/administration_install_docker.html) -- [Ansible](https://dev.lemmy.ml/docs/administration_install_ansible.html) -- [Kubernetes](https://dev.lemmy.ml/docs/administration_install_kubernetes.html) +and go to http://localhost:8536. -## Support / Donate +[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 +``` +#### Updating + +To update to the newest version, run: + +```bash +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml +docker-compose up -d +``` + +### Ansible + +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: + +```bash +git clone https://github.com/dessalines/lemmy.git +cd lemmy/ansible/ +cp inventory.example inventory +nano inventory # enter your server, domain, contact email +ansible-playbook lemmy.yml --become +``` + +### Kubernetes + +You'll need to have an existing Kubernetes cluster and [storage class](https://kubernetes.io/docs/concepts/storage/storage-classes/). +Setting this up will vary depending on your provider. +To try it locally, you can use [MicroK8s](https://microk8s.io/) or [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/). + +Once you have a working cluster, edit the environment variables and volume sizes in `docker/k8s/*.yml`. +You may also want to change the service types to use `LoadBalancer`s depending on where you're running your cluster (add `type: LoadBalancer` to `ports)`, or `NodePort`s. +By default they will use `ClusterIP`s, which will allow access only within the cluster. See the [docs](https://kubernetes.io/docs/concepts/services-networking/service/) for more on networking in Kubernetes. + +**Important** Running a database in Kubernetes will work, but is generally not recommended. +If you're deploying on any of the common cloud providers, you should consider using their managed database service instead (RDS, Cloud SQL, Azure Databse, etc.). + +Now you can deploy: + +```bash +# Add `-n foo` if you want to deploy into a specific namespace `foo`; +# otherwise your resources will be created in the `default` namespace. +kubectl apply -f docker/k8s/db.yml +kubectl apply -f docker/k8s/pictshare.yml +kubectl apply -f docker/k8s/lemmy.yml +``` + +If you used a `LoadBalancer`, you should see it in your cloud provider's console. + +## Develop + +### Docker Development + +Run: + +```bash +git clone https://github.com/dessalines/lemmy +cd lemmy/docker/dev +./docker_update.sh # This builds and runs it, updating for your changes +``` + +and go to http://localhost:8536. + +### Local Development + +#### Requirements + +- [Rust](https://www.rust-lang.org/) +- [Yarn](https://yarnpkg.com/en/) +- [Postgres](https://www.postgresql.org/) + +#### Set up Postgres DB + +```bash + psql -c "create user lemmy with password 'password' superuser;" -U postgres + psql -c 'create database lemmy with owner lemmy;' -U postgres + export DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy +``` + +#### Running + +```bash +git clone https://github.com/dessalines/lemmy +cd lemmy +./install.sh +# For live coding, where both the front and back end, automagically reload on any save, do: +# cd ui && yarn start +# cd server && cargo watch -x run +``` + +## Configuration + +The configuration is based on the file [defaults.hjson](server/config/defaults.hjson). This file also contains +documentation for all the available options. To override the defaults, you can copy the options you want to change +into your local `config.hjson` file. + +Additionally, you can override any config files with environment variables. These have the same name as the config +options, and are prefixed with `LEMMY_`. For example, you can override the `database.password` with +`LEMMY_DATABASE_PASSWORD=my_password`. + +An additional option `LEMMY_DATABASE_URL` is available, which can be used with a PostgreSQL connection string like +`postgres://lemmy:password@lemmy_db:5432/lemmy`, passing all connection details at once. + +## Documentation + +- [Websocket API for App developers](docs/api.md) +- [ActivityPub API.md](docs/apub_api_outline.md) +- [Goals](docs/goals.md) +- [Ranking Algorithm](docs/ranking.md) + +## Support Lemmy is free, open-source software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project. -- [Support on Liberapay](https://liberapay.com/Lemmy). - [Support on Patreon](https://www.patreon.com/dessalines). -- [Support on OpenCollective](https://opencollective.com/lemmy). -- [List of Sponsors](https://dev.lemmy.ml/sponsors). - -### Crypto - +- [Sponsor List](https://dev.lemmy.ml/sponsors). - bitcoin: `1Hefs7miXS5ff5Ck5xvmjKjXf5242KzRtK` - ethereum: `0x400c96c96acbC6E7B3B43B1dc1BB446540a88A01` - monero: `41taVyY6e1xApqKyMVDRVxJ76sPkfZhALLTjRvVKpaAh2pBd4wv9RgYj1tSPrx8wc6iE1uWUfjtQdTmTy2FGMeChGVKPQuV` -## Contributing +## Translations -- [Contributing instructions](https://dev.lemmy.ml/docs/contributing.html) -- [Docker Development](https://dev.lemmy.ml/docs/contributing_docker_development.html) -- [Local Development](https://dev.lemmy.ml/docs/contributing_local_development.html) +If you'd like to add translations, take a look a look at the [English translation file](ui/src/translations/en.ts). -### Translations +- Languages supported: English (`en`), Chinese (`zh`), Dutch (`nl`), Esperanto (`eo`), French (`fr`), Spanish (`es`), Swedish (`sv`), German (`de`), Russian (`ru`), Italian (`it`). -If you want to help with translating, take a look at [Weblate](https://weblate.yerbamate.dev/projects/lemmy/). +lang | done | missing +--- | --- | --- +de | 100% | +eo | 86% | number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,theme,are_you_sure,yes,no +es | 95% | archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default +fr | 95% | archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default +it | 96% | archive_link,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default +nl | 88% | preview,upload_image,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,theme +ru | 82% | cross_posts,cross_post,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,archive_link,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,recent_comments,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no +sv | 95% | archive_link,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default +zh | 80% | cross_posts,cross_post,users,number_of_communities,preview,upload_image,formatting_help,view_source,sticky,unsticky,archive_link,settings,stickied,delete_account,delete_account_confirm,banned,creator,number_online,replies,mentions,forgot_password,reset_password_mail_sent,password_change,new_password,no_email_setup,language,browser_default,recent_comments,nsfw,show_nsfw,theme,monero,by,to,transfer_community,transfer_site,are_you_sure,yes,no -## Contact -- [Mastodon](https://mastodon.social/@LemmyDev) -- [Matrix](https://riot.im/app/#/room/#rust-reddit-fediverse:matrix.org) +If you'd like to update this report, run: -## Code Mirrors - -- [GitHub](https://github.com/LemmyNet/lemmy) -- [Gitea](https://yerbamate.dev/LemmyNet/lemmy) -- [GitLab](https://gitlab.com/dessalines/lemmy) +```bash +cd ui +ts-node translation_report.ts > tmp # And replace the text above. +``` ## Credits diff --git a/RELEASES.md b/RELEASES.md deleted file mode 100644 index 5a4c7645..00000000 --- a/RELEASES.md +++ /dev/null @@ -1,22 +0,0 @@ -# Lemmy v0.6.0 Release (2020-01-16) - -`v0.6.0` is here, and we've closed [41 issues!](https://github.com/LemmyNet/lemmy/milestone/15?closed=1) - -This is the biggest release by far: - -- Avatars! -- Optional Email notifications for username mentions, post and comment replies. -- Ability to change your password and email address. -- Can set a custom language. -- Lemmy-wide settings to disable downvotes, and close registration. -- A better documentation system, hosted in lemmy itself. -- [Huge DB performance gains](https://github.com/LemmyNet/lemmy/issues/411) (everthing down to < `30ms`) by using materialized views. -- Fixed major issue with similar post URL and title searching. -- Upgraded to Actix `2.0` -- Faster comment / post voting. -- Better small screen support. -- Lots of bug fixes, refactoring of back end code. - -Another major announcement is that Lemmy now has another lead developer besides me, [@felix@radical.town](https://radical.town/@felix). Theyve created a better documentation system, implemented RSS feeds, simplified docker and project configs, upgraded actix, working on federation, a whole lot else. - -https://dev.lemmy.ml diff --git a/ansible/VERSION b/ansible/VERSION deleted file mode 100644 index fe18d878..00000000 --- a/ansible/VERSION +++ /dev/null @@ -1 +0,0 @@ -v0.6.74 diff --git a/ansible/lemmy.yml b/ansible/lemmy.yml index bc01623f..acdb6b06 100644 --- a/ansible/lemmy.yml +++ b/ansible/lemmy.yml @@ -29,25 +29,25 @@ - { 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/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' } - vars: - lemmy_docker_image: "dessalines/lemmy:{{ lookup('file', 'VERSION') }}" - lemmy_port: "8536" - pictshare_port: "8537" - iframely_port: "8538" - - - name: add config file (only during initial setup) - template: src='templates/config.hjson' dest='/lemmy/lemmy.hjson' mode='0600' force='no' owner='1000' group='1000' + - name: add all template files + template: src={{item.src}} dest={{item.dest}} + with_items: + - { src: 'templates/env', dest: '/lemmy/.env' } + - { src: 'templates/config.hjson', dest: '/lemmy/config.hjson' } + - { src: '../docker/prod/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } + - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf' } vars: 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: set env file permissions + file: + path: "/lemmy/.env" + state: touch + mode: 0600 + access_time: preserve + modification_time: preserve + - name: enable and start docker service systemd: name: docker @@ -68,4 +68,4 @@ special_time=daily name=certbot-renew-lemmy user=root - job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'nginx -s reload'" + job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'docker-compose -f /peertube/docker-compose.yml exec nginx nginx -s reload'" diff --git a/ansible/lemmy_dev.yml b/ansible/lemmy_dev.yml deleted file mode 100644 index e9b8364f..00000000 --- a/ansible/lemmy_dev.yml +++ /dev/null @@ -1,101 +0,0 @@ ---- -- hosts: all - vars: - lemmy_docker_image: "lemmy:dev" - - # Install python if required - # https://www.josharcher.uk/code/ansible-python-connection-failure-ubuntu-server-1604/ - gather_facts: False - pre_tasks: - - name: install python for Ansible - raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal python-setuptools) - args: - executable: /bin/bash - register: output - changed_when: output.stdout != "" - - setup: # gather facts - - tasks: - - name: install dependencies - apt: - pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot', 'python-certbot-nginx'] - - - name: request initial letsencrypt certificate - command: certbot certonly --nginx --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' - args: - creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' - - - name: create lemmy folder - file: path={{item.path}} state=directory - with_items: - - { 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/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/lemmy.hjson' mode='0600' force='no' owner='1000' group='1000' - vars: - 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 - register: image_build - - - name: find hash of the new docker image - set_fact: - image_hash: "{{ image_build.stdout | regex_search('(?<=Successfully built )[0-9a-f]{12}') }}" - - # this does not use become so that the output file is written as non-root user and is easy to delete later - - name: save dev docker image to file - local_action: shell sudo docker save lemmy:dev > lemmy-dev.tar - - - name: copy dev docker image to server - copy: src=lemmy-dev.tar dest=/lemmy/lemmy-dev.tar - - - name: import docker image - docker_image: - name: lemmy - tag: dev - load_path: /lemmy/lemmy-dev.tar - source: load - force_source: yes - register: image_import - - - name: delete remote image file - file: path=/lemmy/lemmy-dev.tar state=absent - - - name: delete local image file - local_action: file path=lemmy-dev.tar state=absent - - - name: enable and start docker service - systemd: - name: docker - enabled: yes - state: started - - # cant pull here because that fails due to lemmy:dev (without dessalines/) not being on docker hub, but that shouldnt - # be a problem for testing - - name: start docker-compose - docker_compose: - project_src: /lemmy/ - state: present - recreate: always - ignore_errors: yes - - - name: reload nginx with new config - shell: nginx -s reload - - - name: certbot renewal cronjob - cron: - special_time=daily - name=certbot-renew-lemmy - user=root - job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'nginx -s reload'" diff --git a/ansible/templates/config.hjson b/ansible/templates/config.hjson index c0f387c4..adfaf33c 100644 --- a/ansible/templates/config.hjson +++ b/ansible/templates/config.hjson @@ -1,14 +1,13 @@ { database: { password: "{{ postgres_password }}" - host: "postgres" } hostname: "{{ domain }}" jwt_secret: "{{ jwt_password }}" - front_end_dir: "/app/dist" email: { - smtp_server: "postfix:25" - smtp_from_address: "noreply@{{ domain }}" - use_tls: false + smtp_server: "{{ smtp_server }}" + smtp_login: "{{ smtp_login }}" + smtp_password: "{{ smtp_password }}" + smtp_from_address: "{{ smtp_from_address }}" } } diff --git a/ansible/templates/docker-compose.yml b/ansible/templates/docker-compose.yml deleted file mode 100644 index 9ec1bfbc..00000000 --- a/ansible/templates/docker-compose.yml +++ /dev/null @@ -1,48 +0,0 @@ -version: '3.3' - -services: - lemmy: - image: {{ lemmy_docker_image }} - ports: - - "127.0.0.1:8536:8536" - restart: always - environment: - - RUST_LOG=error - volumes: - - ./lemmy.hjson:/config/config.hjson:ro - depends_on: - - postgres - - pictshare - - iframely - - postgres: - image: postgres:12-alpine - environment: - - POSTGRES_USER=lemmy - - POSTGRES_PASSWORD={{ postgres_password }} - - POSTGRES_DB=lemmy - volumes: - - ./volumes/postgres:/var/lib/postgresql/data - restart: always - - pictshare: - image: hascheksolutions/pictshare:latest - ports: - - "127.0.0.1:8537:80" - volumes: - - ./volumes/pictshare:/usr/share/nginx/html/data - restart: always - - iframely: - image: dogbin/iframely:latest - ports: - - "127.0.0.1:8061:80" - volumes: - - ./iframely.config.local.js:/iframely/config.local.js:ro - restart: always - - postfix: - image: mwader/postfix-relay - environment: - - POSTFIX_myhostname={{ domain }} - restart: "always" diff --git a/ansible/templates/env b/ansible/templates/env new file mode 100644 index 00000000..c2b15f57 --- /dev/null +++ b/ansible/templates/env @@ -0,0 +1,2 @@ +DATABASE_PASSWORD={{ postgres_password }} +LEMMY_FRONT_END_DIR=/app/dist diff --git a/ansible/templates/nginx.conf b/ansible/templates/nginx.conf index a978c189..4af5be01 100644 --- a/ansible/templates/nginx.conf +++ b/ansible/templates/nginx.conf @@ -1,5 +1,3 @@ -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; server_name {{ domain }}; @@ -36,7 +34,7 @@ server { # It might be nice to compress JSON, but leaving that out to protect against potential # compression+encryption information leak attacks like BREACH. gzip on; - gzip_types text/css application/javascript image/svg+xml; + gzip_types text/css application/javascript; gzip_vary on; # Only connect to this site via HTTPS for the two years @@ -61,13 +59,6 @@ server { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - - # Proxy Cache - 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; - proxy_cache_min_uses 5; } location /pictshare/ { @@ -77,16 +68,10 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; if ($request_uri ~ \.(?:ico|gif|jpe?g|png|webp|bmp|mp4)$) { - add_header Cache-Control "public, max-age=31536000, immutable"; + add_header Cache-Control "public"; + expires max; } } - - location /iframely/ { - 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; - } } # Anonymize IP addresses @@ -100,4 +85,4 @@ map $remote_addr $remote_addr_anon { } 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; +access_log /dev/stdout main; diff --git a/ansible/uninstall.yml b/ansible/uninstall.yml deleted file mode 100644 index 252c5bd1..00000000 --- a/ansible/uninstall.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -- hosts: all - - vars_prompt: - - - name: confirm_uninstall - prompt: "Do you really want to uninstall Lemmy? This will delete all data and can not be reverted [yes/no]" - private: no - - - name: delete_certs - prompt: "Delete certificates? Select 'no' if you want to reinstall Lemmy [yes/no]" - private: no - - tasks: - - name: end play if no confirmation was given - debug: - msg: "Uninstall cancelled, doing nothing" - when: not confirm_uninstall|bool - - - meta: end_play - when: not confirm_uninstall|bool - - - name: stop docker-compose - docker_compose: - project_src: /lemmy/ - state: absent - - - name: delete data - file: path={{item.path}} state=absent - with_items: - - { 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: - name=certbot-renew-lemmy - state=absent - diff --git a/docker/dev/.env b/docker/dev/.env new file mode 100644 index 00000000..4e1bf7f6 --- /dev/null +++ b/docker/dev/.env @@ -0,0 +1,5 @@ +LEMMY_DOMAIN=my_domain +LEMMY_DATABASE_PASSWORD=password +LEMMY_DATABASE_URL=postgres://lemmy:password@lemmy_db:5432/lemmy +LEMMY_JWT_SECRET=changeme +LEMMY_FRONT_END_DIR=/app/dist diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 3ebfa097..080b7fb3 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -10,11 +10,7 @@ RUN yarn install --pure-lockfile COPY ui /app/ui RUN yarn build -FROM ekidd/rust-musl-builder:1.42.0-openssl11 as rust - -# Install lld -RUN sudo apt-get update -y -RUN sudo apt-get install -y lld +FROM ekidd/rust-musl-builder:1.38.0-openssl11 as rust # Cache deps WORKDIR /app @@ -25,20 +21,16 @@ COPY server/Cargo.toml server/Cargo.lock ./ RUN sudo chown -R rust:rust . RUN mkdir -p ./src/bin \ && echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs -RUN RUSTFLAGS="-Clink-arg=-fuse-ld=lld" cargo build +RUN cargo build --release RUN rm -f ./target/x86_64-unknown-linux-musl/release/deps/lemmy_server* COPY server/src ./src/ COPY server/migrations ./migrations/ -# Build for debug -RUN RUSTFLAGS="-Clink-arg=-fuse-ld=lld" cargo build - -FROM ekidd/rust-musl-builder:1.42.0-openssl11 as docs -WORKDIR /app -COPY docs ./docs -RUN sudo chown -R rust:rust . -RUN mdbook build docs/ +# Build for release +RUN cargo build --frozen --release +# Get diesel-cli on there just in case +# RUN cargo install diesel_cli --no-default-features --features postgres FROM alpine:3.10 @@ -46,9 +38,8 @@ FROM alpine:3.10 RUN apk add libpq # Copy resources -COPY server/config/defaults.hjson /config/defaults.hjson -COPY --from=rust /app/server/target/x86_64-unknown-linux-musl/debug/lemmy_server /app/lemmy -COPY --from=docs /app/docs/book/ /app/dist/documentation/ +COPY server/config /config +COPY --from=rust /app/server/target/x86_64-unknown-linux-musl/release/lemmy_server /app/lemmy COPY --from=node /app/ui/dist /app/dist RUN addgroup -g 1000 lemmy diff --git a/docker/prod/Dockerfile.aarch64 b/docker/dev/Dockerfile.aarch64 similarity index 92% rename from docker/prod/Dockerfile.aarch64 rename to docker/dev/Dockerfile.aarch64 index 7a1c833f..1b08c64e 100644 --- a/docker/prod/Dockerfile.aarch64 +++ b/docker/dev/Dockerfile.aarch64 @@ -15,7 +15,7 @@ RUN yarn build FROM multiarch/qemu-user-static as qemu -FROM arm64v8/rust:1.40-buster as rust +FROM arm64v8/rust:1.37-buster as rust COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin #COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin @@ -40,11 +40,10 @@ COPY server/src ./src/ COPY server/migrations ./migrations/ RUN rm -f ./target/release/deps/lemmy_server* ; rm -f ./target/debug/deps/lemmy_server* + # build for release RUN cargo build --frozen --release - -# reduce binary size -RUN strip /app/server/target/release/lemmy_server +# RUN cargo build --frozen # Get diesel-cli on there just in case # RUN cargo install diesel_cli --no-default-features --features postgres @@ -70,7 +69,7 @@ RUN addgroup --gid 1000 lemmy RUN adduser --disabled-password --shell /bin/sh --uid 1000 --ingroup lemmy lemmy # Copy resources -COPY server/config/defaults.hjson /config/defaults.hjson +COPY server/config /app/config COPY --from=rust /app/server/ready /app/lemmy COPY --from=node /app/ui/dist /app/dist diff --git a/docker/prod/Dockerfile.armv7hf b/docker/dev/Dockerfile.armv7hf similarity index 92% rename from docker/prod/Dockerfile.armv7hf rename to docker/dev/Dockerfile.armv7hf index efd937a3..67067a18 100644 --- a/docker/prod/Dockerfile.armv7hf +++ b/docker/dev/Dockerfile.armv7hf @@ -43,10 +43,8 @@ RUN rm -f ./target/release/deps/lemmy_server* ; rm -f ./target/debug/deps/lemmy_ # build for release -RUN cargo build --frozen --release - -# reduce binary size -RUN strip /app/server/target/debug/lemmy_server +#RUN cargo build --frozen --release +RUN cargo build --frozen # Get diesel-cli on there just in case # RUN cargo install diesel_cli --no-default-features --features postgres @@ -71,7 +69,7 @@ RUN addgroup --gid 1000 lemmy RUN adduser --disabled-password --shell /bin/sh --uid 1000 --ingroup lemmy lemmy # Copy resources -COPY server/config/defaults.hjson /config/defaults.hjson +COPY server/config /config COPY --from=rust /app/server/ready /app/lemmy COPY --from=node /app/ui/dist /app/dist diff --git a/docker/prod/Dockerfile.libc b/docker/dev/Dockerfile.libc similarity index 73% rename from docker/prod/Dockerfile.libc rename to docker/dev/Dockerfile.libc index 9c750282..22d8d910 100644 --- a/docker/prod/Dockerfile.libc +++ b/docker/dev/Dockerfile.libc @@ -20,11 +20,10 @@ COPY ui /app/ui RUN yarn build -FROM rust:1.42 as rust +FROM rust:1.37 as rust # Cache deps WORKDIR /app - RUN USER=root cargo new server WORKDIR /app/server COPY server/Cargo.toml server/Cargo.lock ./ @@ -32,37 +31,24 @@ RUN mkdir -p ./src/bin \ && echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs -RUN cargo build --release -#RUN cargo build && \ -# rm -f ./target/release/deps/lemmy_server* ; rm -f ./target/debug/deps/lemmy_server* +#RUN cargo build --release +RUN cargo build && \ + rm -f ./target/release/deps/lemmy_server* ; rm -f ./target/debug/deps/lemmy_server* COPY server/src ./src/ COPY server/migrations ./migrations/ - # build for release # workaround for https://github.com/rust-lang/rust/issues/62896 #RUN RUSTFLAGS='-Ccodegen-units=1' cargo build --release -RUN cargo build --release --frozen - -# reduce binary size -RUN strip /app/server/target/release/lemmy_server +#RUN cargo build --release --frozen +RUN cargo build --frozen # Get diesel-cli on there just in case # RUN cargo install diesel_cli --no-default-features --features postgres # make result place always the same for lemmy container -RUN cp /app/server/target/release/lemmy_server /app/server/ready -#RUN cp /app/server/target/debug/lemmy_server /app/server/ready - - -FROM rust:1.42 as docs - -WORKDIR /app - -# Build docs -COPY docs ./docs -RUN cargo install mdbook -RUN mdbook build docs/ +#RUN cp /app/server/target/release/lemmy_server /app/server/ready +RUN cp /app/server/target/debug/lemmy_server /app/server/ready #FROM alpine:3.10 @@ -79,10 +65,9 @@ RUN addgroup --gid 1000 lemmy RUN adduser --disabled-password --shell /bin/sh --uid 1000 --ingroup lemmy lemmy # Copy resources -COPY server/config/defaults.hjson /config/defaults.hjson -COPY --from=node /app/ui/dist /app/dist -COPY --from=docs /app/docs/book/ /app/dist/documentation/ +COPY server/config /app/config COPY --from=rust /app/server/ready /app/lemmy +COPY --from=node /app/ui/dist /app/dist RUN chown lemmy:lemmy /app/lemmy USER lemmy diff --git a/docker/dev/config/config.hjson b/docker/dev/config/config.hjson new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/docker/dev/config/config.hjson @@ -0,0 +1,2 @@ +{ +} diff --git a/docker/dev/deploy.sh b/docker/dev/deploy.sh new file mode 100755 index 00000000..ba3675f3 --- /dev/null +++ b/docker/dev/deploy.sh @@ -0,0 +1,58 @@ +#!/bin/sh +git checkout master + +# Creating the new tag +new_tag="$1" +git tag $new_tag + +# Setting the version on the front end +cd ../../ +echo "export let version: string = '$(git describe --tags)';" > "ui/src/version.ts" +git add "ui/src/version.ts" +# Setting the version on the backend +echo "pub const VERSION: &'static str = \"$(git describe --tags)\";" > "server/src/version.rs" +git add "server/src/version.rs" + +cd docker/dev + +# Changing the docker-compose prod +sed -i "s/dessalines\/lemmy:.*/dessalines\/lemmy:$new_tag/" ../prod/docker-compose.yml +git add ../prod/docker-compose.yml + +# The commit +git commit -m"Version $new_tag" + +# Registering qemu binaries +docker run --rm --privileged multiarch/qemu-user-static:register --reset + +# Rebuilding docker +docker-compose build +docker tag dev_lemmy:latest dessalines/lemmy:x64-$new_tag +docker push dessalines/lemmy:x64-$new_tag + +# Build for Raspberry Pi / other archs + +# Arm currently not working +# docker build -t lemmy:armv7hf -f Dockerfile.armv7hf ../../ +# docker tag lemmy:armv7hf dessalines/lemmy:armv7hf-$new_tag +# docker push dessalines/lemmy:armv7hf-$new_tag + +# aarch64 +docker build -t lemmy:aarch64 -f Dockerfile.aarch64 ../../ +docker tag lemmy:aarch64 dessalines/lemmy:arm64-$new_tag +docker push dessalines/lemmy:arm64-$new_tag + +# Creating the manifest for the multi-arch build +docker manifest create dessalines/lemmy:$new_tag \ + dessalines/lemmy:x64-$new_tag \ + dessalines/lemmy:arm64-$new_tag + +docker manifest push dessalines/lemmy:$new_tag + +# Push +git push origin $new_tag +git push + +# Pushing to any ansible deploys +cd ../../ansible +ansible-playbook lemmy.yml --become diff --git a/docker/dev/dev_deploy.sh b/docker/dev/dev_deploy.sh new file mode 100755 index 00000000..4bf1d96c --- /dev/null +++ b/docker/dev/dev_deploy.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# Building from the dev branch for dev servers +git checkout dev + +# Rebuilding dev docker +docker-compose build +docker tag dev_lemmy:latest dessalines/lemmy:dev +docker push dessalines/lemmy:dev diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index 1702f66d..92a8ee45 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -1,44 +1,35 @@ version: '3.3' services: - postgres: + lemmy_db: image: postgres:12-alpine environment: - POSTGRES_USER=lemmy - - POSTGRES_PASSWORD=password + - POSTGRES_PASSWORD=${LEMMY_DATABASE_PASSWORD} - POSTGRES_DB=lemmy volumes: - - ./volumes/postgres:/var/lib/postgresql/data + - lemmy_db:/var/lib/postgresql/data restart: always - lemmy: build: context: ../../ dockerfile: docker/dev/Dockerfile ports: - "127.0.0.1:8536:8536" + env_file: + - .env restart: always - environment: - - RUST_LOG=debug volumes: - - ../lemmy.hjson:/config/config.hjson + - ./config/config.hjson:/config/config.hjson:ro depends_on: - - postgres - - pictshare - - iframely - - pictshare: - image: hascheksolutions/pictshare:latest + - lemmy_db + lemmy_pictshare: + image: shtripok/pictshare:latest ports: - "127.0.0.1:8537:80" volumes: - - ./volumes/pictshare:/usr/share/nginx/html/data - restart: always - - iframely: - image: dogbin/iframely:latest - ports: - - "127.0.0.1:8061:80" - volumes: - - ../iframely.config.local.js:/iframely/config.local.js:ro + - lemmy_pictshare:/usr/share/nginx/html/data restart: always +volumes: + lemmy_db: + lemmy_pictshare: diff --git a/docker/dev/docker_update.sh b/docker/dev/docker_update.sh index 21e92ba8..9d0f4542 100755 --- a/docker/dev/docker_update.sh +++ b/docker/dev/docker_update.sh @@ -1,6 +1,2 @@ #!/bin/sh -set -e - -export COMPOSE_DOCKER_CLI_BUILD=1 -export DOCKER_BUILDKIT=1 docker-compose up -d --no-deps --build diff --git a/docker/dev/test_deploy.sh b/docker/dev/test_deploy.sh deleted file mode 100755 index ce85f60c..00000000 --- a/docker/dev/test_deploy.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -set -e - -export COMPOSE_DOCKER_CLI_BUILD=1 -export DOCKER_BUILDKIT=1 - -# Rebuilding dev docker -docker-compose build -docker tag dev_lemmy:latest dessalines/lemmy:test -docker push dessalines/lemmy:test - -# Run the playbook -pushd ../../../lemmy-ansible -ansible-playbook -i test playbooks/site.yml --vault-password-file vault_pass -popd diff --git a/docker/iframely.config.local.js b/docker/iframely.config.local.js deleted file mode 100644 index 8e8bc21f..00000000 --- a/docker/iframely.config.local.js +++ /dev/null @@ -1,283 +0,0 @@ -(function() { - var config = { - - // Specify a path for custom plugins. Custom plugins will override core plugins. - // CUSTOM_PLUGINS_PATH: __dirname + '/yourcustom-plugin-folder', - - DEBUG: false, - RICH_LOG_ENABLED: false, - - // For embeds that require render, baseAppUrl will be used as the host. - baseAppUrl: "http://yourdomain.com", - relativeStaticUrl: "/r", - - // Or just skip built-in renders altogether - SKIP_IFRAMELY_RENDERS: true, - - // For legacy reasons the response format of Iframely open-source is - // different by default as it does not group the links array by rel. - // In order to get the same grouped response as in Cloud API, - // add `&group=true` to your request to change response per request - // or set `GROUP_LINKS` in your config to `true` for a global change. - GROUP_LINKS: true, - - // Number of maximum redirects to follow before aborting the page - // request with `redirect loop` error. - MAX_REDIRECTS: 4, - - SKIP_OEMBED_RE_LIST: [ - // /^https?:\/\/yourdomain\.com\//, - ], - - /* - // Used to pass parameters to the generate functions when creating HTML elements - // disableSizeWrapper: Don't wrap element (iframe, video, etc) in a positioned div - GENERATE_LINK_PARAMS: { - disableSizeWrapper: true - }, - */ - - port: 80, //can be overridden by PORT env var - host: '0.0.0.0', // Dockers beware. See https://github.com/itteco/iframely/issues/132#issuecomment-242991246 - //can be overridden by HOST env var - - // Optional SSL cert, if you serve under HTTPS. - /* - ssl: { - key: require('fs').readFileSync(__dirname + '/key.pem'), - cert: require('fs').readFileSync(__dirname + '/cert.pem'), - port: 443 - }, - */ - - /* - Supported cache engines: - - no-cache - no caching will be used. - - node-cache - good for debug, node memory will be used (https://github.com/tcs-de/nodecache). - - redis - https://github.com/mranney/node_redis. - - memcached - https://github.com/3rd-Eden/node-memcached - */ - CACHE_ENGINE: 'node-cache', - CACHE_TTL: 0, // In seconds. - // 0 = 'never expire' for memcached & node-cache to let cache engine decide itself when to evict the record - // 0 = 'no cache' for redis. Use high enough (e.g. 365*24*60*60*1000) ttl for similar 'never expire' approach instead - - /* - // Redis cache options. - REDIS_OPTIONS: { - host: '127.0.0.1', - port: 6379 - }, - */ - - /* - // Memcached options. See https://github.com/3rd-Eden/node-memcached#server-locations - MEMCACHED_OPTIONS: { - locations: "127.0.0.1:11211" - } - */ - - /* - // Access-Control-Allow-Origin list. - allowedOrigins: [ - "*", - "http://another_domain.com" - ], - */ - - /* - // Uncomment to enable plugin testing framework. - tests: { - mongodb: 'mongodb://localhost:27017/iframely-tests', - single_test_timeout: 10 * 1000, - plugin_test_period: 2 * 60 * 60 * 1000, - relaunch_script_period: 5 * 60 * 1000 - }, - */ - - // If there's no response from remote server, the timeout will occur after - RESPONSE_TIMEOUT: 5 * 1000, //ms - - /* From v1.4.0, Iframely supports HTTP/2 by default. Disable it, if you'd rather not. - Alternatively, you can also disable per origin. See `proxy` option below. - */ - // DISABLE_HTTP2: true, - - // Customize API calls to oembed endpoints. - ADD_OEMBED_PARAMS: [{ - // Endpoint url regexp array. - re: [/^http:\/\/api\.instagram\.com\/oembed/], - // Custom get params object. - params: { - hidecaption: true - } - }, { - re: [/^https:\/\/www\.facebook\.com\/plugins\/page\/oembed\.json/i], - params: { - show_posts: 0, - show_facepile: 0, - maxwidth: 600 - } - }, { - // match i=user or i=moment or i=timeline to configure these types invidually - // see params spec at https://dev.twitter.com/web/embedded-timelines/oembed - re: [/^https?:\/\/publish\.twitter\.com\/oembed\?i=user/i], - params: { - limit: 1, - maxwidth: 600 - } - /* - }, { - // Facebook https://developers.facebook.com/docs/plugins/oembed-endpoints - re: [/^https:\/\/www\.facebook\.com\/plugins\/\w+\/oembed\.json/i], - params: { - // Skip script tag and fb-root div. - omitscript: true - } - */ - }], - - /* - // Configure use of HTTP proxies as needed. - // You don't have to specify all options per regex - just what you need to override - PROXY: [{ - re: [/^https?:\/\/www\.domain\.com/], - proxy_server: 'http://1.2.3.4:8080', - user_agent: 'CHANGE YOUR AGENT', - headers: { - // HTTP headers - // Overrides previous params if overlapped. - }, - request_options: { - // Refer to: https://github.com/request/request - // Overrides previous params if overlapped. - }, - disable_http2: true - }], - */ - - // Customize API calls to 3rd parties. At the very least - configure required keys. - providerOptions: { - locale: "en_US", // ISO 639-1 two-letter language code, e.g. en_CA or fr_CH. - // Will be added as highest priotity in accept-language header with each request. - // Plus is used in FB, YouTube and perhaps other plugins - "twitter": { - "max-width": 550, - "min-width": 250, - hide_media: false, - hide_thread: false, - omit_script: false, - center: false, - // dnt: true, - cache_ttl: 100 * 365 * 24 * 3600 // 100 Years. - }, - readability: { - enabled: false - // allowPTagDescription: true // to enable description fallback to first paragraph - }, - images: { - loadSize: false, // if true, will try an load first bytes of all images to get/confirm the sizes - checkFavicon: false // if true, will verify all favicons - }, - tumblr: { - consumer_key: "INSERT YOUR VALUE" - // media_only: true // disables status embeds for images and videos - will return plain media - }, - google: { - // https://developers.google.com/maps/documentation/embed/guide#api_key - maps_key: "INSERT YOUR VALUE" - }, - - /* - // Optional Camo Proxy to wrap all images: https://github.com/atmos/camo - camoProxy: { - camo_proxy_key: "INSERT YOUR VALUE", - camo_proxy_host: "INSERT YOUR VALUE" - // ssl_only: true // will only proxy non-ssl images - }, - */ - - // List of query parameters to add to YouTube and Vimeo frames - // Start it with leading "?". Or omit alltogether for default values - // API key is optional, youtube will work without it too. - // It is probably the same API key you use for Google Maps. - youtube: { - // api_key: "INSERT YOUR VALUE", - get_params: "?rel=0&showinfo=1" // https://developers.google.com/youtube/player_parameters - }, - vimeo: { - get_params: "?byline=0&badge=0" // https://developer.vimeo.com/player/embedding - }, - - /* - soundcloud: { - old_player: true // enables classic player - }, - giphy: { - media_only: true // disables branded player for gifs and returns just the image - } - */ - /* - bandcamp: { - get_params: '/size=large/bgcol=333333/linkcol=ffffff/artwork=small/transparent=true/', - media: { - album: { - height: 472, - 'max-width': 700 - }, - track: { - height: 120, - 'max-width': 700 - } - } - } - */ - }, - - // WHITELIST_WILDCARD, if present, will be added to whitelist as record for top level domain: "*" - // with it, you can define what parsers do when they run accross unknown publisher. - // If absent or empty, all generic media parsers will be disabled except for known domains - // More about format: https://iframely.com/docs/qa-format - - /* - WHITELIST_WILDCARD: { - "twitter": { - "player": "allow", - "photo": "deny" - }, - "oembed": { - "video": "allow", - "photo": "allow", - "rich": "deny", - "link": "deny" - }, - "og": { - "video": ["allow", "ssl", "responsive"] - }, - "iframely": { - "survey": "allow", - "reader": "allow", - "player": "allow", - "image": "allow" - }, - "html-meta": { - "video": ["allow", "responsive"], - "promo": "allow" - } - } - */ - - // Black-list any of the inappropriate domains. Iframely will return 417 - // At minimum, keep your localhosts blacklisted to avoid SSRF - BLACKLIST_DOMAINS_RE: [ - /^https?:\/\/127\.0\.0\.1/i, - /^https?:\/\/localhost/i, - - // And this is AWS metadata service - // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html - /^https?:\/\/169\.254\.169\.254/ - ] - }; - - module.exports = config; -})(); diff --git a/docker/lemmy.hjson b/docker/lemmy.hjson deleted file mode 100644 index 271fc78d..00000000 --- a/docker/lemmy.hjson +++ /dev/null @@ -1,63 +0,0 @@ -{ - database: { - # username to connect to postgres - user: "lemmy" - # password to connect to postgres - password: "password" - # host where postgres is running - host: "postgres" - # port where postgres can be accessed - port: 5432 - # name of the postgres database for lemmy - database: "lemmy" - # maximum number of active sql connections - pool_size: 5 - } - # the domain name of your instance (eg "dev.lemmy.ml") - hostname: "my_domain" - # address where lemmy should listen for incoming requests - bind: "0.0.0.0" - # port where lemmy should listen for incoming requests - port: 8536 - # json web token for authorization between server and client - jwt_secret: "changeme" - # The dir for the front end - front_end_dir: "/app/dist" - # rate limits for various user actions, by user ip - rate_limit: { - # maximum number of messages created in interval - message: 180 - # interval length for message limit - message_per_second: 60 - # maximum number of posts created in interval - post: 6 - # interval length for post limit - post_per_second: 600 - # maximum number of registrations in interval - register: 3 - # interval length for registration limit - register_per_second: 3600 - } -# # 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: "" -# # login name for smtp server -# smtp_login: "" -# # password to login to the smtp server -# smtp_password: "" -# # address to send emails from, eg "info@your-instance.com" -# smtp_from_address: "" -# use_tls: true -# } -} - diff --git a/docker/prod/.env b/docker/prod/.env new file mode 100644 index 00000000..4e1bf7f6 --- /dev/null +++ b/docker/prod/.env @@ -0,0 +1,5 @@ +LEMMY_DOMAIN=my_domain +LEMMY_DATABASE_PASSWORD=password +LEMMY_DATABASE_URL=postgres://lemmy:password@lemmy_db:5432/lemmy +LEMMY_JWT_SECRET=changeme +LEMMY_FRONT_END_DIR=/app/dist diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile deleted file mode 100644 index bc17a186..00000000 --- a/docker/prod/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -FROM node:10-jessie as node - -WORKDIR /app/ui - -# Cache deps -COPY ui/package.json ui/yarn.lock ./ -RUN yarn install --pure-lockfile - -# Build -COPY ui /app/ui -RUN yarn build - -FROM ekidd/rust-musl-builder:1.42.0-openssl11 as rust - -# Install lld -RUN sudo apt-get update -y -RUN sudo apt-get install -y lld - -# Cache deps -WORKDIR /app -RUN sudo chown -R rust:rust . -RUN USER=root cargo new server -WORKDIR /app/server -COPY server/Cargo.toml server/Cargo.lock ./ -RUN sudo chown -R rust:rust . -RUN mkdir -p ./src/bin \ - && echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs -RUN RUSTFLAGS="-Clink-arg=-fuse-ld=lld" cargo build --release -RUN rm -f ./target/x86_64-unknown-linux-musl/release/deps/lemmy_server* -COPY server/src ./src/ -COPY server/migrations ./migrations/ - -# Build for release -RUN RUSTFLAGS="-Clink-arg=-fuse-ld=lld" cargo build --frozen --release - -# reduce binary size -RUN strip /app/server/target/x86_64-unknown-linux-musl/release/lemmy_server - -FROM ekidd/rust-musl-builder:1.42.0-openssl11 as docs -WORKDIR /app -COPY docs ./docs -RUN sudo chown -R rust:rust . -RUN mdbook build docs/ - - -FROM alpine:3.10 - -# Install libpq for postgres -RUN apk add libpq - -# Copy resources -COPY server/config/defaults.hjson /config/defaults.hjson -COPY --from=rust /app/server/target/x86_64-unknown-linux-musl/release/lemmy_server /app/lemmy -COPY --from=docs /app/docs/book/ /app/dist/documentation/ -COPY --from=node /app/ui/dist /app/dist - -RUN addgroup -g 1000 lemmy -RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy -RUN chown lemmy:lemmy /app/lemmy -USER lemmy -EXPOSE 8536 -CMD ["/app/lemmy"] diff --git a/docker/prod/deploy.sh b/docker/prod/deploy.sh deleted file mode 100755 index 0ad47bcd..00000000 --- a/docker/prod/deploy.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/sh -set -e -git checkout master - -# Import translations -git fetch weblate -git merge weblate/master - -# Creating the new tag -new_tag="$1" -third_semver=$(echo $new_tag | cut -d "." -f 3) - -# Setting the version on the front end -cd ../../ -echo "export const version: string = '$new_tag';" > "ui/src/version.ts" -git add "ui/src/version.ts" -# Setting the version on the backend -echo "pub const VERSION: &str = \"$new_tag\";" > "server/src/version.rs" -git add "server/src/version.rs" -# Setting the version for Ansible -echo $new_tag > "ansible/VERSION" -git add "ansible/VERSION" - -cd docker/prod || exit - -# Changing the docker-compose prod -sed -i "s/dessalines\/lemmy:.*/dessalines\/lemmy:$new_tag/" ../prod/docker-compose.yml -sed -i "s/dessalines\/lemmy:.*/dessalines\/lemmy:$new_tag/" ../../ansible/templates/docker-compose.yml -git add ../prod/docker-compose.yml -git add ../../ansible/templates/docker-compose.yml - -# The commit -git commit -m"Version $new_tag" -git tag $new_tag - -export COMPOSE_DOCKER_CLI_BUILD=1 -export DOCKER_BUILDKIT=1 - -# Rebuilding docker -docker-compose build -docker tag dev_lemmy:latest dessalines/lemmy:x64-$new_tag -docker push dessalines/lemmy:x64-$new_tag - -# Build for Raspberry Pi / other archs - -# Arm currently not working -# docker build -t lemmy:armv7hf -f Dockerfile.armv7hf ../../ -# docker tag lemmy:armv7hf dessalines/lemmy:armv7hf-$new_tag -# docker push dessalines/lemmy:armv7hf-$new_tag - -# aarch64 -# Only do this on major releases (IE the third semver is 0) -if [ $third_semver -eq 0 ]; then - # Registering qemu binaries - docker run --rm --privileged multiarch/qemu-user-static:register --reset - - docker build -t lemmy:aarch64 -f Dockerfile.aarch64 ../../ - docker tag lemmy:aarch64 dessalines/lemmy:arm64-$new_tag - docker push dessalines/lemmy:arm64-$new_tag -fi - -# Creating the manifest for the multi-arch build -if [ $third_semver -eq 0 ]; then - docker manifest create dessalines/lemmy:$new_tag \ - dessalines/lemmy:x64-$new_tag \ - dessalines/lemmy:arm64-$new_tag -else - docker manifest create dessalines/lemmy:$new_tag \ - dessalines/lemmy:x64-$new_tag -fi - -docker manifest push dessalines/lemmy:$new_tag - -# Push -git push origin $new_tag -git push - -# Pushing to any ansible deploys -cd ../../../lemmy-ansible || exit -ansible-playbook -i prod playbooks/site.yml --vault-password-file vault_pass diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 246a9586..aef2f301 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -1,44 +1,34 @@ -version: '2.2' +version: '3.3' services: - postgres: + lemmy_db: image: postgres:12-alpine environment: - POSTGRES_USER=lemmy - - POSTGRES_PASSWORD=password + - POSTGRES_PASSWORD=${DATABASE_PASSWORD} - POSTGRES_DB=lemmy volumes: - - ./volumes/postgres:/var/lib/postgresql/data + - lemmy_db:/var/lib/postgresql/data restart: always - lemmy: - image: dessalines/lemmy:v0.6.74 + image: dessalines/lemmy:v0.5.0.2 + ports: - "127.0.0.1:8536:8536" + env_file: + - .env restart: always - environment: - - RUST_LOG=error volumes: - - ./lemmy.hjson:/config/config.hjson + - ./config.hjson:/config/config.hjson:ro depends_on: - - postgres - - pictshare - - iframely - - pictshare: - image: hascheksolutions/pictshare:latest + - lemmy_db + lemmy_pictshare: + image: shtripok/pictshare:latest ports: - "127.0.0.1:8537:80" volumes: - - ./volumes/pictshare:/usr/share/nginx/html/data + - lemmy_pictshare:/usr/share/nginx/html/data restart: always - mem_limit: 100m - - iframely: - image: dogbin/iframely:latest - ports: - - "127.0.0.1:8061:80" - volumes: - - ./iframely.config.local.js:/iframely/config.local.js:ro - restart: always - mem_limit: 100m +volumes: + lemmy_db: + lemmy_pictshare: diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 7585238e..00000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/docs/src/contributing_websocket_http_api.md b/docs/api.md similarity index 65% rename from docs/src/contributing_websocket_http_api.md rename to docs/api.md index 567f674c..16383d53 100644 --- a/docs/src/contributing_websocket_http_api.md +++ b/docs/api.md @@ -1,184 +1,130 @@ # Lemmy API - -*Note: this may lag behind the actual API endpoints [here](../server/src/api). The API should be considered unstable and may change any time.* +*Note: this may lag behind the actual API endpoints [here](../server/src/api).* - [Data types](#data-types) - [Basic usage](#basic-usage) - * [WebSocket](#websocket) - + [Testing with Websocat](#testing-with-websocat) - + [Testing with the WebSocket JavaScript API](#testing-with-the-websocket-javascript-api) - * [HTTP](#http) - + [Testing with Curl](#testing-with-curl) - - [Get Example](#get-example) - - [Post Example](#post-example) + * [WebSocket Endpoint](#websocket-endpoint) + * [Testing with Websocat](#testing-with-websocat) + * [Testing with the WebSocket JavaScript API](#testing-with-the-websocket-javascript-api) - [Rate limits](#rate-limits) - [Errors](#errors) - [API documentation](#api-documentation) * [Sort Types](#sort-types) - * [Websocket vs HTTP](#websocket-vs-http) * [User / Authentication / Admin actions](#user--authentication--admin-actions) + [Login](#login) - [Request](#request) - [Response](#response) - - [HTTP](#http-1) + [Register](#register) - [Request](#request-1) - [Response](#response-1) - - [HTTP](#http-2) + [Get User Details](#get-user-details) - [Request](#request-2) - [Response](#response-2) - - [HTTP](#http-3) + [Save User Settings](#save-user-settings) - [Request](#request-3) - [Response](#response-3) - - [HTTP](#http-4) + [Get Replies / Inbox](#get-replies--inbox) - [Request](#request-4) - [Response](#response-4) - - [HTTP](#http-5) + [Get User Mentions](#get-user-mentions) - [Request](#request-5) - [Response](#response-5) - - [HTTP](#http-6) - + [Edit User Mention](#edit-user-mention) + + [Mark All As Read](#mark-all-as-read) - [Request](#request-6) - [Response](#response-6) - - [HTTP](#http-7) - + [Mark All As Read](#mark-all-as-read) + + [Delete Account](#delete-account) - [Request](#request-7) - [Response](#response-7) - - [HTTP](#http-8) - + [Delete Account](#delete-account) + + [Add admin](#add-admin) - [Request](#request-8) - [Response](#response-8) - - [HTTP](#http-9) - + [Add admin](#add-admin) + + [Ban user](#ban-user) - [Request](#request-9) - [Response](#response-9) - - [HTTP](#http-10) - + [Ban user](#ban-user) - - [Request](#request-10) - - [Response](#response-10) - - [HTTP](#http-11) * [Site](#site) + [List Categories](#list-categories) + - [Request](#request-10) + - [Response](#response-10) + + [Search](#search) - [Request](#request-11) - [Response](#response-11) - - [HTTP](#http-12) - + [Search](#search) + + [Get Modlog](#get-modlog) - [Request](#request-12) - [Response](#response-12) - - [HTTP](#http-13) - + [Get Modlog](#get-modlog) + + [Create Site](#create-site) - [Request](#request-13) - [Response](#response-13) - - [HTTP](#http-14) - + [Create Site](#create-site) + + [Edit Site](#edit-site) - [Request](#request-14) - [Response](#response-14) - - [HTTP](#http-15) - + [Edit Site](#edit-site) + + [Get Site](#get-site) - [Request](#request-15) - [Response](#response-15) - - [HTTP](#http-16) - + [Get Site](#get-site) + + [Transfer Site](#transfer-site) - [Request](#request-16) - [Response](#response-16) - - [HTTP](#http-17) - + [Transfer Site](#transfer-site) - - [Request](#request-17) - - [Response](#response-17) - - [HTTP](#http-18) - + [Get Site Config](#get-site-config) - - [Request](#request-18) - - [Response](#response-18) - - [HTTP](#http-19) - + [Save Site Config](#save-site-config) - - [Request](#request-19) - - [Response](#response-19) - - [HTTP](#http-20) * [Community](#community) + [Get Community](#get-community) + - [Request](#request-17) + - [Response](#response-17) + + [Create Community](#create-community) + - [Request](#request-18) + - [Response](#response-18) + + [List Communities](#list-communities) + - [Request](#request-19) + - [Response](#response-19) + + [Ban from Community](#ban-from-community) - [Request](#request-20) - [Response](#response-20) - - [HTTP](#http-21) - + [Create Community](#create-community) + + [Add Mod to Community](#add-mod-to-community) - [Request](#request-21) - [Response](#response-21) - - [HTTP](#http-22) - + [List Communities](#list-communities) + + [Edit Community](#edit-community) - [Request](#request-22) - [Response](#response-22) - - [HTTP](#http-23) - + [Ban from Community](#ban-from-community) + + [Follow Community](#follow-community) - [Request](#request-23) - [Response](#response-23) - - [HTTP](#http-24) - + [Add Mod to Community](#add-mod-to-community) + + [Get Followed Communities](#get-followed-communities) - [Request](#request-24) - [Response](#response-24) - - [HTTP](#http-25) - + [Edit Community](#edit-community) + + [Transfer Community](#transfer-community) - [Request](#request-25) - [Response](#response-25) - - [HTTP](#http-26) - + [Follow Community](#follow-community) - - [Request](#request-26) - - [Response](#response-26) - - [HTTP](#http-27) - + [Get Followed Communities](#get-followed-communities) - - [Request](#request-27) - - [Response](#response-27) - - [HTTP](#http-28) - + [Transfer Community](#transfer-community) - - [Request](#request-28) - - [Response](#response-28) - - [HTTP](#http-29) * [Post](#post) + [Create Post](#create-post) + - [Request](#request-26) + - [Response](#response-26) + + [Get Post](#get-post) + - [Request](#request-27) + - [Response](#response-27) + + [Get Posts](#get-posts) + - [Request](#request-28) + - [Response](#response-28) + + [Create Post Like](#create-post-like) - [Request](#request-29) - [Response](#response-29) - - [HTTP](#http-30) - + [Get Post](#get-post) + + [Edit Post](#edit-post) - [Request](#request-30) - [Response](#response-30) - - [HTTP](#http-31) - + [Get Posts](#get-posts) + + [Save Post](#save-post) - [Request](#request-31) - [Response](#response-31) - - [HTTP](#http-32) - + [Create Post Like](#create-post-like) - - [Request](#request-32) - - [Response](#response-32) - - [HTTP](#http-33) - + [Edit Post](#edit-post) - - [Request](#request-33) - - [Response](#response-33) - - [HTTP](#http-34) - + [Save Post](#save-post) - - [Request](#request-34) - - [Response](#response-34) - - [HTTP](#http-35) * [Comment](#comment) + [Create Comment](#create-comment) + - [Request](#request-32) + - [Response](#response-32) + + [Edit Comment](#edit-comment) + - [Request](#request-33) + - [Response](#response-33) + + [Save Comment](#save-comment) + - [Request](#request-34) + - [Response](#response-34) + + [Create Comment Like](#create-comment-like) - [Request](#request-35) - [Response](#response-35) - - [HTTP](#http-36) - + [Edit Comment](#edit-comment) - - [Request](#request-36) - - [Response](#response-36) - - [HTTP](#http-37) - + [Save Comment](#save-comment) - - [Request](#request-37) - - [Response](#response-37) - - [HTTP](#http-38) - + [Create Comment Like](#create-comment-like) - - [Request](#request-38) - - [Response](#response-38) - - [HTTP](#http-39) * [RSS / Atom feeds](#rss--atom-feeds) + [All](#all) + [Community](#community-1) @@ -198,13 +144,13 @@ Request and response strings are in [JSON format](https://www.json.org). -### WebSocket +### WebSocket Endpoint Connect to ws://***host***/api/v1/ws to get started. If the ***`host`*** supports secure connections, you can use wss://***host***/api/v1/ws. -#### Testing with Websocat +### Testing with Websocat [Websocat link](https://github.com/vi/websocat) @@ -213,7 +159,7 @@ If the ***`host`*** supports secure connections, you can use wss://***host A simple test command: `{"op": "ListCategories"}` -#### Testing with the WebSocket JavaScript API +### Testing with the WebSocket JavaScript API [WebSocket JavaScript API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) ```javascript @@ -225,32 +171,6 @@ ws.onopen = function () { })); }; ``` -### HTTP - -Endpoints are at http://***host***/api/v1/***endpoint***. They'll be listed below for each action. - -#### Testing with Curl - -##### Get Example - -``` -curl /community/list?sort=Hot -``` - -##### Post Example - -``` -curl -i -H \ -"Content-Type: application/json" \ --X POST \ --d '{ - "comment_id": X, - "post_id": X, - "score": X, - "auth": "..." -}' \ -/comment/like -``` ## Rate limits @@ -281,11 +201,6 @@ These go wherever there is a `sort` field. The available sort types are: - `TopYear` - the most upvoted posts/communities of the current year. - `TopAll` - the most upvoted posts/communities on the current instance. -### Websocket vs HTTP - -- Below are the websocket JSON requests / responses. For HTTP, ignore all fields except those inside `data`. -- For example, an http login will be a `POST` `{username_or_email: X, password: X}` - ### User / Authentication / Admin actions #### Login @@ -305,19 +220,13 @@ The `jwt` string should be stored and used anywhere `auth` is called for. ##### Response ```rust { - op: "Login", - data: { - jwt: String, - } + op: String, + jwt: String } ``` -##### HTTP - -`POST /user/login` #### Register - Only the first user will be able to be the admin. ##### Request @@ -336,17 +245,11 @@ Only the first user will be able to be the admin. ##### Response ```rust { - op: "Register", - data: { - jwt: String, - } + op: String, + jwt: String } ``` -##### HTTP - -`POST /user/register` - #### Get User Details ##### Request ```rust @@ -367,20 +270,14 @@ Only the first user will be able to be the admin. ##### Response ```rust { - op: "GetUserDetails", - data: { - user: UserView, - follows: Vec, - moderates: Vec, - comments: Vec, - posts: Vec, - } + op: String, + user: UserView, + follows: Vec, + moderates: Vec, + comments: Vec, + posts: Vec, } ``` -##### HTTP - -`GET /user` - #### Save User Settings ##### Request ```rust @@ -398,16 +295,10 @@ Only the first user will be able to be the admin. ##### Response ```rust { - op: "SaveUserSettings", - data: { - jwt: String - } + op: String, + jwt: String } ``` -##### HTTP - -`PUT /save_user_settings` - #### Get Replies / Inbox ##### Request ```rust @@ -425,16 +316,10 @@ Only the first user will be able to be the admin. ##### Response ```rust { - op: "GetReplies", - data: { - replies: Vec, - } + op: String, + replies: Vec, } ``` -##### HTTP - -`GET /user/replies` - #### Get User Mentions ##### Request @@ -453,42 +338,11 @@ Only the first user will be able to be the admin. ##### Response ```rust { - op: "GetUserMentions", - data: { - mentions: Vec, - } + op: String, + mentions: Vec, } ``` -##### HTTP - -`GET /user/mentions` - -#### Edit User Mention -##### Request -```rust -{ - op: "EditUserMention", - data: { - user_mention_id: i32, - read: Option, - auth: String, - } -} -``` -##### Response -```rust -{ - op: "EditUserMention", - data: { - mention: UserMentionView, - } -} -``` -##### HTTP - -`PUT /user/mention` - #### Mark All As Read Marks all user replies and mentions as read. @@ -505,17 +359,11 @@ Marks all user replies and mentions as read. ##### Response ```rust { - op: "MarkAllAsRead", - data: { - replies: Vec, - } + op: String, + replies: Vec, } ``` -##### HTTP - -`POST /user/mark_all_as_read` - #### Delete Account *Permananently deletes your posts and comments* @@ -533,17 +381,11 @@ Marks all user replies and mentions as read. ##### Response ```rust { - op: "DeleteAccount", - data: { - jwt: String, - } + op: String, + jwt: String, } ``` -##### HTTP - -`POST /user/delete_account` - #### Add admin ##### Request ```rust @@ -559,15 +401,10 @@ Marks all user replies and mentions as read. ##### Response ```rust { - op: "AddAdmin", - data: { - admins: Vec, - } + op: String, + admins: Vec, } ``` -##### HTTP - -`POST /admin/add` #### Ban user ##### Request @@ -586,16 +423,11 @@ Marks all user replies and mentions as read. ##### Response ```rust { - op: "BanUser", - data: { - user: UserView, - banned: bool, - } + op: String, + user: UserView, + banned: bool, } ``` -##### HTTP - -`POST /user/ban` ### Site #### List Categories @@ -608,19 +440,13 @@ Marks all user replies and mentions as read. ##### Response ```rust { - op: "ListCategories", - data: { - categories: Vec - } + op: String, + categories: Vec } ``` -##### HTTP - -`GET /categories` #### Search - -Search types are `All, Comments, Posts, Communities, Users, Url` +Search types are `Both, Comments, Posts`. ##### Request ```rust @@ -633,26 +459,17 @@ Search types are `All, Comments, Posts, Communities, Users, Url` sort: String, page: Option, limit: Option, - auth?: Option, } } ``` ##### Response ```rust { - op: "Search", - data: { - type_: String, - comments: Vec, - posts: Vec, - communities: Vec, - users: Vec, - } + op: String, + comments: Vec, + posts: Vec, } ``` -##### HTTP - -`POST /search` #### Get Modlog ##### Request @@ -670,24 +487,18 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "GetModlog", - data: { - removed_posts: Vec, - locked_posts: Vec, - removed_comments: Vec, - removed_communities: Vec, - banned_from_community: Vec, - banned: Vec, - added_to_community: Vec, - added: Vec, - } + op: String, + removed_posts: Vec, + locked_posts: Vec, + removed_comments: Vec, + removed_communities: Vec, + banned_from_community: Vec, + banned: Vec, + added_to_community: Vec, + added: Vec, } ``` -##### HTTP - -`GET /modlog` - #### Create Site ##### Request ```rust @@ -703,17 +514,11 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "CreateSite", - data: { - site: SiteView, - } + op: String, + site: SiteView, } ``` -##### HTTP - -`POST /site` - #### Edit Site ##### Request ```rust @@ -729,15 +534,10 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "EditSite", - data: { - site: SiteView, - } + op: String, + site: SiteView, } ``` -##### HTTP - -`PUT /site` #### Get Site ##### Request @@ -749,17 +549,12 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "GetSite", - data: { - site: Option, - admins: Vec, - banned: Vec, - } + op: String, + site: Option, + admins: Vec, + banned: Vec, } ``` -##### HTTP - -`GET /site` #### Transfer Site ##### Request @@ -775,64 +570,12 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "TransferSite", - data: { - site: Option, - admins: Vec, - banned: Vec, - } + op: String, + site: Option, + admins: Vec, + banned: Vec, } ``` -##### HTTP - -`POST /site/transfer` - -#### Get Site Config -##### Request -```rust -{ - op: "GetSiteConfig", - data: { - auth: String - } -} -``` -##### Response -```rust -{ - op: "GetSiteConfig", - data: { - config_hjson: String, - } -} -``` -##### HTTP - -`GET /site/config` - -#### Save Site Config -##### Request -```rust -{ - op: "SaveSiteConfig", - data: { - config_hjson: String, - auth: String - } -} -``` -##### Response -```rust -{ - op: "SaveSiteConfig", - data: { - config_hjson: String, - } -} -``` -##### HTTP - -`PUT /site/config` ### Community #### Get Community @@ -850,17 +593,12 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "GetCommunity", - data: { - community: CommunityView, - moderators: Vec, - admins: Vec, - } + op: String, + community: CommunityView, + moderators: Vec, + admins: Vec, } ``` -##### HTTP - -`GET /community` #### Create Community ##### Request @@ -879,15 +617,10 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "CreateCommunity", - data: { - community: CommunityView - } + op: String, + community: CommunityView } ``` -##### HTTP - -`POST /community` #### List Communities ##### Request @@ -905,15 +638,10 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "ListCommunities", - data: { - communities: Vec - } + op: String, + communities: Vec } ``` -##### HTTP - -`GET /community/list` #### Ban from Community ##### Request @@ -933,16 +661,11 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "BanFromCommunity", - data: { - user: UserView, - banned: bool, - } + op: String, + user: UserView, + banned: bool, } ``` -##### HTTP - -`POST /community/ban_user` #### Add Mod to Community ##### Request @@ -960,15 +683,10 @@ Search types are `All, Comments, Posts, Communities, Users, Url` ##### Response ```rust { - op: "AddModToCommunity", - data: { - moderators: Vec, - } + op: String, + moderators: Vec, } ``` -##### HTTP - -`POST /community/mod` #### Edit Community Mods and admins can remove and lock a community, creators can delete it. @@ -994,15 +712,10 @@ Mods and admins can remove and lock a community, creators can delete it. ##### Response ```rust { - op: "EditCommunity", - data: { - community: CommunityView - } + op: String, + community: CommunityView } ``` -##### HTTP - -`PUT /community` #### Follow Community ##### Request @@ -1019,15 +732,10 @@ Mods and admins can remove and lock a community, creators can delete it. ##### Response ```rust { - op: "FollowCommunity", - data: { - community: CommunityView - } + op: String, + community: CommunityView } ``` -##### HTTP - -`POST /community/follow` #### Get Followed Communities ##### Request @@ -1042,15 +750,10 @@ Mods and admins can remove and lock a community, creators can delete it. ##### Response ```rust { - op: "GetFollowedCommunities", - data: { - communities: Vec - } + op: String, + communities: Vec } ``` -##### HTTP - -`GET /user/followed_communities` #### Transfer Community ##### Request @@ -1067,17 +770,12 @@ Mods and admins can remove and lock a community, creators can delete it. ##### Response ```rust { - op: "TransferCommunity", - data: { - community: CommunityView, - moderators: Vec, - admins: Vec, - } + op: String, + community: CommunityView, + moderators: Vec, + admins: Vec, } ``` -##### HTTP - -`POST /community/transfer` ### Post #### Create Post @@ -1097,15 +795,10 @@ Mods and admins can remove and lock a community, creators can delete it. ##### Response ```rust { - op: "CreatePost", - data: { - post: PostView - } + op: String, + post: PostView } ``` -##### HTTP - -`POST /post` #### Get Post ##### Request @@ -1121,22 +814,16 @@ Mods and admins can remove and lock a community, creators can delete it. ##### Response ```rust { - op: "GetPost", - data: { - post: PostView, - comments: Vec, - community: CommunityView, - moderators: Vec, - admins: Vec, - } + op: String, + post: PostView, + comments: Vec, + community: CommunityView, + moderators: Vec, + admins: Vec, } ``` -##### HTTP - -`GET /post` #### Get Posts - Post listing types are `All, Subscribed, Community` ##### Request @@ -1156,18 +843,12 @@ Post listing types are `All, Subscribed, Community` ##### Response ```rust { - op: "GetPosts", - data: { - posts: Vec, - } + op: String, + posts: Vec, } ``` -##### HTTP - -`GET /post/list` #### Create Post Like - `score` can be 0, -1, or 1 ##### Request @@ -1184,18 +865,12 @@ Post listing types are `All, Subscribed, Community` ##### Response ```rust { - op: "CreatePostLike", - data: { - post: PostView - } + op: String, + post: PostView } ``` -##### HTTP - -`POST /post/like` #### Edit Post - Mods and admins can remove and lock a post, creators can delete it. ##### Request @@ -1220,17 +895,11 @@ Mods and admins can remove and lock a post, creators can delete it. ##### Response ```rust { - op: "EditPost", - data: { - post: PostView - } + op: String, + post: PostView } ``` -##### HTTP - -`PUT /post` - #### Save Post ##### Request ```rust @@ -1246,15 +915,10 @@ Mods and admins can remove and lock a post, creators can delete it. ##### Response ```rust { - op: "SavePost", - data: { - post: PostView - } + op: String, + post: PostView } ``` -##### HTTP - -`POST /post/save` ### Comment #### Create Comment @@ -1274,19 +938,12 @@ Mods and admins can remove and lock a post, creators can delete it. ##### Response ```rust { - op: "CreateComment", - data: { - comment: CommentView - } + op: String, + comment: CommentView } ``` -##### HTTP - -`POST /comment` - #### Edit Comment - Mods and admins can remove a comment, creators can delete it. ##### Request @@ -1310,15 +967,10 @@ Mods and admins can remove a comment, creators can delete it. ##### Response ```rust { - op: "EditComment", - data: { - comment: CommentView - } + op: String, + comment: CommentView } ``` -##### HTTP - -`PUT /comment` #### Save Comment ##### Request @@ -1335,18 +987,12 @@ Mods and admins can remove a comment, creators can delete it. ##### Response ```rust { - op: "SaveComment", - data: { - comment: CommentView - } + op: String, + comment: CommentView } ``` -##### HTTP - -`POST /comment/save` #### Create Comment Like - `score` can be 0, -1, or 1 ##### Request @@ -1364,15 +1010,10 @@ Mods and admins can remove a comment, creators can delete it. ##### Response ```rust { - op: "CreateCommentLike", - data: { - comment: CommentView - } + op: String, + comment: CommentView } ``` -##### HTTP - -`POST /comment/like` ### RSS / Atom feeds diff --git a/docs/src/contributing_apub_api_outline.md b/docs/apub_api_outline.md similarity index 100% rename from docs/src/contributing_apub_api_outline.md rename to docs/apub_api_outline.md diff --git a/docs/book.toml b/docs/book.toml deleted file mode 100644 index 55cce8c0..00000000 --- a/docs/book.toml +++ /dev/null @@ -1,6 +0,0 @@ -[book] -authors = ["Felix Ableitner"] -language = "en" -multilingual = false -src = "src" -title = "Lemmy Documentation" diff --git a/docs/src/about_goals.md b/docs/goals.md similarity index 74% rename from docs/src/about_goals.md rename to docs/goals.md index e0427481..d8a71794 100644 --- a/docs/src/about_goals.md +++ b/docs/goals.md @@ -1,5 +1,4 @@ # Goals - - Come up with a name / codename. - Must have communities. - Must have threaded comments. @@ -8,7 +7,6 @@ - Use websockets for post / gets to your own instance. # Questions - - How does voting work? Should we go back to the old way of showing up and downvote counts? Or just a score? - Decide on tech to be used - Backend: Actix, Diesel. @@ -19,7 +17,10 @@ - On mobile, allow you to switch between them. Default? # Resources / Potential Libraries - +- Use the [activitypub crate.](https://docs.rs/activitypub/0.1.4/activitypub/) +- https://docs.rs/activitypub/0.1.4/activitypub/ +- [Activitypub vocab.](https://www.w3.org/TR/activitystreams-vocabulary/) +- [Activitypub main](https://www.w3.org/TR/activitypub/) - [Diesel to Postgres data types](https://kotiri.com/2018/01/31/postgresql-diesel-rust-types.html) - [helpful diesel examples](http://siciarz.net/24-days-rust-diesel/) - [Recursive query for adjacency list for nested comments](https://stackoverflow.com/questions/192220/what-is-the-most-efficient-elegant-way-to-parse-a-flat-table-into-a-tree/192462#192462) @@ -35,20 +36,9 @@ - [Temp Icon](https://www.flaticon.com/free-icon/mouse_194242) - [Rust docker build](https://shaneutt.com/blog/rust-fast-small-docker-image-builds/) - [Zurb mentions](https://github.com/zurb/tribute) -- [TippyJS](https://github.com/atomiks/tippyjs) +- Activitypub guides + - https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/ + - https://raw.githubusercontent.com/w3c/activitypub/gh-pages/activitypub-tutorial.txt + - https://github.com/tOkeshu/activitypub-example + - https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/ -## Activitypub guides - -- https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/ -- https://raw.githubusercontent.com/w3c/activitypub/gh-pages/activitypub-tutorial.txt -- https://github.com/tOkeshu/activitypub-example -- https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/ -- Use the [activitypub crate.](https://docs.rs/activitypub/0.1.4/activitypub/) -- https://docs.rs/activitypub/0.1.4/activitypub/ -- [Activitypub vocab.](https://www.w3.org/TR/activitystreams-vocabulary/) -- [Activitypub main](https://www.w3.org/TR/activitypub/) -- [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) -- [Asonix http signatures in rust](https://git.asonix.dog/Aardwolf/http-signature-normalization) diff --git a/docs/img/chat_screen.png b/docs/img/chat_screen.png deleted file mode 100644 index 21a452dc..00000000 Binary files a/docs/img/chat_screen.png and /dev/null differ diff --git a/docs/img/main_screen.png b/docs/img/main_screen.png deleted file mode 100644 index 5d1f0c32..00000000 Binary files a/docs/img/main_screen.png and /dev/null differ diff --git a/docs/img/rank_algorithm.png b/docs/img/rank_algorithm.png deleted file mode 100644 index c8200f91..00000000 Binary files a/docs/img/rank_algorithm.png and /dev/null differ diff --git a/docs/src/about_ranking.md b/docs/ranking.md similarity index 88% rename from docs/src/about_ranking.md rename to docs/ranking.md index fe9e82bb..361dc24d 100644 --- a/docs/src/about_ranking.md +++ b/docs/ranking.md @@ -18,7 +18,7 @@ Score = Upvotes - Downvotes Time = time since submission (in hours) Gravity = Decay gravity, 1.8 is default ``` -- For posts, in order to bring up active posts, it uses the latest comment time (limited to a max creation age of a month ago) + - Use Max(1, score) to make sure all comments are affected by time decay. - Add 3 to the score, so that everything that has less than 3 downvotes will seem new. Otherwise all new comments would stay at zero, near the bottom. - The sign and abs of the score are necessary for dealing with the log of negative scores. @@ -26,4 +26,4 @@ Gravity = Decay gravity, 1.8 is default A plot of rank over 24 hours, of scores of 1, 5, 10, 100, 1000, with a scale factor of 10k. -![](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/rank_algorithm.png) +![](https://i.imgur.com/w8oBLlL.png) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md deleted file mode 100644 index bff5cbf6..00000000 --- a/docs/src/SUMMARY.md +++ /dev/null @@ -1,21 +0,0 @@ -# Summary - -- [About](about.md) - - [Features](about_features.md) - - [Goals](about_goals.md) - - [Post and Comment Ranking](about_ranking.md) - - [Guide](about_guide.md) -- [Administration](administration.md) - - [Install with Docker](administration_install_docker.md) - - [Install with Ansible](administration_install_ansible.md) - - [Install with Kubernetes](administration_install_kubernetes.md) - - [Configuration](administration_configuration.md) - - [Backup and Restore](administration_backup_and_restore.md) -- [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) -- [Lemmy Council](lemmy_council.md) diff --git a/docs/src/about.md b/docs/src/about.md deleted file mode 100644 index 2c0e418b..00000000 --- a/docs/src/about.md +++ /dev/null @@ -1,30 +0,0 @@ -## About The Project - -Front Page|Post ----|--- -![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/chat_screen.png) - -[Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse). - -For a link aggregator, this means a user registered on one server can subscribe to forums on any other server, and can have discussions with users registered elsewhere. - -The overall goal is to create an easily self-hostable, decentralized alternative to reddit and other link aggregators, outside of their corporate control and meddling. - -Each lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing. - -*Note: Federation is still in active development* - -### Why's it called Lemmy? - -- Lead singer from [Motörhead](https://invidio.us/watch?v=pWB5JZRGl0U). -- The old school [video game](). -- The [Koopa from Super Mario](https://www.mariowiki.com/Lemmy_Koopa). -- The [furry rodents](http://sunchild.fpwc.org/lemming-the-little-giant-of-the-north/). - -### Built With - -- [Rust](https://www.rust-lang.org) -- [Actix](https://actix.rs/) -- [Diesel](http://diesel.rs/) -- [Inferno](https://infernojs.org) -- [Typescript](https://www.typescriptlang.org/) diff --git a/docs/src/about_features.md b/docs/src/about_features.md deleted file mode 100644 index a8371fc4..00000000 --- a/docs/src/about_features.md +++ /dev/null @@ -1,34 +0,0 @@ -# Features - -- Open source, [AGPL License](/LICENSE). -- Self hostable, easy to deploy. - - Comes with [Docker](#docker), [Ansible](#ansible), [Kubernetes](#kubernetes). -- Clean, mobile-friendly interface. - - Only a minimum of a username and password is required to sign up! - - User avatar support. - - Live-updating Comment threads. - - Full vote scores `(+/-)` like old reddit. - - Themes, including light, dark, and solarized. - - Emojis with autocomplete support. Start typing `:` - - User tagging using `@`, Community tagging using `#`. - - Integrated image uploading in both posts and comments. - - A post can consist of a title and any combination of self text, a URL, or nothing else. - - Notifications, on comment replies and when you're tagged. - - Notifications can be sent via email. - - i18n / internationalization support. - - RSS / Atom feeds for `All`, `Subscribed`, `Inbox`, `User`, and `Community`. -- Cross-posting support. - - A *similar post search* when creating new posts. Great for question / answer communities. -- Moderation abilities. - - Public Moderation Logs. - - Can sticky posts to the top of communities. - - Both site admins, and community moderators, who can appoint other moderators. - - Can lock, remove, and restore posts and comments. - - Can ban and unban users from communities and the site. - - Can transfer site and communities to others. -- Can fully erase your data, replacing all posts and comments. -- NSFW post / community support. -- High performance. - - Server is written in rust. - - Front end is `~80kB` gzipped. - - Supports arm64 / Raspberry Pi. diff --git a/docs/src/about_guide.md b/docs/src/about_guide.md deleted file mode 100644 index 16788ab9..00000000 --- a/docs/src/about_guide.md +++ /dev/null @@ -1,40 +0,0 @@ -# Lemmy Guide - -Start typing... - -- `@a_user_name` to get a list of usernames. -- `#a_community` to get a list of communities. -- `:emoji` to get a list of emojis. - -## Sorting - -*Applies to both posts and comments* - -Type | Description ---- | --- -Hot | Shows *trending* posts, based on the score, and the most recent comment time. -New | Newest posts. -Top | Shows the highest scoring posts in the given time frame. - -For more detail, check the [Post and Comment Ranking details](about_ranking.md). - -## Markdown Guide - -Type | Or | … to Get ---- | --- | --- -\*Italic\* | \_Italic\_ | _Italic_ -\*\*Bold\*\* | \_\_Bold\_\_ | **Bold** -\# Heading 1 | Heading 1
========= |

Heading 1

-\## Heading 2 | Heading 2
--------- |
Heading 2
-\[Link\](http://a.com) | \[Link\]\[1\]

\[1\]: http://b.org | [Link](https://commonmark.org/) -!\[Image\](http://url/a.png) | !\[Image\]\[1\]

\[1\]: http://url/b.jpg | ![Markdown](https://commonmark.org/help/images/favicon.png) -\> Blockquote | |
Blockquote
-\* List
\* List
\* List | \- List
\- List
\- List
| * List
* List
* List
-1\. One
2\. Two
3\. Three | 1) One
2) Two
3) Three | 1. One
2. Two
3. Three -Horizontal Rule
\--- | Horizontal Rule
\*\*\* | Horizontal Rule

-\`Inline code\` with backticks | |`Inline code` with backticks -\`\`\`
\# code block
print '3 backticks or'
print 'indent 4 spaces'
\`\`\` | ····\# code block
····print '3 backticks or'
····print 'indent 4 spaces' | \# code block
print '3 backticks or'
print 'indent 4 spaces' -::: spoiler hidden or nsfw stuff
*a bunch of spoilers here*
::: | |
hidden or nsfw stuff

a bunch of spoilers here

- -[CommonMark Tutorial](https://commonmark.org/help/tutorial/) - diff --git a/docs/src/administration.md b/docs/src/administration.md deleted file mode 100644 index 9851232d..00000000 --- a/docs/src/administration.md +++ /dev/null @@ -1,3 +0,0 @@ -# Admin info - -Information for Lemmy instance admins, and those who want to start an instance. diff --git a/docs/src/administration_backup_and_restore.md b/docs/src/administration_backup_and_restore.md deleted file mode 100644 index fe97cf88..00000000 --- a/docs/src/administration_backup_and_restore.md +++ /dev/null @@ -1,44 +0,0 @@ -# Backup and Restore Guide - -## Docker and Ansible - -When using docker or ansible, there should be a `volumes` folder, which contains both the database, and all the pictures. Copy this folder to the new instance to restore your data. - -### Incremental Database backup - -To incrementally backup the DB to an `.sql` file, you can run: - -```bash -docker exec -t FOLDERNAME_postgres_1 pg_dumpall -c -U lemmy > lemmy_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql -``` -### A Sample backup script - -```bash -#!/bin/sh -# DB Backup -ssh MY_USER@MY_IP "docker exec -t FOLDERNAME_postgres_1 pg_dumpall -c -U lemmy" > ~/BACKUP_LOCATION/INSTANCE_NAME_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql - -# Volumes folder Backup -rsync -avP -zz --rsync-path="sudo rsync" MY_USER@MY_IP:/LEMMY_LOCATION/volumes ~/BACKUP_LOCATION/FOLDERNAME -``` - -### Restoring the DB - -If you need to restore from a `pg_dumpall` file, you need to first clear out your existing database - -```bash -# Drop the existing DB -docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" - -# Restore from the .sql backup -cat db_dump.sql | docker exec -i FOLDERNAME_postgres_1 psql -U lemmy # restores the db - -# This also might be necessary when doing a db import with a different password. -docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "alter user lemmy with password 'bleh'" -``` - -## More resources - -- https://stackoverflow.com/questions/24718706/backup-restore-a-dockerized-postgresql-database - - diff --git a/docs/src/administration_configuration.md b/docs/src/administration_configuration.md deleted file mode 100644 index 8900ce8d..00000000 --- a/docs/src/administration_configuration.md +++ /dev/null @@ -1,15 +0,0 @@ -# Configuration - -The configuration is based on the file [defaults.hjson](server/config/defaults.hjson). This file also contains documentation for all the available options. To override the defaults, you can copy the options you want to change into your local `config.hjson` file. - -Additionally, you can override any config files with environment variables. These have the same name as the config options, and are prefixed with `LEMMY_`. For example, you can override the `database.password` with -`LEMMY__DATABASE__POOL_SIZE=10`. - -An additional option `LEMMY_DATABASE_URL` is available, which can be used with a PostgreSQL connection string like `postgres://lemmy:password@lemmy_db:5432/lemmy`, passing all connection details at once. - -If the Docker container is not used, manually create the database specified above by running the following commands: - -```bash -cd server -./db-init.sh -``` diff --git a/docs/src/administration_install_ansible.md b/docs/src/administration_install_ansible.md deleted file mode 100644 index 77d901b3..00000000 --- a/docs/src/administration_install_ansible.md +++ /dev/null @@ -1,22 +0,0 @@ -# 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: - -```bash -git clone https://github.com/LemmyNet/lemmy.git -cd lemmy/ansible/ -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 -``` diff --git a/docs/src/administration_install_docker.md b/docs/src/administration_install_docker.md deleted file mode 100644 index 236faa6b..00000000 --- a/docs/src/administration_install_docker.md +++ /dev/null @@ -1,36 +0,0 @@ -# Docker Installation - -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 -# 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 -``` - -After this, have a look at the [config file](administration_configuration.md) named `lemmy.hjson`, and adjust it, in particular the hostname, and possibly the db password. Then run: - -`docker-compose up -d` - -To make Lemmy available outside the server, you need to setup a reverse proxy, like Nginx. [A sample nginx config](https://raw.githubusercontent.com/dessalines/lemmy/master/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, 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 -docker-compose up -d -``` diff --git a/docs/src/administration_install_kubernetes.md b/docs/src/administration_install_kubernetes.md deleted file mode 100644 index 729cb155..00000000 --- a/docs/src/administration_install_kubernetes.md +++ /dev/null @@ -1,24 +0,0 @@ -# Kubernetes Installation - -You'll need to have an existing Kubernetes cluster and [storage class](https://kubernetes.io/docs/concepts/storage/storage-classes/). -Setting this up will vary depending on your provider. -To try it locally, you can use [MicroK8s](https://microk8s.io/) or [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/). - -Once you have a working cluster, edit the environment variables and volume sizes in `docker/k8s/*.yml`. -You may also want to change the service types to use `LoadBalancer`s depending on where you're running your cluster (add `type: LoadBalancer` to `ports)`, or `NodePort`s. -By default they will use `ClusterIP`s, which will allow access only within the cluster. See the [docs](https://kubernetes.io/docs/concepts/services-networking/service/) for more on networking in Kubernetes. - -**Important** Running a database in Kubernetes will work, but is generally not recommended. -If you're deploying on any of the common cloud providers, you should consider using their managed database service instead (RDS, Cloud SQL, Azure Databse, etc.). - -Now you can deploy: - -```bash -# Add `-n foo` if you want to deploy into a specific namespace `foo`; -# otherwise your resources will be created in the `default` namespace. -kubectl apply -f docker/k8s/db.yml -kubectl apply -f docker/k8s/pictshare.yml -kubectl apply -f docker/k8s/lemmy.yml -``` - -If you used a `LoadBalancer`, you should see it in your cloud provider's console. diff --git a/docs/src/contributing.md b/docs/src/contributing.md deleted file mode 100644 index 4eabd6fc..00000000 --- a/docs/src/contributing.md +++ /dev/null @@ -1,39 +0,0 @@ -# Contributing - -Information about contributing to Lemmy, whether it is translating, testing, designing or programming. - -## Issue tracking / Repositories - -- [GitHub (for issues)](https://github.com/LemmyNet/lemmy) -- [Gitea](https://yerbamate.dev/dessalines/lemmy) -- [GitLab](https://gitlab.com/dessalines/lemmy) - -## Translating - -Check out [Lemmy's Weblate](https://weblate.yerbamate.dev/projects/lemmy/) for translations. - - -## Architecture - -### Front end - -- The front end is written in `typescript`, using a react-like framework called [inferno](https://infernojs.org/). All UI elements are reusable `.tsx` components. -- The main page and routing are in `ui/src/index.tsx`. -- The components are located in `ui/src/components`. - -### Back end - -- The back end is written in `rust`, using `diesel`, and `actix`. -- The server source code is split into main sections in `server/src`. These include: - - `db` - The low level database actions. - - Database additions are done using diesel migrations. Run `diesel migration generate xxxxx` to add new things. - - `api` - The high level user interactions (things like `CreateComment`) - - `routes` - The server endpoints . - - `apub` - The activitypub conversions. - - `websocket` - Creates the websocket server. - -## Linting / Formatting - -- Every front and back end commit is automatically formatted then linted using `husky`, and `lint-staged`. -- Rust with `cargo fmt` and `cargo clippy`. -- Typescript with `prettier` and `eslint`. diff --git a/docs/src/contributing_docker_development.md b/docs/src/contributing_docker_development.md deleted file mode 100644 index afa05107..00000000 --- a/docs/src/contributing_docker_development.md +++ /dev/null @@ -1,24 +0,0 @@ -# Docker Development - -## Running - -```bash -sudo apt install git docker-compose -git clone https://github.com/LemmyNet/lemmy -cd lemmy/docker/dev -sudo docker-compose up --no-deps --build -``` - -and go to http://localhost:8536. - -To speed up the Docker compile, add the following to `/etc/docker/daemon.json` and restart Docker. -``` -{ - "features": { - "buildkit": true - } -} -``` - -If the build is still too slow, you will have to use a -[local build](contributing_local_development.md) instead. diff --git a/docs/src/contributing_federation_development.md b/docs/src/contributing_federation_development.md deleted file mode 100644 index 80c9ec7e..00000000 --- a/docs/src/contributing_federation_development.md +++ /dev/null @@ -1,37 +0,0 @@ -# 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://github.com/LemmyNet/lemmy -b federation -``` - -If you already have the Lemmy repo cloned, you need to add a new remote: -```bash -git remote add federation https://github.com/LemmyNet/lemmy -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. diff --git a/docs/src/contributing_local_development.md b/docs/src/contributing_local_development.md deleted file mode 100644 index 066386f5..00000000 --- a/docs/src/contributing_local_development.md +++ /dev/null @@ -1,67 +0,0 @@ -### Ubuntu - - -#### Build requirements: -``` -sudo apt install git cargo libssl-dev pkg-config libpq-dev yarn curl gnupg2 git -# install yarn -curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - -echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list -sudo apt update && sudo apt install yarn -``` - -#### Get the source code -``` -git clone https://github.com/LemmyNet/lemmy.git -# or alternatively from gitea -# git clone https://yerbamate.dev/LemmyNet/lemmy.git -``` - -All the following commands need to be run either in `lemmy/server` or `lemmy/ui`, as indicated -by the `cd` command. - -#### Build the backend (Rust) -``` -cd server -cargo build -# for development, use `cargo check` instead) -``` - -#### Build the frontend (Typescript) -``` -cd ui -yarn -yarn build -``` - -#### Setup postgresql -``` -sudo apt install postgresql -sudo systemctl start postgresql -# initialize postgres database -sudo -u postgres psql -c "create user lemmy with password 'password' superuser;" -U postgres -sudo -u postgres psql -c 'create database lemmy with owner lemmy;' -U postgres -export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy -# or execute server/db-init.sh -``` - -#### Run a local development instance -``` -# run each of these in a seperate terminal -cd server && cargo run -ui & yarn start -``` - -Then open [localhost:4444](http://localhost:4444) in your browser. It will auto-refresh if you edit -any frontend files. For backend coding, you will have to rerun `cargo run`. You can use -`cargo check` as a faster way to find compilation errors. - -To speed up incremental builds, you can add the following to `~/.cargo/config`: -``` -[target.x86_64-unknown-linux-gnu] -rustflags = ["-Clink-arg=-fuse-ld=lld"] -``` - -Note that this setup doesn't include image uploads or link previews (provided by pict-rs and -iframely respectively). If you want to test those, you should use the -[Docker development](contributing_docker_development.md). diff --git a/docs/src/contributing_theming.md b/docs/src/contributing_theming.md deleted file mode 100644 index 25c8ca6d..00000000 --- a/docs/src/contributing_theming.md +++ /dev/null @@ -1,18 +0,0 @@ -# Theming Guide - -Lemmy uses [Bootstrap v4](https://getbootstrap.com/), and very few custom css classes, so any bootstrap v4 compatible theme should work fine. - -## Creating - -- Use a tool like [bootstrap.build](https://bootstrap.build/) to create a bootstrap v4 theme. Export the `bootstrap.min.css` once you're done, and save the `_variables.scss` too. - -## Testing - -- To test out a theme, you can either use your browser's web tools, or a plugin like stylus to copy-paste a theme, when viewing Lemmy. - -## Adding - -1. Copy `{my-theme-name}.min.css` to `ui/assets/css/themes`. (You can also copy the `_variables.scss` here if you want). -1. Go to `ui/src/utils.ts` and add `{my-theme-name}` to the themes list. -1. Test locally -1. Do a pull request with those changes. diff --git a/docs/src/lemmy_council.md b/docs/src/lemmy_council.md deleted file mode 100644 index 96234ff9..00000000 --- a/docs/src/lemmy_council.md +++ /dev/null @@ -1,53 +0,0 @@ -# Lemmy Council - -- A group of lemmy developers and users that use a well-defined democratic process to steer the project in a positive direction, keep it aligned to community goals, and resolve conflicts. - -## Voting / Decision-Making - -### Process -- Anything is open for discussion -- Voting done through matrix chat reacts (thumbs up/thumbs down) -- Require a simple majority for votes. (Maybe 2/3rds for more debated decisions). -- Once a decision is reached democratically, the dicision is binding and all group members have to follow it -- All members of the Lemmy council have equal voting power. -- Voting must stay open for at least 2 days. - -### What gets voted on -- Membership (joining, removing) -- Coding direction - - Priorities / Emphasis - - Controversial features (For example, an unpopular feature should be removed) -- Communication mediums -- Conflict resolution -- dev.lemmy.ml (domain and server) -- lemmy.ml and subdomains (excluding communism.lemmy.ml) -- git repo including mirrors (on github, gitea, etc) -- Any official accounts of the Lemmy project, for example the Mastodon account or the Liberapay account -- Changes to these rules - -## Joining -- We use the following process: anyone who is active around Lemmy can recommend any other active person to join the council. This has to be approved by a majority of the council. -- Active users are defined as those who contribute to Lemmy in some way for at least an hour per week on average, doing things like reporting bugs, discussing rules and features, translating, promoting, developing, or doing other things that aim to improve Lemmy as a whole. - -> people should have joined at least a month ago. -- The member list is public. -- Note: we would like to have a process where community members can elect candidates for the council, but this is not realistic because a single user could easily create multiple accounts and cheat the vote. -- Limit growth to one new member per month at most. - -## Removing members -- Inactive members should be removed from the council after a few months of inactivity, and after receiving a notification about this. -- Members that dont follow binding council decisions should be removed. -- Any member can be removed in a vote. - -## Goals -- We encourage the membership of groups such as LGBT, religious or ethnic minorities, abuse victims, etc etc, and strive to create a safe space for them to express their opinions. We also support measures to increase participation by the previously mentioned groups. -- The following are banned, and will always be harshly punished: fascism, abuse, racism, sexism, etc etc, - -## Communication -- A private Matrix chat for all council members. -- (Once private communities are done) A private community on dev.lemmy.ml for issues. - -## Member List / Contact Info -General Contact [@LemmyDev Mastodon](https://mastodon.social/@LemmyDev) - -- Dessalines [Matrix](https://matrix.to/#/@happydooby:matrix.org) -- Nutomic [Matrix](https://matrix.to/#/@nutomic:matrix.org), [Mastodon](https://radical.town/@felix) diff --git a/install.sh b/install.sh index fb42b26d..bbed1c9b 100755 --- a/install.sh +++ b/install.sh @@ -1,73 +1,16 @@ #!/bin/sh set -e -# Set the database variable to the default first. -# Don't forget to change this string to your actual database parameters -# if you don't plan to initialize the database in this script. -export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy - -# Set other environment variables +export DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy export JWT_SECRET=changeme export HOSTNAME=rrr -yes_no_prompt_invalid() { - echo "Invalid input. Please enter either \"y\" or \"n\"." 1>&2 -} - -ask_to_init_db() { - init_db_valid=0 - init_db_final=0 - while [ "$init_db_valid" == 0 ] - do - read -p "Initialize database (y/n)? " init_db - case "$init_db" in - [yY]* ) init_db_valid=1; init_db_final=1;; - [nN]* ) init_db_valid=1; init_db_final=0;; - * ) yes_no_prompt_invalid;; - esac - echo - done - if [ "$init_db_final" = 1 ] - then - source ./server/db-init.sh - read -n 1 -s -r -p "Press ANY KEY to continue execution of this script, press CTRL+C to quit..." - echo - fi -} - -ask_to_auto_reload() { - auto_reload_valid=0 - auto_reload_final=0 - while [ "$auto_reload_valid" == 0 ] - do - echo "Automagically reload the project when source files are changed?" - echo "ONLY ENABLE THIS FOR DEVELOPMENT!" - read -p "(y/n) " auto_reload - case "$auto_reload" in - [yY]* ) auto_reload_valid=1; auto_reload_final=1;; - [nN]* ) auto_reload_valid=1; auto_reload_final=0;; - * ) yes_no_prompt_invalid;; - esac - echo - done - if [ "$auto_reload_final" = 1 ] - then - cd ui && yarn start - cd server && cargo watch -x run - fi -} - -# Optionally initialize the database -ask_to_init_db - -# Build the web client cd ui yarn yarn build - -# Build and run the backend cd ../server -RUST_LOG=debug cargo run +cargo run -# For live coding, where both the front and back end, automagically reload on any save -ask_to_auto_reload +# For live coding, where both the front and back end, automagically reload on any save, do: +# cd ui && yarn start +# cd server && cargo watch -x run diff --git a/server/.gitignore b/server/.gitignore index 00e9d452..a68a6365 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -2,5 +2,3 @@ .env .idea env_setup.sh -query_testing/*.json -query_testing/*.json.old diff --git a/server/.rustfmt.toml b/server/.rustfmt.toml index 684a7f8a..b196eaa2 100644 --- a/server/.rustfmt.toml +++ b/server/.rustfmt.toml @@ -1,2 +1 @@ tab_spaces = 2 -edition="2018" \ No newline at end of file diff --git a/server/Cargo.lock b/server/Cargo.lock index f91b665b..2bd51b8c 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -2,485 +2,419 @@ # It is not intended for manual editing. [[package]] name = "activitypub" -version = "0.2.0" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "activitystreams-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "activitystreams-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "activitystreams-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", + "activitystreams-derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "activitystreams-types 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "activitystreams-derive" -version = "0.2.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.13.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "activitystreams-traits" -version = "0.2.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "activitystreams-types" -version = "0.3.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "activitystreams-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "activitystreams-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", + "activitystreams-derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix" -version = "0.9.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix_derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)", - "trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-http 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-rt 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "actix_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "trust-dns-resolver 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-codec" -version = "0.2.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-connect" -version = "1.0.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)", - "trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-service 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-utils 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "trust-dns-resolver 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-files" -version = "0.2.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "v_htmlescape 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-http 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-service 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-web 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "v_htmlescape 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-http" -version = "1.0.1" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-connect 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-connect 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-server-config 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-service 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-threadpool 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-utils 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "brotli2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "h2 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "actix-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "trust-dns-resolver 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-router" -version = "0.2.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytestring 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-rt" -version = "1.1.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-threadpool 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-server" -version = "1.0.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-rt 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-server-config 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-service 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-signal 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "actix-server-config" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-service" -version = "1.0.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "actix-testing" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-server 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-threadpool" -version = "0.3.1" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "actix-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "actix-utils" -version = "1.0.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-service 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-web" -version = "2.0.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-router 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-server 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-testing 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-web-codegen 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "awc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", - "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-http 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-router 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-rt 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-server 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-server-config 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-service 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-threadpool 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-utils 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-web-codegen 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "awc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-web-actors" -version = "2.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "actix 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-http 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-web 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix-web-codegen" -version = "0.2.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "actix_derive" -version = "0.5.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "adler32" -version = "1.0.4" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "aho-corasick" -version = "0.7.10" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "ansi_term" -version = "0.11.0" +name = "aho-corasick" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "arc-swap" -version = "0.4.5" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "arrayvec" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ascii_utils" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "async-trait" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "attohttpc" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rustls 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", - "webpki-roots 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "atty" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hermit-abi 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "autocfg" -version = "1.0.0" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "awc" -version = "1.0.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-http 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-service 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "backtrace" -version = "0.3.46" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "backtrace-sys" -version = "0.1.35" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -488,7 +422,7 @@ name = "base64" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -497,47 +431,26 @@ name = "base64" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "base64" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "bcrypt" -version = "0.7.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bitflags" -version = "1.2.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "block-cipher-trait" version = "0.6.2" @@ -546,22 +459,14 @@ dependencies = [ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "blowfish" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -569,8 +474,8 @@ name = "brotli-sys" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -579,7 +484,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -587,102 +492,72 @@ name = "bufstream" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "bumpalo" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "byteorder" -version = "1.3.4" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bytes" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "bytestring" -version = "0.1.5" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "c2-chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cc" -version = "1.0.50" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "chrono" -version = "0.4.11" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "clap" -version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "comrak" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "twoway 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "config" -version = "0.10.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -692,16 +567,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "core-foundation" -version = "0.7.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "core-foundation-sys" -version = "0.7.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -709,114 +584,127 @@ name = "crc32fast" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam-channel" -version = "0.4.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam-utils" -version = "0.7.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "darling" -version = "0.10.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "darling_core" -version = "0.10.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "darling_macro" -version = "0.10.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "derive_builder" -version = "0.9.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "derive_builder_core" -version = "0.9.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "derive_more" -version = "0.99.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "derive_more" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "diesel" -version = "1.4.4" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", + "r2d2 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "diesel_derives" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -824,31 +712,44 @@ name = "diesel_migrations" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "migrations_internals 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "migrations_macros 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "migrations_internals 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "migrations_macros 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "digest" -version = "0.8.1" +name = "docopt" +version = "0.6.86" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "dotenv" -version = "0.15.0" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dtoa" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "dtoa" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "either" -version = "1.5.3" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -857,9 +758,9 @@ version = "0.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -924,65 +825,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "encoding_rs" -version = "0.8.22" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "entities" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "enum-as-inner" -version = "0.3.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "env_logger" -version = "0.7.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "failure" -version = "0.1.8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.33 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "failure_derive" -version = "0.1.7" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "fast_chemail" version = "0.9.6" @@ -993,13 +883,13 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.14" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide_c_api 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1030,7 +920,7 @@ name = "fuchsia-zircon" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1041,144 +931,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-channel" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-core" -version = "0.3.4" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "futures-executor" -version = "0.3.4" +name = "gcc" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-io" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-macro" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-sink" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-task" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-util" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "generic-array" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "getrandom" -version = "0.1.14" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "h2" -version = "0.2.4" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hashbrown" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hashbrown" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "heck" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "hermit-abi" -version = "0.1.11" +name = "hjson" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1186,20 +1007,10 @@ name = "hostname" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", "winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "match_cfg 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "htmlescape" version = "0.3.1" @@ -1207,12 +1018,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "http" -version = "0.2.1" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1222,10 +1033,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "humantime" -version = "1.3.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1235,28 +1046,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "idna" -version = "0.2.0" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "indexmap" -version = "1.3.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "iovec" -version = "0.1.4" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1264,36 +1073,34 @@ name = "ipconfig" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winreg 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "itoa" -version = "0.4.5" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "js-sys" -version = "0.3.37" +name = "itoa" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "wasm-bindgen 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "jsonwebtoken" -version = "7.1.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", - "simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1312,93 +1119,83 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazy_static" -version = "1.4.0" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lemmy_server" version = "0.0.1" dependencies = [ - "activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-files 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "actix-web-actors 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "attohttpc 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bcrypt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "comrak 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "diesel 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "activitypub 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "actix 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-files 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-web 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "actix-web-actors 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bcrypt 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", + "diesel 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "dotenv 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "jsonwebtoken 7.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lettre 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "lettre_email 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rss 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", - "strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rss 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lettre" -version = "0.9.3" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lettre_email" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "email 0.0.20 (registry+https://github.com/rust-lang/crates.io-index)", - "lettre 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "lexical-core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "libc" -version = "0.2.69" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1417,18 +1214,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lock_api" -version = "0.3.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lock_api" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lock_api" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "log" -version = "0.4.8" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1439,84 +1253,100 @@ dependencies = [ "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "maybe-uninit" -version = "2.0.0" +name = "memchr" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "memchr" -version = "2.3.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "migrations_internals" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "diesel 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "diesel 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "migrations_macros" -version = "1.4.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "migrations_internals 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "migrations_internals 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mime" -version = "0.3.16" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "mime_guess" -version = "2.0.3" +version = "2.0.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz-sys" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "miniz_oxide" -version = "0.3.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide_c_api" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mio" -version = "0.6.21" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1528,9 +1358,9 @@ name = "mio-uds" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1546,18 +1376,18 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1566,52 +1396,27 @@ name = "net2" version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "nom" version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "nom" -version = "5.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-bigint" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-integer" -version = "0.1.42" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1619,42 +1424,41 @@ name = "num-traits" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.11" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hermit-abi 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "opaque-debug" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl" -version = "0.10.29" +version = "0.10.25" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1664,128 +1468,164 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl-sys" -version = "0.9.58" +version = "0.9.52" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "owning_ref" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "parking_lot" -version = "0.10.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "parking_lot_core" -version = "0.7.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "pem" +name = "parking_lot_core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "percent-encoding" -version = "2.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "pest" -version = "2.1.3" +name = "phf" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "pest_derive" -version = "2.1.0" +name = "phf_codegen" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pest_generator 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "pest_generator" -version = "2.1.3" +name = "phf_generator" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "pest_meta" -version = "2.1.3" +name = "phf_shared" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "pin-project" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "pin-project-internal 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "pin-project-lite" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pin-utils" -version = "0.1.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "pkg-config" -version = "0.3.17" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1793,57 +1633,80 @@ name = "pq-sys" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "proc-macro-hack" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "proc-macro-nested" -version = "0.1.4" +name = "proc-macro2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "proc-macro2" -version = "1.0.10" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "quick-error" -version = "1.2.3" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "quick-xml" -version = "0.17.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "quote" -version = "1.0.3" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "r2d2" -version = "0.8.8" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", - "scheduled-thread-pool 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "scheduled-thread-pool 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1852,10 +1715,10 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1863,28 +1726,28 @@ name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand" -version = "0.7.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1893,17 +1756,18 @@ name = "rand_chacha" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_chacha" -version = "0.2.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1911,20 +1775,20 @@ name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" -version = "0.4.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rand_core" -version = "0.5.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1940,7 +1804,7 @@ name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1956,9 +1820,9 @@ name = "rand_jitter" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1968,10 +1832,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1979,8 +1843,8 @@ name = "rand_pcg" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2006,63 +1870,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "regex" -version = "1.3.7" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.6.17" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "regex-syntax" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "remove_dir_all" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "resolv-conf" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hostname 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ring" -version = "0.16.12" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rss" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)", + "derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-xml 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc-demangle" -version = "0.1.16" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-serialize" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -2073,21 +1980,9 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rustls" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)", - "sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "ryu" -version = "1.0.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -2097,54 +1992,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "scheduled-thread-pool" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "scopeguard" -version = "1.1.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "sct" -version = "0.6.0" +name = "scopeguard" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)", - "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "security-framework" -version = "0.4.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "security-framework-sys" -version = "0.4.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2167,42 +2056,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.106" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde-hjson" -version = "0.9.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.106" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.52" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", + "dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2215,24 +2116,13 @@ dependencies = [ [[package]] name = "serde_urlencoded" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2241,34 +2131,27 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "sha2" -version = "0.8.1" +name = "signal-hook" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "signal-hook-registry" -version = "1.2.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arc-swap 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "simple_asn1" -version = "0.4.0" +name = "siphasher" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "slab" @@ -2277,75 +2160,116 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "1.3.0" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "socket2" -version = "0.3.12" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "spin" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "stable_deref_trait" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "string" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "static_assertions" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "strsim" -version = "0.9.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strum" -version = "0.18.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strum_macros" -version = "0.18.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" -version = "1.0.17" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.13.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.15.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "synstructure" -version = "0.12.3" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2353,54 +2277,45 @@ name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "termcolor" -version = "1.1.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "thread-id" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thiserror" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "thread_local" -version = "1.0.1" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2408,7 +2323,7 @@ name = "threadpool" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2416,129 +2331,204 @@ name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "tokio" -version = "0.2.20" +name = "tokio-codec" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-executor" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-io" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "tokio-util" -version = "0.2.0" +name = "tokio-signal" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "tokio-util" -version = "0.3.1" +name = "tokio-sync" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-timer" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-udp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "trust-dns-proto" -version = "0.18.0-alpha.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "async-trait 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", - "enum-as-inner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", - "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "enum-as-inner 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "trust-dns-resolver" -version = "0.18.0-alpha.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "ipconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "resolv-conf 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)", - "trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)", + "resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "trust-dns-proto 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "twoway" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unchecked-index 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "typed-arena" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "typenum" -version = "1.12.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "ucd-trie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unchecked-index" -version = "0.2.2" +name = "ucd-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicase" -version = "2.6.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicase" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2551,47 +2541,53 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.12" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-segmentation" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicode-width" -version = "0.1.7" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "unicode_categories" -version = "0.1.1" +name = "unicode-xid" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "untrusted" -version = "0.7.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "url" -version = "2.1.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "uuid" version = "0.7.4" @@ -2602,40 +2598,36 @@ dependencies = [ [[package]] name = "v_escape" -version = "0.7.4" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "v_escape_derive 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "v_escape_derive 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "v_escape_derive" -version = "0.5.6" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "v_htmlescape" -version = "0.4.5" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "v_escape 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "v_escape 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "vcpkg" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "vec_map" -version = "0.8.1" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -2643,91 +2635,6 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "version_check" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "wasm-bindgen" -version = "0.2.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bumpalo 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro-support 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-backend 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.60" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "web-sys" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "js-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "webpki" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)", - "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "webpki-roots" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "widestring" version = "0.4.0" @@ -2740,7 +2647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2759,10 +2666,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-util" -version = "0.1.4" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2771,11 +2678,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "winreg" -version = "0.6.2" +name = "wincolor" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winreg" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2783,7 +2699,7 @@ name = "winutil" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2795,87 +2711,83 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] -"checksum activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d538a21b137ec0f63cc579ef4afa4ab13aa85b4f8af15a033683edd97c50718d" -"checksum activitystreams-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "65608fdeae5eb05485d5b71a3d2242d76b2b7413608c196d47eb4dff3eed7b85" -"checksum activitystreams-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c2a3958d240f40eff1f31b5f679a6e0d4ce2a16812886a3ec0164f3a2ca517" -"checksum activitystreams-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0598820663a59e5eaafeeedd3a7f7efc93db4ed6172905baec05503095ba5c0e" -"checksum actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4af87564ff659dee8f9981540cac9418c45e910c8072fdedd643a262a38fcaf" -"checksum actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380" -"checksum actix-connect 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c" -"checksum actix-files 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "301482841d3d74483a446ead63cb7d362e187d2c8b603f13d91995621ea53c46" -"checksum actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c16664cc4fdea8030837ad5a845eb231fb93fc3c5c171edfefb52fad92ce9019" -"checksum actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21705adc76bbe4bc98434890e73a89cd00c6015e5704a60bb6eea6c3b72316b6" -"checksum actix-router 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9d7a10ca4d94e8c8e7a87c5173aba1b97ba9a6563ca02b0e1cd23531093d3ec8" -"checksum actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227" -"checksum actix-server 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "582a7173c281a4f46b5aa168a11e7f37183dcb71177a39312cc2264da7a632c9" -"checksum actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3e4fc95dfa7e24171b2d0bb46b85f8ab0e8499e4e3caec691fc4ea65c287564" -"checksum actix-testing 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48494745b72d0ea8ff0cf874aaf9b622a3ee03d7081ee0c04edea4f26d32c911" -"checksum actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4082192601de5f303013709ff84d81ca6a1bc4af7fb24f367a500a23c6e84e" -"checksum actix-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4e5b4faaf105e9a6d389c606c298dcdb033061b00d532af9df56ff3a54995a8" -"checksum actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fcf8f5631bf01adec2267808f00e228b761c60c0584cc9fa0b5364f41d147f4e" -"checksum actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3158e822461040822f0dbf1735b9c2ce1f95f93b651d7a7aded00b1efbb1f635" -"checksum actix-web-actors 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc1bd41bd66c4e9b5274cec87aac30168e63d64e96fd19db38edef6b46ba2982" -"checksum actix-web-codegen 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4f00371942083469785f7e28c540164af1913ee7c96a4534acb9cea92c39f057" -"checksum actix_derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b95aceadaf327f18f0df5962fedc1bde2f870566a0b9f65c89508a3b1f79334c" -"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" -"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum arc-swap 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" -"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +"checksum activitypub 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dbb11d9099278667d3723c6491f25ea34dcae3eb54d73070e665d312c4455b25" +"checksum activitystreams-derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "176bdecfca82b1980e4769e3d54b6a392284b724083e0bff68272e290f17458f" +"checksum activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "670ef03168e704b0cae242e7a5d8b40506772b339687e01a3496fc4afe2e8542" +"checksum activitystreams-types 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ff74c5765278614a009f97b9ec12f9a7c732bbcc5e0337fcfcab619b784860ec" +"checksum actix 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "671ce3d27313f236827a5dd153a1073ad03ef31fc77f562020263e7830cf1ef7" +"checksum actix-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9f2c11af4b06dc935d8e1b1491dad56bfb32febc49096a91e773f8535c176453" +"checksum actix-connect 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d161322a26e6b76d6598f48654afbdcfee644c900d4368e9962ec68abd0713b" +"checksum actix-files 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f1e4640b28cd96059541c932f6f350c63c76688e43d68305cdb88e0004da12b5" +"checksum actix-http 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "59698e11ceb42ea16a2e491bd5a9b48adc7268323a5b600522d408d09783828c" +"checksum actix-router 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "23224bb527e204261d0291102cb9b52713084def67d94f7874923baefe04ccf7" +"checksum actix-rt 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "18d9054b1cfacfa441846b9b99001010cb8fbb02130e6cfdb25cea36d3e98e87" +"checksum actix-server 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb3038e9e457e0a498ea682723e0f4e6cc2c4f362a1868d749808355275ad959" +"checksum actix-server-config 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "483a34989c682d93142bacad6300375bb6ad8002d2e0bb249dbad86128b9ff30" +"checksum actix-service 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aaecc01bbc595ebd7a563a7d4f8a607d0b964bb55273c6f362b0b02c26508cf2" +"checksum actix-threadpool 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1c29f7c554d56b3841f4bb85d5b3dee01ba536e1307679f56eb54de28aaec3fb" +"checksum actix-utils 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6ea501068a0173533704be321f149853f702d9e3c3ce9d57e7a96d94b1ab5aca" +"checksum actix-web 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0147b2fd52ced64145c8370af627f12f098222a1c6ba67d021e21cd0d806f574" +"checksum actix-web-actors 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "008c1b686048a16fef4ef2fc6b6e5fcf5f29829891ad87fc0848ade26b285627" +"checksum actix-web-codegen 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe9e3cdec1e645b675f354766e0688c5705021c85ab3cf739be1c8999b91c76" +"checksum actix_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bf5f6d7bf2d220ae8b4a7ae02a572bb35b7c4806b24049af905ab8110de156c" +"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" +"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +"checksum aho-corasick 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36b7aa1ccb7d7ea3f437cf025a2ab1c47cc6c1bc9fc84918ff449def12f5e282" +"checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" "checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" -"checksum async-trait 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "da71fef07bc806586090247e971229289f64c210a278ee5ae419314eb386b31d" -"checksum attohttpc 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "93610ce1c035e8a273fe56a19852e42798f3c019ca2726e52d2971197f116525" -"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" -"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum awc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5" -"checksum backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)" = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" -"checksum backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" +"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" +"checksum autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "22130e92352b948e7e82a49cdb0aa94f2211761117f29e052dd397c1ac33542b" +"checksum awc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4c4763e6aa29a801d761dc3464f081d439ea5249ba90c3c3bdfc8dd3f739d233" +"checksum backtrace 0.3.33 (registry+https://github.com/rust-lang/crates.io-index)" = "88fb679bc9af8fa639198790a77f52d345fe13656c08b43afa9424c206b731c6" +"checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" -"checksum base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -"checksum bcrypt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f02d7d008a57bcb2251ba115b803934e02315edbde9a861c88713493e381b63" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum bcrypt 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4fd6a91ff640809cfab4ea74312a892238a7bbae53adbf717b71122deb0c85" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" -"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" "checksum blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3" "checksum brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" "checksum brotli2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" "checksum bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" -"checksum bumpalo 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" -"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" -"checksum bytestring 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" -"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" +"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "77d81f58b7301084de3b958691458a53c3f7e0b1d702f77e550b6a88e3a88abe" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum comrak 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c" -"checksum config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +"checksum config 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f9107d78ed62b3fa5a86e7d18e647abed48cfd8f8fab6c72f4cdb982d196f7e6" "checksum copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127" -"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" -"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" -"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -"checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -"checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" -"checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" -"checksum derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" -"checksum derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" -"checksum derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2323f3f47db9a0e77ce7a300605d8d2098597fc451ed1a97bb1f6411bb550a7" -"checksum diesel 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "33d7ca63eb2efea87a7f56a283acc49e2ce4b2bd54adf7465dc1d81fef13d8fc" -"checksum diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" +"checksum crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b" +"checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" +"checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6" +"checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c" +"checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1" +"checksum derive_builder 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ac53fa6a3cda160df823a9346442525dcaf1e171999a1cf23e67067e4fd64d4" +"checksum derive_builder_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0288a23da9333c246bb18c143426074a6ae96747995c5819d2947b64cd942b37" +"checksum derive_more 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6d944ac6003ed268757ef1ee686753b57efc5fcf0ebe7b64c9fc81e7e32ff839" +"checksum derive_more 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a141330240c921ec6d074a3e188a7c7ef95668bb95e7d44fa0e5778ec2a7afe" +"checksum diesel 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d24935ba50c4a8dc375a0fd1f8a2ba6bdbdc4125713126a74b965d6a01a06d7" +"checksum diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62a27666098617d52c487a41f70de23d44a1dc1f3aa5877ceba2790fb1f1cab4" "checksum diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c" -"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -"checksum dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" -"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)" = "4a7ef30445607f6fc8720f0a0a2c7442284b629cf0d049286860fae23e71c4d9" +"checksum dotenv 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4424bad868b0ffe6ae351ee463526ba625bbca817978293bbe6bb7dc1804a175" +"checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d" +"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum email 0.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4" "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" "checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" @@ -2884,119 +2796,113 @@ dependencies = [ "checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" "checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" -"checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28" -"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" -"checksum enum-as-inner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c" -"checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -"checksum failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -"checksum failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" -"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4155785c79f2f6701f185eb2e6b4caf0555ec03477cb4c70db67b465311620ed" +"checksum enum-as-inner 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3d58266c97445680766be408285e798d3401c6d4c378ec5552e78737e681e37d" +"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" -"checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" +"checksum flate2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "550934ad4808d5d39365e5d61727309bf18b3b02c6c56b729cb92e7dd84bc3d8" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" -"checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" -"checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" -"checksum futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" -"checksum futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" -"checksum futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" -"checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" -"checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" -"checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" -"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +"checksum futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869" +"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum h2 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42" +"checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55" +"checksum h2 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "a539b63339fbbb00e081e84b6e11bd1d9634a82d91da2984a18ac74a8823f392" +"checksum hashbrown 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "29fba9abe4742d586dfd0c06ae4f7e73a1c2d86b856933509b269d82cdf06e18" +"checksum hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -"checksum hermit-abi 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8a0d737e0f947a1864e93d33fdef4af8445a00d1ed8dc0c8ddb73139ea6abf15" +"checksum hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0849d73a64ec77d1c8354aff489cf31943c4b4d3716de1eabfba572c70fde530" "checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" -"checksum hostname 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" "checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" -"checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +"checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" -"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" -"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" +"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum ipconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa79fa216fbe60834a9c0737d7fcd30425b32d1c58854663e24d4c4b328ed83f" -"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -"checksum js-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" -"checksum jsonwebtoken 7.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d11f9e80a85927748a334df8e4f6782a04033517bb28f3863a563daad882da7f" +"checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a81d1812d731546d2614737bee92aa071d37e9afa1409bc374da9e5e70e70b22" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum lettre 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bf43f3202a879fbdab4ecafec3349b0139f81d31c626246d53bcbb546253ffaa" -"checksum lettre_email 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fd02480f8dcf48798e62113974d6ccca2129a51d241fa20f1ea349c8a42559d5" -"checksum lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890" -"checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" +"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c66afaa5dfadbb81d4e00fd1d1ab057c7cd4c799c5a44e0009386d553587e728" +"checksum lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bbb68ca999042d965476e47bbdbacd52db0927348b6f8062c44dd04a3b1fd43b" +"checksum libc 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d44e80633f007889c7eff624b709ab43c92d708caad982295768a7b13ca3b5eb" "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" -"checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" +"checksum lock_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff" +"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" +"checksum log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c275b6ad54070ac2d665eef9197db647b32239c9d244bfb6f041a766d00da5b3" "checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -"checksum match_cfg 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -"checksum migrations_internals 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860" -"checksum migrations_macros 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" -"checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -"checksum mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" -"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" -"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum migrations_internals 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8089920229070f914b9ce9b07ef60e175b2b9bc2d35c3edd8bf4433604e863b9" +"checksum migrations_macros 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1664412abf7db2b8a6d58be42a38b099780cc542b5b350383b805d88932833fe" +"checksum mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "3e27ca21f40a310bd06d9031785f4801710d566c184a6e15bad4f1d9b65f9425" +"checksum mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "30de2e4613efcba1ec63d8133f344076952090c122992a903359be5a4f99c3ed" +"checksum miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" +"checksum miniz_oxide 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c061edee74a88eb35d876ce88b94d77a0448a201de111c244b70d047f5820516" +"checksum miniz_oxide_c_api 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6c675792957b0d19933816c4e1d56663c341dd9bfa31cb2140ff2267c1d8ecf4" +"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" +"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" -"checksum num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" -"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)" = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" +"checksum openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2f372b2b53ce10fb823a337aaa674e3a7d072b957c6264d0f4ff0bd86e657449" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)" = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" -"checksum parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" -"checksum parking_lot_core 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb" -"checksum pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1581760c757a756a41f0ee3ff01256227bdf64cb752839779b95ffb01c59793" -"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -"checksum pest_generator 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" -"checksum pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" -"checksum pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f6a7f5eee6292c559c793430c55c00aea9d3b3d1905e855806ca4d7253426a2" -"checksum pin-project-internal 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "8988430ce790d8682672117bc06dda364c0be32d3abd738234f19f3240bad99a" -"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" -"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" -"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" -"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +"checksum openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)" = "c977d08e1312e2f7e4b86f9ebaa0ed3b19d1daff75fae88bbb88108afbd801fc" +"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" +"checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" +"checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7" +"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +"checksum parking_lot_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c" +"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +"checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +"checksum phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" +"checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +"checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +"checksum pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "72d5370d90f49f70bd033c3d75e87fc529fbfff9d6f7cccef07d6170079d91ea" +"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda" -"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" -"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" -"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" -"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -"checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0" -"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" -"checksum r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" +"checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quick-xml 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0a8b2062cd4735d683121dbd525f5961226936229b0ac6bbbc40b34155744a41" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum r2d2 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "22b5c5fc5fba064373f03887337b412e0e1562d63023393db77251146cb75553" +"checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +"checksum rand_chacha 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e193067942ef6f485a349a113329140d0ab9e2168ce92274499bb0e9a4190d9d" "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615e683324e75af5d43d8f7a39ffe3ee4a9dc42c5c701167a71dc59c3a493aca" "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" @@ -3006,99 +2912,105 @@ dependencies = [ "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" -"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +"checksum regex 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b23da8dfd98a84bd7e08700190a5d9f7d2d38abd4369dd1dae651bc40bfd2cc" +"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" +"checksum regex-syntax 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5485bf1523a9ed51c4964273f22f63f24e31632adb5dad134f488f86a3875c" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum resolv-conf 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a" -"checksum ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c" -"checksum rss 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "99979205510c60f80a119dedbabd0b8426517384edf205322f8bcd51796bcef9" -"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +"checksum resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b263b4aa1b5de9ffc0054a2386f96992058bb6870aab516f8cdeb8a667d56dcb" +"checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c" +"checksum rss 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0706a43e890fbaf1714d495d12f69a7b34b70c6e903586d70311c2ce15ffe67" +"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" +"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum rustls 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" -"checksum ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" "checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -"checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -"checksum scheduled-thread-pool 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6" -"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -"checksum sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -"checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" -"checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" +"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" +"checksum scheduled-thread-pool 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5de7bc31f28f8e6c28df5e1bf3d10610f5fdc14cc95f272853512c70a2bd779" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2" +"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" -"checksum serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" -"checksum serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" -"checksum serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" -"checksum serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)" = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" +"checksum serde 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)" = "d46b3dfedb19360a74316866cef04687cd4d6a70df8e6a506c63512790769b72" +"checksum serde-hjson 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0b833c5ad67d52ced5f5938b2980f32a9c1c5ef047f0b4fb3127e7a423c76153" +"checksum serde_derive 1.0.97 (registry+https://github.com/rust-lang/crates.io-index)" = "c22a0820adfe2f257b098714323563dd06426502abbbce4f51b72ef544c5027f" +"checksum serde_json 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "67f7d2e9edc3523a9c8ec8cd6ec481b3a27810aafee3e625d311febd3e656b4c" +"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" "checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" -"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" -"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +"checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" -"checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" -"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" -"checksum simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618" +"checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68" +"checksum signal-hook-registry 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cded4ffa32146722ec54ab1f16320568465aa922aa9ab4708129599740da85d7" +"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" -"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" -"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" -"checksum strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" -"checksum strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" -"checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" -"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" +"checksum socket2 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "4e626972d3593207547f14bf5fc9efa4d0e7283deb73fef1dff313dae9ab8878" +"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" +"checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1c33039533f051704951680f1adfd468fd37ac46816ded0d9ee068e60f05f" +"checksum strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47cd23f5c7dee395a00fa20135e2ec0fffcdfa151c56182966d7a3261343432e" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum syn 0.13.11 (registry+https://github.com/rust-lang/crates.io-index)" = "14f9bf6292f3a61d2c716723fdb789a41bbe104168e6f496dc6497e531ea1b9b" +"checksum syn 0.15.40 (registry+https://github.com/rust-lang/crates.io-index)" = "bc945221ccf4a7e8c31222b9d1fc77aefdd6638eb901a6ce457a3dc29d4c31e8" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" -"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" -"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" +"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "05c1d570eb1a36f0345a5ce9c6c6e665b70b73d11236912c0b477616aeec47b1" -"checksum tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" -"checksum tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" -"checksum trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2a7f3a2ab8a919f5eca52a468866a67ed7d3efa265d48a652a9a3452272b413f" -"checksum trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6f90b1502b226f8b2514c6d5b37bafa8c200d7ca4102d57dc36ee0f3b7a04a2f" -"checksum twoway 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc" -"checksum typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" -"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" -"checksum ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" -"checksum unchecked-index 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" -"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +"checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" +"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" +"checksum tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac" +"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" +"checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" +"checksum tokio-signal 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "dd6dc5276ea05ce379a16de90083ec80836440d5ef8a6a39545a3207373b8296" +"checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7" +"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" +"checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" +"checksum tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92" +"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +"checksum trust-dns-proto 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5559ebdf6c2368ddd11e20b11d6bbaf9e46deb803acd7815e93f5a7b4a6d2901" +"checksum trust-dns-resolver 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c9992e58dba365798803c0b91018ff6c8d3fc77e06977c4539af2a6bfe0a039" +"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum ucd-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa9b3b49edd3468c0e6565d85783f51af95212b6fa3986a5500954f00b460874" +"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +"checksum unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a84e5511b2a947f3ae965dcb29b13b7b1691b6e7332cf5dbc1744138d5acb7f6" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" -"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" -"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" -"checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" -"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" -"checksum v_escape 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "660b101c07b5d0863deb9e7fb3138777e858d6d2a79f9e6049a27d1cc77c6da6" -"checksum v_escape_derive 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae" -"checksum v_htmlescape 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41" -"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum v_escape 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8865501b78eef9193c1b45486acf18ba889e5662eba98854d6fc59d8ecf3542d" +"checksum v_escape_derive 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "306896ff4b75998501263a1dc000456de442e21d68fe8c8bdf75c66a33a58e23" +"checksum v_htmlescape 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7fbbe0fa88dd36f9c8cf61a218d4b953ba669de4d0785832f33cc72bd081e1be" +"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" -"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -"checksum wasm-bindgen 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" -"checksum wasm-bindgen-backend 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" -"checksum wasm-bindgen-macro 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" -"checksum wasm-bindgen-macro-support 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" -"checksum wasm-bindgen-shared 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" -"checksum web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" -"checksum webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef" -"checksum webpki-roots 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739" "checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" +"checksum winreg 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73f1f3c6c4d3cab118551b96c476a2caab920701e28875b64a458f2ecb96ec9d" "checksum winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" diff --git a/server/Cargo.toml b/server/Cargo.toml index 43933e53..f7517153 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,43 +1,34 @@ [package] name = "lemmy_server" version = "0.0.1" -authors = ["Dessalines "] +authors = ["Dessalines "] edition = "2018" -[profile.release] -lto = true - [dependencies] -diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2", "64-column-tables"] } +diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2"] } diesel_migrations = "1.4.0" -dotenv = "0.15.0" -bcrypt = "0.7.0" -activitypub = "0.2.0" -chrono = "0.4.7" -failure = "0.1.8" -serde_json = "1.0.52" -serde = "1.0.105" -actix = "0.9.0" -actix-web = "2.0.0" -actix-files = "0.2.1" -actix-web-actors = "2.0.0" -actix-rt = "1.1.1" -log = "0.4.0" -env_logger = "0.7.1" -rand = "0.7.3" -strum = "0.18.0" -strum_macros = "0.18.0" -jsonwebtoken = "7.0.1" -regex = "1.3.5" +dotenv = "0.14.1" +bcrypt = "0.5.0" +activitypub = "0.1.5" +chrono = { version = "0.4.7", features = ["serde"] } +failure = "0.1.5" +serde_json = { version = "1.0.40", features = ["preserve_order"]} +serde = { version = "1.0.94", features = ["derive"] } +actix = "0.8.3" +actix-web = "1.0" +actix-files = "0.1.3" +actix-web-actors = "1.0" +env_logger = "0.6.2" +rand = "0.7.0" +strum = "0.15.0" +strum_macros = "0.15.0" +jsonwebtoken = "6.0.1" +regex = "1.1.9" lazy_static = "1.3.0" -lettre = "0.9.3" -lettre_email = "0.9.4" -sha2 = "0.8.1" -rss = "1.9.0" +lettre = "0.9.2" +lettre_email = "0.9.2" +rust-crypto = "^0.2" +rss = "1.8.0" htmlescape = "0.3.1" -config = {version = "0.10.1", default-features = false, features = ["hjson"] } -percent-encoding = "2.1.0" -attohttpc = { version = "0.14.0", default-features = false, features = ["tls-rustls"] } -comrak = "0.7" -tokio = "0.2.20" -futures = "0.3.4" +config = "0.9.3" +hjson = "0.8.2" diff --git a/server/clean.sh b/server/clean.sh deleted file mode 100755 index 3666a729..00000000 --- a/server/clean.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -cargo update -cargo fmt -cargo check -cargo clippy -cargo outdated -R diff --git a/server/config/defaults.hjson b/server/config/defaults.hjson index 97b9429c..e65bbcf4 100644 --- a/server/config/defaults.hjson +++ b/server/config/defaults.hjson @@ -1,57 +1,41 @@ { -# # 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 - user: "lemmy" - # password to connect to postgres - password: "password" - # host where postgres is running - host: "localhost" - # port where postgres can be accessed - port: 5432 - # name of the postgres database for lemmy - database: "lemmy" - # maximum number of active sql connections - pool_size: 5 - } + # username to connect to postgres + user: "lemmy" + # password to connect to postgres + password: "password" + # host where postgres is running + host: "localhost" + # port where postgres can be accessed + port: 5432 + # name of the postgres database for lemmy + database: "lemmy" + # maximum number of active sql connections + pool_size: 5 + } # the domain name of your instance (eg "dev.lemmy.ml") - hostname: "my_domain" + hostname: "rrr" # address where lemmy should listen for incoming requests bind: "0.0.0.0" # port where lemmy should listen for incoming requests port: 8536 # json web token for authorization between server and client jwt_secret: "changeme" - # The dir for the front end - front_end_dir: "../ui/dist" - # whether to enable activitypub federation. this feature is in alpha, do not enable in production, as might - # cause problems like remote instances fetching and permanently storing bad data. - federation_enabled: false # rate limits for various user actions, by user ip rate_limit: { # maximum number of messages created in interval - message: 180 + message: 30, # interval length for message limit - message_per_second: 60 + message_per_second: 60, # maximum number of posts created in interval - post: 6 + post: 6, # interval length for post limit - post_per_second: 600 + post_per_second: 600, # maximum number of registrations in interval - register: 3 + register: 3, # interval length for registration limit - register_per_second: 3600 + register_per_second: 3600, } # # email sending configuration # email: { diff --git a/server/db-init.sh b/server/db-init.sh deleted file mode 100755 index a2ad77b5..00000000 --- a/server/db-init.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/sh - -# Default configurations -username=lemmy -dbname=lemmy -port=5432 - -yes_no_prompt_invalid() { - echo "Invalid input. Please enter either \"y\" or \"n\"." 1>&2 -} - -print_config() { - echo " database name: $dbname" - echo " username: $username" - echo " port: $port" -} - -ask_for_db_config() { - echo "The default database configuration is:" - print_config - echo - - default_config_final=0 - default_config_valid=0 - while [ "$default_config_valid" == 0 ] - do - read -p "Use this configuration (y/n)? " default_config - case "$default_config" in - [yY]* ) default_config_valid=1; default_config_final=1;; - [nN]* ) default_config_valid=1; default_config_final=0;; - * ) yes_no_prompt_invalid;; - esac - echo - done - - if [ "$default_config_final" == 0 ] - then - config_ok_final=0 - while [ "$config_ok_final" == 0 ] - do - read -p "Database name: " dbname - read -p "Username: " username - read -p "Port: " port - #echo - - #echo "The database configuration is:" - #print_config - #echo - - config_ok_valid=0 - while [ "$config_ok_valid" == 0 ] - do - read -p "Use this configuration (y/n)? " config_ok - case "$config_ok" in - [yY]* ) config_ok_valid=1; config_ok_final=1;; - [nN]* ) config_ok_valid=1; config_ok_final=0;; - * ) yes_no_prompt_invalid;; - esac - echo - done - done - fi -} - -ask_for_password() { - password="" - password_confirm="" - password_valid=0 - while [ "$password_valid" == 0 ] - do - read -p "Enter database password: " -s password - echo - - read -p "Verify database password: " -s password_confirm - echo - echo - - # Start the loop from the top if either check fails - if [ -z "$password" ] - then - echo "Error: Password cannot be empty." 1>&2 - echo - continue - fi - if [ "$password" != "$password_confirm" ] - then - echo "Error: Passwords don't match." 1>&2 - echo - continue - fi - - # Set the password_valid variable to break out of the loop - password_valid=1 - done -} - -ask_for_db_config - -ask_for_password - -psql -c "CREATE USER $username WITH PASSWORD '$password' SUPERUSER;" -U postgres -psql -c "CREATE DATABASE $dbname WITH OWNER $username;" -U postgres -export LEMMY_DATABASE_URL=postgres://$username:$password@localhost:$port/$dbname - -echo "The database URL is $LEMMY_DATABASE_URL" - diff --git a/server/migrations/2019-12-29-164820_add_avatar/down.sql b/server/migrations/2019-12-29-164820_add_avatar/down.sql deleted file mode 100644 index 74b4146d..00000000 --- a/server/migrations/2019-12-29-164820_add_avatar/down.sql +++ /dev/null @@ -1,224 +0,0 @@ --- the views -drop view user_mention_view; -drop view reply_view; -drop view comment_view; -drop view user_view; - --- user -create view user_view as -select id, -name, -fedi_name, -admin, -banned, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; - --- post --- Recreate the view -drop view post_view; -create view post_view as -with all_post as -( - select - p.*, - (select u.banned from user_ u where p.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where p.creator_id = user_.id) as creator_name, - (select name from community where p.community_id = community.id) as community_name, - (select removed from community c where p.community_id = c.id) as community_removed, - (select deleted from community c where p.community_id = c.id) as community_deleted, - (select nsfw from community c where p.community_id = c.id) as community_nsfw, - (select count(*) from comment where comment.post_id = p.id) as number_of_comments, - coalesce(sum(pl.score), 0) as score, - count (case when pl.score = 1 then 1 else null end) as upvotes, - count (case when pl.score = -1 then 1 else null end) as downvotes, - hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank - from post p - left join post_like pl on p.id = pl.post_id - group by p.id -) - -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - --- community - -drop view community_view; -create view community_view as -with all_community as -( - select *, - (select name from user_ u where c.creator_id = u.id) as creator_name, - (select name from category ct where c.category_id = ct.id) as category_name, - (select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers, - (select count(*) from post p where p.community_id = c.id) as number_of_posts, - (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments, - hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank - from community c -) - -select -ac.*, -u.id as user_id, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed -from user_ u -cross join all_community ac - -union all - -select -ac.*, -null as user_id, -null as subscribed -from all_community ac -; - --- Reply and comment view -create view comment_view as -with all_comment as -( - select - c.*, - (select community_id from post p where p.id = c.post_id), - (select u.banned from user_ u where c.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where c.creator_id = user_.id) as creator_name, - coalesce(sum(cl.score), 0) as score, - count (case when cl.score = 1 then 1 else null end) as upvotes, - count (case when cl.score = -1 then 1 else null end) as downvotes - from comment c - left join comment_like cl on c.id = cl.comment_id - group by c.id -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_view cv, closereply -where closereply.id = cv.id -; - --- user mention -create view user_mention_view as -select - c.id, - um.id as user_mention_id, - c.creator_id, - c.post_id, - c.parent_id, - c.content, - c.removed, - um.read, - c.published, - c.updated, - c.deleted, - c.community_id, - c.banned, - c.banned_from_community, - c.creator_name, - c.score, - c.upvotes, - c.downvotes, - c.user_id, - c.my_vote, - c.saved, - um.recipient_id -from user_mention um, comment_view c -where um.comment_id = c.id; - --- community tables -drop view community_moderator_view; -drop view community_follower_view; -drop view community_user_ban_view; -drop view site_view; - -create view community_moderator_view as -select *, -(select name from user_ u where cm.user_id = u.id) as user_name, -(select name from community c where cm.community_id = c.id) as community_name -from community_moderator cm; - -create view community_follower_view as -select *, -(select name from user_ u where cf.user_id = u.id) as user_name, -(select name from community c where cf.community_id = c.id) as community_name -from community_follower cf; - -create view community_user_ban_view as -select *, -(select name from user_ u where cm.user_id = u.id) as user_name, -(select name from community c where cm.community_id = c.id) as community_name -from community_user_ban cm; - -create view site_view as -select *, -(select name from user_ u where s.creator_id = u.id) as creator_name, -(select count(*) from user_) as number_of_users, -(select count(*) from post) as number_of_posts, -(select count(*) from comment) as number_of_comments, -(select count(*) from community) as number_of_communities -from site s; - -alter table user_ rename column avatar to icon; -alter table user_ alter column icon type bytea using icon::bytea; diff --git a/server/migrations/2019-12-29-164820_add_avatar/up.sql b/server/migrations/2019-12-29-164820_add_avatar/up.sql deleted file mode 100644 index f9265154..00000000 --- a/server/migrations/2019-12-29-164820_add_avatar/up.sql +++ /dev/null @@ -1,234 +0,0 @@ --- Rename to avatar -alter table user_ rename column icon to avatar; -alter table user_ alter column avatar type text; - --- Rebuild nearly all the views, to include the creator avatars - --- user -drop view user_view; -create view user_view as -select id, -name, -avatar, -fedi_name, -admin, -banned, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; - --- post --- Recreate the view -drop view post_view; -create view post_view as -with all_post as -( - select - p.*, - (select u.banned from user_ u where p.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where p.creator_id = user_.id) as creator_name, - (select avatar from user_ where p.creator_id = user_.id) as creator_avatar, - (select name from community where p.community_id = community.id) as community_name, - (select removed from community c where p.community_id = c.id) as community_removed, - (select deleted from community c where p.community_id = c.id) as community_deleted, - (select nsfw from community c where p.community_id = c.id) as community_nsfw, - (select count(*) from comment where comment.post_id = p.id) as number_of_comments, - coalesce(sum(pl.score), 0) as score, - count (case when pl.score = 1 then 1 else null end) as upvotes, - count (case when pl.score = -1 then 1 else null end) as downvotes, - hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank - from post p - left join post_like pl on p.id = pl.post_id - group by p.id -) - -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - - --- community -drop view community_view; -create view community_view as -with all_community as -( - select *, - (select name from user_ u where c.creator_id = u.id) as creator_name, - (select avatar from user_ u where c.creator_id = u.id) as creator_avatar, - (select name from category ct where c.category_id = ct.id) as category_name, - (select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers, - (select count(*) from post p where p.community_id = c.id) as number_of_posts, - (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments, - hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank - from community c -) - -select -ac.*, -u.id as user_id, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed -from user_ u -cross join all_community ac - -union all - -select -ac.*, -null as user_id, -null as subscribed -from all_community ac -; - --- reply and comment view -drop view reply_view; -drop view user_mention_view; -drop view comment_view; -create view comment_view as -with all_comment as -( - select - c.*, - (select community_id from post p where p.id = c.post_id), - (select u.banned from user_ u where c.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where c.creator_id = user_.id) as creator_name, - (select avatar from user_ where c.creator_id = user_.id) as creator_avatar, - coalesce(sum(cl.score), 0) as score, - count (case when cl.score = 1 then 1 else null end) as upvotes, - count (case when cl.score = -1 then 1 else null end) as downvotes - from comment c - left join comment_like cl on c.id = cl.comment_id - group by c.id -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_view cv, closereply -where closereply.id = cv.id -; - --- user mention -create view user_mention_view as -select - c.id, - um.id as user_mention_id, - c.creator_id, - c.post_id, - c.parent_id, - c.content, - c.removed, - um.read, - c.published, - c.updated, - c.deleted, - c.community_id, - c.banned, - c.banned_from_community, - c.creator_name, - c.creator_avatar, - c.score, - c.upvotes, - c.downvotes, - c.user_id, - c.my_vote, - c.saved, - um.recipient_id -from user_mention um, comment_view c -where um.comment_id = c.id; - --- community views -drop view community_moderator_view; -drop view community_follower_view; -drop view community_user_ban_view; -drop view site_view; - -create view community_moderator_view as -select *, -(select name from user_ u where cm.user_id = u.id) as user_name, -(select avatar from user_ u where cm.user_id = u.id), -(select name from community c where cm.community_id = c.id) as community_name -from community_moderator cm; - -create view community_follower_view as -select *, -(select name from user_ u where cf.user_id = u.id) as user_name, -(select avatar from user_ u where cf.user_id = u.id), -(select name from community c where cf.community_id = c.id) as community_name -from community_follower cf; - -create view community_user_ban_view as -select *, -(select name from user_ u where cm.user_id = u.id) as user_name, -(select avatar from user_ u where cm.user_id = u.id), -(select name from community c where cm.community_id = c.id) as community_name -from community_user_ban cm; - -create view site_view as -select *, -(select name from user_ u where s.creator_id = u.id) as creator_name, -(select avatar from user_ u where s.creator_id = u.id) as creator_avatar, -(select count(*) from user_) as number_of_users, -(select count(*) from post) as number_of_posts, -(select count(*) from comment) as number_of_comments, -(select count(*) from community) as number_of_communities -from site s; diff --git a/server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql b/server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql deleted file mode 100644 index 92f771f8..00000000 --- a/server/migrations/2020-01-01-200418_add_email_to_user_view/down.sql +++ /dev/null @@ -1,15 +0,0 @@ --- user -drop view user_view; -create view user_view as -select id, -name, -avatar, -fedi_name, -admin, -banned, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; diff --git a/server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql b/server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql deleted file mode 100644 index 59972dfb..00000000 --- a/server/migrations/2020-01-01-200418_add_email_to_user_view/up.sql +++ /dev/null @@ -1,16 +0,0 @@ --- user -drop view user_view; -create view user_view as -select id, -name, -avatar, -email, -fedi_name, -admin, -banned, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; diff --git a/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/down.sql b/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/down.sql deleted file mode 100644 index ec061223..00000000 --- a/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/down.sql +++ /dev/null @@ -1,20 +0,0 @@ --- Drop the columns -drop view user_view; -alter table user_ drop column show_avatars; -alter table user_ drop column send_notifications_to_email; - --- Rebuild the view -create view user_view as -select id, -name, -avatar, -email, -fedi_name, -admin, -banned, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; diff --git a/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/up.sql b/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/up.sql deleted file mode 100644 index 21f0aff4..00000000 --- a/server/migrations/2020-01-02-172755_add_show_avatar_and_email_notifications_to_user/up.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Add columns -alter table user_ add column show_avatars boolean default true not null; -alter table user_ add column send_notifications_to_email boolean default false not null; - --- Rebuild the user_view -drop view user_view; -create view user_view as -select id, -name, -avatar, -email, -fedi_name, -admin, -banned, -show_avatars, -send_notifications_to_email, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; diff --git a/server/migrations/2020-01-11-012452_add_indexes/down.sql b/server/migrations/2020-01-11-012452_add_indexes/down.sql deleted file mode 100644 index ad674c50..00000000 --- a/server/migrations/2020-01-11-012452_add_indexes/down.sql +++ /dev/null @@ -1,16 +0,0 @@ -drop index idx_post_creator; -drop index idx_post_community; - -drop index idx_post_like_post; -drop index idx_post_like_user; - -drop index idx_comment_creator; -drop index idx_comment_parent; -drop index idx_comment_post; - -drop index idx_comment_like_comment; -drop index idx_comment_like_user; -drop index idx_comment_like_post; - -drop index idx_community_creator; -drop index idx_community_category; diff --git a/server/migrations/2020-01-11-012452_add_indexes/up.sql b/server/migrations/2020-01-11-012452_add_indexes/up.sql deleted file mode 100644 index 911f0875..00000000 --- a/server/migrations/2020-01-11-012452_add_indexes/up.sql +++ /dev/null @@ -1,17 +0,0 @@ --- Go through all the tables joins, optimize every view, CTE, etc. -create index idx_post_creator on post (creator_id); -create index idx_post_community on post (community_id); - -create index idx_post_like_post on post_like (post_id); -create index idx_post_like_user on post_like (user_id); - -create index idx_comment_creator on comment (creator_id); -create index idx_comment_parent on comment (parent_id); -create index idx_comment_post on comment (post_id); - -create index idx_comment_like_comment on comment_like (comment_id); -create index idx_comment_like_user on comment_like (user_id); -create index idx_comment_like_post on comment_like (post_id); - -create index idx_community_creator on community (creator_id); -create index idx_community_category on community (category_id); diff --git a/server/migrations/2020-01-13-025151_create_materialized_views/down.sql b/server/migrations/2020-01-13-025151_create_materialized_views/down.sql deleted file mode 100644 index 39985ab5..00000000 --- a/server/migrations/2020-01-13-025151_create_materialized_views/down.sql +++ /dev/null @@ -1,223 +0,0 @@ --- functions and triggers -drop trigger refresh_user on user_; -drop function refresh_user(); -drop trigger refresh_post on post; -drop function refresh_post(); -drop trigger refresh_post_like on post_like; -drop function refresh_post_like(); -drop trigger refresh_community on community; -drop function refresh_community(); -drop trigger refresh_community_follower on community_follower; -drop function refresh_community_follower(); -drop trigger refresh_community_user_ban on community_user_ban; -drop function refresh_community_user_ban(); -drop trigger refresh_comment on comment; -drop function refresh_comment(); -drop trigger refresh_comment_like on comment_like; -drop function refresh_comment_like(); - --- post --- Recreate the view -drop view post_view; -create view post_view as -with all_post as -( - select - p.*, - (select u.banned from user_ u where p.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where p.creator_id = user_.id) as creator_name, - (select avatar from user_ where p.creator_id = user_.id) as creator_avatar, - (select name from community where p.community_id = community.id) as community_name, - (select removed from community c where p.community_id = c.id) as community_removed, - (select deleted from community c where p.community_id = c.id) as community_deleted, - (select nsfw from community c where p.community_id = c.id) as community_nsfw, - (select count(*) from comment where comment.post_id = p.id) as number_of_comments, - coalesce(sum(pl.score), 0) as score, - count (case when pl.score = 1 then 1 else null end) as upvotes, - count (case when pl.score = -1 then 1 else null end) as downvotes, - hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank - from post p - left join post_like pl on p.id = pl.post_id - group by p.id -) - -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - -drop view post_mview; -drop materialized view post_aggregates_mview; -drop view post_aggregates_view; - --- user -drop materialized view user_mview; -drop view user_view; -create view user_view as -select id, -name, -avatar, -email, -fedi_name, -admin, -banned, -show_avatars, -send_notifications_to_email, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; - --- community -drop view community_mview; -drop materialized view community_aggregates_mview; -drop view community_view; -drop view community_aggregates_view; -create view community_view as -with all_community as -( - select *, - (select name from user_ u where c.creator_id = u.id) as creator_name, - (select avatar from user_ u where c.creator_id = u.id) as creator_avatar, - (select name from category ct where c.category_id = ct.id) as category_name, - (select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers, - (select count(*) from post p where p.community_id = c.id) as number_of_posts, - (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments, - hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank - from community c -) - -select -ac.*, -u.id as user_id, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed -from user_ u -cross join all_community ac - -union all - -select -ac.*, -null as user_id, -null as subscribed -from all_community ac -; - --- reply and comment view -drop view reply_view; -drop view user_mention_view; -drop view comment_view; -drop view comment_mview; -drop materialized view comment_aggregates_mview; -drop view comment_aggregates_view; -create view comment_view as -with all_comment as -( - select - c.*, - (select community_id from post p where p.id = c.post_id), - (select u.banned from user_ u where c.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where c.creator_id = user_.id) as creator_name, - (select avatar from user_ where c.creator_id = user_.id) as creator_avatar, - coalesce(sum(cl.score), 0) as score, - count (case when cl.score = 1 then 1 else null end) as upvotes, - count (case when cl.score = -1 then 1 else null end) as downvotes - from comment c - left join comment_like cl on c.id = cl.comment_id - group by c.id -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_view cv, closereply -where closereply.id = cv.id -; - --- user mention -create view user_mention_view as -select - c.id, - um.id as user_mention_id, - c.creator_id, - c.post_id, - c.parent_id, - c.content, - c.removed, - um.read, - c.published, - c.updated, - c.deleted, - c.community_id, - c.banned, - c.banned_from_community, - c.creator_name, - c.creator_avatar, - c.score, - c.upvotes, - c.downvotes, - c.user_id, - c.my_vote, - c.saved, - um.recipient_id -from user_mention um, comment_view c -where um.comment_id = c.id; - diff --git a/server/migrations/2020-01-13-025151_create_materialized_views/up.sql b/server/migrations/2020-01-13-025151_create_materialized_views/up.sql deleted file mode 100644 index e0f206d3..00000000 --- a/server/migrations/2020-01-13-025151_create_materialized_views/up.sql +++ /dev/null @@ -1,437 +0,0 @@ --- post -create view post_aggregates_view as -select -p.*, -(select u.banned from user_ u where p.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where p.creator_id = user_.id) as creator_name, -(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, -(select name from community where p.community_id = community.id) as community_name, -(select removed from community c where p.community_id = c.id) as community_removed, -(select deleted from community c where p.community_id = c.id) as community_deleted, -(select nsfw from community c where p.community_id = c.id) as community_nsfw, -(select count(*) from comment where comment.post_id = p.id) as number_of_comments, -coalesce(sum(pl.score), 0) as score, -count (case when pl.score = 1 then 1 else null end) as upvotes, -count (case when pl.score = -1 then 1 else null end) as downvotes, -hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank -from post p -left join post_like pl on p.id = pl.post_id -group by p.id; - -create materialized view post_aggregates_mview as select * from post_aggregates_view; - -create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); - -drop view post_view; -create view post_view as -with all_post as ( - select - pa.* - from post_aggregates_view pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - -create view post_mview as -with all_post as ( - select - pa.* - from post_aggregates_mview pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - - --- user_view -drop view user_view; -create view user_view as -select -u.id, -u.name, -u.avatar, -u.email, -u.fedi_name, -u.admin, -u.banned, -u.show_avatars, -u.send_notifications_to_email, -u.published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; - -create materialized view user_mview as select * from user_view; - -create unique index idx_user_mview_id on user_mview (id); - --- community -create view community_aggregates_view as -select c.*, -(select name from user_ u where c.creator_id = u.id) as creator_name, -(select avatar from user_ u where c.creator_id = u.id) as creator_avatar, -(select name from category ct where c.category_id = ct.id) as category_name, -(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers, -(select count(*) from post p where p.community_id = c.id) as number_of_posts, -(select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments, -hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank -from community c; - -create materialized view community_aggregates_mview as select * from community_aggregates_view; - -create unique index idx_community_aggregates_mview_id on community_aggregates_mview (id); - -drop view community_view; -create view community_view as -with all_community as -( - select - ca.* - from community_aggregates_view ca -) - -select -ac.*, -u.id as user_id, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed -from user_ u -cross join all_community ac - -union all - -select -ac.*, -null as user_id, -null as subscribed -from all_community ac -; - -create view community_mview as -with all_community as -( - select - ca.* - from community_aggregates_mview ca -) - -select -ac.*, -u.id as user_id, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed -from user_ u -cross join all_community ac - -union all - -select -ac.*, -null as user_id, -null as subscribed -from all_community ac -; - - --- reply and comment view -create view comment_aggregates_view as -select -c.*, -(select community_id from post p where p.id = c.post_id), -(select u.banned from user_ u where c.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where c.creator_id = user_.id) as creator_name, -(select avatar from user_ where c.creator_id = user_.id) as creator_avatar, -coalesce(sum(cl.score), 0) as score, -count (case when cl.score = 1 then 1 else null end) as upvotes, -count (case when cl.score = -1 then 1 else null end) as downvotes -from comment c -left join comment_like cl on c.id = cl.comment_id -group by c.id; - -create materialized view comment_aggregates_mview as select * from comment_aggregates_view; - -create unique index idx_comment_aggregates_mview_id on comment_aggregates_mview (id); - -drop view reply_view; -drop view user_mention_view; -drop view comment_view; - -create view comment_view as -with all_comment as -( - select - ca.* - from comment_aggregates_view ca -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - -create view comment_mview as -with all_comment as -( - select - ca.* - from comment_aggregates_mview ca -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_view cv, closereply -where closereply.id = cv.id -; - --- user mention -create view user_mention_view as -select - c.id, - um.id as user_mention_id, - c.creator_id, - c.post_id, - c.parent_id, - c.content, - c.removed, - um.read, - c.published, - c.updated, - c.deleted, - c.community_id, - c.banned, - c.banned_from_community, - c.creator_name, - c.creator_avatar, - c.score, - c.upvotes, - c.downvotes, - c.user_id, - c.my_vote, - c.saved, - um.recipient_id -from user_mention um, comment_view c -where um.comment_id = c.id; - --- user -create or replace function refresh_user() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently user_mview; - refresh materialized view concurrently comment_aggregates_mview; -- cause of bans - refresh materialized view concurrently post_aggregates_mview; - return null; -end $$; - -create trigger refresh_user -after insert or update or delete or truncate -on user_ -for each statement -execute procedure refresh_user(); - --- post -create or replace function refresh_post() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently post_aggregates_mview; - refresh materialized view concurrently user_mview; - return null; -end $$; - -create trigger refresh_post -after insert or update or delete or truncate -on post -for each statement -execute procedure refresh_post(); - --- post_like -create or replace function refresh_post_like() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently post_aggregates_mview; - refresh materialized view concurrently user_mview; - return null; -end $$; - -create trigger refresh_post_like -after insert or update or delete or truncate -on post_like -for each statement -execute procedure refresh_post_like(); - --- community -create or replace function refresh_community() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently post_aggregates_mview; - refresh materialized view concurrently community_aggregates_mview; - refresh materialized view concurrently user_mview; - return null; -end $$; - -create trigger refresh_community -after insert or update or delete or truncate -on community -for each statement -execute procedure refresh_community(); - --- community_follower -create or replace function refresh_community_follower() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently community_aggregates_mview; - refresh materialized view concurrently post_aggregates_mview; - return null; -end $$; - -create trigger refresh_community_follower -after insert or update or delete or truncate -on community_follower -for each statement -execute procedure refresh_community_follower(); - --- community_user_ban -create or replace function refresh_community_user_ban() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently comment_aggregates_mview; - refresh materialized view concurrently post_aggregates_mview; - return null; -end $$; - -create trigger refresh_community_user_ban -after insert or update or delete or truncate -on community_user_ban -for each statement -execute procedure refresh_community_user_ban(); - --- comment -create or replace function refresh_comment() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently post_aggregates_mview; - refresh materialized view concurrently comment_aggregates_mview; - refresh materialized view concurrently community_aggregates_mview; - refresh materialized view concurrently user_mview; - return null; -end $$; - -create trigger refresh_comment -after insert or update or delete or truncate -on comment -for each statement -execute procedure refresh_comment(); - --- comment_like -create or replace function refresh_comment_like() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently comment_aggregates_mview; - refresh materialized view concurrently user_mview; - return null; -end $$; - -create trigger refresh_comment_like -after insert or update or delete or truncate -on comment_like -for each statement -execute procedure refresh_comment_like(); diff --git a/server/migrations/2020-01-21-001001_create_private_message/down.sql b/server/migrations/2020-01-21-001001_create_private_message/down.sql deleted file mode 100644 index 0d951e3e..00000000 --- a/server/migrations/2020-01-21-001001_create_private_message/down.sql +++ /dev/null @@ -1,34 +0,0 @@ --- Drop the triggers -drop trigger refresh_private_message on private_message; -drop function refresh_private_message(); - --- Drop the view and table -drop view private_message_view cascade; -drop table private_message; - --- Rebuild the old views -drop view user_view cascade; -create view user_view as -select -u.id, -u.name, -u.avatar, -u.email, -u.fedi_name, -u.admin, -u.banned, -u.show_avatars, -u.send_notifications_to_email, -u.published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; - -create materialized view user_mview as select * from user_view; - -create unique index idx_user_mview_id on user_mview (id); - --- Drop the columns -alter table user_ drop column matrix_user_id; diff --git a/server/migrations/2020-01-21-001001_create_private_message/up.sql b/server/migrations/2020-01-21-001001_create_private_message/up.sql deleted file mode 100644 index 48e16dd8..00000000 --- a/server/migrations/2020-01-21-001001_create_private_message/up.sql +++ /dev/null @@ -1,90 +0,0 @@ --- Creating private message -create table private_message ( - id serial primary key, - creator_id int references user_ on update cascade on delete cascade not null, - recipient_id int references user_ on update cascade on delete cascade not null, - content text not null, - deleted boolean default false not null, - read boolean default false not null, - published timestamp not null default now(), - updated timestamp -); - --- Create the view and materialized view which has the avatar and creator name -create view private_message_view as -select -pm.*, -u.name as creator_name, -u.avatar as creator_avatar, -u2.name as recipient_name, -u2.avatar as recipient_avatar -from private_message pm -inner join user_ u on u.id = pm.creator_id -inner join user_ u2 on u2.id = pm.recipient_id; - -create materialized view private_message_mview as select * from private_message_view; - -create unique index idx_private_message_mview_id on private_message_mview (id); - --- Create the triggers -create or replace function refresh_private_message() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently private_message_mview; - return null; -end $$; - -create trigger refresh_private_message -after insert or update or delete or truncate -on private_message -for each statement -execute procedure refresh_private_message(); - --- Update user to include matrix id -alter table user_ add column matrix_user_id text unique; - -drop view user_view cascade; -create view user_view as -select -u.id, -u.name, -u.avatar, -u.email, -u.matrix_user_id, -u.fedi_name, -u.admin, -u.banned, -u.show_avatars, -u.send_notifications_to_email, -u.published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; - -create materialized view user_mview as select * from user_view; - -create unique index idx_user_mview_id on user_mview (id); - --- This is what a group pm table would look like --- Not going to do it now because of the complications --- --- create table private_message ( --- id serial primary key, --- creator_id int references user_ on update cascade on delete cascade not null, --- content text not null, --- deleted boolean default false not null, --- published timestamp not null default now(), --- updated timestamp --- ); --- --- create table private_message_recipient ( --- id serial primary key, --- private_message_id int references private_message on update cascade on delete cascade not null, --- recipient_id int references user_ on update cascade on delete cascade not null, --- read boolean default false not null, --- published timestamp not null default now(), --- unique(private_message_id, recipient_id) --- ) diff --git a/server/migrations/2020-01-29-011901_create_reply_materialized_view/down.sql b/server/migrations/2020-01-29-011901_create_reply_materialized_view/down.sql deleted file mode 100644 index 06ec5971..00000000 --- a/server/migrations/2020-01-29-011901_create_reply_materialized_view/down.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Drop the materialized / built views -drop view reply_view; -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_view cv, closereply -where closereply.id = cv.id -; diff --git a/server/migrations/2020-01-29-011901_create_reply_materialized_view/up.sql b/server/migrations/2020-01-29-011901_create_reply_materialized_view/up.sql deleted file mode 100644 index ebbb1dff..00000000 --- a/server/migrations/2020-01-29-011901_create_reply_materialized_view/up.sql +++ /dev/null @@ -1,27 +0,0 @@ --- https://github.com/dessalines/lemmy/issues/197 -drop view reply_view; - --- Do the reply_view referencing the comment_mview -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_mview cv, closereply -where closereply.id = cv.id -; diff --git a/server/migrations/2020-01-29-030825_create_user_mention_materialized_view/down.sql b/server/migrations/2020-01-29-030825_create_user_mention_materialized_view/down.sql deleted file mode 100644 index d93ebc2e..00000000 --- a/server/migrations/2020-01-29-030825_create_user_mention_materialized_view/down.sql +++ /dev/null @@ -1 +0,0 @@ -drop view user_mention_mview; diff --git a/server/migrations/2020-01-29-030825_create_user_mention_materialized_view/up.sql b/server/migrations/2020-01-29-030825_create_user_mention_materialized_view/up.sql deleted file mode 100644 index b0ae4e9d..00000000 --- a/server/migrations/2020-01-29-030825_create_user_mention_materialized_view/up.sql +++ /dev/null @@ -1,67 +0,0 @@ -create view user_mention_mview as -with all_comment as -( - select - ca.* - from comment_aggregates_mview ca -) - -select - ac.id, - um.id as user_mention_id, - ac.creator_id, - ac.post_id, - ac.parent_id, - ac.content, - ac.removed, - um.read, - ac.published, - ac.updated, - ac.deleted, - ac.community_id, - ac.banned, - ac.banned_from_community, - ac.creator_name, - ac.creator_avatar, - ac.score, - ac.upvotes, - ac.downvotes, - u.id as user_id, - coalesce(cl.score, 0) as my_vote, - (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved, - um.recipient_id -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id -left join user_mention um on um.comment_id = ac.id - -union all - -select - ac.id, - um.id as user_mention_id, - ac.creator_id, - ac.post_id, - ac.parent_id, - ac.content, - ac.removed, - um.read, - ac.published, - ac.updated, - ac.deleted, - ac.community_id, - ac.banned, - ac.banned_from_community, - ac.creator_name, - ac.creator_avatar, - ac.score, - ac.upvotes, - ac.downvotes, - null as user_id, - null as my_vote, - null as saved, - um.recipient_id -from all_comment ac -left join user_mention um on um.comment_id = ac.id -; - diff --git a/server/migrations/2020-02-02-004806_add_case_insensitive_usernames/down.sql b/server/migrations/2020-02-02-004806_add_case_insensitive_usernames/down.sql deleted file mode 100644 index 0c6f112f..00000000 --- a/server/migrations/2020-02-02-004806_add_case_insensitive_usernames/down.sql +++ /dev/null @@ -1,2 +0,0 @@ -drop index idx_user_name_lower; -drop index idx_user_email_lower; diff --git a/server/migrations/2020-02-02-004806_add_case_insensitive_usernames/up.sql b/server/migrations/2020-02-02-004806_add_case_insensitive_usernames/up.sql deleted file mode 100644 index 07f854b1..00000000 --- a/server/migrations/2020-02-02-004806_add_case_insensitive_usernames/up.sql +++ /dev/null @@ -1,29 +0,0 @@ --- Add case insensitive username and email uniqueness - --- An example of showing the dupes: --- select --- max(id) as id, --- lower(name) as lname, --- count(*) --- from user_ --- group by lower(name) --- having count(*) > 1; - --- Delete username dupes, keeping the first one -delete -from user_ -where id not in ( - select min(id) - from user_ - group by lower(name), lower(fedi_name) -); - --- The user index -create unique index idx_user_name_lower on user_ (lower(name)); - --- Email lower -create unique index idx_user_email_lower on user_ (lower(email)); - --- Set empty emails properly to null -update user_ set email = null where email = ''; - diff --git a/server/migrations/2020-02-06-165953_change_post_title_length/down.sql b/server/migrations/2020-02-06-165953_change_post_title_length/down.sql deleted file mode 100644 index 2bc765f8..00000000 --- a/server/migrations/2020-02-06-165953_change_post_title_length/down.sql +++ /dev/null @@ -1,132 +0,0 @@ --- Drop the dependent views -drop view post_view; -drop view post_mview; -drop materialized view post_aggregates_mview; -drop view post_aggregates_view; -drop view mod_remove_post_view; -drop view mod_sticky_post_view; -drop view mod_lock_post_view; -drop view mod_remove_comment_view; - -alter table post alter column name type varchar(100); - --- regen post view -create view post_aggregates_view as -select -p.*, -(select u.banned from user_ u where p.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where p.creator_id = user_.id) as creator_name, -(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, -(select name from community where p.community_id = community.id) as community_name, -(select removed from community c where p.community_id = c.id) as community_removed, -(select deleted from community c where p.community_id = c.id) as community_deleted, -(select nsfw from community c where p.community_id = c.id) as community_nsfw, -(select count(*) from comment where comment.post_id = p.id) as number_of_comments, -coalesce(sum(pl.score), 0) as score, -count (case when pl.score = 1 then 1 else null end) as upvotes, -count (case when pl.score = -1 then 1 else null end) as downvotes, -hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank -from post p -left join post_like pl on p.id = pl.post_id -group by p.id; - -create materialized view post_aggregates_mview as select * from post_aggregates_view; - -create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); - -create view post_view as -with all_post as ( - select - pa.* - from post_aggregates_view pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - -create view post_mview as -with all_post as ( - select - pa.* - from post_aggregates_mview pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - --- The mod views - -create view mod_remove_post_view as -select mrp.*, -(select name from user_ u where mrp.mod_user_id = u.id) as mod_user_name, -(select name from post p where mrp.post_id = p.id) as post_name, -(select c.id from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_id, -(select c.name from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_name -from mod_remove_post mrp; - -create view mod_lock_post_view as -select mlp.*, -(select name from user_ u where mlp.mod_user_id = u.id) as mod_user_name, -(select name from post p where mlp.post_id = p.id) as post_name, -(select c.id from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_id, -(select c.name from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_name -from mod_lock_post mlp; - -create view mod_remove_comment_view as -select mrc.*, -(select name from user_ u where mrc.mod_user_id = u.id) as mod_user_name, -(select c.id from comment c where mrc.comment_id = c.id) as comment_user_id, -(select name from user_ u, comment c where mrc.comment_id = c.id and u.id = c.creator_id) as comment_user_name, -(select content from comment c where mrc.comment_id = c.id) as comment_content, -(select p.id from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_id, -(select p.name from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_name, -(select co.id from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_id, -(select co.name from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_name -from mod_remove_comment mrc; - -create view mod_sticky_post_view as -select msp.*, -(select name from user_ u where msp.mod_user_id = u.id) as mod_user_name, -(select name from post p where msp.post_id = p.id) as post_name, -(select c.id from post p, community c where msp.post_id = p.id and p.community_id = c.id) as community_id, -(select c.name from post p, community c where msp.post_id = p.id and p.community_id = c.id) as community_name -from mod_sticky_post msp; diff --git a/server/migrations/2020-02-06-165953_change_post_title_length/up.sql b/server/migrations/2020-02-06-165953_change_post_title_length/up.sql deleted file mode 100644 index 006a7d04..00000000 --- a/server/migrations/2020-02-06-165953_change_post_title_length/up.sql +++ /dev/null @@ -1,133 +0,0 @@ --- Drop the dependent views -drop view post_view; -drop view post_mview; -drop materialized view post_aggregates_mview; -drop view post_aggregates_view; -drop view mod_remove_post_view; -drop view mod_sticky_post_view; -drop view mod_lock_post_view; -drop view mod_remove_comment_view; - --- Add the extra post limit -alter table post alter column name type varchar(200); - --- regen post view -create view post_aggregates_view as -select -p.*, -(select u.banned from user_ u where p.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where p.creator_id = user_.id) as creator_name, -(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, -(select name from community where p.community_id = community.id) as community_name, -(select removed from community c where p.community_id = c.id) as community_removed, -(select deleted from community c where p.community_id = c.id) as community_deleted, -(select nsfw from community c where p.community_id = c.id) as community_nsfw, -(select count(*) from comment where comment.post_id = p.id) as number_of_comments, -coalesce(sum(pl.score), 0) as score, -count (case when pl.score = 1 then 1 else null end) as upvotes, -count (case when pl.score = -1 then 1 else null end) as downvotes, -hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank -from post p -left join post_like pl on p.id = pl.post_id -group by p.id; - -create materialized view post_aggregates_mview as select * from post_aggregates_view; - -create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); - -create view post_view as -with all_post as ( - select - pa.* - from post_aggregates_view pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - -create view post_mview as -with all_post as ( - select - pa.* - from post_aggregates_mview pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - --- The mod views - -create view mod_remove_post_view as -select mrp.*, -(select name from user_ u where mrp.mod_user_id = u.id) as mod_user_name, -(select name from post p where mrp.post_id = p.id) as post_name, -(select c.id from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_id, -(select c.name from post p, community c where mrp.post_id = p.id and p.community_id = c.id) as community_name -from mod_remove_post mrp; - -create view mod_lock_post_view as -select mlp.*, -(select name from user_ u where mlp.mod_user_id = u.id) as mod_user_name, -(select name from post p where mlp.post_id = p.id) as post_name, -(select c.id from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_id, -(select c.name from post p, community c where mlp.post_id = p.id and p.community_id = c.id) as community_name -from mod_lock_post mlp; - -create view mod_remove_comment_view as -select mrc.*, -(select name from user_ u where mrc.mod_user_id = u.id) as mod_user_name, -(select c.id from comment c where mrc.comment_id = c.id) as comment_user_id, -(select name from user_ u, comment c where mrc.comment_id = c.id and u.id = c.creator_id) as comment_user_name, -(select content from comment c where mrc.comment_id = c.id) as comment_content, -(select p.id from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_id, -(select p.name from post p, comment c where mrc.comment_id = c.id and c.post_id = p.id) as post_name, -(select co.id from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_id, -(select co.name from comment c, post p, community co where mrc.comment_id = c.id and c.post_id = p.id and p.community_id = co.id) as community_name -from mod_remove_comment mrc; - -create view mod_sticky_post_view as -select msp.*, -(select name from user_ u where msp.mod_user_id = u.id) as mod_user_name, -(select name from post p where msp.post_id = p.id) as post_name, -(select c.id from post p, community c where msp.post_id = p.id and p.community_id = c.id) as community_id, -(select c.name from post p, community c where msp.post_id = p.id and p.community_id = c.id) as community_name -from mod_sticky_post msp; diff --git a/server/migrations/2020-02-07-210055_add_comment_subscribed/down.sql b/server/migrations/2020-02-07-210055_add_comment_subscribed/down.sql deleted file mode 100644 index b6120d15..00000000 --- a/server/migrations/2020-02-07-210055_add_comment_subscribed/down.sql +++ /dev/null @@ -1,206 +0,0 @@ - -drop view reply_view; -drop view user_mention_view; -drop view user_mention_mview; -drop view comment_view; -drop view comment_mview; -drop materialized view comment_aggregates_mview; -drop view comment_aggregates_view; - --- reply and comment view -create view comment_aggregates_view as -select -c.*, -(select community_id from post p where p.id = c.post_id), -(select u.banned from user_ u where c.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where c.creator_id = user_.id) as creator_name, -(select avatar from user_ where c.creator_id = user_.id) as creator_avatar, -coalesce(sum(cl.score), 0) as score, -count (case when cl.score = 1 then 1 else null end) as upvotes, -count (case when cl.score = -1 then 1 else null end) as downvotes -from comment c -left join comment_like cl on c.id = cl.comment_id -group by c.id; - -create materialized view comment_aggregates_mview as select * from comment_aggregates_view; - -create unique index idx_comment_aggregates_mview_id on comment_aggregates_mview (id); - -create view comment_view as -with all_comment as -( - select - ca.* - from comment_aggregates_view ca -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - -create view comment_mview as -with all_comment as -( - select - ca.* - from comment_aggregates_mview ca -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - - --- Do the reply_view referencing the comment_mview -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_mview cv, closereply -where closereply.id = cv.id -; - --- user mention -create view user_mention_view as -select - c.id, - um.id as user_mention_id, - c.creator_id, - c.post_id, - c.parent_id, - c.content, - c.removed, - um.read, - c.published, - c.updated, - c.deleted, - c.community_id, - c.banned, - c.banned_from_community, - c.creator_name, - c.creator_avatar, - c.score, - c.upvotes, - c.downvotes, - c.user_id, - c.my_vote, - c.saved, - um.recipient_id -from user_mention um, comment_view c -where um.comment_id = c.id; - - -create view user_mention_mview as -with all_comment as -( - select - ca.* - from comment_aggregates_mview ca -) - -select - ac.id, - um.id as user_mention_id, - ac.creator_id, - ac.post_id, - ac.parent_id, - ac.content, - ac.removed, - um.read, - ac.published, - ac.updated, - ac.deleted, - ac.community_id, - ac.banned, - ac.banned_from_community, - ac.creator_name, - ac.creator_avatar, - ac.score, - ac.upvotes, - ac.downvotes, - u.id as user_id, - coalesce(cl.score, 0) as my_vote, - (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved, - um.recipient_id -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id -left join user_mention um on um.comment_id = ac.id - -union all - -select - ac.id, - um.id as user_mention_id, - ac.creator_id, - ac.post_id, - ac.parent_id, - ac.content, - ac.removed, - um.read, - ac.published, - ac.updated, - ac.deleted, - ac.community_id, - ac.banned, - ac.banned_from_community, - ac.creator_name, - ac.creator_avatar, - ac.score, - ac.upvotes, - ac.downvotes, - null as user_id, - null as my_vote, - null as saved, - um.recipient_id -from all_comment ac -left join user_mention um on um.comment_id = ac.id -; - diff --git a/server/migrations/2020-02-07-210055_add_comment_subscribed/up.sql b/server/migrations/2020-02-07-210055_add_comment_subscribed/up.sql deleted file mode 100644 index 8836a571..00000000 --- a/server/migrations/2020-02-07-210055_add_comment_subscribed/up.sql +++ /dev/null @@ -1,220 +0,0 @@ - --- Adding community name, hot_rank, to comment_view, user_mention_view, and subscribed to comment_view - --- Rebuild the comment view -drop view reply_view; -drop view user_mention_view; -drop view user_mention_mview; -drop view comment_view; -drop view comment_mview; -drop materialized view comment_aggregates_mview; -drop view comment_aggregates_view; - --- reply and comment view -create view comment_aggregates_view as -select -c.*, -(select community_id from post p where p.id = c.post_id), -(select co.name from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_name, -(select u.banned from user_ u where c.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where c.creator_id = user_.id) as creator_name, -(select avatar from user_ where c.creator_id = user_.id) as creator_avatar, -coalesce(sum(cl.score), 0) as score, -count (case when cl.score = 1 then 1 else null end) as upvotes, -count (case when cl.score = -1 then 1 else null end) as downvotes, -hot_rank(coalesce(sum(cl.score) , 0), c.published) as hot_rank -from comment c -left join comment_like cl on c.id = cl.comment_id -group by c.id; - -create materialized view comment_aggregates_mview as select * from comment_aggregates_view; - -create unique index idx_comment_aggregates_mview_id on comment_aggregates_mview (id); - -create view comment_view as -with all_comment as -( - select - ca.* - from comment_aggregates_view ca -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as subscribed, - null as saved -from all_comment ac -; - -create view comment_mview as -with all_comment as -( - select - ca.* - from comment_aggregates_mview ca -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as subscribed, - null as saved -from all_comment ac -; - --- Do the reply_view referencing the comment_mview -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_mview cv, closereply -where closereply.id = cv.id -; - --- user mention -create view user_mention_view as -select - c.id, - um.id as user_mention_id, - c.creator_id, - c.post_id, - c.parent_id, - c.content, - c.removed, - um.read, - c.published, - c.updated, - c.deleted, - c.community_id, - c.community_name, - c.banned, - c.banned_from_community, - c.creator_name, - c.creator_avatar, - c.score, - c.upvotes, - c.downvotes, - c.hot_rank, - c.user_id, - c.my_vote, - c.saved, - um.recipient_id -from user_mention um, comment_view c -where um.comment_id = c.id; - - -create view user_mention_mview as -with all_comment as -( - select - ca.* - from comment_aggregates_mview ca -) - -select - ac.id, - um.id as user_mention_id, - ac.creator_id, - ac.post_id, - ac.parent_id, - ac.content, - ac.removed, - um.read, - ac.published, - ac.updated, - ac.deleted, - ac.community_id, - ac.community_name, - ac.banned, - ac.banned_from_community, - ac.creator_name, - ac.creator_avatar, - ac.score, - ac.upvotes, - ac.downvotes, - ac.hot_rank, - u.id as user_id, - coalesce(cl.score, 0) as my_vote, - (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved, - um.recipient_id -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id -left join user_mention um on um.comment_id = ac.id - -union all - -select - ac.id, - um.id as user_mention_id, - ac.creator_id, - ac.post_id, - ac.parent_id, - ac.content, - ac.removed, - um.read, - ac.published, - ac.updated, - ac.deleted, - ac.community_id, - ac.community_name, - ac.banned, - ac.banned_from_community, - ac.creator_name, - ac.creator_avatar, - ac.score, - ac.upvotes, - ac.downvotes, - ac.hot_rank, - null as user_id, - null as my_vote, - null as saved, - um.recipient_id -from all_comment ac -left join user_mention um on um.comment_id = ac.id -; - diff --git a/server/migrations/2020-02-08-145624_add_post_newest_activity_time/down.sql b/server/migrations/2020-02-08-145624_add_post_newest_activity_time/down.sql deleted file mode 100644 index 8b912fa3..00000000 --- a/server/migrations/2020-02-08-145624_add_post_newest_activity_time/down.sql +++ /dev/null @@ -1,88 +0,0 @@ -drop view post_view; -drop view post_mview; -drop materialized view post_aggregates_mview; -drop view post_aggregates_view; - --- regen post view -create view post_aggregates_view as -select -p.*, -(select u.banned from user_ u where p.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where p.creator_id = user_.id) as creator_name, -(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, -(select name from community where p.community_id = community.id) as community_name, -(select removed from community c where p.community_id = c.id) as community_removed, -(select deleted from community c where p.community_id = c.id) as community_deleted, -(select nsfw from community c where p.community_id = c.id) as community_nsfw, -(select count(*) from comment where comment.post_id = p.id) as number_of_comments, -coalesce(sum(pl.score), 0) as score, -count (case when pl.score = 1 then 1 else null end) as upvotes, -count (case when pl.score = -1 then 1 else null end) as downvotes, -hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank -from post p -left join post_like pl on p.id = pl.post_id -group by p.id; - -create materialized view post_aggregates_mview as select * from post_aggregates_view; - -create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); - -create view post_view as -with all_post as ( - select - pa.* - from post_aggregates_view pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - -create view post_mview as -with all_post as ( - select - pa.* - from post_aggregates_mview pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - diff --git a/server/migrations/2020-02-08-145624_add_post_newest_activity_time/up.sql b/server/migrations/2020-02-08-145624_add_post_newest_activity_time/up.sql deleted file mode 100644 index e1541277..00000000 --- a/server/migrations/2020-02-08-145624_add_post_newest_activity_time/up.sql +++ /dev/null @@ -1,106 +0,0 @@ --- Adds a newest_activity_time for the post_views, in order to sort by newest comment -drop view post_view; -drop view post_mview; -drop materialized view post_aggregates_mview; -drop view post_aggregates_view; - --- regen post view -create view post_aggregates_view as -select -p.*, -(select u.banned from user_ u where p.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where p.creator_id = user_.id) as creator_name, -(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, -(select name from community where p.community_id = community.id) as community_name, -(select removed from community c where p.community_id = c.id) as community_removed, -(select deleted from community c where p.community_id = c.id) as community_deleted, -(select nsfw from community c where p.community_id = c.id) as community_nsfw, -(select count(*) from comment where comment.post_id = p.id) as number_of_comments, -coalesce(sum(pl.score), 0) as score, -count (case when pl.score = 1 then 1 else null end) as upvotes, -count (case when pl.score = -1 then 1 else null end) as downvotes, -hot_rank(coalesce(sum(pl.score) , 0), - ( - case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps - else greatest(c.recent_comment_time, p.published) - end - ) -) as hot_rank, -( - case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps - else greatest(c.recent_comment_time, p.published) - end -) as newest_activity_time -from post p -left join post_like pl on p.id = pl.post_id -left join ( - select post_id, - max(published) as recent_comment_time - from comment - group by 1 -) c on p.id = c.post_id -group by p.id, c.recent_comment_time; - -create materialized view post_aggregates_mview as select * from post_aggregates_view; - -create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); - -create view post_view as -with all_post as ( - select - pa.* - from post_aggregates_view pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - -create view post_mview as -with all_post as ( - select - pa.* - from post_aggregates_mview pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - diff --git a/server/migrations/2020-03-06-202329_add_post_iframely_data/down.sql b/server/migrations/2020-03-06-202329_add_post_iframely_data/down.sql deleted file mode 100644 index 71dcc6ba..00000000 --- a/server/migrations/2020-03-06-202329_add_post_iframely_data/down.sql +++ /dev/null @@ -1,112 +0,0 @@ --- Adds a newest_activity_time for the post_views, in order to sort by newest comment -drop view post_view; -drop view post_mview; -drop materialized view post_aggregates_mview; -drop view post_aggregates_view; - --- Drop the columns -alter table post drop column embed_title; -alter table post drop column embed_description; -alter table post drop column embed_html; -alter table post drop column thumbnail_url; - --- regen post view -create view post_aggregates_view as -select -p.*, -(select u.banned from user_ u where p.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where p.creator_id = user_.id) as creator_name, -(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, -(select name from community where p.community_id = community.id) as community_name, -(select removed from community c where p.community_id = c.id) as community_removed, -(select deleted from community c where p.community_id = c.id) as community_deleted, -(select nsfw from community c where p.community_id = c.id) as community_nsfw, -(select count(*) from comment where comment.post_id = p.id) as number_of_comments, -coalesce(sum(pl.score), 0) as score, -count (case when pl.score = 1 then 1 else null end) as upvotes, -count (case when pl.score = -1 then 1 else null end) as downvotes, -hot_rank(coalesce(sum(pl.score) , 0), - ( - case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps - else greatest(c.recent_comment_time, p.published) - end - ) -) as hot_rank, -( - case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps - else greatest(c.recent_comment_time, p.published) - end -) as newest_activity_time -from post p -left join post_like pl on p.id = pl.post_id -left join ( - select post_id, - max(published) as recent_comment_time - from comment - group by 1 -) c on p.id = c.post_id -group by p.id, c.recent_comment_time; - -create materialized view post_aggregates_mview as select * from post_aggregates_view; - -create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); - -create view post_view as -with all_post as ( - select - pa.* - from post_aggregates_view pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - -create view post_mview as -with all_post as ( - select - pa.* - from post_aggregates_mview pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - diff --git a/server/migrations/2020-03-06-202329_add_post_iframely_data/up.sql b/server/migrations/2020-03-06-202329_add_post_iframely_data/up.sql deleted file mode 100644 index d431bb7f..00000000 --- a/server/migrations/2020-03-06-202329_add_post_iframely_data/up.sql +++ /dev/null @@ -1,115 +0,0 @@ --- Add the columns -alter table post add column embed_title text; -alter table post add column embed_description text; -alter table post add column embed_html text; -alter table post add column thumbnail_url text; - --- Regenerate the views - --- Adds a newest_activity_time for the post_views, in order to sort by newest comment -drop view post_view; -drop view post_mview; -drop materialized view post_aggregates_mview; -drop view post_aggregates_view; - --- regen post view -create view post_aggregates_view as -select -p.*, -(select u.banned from user_ u where p.creator_id = u.id) as banned, -(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, -(select name from user_ where p.creator_id = user_.id) as creator_name, -(select avatar from user_ where p.creator_id = user_.id) as creator_avatar, -(select name from community where p.community_id = community.id) as community_name, -(select removed from community c where p.community_id = c.id) as community_removed, -(select deleted from community c where p.community_id = c.id) as community_deleted, -(select nsfw from community c where p.community_id = c.id) as community_nsfw, -(select count(*) from comment where comment.post_id = p.id) as number_of_comments, -coalesce(sum(pl.score), 0) as score, -count (case when pl.score = 1 then 1 else null end) as upvotes, -count (case when pl.score = -1 then 1 else null end) as downvotes, -hot_rank(coalesce(sum(pl.score) , 0), - ( - case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps - else greatest(c.recent_comment_time, p.published) - end - ) -) as hot_rank, -( - case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps - else greatest(c.recent_comment_time, p.published) - end -) as newest_activity_time -from post p -left join post_like pl on p.id = pl.post_id -left join ( - select post_id, - max(published) as recent_comment_time - from comment - group by 1 -) c on p.id = c.post_id -group by p.id, c.recent_comment_time; - -create materialized view post_aggregates_mview as select * from post_aggregates_view; - -create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id); - -create view post_view as -with all_post as ( - select - pa.* - from post_aggregates_view pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - -create view post_mview as -with all_post as ( - select - pa.* - from post_aggregates_mview pa -) -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - - diff --git a/server/migrations_testing/2020-01-13-025151_create_materialized_views/down.sql b/server/migrations_testing/2020-01-13-025151_create_materialized_views/down.sql deleted file mode 100644 index ba801ba5..00000000 --- a/server/migrations_testing/2020-01-13-025151_create_materialized_views/down.sql +++ /dev/null @@ -1,211 +0,0 @@ --- functions and triggers -drop trigger refresh_user on user_; -drop function refresh_user(); -drop trigger refresh_post on post; -drop function refresh_post(); -drop trigger refresh_post_like on post_like; -drop function refresh_post_like(); -drop trigger refresh_community on community; -drop function refresh_community(); -drop trigger refresh_community_follower on community_follower; -drop function refresh_community_follower(); -drop trigger refresh_comment on comment; -drop function refresh_comment(); -drop trigger refresh_comment_like on comment_like; -drop function refresh_comment_like(); - --- post --- Recreate the view -drop materialized view post_view; -create view post_view as -with all_post as -( - select - p.*, - (select u.banned from user_ u where p.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where p.creator_id = user_.id) as creator_name, - (select avatar from user_ where p.creator_id = user_.id) as creator_avatar, - (select name from community where p.community_id = community.id) as community_name, - (select removed from community c where p.community_id = c.id) as community_removed, - (select deleted from community c where p.community_id = c.id) as community_deleted, - (select nsfw from community c where p.community_id = c.id) as community_nsfw, - (select count(*) from comment where comment.post_id = p.id) as number_of_comments, - coalesce(sum(pl.score), 0) as score, - count (case when pl.score = 1 then 1 else null end) as upvotes, - count (case when pl.score = -1 then 1 else null end) as downvotes, - hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank - from post p - left join post_like pl on p.id = pl.post_id - group by p.id -) - -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -; - - -drop materialized view user_view; -create view user_view as -select id, -name, -avatar, -email, -fedi_name, -admin, -banned, -show_avatars, -send_notifications_to_email, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; - - --- community -drop materialized view community_view; -create view community_view as -with all_community as -( - select *, - (select name from user_ u where c.creator_id = u.id) as creator_name, - (select avatar from user_ u where c.creator_id = u.id) as creator_avatar, - (select name from category ct where c.category_id = ct.id) as category_name, - (select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers, - (select count(*) from post p where p.community_id = c.id) as number_of_posts, - (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments, - hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank - from community c -) - -select -ac.*, -u.id as user_id, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed -from user_ u -cross join all_community ac - -union all - -select -ac.*, -null as user_id, -null as subscribed -from all_community ac -; - --- reply and comment view -drop view reply_view; -drop view user_mention_view; -drop materialized view comment_view; -create view comment_view as -with all_comment as -( - select - c.*, - (select community_id from post p where p.id = c.post_id), - (select u.banned from user_ u where c.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where c.creator_id = user_.id) as creator_name, - (select avatar from user_ where c.creator_id = user_.id) as creator_avatar, - coalesce(sum(cl.score), 0) as score, - count (case when cl.score = 1 then 1 else null end) as upvotes, - count (case when cl.score = -1 then 1 else null end) as downvotes - from comment c - left join comment_like cl on c.id = cl.comment_id - group by c.id -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_view cv, closereply -where closereply.id = cv.id -; - --- user mention -create view user_mention_view as -select - c.id, - um.id as user_mention_id, - c.creator_id, - c.post_id, - c.parent_id, - c.content, - c.removed, - um.read, - c.published, - c.updated, - c.deleted, - c.community_id, - c.banned, - c.banned_from_community, - c.creator_name, - c.creator_avatar, - c.score, - c.upvotes, - c.downvotes, - c.user_id, - c.my_vote, - c.saved, - um.recipient_id -from user_mention um, comment_view c -where um.comment_id = c.id; - diff --git a/server/migrations_testing/2020-01-13-025151_create_materialized_views/up.sql b/server/migrations_testing/2020-01-13-025151_create_materialized_views/up.sql deleted file mode 100644 index 33b0442f..00000000 --- a/server/migrations_testing/2020-01-13-025151_create_materialized_views/up.sql +++ /dev/null @@ -1,324 +0,0 @@ --- post -drop view post_view; -create materialized view post_view as -with all_post as -( - select - p.*, - (select u.banned from user_ u where p.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where p.creator_id = user_.id) as creator_name, - (select avatar from user_ where p.creator_id = user_.id) as creator_avatar, - (select name from community where p.community_id = community.id) as community_name, - (select removed from community c where p.community_id = c.id) as community_removed, - (select deleted from community c where p.community_id = c.id) as community_deleted, - (select nsfw from community c where p.community_id = c.id) as community_nsfw, - (select count(*) from comment where comment.post_id = p.id) as number_of_comments, - coalesce(sum(pl.score), 0) as score, - count (case when pl.score = 1 then 1 else null end) as upvotes, - count (case when pl.score = -1 then 1 else null end) as downvotes, - hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank - from post p - left join post_like pl on p.id = pl.post_id - group by p.id -) - -select -ap.*, -u.id as user_id, -coalesce(pl.score, 0) as my_vote, -(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed, -(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read, -(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved -from user_ u -cross join all_post ap -left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id - -union all - -select -ap.*, -null as user_id, -null as my_vote, -null as subscribed, -null as read, -null as saved -from all_post ap -with data -; - -create unique index idx_post_view_unique on post_view (id, user_id); -create index idx_post_view_user_id on post_view (user_id); -create index idx_post_view_hot_rank_published on post_view (hot_rank desc, published desc); -create index idx_post_view_published on post_view (published desc); -create index idx_post_view_score on post_view (score desc); - --- user_view -drop view user_view; -create materialized view user_view as -select id, -name, -avatar, -email, -fedi_name, -admin, -banned, -show_avatars, -send_notifications_to_email, -published, -(select count(*) from post p where p.creator_id = u.id) as number_of_posts, -(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score, -(select count(*) from comment c where c.creator_id = u.id) as number_of_comments, -(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score -from user_ u; - -create unique index idx_user_view_unique on user_view (id); -create index idx_user_view_comment_published on user_view (comment_score desc, published desc); -create index idx_user_view_admin on user_view (admin); -create index idx_user_view_banned on user_view (banned); - --- community -drop view community_view; -create materialized view community_view as -with all_community as -( - select *, - (select name from user_ u where c.creator_id = u.id) as creator_name, - (select avatar from user_ u where c.creator_id = u.id) as creator_avatar, - (select name from category ct where c.category_id = ct.id) as category_name, - (select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers, - (select count(*) from post p where p.community_id = c.id) as number_of_posts, - (select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments, - hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank - from community c -) - -select -ac.*, -u.id as user_id, -(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed -from user_ u -cross join all_community ac - -union all - -select -ac.*, -null as user_id, -null as subscribed -from all_community ac -; - -create unique index idx_community_view_unique on community_view (id, user_id); -create index idx_community_view_user_id on community_view (user_id); -create index idx_community_view_hot_rank_subscribed on community_view (hot_rank desc, number_of_subscribers desc); - - --- reply and comment view -drop view reply_view; -drop view user_mention_view; -drop view comment_view; -create materialized view comment_view as -with all_comment as -( - select - c.*, - (select community_id from post p where p.id = c.post_id), - (select u.banned from user_ u where c.creator_id = u.id) as banned, - (select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, - (select name from user_ where c.creator_id = user_.id) as creator_name, - (select avatar from user_ where c.creator_id = user_.id) as creator_avatar, - coalesce(sum(cl.score), 0) as score, - count (case when cl.score = 1 then 1 else null end) as upvotes, - count (case when cl.score = -1 then 1 else null end) as downvotes - from comment c - left join comment_like cl on c.id = cl.comment_id - group by c.id -) - -select -ac.*, -u.id as user_id, -coalesce(cl.score, 0) as my_vote, -(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved -from user_ u -cross join all_comment ac -left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id - -union all - -select - ac.*, - null as user_id, - null as my_vote, - null as saved -from all_comment ac -; - -create unique index idx_comment_view_unique on comment_view (id, user_id); -create index idx_comment_view_user_id on comment_view (user_id); -create index idx_comment_view_creator_id on comment_view (creator_id); -create index idx_comment_view_post_id on comment_view (post_id); -create index idx_comment_view_score on comment_view (score desc); - -create view reply_view as -with closereply as ( - select - c2.id, - c2.creator_id as sender_id, - c.creator_id as recipient_id - from comment c - inner join comment c2 on c.id = c2.parent_id - where c2.creator_id != c.creator_id - -- Do union where post is null - union - select - c.id, - c.creator_id as sender_id, - p.creator_id as recipient_id - from comment c, post p - where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id -) -select cv.*, -closereply.recipient_id -from comment_view cv, closereply -where closereply.id = cv.id -; - --- user mention -create view user_mention_view as -select - c.id, - um.id as user_mention_id, - c.creator_id, - c.post_id, - c.parent_id, - c.content, - c.removed, - um.read, - c.published, - c.updated, - c.deleted, - c.community_id, - c.banned, - c.banned_from_community, - c.creator_name, - c.creator_avatar, - c.score, - c.upvotes, - c.downvotes, - c.user_id, - c.my_vote, - c.saved, - um.recipient_id -from user_mention um, comment_view c -where um.comment_id = c.id; - --- user -create or replace function refresh_user() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently comment_view; -- cause of bans - refresh materialized view concurrently post_view; - return null; -end $$; - -create trigger refresh_user -after insert or update or delete or truncate -on user_ -for each statement -execute procedure refresh_user(); - --- post -create or replace function refresh_post() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently post_view; - return null; -end $$; - -create trigger refresh_post -after insert or update or delete or truncate -on post -for each statement -execute procedure refresh_post(); - --- post_like -create or replace function refresh_post_like() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently post_view; - return null; -end $$; - -create trigger refresh_post_like -after insert or update or delete or truncate -on post_like -for each statement -execute procedure refresh_post_like(); - --- community -create or replace function refresh_community() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently post_view; - refresh materialized view concurrently community_view; - return null; -end $$; - -create trigger refresh_community -after insert or update or delete or truncate -on community -for each statement -execute procedure refresh_community(); - --- community_follower -create or replace function refresh_community_follower() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently community_view; - refresh materialized view concurrently post_view; - return null; -end $$; - -create trigger refresh_community_follower -after insert or update or delete or truncate -on community_follower -for each statement -execute procedure refresh_community_follower(); - --- comment -create or replace function refresh_comment() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently post_view; - refresh materialized view concurrently comment_view; - return null; -end $$; - -create trigger refresh_comment -after insert or update or delete or truncate -on comment -for each statement -execute procedure refresh_comment(); - --- comment_like -create or replace function refresh_comment_like() -returns trigger language plpgsql -as $$ -begin - refresh materialized view concurrently comment_view; - return null; -end $$; - -create trigger refresh_comment_like -after insert or update or delete or truncate -on comment_like -for each statement -execute procedure refresh_comment_like(); diff --git a/server/query_testing/apache_bench_report.sh b/server/query_testing/apache_bench_report.sh deleted file mode 100755 index 06ceb484..00000000 --- a/server/query_testing/apache_bench_report.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -set -e - -declare -a arr=( -"https://mastodon.social/" -"https://peertube.social/" -"https://dev.lemmy.ml/" -"https://dev.lemmy.ml/feeds/all.xml" -"https://dev.lemmy.ml/.well-known/nodeinfo" -"https://fediverse.blog/.well-known/nodeinfo" -"https://torrents-csv.ml/service/search?q=wheel&page=1&type_=torrent" -) - -## check if ab installed -if ! [ -x "$(command -v ab)" ]; then - echo 'Error: ab (Apache Bench) is not installed. https://httpd.apache.org/docs/2.4/programs/ab.html' >&2 - exit 1 -fi - -## now loop through the above array -for i in "${arr[@]}" -do - ab -c 10 -t 10 "$i" > out.abtest - grep "Server Hostname:" out.abtest - grep "Document Path:" out.abtest - grep "Requests per second" out.abtest - grep "(mean, across all concurrent requests)" out.abtest - grep "Transfer rate:" out.abtest - echo "---" -done - -rm *.abtest diff --git a/server/query_testing/api_benchmark.sh b/server/query_testing/api_benchmark.sh deleted file mode 100755 index 9f06580a..00000000 --- a/server/query_testing/api_benchmark.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -set -e - -# By default, this script runs against `http://127.0.0.1:8536`, but you can pass a different Lemmy instance, -# eg `./api_benchmark.sh "https://example.com"`. -DOMAIN=${1:-"http://127.0.0.1:8536"} - -declare -a arr=( -"/api/v1/site" -"/api/v1/categories" -"/api/v1/modlog" -"/api/v1/search?q=test&type_=Posts&sort=Hot" -"/api/v1/community" -"/api/v1/community/list?sort=Hot" -"/api/v1/post/list?sort=Hot&type_=All" -) - -## check if ab installed -if ! [ -x "$(command -v ab)" ]; then - echo 'Error: ab (Apache Bench) is not installed. https://httpd.apache.org/docs/2.4/programs/ab.html' >&2 - exit 1 -fi - -## now loop through the above array -for path in "${arr[@]}" -do - URL="$DOMAIN$path" - printf "\n\n\n" - echo "testing $URL" - curl --show-error --fail --silent "$URL" >/dev/null - ab -c 64 -t 10 "$URL" > out.abtest - grep "Server Hostname:" out.abtest - grep "Document Path:" out.abtest - grep "Requests per second" out.abtest - grep "(mean, across all concurrent requests)" out.abtest - grep "Transfer rate:" out.abtest - echo "---" -done - -rm *.abtest diff --git a/server/query_testing/generate_explain_reports.sh b/server/query_testing/generate_explain_reports.sh deleted file mode 100755 index 6ce7dc42..00000000 --- a/server/query_testing/generate_explain_reports.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -set -e - -# Do the views first - -echo "explain (analyze, format json) select * from user_mview" > explain.sql -psql -qAt -U lemmy -f explain.sql > user_view.json - -echo "explain (analyze, format json) select * from post_mview where user_id is null order by hot_rank desc, published desc" > explain.sql -psql -qAt -U lemmy -f explain.sql > post_view.json - -echo "explain (analyze, format json) select * from comment_mview where user_id is null" > explain.sql -psql -qAt -U lemmy -f explain.sql > comment_view.json - -echo "explain (analyze, format json) select * from community_mview where user_id is null order by hot_rank desc" > explain.sql -psql -qAt -U lemmy -f explain.sql > community_view.json - -echo "explain (analyze, format json) select * from site_view limit 1" > explain.sql -psql -qAt -U lemmy -f explain.sql > site_view.json - -echo "explain (analyze, format json) select * from reply_view where user_id = 34 and recipient_id = 34" > explain.sql -psql -qAt -U lemmy -f explain.sql > reply_view.json - -echo "explain (analyze, format json) select * from user_mention_view where user_id = 34 and recipient_id = 34" > explain.sql -psql -qAt -U lemmy -f explain.sql > user_mention_view.json - -echo "explain (analyze, format json) select * from user_mention_mview where user_id = 34 and recipient_id = 34" > explain.sql -psql -qAt -U lemmy -f explain.sql > user_mention_mview.json - -grep "Execution Time" *.json - -rm explain.sql diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index 058c7267..9f9d46d3 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -4,7 +4,7 @@ use super::*; pub struct CreateComment { content: String, parent_id: Option, - edit_id: Option, // TODO this isn't used + edit_id: Option, pub post_id: i32, auth: String, } @@ -12,7 +12,7 @@ pub struct CreateComment { #[derive(Serialize, Deserialize)] pub struct EditComment { content: String, - parent_id: Option, // TODO why are the parent_id, creator_id, post_id, etc fields required? They aren't going to change + parent_id: Option, edit_id: i32, creator_id: i32, pub post_id: i32, @@ -32,8 +32,8 @@ pub struct SaveComment { #[derive(Serialize, Deserialize, Clone)] pub struct CommentResponse { + op: String, pub comment: CommentView, - pub recipient_ids: Vec, } #[derive(Serialize, Deserialize)] @@ -44,51 +44,27 @@ pub struct CreateCommentLike { auth: String, } -#[derive(Serialize, Deserialize)] -pub struct GetComments { - type_: String, - sort: String, - page: Option, - limit: Option, - pub community_id: Option, - auth: Option, -} - -#[derive(Serialize, Deserialize)] -pub struct GetCommentsResponse { - comments: Vec, -} - -impl Perform for Oper { - type Response = CommentResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &CreateComment = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let hostname = &format!("https://{}", Settings::get().hostname); - - let conn = pool.get()?; - // Check for a community ban let post = Post::read(&conn, data.post_id)?; if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(APIError::err("community_ban").into()); + return Err(APIError::err(&self.op, "community_ban"))?; } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); + return Err(APIError::err(&self.op, "site_ban"))?; } let content_slurs_removed = remove_slurs(&data.content.to_owned()); @@ -106,24 +82,24 @@ impl Perform for Oper { let inserted_comment = match Comment::create(&conn, &comment_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_create_comment"))?, }; - let mut recipient_ids = Vec::new(); - // Scan the comment for user mentions, add those rows let extracted_usernames = extract_usernames(&comment_form.content); for username_mention in &extracted_usernames { - if let Ok(mention_user) = User_::read_from_name(&conn, (*username_mention).to_string()) { + let mention_user = User_::read_from_name(&conn, username_mention.to_string()); + + if mention_user.is_ok() { + let mention_user_id = mention_user?.id; + // You can't mention yourself // At some point, make it so you can't tag the parent creator either // This can cause two notifications, one for reply and the other for mention - if mention_user.id != user_id { - recipient_ids.push(mention_user.id); - + if mention_user_id != user_id { let user_mention_form = UserMentionForm { - recipient_id: mention_user.id, + recipient_id: mention_user_id, comment_id: inserted_comment.id, read: None, }; @@ -132,85 +108,12 @@ impl Perform for Oper { // Let the uniqueness handle this fail match UserMention::create(&conn, &user_mention_form) { Ok(_mention) => (), - Err(_e) => error!("{}", &_e), - }; - - // Send an email to those users that have notifications on - if mention_user.send_notifications_to_email { - if let Some(mention_email) = mention_user.email { - let subject = &format!( - "{} - Mentioned by {}", - Settings::get().hostname, - claims.username - ); - let html = &format!( - "

User Mention


{} - {}

inbox", - claims.username, comment_form.content, hostname - ); - match send_email(subject, &mention_email, &mention_user.name, html) { - Ok(_o) => _o, - Err(e) => error!("{}", e), - }; - } + Err(_e) => eprintln!("{}", &_e), } } } } - // Send notifs to the parent commenter / poster - match data.parent_id { - Some(parent_id) => { - let parent_comment = Comment::read(&conn, parent_id)?; - if parent_comment.creator_id != user_id { - let parent_user = User_::read(&conn, parent_comment.creator_id)?; - recipient_ids.push(parent_user.id); - - if parent_user.send_notifications_to_email { - if let Some(comment_reply_email) = parent_user.email { - let subject = &format!( - "{} - Reply from {}", - Settings::get().hostname, - claims.username - ); - let html = &format!( - "

Comment Reply


{} - {}

inbox", - claims.username, comment_form.content, hostname - ); - match send_email(subject, &comment_reply_email, &parent_user.name, html) { - Ok(_o) => _o, - Err(e) => error!("{}", e), - }; - } - } - } - } - // Its a post - None => { - if post.creator_id != user_id { - let parent_user = User_::read(&conn, post.creator_id)?; - recipient_ids.push(parent_user.id); - - if parent_user.send_notifications_to_email { - if let Some(post_reply_email) = parent_user.email { - let subject = &format!( - "{} - Reply from {}", - Settings::get().hostname, - claims.username - ); - let html = &format!( - "

Post Reply


{} - {}

inbox", - claims.username, comment_form.content, hostname - ); - match send_email(subject, &post_reply_email, &parent_user.name, html) { - Ok(_o) => _o, - Err(e) => error!("{}", e), - }; - } - } - } - } - }; - // You like your own comment by default let like_form = CommentLikeForm { comment_id: inserted_comment.id, @@ -221,51 +124,30 @@ impl Perform for Oper { let _inserted_like = match CommentLike::like(&conn, &like_form) { Ok(like) => like, - Err(_e) => return Err(APIError::err("couldnt_like_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_comment"))?, }; let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?; - let mut res = CommentResponse { + Ok(CommentResponse { + op: self.op.to_string(), comment: comment_view, - recipient_ids, - }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendComment { - op: UserOperation::CreateComment, - comment: res.clone(), - my_id: ws.id, - }); - - // strip out the recipient_ids, so that - // users don't get double notifs - res.recipient_ids = Vec::new(); - } - - Ok(res) + }) } } -impl Perform for Oper { - type Response = CommentResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &EditComment = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - let orig_comment = CommentView::read(&conn, data.edit_id, None)?; // You are allowed to mark the comment as read even if you're banned. @@ -281,17 +163,17 @@ impl Perform for Oper { editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { - return Err(APIError::err("no_comment_edit_allowed").into()); + return Err(APIError::err(&self.op, "no_comment_edit_allowed"))?; } // Check for a community ban if CommunityUserBanView::get(&conn, user_id, orig_comment.community_id).is_ok() { - return Err(APIError::err("community_ban").into()); + return Err(APIError::err(&self.op, "community_ban"))?; } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); + return Err(APIError::err(&self.op, "site_ban"))?; } } @@ -314,16 +196,14 @@ impl Perform for Oper { let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, }; - let mut recipient_ids = Vec::new(); - // Scan the comment for user mentions, add those rows let extracted_usernames = extract_usernames(&comment_form.content); for username_mention in &extracted_usernames { - let mention_user = User_::read_from_name(&conn, (*username_mention).to_string()); + let mention_user = User_::read_from_name(&conn, username_mention.to_string()); if mention_user.is_ok() { let mention_user_id = mention_user?.id; @@ -332,8 +212,6 @@ impl Perform for Oper { // At some point, make it so you can't tag the parent creator either // This can cause two notifications, one for reply and the other for mention if mention_user_id != user_id { - recipient_ids.push(mention_user_id); - let user_mention_form = UserMentionForm { recipient_id: mention_user_id, comment_id: data.edit_id, @@ -344,27 +222,12 @@ impl Perform for Oper { // Let the uniqueness handle this fail match UserMention::create(&conn, &user_mention_form) { Ok(_mention) => (), - Err(_e) => error!("{}", &_e), + Err(_e) => eprintln!("{}", &_e), } } } } - // Add to recipient ids - match data.parent_id { - Some(parent_id) => { - let parent_comment = Comment::read(&conn, parent_id)?; - if parent_comment.creator_id != user_id { - let parent_user = User_::read(&conn, parent_comment.creator_id)?; - recipient_ids.push(parent_user.id); - } - } - None => { - let post = Post::read(&conn, data.post_id)?; - recipient_ids.push(post.creator_id); - } - } - // Mod tables if let Some(removed) = data.removed.to_owned() { let form = ModRemoveCommentForm { @@ -378,40 +241,21 @@ impl Perform for Oper { let comment_view = CommentView::read(&conn, data.edit_id, Some(user_id))?; - let mut res = CommentResponse { + Ok(CommentResponse { + op: self.op.to_string(), comment: comment_view, - recipient_ids, - }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendComment { - op: UserOperation::EditComment, - comment: res.clone(), - my_id: ws.id, - }); - - // strip out the recipient_ids, so that - // users don't get double notifs - res.recipient_ids = Vec::new(); - } - - Ok(res) + }) } } -impl Perform for Oper { - type Response = CommentResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &SaveComment = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; @@ -421,83 +265,56 @@ impl Perform for Oper { user_id, }; - let conn = pool.get()?; - if data.save { match CommentSaved::save(&conn, &comment_saved_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_save_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_comment"))?, }; } else { match CommentSaved::unsave(&conn, &comment_saved_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_save_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_comment"))?, }; } let comment_view = CommentView::read(&conn, data.comment_id, Some(user_id))?; Ok(CommentResponse { + op: self.op.to_string(), comment: comment_view, - recipient_ids: Vec::new(), }) } } -impl Perform for Oper { - type Response = CommentResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &CreateCommentLike = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let mut recipient_ids = Vec::new(); - - let conn = pool.get()?; - // Don't do a downvote if site has downvotes disabled if data.score == -1 { let site = SiteView::read(&conn)?; - if !site.enable_downvotes { - return Err(APIError::err("downvotes_disabled").into()); + if site.enable_downvotes == false { + return Err(APIError::err(&self.op, "downvotes_disabled"))?; } } // Check for a community ban let post = Post::read(&conn, data.post_id)?; if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(APIError::err("community_ban").into()); + return Err(APIError::err(&self.op, "community_ban"))?; } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); - } - - let comment = Comment::read(&conn, data.comment_id)?; - - // Add to recipient ids - match comment.parent_id { - Some(parent_id) => { - let parent_comment = Comment::read(&conn, parent_id)?; - if parent_comment.creator_id != user_id { - let parent_user = User_::read(&conn, parent_comment.creator_id)?; - recipient_ids.push(parent_user.id); - } - } - None => { - recipient_ids.push(post.creator_id); - } + return Err(APIError::err(&self.op, "site_ban"))?; } let like_form = CommentLikeForm { @@ -511,93 +328,20 @@ impl Perform for Oper { CommentLike::remove(&conn, &like_form)?; // Only add the like if the score isnt 0 - let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); + let do_add = &like_form.score != &0 && (&like_form.score == &1 || &like_form.score == &-1); if do_add { let _inserted_like = match CommentLike::like(&conn, &like_form) { Ok(like) => like, - Err(_e) => return Err(APIError::err("couldnt_like_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_comment"))?, }; } // Have to refetch the comment to get the current state let liked_comment = CommentView::read(&conn, data.comment_id, Some(user_id))?; - let mut res = CommentResponse { + Ok(CommentResponse { + op: self.op.to_string(), comment: liked_comment, - recipient_ids, - }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendComment { - op: UserOperation::CreateCommentLike, - comment: res.clone(), - my_id: ws.id, - }); - - // strip out the recipient_ids, so that - // users don't get double notifs - res.recipient_ids = Vec::new(); - } - - Ok(res) - } -} - -impl Perform for Oper { - type Response = GetCommentsResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { - let data: &GetComments = &self.data; - - let user_claims: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => Some(claims.claims), - Err(_e) => None, - }, - None => None, - }; - - let user_id = match &user_claims { - Some(claims) => Some(claims.id), - None => None, - }; - - let type_ = ListingType::from_str(&data.type_)?; - let sort = SortType::from_str(&data.sort)?; - - let conn = pool.get()?; - - let comments = match CommentQueryBuilder::create(&conn) - .listing_type(type_) - .sort(&sort) - .for_community_id(data.community_id) - .my_user_id(user_id) - .page(data.page) - .limit(data.limit) - .list() - { - Ok(comments) => comments, - Err(_e) => return Err(APIError::err("couldnt_get_comments").into()), - }; - - if let Some(ws) = websocket_info { - // You don't need to join the specific community room, bc this is already handled by - // GetCommunity - if data.community_id.is_none() { - if let Some(id) = ws.id { - // 0 is the "all" community - ws.chatserver.do_send(JoinCommunityRoom { - community_id: 0, - id, - }); - } - } - } - - Ok(GetCommentsResponse { comments }) + }) } } diff --git a/server/src/api/community.rs b/server/src/api/community.rs index df03546c..5c97f088 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -1,4 +1,5 @@ use super::*; +use std::str::FromStr; #[derive(Serialize, Deserialize)] pub struct GetCommunity { @@ -9,10 +10,10 @@ pub struct GetCommunity { #[derive(Serialize, Deserialize)] pub struct GetCommunityResponse { - pub community: CommunityView, + op: String, + community: CommunityView, moderators: Vec, admins: Vec, - pub online: usize, } #[derive(Serialize, Deserialize)] @@ -27,6 +28,7 @@ pub struct CreateCommunity { #[derive(Serialize, Deserialize, Clone)] pub struct CommunityResponse { + op: String, pub community: CommunityView, } @@ -40,6 +42,7 @@ pub struct ListCommunities { #[derive(Serialize, Deserialize)] pub struct ListCommunitiesResponse { + op: String, communities: Vec, } @@ -53,8 +56,9 @@ pub struct BanFromCommunity { auth: String, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize)] pub struct BanFromCommunityResponse { + op: String, user: UserView, banned: bool, } @@ -67,8 +71,9 @@ pub struct AddModToCommunity { auth: String, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize)] pub struct AddModToCommunityResponse { + op: String, moderators: Vec, } @@ -101,6 +106,7 @@ pub struct GetFollowedCommunities { #[derive(Serialize, Deserialize)] pub struct GetFollowedCommunitiesResponse { + op: String, communities: Vec, } @@ -111,15 +117,10 @@ pub struct TransferCommunity { auth: String, } -impl Perform for Oper { - type Response = GetCommunityResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &GetCommunity = &self.data; + let conn = establish_connection(); let user_id: Option = match &data.auth { Some(auth) => match Claims::decode(&auth) { @@ -132,29 +133,24 @@ impl Perform for Oper { None => None, }; - let conn = pool.get()?; - let community_id = match data.id { Some(id) => id, None => { - match Community::read_from_name( - &conn, - data.name.to_owned().unwrap_or_else(|| "main".to_string()), - ) { + match Community::read_from_name(&conn, data.name.to_owned().unwrap_or("main".to_string())) { Ok(community) => community.id, - Err(_e) => return Err(APIError::err("couldnt_find_community").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, } } }; let community_view = match CommunityView::read(&conn, community_id, user_id) { Ok(community) => community, - Err(_e) => return Err(APIError::err("couldnt_find_community").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, }; let moderators = match CommunityModeratorView::for_community(&conn, community_id) { Ok(moderators) => moderators, - Err(_e) => return Err(APIError::err("couldnt_find_community").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, }; let site_creator_id = Site::read(&conn, 1)?.creator_id; @@ -163,70 +159,38 @@ impl Perform for Oper { let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); - let online = if let Some(ws) = websocket_info { - if let Some(id) = ws.id { - ws.chatserver - .do_send(JoinCommunityRoom { community_id, id }); - } - - // TODO - 1 - // let fut = async { - // ws.chatserver.send(GetCommunityUsersOnline {community_id}).await.unwrap() - // }; - // Runtime::new().unwrap().block_on(fut) - } else { - 0 - }; - - let res = GetCommunityResponse { + // Return the jwt + Ok(GetCommunityResponse { + op: self.op.to_string(), community: community_view, moderators, admins, - online, - }; - - // Return the jwt - Ok(res) + }) } } -impl Perform for Oper { - type Response = CommunityResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &CreateCommunity = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Err(slurs) = slur_check(&data.title) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(description) = &data.description { - if let Err(slurs) = slur_check(description) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } + if has_slurs(&data.name) + || has_slurs(&data.title) + || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) + { + return Err(APIError::err(&self.op, "no_slurs"))?; } let user_id = claims.id; - let conn = pool.get()?; - // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); + return Err(APIError::err(&self.op, "site_ban"))?; } // When you create a community, make sure the user becomes a moderator and a follower @@ -244,7 +208,7 @@ impl Perform for Oper { let inserted_community = match Community::create(&conn, &community_form) { Ok(community) => community, - Err(_e) => return Err(APIError::err("community_already_exists").into()), + Err(_e) => return Err(APIError::err(&self.op, "community_already_exists"))?, }; let community_moderator_form = CommunityModeratorForm { @@ -255,7 +219,12 @@ impl Perform for Oper { let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), + Err(_e) => { + return Err(APIError::err( + &self.op, + "community_moderator_already_exists", + ))? + } }; let community_follower_form = CommunityFollowerForm { @@ -266,53 +235,38 @@ impl Perform for Oper { let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), + Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists"))?, }; let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?; Ok(CommunityResponse { + op: self.op.to_string(), community: community_view, }) } } -impl Perform for Oper { - type Response = CommunityResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &EditCommunity = &self.data; - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + if has_slurs(&data.name) || has_slurs(&data.title) { + return Err(APIError::err(&self.op, "no_slurs"))?; } - if let Err(slurs) = slur_check(&data.title) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(description) = &data.description { - if let Err(slurs) = slur_check(description) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - } + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); + return Err(APIError::err(&self.op, "site_ban"))?; } // Verify its a mod @@ -325,7 +279,7 @@ impl Perform for Oper { ); editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { - return Err(APIError::err("no_community_edit_allowed").into()); + return Err(APIError::err(&self.op, "no_community_edit_allowed"))?; } let community_form = CommunityForm { @@ -342,7 +296,7 @@ impl Perform for Oper { let _updated_community = match Community::update(&conn, data.edit_id, &community_form) { Ok(community) => community, - Err(_e) => return Err(APIError::err("couldnt_update_community").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_community"))?, }; // Mod tables @@ -363,37 +317,17 @@ impl Perform for Oper { let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?; - let res = CommunityResponse { + Ok(CommunityResponse { + op: self.op.to_string(), community: community_view, - }; - - if let Some(ws) = websocket_info { - // Strip out the user id and subscribed when sending to others - let mut res_sent = res.clone(); - res_sent.community.user_id = None; - res_sent.community.subscribed = None; - - ws.chatserver.do_send(SendCommunityRoomMessage { - op: UserOperation::EditCommunity, - response: res_sent, - community_id: data.edit_id, - my_id: ws.id, - }); - } - - Ok(res) + }) } } -impl Perform for Oper { - type Response = ListCommunitiesResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &ListCommunities = &self.data; + let conn = establish_connection(); let user_claims: Option = match &data.auth { Some(auth) => match Claims::decode(&auth) { @@ -415,34 +349,30 @@ impl Perform for Oper { let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - let communities = CommunityQueryBuilder::create(&conn) .sort(&sort) - .for_user(user_id) + .from_user_id(user_id) .show_nsfw(show_nsfw) .page(data.page) .limit(data.limit) .list()?; // Return the jwt - Ok(ListCommunitiesResponse { communities }) + Ok(ListCommunitiesResponse { + op: self.op.to_string(), + communities, + }) } } -impl Perform for Oper { - type Response = CommunityResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &FollowCommunity = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; @@ -452,71 +382,61 @@ impl Perform for Oper { user_id, }; - let conn = pool.get()?; - if data.follow { match CommunityFollower::follow(&conn, &community_follower_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), + Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists"))?, }; } else { match CommunityFollower::ignore(&conn, &community_follower_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), + Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists"))?, }; } let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?; Ok(CommunityResponse { + op: self.op.to_string(), community: community_view, }) } } -impl Perform for Oper { - type Response = GetFollowedCommunitiesResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &GetFollowedCommunities = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - let communities: Vec = match CommunityFollowerView::for_user(&conn, user_id) { Ok(communities) => communities, - Err(_e) => return Err(APIError::err("system_err_login").into()), + Err(_e) => return Err(APIError::err(&self.op, "system_err_login"))?, }; // Return the jwt - Ok(GetFollowedCommunitiesResponse { communities }) + Ok(GetFollowedCommunitiesResponse { + op: self.op.to_string(), + communities, + }) } } -impl Perform for Oper { - type Response = BanFromCommunityResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &BanFromCommunity = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; @@ -526,17 +446,15 @@ impl Perform for Oper { user_id: data.user_id, }; - let conn = pool.get()?; - if data.ban { match CommunityUserBan::ban(&conn, &community_user_ban_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_user_already_banned").into()), + Err(_e) => return Err(APIError::err(&self.op, "community_user_already_banned"))?, }; } else { match CommunityUserBan::unban(&conn, &community_user_ban_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_user_already_banned").into()), + Err(_e) => return Err(APIError::err(&self.op, "community_user_already_banned"))?, }; } @@ -558,37 +476,22 @@ impl Perform for Oper { let user_view = UserView::read(&conn, data.user_id)?; - let res = BanFromCommunityResponse { + Ok(BanFromCommunityResponse { + op: self.op.to_string(), user: user_view, banned: data.ban, - }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendCommunityRoomMessage { - op: UserOperation::BanFromCommunity, - response: res.clone(), - community_id: data.community_id, - my_id: ws.id, - }); - } - - Ok(res) + }) } } -impl Perform for Oper { - type Response = AddModToCommunityResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &AddModToCommunity = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; @@ -598,17 +501,25 @@ impl Perform for Oper { user_id: data.user_id, }; - let conn = pool.get()?; - if data.added { match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), + Err(_e) => { + return Err(APIError::err( + &self.op, + "community_moderator_already_exists", + ))? + } }; } else { match CommunityModerator::leave(&conn, &community_moderator_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), + Err(_e) => { + return Err(APIError::err( + &self.op, + "community_moderator_already_exists", + ))? + } }; } @@ -623,40 +534,25 @@ impl Perform for Oper { let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?; - let res = AddModToCommunityResponse { moderators }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendCommunityRoomMessage { - op: UserOperation::AddModToCommunity, - response: res.clone(), - community_id: data.community_id, - my_id: ws.id, - }); - } - - Ok(res) + Ok(AddModToCommunityResponse { + op: self.op.to_string(), + moderators, + }) } } -impl Perform for Oper { - type Response = GetCommunityResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &TransferCommunity = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - let read_community = Community::read(&conn, data.community_id)?; let site_creator_id = Site::read(&conn, 1)?.creator_id; @@ -666,8 +562,14 @@ impl Perform for Oper { admins.insert(0, creator_user); // Make sure user is the creator, or an admin - if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) { - return Err(APIError::err("not_an_admin").into()); + if user_id != read_community.creator_id + && !admins + .iter() + .map(|a| a.id) + .collect::>() + .contains(&user_id) + { + return Err(APIError::err(&self.op, "not_an_admin"))?; } let community_form = CommunityForm { @@ -684,7 +586,7 @@ impl Perform for Oper { let _updated_community = match Community::update(&conn, data.community_id, &community_form) { Ok(community) => community, - Err(_e) => return Err(APIError::err("couldnt_update_community").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_community"))?, }; // You also have to re-do the community_moderator table, reordering it. @@ -707,7 +609,12 @@ impl Perform for Oper { let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), + Err(_e) => { + return Err(APIError::err( + &self.op, + "community_moderator_already_exists", + ))? + } }; } @@ -722,20 +629,20 @@ impl Perform for Oper { let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) { Ok(community) => community, - Err(_e) => return Err(APIError::err("couldnt_find_community").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, }; let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) { Ok(moderators) => moderators, - Err(_e) => return Err(APIError::err("couldnt_find_community").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_community"))?, }; // Return the jwt Ok(GetCommunityResponse { + op: self.op.to_string(), community: community_view, moderators, admins, - online: 0, }) } } diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index 3488a8c4..07712e87 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -8,8 +8,6 @@ use crate::db::moderator_views::*; use crate::db::password_reset_request::*; use crate::db::post::*; use crate::db::post_view::*; -use crate::db::private_message::*; -use crate::db::private_message_view::*; use crate::db::site::*; use crate::db::site_view::*; use crate::db::user::*; @@ -17,26 +15,9 @@ use crate::db::user_mention::*; use crate::db::user_mention_view::*; use crate::db::user_view::*; use crate::db::*; -use crate::{ - extract_usernames, fetch_iframely_and_pictshare_data, generate_random_string, naive_from_unix, - naive_now, remove_slurs, send_email, slur_check, slurs_vec_to_str, -}; - -use crate::settings::Settings; -use crate::websocket::UserOperation; -use crate::websocket::{ - server::{ - JoinCommunityRoom, JoinPostRoom, JoinUserRoom, SendAllMessage, SendComment, - SendCommunityRoomMessage, SendPost, SendUserRoomMessage, - }, - WebsocketInfo, -}; -use diesel::r2d2::{ConnectionManager, Pool}; -use diesel::PgConnection; +use crate::{extract_usernames, has_slurs, naive_from_unix, naive_now, remove_slurs}; use failure::Error; -use log::{error, info}; use serde::{Deserialize, Serialize}; -use std::str::FromStr; pub mod comment; pub mod community; @@ -44,36 +25,78 @@ pub mod post; pub mod site; pub mod user; +#[derive(EnumString, ToString, Debug)] +pub enum UserOperation { + Login, + Register, + CreateCommunity, + CreatePost, + ListCommunities, + ListCategories, + GetPost, + GetCommunity, + CreateComment, + EditComment, + SaveComment, + CreateCommentLike, + GetPosts, + CreatePostLike, + EditPost, + SavePost, + EditCommunity, + FollowCommunity, + GetFollowedCommunities, + GetUserDetails, + GetReplies, + GetUserMentions, + EditUserMention, + GetModlog, + BanFromCommunity, + AddModToCommunity, + CreateSite, + EditSite, + GetSite, + AddAdmin, + BanUser, + Search, + MarkAllAsRead, + SaveUserSettings, + TransferCommunity, + TransferSite, + DeleteAccount, + PasswordReset, + PasswordChange, +} + #[derive(Fail, Debug)] -#[fail(display = "{{\"error\":\"{}\"}}", message)] +#[fail(display = "{{\"op\":\"{}\", \"error\":\"{}\"}}", op, message)] pub struct APIError { + pub op: String, pub message: String, } impl APIError { - pub fn err(msg: &str) -> Self { + pub fn err(op: &UserOperation, msg: &str) -> Self { APIError { + op: op.to_string(), message: msg.to_string(), } } } pub struct Oper { + op: UserOperation, data: T, } impl Oper { - pub fn new(data: T) -> Oper { - Oper { data } + pub fn new(op: UserOperation, data: T) -> Oper { + Oper { op, data } } } -pub trait Perform { - type Response: serde::ser::Serialize + Send; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result; +pub trait Perform { + fn perform(&self) -> Result + where + T: Sized; } diff --git a/server/src/api/post.rs b/server/src/api/post.rs index 84ef89f1..4b2395a8 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -1,4 +1,5 @@ use super::*; +use std::str::FromStr; #[derive(Serialize, Deserialize)] pub struct CreatePost { @@ -6,12 +7,13 @@ pub struct CreatePost { url: Option, body: Option, nsfw: bool, - pub community_id: i32, + community_id: i32, auth: String, } #[derive(Serialize, Deserialize, Clone)] pub struct PostResponse { + op: String, pub post: PostView, } @@ -23,12 +25,12 @@ pub struct GetPost { #[derive(Serialize, Deserialize)] pub struct GetPostResponse { + op: String, post: PostView, comments: Vec, community: CommunityView, moderators: Vec, admins: Vec, - pub online: usize, } #[derive(Serialize, Deserialize)] @@ -37,12 +39,13 @@ pub struct GetPosts { sort: String, page: Option, limit: Option, - pub community_id: Option, + community_id: Option, auth: Option, } #[derive(Serialize, Deserialize)] pub struct GetPostsResponse { + op: String, posts: Vec, } @@ -53,6 +56,12 @@ pub struct CreatePostLike { auth: String, } +#[derive(Serialize, Deserialize)] +pub struct CreatePostLikeResponse { + op: String, + post: PostView, +} + #[derive(Serialize, Deserialize)] pub struct EditPost { pub edit_id: i32, @@ -77,49 +86,32 @@ pub struct SavePost { auth: String, } -impl Perform for Oper { - type Response = PostResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &CreatePost = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(body) = &data.body { - if let Err(slurs) = slur_check(body) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } + if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { + return Err(APIError::err(&self.op, "no_slurs"))?; } let user_id = claims.id; - let conn = pool.get()?; - // Check for a community ban if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { - return Err(APIError::err("community_ban").into()); + return Err(APIError::err(&self.op, "community_ban"))?; } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); + return Err(APIError::err(&self.op, "site_ban"))?; } - // Fetch Iframely and Pictshare cached image - let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) = - fetch_iframely_and_pictshare_data(data.url.to_owned()); - let post_form = PostForm { name: data.name.to_owned(), url: data.url.to_owned(), @@ -132,23 +124,11 @@ impl Perform for Oper { locked: None, stickied: None, updated: None, - embed_title: iframely_title, - embed_description: iframely_description, - embed_html: iframely_html, - thumbnail_url: pictshare_thumbnail, }; let inserted_post = match Post::create(&conn, &post_form) { Ok(post) => post, - Err(e) => { - let err_type = if e.to_string() == "value too long for type character varying(200)" { - "post_title_too_long" - } else { - "couldnt_create_post" - }; - - return Err(APIError::err(err_type).into()); - } + Err(_e) => return Err(APIError::err(&self.op, "couldnt_create_post"))?, }; // They like their own post by default @@ -161,38 +141,26 @@ impl Perform for Oper { // Only add the like if the score isnt 0 let _inserted_like = match PostLike::like(&conn, &like_form) { Ok(like) => like, - Err(_e) => return Err(APIError::err("couldnt_like_post").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_post"))?, }; // Refetch the view let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) { Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_find_post").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post"))?, }; - let res = PostResponse { post: post_view }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::CreatePost, - post: res.clone(), - my_id: ws.id, - }); - } - - Ok(res) + Ok(PostResponse { + op: self.op.to_string(), + post: post_view, + }) } } -impl Perform for Oper { - type Response = GetPostResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &GetPost = &self.data; + let conn = establish_connection(); let user_id: Option = match &data.auth { Some(auth) => match Claims::decode(&auth) { @@ -205,11 +173,9 @@ impl Perform for Oper { None => None, }; - let conn = pool.get()?; - let post_view = match PostView::read(&conn, data.id, user_id) { Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_find_post").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post"))?, }; let comments = CommentQueryBuilder::create(&conn) @@ -228,45 +194,22 @@ impl Perform for Oper { let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); - let online = if let Some(ws) = websocket_info { - if let Some(id) = ws.id { - ws.chatserver.do_send(JoinPostRoom { - post_id: data.id, - id, - }); - } - - // TODO - 1 - // let fut = async { - // ws.chatserver.send(GetPostUsersOnline {post_id: data.id}).await.unwrap() - // }; - // Runtime::new().unwrap().block_on(fut) - } else { - 0 - }; - // Return the jwt Ok(GetPostResponse { + op: self.op.to_string(), post: post_view, comments, community, moderators, admins, - online, }) } } -impl Perform for Oper { - type Response = GetPostsResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &GetPosts = &self.data; + let conn = establish_connection(); let user_claims: Option = match &data.auth { Some(auth) => match Claims::decode(&auth) { @@ -289,8 +232,6 @@ impl Perform for Oper { let type_ = ListingType::from_str(&data.type_)?; let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - let posts = match PostQueryBuilder::create(&conn) .listing_type(type_) .sort(&sort) @@ -302,63 +243,45 @@ impl Perform for Oper { .list() { Ok(posts) => posts, - Err(_e) => return Err(APIError::err("couldnt_get_posts").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_get_posts"))?, }; - if let Some(ws) = websocket_info { - // You don't need to join the specific community room, bc this is already handled by - // GetCommunity - if data.community_id.is_none() { - if let Some(id) = ws.id { - // 0 is the "all" community - ws.chatserver.do_send(JoinCommunityRoom { - community_id: 0, - id, - }); - } - } - } - - Ok(GetPostsResponse { posts }) + Ok(GetPostsResponse { + op: self.op.to_string(), + posts, + }) } } -impl Perform for Oper { - type Response = PostResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &CreatePostLike = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - // Don't do a downvote if site has downvotes disabled if data.score == -1 { let site = SiteView::read(&conn)?; - if !site.enable_downvotes { - return Err(APIError::err("downvotes_disabled").into()); + if site.enable_downvotes == false { + return Err(APIError::err(&self.op, "downvotes_disabled"))?; } } // Check for a community ban let post = Post::read(&conn, data.post_id)?; if CommunityUserBanView::get(&conn, user_id, post.community_id).is_ok() { - return Err(APIError::err("community_ban").into()); + return Err(APIError::err(&self.op, "community_ban"))?; } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); + return Err(APIError::err(&self.op, "site_ban"))?; } let like_form = PostLikeForm { @@ -371,62 +294,43 @@ impl Perform for Oper { PostLike::remove(&conn, &like_form)?; // Only add the like if the score isnt 0 - let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); + let do_add = &like_form.score != &0 && (&like_form.score == &1 || &like_form.score == &-1); if do_add { let _inserted_like = match PostLike::like(&conn, &like_form) { Ok(like) => like, - Err(_e) => return Err(APIError::err("couldnt_like_post").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_like_post"))?, }; } let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) { Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_find_post").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_find_post"))?, }; - let res = PostResponse { post: post_view }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::CreatePostLike, - post: res.clone(), - my_id: ws.id, - }); - } - - Ok(res) + // just output the score + Ok(CreatePostLikeResponse { + op: self.op.to_string(), + post: post_view, + }) } } -impl Perform for Oper { - type Response = PostResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &EditPost = &self.data; - - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + if has_slurs(&data.name) || (data.body.is_some() && has_slurs(&data.body.to_owned().unwrap())) { + return Err(APIError::err(&self.op, "no_slurs"))?; } - if let Some(body) = &data.body { - if let Err(slurs) = slur_check(body) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - } + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - // Verify its the creator or a mod or admin let mut editors: Vec = vec![data.creator_id]; editors.append( @@ -437,23 +341,19 @@ impl Perform for Oper { ); editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect()); if !editors.contains(&user_id) { - return Err(APIError::err("no_post_edit_allowed").into()); + return Err(APIError::err(&self.op, "no_post_edit_allowed"))?; } // Check for a community ban if CommunityUserBanView::get(&conn, user_id, data.community_id).is_ok() { - return Err(APIError::err("community_ban").into()); + return Err(APIError::err(&self.op, "community_ban"))?; } // Check for a site ban if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); + return Err(APIError::err(&self.op, "site_ban"))?; } - // Fetch Iframely and Pictshare cached image - let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) = - fetch_iframely_and_pictshare_data(data.url.to_owned()); - let post_form = PostForm { name: data.name.to_owned(), url: data.url.to_owned(), @@ -466,23 +366,11 @@ impl Perform for Oper { locked: data.locked.to_owned(), stickied: data.stickied.to_owned(), updated: Some(naive_now()), - embed_title: iframely_title, - embed_description: iframely_description, - embed_html: iframely_html, - thumbnail_url: pictshare_thumbnail, }; let _updated_post = match Post::update(&conn, data.edit_id, &post_form) { Ok(post) => post, - Err(e) => { - let err_type = if e.to_string() == "value too long for type character varying(200)" { - "post_title_too_long" - } else { - "couldnt_update_post" - }; - - return Err(APIError::err(err_type).into()); - } + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_post"))?, }; // Mod tables @@ -516,33 +404,21 @@ impl Perform for Oper { let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?; - let res = PostResponse { post: post_view }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendPost { - op: UserOperation::EditPost, - post: res.clone(), - my_id: ws.id, - }); - } - - Ok(res) + Ok(PostResponse { + op: self.op.to_string(), + post: post_view, + }) } } -impl Perform for Oper { - type Response = PostResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &SavePost = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; @@ -552,22 +428,23 @@ impl Perform for Oper { user_id, }; - let conn = pool.get()?; - if data.save { match PostSaved::save(&conn, &post_saved_form) { Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_save_post").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_post"))?, }; } else { match PostSaved::unsave(&conn, &post_saved_form) { Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_save_post").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_save_post"))?, }; } let post_view = PostView::read(&conn, data.post_id, Some(user_id))?; - Ok(PostResponse { post: post_view }) + Ok(PostResponse { + op: self.op.to_string(), + post: post_view, + }) } } diff --git a/server/src/api/site.rs b/server/src/api/site.rs index e05487df..ec89e46c 100644 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@ -1,11 +1,12 @@ -use super::user::Register; use super::*; +use std::str::FromStr; #[derive(Serialize, Deserialize)] -pub struct ListCategories {} +pub struct ListCategories; #[derive(Serialize, Deserialize)] pub struct ListCategoriesResponse { + op: String, categories: Vec, } @@ -17,11 +18,11 @@ pub struct Search { sort: String, page: Option, limit: Option, - auth: Option, } #[derive(Serialize, Deserialize)] pub struct SearchResponse { + op: String, type_: String, comments: Vec, posts: Vec, @@ -39,6 +40,7 @@ pub struct GetModlog { #[derive(Serialize, Deserialize)] pub struct GetModlogResponse { + op: String, removed_posts: Vec, locked_posts: Vec, stickied_posts: Vec, @@ -52,12 +54,12 @@ pub struct GetModlogResponse { #[derive(Serialize, Deserialize)] pub struct CreateSite { - pub name: String, - pub description: Option, - pub enable_downvotes: bool, - pub open_registration: bool, - pub enable_nsfw: bool, - pub auth: String, + name: String, + description: Option, + enable_downvotes: bool, + open_registration: bool, + enable_nsfw: bool, + auth: String, } #[derive(Serialize, Deserialize)] @@ -71,15 +73,17 @@ pub struct EditSite { } #[derive(Serialize, Deserialize)] -pub struct GetSite {} +pub struct GetSite; -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize)] pub struct SiteResponse { + op: String, site: SiteView, } #[derive(Serialize, Deserialize)] pub struct GetSiteResponse { + op: String, site: Option, admins: Vec, banned: Vec, @@ -92,52 +96,25 @@ pub struct TransferSite { auth: String, } -#[derive(Serialize, Deserialize)] -pub struct GetSiteConfig { - auth: String, -} - -#[derive(Serialize, Deserialize)] -pub struct GetSiteConfigResponse { - config_hjson: String, -} - -#[derive(Serialize, Deserialize)] -pub struct SaveSiteConfig { - config_hjson: String, - auth: String, -} - -impl Perform for Oper { - type Response = ListCategoriesResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let _data: &ListCategories = &self.data; - - let conn = pool.get()?; + let conn = establish_connection(); let categories: Vec = Category::list_all(&conn)?; // Return the jwt - Ok(ListCategoriesResponse { categories }) + Ok(ListCategoriesResponse { + op: self.op.to_string(), + categories, + }) } } -impl Perform for Oper { - type Response = GetModlogResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &GetModlog = &self.data; - - let conn = pool.get()?; + let conn = establish_connection(); let removed_posts = ModRemovePostView::list( &conn, @@ -183,18 +160,20 @@ impl Perform for Oper { )?; // These arrays are only for the full modlog, when a community isn't given - let (removed_communities, banned, added) = if data.community_id.is_none() { - ( - ModRemoveCommunityView::list(&conn, data.mod_user_id, data.page, data.limit)?, - ModBanView::list(&conn, data.mod_user_id, data.page, data.limit)?, - ModAddView::list(&conn, data.mod_user_id, data.page, data.limit)?, - ) - } else { - (Vec::new(), Vec::new(), Vec::new()) - }; + let mut removed_communities = Vec::new(); + let mut banned = Vec::new(); + let mut added = Vec::new(); + + if data.community_id.is_none() { + removed_communities = + ModRemoveCommunityView::list(&conn, data.mod_user_id, data.page, data.limit)?; + banned = ModBanView::list(&conn, data.mod_user_id, data.page, data.limit)?; + added = ModAddView::list(&conn, data.mod_user_id, data.page, data.limit)?; + } // Return the jwt Ok(GetModlogResponse { + op: self.op.to_string(), removed_posts, locked_posts, stickied_posts, @@ -208,38 +187,27 @@ impl Perform for Oper { } } -impl Perform for Oper { - type Response = SiteResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &CreateSite = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(description) = &data.description { - if let Err(slurs) = slur_check(description) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } + if has_slurs(&data.name) + || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) + { + return Err(APIError::err(&self.op, "no_slurs"))?; } let user_id = claims.id; - let conn = pool.get()?; - // Make sure user is an admin if !UserView::read(&conn, user_id)?.admin { - return Err(APIError::err("not_an_admin").into()); + return Err(APIError::err(&self.op, "not_an_admin"))?; } let site_form = SiteForm { @@ -254,46 +222,39 @@ impl Perform for Oper { match Site::create(&conn, &site_form) { Ok(site) => site, - Err(_e) => return Err(APIError::err("site_already_exists").into()), + Err(_e) => return Err(APIError::err(&self.op, "site_already_exists"))?, }; let site_view = SiteView::read(&conn)?; - Ok(SiteResponse { site: site_view }) + Ok(SiteResponse { + op: self.op.to_string(), + site: site_view, + }) } } -impl Perform for Oper { - type Response = SiteResponse; - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &EditSite = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(description) = &data.description { - if let Err(slurs) = slur_check(description) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } + if has_slurs(&data.name) + || (data.description.is_some() && has_slurs(&data.description.to_owned().unwrap())) + { + return Err(APIError::err(&self.op, "no_slurs"))?; } let user_id = claims.id; - let conn = pool.get()?; - // Make sure user is an admin - if !UserView::read(&conn, user_id)?.admin { - return Err(APIError::err("not_an_admin").into()); + if UserView::read(&conn, user_id)?.admin == false { + return Err(APIError::err(&self.op, "not_an_admin"))?; } let found_site = Site::read(&conn, 1)?; @@ -310,66 +271,27 @@ impl Perform for Oper { match Site::update(&conn, 1, &site_form) { Ok(site) => site, - Err(_e) => return Err(APIError::err("couldnt_update_site").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_site"))?, }; let site_view = SiteView::read(&conn)?; - let res = SiteResponse { site: site_view }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendAllMessage { - op: UserOperation::EditSite, - response: res.clone(), - my_id: ws.id, - }); - } - - Ok(res) + Ok(SiteResponse { + op: self.op.to_string(), + site: site_view, + }) } } -impl Perform for Oper { - type Response = GetSiteResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let _data: &GetSite = &self.data; + let conn = establish_connection(); - let conn = pool.get()?; - - // TODO refactor this a little - 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(pool.clone(), websocket_info.clone())?; - 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(pool, websocket_info.clone())?; - info!("Site {} created", setup.site_name); - Some(SiteView::read(&conn)?) - } else { - None + // 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 mut admins = UserView::admins(&conn)?; @@ -382,46 +304,20 @@ impl Perform for Oper { let banned = UserView::banned(&conn)?; - let online = if let Some(_ws) = websocket_info { - // TODO - 1 - // let fut = async { - // ws.chatserver.send(GetUsersOnline).await.unwrap() - // }; - // Runtime::new().unwrap().block_on(fut) - } else { - 0 - }; - Ok(GetSiteResponse { + op: self.op.to_string(), site: site_view, admins, banned, - online, + online: 0, }) } } -impl Perform for Oper { - type Response = SearchResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &Search = &self.data; - - let user_id: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None, - }, - None => None, - }; + let conn = establish_connection(); let sort = SortType::from_str(&data.sort)?; let type_ = SearchType::from_str(&data.type_)?; @@ -433,8 +329,6 @@ impl Perform for Oper { // TODO no clean / non-nsfw searching rn - let conn = pool.get()?; - match type_ { SearchType::Posts => { posts = PostQueryBuilder::create(&conn) @@ -442,7 +336,6 @@ impl Perform for Oper { .show_nsfw(true) .for_community_id(data.community_id) .search_term(data.q.to_owned()) - .my_user_id(user_id) .page(data.page) .limit(data.limit) .list()?; @@ -451,7 +344,6 @@ impl Perform for Oper { comments = CommentQueryBuilder::create(&conn) .sort(&sort) .search_term(data.q.to_owned()) - .my_user_id(user_id) .page(data.page) .limit(data.limit) .list()?; @@ -478,7 +370,6 @@ impl Perform for Oper { .show_nsfw(true) .for_community_id(data.community_id) .search_term(data.q.to_owned()) - .my_user_id(user_id) .page(data.page) .limit(data.limit) .list()?; @@ -486,7 +377,6 @@ impl Perform for Oper { comments = CommentQueryBuilder::create(&conn) .sort(&sort) .search_term(data.q.to_owned()) - .my_user_id(user_id) .page(data.page) .limit(data.limit) .list()?; @@ -519,6 +409,7 @@ impl Perform for Oper { // Return the jwt Ok(SearchResponse { + op: self.op.to_string(), type_: data.type_.to_owned(), comments, posts, @@ -528,30 +419,23 @@ impl Perform for Oper { } } -impl Perform for Oper { - type Response = GetSiteResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &TransferSite = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - let read_site = Site::read(&conn, 1)?; // Make sure user is the creator if read_site.creator_id != user_id { - return Err(APIError::err("not_an_admin").into()); + return Err(APIError::err(&self.op, "not_an_admin"))?; } let site_form = SiteForm { @@ -566,7 +450,7 @@ impl Perform for Oper { match Site::update(&conn, 1, &site_form) { Ok(site) => site, - Err(_e) => return Err(APIError::err("couldnt_update_site").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_site"))?, }; // Mod tables @@ -591,6 +475,7 @@ impl Perform for Oper { let banned = UserView::banned(&conn)?; Ok(GetSiteResponse { + op: self.op.to_string(), site: Some(site_view), admins, banned, @@ -598,73 +483,3 @@ impl Perform for Oper { }) } } - -impl Perform for Oper { - type Response = GetSiteConfigResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { - let data: &GetSiteConfig = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - let conn = pool.get()?; - - // Only let admins read this - let admins = UserView::admins(&conn)?; - let admin_ids: Vec = admins.into_iter().map(|m| m.id).collect(); - - if !admin_ids.contains(&user_id) { - return Err(APIError::err("not_an_admin").into()); - } - - let config_hjson = Settings::read_config_file()?; - - Ok(GetSiteConfigResponse { config_hjson }) - } -} - -impl Perform for Oper { - type Response = GetSiteConfigResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { - let data: &SaveSiteConfig = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - let conn = pool.get()?; - - // Only let admins read this - let admins = UserView::admins(&conn)?; - let admin_ids: Vec = admins.into_iter().map(|m| m.id).collect(); - - if !admin_ids.contains(&user_id) { - return Err(APIError::err("not_an_admin").into()); - } - - // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem - let config_hjson = match Settings::save_config_file(&data.config_hjson) { - Ok(config_hjson) => config_hjson, - Err(_e) => return Err(APIError::err("couldnt_update_site").into()), - }; - - Ok(GetSiteConfigResponse { config_hjson }) - } -} diff --git a/server/src/api/user.rs b/server/src/api/user.rs index ee57723a..9a343427 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -1,6 +1,8 @@ use super::*; -use crate::is_valid_username; +use crate::settings::Settings; +use crate::{generate_random_string, send_email}; use bcrypt::verify; +use std::str::FromStr; #[derive(Serialize, Deserialize, Debug)] pub struct Login { @@ -10,12 +12,12 @@ pub struct Login { #[derive(Serialize, Deserialize)] pub struct Register { - pub username: String, - pub email: Option, - pub password: String, - pub password_verify: String, - pub admin: bool, - pub show_nsfw: bool, + username: String, + email: Option, + password: String, + password_verify: String, + admin: bool, + show_nsfw: bool, } #[derive(Serialize, Deserialize)] @@ -25,20 +27,13 @@ pub struct SaveUserSettings { default_sort_type: i16, default_listing_type: i16, lang: String, - avatar: Option, - email: Option, - matrix_user_id: Option, - new_password: Option, - new_password_verify: Option, - old_password: Option, - show_avatars: bool, - send_notifications_to_email: bool, auth: String, } #[derive(Serialize, Deserialize)] pub struct LoginResponse { - pub jwt: String, + op: String, + jwt: String, } #[derive(Serialize, Deserialize)] @@ -55,6 +50,7 @@ pub struct GetUserDetails { #[derive(Serialize, Deserialize)] pub struct GetUserDetailsResponse { + op: String, user: UserView, follows: Vec, moderates: Vec, @@ -65,11 +61,13 @@ pub struct GetUserDetailsResponse { #[derive(Serialize, Deserialize)] pub struct GetRepliesResponse { + op: String, replies: Vec, } #[derive(Serialize, Deserialize)] pub struct GetUserMentionsResponse { + op: String, mentions: Vec, } @@ -85,8 +83,9 @@ pub struct AddAdmin { auth: String, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize)] pub struct AddAdminResponse { + op: String, admins: Vec, } @@ -99,8 +98,9 @@ pub struct BanUser { auth: String, } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize)] pub struct BanUserResponse { + op: String, user: UserView, banned: bool, } @@ -132,6 +132,7 @@ pub struct EditUserMention { #[derive(Serialize, Deserialize, Clone)] pub struct UserMentionResponse { + op: String, mention: UserMentionView, } @@ -147,7 +148,9 @@ pub struct PasswordReset { } #[derive(Serialize, Deserialize, Clone)] -pub struct PasswordResetResponse {} +pub struct PasswordResetResponse { + op: String, +} #[derive(Serialize, Deserialize)] pub struct PasswordChange { @@ -156,123 +159,67 @@ pub struct PasswordChange { password_verify: String, } -#[derive(Serialize, Deserialize)] -pub struct CreatePrivateMessage { - content: String, - pub recipient_id: i32, - auth: String, -} - -#[derive(Serialize, Deserialize)] -pub struct EditPrivateMessage { - edit_id: i32, - content: Option, - deleted: Option, - read: Option, - auth: String, -} - -#[derive(Serialize, Deserialize)] -pub struct GetPrivateMessages { - unread_only: bool, - page: Option, - limit: Option, - auth: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct PrivateMessagesResponse { - messages: Vec, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct PrivateMessageResponse { - message: PrivateMessageView, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct UserJoin { - auth: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct UserJoinResponse { - pub user_id: i32, -} - -impl Perform for Oper { - type Response = LoginResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &Login = &self.data; - - let conn = pool.get()?; + let conn = establish_connection(); // Fetch that username / email let user: User_ = match User_::find_by_email_or_username(&conn, &data.username_or_email) { Ok(user) => user, - Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()), + Err(_e) => { + return Err(APIError::err( + &self.op, + "couldnt_find_that_username_or_email", + ))? + } }; // Verify the password let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false); if !valid { - return Err(APIError::err("password_incorrect").into()); + return Err(APIError::err(&self.op, "password_incorrect"))?; } // Return the jwt - Ok(LoginResponse { jwt: user.jwt() }) + Ok(LoginResponse { + op: self.op.to_string(), + jwt: user.jwt(), + }) } } -impl Perform for Oper { - type Response = LoginResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &Register = &self.data; - - let conn = pool.get()?; + let conn = establish_connection(); // Make sure site has open registration if let Ok(site) = SiteView::read(&conn) { if !site.open_registration { - return Err(APIError::err("registration_closed").into()); + return Err(APIError::err(&self.op, "registration_closed"))?; } } // Make sure passwords match - if data.password != data.password_verify { - return Err(APIError::err("passwords_dont_match").into()); + if &data.password != &data.password_verify { + return Err(APIError::err(&self.op, "passwords_dont_match"))?; } - if let Err(slurs) = slur_check(&data.username) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); + if has_slurs(&data.username) { + return Err(APIError::err(&self.op, "no_slurs"))?; } // Make sure there are no admins - if data.admin && !UserView::admins(&conn)?.is_empty() { - return Err(APIError::err("admin_already_created").into()); - } - - if !is_valid_username(&data.username) { - return Err(APIError::err("invalid_username").into()); + if data.admin && UserView::admins(&conn)?.len() > 0 { + return Err(APIError::err(&self.op, "admin_already_created"))?; } // Register the new user let user_form = UserForm { name: data.username.to_owned(), - fedi_name: Settings::get().hostname, + fedi_name: Settings::get().hostname.to_owned(), email: data.email.to_owned(), - matrix_user_id: None, - avatar: None, password_encrypted: data.password.to_owned(), preferred_username: None, updated: None, @@ -283,24 +230,12 @@ impl Perform for Oper { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; // Create the user let inserted_user = match User_::register(&conn, &user_form) { Ok(user) => user, - Err(e) => { - let err_type = if e.to_string() - == "duplicate key value violates unique constraint \"user__email_key\"" - { - "email_already_exists" - } else { - "user_already_exists" - }; - - return Err(APIError::err(err_type).into()); - } + Err(_e) => return Err(APIError::err(&self.op, "user_already_exists"))?, }; // Create the main community if it doesn't exist @@ -331,7 +266,7 @@ impl Perform for Oper { let _inserted_community_follower = match CommunityFollower::follow(&conn, &community_follower_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_follower_already_exists").into()), + Err(_e) => return Err(APIError::err(&self.op, "community_follower_already_exists"))?, }; // If its an admin, add them as a mod and follower to main @@ -344,78 +279,42 @@ impl Perform for Oper { let _inserted_community_moderator = match CommunityModerator::join(&conn, &community_moderator_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()), + Err(_e) => { + return Err(APIError::err( + &self.op, + "community_moderator_already_exists", + ))? + } }; } // Return the jwt Ok(LoginResponse { + op: self.op.to_string(), jwt: inserted_user.jwt(), }) } } -impl Perform for Oper { - type Response = LoginResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &SaveUserSettings = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - let read_user = User_::read(&conn, user_id)?; - let email = match &data.email { - Some(email) => Some(email.to_owned()), - None => read_user.email, - }; - - let password_encrypted = match &data.new_password { - Some(new_password) => { - match &data.new_password_verify { - Some(new_password_verify) => { - // Make sure passwords match - if new_password != new_password_verify { - return Err(APIError::err("passwords_dont_match").into()); - } - - // Check the old password - match &data.old_password { - Some(old_password) => { - let valid: bool = - verify(old_password, &read_user.password_encrypted).unwrap_or(false); - if !valid { - return Err(APIError::err("password_incorrect").into()); - } - User_::update_password(&conn, user_id, &new_password)?.password_encrypted - } - None => return Err(APIError::err("password_incorrect").into()), - } - } - None => return Err(APIError::err("passwords_dont_match").into()), - } - } - None => read_user.password_encrypted, - }; - let user_form = UserForm { name: read_user.name, fedi_name: read_user.fedi_name, - email, - matrix_user_id: data.matrix_user_id.to_owned(), - avatar: data.avatar.to_owned(), - password_encrypted, + email: read_user.email, + password_encrypted: read_user.password_encrypted, preferred_username: read_user.preferred_username, updated: Some(naive_now()), admin: read_user.admin, @@ -425,43 +324,25 @@ impl Perform for Oper { default_sort_type: data.default_sort_type, default_listing_type: data.default_listing_type, lang: data.lang.to_owned(), - show_avatars: data.show_avatars, - send_notifications_to_email: data.send_notifications_to_email, }; let updated_user = match User_::update(&conn, user_id, &user_form) { Ok(user) => user, - Err(e) => { - let err_type = if e.to_string() - == "duplicate key value violates unique constraint \"user__email_key\"" - { - "email_already_exists" - } else { - "user_already_exists" - }; - - return Err(APIError::err(err_type).into()); - } + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, }; // Return the jwt Ok(LoginResponse { + op: self.op.to_string(), jwt: updated_user.jwt(), }) } } -impl Perform for Oper { - type Response = GetUserDetailsResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &GetUserDetails = &self.data; - - let conn = pool.get()?; + let conn = establish_connection(); let user_claims: Option = match &data.auth { Some(auth) => match Claims::decode(&auth) { @@ -486,20 +367,15 @@ impl Perform for Oper { let user_details_id = match data.user_id { Some(id) => id, None => { - match User_::read_from_name( + User_::read_from_name( &conn, - data - .username - .to_owned() - .unwrap_or_else(|| "admin".to_string()), - ) { - Ok(user) => user.id, - Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()), - } + data.username.to_owned().unwrap_or("admin".to_string()), + )? + .id } }; - let mut user_view = UserView::read(&conn, user_details_id)?; + let user_view = UserView::read(&conn, user_details_id)?; let mut posts_query = PostQueryBuilder::create(&conn) .sort(&sort) @@ -535,17 +411,9 @@ impl Perform for Oper { let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); - // If its not the same user, remove the email - if let Some(user_id) = user_id { - if user_details_id != user_id { - user_view.email = None; - } - } else { - user_view.email = None; - } - // Return the jwt Ok(GetUserDetailsResponse { + op: self.op.to_string(), user: user_view, follows, moderates, @@ -556,39 +424,29 @@ impl Perform for Oper { } } -impl Perform for Oper { - type Response = AddAdminResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &AddAdmin = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - // Make sure user is an admin - if !UserView::read(&conn, user_id)?.admin { - return Err(APIError::err("not_an_admin").into()); + if UserView::read(&conn, user_id)?.admin == false { + return Err(APIError::err(&self.op, "not_an_admin"))?; } let read_user = User_::read(&conn, data.user_id)?; - // TODO make addadmin easier let user_form = UserForm { name: read_user.name, fedi_name: read_user.fedi_name, email: read_user.email, - matrix_user_id: read_user.matrix_user_id, - avatar: read_user.avatar, password_encrypted: read_user.password_encrypted, preferred_username: read_user.preferred_username, updated: Some(naive_now()), @@ -599,13 +457,11 @@ impl Perform for Oper { default_sort_type: read_user.default_sort_type, default_listing_type: read_user.default_listing_type, lang: read_user.lang, - show_avatars: read_user.show_avatars, - send_notifications_to_email: read_user.send_notifications_to_email, }; match User_::update(&conn, data.user_id, &user_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("couldnt_update_user").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, }; // Mod tables @@ -623,53 +479,36 @@ impl Perform for Oper { let creator_user = admins.remove(creator_index); admins.insert(0, creator_user); - let res = AddAdminResponse { admins }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendAllMessage { - op: UserOperation::AddAdmin, - response: res.clone(), - my_id: ws.id, - }); - } - - Ok(res) + Ok(AddAdminResponse { + op: self.op.to_string(), + admins, + }) } } -impl Perform for Oper { - type Response = BanUserResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &BanUser = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - // Make sure user is an admin - if !UserView::read(&conn, user_id)?.admin { - return Err(APIError::err("not_an_admin").into()); + if UserView::read(&conn, user_id)?.admin == false { + return Err(APIError::err(&self.op, "not_an_admin"))?; } let read_user = User_::read(&conn, data.user_id)?; - // TODO make bans and addadmins easier let user_form = UserForm { name: read_user.name, fedi_name: read_user.fedi_name, email: read_user.email, - matrix_user_id: read_user.matrix_user_id, - avatar: read_user.avatar, password_encrypted: read_user.password_encrypted, preferred_username: read_user.preferred_username, updated: Some(naive_now()), @@ -680,13 +519,11 @@ impl Perform for Oper { default_sort_type: read_user.default_sort_type, default_listing_type: read_user.default_listing_type, lang: read_user.lang, - show_avatars: read_user.show_avatars, - send_notifications_to_email: read_user.send_notifications_to_email, }; match User_::update(&conn, data.user_id, &user_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("couldnt_update_user").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, }; // Mod tables @@ -707,44 +544,28 @@ impl Perform for Oper { let user_view = UserView::read(&conn, data.user_id)?; - let res = BanUserResponse { + Ok(BanUserResponse { + op: self.op.to_string(), user: user_view, banned: data.ban, - }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendAllMessage { - op: UserOperation::BanUser, - response: res.clone(), - my_id: ws.id, - }); - } - - Ok(res) + }) } } -impl Perform for Oper { - type Response = GetRepliesResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &GetReplies = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - let replies = ReplyQueryBuilder::create(&conn, user_id) .sort(&sort) .unread_only(data.unread_only) @@ -752,31 +573,27 @@ impl Perform for Oper { .limit(data.limit) .list()?; - Ok(GetRepliesResponse { replies }) + Ok(GetRepliesResponse { + op: self.op.to_string(), + replies, + }) } } -impl Perform for Oper { - type Response = GetUserMentionsResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &GetUserMentions = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; let sort = SortType::from_str(&data.sort)?; - let conn = pool.get()?; - let mentions = UserMentionQueryBuilder::create(&conn, user_id) .sort(&sort) .unread_only(data.unread_only) @@ -784,29 +601,25 @@ impl Perform for Oper { .limit(data.limit) .list()?; - Ok(GetUserMentionsResponse { mentions }) + Ok(GetUserMentionsResponse { + op: self.op.to_string(), + mentions, + }) } } -impl Perform for Oper { - type Response = UserMentionResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &EditUserMention = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - let user_mention = UserMention::read(&conn, data.user_mention_id)?; let user_mention_form = UserMentionForm { @@ -818,36 +631,30 @@ impl Perform for Oper { let _updated_user_mention = match UserMention::update(&conn, user_mention.id, &user_mention_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, }; let user_mention_view = UserMentionView::read(&conn, user_mention.id, user_id)?; Ok(UserMentionResponse { + op: self.op.to_string(), mention: user_mention_view, }) } } -impl Perform for Oper { - type Response = GetRepliesResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &MarkAllAsRead = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - let replies = ReplyQueryBuilder::create(&conn, user_id) .unread_only(true) .page(1) @@ -868,7 +675,7 @@ impl Perform for Oper { let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, }; } @@ -889,63 +696,35 @@ impl Perform for Oper { let _updated_mention = match UserMention::update(&conn, mention.user_mention_id, &mention_form) { Ok(mention) => mention, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, }; } - // messages - let messages = PrivateMessageQueryBuilder::create(&conn, user_id) - .page(1) - .limit(999) - .unread_only(true) - .list()?; - - for message in &messages { - let private_message_form = PrivateMessageForm { - content: None, - creator_id: message.to_owned().creator_id, - recipient_id: message.to_owned().recipient_id, - deleted: None, - read: Some(true), - updated: None, - }; - - let _updated_message = match PrivateMessage::update(&conn, message.id, &private_message_form) - { - Ok(message) => message, - Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()), - }; - } - - Ok(GetRepliesResponse { replies: vec![] }) + Ok(GetRepliesResponse { + op: self.op.to_string(), + replies: vec![], + }) } } -impl Perform for Oper { - type Response = LoginResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &DeleteAccount = &self.data; + let conn = establish_connection(); let claims = match Claims::decode(&data.auth) { Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), + Err(_e) => return Err(APIError::err(&self.op, "not_logged_in"))?, }; let user_id = claims.id; - let conn = pool.get()?; - let user: User_ = User_::read(&conn, user_id)?; // Verify the password let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false); if !valid { - return Err(APIError::err("password_incorrect").into()); + return Err(APIError::err(&self.op, "password_incorrect"))?; } // Comments @@ -968,7 +747,7 @@ impl Perform for Oper { let _updated_comment = match Comment::update(&conn, comment.id, &comment_form) { Ok(comment) => comment, - Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_comment"))?, }; } @@ -992,40 +771,35 @@ impl Perform for Oper { locked: None, stickied: None, updated: Some(naive_now()), - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let _updated_post = match Post::update(&conn, post.id, &post_form) { Ok(post) => post, - Err(_e) => return Err(APIError::err("couldnt_update_post").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_post"))?, }; } Ok(LoginResponse { + op: self.op.to_string(), jwt: data.auth.to_owned(), }) } } -impl Perform for Oper { - type Response = PasswordResetResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &PasswordReset = &self.data; - - let conn = pool.get()?; + let conn = establish_connection(); // Fetch that email let user: User_ = match User_::find_by_email(&conn, &data.email) { Ok(user) => user, - Err(_e) => return Err(APIError::err("couldnt_find_that_username_or_email").into()), + Err(_e) => { + return Err(APIError::err( + &self.op, + "couldnt_find_that_username_or_email", + ))? + } }; // Generate a random token @@ -1042,242 +816,57 @@ impl Perform for Oper { let html = &format!("

Password Reset Request for {}


Click here to reset your password", user.name, hostname, &token); match send_email(subject, user_email, &user.name, html) { Ok(_o) => _o, - Err(_e) => return Err(APIError::err(&_e).into()), + Err(_e) => return Err(APIError::err(&self.op, &_e.to_string()))?, }; - Ok(PasswordResetResponse {}) + Ok(PasswordResetResponse { + op: self.op.to_string(), + }) } } -impl Perform for Oper { - type Response = LoginResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { +impl Perform for Oper { + fn perform(&self) -> Result { let data: &PasswordChange = &self.data; - - let conn = pool.get()?; + let conn = establish_connection(); // Fetch the user_id from the token let user_id = PasswordResetRequest::read_from_token(&conn, &data.token)?.user_id; // Make sure passwords match - if data.password != data.password_verify { - return Err(APIError::err("passwords_dont_match").into()); + if &data.password != &data.password_verify { + return Err(APIError::err(&self.op, "passwords_dont_match"))?; } + // Fetch the user + let read_user = User_::read(&conn, user_id)?; + // Update the user with the new password - let updated_user = match User_::update_password(&conn, user_id, &data.password) { + let user_form = UserForm { + name: read_user.name, + fedi_name: read_user.fedi_name, + email: read_user.email, + password_encrypted: data.password.to_owned(), + preferred_username: read_user.preferred_username, + updated: Some(naive_now()), + admin: read_user.admin, + banned: read_user.banned, + show_nsfw: read_user.show_nsfw, + theme: read_user.theme, + default_sort_type: read_user.default_sort_type, + default_listing_type: read_user.default_listing_type, + lang: read_user.lang, + }; + + let updated_user = match User_::update_password(&conn, user_id, &user_form) { Ok(user) => user, - Err(_e) => return Err(APIError::err("couldnt_update_user").into()), + Err(_e) => return Err(APIError::err(&self.op, "couldnt_update_user"))?, }; // Return the jwt Ok(LoginResponse { + op: self.op.to_string(), jwt: updated_user.jwt(), }) } } - -impl Perform for Oper { - type Response = PrivateMessageResponse; - - fn perform( - &self, - pool: Pool>, - websocket_info: Option, - ) -> Result { - let data: &CreatePrivateMessage = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - let hostname = &format!("https://{}", Settings::get().hostname); - - let conn = pool.get()?; - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); - } - - let content_slurs_removed = remove_slurs(&data.content.to_owned()); - - let private_message_form = PrivateMessageForm { - content: Some(content_slurs_removed.to_owned()), - creator_id: user_id, - recipient_id: data.recipient_id, - deleted: None, - read: None, - updated: None, - }; - - let inserted_private_message = match PrivateMessage::create(&conn, &private_message_form) { - Ok(private_message) => private_message, - Err(_e) => { - return Err(APIError::err("couldnt_create_private_message").into()); - } - }; - - // Send notifications to the recipient - let recipient_user = User_::read(&conn, data.recipient_id)?; - if recipient_user.send_notifications_to_email { - if let Some(email) = recipient_user.email { - let subject = &format!( - "{} - Private Message from {}", - Settings::get().hostname, - claims.username - ); - let html = &format!( - "

Private Message


{} - {}

inbox", - claims.username, &content_slurs_removed, hostname - ); - match send_email(subject, &email, &recipient_user.name, html) { - Ok(_o) => _o, - Err(e) => error!("{}", e), - }; - } - } - - let message = PrivateMessageView::read(&conn, inserted_private_message.id)?; - - let res = PrivateMessageResponse { message }; - - if let Some(ws) = websocket_info { - ws.chatserver.do_send(SendUserRoomMessage { - op: UserOperation::CreatePrivateMessage, - response: res.clone(), - recipient_id: recipient_user.id, - my_id: ws.id, - }); - } - - Ok(res) - } -} - -impl Perform for Oper { - type Response = PrivateMessageResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { - let data: &EditPrivateMessage = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - let conn = pool.get()?; - - let orig_private_message = PrivateMessage::read(&conn, data.edit_id)?; - - // Check for a site ban - if UserView::read(&conn, user_id)?.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check to make sure they are the creator (or the recipient marking as read - if !(data.read.is_some() && orig_private_message.recipient_id.eq(&user_id) - || orig_private_message.creator_id.eq(&user_id)) - { - return Err(APIError::err("no_private_message_edit_allowed").into()); - } - - let content_slurs_removed = match &data.content { - Some(content) => Some(remove_slurs(content)), - None => None, - }; - - let private_message_form = PrivateMessageForm { - content: content_slurs_removed, - creator_id: orig_private_message.creator_id, - recipient_id: orig_private_message.recipient_id, - deleted: data.deleted.to_owned(), - read: data.read.to_owned(), - updated: if data.read.is_some() { - orig_private_message.updated - } else { - Some(naive_now()) - }, - }; - - let _updated_private_message = - match PrivateMessage::update(&conn, data.edit_id, &private_message_form) { - Ok(private_message) => private_message, - Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()), - }; - - let message = PrivateMessageView::read(&conn, data.edit_id)?; - - Ok(PrivateMessageResponse { message }) - } -} - -impl Perform for Oper { - type Response = PrivateMessagesResponse; - - fn perform( - &self, - pool: Pool>, - _websocket_info: Option, - ) -> Result { - let data: &GetPrivateMessages = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - let conn = pool.get()?; - - let messages = PrivateMessageQueryBuilder::create(&conn, user_id) - .page(data.page) - .limit(data.limit) - .unread_only(data.unread_only) - .list()?; - - Ok(PrivateMessagesResponse { messages }) - } -} - -impl Perform for Oper { - type Response = UserJoinResponse; - - fn perform( - &self, - _pool: Pool>, - websocket_info: Option, - ) -> Result { - let data: &UserJoin = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - if let Some(ws) = websocket_info { - if let Some(id) = ws.id { - ws.chatserver.do_send(JoinUserRoom { user_id, id }); - } - } - - Ok(UserJoinResponse { user_id }) - } -} diff --git a/server/src/apub.rs b/server/src/apub.rs new file mode 100644 index 00000000..66878d37 --- /dev/null +++ b/server/src/apub.rs @@ -0,0 +1,90 @@ +extern crate activitypub; +use self::activitypub::{actor::Person, context}; +use crate::db::user::User_; + +impl User_ { + pub fn person(&self) -> Person { + use crate::{to_datetime_utc, Settings}; + let base_url = &format!("{}/user/{}", Settings::get().api_endpoint(), self.name); + let mut person = Person::default(); + person.object_props.set_context_object(context()).ok(); + person.object_props.set_id_string(base_url.to_string()).ok(); + person + .object_props + .set_name_string(self.name.to_owned()) + .ok(); + person + .object_props + .set_published_utctime(to_datetime_utc(self.published)) + .ok(); + if let Some(i) = self.updated { + person + .object_props + .set_updated_utctime(to_datetime_utc(i)) + .ok(); + } + // person.object_props.summary = self.summary; + + person + .ap_actor_props + .set_inbox_string(format!("{}/inbox", &base_url)) + .ok(); + person + .ap_actor_props + .set_outbox_string(format!("{}/outbox", &base_url)) + .ok(); + person + .ap_actor_props + .set_following_string(format!("{}/following", &base_url)) + .ok(); + person + .ap_actor_props + .set_liked_string(format!("{}/liked", &base_url)) + .ok(); + if let Some(i) = &self.preferred_username { + person + .ap_actor_props + .set_preferred_username_string(i.to_string()) + .ok(); + } + + person + } +} + +#[cfg(test)] +mod tests { + use super::User_; + use crate::db::{ListingType, SortType}; + use crate::naive_now; + + #[test] + fn test_person() { + let expected_user = User_ { + id: 52, + name: "thom".into(), + fedi_name: "rrf".into(), + preferred_username: None, + password_encrypted: "here".into(), + email: None, + icon: None, + published: naive_now(), + admin: false, + banned: false, + updated: None, + show_nsfw: false, + theme: "darkly".into(), + default_sort_type: SortType::Hot as i16, + default_listing_type: ListingType::Subscribed as i16, + lang: "browser".into(), + }; + + let person = expected_user.person(); + assert_eq!( + "rrr/api/v1/user/thom", + person.object_props.id_string().unwrap() + ); + let json = serde_json::to_string_pretty(&person).unwrap(); + println!("{}", json); + } +} diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs deleted file mode 100644 index 32f14eeb..00000000 --- a/server/src/apub/community.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::apub::make_apub_endpoint; -use crate::db::community::Community; -use crate::db::community_view::CommunityFollowerView; -use crate::db::establish_unpooled_connection; -use crate::to_datetime_utc; -use activitypub::{actor::Group, collection::UnorderedCollection, context}; -use actix_web::body::Body; -use actix_web::web::Path; -use actix_web::HttpResponse; -use serde::Deserialize; - -impl Community { - pub fn as_group(&self) -> Group { - let base_url = make_apub_endpoint("c", &self.name); - - let mut group = Group::default(); - - group.object_props.set_context_object(context()).ok(); - group.object_props.set_id_string(base_url.to_string()).ok(); - group - .object_props - .set_name_string(self.name.to_owned()) - .ok(); - group - .object_props - .set_published_utctime(to_datetime_utc(self.published)) - .ok(); - if let Some(updated) = self.updated { - group - .object_props - .set_updated_utctime(to_datetime_utc(updated)) - .ok(); - } - - if let Some(description) = &self.description { - group - .object_props - .set_summary_string(description.to_string()) - .ok(); - } - - group - .ap_actor_props - .set_inbox_string(format!("{}/inbox", &base_url)) - .ok(); - group - .ap_actor_props - .set_outbox_string(format!("{}/outbox", &base_url)) - .ok(); - group - .ap_actor_props - .set_followers_string(format!("{}/followers", &base_url)) - .ok(); - - group - } - - pub fn followers_as_collection(&self) -> UnorderedCollection { - let base_url = make_apub_endpoint("c", &self.name); - - let mut collection = UnorderedCollection::default(); - collection.object_props.set_context_object(context()).ok(); - collection.object_props.set_id_string(base_url).ok(); - - let connection = establish_unpooled_connection(); - //As we are an object, we validated that the community id was valid - let community_followers = CommunityFollowerView::for_community(&connection, self.id).unwrap(); - - let ap_followers = community_followers - .iter() - .map(|follower| make_apub_endpoint("u", &follower.user_name)) - .collect(); - - collection - .collection_props - .set_items_string_vec(ap_followers) - .unwrap(); - collection - } -} - -#[derive(Deserialize)] -pub struct CommunityQuery { - community_name: String, -} - -pub async fn get_apub_community(info: Path) -> HttpResponse { - let connection = establish_unpooled_connection(); - - if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) { - HttpResponse::Ok() - .content_type("application/activity+json") - .body(serde_json::to_string(&community.as_group()).unwrap()) - } else { - HttpResponse::NotFound().finish() - } -} - -pub async fn get_apub_community_followers(info: Path) -> HttpResponse { - let connection = establish_unpooled_connection(); - - if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) { - HttpResponse::Ok() - .content_type("application/activity+json") - .body(serde_json::to_string(&community.followers_as_collection()).unwrap()) - } else { - HttpResponse::NotFound().finish() - } -} diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs deleted file mode 100644 index e224e259..00000000 --- a/server/src/apub/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -pub mod community; -pub mod post; -pub mod user; -use crate::Settings; - -use std::fmt::Display; - -#[cfg(test)] -mod tests { - use crate::db::community::Community; - use crate::db::post::Post; - use crate::db::user::User_; - use crate::db::{ListingType, SortType}; - use crate::{naive_now, Settings}; - - #[test] - fn test_person() { - let user = User_ { - id: 52, - name: "thom".into(), - fedi_name: "rrf".into(), - preferred_username: None, - password_encrypted: "here".into(), - email: None, - matrix_user_id: None, - avatar: None, - published: naive_now(), - admin: false, - banned: false, - updated: None, - show_nsfw: false, - theme: "darkly".into(), - default_sort_type: SortType::Hot as i16, - default_listing_type: ListingType::Subscribed as i16, - lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, - }; - - let person = user.as_person(); - assert_eq!( - format!("https://{}/federation/u/thom", Settings::get().hostname), - person.object_props.id_string().unwrap() - ); - } - - #[test] - fn test_community() { - let community = Community { - id: 42, - name: "Test".into(), - title: "Test Title".into(), - description: Some("Test community".into()), - category_id: 32, - creator_id: 52, - removed: false, - published: naive_now(), - updated: Some(naive_now()), - deleted: false, - nsfw: false, - }; - - let group = community.as_group(); - assert_eq!( - format!("https://{}/federation/c/Test", Settings::get().hostname), - group.object_props.id_string().unwrap() - ); - } - - #[test] - fn test_post() { - let post = Post { - id: 62, - name: "A test post".into(), - url: None, - body: None, - creator_id: 52, - community_id: 42, - published: naive_now(), - removed: false, - locked: false, - stickied: false, - nsfw: false, - deleted: false, - updated: None, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, - }; - - let page = post.as_page(); - assert_eq!( - format!("https://{}/federation/post/62", Settings::get().hostname), - page.object_props.id_string().unwrap() - ); - } -} - -pub fn make_apub_endpoint(point: S, value: T) -> String { - format!( - "https://{}/federation/{}/{}", - Settings::get().hostname, - point, - value - ) -} diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs deleted file mode 100644 index ebb17129..00000000 --- a/server/src/apub/post.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::apub::make_apub_endpoint; -use crate::db::post::Post; -use crate::to_datetime_utc; -use activitypub::{context, object::Page}; - -impl Post { - pub fn as_page(&self) -> Page { - let base_url = make_apub_endpoint("post", self.id); - let mut page = Page::default(); - - page.object_props.set_context_object(context()).ok(); - page.object_props.set_id_string(base_url).ok(); - page.object_props.set_name_string(self.name.to_owned()).ok(); - - if let Some(body) = &self.body { - page.object_props.set_content_string(body.to_owned()).ok(); - } - - if let Some(url) = &self.url { - page.object_props.set_url_string(url.to_owned()).ok(); - } - - //page.object_props.set_attributed_to_string - - page - .object_props - .set_published_utctime(to_datetime_utc(self.published)) - .ok(); - if let Some(updated) = self.updated { - page - .object_props - .set_updated_utctime(to_datetime_utc(updated)) - .ok(); - } - - page - } -} diff --git a/server/src/apub/user.rs b/server/src/apub/user.rs deleted file mode 100644 index 5f2421f1..00000000 --- a/server/src/apub/user.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::apub::make_apub_endpoint; -use crate::db::establish_unpooled_connection; -use crate::db::user::User_; -use crate::to_datetime_utc; -use activitypub::{actor::Person, context}; -use actix_web::body::Body; -use actix_web::web::Path; -use actix_web::HttpResponse; -use serde::Deserialize; - -impl User_ { - pub fn as_person(&self) -> Person { - let base_url = make_apub_endpoint("u", &self.name); - let mut person = Person::default(); - person.object_props.set_context_object(context()).ok(); - person.object_props.set_id_string(base_url.to_string()).ok(); - person - .object_props - .set_name_string(self.name.to_owned()) - .ok(); - person - .object_props - .set_published_utctime(to_datetime_utc(self.published)) - .ok(); - if let Some(updated) = self.updated { - person - .object_props - .set_updated_utctime(to_datetime_utc(updated)) - .ok(); - } - - person - .ap_actor_props - .set_inbox_string(format!("{}/inbox", &base_url)) - .ok(); - person - .ap_actor_props - .set_outbox_string(format!("{}/outbox", &base_url)) - .ok(); - person - .ap_actor_props - .set_following_string(format!("{}/following", &base_url)) - .ok(); - person - .ap_actor_props - .set_liked_string(format!("{}/liked", &base_url)) - .ok(); - if let Some(i) = &self.preferred_username { - person - .ap_actor_props - .set_preferred_username_string(i.to_string()) - .ok(); - } - - person - } -} - -#[derive(Deserialize)] -pub struct UserQuery { - user_name: String, -} - -pub async fn get_apub_user(info: Path) -> HttpResponse { - let connection = establish_unpooled_connection(); - - if let Ok(user) = User_::find_by_email_or_username(&connection, &info.user_name) { - HttpResponse::Ok() - .content_type("application/activity+json") - .body(serde_json::to_string(&user.as_person()).unwrap()) - } else { - HttpResponse::NotFound().finish() - } -} diff --git a/server/src/db/category.rs b/server/src/db/category.rs index 408c8231..6e483ce0 100644 --- a/server/src/db/category.rs +++ b/server/src/db/category.rs @@ -52,7 +52,7 @@ mod tests { use super::*; #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let categories = Category::list_all(&conn).unwrap(); let expected_first_category = Category { diff --git a/server/src/db/comment.rs b/server/src/db/comment.rs index c9bfbac6..b7bd562d 100644 --- a/server/src/db/comment.rs +++ b/server/src/db/comment.rs @@ -166,7 +166,7 @@ mod tests { use super::*; #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let new_user = UserForm { name: "terry".into(), @@ -174,8 +174,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -184,8 +182,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -216,10 +212,6 @@ mod tests { stickied: None, updated: None, nsfw: false, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); diff --git a/server/src/db/comment_view.rs b/server/src/db/comment_view.rs index 85b41d82..5c321e2e 100644 --- a/server/src/db/comment_view.rs +++ b/server/src/db/comment_view.rs @@ -15,47 +15,14 @@ table! { updated -> Nullable, deleted -> Bool, community_id -> Int4, - community_name -> Varchar, banned -> Bool, banned_from_community -> Bool, creator_name -> Varchar, - creator_avatar -> Nullable, score -> BigInt, upvotes -> BigInt, downvotes -> BigInt, - hot_rank -> Int4, user_id -> Nullable, my_vote -> Nullable, - subscribed -> Nullable, - saved -> Nullable, - } -} - -table! { - comment_mview (id) { - id -> Int4, - creator_id -> Int4, - post_id -> Int4, - parent_id -> Nullable, - content -> Text, - removed -> Bool, - read -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - community_id -> Int4, - community_name -> Varchar, - banned -> Bool, - banned_from_community -> Bool, - creator_name -> Varchar, - creator_avatar -> Nullable, - score -> BigInt, - upvotes -> BigInt, - downvotes -> BigInt, - hot_rank -> Int4, - user_id -> Nullable, - my_vote -> Nullable, - subscribed -> Nullable, saved -> Nullable, } } @@ -76,27 +43,21 @@ pub struct CommentView { pub updated: Option, pub deleted: bool, pub community_id: i32, - pub community_name: String, pub banned: bool, pub banned_from_community: bool, pub creator_name: String, - pub creator_avatar: Option, pub score: i64, pub upvotes: i64, pub downvotes: i64, - pub hot_rank: i32, pub user_id: Option, pub my_vote: Option, - pub subscribed: Option, pub saved: Option, } pub struct CommentQueryBuilder<'a> { conn: &'a PgConnection, - query: super::comment_view::comment_mview::BoxedQuery<'a, Pg>, - listing_type: ListingType, + query: super::comment_view::comment_view::BoxedQuery<'a, Pg>, sort: &'a SortType, - for_community_id: Option, for_post_id: Option, for_creator_id: Option, search_term: Option, @@ -108,16 +69,14 @@ pub struct CommentQueryBuilder<'a> { impl<'a> CommentQueryBuilder<'a> { pub fn create(conn: &'a PgConnection) -> Self { - use super::comment_view::comment_mview::dsl::*; + use super::comment_view::comment_view::dsl::*; - let query = comment_mview.into_boxed(); + let query = comment_view.into_boxed(); CommentQueryBuilder { conn, query, - listing_type: ListingType::All, sort: &SortType::New, - for_community_id: None, for_post_id: None, for_creator_id: None, search_term: None, @@ -128,11 +87,6 @@ impl<'a> CommentQueryBuilder<'a> { } } - pub fn listing_type(mut self, listing_type: ListingType) -> Self { - self.listing_type = listing_type; - self - } - pub fn sort(mut self, sort: &'a SortType) -> Self { self.sort = sort; self @@ -148,11 +102,6 @@ impl<'a> CommentQueryBuilder<'a> { self } - pub fn for_community_id>(mut self, for_community_id: T) -> Self { - self.for_community_id = for_community_id.get_optional(); - self - } - pub fn search_term>(mut self, search_term: T) -> Self { self.search_term = search_term.get_optional(); self @@ -179,7 +128,7 @@ impl<'a> CommentQueryBuilder<'a> { } pub fn list(self) -> Result, Error> { - use super::comment_view::comment_mview::dsl::*; + use super::comment_view::comment_view::dsl::*; let mut query = self.query; @@ -194,10 +143,6 @@ impl<'a> CommentQueryBuilder<'a> { query = query.filter(creator_id.eq(for_creator_id)); }; - if let Some(for_community_id) = self.for_community_id { - query = query.filter(community_id.eq(for_community_id)); - } - if let Some(for_post_id) = self.for_post_id { query = query.filter(post_id.eq(for_post_id)); }; @@ -206,18 +151,12 @@ impl<'a> CommentQueryBuilder<'a> { query = query.filter(content.ilike(fuzzy_search(&search_term))); }; - if let ListingType::Subscribed = self.listing_type { - query = query.filter(subscribed.eq(true)); - } - if self.saved_only { query = query.filter(saved.eq(true)); } query = match self.sort { - SortType::Hot => query - .order_by(hot_rank.desc()) - .then_order_by(published.desc()), + // SortType::Hot => query.order(hot_rank.desc(), published.desc()), SortType::New => query.order_by(published.desc()), SortType::TopAll => query.order_by(score.desc()), SortType::TopYear => query @@ -232,7 +171,7 @@ impl<'a> CommentQueryBuilder<'a> { SortType::TopDay => query .filter(published.gt(now - 1.days())) .order_by(score.desc()), - // _ => query.order_by(published.desc()), + _ => query.order_by(published.desc()), }; let (limit, offset) = limit_and_offset(self.page, self.limit); @@ -251,8 +190,9 @@ impl CommentView { from_comment_id: i32, my_user_id: Option, ) -> Result { - use super::comment_view::comment_mview::dsl::*; - let mut query = comment_mview.into_boxed(); + use super::comment_view::comment_view::dsl::*; + + let mut query = comment_view.into_boxed(); // The view lets you pass a null user_id, if you're not logged in if let Some(my_user_id) = my_user_id { @@ -283,18 +223,14 @@ table! { updated -> Nullable, deleted -> Bool, community_id -> Int4, - community_name -> Varchar, banned -> Bool, banned_from_community -> Bool, creator_name -> Varchar, - creator_avatar -> Nullable, score -> BigInt, upvotes -> BigInt, downvotes -> BigInt, - hot_rank -> Int4, user_id -> Nullable, my_vote -> Nullable, - subscribed -> Nullable, saved -> Nullable, recipient_id -> Int4, } @@ -316,18 +252,14 @@ pub struct ReplyView { pub updated: Option, pub deleted: bool, pub community_id: i32, - pub community_name: String, pub banned: bool, pub banned_from_community: bool, pub creator_name: String, - pub creator_avatar: Option, pub score: i64, pub upvotes: i64, pub downvotes: i64, - pub hot_rank: i32, pub user_id: Option, pub my_vote: Option, - pub subscribed: Option, pub saved: Option, pub recipient_id: i32, } @@ -386,9 +318,7 @@ impl<'a> ReplyQueryBuilder<'a> { query = query .filter(user_id.eq(self.for_user_id)) - .filter(recipient_id.eq(self.for_user_id)) - .filter(deleted.eq(false)) - .filter(removed.eq(false)); + .filter(recipient_id.eq(self.for_user_id)); if self.unread_only { query = query.filter(read.eq(false)); @@ -430,7 +360,7 @@ mod tests { use super::*; #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let new_user = UserForm { name: "timmy".into(), @@ -438,8 +368,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -448,8 +376,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -480,10 +406,6 @@ mod tests { stickied: None, updated: None, nsfw: false, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); @@ -516,7 +438,6 @@ mod tests { creator_id: inserted_user.id, post_id: inserted_post.id, community_id: inserted_community.id, - community_name: inserted_community.name.to_owned(), parent_id: None, removed: false, deleted: false, @@ -526,14 +447,11 @@ mod tests { published: inserted_comment.published, updated: None, creator_name: inserted_user.name.to_owned(), - creator_avatar: None, score: 1, downvotes: 0, - hot_rank: 0, upvotes: 1, user_id: None, my_vote: None, - subscribed: None, saved: None, }; @@ -543,7 +461,6 @@ mod tests { creator_id: inserted_user.id, post_id: inserted_post.id, community_id: inserted_community.id, - community_name: inserted_community.name.to_owned(), parent_id: None, removed: false, deleted: false, @@ -553,29 +470,23 @@ mod tests { published: inserted_comment.published, updated: None, creator_name: inserted_user.name.to_owned(), - creator_avatar: None, score: 1, downvotes: 0, - hot_rank: 0, upvotes: 1, user_id: Some(inserted_user.id), my_vote: Some(1), - subscribed: None, saved: None, }; - let mut read_comment_views_no_user = CommentQueryBuilder::create(&conn) + let read_comment_views_no_user = CommentQueryBuilder::create(&conn) .for_post_id(inserted_post.id) .list() .unwrap(); - read_comment_views_no_user[0].hot_rank = 0; - - let mut read_comment_views_with_user = CommentQueryBuilder::create(&conn) + let read_comment_views_with_user = CommentQueryBuilder::create(&conn) .for_post_id(inserted_post.id) .my_user_id(inserted_user.id) .list() .unwrap(); - read_comment_views_with_user[0].hot_rank = 0; let like_removed = CommentLike::remove(&conn, &comment_like_form).unwrap(); let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap(); diff --git a/server/src/db/community.rs b/server/src/db/community.rs index 63500963..74579535 100644 --- a/server/src/db/community.rs +++ b/server/src/db/community.rs @@ -69,8 +69,8 @@ impl Community { .first::(conn) } - pub fn get_url(&self) -> String { - format!("https://{}/c/{}", Settings::get().hostname, self.name) + pub fn get_community_url(community_name: &str) -> String { + format!("https://{}/c/{}", Settings::get().hostname, community_name) } } @@ -212,7 +212,7 @@ mod tests { use super::*; #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let new_user = UserForm { name: "bobbee".into(), @@ -220,8 +220,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -230,8 +228,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); diff --git a/server/src/db/community_view.rs b/server/src/db/community_view.rs index f24c922c..157c4d91 100644 --- a/server/src/db/community_view.rs +++ b/server/src/db/community_view.rs @@ -1,4 +1,4 @@ -use super::community_view::community_mview::BoxedQuery; +use super::community_view::community_view::BoxedQuery; use super::*; use diesel::pg::Pg; @@ -16,32 +16,6 @@ table! { deleted -> Bool, nsfw -> Bool, creator_name -> Varchar, - creator_avatar -> Nullable, - category_name -> Varchar, - number_of_subscribers -> BigInt, - number_of_posts -> BigInt, - number_of_comments -> BigInt, - hot_rank -> Int4, - user_id -> Nullable, - subscribed -> Nullable, - } -} - -table! { - community_mview (id) { - id -> Int4, - name -> Varchar, - title -> Varchar, - description -> Nullable, - category_id -> Int4, - creator_id -> Int4, - removed -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - nsfw -> Bool, - creator_name -> Varchar, - creator_avatar -> Nullable, category_name -> Varchar, number_of_subscribers -> BigInt, number_of_posts -> BigInt, @@ -59,7 +33,6 @@ table! { user_id -> Int4, published -> Timestamp, user_name -> Varchar, - avatar -> Nullable, community_name -> Varchar, } } @@ -71,7 +44,6 @@ table! { user_id -> Int4, published -> Timestamp, user_name -> Varchar, - avatar -> Nullable, community_name -> Varchar, } } @@ -83,7 +55,6 @@ table! { user_id -> Int4, published -> Timestamp, user_name -> Varchar, - avatar -> Nullable, community_name -> Varchar, } } @@ -105,7 +76,6 @@ pub struct CommunityView { pub deleted: bool, pub nsfw: bool, pub creator_name: String, - pub creator_avatar: Option, pub category_name: String, pub number_of_subscribers: i64, pub number_of_posts: i64, @@ -128,9 +98,9 @@ pub struct CommunityQueryBuilder<'a> { impl<'a> CommunityQueryBuilder<'a> { pub fn create(conn: &'a PgConnection) -> Self { - use super::community_view::community_mview::dsl::*; + use super::community_view::community_view::dsl::*; - let query = community_mview.into_boxed(); + let query = community_view.into_boxed(); CommunityQueryBuilder { conn, @@ -149,7 +119,7 @@ impl<'a> CommunityQueryBuilder<'a> { self } - pub fn for_user>(mut self, from_user_id: T) -> Self { + pub fn from_user_id>(mut self, from_user_id: T) -> Self { self.from_user_id = from_user_id.get_optional(); self } @@ -175,16 +145,12 @@ impl<'a> CommunityQueryBuilder<'a> { } pub fn list(self) -> Result, Error> { - use super::community_view::community_mview::dsl::*; + use super::community_view::community_view::dsl::*; let mut query = self.query; if let Some(search_term) = self.search_term { - let searcher = fuzzy_search(&search_term); - query = query - .filter(name.ilike(searcher.to_owned())) - .or_filter(title.ilike(searcher.to_owned())) - .or_filter(description.ilike(searcher)); + query = query.filter(name.ilike(fuzzy_search(&search_term))); }; // The view lets you pass a null user_id, if you're not logged in @@ -231,9 +197,9 @@ impl CommunityView { from_community_id: i32, from_user_id: Option, ) -> Result { - use super::community_view::community_mview::dsl::*; + use super::community_view::community_view::dsl::*; - let mut query = community_mview.into_boxed(); + let mut query = community_view.into_boxed(); query = query.filter(id.eq(from_community_id)); @@ -258,7 +224,6 @@ pub struct CommunityModeratorView { pub user_id: i32, pub published: chrono::NaiveDateTime, pub user_name: String, - pub avatar: Option, pub community_name: String, } @@ -288,7 +253,6 @@ pub struct CommunityFollowerView { pub user_id: i32, pub published: chrono::NaiveDateTime, pub user_name: String, - pub avatar: Option, pub community_name: String, } @@ -318,7 +282,6 @@ pub struct CommunityUserBanView { pub user_id: i32, pub published: chrono::NaiveDateTime, pub user_name: String, - pub avatar: Option, pub community_name: String, } diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index e0d358ff..7824580d 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -1,5 +1,7 @@ +extern crate lazy_static; use crate::settings::Settings; use diesel::dsl::*; +use diesel::r2d2::*; use diesel::result::Error; use diesel::*; use serde::{Deserialize, Serialize}; @@ -14,8 +16,6 @@ pub mod moderator_views; pub mod password_reset_request; pub mod post; pub mod post_view; -pub mod private_message; -pub mod private_message_view; pub mod site; pub mod site_view; pub mod user; @@ -101,19 +101,29 @@ pub trait MaybeOptional { impl MaybeOptional for T { fn get_optional(self) -> Option { - Some(self) + return Some(self); } } impl MaybeOptional for Option { fn get_optional(self) -> Option { - self + return self; } } -pub fn establish_unpooled_connection() -> PgConnection { - let db_url = Settings::get().get_database_url(); - PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url)) +lazy_static! { + static ref PG_POOL: Pool> = { + let db_url = Settings::get().get_database_url(); + let manager = ConnectionManager::::new(&db_url); + Pool::builder() + .max_size(Settings::get().database.pool_size) + .build(manager) + .expect(&format!("Error connecting to {}", db_url)) + }; +} + +pub fn establish_connection() -> PooledConnection> { + return PG_POOL.get().unwrap(); } #[derive(EnumString, ToString, Debug, Serialize, Deserialize)] diff --git a/server/src/db/moderator.rs b/server/src/db/moderator.rs index a8c3df4f..22547ca4 100644 --- a/server/src/db/moderator.rs +++ b/server/src/db/moderator.rs @@ -434,7 +434,7 @@ mod tests { // use Crud; #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let new_mod = UserForm { name: "the mod".into(), @@ -442,8 +442,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -452,8 +450,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_mod = User_::create(&conn, &new_mod).unwrap(); @@ -464,8 +460,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -474,8 +468,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -506,10 +498,6 @@ mod tests { stickied: None, updated: None, nsfw: false, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); diff --git a/server/src/db/password_reset_request.rs b/server/src/db/password_reset_request.rs index 6951fd39..8c65b4c0 100644 --- a/server/src/db/password_reset_request.rs +++ b/server/src/db/password_reset_request.rs @@ -1,7 +1,8 @@ use super::*; use crate::schema::password_reset_request; use crate::schema::password_reset_request::dsl::*; -use sha2::{Digest, Sha256}; +use crypto::digest::Digest; +use crypto::sha2::Sha256; #[derive(Queryable, Identifiable, PartialEq, Debug)] #[table_name = "password_reset_request"] @@ -48,8 +49,8 @@ impl Crud for PasswordResetRequest { impl PasswordResetRequest { pub fn create_token(conn: &PgConnection, from_user_id: i32, token: &str) -> Result { let mut hasher = Sha256::new(); - hasher.input(token); - let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.result().to_vec()); + hasher.input_str(token); + let token_hash = hasher.result_str(); let form = PasswordResetRequestForm { user_id: from_user_id, @@ -60,21 +61,13 @@ impl PasswordResetRequest { } pub fn read_from_token(conn: &PgConnection, token: &str) -> Result { let mut hasher = Sha256::new(); - hasher.input(token); - let token_hash: String = PasswordResetRequest::bytes_to_hex(hasher.result().to_vec()); + hasher.input_str(token); + let token_hash = hasher.result_str(); password_reset_request .filter(token_encrypted.eq(token_hash)) .filter(published.gt(now - 1.days())) .first::(conn) } - - fn bytes_to_hex(bytes: Vec) -> String { - let mut str = String::new(); - for byte in bytes { - str = format!("{}{:02x}", str, byte); - } - str - } } #[cfg(test)] @@ -84,7 +77,7 @@ mod tests { #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let new_user = UserForm { name: "thommy prw".into(), @@ -92,8 +85,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -102,26 +93,27 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); - let token = "nope"; - let token_encrypted_ = "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce"; + let new_password_reset_request = PasswordResetRequestForm { + user_id: inserted_user.id, + token_encrypted: "no".into(), + }; let inserted_password_reset_request = - PasswordResetRequest::create_token(&conn, inserted_user.id, token).unwrap(); + PasswordResetRequest::create(&conn, &new_password_reset_request).unwrap(); let expected_password_reset_request = PasswordResetRequest { id: inserted_password_reset_request.id, user_id: inserted_user.id, - token_encrypted: token_encrypted_.to_string(), + token_encrypted: "no".into(), published: inserted_password_reset_request.published, }; - let read_password_reset_request = PasswordResetRequest::read_from_token(&conn, token).unwrap(); + let read_password_reset_request = + PasswordResetRequest::read(&conn, inserted_password_reset_request.id).unwrap(); let num_deleted = User_::delete(&conn, inserted_user.id).unwrap(); assert_eq!(expected_password_reset_request, read_password_reset_request); diff --git a/server/src/db/post.rs b/server/src/db/post.rs index ffde14d3..96ae31db 100644 --- a/server/src/db/post.rs +++ b/server/src/db/post.rs @@ -17,10 +17,6 @@ pub struct Post { pub deleted: bool, pub nsfw: bool, pub stickied: bool, - pub embed_title: Option, - pub embed_description: Option, - pub embed_html: Option, - pub thumbnail_url: Option, } #[derive(Insertable, AsChangeset, Clone)] @@ -37,10 +33,6 @@ pub struct PostForm { pub deleted: Option, pub nsfw: bool, pub stickied: Option, - pub embed_title: Option, - pub embed_description: Option, - pub embed_html: Option, - pub thumbnail_url: Option, } impl Crud for Post { @@ -187,7 +179,7 @@ mod tests { use super::*; #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let new_user = UserForm { name: "jim".into(), @@ -195,8 +187,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -205,8 +195,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -237,10 +225,6 @@ mod tests { stickied: None, nsfw: false, updated: None, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); @@ -259,10 +243,6 @@ mod tests { nsfw: false, deleted: false, updated: None, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; // Post Like diff --git a/server/src/db/post_view.rs b/server/src/db/post_view.rs index f48f4f68..615b9b0d 100644 --- a/server/src/db/post_view.rs +++ b/server/src/db/post_view.rs @@ -1,4 +1,4 @@ -use super::post_view::post_mview::BoxedQuery; +use super::post_view::post_view::BoxedQuery; use super::*; use diesel::pg::Pg; @@ -17,15 +17,10 @@ table! { updated -> Nullable, deleted -> Bool, nsfw -> Bool, - stickied -> Bool, - embed_title -> Nullable, - embed_description -> Nullable, - embed_html -> Nullable, - thumbnail_url -> Nullable, banned -> Bool, banned_from_community -> Bool, + stickied -> Bool, creator_name -> Varchar, - creator_avatar -> Nullable, community_name -> Varchar, community_removed -> Bool, community_deleted -> Bool, @@ -35,48 +30,6 @@ table! { upvotes -> BigInt, downvotes -> BigInt, hot_rank -> Int4, - newest_activity_time -> Timestamp, - user_id -> Nullable, - my_vote -> Nullable, - subscribed -> Nullable, - read -> Nullable, - saved -> Nullable, - } -} - -table! { - post_mview (id) { - id -> Int4, - name -> Varchar, - url -> Nullable, - body -> Nullable, - creator_id -> Int4, - community_id -> Int4, - removed -> Bool, - locked -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - nsfw -> Bool, - stickied -> Bool, - embed_title -> Nullable, - embed_description -> Nullable, - embed_html -> Nullable, - thumbnail_url -> Nullable, - banned -> Bool, - banned_from_community -> Bool, - creator_name -> Varchar, - creator_avatar -> Nullable, - community_name -> Varchar, - community_removed -> Bool, - community_deleted -> Bool, - community_nsfw -> Bool, - number_of_comments -> BigInt, - score -> BigInt, - upvotes -> BigInt, - downvotes -> BigInt, - hot_rank -> Int4, - newest_activity_time -> Timestamp, user_id -> Nullable, my_vote -> Nullable, subscribed -> Nullable, @@ -102,15 +55,10 @@ pub struct PostView { pub updated: Option, pub deleted: bool, pub nsfw: bool, - pub stickied: bool, - pub embed_title: Option, - pub embed_description: Option, - pub embed_html: Option, - pub thumbnail_url: Option, pub banned: bool, pub banned_from_community: bool, + pub stickied: bool, pub creator_name: String, - pub creator_avatar: Option, pub community_name: String, pub community_removed: bool, pub community_deleted: bool, @@ -120,7 +68,6 @@ pub struct PostView { pub upvotes: i64, pub downvotes: i64, pub hot_rank: i32, - pub newest_activity_time: chrono::NaiveDateTime, pub user_id: Option, pub my_vote: Option, pub subscribed: Option, @@ -135,9 +82,6 @@ pub struct PostQueryBuilder<'a> { sort: &'a SortType, my_user_id: Option, for_creator_id: Option, - for_community_id: Option, - search_term: Option, - url_search: Option, show_nsfw: bool, saved_only: bool, unread_only: bool, @@ -147,20 +91,17 @@ pub struct PostQueryBuilder<'a> { impl<'a> PostQueryBuilder<'a> { pub fn create(conn: &'a PgConnection) -> Self { - use super::post_view::post_mview::dsl::*; + use super::post_view::post_view::dsl::*; - let query = post_mview.into_boxed(); + let query = post_view.into_boxed(); PostQueryBuilder { conn, query, - listing_type: ListingType::All, - sort: &SortType::Hot, my_user_id: None, for_creator_id: None, - for_community_id: None, - search_term: None, - url_search: None, + listing_type: ListingType::All, + sort: &SortType::Hot, show_nsfw: true, saved_only: false, unread_only: false, @@ -180,22 +121,34 @@ impl<'a> PostQueryBuilder<'a> { } pub fn for_community_id>(mut self, for_community_id: T) -> Self { - self.for_community_id = for_community_id.get_optional(); + use super::post_view::post_view::dsl::*; + if let Some(for_community_id) = for_community_id.get_optional() { + self.query = self.query.filter(community_id.eq(for_community_id)); + self.query = self.query.then_order_by(stickied.desc()); + } self } pub fn for_creator_id>(mut self, for_creator_id: T) -> Self { - self.for_creator_id = for_creator_id.get_optional(); + if let Some(for_creator_id) = for_creator_id.get_optional() { + self.for_creator_id = Some(for_creator_id); + } self } pub fn search_term>(mut self, search_term: T) -> Self { - self.search_term = search_term.get_optional(); + use super::post_view::post_view::dsl::*; + if let Some(search_term) = search_term.get_optional() { + self.query = self.query.filter(name.ilike(fuzzy_search(&search_term))); + } self } pub fn url_search>(mut self, url_search: T) -> Self { - self.url_search = url_search.get_optional(); + use super::post_view::post_view::dsl::*; + if let Some(url_search) = url_search.get_optional() { + self.query = self.query.filter(url.eq(url_search)); + } self } @@ -230,29 +183,16 @@ impl<'a> PostQueryBuilder<'a> { } pub fn list(self) -> Result, Error> { - use super::post_view::post_mview::dsl::*; + use super::post_view::post_view::dsl::*; let mut query = self.query; - if let ListingType::Subscribed = self.listing_type { - query = query.filter(subscribed.eq(true)); - } - - if let Some(for_community_id) = self.for_community_id { - query = query.filter(community_id.eq(for_community_id)); - query = query.then_order_by(stickied.desc()); - } - - if let Some(url_search) = self.url_search { - query = query.filter(url.eq(url_search)); - } - - if let Some(search_term) = self.search_term { - let searcher = fuzzy_search(&search_term); - query = query - .filter(name.ilike(searcher.to_owned())) - .or_filter(body.ilike(searcher)); - } + match self.listing_type { + ListingType::Subscribed => { + query = query.filter(subscribed.eq(true)); + } + _ => {} + }; query = match self.sort { SortType::Hot => query @@ -326,10 +266,10 @@ impl PostView { from_post_id: i32, my_user_id: Option, ) -> Result { - use super::post_view::post_mview::dsl::*; + use super::post_view::post_view::dsl::*; use diesel::prelude::*; - let mut query = post_mview.into_boxed(); + let mut query = post_view.into_boxed(); query = query.filter(id.eq(from_post_id)); @@ -351,7 +291,7 @@ mod tests { use super::*; #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let user_name = "tegan".to_string(); let community_name = "test_community_3".to_string(); @@ -363,8 +303,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, updated: None, admin: false, banned: false, @@ -373,8 +311,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -405,10 +341,6 @@ mod tests { stickied: None, updated: None, nsfw: false, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); @@ -445,7 +377,6 @@ mod tests { body: None, creator_id: inserted_user.id, creator_name: user_name.to_owned(), - creator_avatar: None, banned: false, banned_from_community: false, community_id: inserted_community.id, @@ -463,23 +394,18 @@ mod tests { downvotes: 0, hot_rank: 1728, published: inserted_post.published, - newest_activity_time: inserted_post.published, updated: None, subscribed: None, read: None, saved: None, nsfw: false, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let expected_post_listing_with_user = PostView { user_id: Some(inserted_user.id), my_vote: Some(1), id: inserted_post.id, - name: post_name, + name: post_name.to_owned(), url: None, body: None, removed: false, @@ -487,12 +413,11 @@ mod tests { locked: false, stickied: false, creator_id: inserted_user.id, - creator_name: user_name, - creator_avatar: None, + creator_name: user_name.to_owned(), banned: false, banned_from_community: false, community_id: inserted_community.id, - community_name, + community_name: community_name.to_owned(), community_removed: false, community_deleted: false, community_nsfw: false, @@ -502,16 +427,11 @@ mod tests { downvotes: 0, hot_rank: 1728, published: inserted_post.published, - newest_activity_time: inserted_post.published, updated: None, subscribed: None, read: None, saved: None, nsfw: false, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let read_post_listings_with_user = PostQueryBuilder::create(&conn) diff --git a/server/src/db/private_message.rs b/server/src/db/private_message.rs deleted file mode 100644 index cc073b59..00000000 --- a/server/src/db/private_message.rs +++ /dev/null @@ -1,144 +0,0 @@ -use super::*; -use crate::schema::private_message; - -#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)] -#[table_name = "private_message"] -pub struct PrivateMessage { - pub id: i32, - pub creator_id: i32, - pub recipient_id: i32, - pub content: String, - pub deleted: bool, - pub read: bool, - pub published: chrono::NaiveDateTime, - pub updated: Option, -} - -#[derive(Insertable, AsChangeset, Clone)] -#[table_name = "private_message"] -pub struct PrivateMessageForm { - pub creator_id: i32, - pub recipient_id: i32, - pub content: Option, - pub deleted: Option, - pub read: Option, - pub updated: Option, -} - -impl Crud for PrivateMessage { - fn read(conn: &PgConnection, private_message_id: i32) -> Result { - use crate::schema::private_message::dsl::*; - private_message.find(private_message_id).first::(conn) - } - - fn delete(conn: &PgConnection, private_message_id: i32) -> Result { - use crate::schema::private_message::dsl::*; - diesel::delete(private_message.find(private_message_id)).execute(conn) - } - - fn create(conn: &PgConnection, private_message_form: &PrivateMessageForm) -> Result { - use crate::schema::private_message::dsl::*; - insert_into(private_message) - .values(private_message_form) - .get_result::(conn) - } - - fn update( - conn: &PgConnection, - private_message_id: i32, - private_message_form: &PrivateMessageForm, - ) -> Result { - use crate::schema::private_message::dsl::*; - diesel::update(private_message.find(private_message_id)) - .set(private_message_form) - .get_result::(conn) - } -} - -#[cfg(test)] -mod tests { - use super::super::user::*; - use super::*; - #[test] - fn test_crud() { - let conn = establish_unpooled_connection(); - - let creator_form = UserForm { - name: "creator_pm".into(), - fedi_name: "rrf".into(), - preferred_username: None, - password_encrypted: "nope".into(), - email: None, - matrix_user_id: None, - avatar: None, - admin: false, - banned: false, - updated: None, - show_nsfw: false, - theme: "darkly".into(), - default_sort_type: SortType::Hot as i16, - default_listing_type: ListingType::Subscribed as i16, - lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, - }; - - let inserted_creator = User_::create(&conn, &creator_form).unwrap(); - - let recipient_form = UserForm { - name: "recipient_pm".into(), - fedi_name: "rrf".into(), - preferred_username: None, - password_encrypted: "nope".into(), - email: None, - matrix_user_id: None, - avatar: None, - admin: false, - banned: false, - updated: None, - show_nsfw: false, - theme: "darkly".into(), - default_sort_type: SortType::Hot as i16, - default_listing_type: ListingType::Subscribed as i16, - lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, - }; - - let inserted_recipient = User_::create(&conn, &recipient_form).unwrap(); - - let private_message_form = PrivateMessageForm { - content: Some("A test private message".into()), - creator_id: inserted_creator.id, - recipient_id: inserted_recipient.id, - deleted: None, - read: None, - updated: None, - }; - - let inserted_private_message = PrivateMessage::create(&conn, &private_message_form).unwrap(); - - let expected_private_message = PrivateMessage { - id: inserted_private_message.id, - content: "A test private message".into(), - creator_id: inserted_creator.id, - recipient_id: inserted_recipient.id, - deleted: false, - read: false, - updated: None, - published: inserted_private_message.published, - }; - - let read_private_message = PrivateMessage::read(&conn, inserted_private_message.id).unwrap(); - let updated_private_message = - PrivateMessage::update(&conn, inserted_private_message.id, &private_message_form).unwrap(); - let num_deleted = PrivateMessage::delete(&conn, inserted_private_message.id).unwrap(); - User_::delete(&conn, inserted_creator.id).unwrap(); - User_::delete(&conn, inserted_recipient.id).unwrap(); - - assert_eq!(expected_private_message, read_private_message); - assert_eq!(expected_private_message, updated_private_message); - assert_eq!(expected_private_message, inserted_private_message); - assert_eq!(1, num_deleted); - } -} diff --git a/server/src/db/private_message_view.rs b/server/src/db/private_message_view.rs deleted file mode 100644 index e22bef50..00000000 --- a/server/src/db/private_message_view.rs +++ /dev/null @@ -1,140 +0,0 @@ -use super::*; -use diesel::pg::Pg; - -// The faked schema since diesel doesn't do views -table! { - private_message_view (id) { - id -> Int4, - creator_id -> Int4, - recipient_id -> Int4, - content -> Text, - deleted -> Bool, - read -> Bool, - published -> Timestamp, - updated -> Nullable, - creator_name -> Varchar, - creator_avatar -> Nullable, - recipient_name -> Varchar, - recipient_avatar -> Nullable, - } -} - -table! { - private_message_mview (id) { - id -> Int4, - creator_id -> Int4, - recipient_id -> Int4, - content -> Text, - deleted -> Bool, - read -> Bool, - published -> Timestamp, - updated -> Nullable, - creator_name -> Varchar, - creator_avatar -> Nullable, - recipient_name -> Varchar, - recipient_avatar -> Nullable, - } -} - -#[derive( - Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone, -)] -#[table_name = "private_message_view"] -pub struct PrivateMessageView { - pub id: i32, - pub creator_id: i32, - pub recipient_id: i32, - pub content: String, - pub deleted: bool, - pub read: bool, - pub published: chrono::NaiveDateTime, - pub updated: Option, - pub creator_name: String, - pub creator_avatar: Option, - pub recipient_name: String, - pub recipient_avatar: Option, -} - -pub struct PrivateMessageQueryBuilder<'a> { - conn: &'a PgConnection, - query: super::private_message_view::private_message_mview::BoxedQuery<'a, Pg>, - for_recipient_id: i32, - unread_only: bool, - page: Option, - limit: Option, -} - -impl<'a> PrivateMessageQueryBuilder<'a> { - pub fn create(conn: &'a PgConnection, for_recipient_id: i32) -> Self { - use super::private_message_view::private_message_mview::dsl::*; - - let query = private_message_mview.into_boxed(); - - PrivateMessageQueryBuilder { - conn, - query, - for_recipient_id, - unread_only: false, - page: None, - limit: None, - } - } - - pub fn unread_only(mut self, unread_only: bool) -> Self { - self.unread_only = unread_only; - self - } - - pub fn page>(mut self, page: T) -> Self { - self.page = page.get_optional(); - self - } - - pub fn limit>(mut self, limit: T) -> Self { - self.limit = limit.get_optional(); - self - } - - pub fn list(self) -> Result, Error> { - use super::private_message_view::private_message_mview::dsl::*; - - let mut query = self.query.filter(deleted.eq(false)); - - // If its unread, I only want the ones to me - if self.unread_only { - query = query - .filter(read.eq(false)) - .filter(recipient_id.eq(self.for_recipient_id)); - } - // Otherwise, I want the ALL view to show both sent and received - else { - query = query.filter( - recipient_id - .eq(self.for_recipient_id) - .or(creator_id.eq(self.for_recipient_id)), - ) - } - - let (limit, offset) = limit_and_offset(self.page, self.limit); - - query - .limit(limit) - .offset(offset) - .order_by(published.desc()) - .load::(self.conn) - } -} - -impl PrivateMessageView { - pub fn read(conn: &PgConnection, from_private_message_id: i32) -> Result { - use super::private_message_view::private_message_view::dsl::*; - - let mut query = private_message_view.into_boxed(); - - query = query - .filter(id.eq(from_private_message_id)) - .order_by(published.desc()); - - query.first::(conn) - } -} diff --git a/server/src/db/site_view.rs b/server/src/db/site_view.rs index 674a7a6e..40b1265f 100644 --- a/server/src/db/site_view.rs +++ b/server/src/db/site_view.rs @@ -12,7 +12,6 @@ table! { open_registration -> Bool, enable_nsfw -> Bool, creator_name -> Varchar, - creator_avatar -> Nullable, number_of_users -> BigInt, number_of_posts -> BigInt, number_of_comments -> BigInt, @@ -35,7 +34,6 @@ pub struct SiteView { pub open_registration: bool, pub enable_nsfw: bool, pub creator_name: String, - pub creator_avatar: Option, pub number_of_users: i64, pub number_of_posts: i64, pub number_of_comments: i64, diff --git a/server/src/db/user.rs b/server/src/db/user.rs index e7c76965..21407e58 100644 --- a/server/src/db/user.rs +++ b/server/src/db/user.rs @@ -3,7 +3,7 @@ use crate::schema::user_; use crate::schema::user_::dsl::*; use crate::{is_email_regex, Settings}; use bcrypt::{hash, DEFAULT_COST}; -use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation}; +use jsonwebtoken::{decode, encode, Header, TokenData, Validation}; #[derive(Queryable, Identifiable, PartialEq, Debug)] #[table_name = "user_"] @@ -14,7 +14,7 @@ pub struct User_ { pub preferred_username: Option, pub password_encrypted: String, pub email: Option, - pub avatar: Option, + pub icon: Option>, pub admin: bool, pub banned: bool, pub published: chrono::NaiveDateTime, @@ -24,9 +24,6 @@ pub struct User_ { pub default_sort_type: i16, pub default_listing_type: i16, pub lang: String, - pub show_avatars: bool, - pub send_notifications_to_email: bool, - pub matrix_user_id: Option, } #[derive(Insertable, AsChangeset, Clone)] @@ -39,16 +36,12 @@ pub struct UserForm { pub admin: bool, pub banned: bool, pub email: Option, - pub avatar: Option, pub updated: Option, pub show_nsfw: bool, pub theme: String, pub default_sort_type: i16, pub default_listing_type: i16, pub lang: String, - pub show_avatars: bool, - pub send_notifications_to_email: bool, - pub matrix_user_id: Option, } impl Crud for User_ { @@ -81,13 +74,14 @@ impl User_ { pub fn update_password( conn: &PgConnection, user_id: i32, - new_password: &str, + form: &UserForm, ) -> Result { - let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password"); + let mut edited_user = form.clone(); + let password_hash = + hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password"); + edited_user.password_encrypted = password_hash; - diesel::update(user_.find(user_id)) - .set(password_encrypted.eq(password_hash)) - .get_result::(conn) + Self::update(&conn, user_id, &edited_user) } pub fn read_from_name(conn: &PgConnection, from_user_name: String) -> Result { @@ -105,8 +99,6 @@ pub struct Claims { pub default_sort_type: i16, pub default_listing_type: i16, pub lang: String, - pub avatar: Option, - pub show_avatars: bool, } impl Claims { @@ -115,11 +107,7 @@ impl Claims { validate_exp: false, ..Validation::default() }; - decode::( - &jwt, - &DecodingKey::from_secret(Settings::get().jwt_secret.as_ref()), - &v, - ) + decode::(&jwt, Settings::get().jwt_secret.as_ref(), &v) } } @@ -135,13 +123,11 @@ impl User_ { default_sort_type: self.default_sort_type, default_listing_type: self.default_listing_type, lang: self.lang.to_owned(), - avatar: self.avatar.to_owned(), - show_avatars: self.show_avatars.to_owned(), }; encode( &Header::default(), &my_claims, - &EncodingKey::from_secret(Settings::get().jwt_secret.as_ref()), + Settings::get().jwt_secret.as_ref(), ) .unwrap() } @@ -165,8 +151,8 @@ impl User_ { } } - pub fn get_profile_url(&self) -> String { - format!("https://{}/u/{}", Settings::get().hostname, self.name) + pub fn get_user_profile_url(username: &str) -> String { + format!("https://{}/u/{}", Settings::get().hostname, username) } pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result { @@ -182,7 +168,7 @@ mod tests { #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let new_user = UserForm { name: "thommy".into(), @@ -190,8 +176,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -200,8 +184,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -213,8 +195,7 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, + icon: None, admin: false, banned: false, published: inserted_user.published, @@ -224,8 +205,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let read_user = User_::read(&conn, inserted_user.id).unwrap(); diff --git a/server/src/db/user_mention.rs b/server/src/db/user_mention.rs index 0cf25795..7eb4d486 100644 --- a/server/src/db/user_mention.rs +++ b/server/src/db/user_mention.rs @@ -60,7 +60,7 @@ mod tests { use super::*; #[test] fn test_crud() { - let conn = establish_unpooled_connection(); + let conn = establish_connection(); let new_user = UserForm { name: "terrylake".into(), @@ -68,8 +68,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -78,8 +76,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_user = User_::create(&conn, &new_user).unwrap(); @@ -90,8 +86,6 @@ mod tests { preferred_username: None, password_encrypted: "nope".into(), email: None, - matrix_user_id: None, - avatar: None, admin: false, banned: false, updated: None, @@ -100,8 +94,6 @@ mod tests { default_sort_type: SortType::Hot as i16, default_listing_type: ListingType::Subscribed as i16, lang: "browser".into(), - show_avatars: true, - send_notifications_to_email: false, }; let inserted_recipient = User_::create(&conn, &recipient_form).unwrap(); @@ -132,10 +124,6 @@ mod tests { stickied: None, updated: None, nsfw: false, - embed_title: None, - embed_description: None, - embed_html: None, - thumbnail_url: None, }; let inserted_post = Post::create(&conn, &new_post).unwrap(); diff --git a/server/src/db/user_mention_view.rs b/server/src/db/user_mention_view.rs index 8046747e..45541861 100644 --- a/server/src/db/user_mention_view.rs +++ b/server/src/db/user_mention_view.rs @@ -1,3 +1,4 @@ +use super::user_mention_view::user_mention_view::BoxedQuery; use super::*; use diesel::pg::Pg; @@ -16,45 +17,12 @@ table! { updated -> Nullable, deleted -> Bool, community_id -> Int4, - community_name -> Varchar, banned -> Bool, banned_from_community -> Bool, creator_name -> Varchar, - creator_avatar -> Nullable, score -> BigInt, upvotes -> BigInt, downvotes -> BigInt, - hot_rank -> Int4, - user_id -> Nullable, - my_vote -> Nullable, - saved -> Nullable, - recipient_id -> Int4, - } -} - -table! { - user_mention_mview (id) { - id -> Int4, - user_mention_id -> Int4, - creator_id -> Int4, - post_id -> Int4, - parent_id -> Nullable, - content -> Text, - removed -> Bool, - read -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - community_id -> Int4, - community_name -> Varchar, - banned -> Bool, - banned_from_community -> Bool, - creator_name -> Varchar, - creator_avatar -> Nullable, - score -> BigInt, - upvotes -> BigInt, - downvotes -> BigInt, - hot_rank -> Int4, user_id -> Nullable, my_vote -> Nullable, saved -> Nullable, @@ -79,15 +47,12 @@ pub struct UserMentionView { pub updated: Option, pub deleted: bool, pub community_id: i32, - pub community_name: String, pub banned: bool, pub banned_from_community: bool, pub creator_name: String, - pub creator_avatar: Option, pub score: i64, pub upvotes: i64, pub downvotes: i64, - pub hot_rank: i32, pub user_id: Option, pub my_vote: Option, pub saved: Option, @@ -96,7 +61,7 @@ pub struct UserMentionView { pub struct UserMentionQueryBuilder<'a> { conn: &'a PgConnection, - query: super::user_mention_view::user_mention_mview::BoxedQuery<'a, Pg>, + query: BoxedQuery<'a, Pg>, for_user_id: i32, sort: &'a SortType, unread_only: bool, @@ -106,14 +71,14 @@ pub struct UserMentionQueryBuilder<'a> { impl<'a> UserMentionQueryBuilder<'a> { pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self { - use super::user_mention_view::user_mention_mview::dsl::*; + use super::user_mention_view::user_mention_view::dsl::*; - let query = user_mention_mview.into_boxed(); + let query = user_mention_view.into_boxed(); UserMentionQueryBuilder { conn, query, - for_user_id, + for_user_id: for_user_id, sort: &SortType::New, unread_only: false, page: None, @@ -142,7 +107,7 @@ impl<'a> UserMentionQueryBuilder<'a> { } pub fn list(self) -> Result, Error> { - use super::user_mention_view::user_mention_mview::dsl::*; + use super::user_mention_view::user_mention_view::dsl::*; let mut query = self.query; @@ -155,9 +120,7 @@ impl<'a> UserMentionQueryBuilder<'a> { .filter(recipient_id.eq(self.for_user_id)); query = match self.sort { - SortType::Hot => query - .order_by(hot_rank.desc()) - .then_order_by(published.desc()), + // SortType::Hot => query.order_by(hot_rank.desc()), SortType::New => query.order_by(published.desc()), SortType::TopAll => query.order_by(score.desc()), SortType::TopYear => query @@ -172,7 +135,7 @@ impl<'a> UserMentionQueryBuilder<'a> { SortType::TopDay => query .filter(published.gt(now - 1.days())) .order_by(score.desc()), - // _ => query.order_by(published.desc()), + _ => query.order_by(published.desc()), }; let (limit, offset) = limit_and_offset(self.page, self.limit); diff --git a/server/src/db/user_view.rs b/server/src/db/user_view.rs index 2274ecbd..0ed95eef 100644 --- a/server/src/db/user_view.rs +++ b/server/src/db/user_view.rs @@ -1,4 +1,4 @@ -use super::user_view::user_mview::BoxedQuery; +use super::user_view::user_view::BoxedQuery; use super::*; use diesel::pg::Pg; @@ -6,34 +6,9 @@ table! { user_view (id) { id -> Int4, name -> Varchar, - avatar -> Nullable, - email -> Nullable, - matrix_user_id -> Nullable, fedi_name -> Varchar, admin -> Bool, banned -> Bool, - show_avatars -> Bool, - send_notifications_to_email -> Bool, - published -> Timestamp, - number_of_posts -> BigInt, - post_score -> BigInt, - number_of_comments -> BigInt, - comment_score -> BigInt, - } -} - -table! { - user_mview (id) { - id -> Int4, - name -> Varchar, - avatar -> Nullable, - email -> Nullable, - matrix_user_id -> Nullable, - fedi_name -> Varchar, - admin -> Bool, - banned -> Bool, - show_avatars -> Bool, - send_notifications_to_email -> Bool, published -> Timestamp, number_of_posts -> BigInt, post_score -> BigInt, @@ -49,14 +24,9 @@ table! { pub struct UserView { pub id: i32, pub name: String, - pub avatar: Option, - pub email: Option, - pub matrix_user_id: Option, pub fedi_name: String, pub admin: bool, pub banned: bool, - pub show_avatars: bool, - pub send_notifications_to_email: bool, pub published: chrono::NaiveDateTime, pub number_of_posts: i64, pub post_score: i64, @@ -74,9 +44,9 @@ pub struct UserQueryBuilder<'a> { impl<'a> UserQueryBuilder<'a> { pub fn create(conn: &'a PgConnection) -> Self { - use super::user_view::user_mview::dsl::*; + use super::user_view::user_view::dsl::*; - let query = user_mview.into_boxed(); + let query = user_view.into_boxed(); UserQueryBuilder { conn, @@ -93,7 +63,7 @@ impl<'a> UserQueryBuilder<'a> { } pub fn search_term>(mut self, search_term: T) -> Self { - use super::user_view::user_mview::dsl::*; + use super::user_view::user_view::dsl::*; if let Some(search_term) = search_term.get_optional() { self.query = self.query.filter(name.ilike(fuzzy_search(&search_term))); } @@ -111,7 +81,7 @@ impl<'a> UserQueryBuilder<'a> { } pub fn list(self) -> Result, Error> { - use super::user_view::user_mview::dsl::*; + use super::user_view::user_view::dsl::*; let mut query = self.query; @@ -144,17 +114,18 @@ impl<'a> UserQueryBuilder<'a> { impl UserView { pub fn read(conn: &PgConnection, from_user_id: i32) -> Result { - use super::user_view::user_mview::dsl::*; - user_mview.find(from_user_id).first::(conn) + use super::user_view::user_view::dsl::*; + + user_view.find(from_user_id).first::(conn) } pub fn admins(conn: &PgConnection) -> Result, Error> { - use super::user_view::user_mview::dsl::*; - user_mview.filter(admin.eq(true)).load::(conn) + use super::user_view::user_view::dsl::*; + user_view.filter(admin.eq(true)).load::(conn) } pub fn banned(conn: &PgConnection) -> Result, Error> { - use super::user_view::user_mview::dsl::*; - user_mview.filter(banned.eq(true)).load::(conn) + use super::user_view::user_view::dsl::*; + user_view.filter(banned.eq(true)).load::(conn) } } diff --git a/server/src/routes/feeds.rs b/server/src/feeds.rs similarity index 50% rename from server/src/routes/feeds.rs rename to server/src/feeds.rs index 815953c5..c735688b 100644 --- a/server/src/routes/feeds.rs +++ b/server/src/feeds.rs @@ -1,11 +1,21 @@ +extern crate rss; + use super::*; use crate::db::comment_view::{ReplyQueryBuilder, ReplyView}; use crate::db::community::Community; use crate::db::post_view::{PostQueryBuilder, PostView}; use crate::db::site_view::SiteView; -use crate::db::user::{Claims, User_}; +use crate::db::user::User_; use crate::db::user_mention_view::{UserMentionQueryBuilder, UserMentionView}; -use crate::db::{ListingType, SortType}; +use crate::db::{establish_connection, ListingType, SortType}; +use crate::Settings; +use actix_web::body::Body; +use actix_web::{web, HttpResponse, Result}; +use failure::Error; +use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder}; +use serde::Deserialize; +use std::str::FromStr; +use strum::ParseError; #[derive(Deserialize)] pub struct Params { @@ -19,31 +29,61 @@ enum RequestType { Inbox, } -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)); -} +pub fn get_all_feed(info: web::Query) -> HttpResponse { + let sort_type = match get_sort_type(info) { + Ok(sort_type) => sort_type, + Err(_) => return HttpResponse::BadRequest().finish(), + }; -async fn get_all_feed( - info: web::Query, - db: web::Data>>, -) -> Result { - let res = web::block(move || { - let conn = db.get()?; - get_feed_all_data(&conn, &get_sort_type(info)?) - }) - .await - .map(|rss| { - HttpResponse::Ok() + let feed_result = get_feed_all_data(&sort_type); + + match feed_result { + Ok(rss) => HttpResponse::Ok() .content_type("application/rss+xml") - .body(rss) - }) - .map_err(ErrorBadRequest)?; - Ok(res) + .body(rss), + Err(_) => HttpResponse::NotFound().finish(), + } } -fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result { +pub fn get_feed(path: web::Path<(String, String)>, info: web::Query) -> HttpResponse { + let sort_type = match get_sort_type(info) { + Ok(sort_type) => sort_type, + Err(_) => return HttpResponse::BadRequest().finish(), + }; + + let request_type = match path.0.as_ref() { + "u" => RequestType::User, + "c" => RequestType::Community, + "front" => RequestType::Front, + "inbox" => RequestType::Inbox, + _ => return HttpResponse::NotFound().finish(), + }; + + let param = path.1.to_owned(); + + let feed_result = match request_type { + RequestType::User => get_feed_user(&sort_type, param), + RequestType::Community => get_feed_community(&sort_type, param), + RequestType::Front => get_feed_front(&sort_type, param), + RequestType::Inbox => get_feed_inbox(param), + }; + + match feed_result { + Ok(rss) => HttpResponse::Ok() + .content_type("application/rss+xml") + .body(rss), + Err(_) => HttpResponse::NotFound().finish(), + } +} + +fn get_sort_type(info: web::Query) -> Result { + let sort_query = info.sort.to_owned().unwrap_or(SortType::Hot.to_string()); + SortType::from_str(&sort_query) +} + +fn get_feed_all_data(sort_type: &SortType) -> Result { + let conn = establish_connection(); + let site_view = SiteView::read(&conn)?; let posts = PostQueryBuilder::create(&conn) @@ -66,60 +106,12 @@ fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result, - info: web::Query, - db: web::Data>>, -) -> Result { - let res = web::block(move || { - let conn = db.get()?; +fn get_feed_user(sort_type: &SortType, user_name: String) -> Result { + let conn = establish_connection(); - let sort_type = get_sort_type(info)?; - - let request_type = match path.0.as_ref() { - "u" => RequestType::User, - "c" => RequestType::Community, - "front" => RequestType::Front, - "inbox" => RequestType::Inbox, - _ => return Err(format_err!("wrong_type")), - }; - - let param = path.1.to_owned(); - - match request_type { - RequestType::User => get_feed_user(&conn, &sort_type, param), - RequestType::Community => get_feed_community(&conn, &sort_type, param), - RequestType::Front => get_feed_front(&conn, &sort_type, param), - RequestType::Inbox => get_feed_inbox(&conn, param), - } - }) - .await - .map(|builder| builder.build().unwrap().to_string()) - .map(|rss| { - HttpResponse::Ok() - .content_type("application/rss+xml") - .body(rss) - }) - .map_err(ErrorBadRequest)?; - Ok(res) -} - -fn get_sort_type(info: web::Query) -> Result { - let sort_query = info - .sort - .to_owned() - .unwrap_or_else(|| SortType::Hot.to_string()); - SortType::from_str(&sort_query) -} - -fn get_feed_user( - conn: &PgConnection, - sort_type: &SortType, - user_name: String, -) -> Result { let site_view = SiteView::read(&conn)?; let user = User_::find_by_username(&conn, &user_name)?; - let user_url = user.get_profile_url(); + let user_url = User_::get_user_profile_url(&user_name); let posts = PostQueryBuilder::create(&conn) .listing_type(ListingType::All) @@ -135,17 +127,15 @@ fn get_feed_user( .link(user_url) .items(items); - Ok(channel_builder) + Ok(channel_builder.build().unwrap().to_string()) } -fn get_feed_community( - conn: &PgConnection, - sort_type: &SortType, - community_name: String, -) -> Result { +fn get_feed_community(sort_type: &SortType, community_name: String) -> Result { + let conn = establish_connection(); + let site_view = SiteView::read(&conn)?; let community = Community::read_from_name(&conn, community_name)?; - let community_url = community.get_url(); + let community_url = Community::get_community_url(&community.name); let posts = PostQueryBuilder::create(&conn) .listing_type(ListingType::All) @@ -165,16 +155,14 @@ fn get_feed_community( channel_builder.description(&community_desc); } - Ok(channel_builder) + Ok(channel_builder.build().unwrap().to_string()) } -fn get_feed_front( - conn: &PgConnection, - sort_type: &SortType, - jwt: String, -) -> Result { +fn get_feed_front(sort_type: &SortType, jwt: String) -> Result { + let conn = establish_connection(); + let site_view = SiteView::read(&conn)?; - let user_id = Claims::decode(&jwt)?.claims.id; + let user_id = db::user::Claims::decode(&jwt)?.claims.id; let posts = PostQueryBuilder::create(&conn) .listing_type(ListingType::Subscribed) @@ -194,12 +182,14 @@ fn get_feed_front( channel_builder.description(&site_desc); } - Ok(channel_builder) + Ok(channel_builder.build().unwrap().to_string()) } -fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result { +fn get_feed_inbox(jwt: String) -> Result { + let conn = establish_connection(); + let site_view = SiteView::read(&conn)?; - let user_id = Claims::decode(&jwt)?.claims.id; + let user_id = db::user::Claims::decode(&jwt)?.claims.id; let sort = SortType::New; @@ -223,61 +213,86 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result, mentions: Vec, ) -> Vec { - 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(); + let mut items: Vec = Vec::new(); - 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(); + for r in replies { + let mut i = ItemBuilder::default(); - reply_items.append(&mut mention_items); - reply_items -} + i.title(format!("Reply from {}", r.creator_name)); -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() + 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 create_post_items(posts: Vec) -> Vec { @@ -324,8 +339,9 @@ fn create_post_items(posts: Vec) -> Vec { i.link(url); } - // TODO add images - let mut description = format!("submitted by {} to {}
{} points | {} comments", + // TODO find a markdown to html parser here, do images, etc + let mut description = format!(" + submitted by {} to {}
{} points | {} comments", author_url, p.creator_name, community_url, @@ -335,8 +351,7 @@ fn create_post_items(posts: Vec) -> Vec { p.number_of_comments); if let Some(body) = p.body { - let html = markdown_to_html(&body); - description.push_str(&html); + description.push_str(&format!("

{}", body)); } i.description(description); diff --git a/server/src/lib.rs b/server/src/lib.rs index 23d6a87a..1eef49e2 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -11,50 +11,38 @@ pub extern crate actix; pub extern crate actix_web; pub extern crate bcrypt; pub extern crate chrono; -pub extern crate comrak; +pub extern crate crypto; pub extern crate dotenv; pub extern crate jsonwebtoken; pub extern crate lettre; pub extern crate lettre_email; 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; pub extern crate strum; pub mod api; pub mod apub; pub mod db; -pub mod rate_limit; -pub mod routes; +pub mod feeds; +pub mod nodeinfo; pub mod schema; pub mod settings; pub mod version; +pub mod webfinger; pub mod websocket; -use actix_web::dev::ConnectionInfo; +use crate::settings::Settings; use chrono::{DateTime, NaiveDateTime, Utc}; use lettre::smtp::authentication::{Credentials, Mechanism}; use lettre::smtp::extension::ClientId; use lettre::smtp::ConnectionReuseParameters; -use lettre::{ClientSecurity, SmtpClient, Transport}; +use lettre::{SmtpClient, Transport}; use lettre_email::Email; -use log::error; -use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use regex::{Regex, RegexBuilder}; -use serde::Deserialize; - -use crate::settings::Settings; - -pub type ConnectionId = usize; -pub type PostId = i32; -pub type CommunityId = i32; -pub type UserId = i32; -pub type IPAddr = String; +use regex::Regex; pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime { DateTime::::from_utc(ndt, Utc) @@ -72,43 +60,12 @@ pub fn is_email_regex(test: &str) -> bool { EMAIL_REGEX.is_match(test) } -pub fn is_image_content_type(test: &str) -> Result<(), failure::Error> { - if attohttpc::get(test) - .send()? - .headers() - .get("Content-Type") - .ok_or_else(|| format_err!("No Content-Type header"))? - .to_str()? - .starts_with("image/") - { - Ok(()) - } else { - Err(format_err!("Not an image type.")) - } -} - pub fn remove_slurs(test: &str) -> String { SLUR_REGEX.replace_all(test, "*removed*").to_string() } -pub fn slur_check(test: &str) -> Result<(), Vec<&str>> { - let mut matches: Vec<&str> = SLUR_REGEX.find_iter(test).map(|mat| mat.as_str()).collect(); - - // Unique - matches.sort_unstable(); - matches.dedup(); - - if matches.is_empty() { - Ok(()) - } else { - Err(matches) - } -} - -pub fn slurs_vec_to_str(slurs: Vec<&str>) -> String { - let start = "No slurs - "; - let combined = &slurs.join(", "); - [start, combined].concat() +pub fn has_slurs(test: &str) -> bool { + SLUR_REGEX.is_match(test) } pub fn extract_usernames(test: &str) -> Vec<&str> { @@ -135,158 +92,45 @@ pub fn send_email( to_username: &str, html: &str, ) -> Result<(), String> { - let email_config = Settings::get().email.ok_or("no_email_setup")?; + let email_config = Settings::get().email.as_ref().ok_or("no_email_setup")?; let email = Email::builder() .to((to_email, to_username)) - .from(email_config.smtp_from_address.to_owned()) + .from(( + email_config.smtp_login.to_owned(), + email_config.smtp_from_address.to_owned(), + )) .subject(subject) .html(html) .build() .unwrap(); - let mailer = if email_config.use_tls { - SmtpClient::new_simple(&email_config.smtp_server).unwrap() - } else { - SmtpClient::new(&email_config.smtp_server, ClientSecurity::None).unwrap() - } - .hello_name(ClientId::Domain(Settings::get().hostname)) - .smtp_utf8(true) - .authentication_mechanism(Mechanism::Plain) - .connection_reuse(ConnectionReuseParameters::ReuseUnlimited); - let mailer = if let (Some(login), Some(password)) = - (&email_config.smtp_login, &email_config.smtp_password) - { - mailer.credentials(Credentials::new(login.to_owned(), password.to_owned())) - } else { - mailer - }; + let mut mailer = SmtpClient::new_simple(&email_config.smtp_server) + .unwrap() + .hello_name(ClientId::Domain("localhost".to_string())) + .credentials(Credentials::new( + email_config.smtp_login.to_owned(), + email_config.smtp_password.to_owned(), + )) + .smtp_utf8(true) + .authentication_mechanism(Mechanism::Plain) + .connection_reuse(ConnectionReuseParameters::ReuseUnlimited) + .transport(); - let mut transport = mailer.transport(); - let result = transport.send(email.into()); - transport.close(); + let result = mailer.send(email.into()); match result { Ok(_) => Ok(()), - Err(e) => Err(e.to_string()), + Err(_) => Err("no_email_setup".to_string()), } } -#[derive(Deserialize, Debug)] -pub struct IframelyResponse { - title: Option, - description: Option, - thumbnail_url: Option, - html: Option, -} - -pub fn fetch_iframely(url: &str) -> Result { - let fetch_url = format!("http://iframely/oembed?url={}", url); - let text: String = attohttpc::get(&fetch_url).send()?.text()?; - let res: IframelyResponse = serde_json::from_str(&text)?; - Ok(res) -} - -#[derive(Deserialize, Debug)] -pub struct PictshareResponse { - status: String, - url: String, -} - -pub fn fetch_pictshare(image_url: &str) -> Result { - is_image_content_type(image_url)?; - - let fetch_url = format!( - "http://pictshare/api/geturl.php?url={}", - utf8_percent_encode(image_url, NON_ALPHANUMERIC) - ); - let text = attohttpc::get(&fetch_url).send()?.text()?; - let res: PictshareResponse = serde_json::from_str(&text)?; - Ok(res) -} - -fn fetch_iframely_and_pictshare_data( - url: Option, -) -> ( - Option, - Option, - Option, - Option, -) { - match &url { - Some(url) => { - // Fetch iframely data - let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = - match fetch_iframely(url) { - Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), - Err(e) => { - error!("iframely err: {}", e); - (None, None, None, None) - } - }; - - // Fetch pictshare thumbnail - let pictshare_thumbnail = match iframely_thumbnail_url { - Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) { - Ok(res) => Some(res.url), - Err(e) => { - error!("pictshare err: {}", e); - None - } - }, - // Try to generate a small thumbnail if iframely is not supported - None => match fetch_pictshare(&url) { - Ok(res) => Some(res.url), - Err(e) => { - error!("pictshare err: {}", e); - None - } - }, - }; - - ( - iframely_title, - iframely_description, - iframely_html, - pictshare_thumbnail, - ) - } - None => (None, None, None, None), - } -} - -pub fn markdown_to_html(text: &str) -> String { - comrak::markdown_to_html(text, &comrak::ComrakOptions::default()) -} - -pub fn get_ip(conn_info: &ConnectionInfo) -> String { - conn_info - .remote() - .unwrap_or("127.0.0.1:12345") - .split(':') - .next() - .unwrap_or("127.0.0.1") - .to_string() -} - -pub fn is_valid_username(name: &str) -> bool { - VALID_USERNAME_REGEX.is_match(name) -} - #[cfg(test)] mod tests { - use crate::{ - extract_usernames, is_email_regex, is_image_content_type, is_valid_username, remove_slurs, - slur_check, slurs_vec_to_str, - }; - + use crate::{extract_usernames, has_slurs, is_email_regex, remove_slurs, Settings}; #[test] - fn test_image() { - assert!(is_image_content_type("https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").is_ok()); - assert!(is_image_content_type( - "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20" - ) - .is_err()); + fn test_api() { + assert_eq!(Settings::get().api_endpoint(), "rrr/api/v1"); } #[test] @@ -295,41 +139,18 @@ mod tests { assert!(!is_email_regex("nada_neutho")); } - #[test] - fn test_valid_register_username() { - assert!(is_valid_username("Hello_98")); - assert!(is_valid_username("ten")); - assert!(!is_valid_username("Hello-98")); - assert!(!is_valid_username("a")); - assert!(!is_valid_username("")); - } - #[test] fn test_slur_filter() { let test = - "coons test dindu ladyboy tranny retardeds. Capitalized Niggerz. This is a bunch of other safe text."; + "coons test dindu ladyboy tranny retardeds. This is a bunch of other safe text.".to_string(); let slur_free = "No slurs here"; assert_eq!( remove_slurs(&test), - "*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text." + "*removed* test *removed* *removed* *removed* *removed*. This is a bunch of other safe text." .to_string() ); - - let has_slurs_vec = vec![ - "Niggerz", - "coons", - "dindu", - "ladyboy", - "retardeds", - "tranny", - ]; - let has_slurs_err_str = "No slurs - Niggerz, coons, dindu, ladyboy, retardeds, tranny"; - - assert_eq!(slur_check(test), Err(has_slurs_vec)); - assert_eq!(slur_check(slur_free), Ok(())); - if let Err(slur_vec) = slur_check(test) { - assert_eq!(&slurs_vec_to_str(slur_vec), has_slurs_err_str); - } + assert!(has_slurs(&test)); + assert!(!has_slurs(slur_free)); } #[test] @@ -339,21 +160,6 @@ mod tests { assert_eq!(usernames, expected); } - // These helped with testing - // #[test] - // fn test_iframely() { - // let res = fetch_iframely("https://www.redspark.nu/?p=15341"); - // assert!(res.is_ok()); - // } - - // #[test] - // fn test_pictshare() { - // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg"); - // assert!(res.is_ok()); - // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu"); - // assert!(res_other.is_err()); - // } - // #[test] // fn test_send_email() { // let result = send_email("not a subject", "test_email@gmail.com", "ur user", "

HI there

"); @@ -363,7 +169,6 @@ mod tests { lazy_static! { static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap(); - static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|nig(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap(); + static ref SLUR_REGEX: Regex = Regex::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|nig(\b|g?(a|er)?s?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").unwrap(); static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").unwrap(); - static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap(); } diff --git a/server/src/main.rs b/server/src/main.rs index 3f91965e..9d1ee4e1 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,114 +1,267 @@ extern crate lemmy_server; #[macro_use] extern crate diesel_migrations; -#[macro_use] -pub extern crate lazy_static; -use crate::lemmy_server::actix_web::dev::Service; use actix::prelude::*; -use actix_web::body::Body; -use actix_web::dev::{ServiceRequest, ServiceResponse}; -use actix_web::http::header::CONTENT_TYPE; -use actix_web::http::{header::CACHE_CONTROL, HeaderValue}; +use actix_files::NamedFile; use actix_web::*; -use diesel::r2d2::{ConnectionManager, Pool}; -use diesel::PgConnection; -use lemmy_server::{ - rate_limit::{rate_limiter::RateLimiter, RateLimit}, - routes::{api, federation, feeds, index, nodeinfo, webfinger}, - settings::Settings, - websocket::server::*, -}; -use regex::Regex; -use std::{io, sync::Arc}; -use tokio::sync::Mutex; - -lazy_static! { - static ref CACHE_CONTROL_REGEX: Regex = - Regex::new("^((text|image)/.+|application/javascript)$").unwrap(); - // static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 365 * 24 * 60 * 60); - // Test out 1 hour here, this is breaking some things - static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 60 * 60); -} +use actix_web_actors::ws; +use lemmy_server::db::establish_connection; +use lemmy_server::feeds; +use lemmy_server::nodeinfo; +use lemmy_server::settings::Settings; +use lemmy_server::webfinger; +use lemmy_server::websocket::server::*; +use std::env; +use std::time::{Duration, Instant}; embed_migrations!(); -#[actix_rt::main] -async fn main() -> io::Result<()> { - env_logger::init(); - let settings = Settings::get(); +/// How often heartbeat pings are sent +const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); +/// How long before lack of client response causes a timeout +const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); - // Set up the r2d2 connection pool - let manager = ConnectionManager::::new(&settings.get_database_url()); - let pool = Pool::builder() - .max_size(settings.database.pool_size) - .build(manager) - .unwrap_or_else(|_| panic!("Error connecting to {}", settings.get_database_url())); +/// Entry point for our route +fn chat_route( + req: HttpRequest, + stream: web::Payload, + chat_server: web::Data>, +) -> Result { + ws::start( + WSSession { + cs_addr: chat_server.get_ref().to_owned(), + id: 0, + hb: Instant::now(), + ip: req + .connection_info() + .remote() + .unwrap_or("127.0.0.1:12345") + .split(":") + .next() + .unwrap_or("127.0.0.1") + .to_string(), + }, + &req, + stream, + ) +} + +struct WSSession { + cs_addr: Addr, + /// unique session id + id: usize, + ip: String, + /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT), + /// otherwise we drop connection. + hb: Instant, +} + +impl Actor for WSSession { + type Context = ws::WebsocketContext; + + /// Method is called on actor start. + /// We register ws session with ChatServer + fn started(&mut self, ctx: &mut Self::Context) { + // we'll start heartbeat process on session start. + self.hb(ctx); + + // register self in chat server. `AsyncContext::wait` register + // future within context, but context waits until this future resolves + // before processing any other events. + // across all routes within application + let addr = ctx.address(); + self + .cs_addr + .send(Connect { + addr: addr.recipient(), + ip: self.ip.to_owned(), + }) + .into_actor(self) + .then(|res, act, ctx| { + match res { + Ok(res) => act.id = res, + // something is wrong with chat server + _ => ctx.stop(), + } + fut::ok(()) + }) + .wait(ctx); + } + + fn stopping(&mut self, _ctx: &mut Self::Context) -> Running { + // notify chat server + self.cs_addr.do_send(Disconnect { + id: self.id, + ip: self.ip.to_owned(), + }); + Running::Stop + } +} + +/// Handle messages from chat server, we simply send it to peer websocket +/// These are room messages, IE sent to others in the room +impl Handler for WSSession { + type Result = (); + + fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) { + // println!("id: {} msg: {}", self.id, msg.0); + ctx.text(msg.0); + } +} + +/// WebSocket message handler +impl StreamHandler for WSSession { + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + // println!("WEBSOCKET MESSAGE: {:?} from id: {}", msg, self.id); + match msg { + ws::Message::Ping(msg) => { + self.hb = Instant::now(); + ctx.pong(&msg); + } + ws::Message::Pong(_) => { + self.hb = Instant::now(); + } + ws::Message::Text(text) => { + let m = text.trim().to_owned(); + println!("WEBSOCKET MESSAGE: {:?} from id: {}", &m, self.id); + + self + .cs_addr + .send(StandardMessage { + id: self.id, + msg: m, + }) + .into_actor(self) + .then(|res, _, ctx| { + match res { + Ok(res) => ctx.text(res), + Err(e) => { + eprintln!("{}", &e); + } + } + fut::ok(()) + }) + .wait(ctx); + } + ws::Message::Binary(_bin) => println!("Unexpected binary"), + ws::Message::Close(_) => { + ctx.stop(); + } + _ => {} + } + } +} + +impl WSSession { + /// helper method that sends ping to client every second. + /// + /// also this method checks heartbeats from client + fn hb(&self, ctx: &mut ws::WebsocketContext) { + ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { + // check client heartbeats + if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { + // heartbeat timed out + println!("Websocket Client heartbeat failed, disconnecting!"); + + // notify chat server + act.cs_addr.do_send(Disconnect { + id: act.id, + ip: act.ip.to_owned(), + }); + + // stop actor + ctx.stop(); + + // don't try to send a ping + return; + } + + ctx.ping(""); + }); + } +} + +fn main() { + let _ = env_logger::init(); + let sys = actix::System::new("lemmy"); // Run the migrations from code - let conn = pool.get().unwrap(); + let conn = establish_connection(); embedded_migrations::run(&conn).unwrap(); - // Set up the rate limiter - let rate_limiter = RateLimit { - rate_limiter: Arc::new(Mutex::new(RateLimiter::default())), - }; + // Start chat server actor in separate thread + let server = ChatServer::default().start(); - // Set up websocket server - let server = ChatServer::startup(pool.clone(), rate_limiter.clone()).start(); - - println!( - "Starting http server at {}:{}", - settings.bind, settings.port - ); + let settings = Settings::get(); // Create Http server with websocket support HttpServer::new(move || { - let settings = Settings::get(); - let rate_limiter = rate_limiter.clone(); App::new() - .wrap_fn(add_cache_headers) - .wrap(middleware::Logger::default()) - .data(pool.clone()) .data(server.clone()) - // The routes - .configure(move |cfg| api::config(cfg, &rate_limiter)) - .configure(federation::config) - .configure(feeds::config) - .configure(index::config) - .configure(nodeinfo::config) - .configure(webfinger::config) - // static files - .service(actix_files::Files::new( - "/static", - settings.front_end_dir.to_owned(), - )) - .service(actix_files::Files::new( - "/docs", - settings.front_end_dir + "/documentation", - )) + // Front end routes + .service(actix_files::Files::new("/static", front_end_dir())) + .route("/", web::get().to(index)) + .route( + "/home/type/{type}/sort/{sort}/page/{page}", + web::get().to(index), + ) + .route("/login", web::get().to(index)) + .route("/create_post", web::get().to(index)) + .route("/create_community", web::get().to(index)) + .route("/communities/page/{page}", web::get().to(index)) + .route("/communities", web::get().to(index)) + .route("/post/{id}/comment/{id2}", web::get().to(index)) + .route("/post/{id}", web::get().to(index)) + .route("/c/{name}/sort/{sort}/page/{page}", web::get().to(index)) + .route("/c/{name}", web::get().to(index)) + .route("/community/{id}", web::get().to(index)) + .route( + "/u/{username}/view/{view}/sort/{sort}/page/{page}", + web::get().to(index), + ) + .route("/u/{username}", web::get().to(index)) + .route("/user/{id}", web::get().to(index)) + .route("/inbox", web::get().to(index)) + .route("/modlog/community/{community_id}", web::get().to(index)) + .route("/modlog", web::get().to(index)) + .route("/setup", web::get().to(index)) + .route( + "/search/q/{q}/type/{type}/sort/{sort}/page/{page}", + web::get().to(index), + ) + .route("/search", web::get().to(index)) + .route("/sponsors", web::get().to(index)) + .route("/password_change/{token}", web::get().to(index)) + // Websocket + .service(web::resource("/api/v1/ws").to(chat_route)) + // NodeInfo + .route("/nodeinfo/2.0.json", web::get().to(nodeinfo::node_info)) + .route( + "/.well-known/nodeinfo", + web::get().to(nodeinfo::node_info_well_known), + ) + // RSS + .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed)) + .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)) + // Federation + .route( + ".well-known/webfinger", + web::get().to(webfinger::get_webfinger_response), + ) }) - .bind((settings.bind, settings.port))? - .run() - .await + .bind((settings.bind, settings.port)) + .unwrap() + .start(); + + println!("Started http server at {}:{}", settings.bind, settings.port); + let _ = sys.run(); } -fn add_cache_headers( - req: ServiceRequest, - srv: &mut S, -) -> impl Future> -where - S: Service, Error = Error>, -{ - let fut = srv.call(req); - async move { - let mut res = fut.await?; - if let Some(content_type) = res.headers().get(CONTENT_TYPE) { - if CACHE_CONTROL_REGEX.is_match(content_type.to_str().unwrap()) { - let header_val = HeaderValue::from_static(&CACHE_CONTROL_VALUE); - res.headers_mut().insert(CACHE_CONTROL, header_val); - } - } - Ok(res) - } +fn index() -> Result { + Ok(NamedFile::open(front_end_dir() + "/index.html")?) +} + +fn front_end_dir() -> String { + env::var("LEMMY_FRONT_END_DIR").unwrap_or("../ui/dist".to_string()) } diff --git a/server/src/nodeinfo.rs b/server/src/nodeinfo.rs new file mode 100644 index 00000000..f8135c7d --- /dev/null +++ b/server/src/nodeinfo.rs @@ -0,0 +1,47 @@ +use crate::db::establish_connection; +use crate::db::site_view::SiteView; +use crate::version; +use crate::Settings; +use actix_web::body::Body; +use actix_web::HttpResponse; +use serde_json::json; + +pub fn node_info_well_known() -> HttpResponse { + let json = json!({ + "links": { + "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0", + "href": format!("https://{}/nodeinfo/2.0.json", Settings::get().hostname), + } + }); + + return HttpResponse::Ok() + .content_type("application/json") + .body(json.to_string()); +} + +pub fn node_info() -> HttpResponse { + let conn = establish_connection(); + let site_view = match SiteView::read(&conn) { + Ok(site_view) => site_view, + Err(_e) => return HttpResponse::InternalServerError().finish(), + }; + let json = json!({ + "version": "2.0", + "software": { + "name": "lemmy", + "version": version::VERSION, + }, + "protocols": [], + "usage": { + "users": { + "total": site_view.number_of_users + }, + "localPosts": site_view.number_of_posts, + "localComments": site_view.number_of_comments, + "openRegistrations": true, + } + }); + return HttpResponse::Ok() + .content_type("application/json") + .body(json.to_string()); +} diff --git a/server/src/rate_limit/mod.rs b/server/src/rate_limit/mod.rs deleted file mode 100644 index 8d698b78..00000000 --- a/server/src/rate_limit/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -pub mod rate_limiter; - -use super::{IPAddr, Settings}; -use crate::api::APIError; -use crate::get_ip; -use crate::settings::RateLimitConfig; -use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; -use failure::Error; -use futures::future::{ok, Ready}; -use log::debug; -use rate_limiter::{RateLimitType, RateLimiter}; -use std::collections::HashMap; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::SystemTime; -use strum::IntoEnumIterator; -use tokio::sync::Mutex; - -#[derive(Debug, Clone)] -pub struct RateLimit { - pub rate_limiter: Arc>, -} - -#[derive(Debug, Clone)] -pub struct RateLimited { - rate_limiter: Arc>, - type_: RateLimitType, -} - -pub struct RateLimitedMiddleware { - rate_limited: RateLimited, - service: S, -} - -impl RateLimit { - pub fn message(&self) -> RateLimited { - self.kind(RateLimitType::Message) - } - - pub fn post(&self) -> RateLimited { - self.kind(RateLimitType::Post) - } - - pub fn register(&self) -> RateLimited { - self.kind(RateLimitType::Register) - } - - fn kind(&self, type_: RateLimitType) -> RateLimited { - RateLimited { - rate_limiter: self.rate_limiter.clone(), - type_, - } - } -} - -impl RateLimited { - pub async fn wrap( - self, - ip_addr: String, - fut: impl Future>, - ) -> Result - where - E: From, - { - let rate_limit: RateLimitConfig = actix_web::web::block(move || { - // needs to be in a web::block because the RwLock in settings is from stdlib - Ok(Settings::get().rate_limit) as Result<_, failure::Error> - }) - .await - .map_err(|e| match e { - actix_web::error::BlockingError::Error(e) => e, - _ => APIError::err("Operation canceled").into(), - })?; - - // before - { - let mut limiter = self.rate_limiter.lock().await; - - match self.type_ { - RateLimitType::Message => { - limiter.check_rate_limit_full( - self.type_, - &ip_addr, - rate_limit.message, - rate_limit.message_per_second, - false, - )?; - - return fut.await; - } - RateLimitType::Post => { - limiter.check_rate_limit_full( - self.type_, - &ip_addr, - rate_limit.post, - rate_limit.post_per_second, - true, - )?; - } - RateLimitType::Register => { - limiter.check_rate_limit_full( - self.type_, - &ip_addr, - rate_limit.register, - rate_limit.register_per_second, - true, - )?; - } - }; - } - - let res = fut.await; - - // after - { - let mut limiter = self.rate_limiter.lock().await; - if res.is_ok() { - match self.type_ { - RateLimitType::Post => { - limiter.check_rate_limit_full( - self.type_, - &ip_addr, - rate_limit.post, - rate_limit.post_per_second, - false, - )?; - } - RateLimitType::Register => { - limiter.check_rate_limit_full( - self.type_, - &ip_addr, - rate_limit.register, - rate_limit.register_per_second, - false, - )?; - } - _ => (), - }; - } - } - - res - } -} - -impl Transform for RateLimited -where - S: Service, - S::Future: 'static, -{ - type Request = S::Request; - type Response = S::Response; - type Error = actix_web::Error; - type InitError = (); - type Transform = RateLimitedMiddleware; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(RateLimitedMiddleware { - rate_limited: self.clone(), - service, - }) - } -} - -type FutResult = dyn Future>; - -impl Service for RateLimitedMiddleware -where - S: Service, - S::Future: 'static, -{ - type Request = S::Request; - type Response = S::Response; - type Error = actix_web::Error; - type Future = Pin>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, req: S::Request) -> Self::Future { - let ip_addr = get_ip(&req.connection_info()); - - let fut = self - .rate_limited - .clone() - .wrap(ip_addr, self.service.call(req)); - - Box::pin(async move { fut.await.map_err(actix_web::Error::from) }) - } -} diff --git a/server/src/rate_limit/rate_limiter.rs b/server/src/rate_limit/rate_limiter.rs deleted file mode 100644 index a30efa09..00000000 --- a/server/src/rate_limit/rate_limiter.rs +++ /dev/null @@ -1,101 +0,0 @@ -use super::*; - -#[derive(Debug, Clone)] -pub struct RateLimitBucket { - last_checked: SystemTime, - allowance: f64, -} - -#[derive(Eq, PartialEq, Hash, Debug, EnumIter, Copy, Clone)] -pub enum RateLimitType { - Message, - Register, - Post, -} - -/// Rate limiting based on rate type and IP addr -#[derive(Debug, Clone)] -pub struct RateLimiter { - pub buckets: HashMap>, -} - -impl Default for RateLimiter { - fn default() -> Self { - Self { - buckets: HashMap::new(), - } - } -} - -impl RateLimiter { - fn insert_ip(&mut self, ip: &str) { - for rate_limit_type in RateLimitType::iter() { - if self.buckets.get(&rate_limit_type).is_none() { - self.buckets.insert(rate_limit_type, HashMap::new()); - } - - if let Some(bucket) = self.buckets.get_mut(&rate_limit_type) { - if bucket.get(ip).is_none() { - bucket.insert( - ip.to_string(), - RateLimitBucket { - last_checked: SystemTime::now(), - allowance: -2f64, - }, - ); - } - } - } - } - - #[allow(clippy::float_cmp)] - pub(super) fn check_rate_limit_full( - &mut self, - type_: RateLimitType, - ip: &str, - rate: i32, - per: i32, - check_only: bool, - ) -> Result<(), Error> { - self.insert_ip(ip); - if let Some(bucket) = self.buckets.get_mut(&type_) { - if let Some(rate_limit) = bucket.get_mut(ip) { - let current = SystemTime::now(); - let time_passed = current.duration_since(rate_limit.last_checked)?.as_secs() as f64; - - // The initial value - if rate_limit.allowance == -2f64 { - rate_limit.allowance = rate as f64; - }; - - rate_limit.last_checked = current; - rate_limit.allowance += time_passed * (rate as f64 / per as f64); - if !check_only && rate_limit.allowance > rate as f64 { - rate_limit.allowance = rate as f64; - } - - if rate_limit.allowance < 1.0 { - debug!( - "Rate limited IP: {}, time_passed: {}, allowance: {}", - ip, time_passed, rate_limit.allowance - ); - Err( - APIError { - message: format!("Too many requests. {} per {} seconds", rate, per), - } - .into(), - ) - } else { - if !check_only { - rate_limit.allowance -= 1.0; - } - Ok(()) - } - } else { - Ok(()) - } - } else { - Ok(()) - } - } -} diff --git a/server/src/routes/api.rs b/server/src/routes/api.rs deleted file mode 100644 index 1565afb8..00000000 --- a/server/src/routes/api.rs +++ /dev/null @@ -1,187 +0,0 @@ -use super::*; -use crate::api::comment::*; -use crate::api::community::*; -use crate::api::post::*; -use crate::api::site::*; -use crate::api::user::*; -use crate::rate_limit::RateLimit; - -pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { - cfg.service( - web::scope("/api/v1") - // Websockets - .service(web::resource("/ws").to(super::websocket::chat_route)) - // Site - .service( - web::scope("/site") - .wrap(rate_limit.message()) - .route("", web::get().to(route_get::)) - // Admin Actions - .route("", web::post().to(route_post::)) - .route("", web::put().to(route_post::)) - .route("/transfer", web::post().to(route_post::)) - .route("/config", web::get().to(route_get::)) - .route("/config", web::put().to(route_post::)), - ) - .service( - web::resource("/categories") - .wrap(rate_limit.message()) - .route(web::get().to(route_get::)), - ) - .service( - web::resource("/modlog") - .wrap(rate_limit.message()) - .route(web::get().to(route_get::)), - ) - .service( - web::resource("/search") - .wrap(rate_limit.message()) - .route(web::get().to(route_get::)), - ) - // Community - .service( - web::resource("/community") - .guard(guard::Post()) - .wrap(rate_limit.register()) - .route(web::post().to(route_post::)), - ) - .service( - web::scope("/community") - .wrap(rate_limit.message()) - .route("", web::get().to(route_get::)) - .route("", web::put().to(route_post::)) - .route("/list", web::get().to(route_get::)) - .route("/follow", web::post().to(route_post::)) - // Mod Actions - .route("/transfer", web::post().to(route_post::)) - .route("/ban_user", web::post().to(route_post::)) - .route("/mod", web::post().to(route_post::)), - ) - // Post - .service( - // Handle POST to /post separately to add the post() rate limitter - web::resource("/post") - .guard(guard::Post()) - .wrap(rate_limit.post()) - .route(web::post().to(route_post::)), - ) - .service( - web::scope("/post") - .wrap(rate_limit.message()) - .route("", web::get().to(route_get::)) - .route("", web::put().to(route_post::)) - .route("/list", web::get().to(route_get::)) - .route("/like", web::post().to(route_post::)) - .route("/save", web::put().to(route_post::)), - ) - // Comment - .service( - web::scope("/comment") - .wrap(rate_limit.message()) - .route("", web::post().to(route_post::)) - .route("", web::put().to(route_post::)) - .route("/like", web::post().to(route_post::)) - .route("/save", web::put().to(route_post::)), - ) - // User - .service( - // Account action, I don't like that it's in /user maybe /accounts - // Handle /user/register separately to add the register() rate limitter - web::resource("/user/register") - .guard(guard::Post()) - .wrap(rate_limit.register()) - .route(web::post().to(route_post::)), - ) - // User actions - .service( - web::scope("/user") - .wrap(rate_limit.message()) - .route("", web::get().to(route_get::)) - .route("/mention", web::get().to(route_get::)) - .route("/mention", web::put().to(route_post::)) - .route("/replies", web::get().to(route_get::)) - .route( - "/followed_communities", - web::get().to(route_get::), - ) - // Admin action. I don't like that it's in /user - .route("/ban", web::post().to(route_post::)) - // Account actions. I don't like that they're in /user maybe /accounts - .route("/login", web::post().to(route_post::)) - .route( - "/delete_account", - web::post().to(route_post::), - ) - .route( - "/password_reset", - web::post().to(route_post::), - ) - .route( - "/password_change", - web::post().to(route_post::), - ) - // mark_all_as_read feels off being in this section as well - .route( - "/mark_all_as_read", - web::post().to(route_post::), - ) - .route( - "/save_user_settings", - web::put().to(route_post::), - ), - ) - // Admin Actions - .service( - web::resource("/admin/add") - .wrap(rate_limit.message()) - .route(web::post().to(route_post::)), - ), - ); -} - -async fn perform( - data: Request, - db: DbPoolParam, - chat_server: ChatServerParam, -) -> Result -where - Oper: Perform, - Request: Send + 'static, -{ - let ws_info = WebsocketInfo { - chatserver: chat_server.get_ref().to_owned(), - id: None, - }; - - let oper: Oper = Oper::new(data); - - let res = web::block(move || oper.perform(db.get_ref().to_owned(), Some(ws_info))) - .await - .map(|json| HttpResponse::Ok().json(json)) - .map_err(ErrorBadRequest)?; - Ok(res) -} - -async fn route_get( - data: web::Query, - db: DbPoolParam, - chat_server: ChatServerParam, -) -> Result -where - Data: Serialize + Send + 'static, - Oper: Perform, -{ - perform::(data.0, db, chat_server).await -} - -async fn route_post( - data: web::Json, - db: DbPoolParam, - chat_server: ChatServerParam, -) -> Result -where - Data: Serialize + Send + 'static, - Oper: Perform, -{ - perform::(data.0, db, chat_server).await -} diff --git a/server/src/routes/federation.rs b/server/src/routes/federation.rs deleted file mode 100644 index bc627cb0..00000000 --- a/server/src/routes/federation.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::*; -use crate::apub; - -pub fn config(cfg: &mut web::ServiceConfig) { - cfg - .route( - "/federation/c/{community_name}", - web::get().to(apub::community::get_apub_community), - ) - .route( - "/federation/c/{community_name}/followers", - web::get().to(apub::community::get_apub_community_followers), - ) - .route( - "/federation/u/{user_name}", - web::get().to(apub::user::get_apub_user), - ); -} diff --git a/server/src/routes/index.rs b/server/src/routes/index.rs deleted file mode 100644 index 895af435..00000000 --- a/server/src/routes/index.rs +++ /dev/null @@ -1,48 +0,0 @@ -use super::*; - -pub fn config(cfg: &mut web::ServiceConfig) { - cfg - .route("/", web::get().to(index)) - .route( - "/home/data_type/{data_type}/listing_type/{listing_type}/sort/{sort}/page/{page}", - web::get().to(index), - ) - .route("/login", web::get().to(index)) - .route("/create_post", web::get().to(index)) - .route("/create_community", web::get().to(index)) - .route("/create_private_message", web::get().to(index)) - .route("/communities/page/{page}", web::get().to(index)) - .route("/communities", web::get().to(index)) - .route("/post/{id}/comment/{id2}", web::get().to(index)) - .route("/post/{id}", web::get().to(index)) - .route( - "/c/{name}/data_type/{data_type}/sort/{sort}/page/{page}", - web::get().to(index), - ) - .route("/c/{name}", web::get().to(index)) - .route("/community/{id}", web::get().to(index)) - .route( - "/u/{username}/view/{view}/sort/{sort}/page/{page}", - web::get().to(index), - ) - .route("/u/{username}", web::get().to(index)) - .route("/user/{id}", web::get().to(index)) - .route("/inbox", web::get().to(index)) - .route("/modlog/community/{community_id}", web::get().to(index)) - .route("/modlog", web::get().to(index)) - .route("/setup", web::get().to(index)) - .route("/admin", web::get().to(index)) - .route( - "/search/q/{q}/type/{type}/sort/{sort}/page/{page}", - web::get().to(index), - ) - .route("/search", web::get().to(index)) - .route("/sponsors", web::get().to(index)) - .route("/password_change/{token}", web::get().to(index)); -} - -async fn index() -> Result { - Ok(NamedFile::open( - Settings::get().front_end_dir + "/index.html", - )?) -} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs deleted file mode 100644 index 37c56eba..00000000 --- a/server/src/routes/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::api::{Oper, Perform}; -use crate::db::site_view::SiteView; -use crate::rate_limit::rate_limiter::RateLimiter; -use crate::websocket::{server::ChatServer, WebsocketInfo}; -use crate::{get_ip, markdown_to_html, version, Settings}; -use actix::prelude::*; -use actix_files::NamedFile; -use actix_web::{body::Body, error::ErrorBadRequest, web::Query, *}; -use actix_web_actors::ws; -use chrono::{DateTime, NaiveDateTime, Utc}; -use diesel::{ - r2d2::{ConnectionManager, Pool}, - PgConnection, -}; -use log::{error, info}; -use regex::Regex; -use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder}; -use serde::{Deserialize, Serialize}; -use serde_json::json; -use std::str::FromStr; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; -use strum::ParseError; - -pub type DbPoolParam = web::Data>>; -pub type RateLimitParam = web::Data>>; -pub type ChatServerParam = web::Data>; - -pub mod api; -pub mod federation; -pub mod feeds; -pub mod index; -pub mod nodeinfo; -pub mod webfinger; -pub mod websocket; diff --git a/server/src/routes/nodeinfo.rs b/server/src/routes/nodeinfo.rs deleted file mode 100644 index af6fc282..00000000 --- a/server/src/routes/nodeinfo.rs +++ /dev/null @@ -1,89 +0,0 @@ -use super::*; - -pub fn config(cfg: &mut web::ServiceConfig) { - cfg - .route("/nodeinfo/2.0.json", web::get().to(node_info)) - .route("/.well-known/nodeinfo", web::get().to(node_info_well_known)); -} - -async fn node_info_well_known() -> HttpResponse { - let node_info = NodeInfoWellKnown { - links: NodeInfoWellKnownLinks { - rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".to_string(), - href: format!("https://{}/nodeinfo/2.0.json", Settings::get().hostname), - }, - }; - HttpResponse::Ok().json(node_info) -} - -async fn node_info( - db: web::Data>>, -) -> Result { - let res = web::block(move || { - let conn = db.get()?; - let site_view = match SiteView::read(&conn) { - Ok(site_view) => site_view, - Err(_) => return Err(format_err!("not_found")), - }; - let protocols = vec![]; - Ok(NodeInfo { - version: "2.0".to_string(), - software: NodeInfoSoftware { - name: "lemmy".to_string(), - version: version::VERSION.to_string(), - }, - protocols, - usage: NodeInfoUsage { - users: NodeInfoUsers { - total: site_view.number_of_users, - }, - local_posts: site_view.number_of_posts, - local_comments: site_view.number_of_comments, - open_registrations: site_view.open_registration, - }, - }) - }) - .await - .map(|json| HttpResponse::Ok().json(json)) - .map_err(ErrorBadRequest)?; - Ok(res) -} - -#[derive(Serialize)] -struct NodeInfoWellKnown { - links: NodeInfoWellKnownLinks, -} - -#[derive(Serialize)] -struct NodeInfoWellKnownLinks { - rel: String, - href: String, -} - -#[derive(Serialize)] -struct NodeInfo { - version: String, - software: NodeInfoSoftware, - protocols: Vec, - usage: NodeInfoUsage, -} - -#[derive(Serialize)] -struct NodeInfoSoftware { - name: String, - version: String, -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct NodeInfoUsage { - users: NodeInfoUsers, - local_posts: i64, - local_comments: i64, - open_registrations: bool, -} - -#[derive(Serialize)] -struct NodeInfoUsers { - total: i64, -} diff --git a/server/src/routes/webfinger.rs b/server/src/routes/webfinger.rs deleted file mode 100644 index 18a21efa..00000000 --- a/server/src/routes/webfinger.rs +++ /dev/null @@ -1,87 +0,0 @@ -use super::*; -use crate::db::community::Community; - -#[derive(Deserialize)] -pub struct Params { - resource: String, -} - -pub fn config(cfg: &mut web::ServiceConfig) { - cfg.route( - ".well-known/webfinger", - web::get().to(get_webfinger_response), - ); -} - -lazy_static! { - static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!( - "^group:([a-z0-9_]{{3, 20}})@{}$", - Settings::get().hostname - )) - .unwrap(); -} - -/// Responds to webfinger requests of the following format. There isn't any real documentation for -/// this, but it described in this blog post: -/// https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social -/// -/// You can also view the webfinger response that Mastodon sends: -/// https://radical.town/.well-known/webfinger?resource=acct:felix@radical.town -async fn get_webfinger_response( - info: Query, - db: web::Data>>, -) -> Result { - let res = web::block(move || { - let conn = db.get()?; - - let regex_parsed = WEBFINGER_COMMUNITY_REGEX - .captures(&info.resource) - .map(|c| c.get(1)); - // TODO: replace this with .flatten() once we are running rust 1.40 - let regex_parsed_flattened = match regex_parsed { - Some(s) => s, - None => None, - }; - let community_name = match regex_parsed_flattened { - Some(c) => c.as_str(), - None => return Err(format_err!("not_found")), - }; - - // Make sure the requested community exists. - let community = match Community::read_from_name(&conn, community_name.to_string()) { - Ok(o) => o, - Err(_) => return Err(format_err!("not_found")), - }; - - let community_url = community.get_url(); - - Ok(json!({ - "subject": info.resource, - "aliases": [ - community_url, - ], - "links": [ - { - "rel": "http://webfinger.net/rel/profile-page", - "type": "text/html", - "href": community_url - }, - { - "rel": "self", - "type": "application/activity+json", - // Yes this is correct, this link doesn't include the `.json` extension - "href": community_url - } - // TODO: this also needs to return the subscribe link once that's implemented - //{ - // "rel": "http://ostatus.org/schema/1.0/subscribe", - // "template": "https://my_instance.com/authorize_interaction?uri={uri}" - //} - ] - })) - }) - .await - .map(|json| HttpResponse::Ok().json(json)) - .map_err(ErrorBadRequest)?; - Ok(res) -} diff --git a/server/src/routes/websocket.rs b/server/src/routes/websocket.rs deleted file mode 100644 index 3814a8e9..00000000 --- a/server/src/routes/websocket.rs +++ /dev/null @@ -1,165 +0,0 @@ -use super::*; -use crate::websocket::server::*; - -/// How often heartbeat pings are sent -const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); -/// How long before lack of client response causes a timeout -const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); - -/// Entry point for our route -pub async fn chat_route( - req: HttpRequest, - stream: web::Payload, - chat_server: web::Data>, -) -> Result { - ws::start( - WSSession { - cs_addr: chat_server.get_ref().to_owned(), - id: 0, - hb: Instant::now(), - ip: get_ip(&req.connection_info()), - }, - &req, - stream, - ) -} - -struct WSSession { - cs_addr: Addr, - /// unique session id - id: usize, - ip: String, - /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT), - /// otherwise we drop connection. - hb: Instant, - // db: Pool>, -} - -impl Actor for WSSession { - type Context = ws::WebsocketContext; - - /// Method is called on actor start. - /// We register ws session with ChatServer - fn started(&mut self, ctx: &mut Self::Context) { - // we'll start heartbeat process on session start. - self.hb(ctx); - - // register self in chat server. `AsyncContext::wait` register - // future within context, but context waits until this future resolves - // before processing any other events. - // across all routes within application - let addr = ctx.address(); - self - .cs_addr - .send(Connect { - addr: addr.recipient(), - ip: self.ip.to_owned(), - }) - .into_actor(self) - .then(|res, act, ctx| { - match res { - Ok(res) => act.id = res, - // something is wrong with chat server - _ => ctx.stop(), - } - actix::fut::ready(()) - }) - .wait(ctx); - } - - fn stopping(&mut self, _ctx: &mut Self::Context) -> Running { - // notify chat server - self.cs_addr.do_send(Disconnect { - id: self.id, - ip: self.ip.to_owned(), - }); - Running::Stop - } -} - -/// Handle messages from chat server, we simply send it to peer websocket -/// These are room messages, IE sent to others in the room -impl Handler for WSSession { - type Result = (); - - fn handle(&mut self, msg: WSMessage, ctx: &mut Self::Context) { - ctx.text(msg.0); - } -} - -/// WebSocket message handler -impl StreamHandler> for WSSession { - fn handle(&mut self, result: Result, ctx: &mut Self::Context) { - let message = match result { - Ok(m) => m, - Err(e) => { - error!("{}", e); - return; - } - }; - match message { - ws::Message::Ping(msg) => { - self.hb = Instant::now(); - ctx.pong(&msg); - } - ws::Message::Pong(_) => { - self.hb = Instant::now(); - } - ws::Message::Text(text) => { - let m = text.trim().to_owned(); - info!("Message received: {:?} from id: {}", &m, self.id); - - self - .cs_addr - .send(StandardMessage { - id: self.id, - msg: m, - }) - .into_actor(self) - .then(|res, _, ctx| { - match res { - Ok(Ok(res)) => ctx.text(res), - Ok(Err(_)) => {} - Err(e) => error!("{}", &e), - } - actix::fut::ready(()) - }) - .spawn(ctx); - } - ws::Message::Binary(_bin) => info!("Unexpected binary"), - ws::Message::Close(_) => { - ctx.stop(); - } - _ => {} - } - } -} - -impl WSSession { - /// helper method that sends ping to client every second. - /// - /// also this method checks heartbeats from client - fn hb(&self, ctx: &mut ws::WebsocketContext) { - ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { - // check client heartbeats - if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { - // heartbeat timed out - error!("Websocket Client heartbeat failed, disconnecting!"); - - // notify chat server - act.cs_addr.do_send(Disconnect { - id: act.id, - ip: act.ip.to_owned(), - }); - - // stop actor - ctx.stop(); - - // don't try to send a ping - return; - } - - ctx.ping(b""); - }); - } -} diff --git a/server/src/schema.rs b/server/src/schema.rs index d9449fef..118f5f4a 100644 --- a/server/src/schema.rs +++ b/server/src/schema.rs @@ -207,10 +207,6 @@ table! { deleted -> Bool, nsfw -> Bool, stickied -> Bool, - embed_title -> Nullable, - embed_description -> Nullable, - embed_html -> Nullable, - thumbnail_url -> Nullable, } } @@ -242,19 +238,6 @@ table! { } } -table! { - private_message (id) { - id -> Int4, - creator_id -> Int4, - recipient_id -> Int4, - content -> Text, - deleted -> Bool, - read -> Bool, - published -> Timestamp, - updated -> Nullable, - } -} - table! { site (id) { id -> Int4, @@ -277,7 +260,7 @@ table! { preferred_username -> Nullable, password_encrypted -> Text, email -> Nullable, - avatar -> Nullable, + icon -> Nullable, admin -> Bool, banned -> Bool, published -> Timestamp, @@ -287,9 +270,6 @@ table! { default_sort_type -> Int2, default_listing_type -> Int2, lang -> Varchar, - show_avatars -> Bool, - send_notifications_to_email -> Bool, - matrix_user_id -> Nullable, } } @@ -375,7 +355,6 @@ allow_tables_to_appear_in_same_query!( post_like, post_read, post_saved, - private_message, site, user_, user_ban, diff --git a/server/src/settings.rs b/server/src/settings.rs index 9d58d5b5..4df2979e 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -1,36 +1,24 @@ +extern crate lazy_static; use config::{Config, ConfigError, Environment, File}; -use failure::Error; use serde::Deserialize; use std::env; -use std::fs; use std::net::IpAddr; -use std::sync::RwLock; static CONFIG_FILE_DEFAULTS: &str = "config/defaults.hjson"; static CONFIG_FILE: &str = "config/config.hjson"; -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize)] pub struct Settings { - pub setup: Option, pub database: Database, pub hostname: String, pub bind: IpAddr, pub port: u16, pub jwt_secret: String, - pub front_end_dir: String, pub rate_limit: RateLimitConfig, pub email: Option, } -#[derive(Debug, Deserialize, Clone)] -pub struct Setup { - pub admin_username: String, - pub admin_password: String, - pub admin_email: Option, - pub site_name: String, -} - -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize)] pub struct RateLimitConfig { pub message: i32, pub message_per_second: i32, @@ -40,16 +28,15 @@ pub struct RateLimitConfig { pub register_per_second: i32, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize)] pub struct EmailConfig { pub smtp_server: String, - pub smtp_login: Option, - pub smtp_password: Option, + pub smtp_login: String, + pub smtp_password: String, pub smtp_from_address: String, - pub use_tls: bool, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize)] pub struct Database { pub user: String, pub password: String, @@ -60,10 +47,12 @@ pub struct Database { } lazy_static! { - static ref SETTINGS: RwLock = RwLock::new(match Settings::init() { - Ok(c) => c, - Err(e) => panic!("{}", e), - }); + static ref SETTINGS: Settings = { + return match Settings::init() { + Ok(c) => c, + Err(e) => panic!("{}", e), + }; + }; } impl Settings { @@ -80,17 +69,14 @@ impl Settings { // Add in settings from the environment (with a prefix of LEMMY) // Eg.. `LEMMY_DEBUG=1 ./target/app` would set the `debug` key - // Note: we need to use double underscore here, because otherwise variables containing - // underscore cant be set from environmnet. - // https://github.com/mehcode/config-rs/issues/73 - s.merge(Environment::with_prefix("LEMMY").separator("__"))?; + s.merge(Environment::with_prefix("LEMMY").separator("_"))?; - s.try_into() + return s.try_into(); } /// Returns the config as a struct. - pub fn get() -> Self { - SETTINGS.read().unwrap().to_owned() + pub fn get() -> &'static Self { + &SETTINGS } /// Returns the postgres connection url. If LEMMY_DATABASE_URL is set, that is used, @@ -112,22 +98,4 @@ impl Settings { pub fn api_endpoint(&self) -> String { format!("{}/api/v1", self.hostname) } - - pub fn read_config_file() -> Result { - Ok(fs::read_to_string(CONFIG_FILE)?) - } - - pub fn save_config_file(data: &str) -> Result { - fs::write(CONFIG_FILE, data)?; - - // Reload the new settings - // From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804 - let mut new_settings = SETTINGS.write().unwrap(); - *new_settings = match Settings::init() { - Ok(c) => c, - Err(e) => panic!("{}", e), - }; - - Self::read_config_file() - } } diff --git a/server/src/version.rs b/server/src/version.rs index 529e3bff..db01c379 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.6.74"; +pub const VERSION: &'static str = "v0.5.0.2"; diff --git a/server/src/webfinger.rs b/server/src/webfinger.rs new file mode 100644 index 00000000..47e2037b --- /dev/null +++ b/server/src/webfinger.rs @@ -0,0 +1,69 @@ +use crate::db::community::Community; +use crate::db::establish_connection; +use crate::Settings; +use actix_web::body::Body; +use actix_web::web::Query; +use actix_web::HttpResponse; +use serde::Deserialize; +use serde_json::json; + +#[derive(Deserialize)] +pub struct Params { + resource: String, +} + +/// Responds to webfinger requests of the following format. There isn't any real documentation for +/// this, but it described in this blog post: +/// https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social +/// +/// You can also view the webfinger response that Mastodon sends: +/// https://radical.town/.well-known/webfinger?resource=acct:felix@radical.town +pub fn get_webfinger_response(info: Query) -> HttpResponse { + // NOTE: Calling the parameter "account" maybe doesn't really make sense, but should give us the + // best compatibility with existing implementations. We could also support an alternative name + // like "group", and encourage others to use that. + let community_identifier = info.resource.replace("acct:", ""); + let split_identifier: Vec<&str> = community_identifier.split("@").collect(); + let community_name = split_identifier[0]; + // It looks like Mastodon does not return webfinger requests for users from other instances, so we + // don't do that either. + if split_identifier.len() != 2 || split_identifier[1] != Settings::get().hostname { + return HttpResponse::NotFound().finish(); + } + + // Make sure the requested community exists. + let conn = establish_connection(); + match Community::read_from_name(&conn, community_name.to_owned()) { + Err(_) => return HttpResponse::NotFound().finish(), + Ok(c) => c, + }; + + let community_url = Community::get_community_url(&community_name); + + let json = json!({ + "subject": info.resource, + "aliases": [ + community_url, + ], + "links": [ + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": community_url + }, + { + "rel": "self", + "type": "application/activity+json", + "href": community_url // Yes this is correct, this link doesn't include the `.json` extension + } + // TODO: this also needs to return the subscribe link once that's implemented + //{ + // "rel": "http://ostatus.org/schema/1.0/subscribe", + // "template": "https://my_instance.com/authorize_interaction?uri={uri}" + //} + ] + }); + return HttpResponse::Ok() + .content_type("application/activity+json") + .body(json.to_string()); +} diff --git a/server/src/websocket/mod.rs b/server/src/websocket/mod.rs index fd200d7d..74f47ad3 100644 --- a/server/src/websocket/mod.rs +++ b/server/src/websocket/mod.rs @@ -1,70 +1 @@ pub mod server; - -use crate::ConnectionId; -use actix::prelude::*; -use diesel::r2d2::{ConnectionManager, Pool}; -use diesel::PgConnection; -use failure::Error; -use log::{error, info}; -use rand::{rngs::ThreadRng, Rng}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use server::ChatServer; -use std::collections::{HashMap, HashSet}; -use std::str::FromStr; - -#[derive(EnumString, ToString, Debug, Clone)] -pub enum UserOperation { - Login, - Register, - CreateCommunity, - CreatePost, - ListCommunities, - ListCategories, - GetPost, - GetCommunity, - CreateComment, - EditComment, - SaveComment, - CreateCommentLike, - GetPosts, - CreatePostLike, - EditPost, - SavePost, - EditCommunity, - FollowCommunity, - GetFollowedCommunities, - GetUserDetails, - GetReplies, - GetUserMentions, - EditUserMention, - GetModlog, - BanFromCommunity, - AddModToCommunity, - CreateSite, - EditSite, - GetSite, - AddAdmin, - BanUser, - Search, - MarkAllAsRead, - SaveUserSettings, - TransferCommunity, - TransferSite, - DeleteAccount, - PasswordReset, - PasswordChange, - CreatePrivateMessage, - EditPrivateMessage, - GetPrivateMessages, - UserJoin, - GetComments, - GetSiteConfig, - SaveSiteConfig, -} - -#[derive(Clone)] -pub struct WebsocketInfo { - pub chatserver: Addr, - pub id: Option, -} diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index 25766011..56fff275 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -2,20 +2,25 @@ //! And manages available rooms. Peers send messages to other peers in same //! room through `ChatServer`. -use super::*; +use actix::prelude::*; +use failure::Error; +use rand::{rngs::ThreadRng, Rng}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::{HashMap, HashSet}; +use std::str::FromStr; +use std::time::SystemTime; + use crate::api::comment::*; use crate::api::community::*; use crate::api::post::*; use crate::api::site::*; use crate::api::user::*; use crate::api::*; -use crate::rate_limit::RateLimit; -use crate::websocket::UserOperation; -use crate::{CommunityId, ConnectionId, IPAddr, PostId, UserId}; +use crate::Settings; /// Chat server sends this messages to session #[derive(Message)] -#[rtype(result = "()")] pub struct WSMessage(pub String); /// Message for chat server communications @@ -25,899 +30,187 @@ pub struct WSMessage(pub String); #[rtype(usize)] pub struct Connect { pub addr: Recipient, - pub ip: IPAddr, + pub ip: String, } /// Session is disconnected #[derive(Message)] -#[rtype(result = "()")] pub struct Disconnect { - pub id: ConnectionId, - pub ip: IPAddr, + pub id: usize, + pub ip: String, } -/// The messages sent to websocket clients -#[derive(Serialize, Deserialize, Message)] -#[rtype(result = "Result")] +/// Send message to specific room +#[derive(Message)] +pub struct ClientMessage { + /// Id of the client session + pub id: usize, + /// Peer message + pub msg: String, + /// Room name + pub room: String, +} + +#[derive(Serialize, Deserialize)] pub struct StandardMessage { /// Id of the client session - pub id: ConnectionId, + pub id: usize, /// Peer message pub msg: String, } -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendAllMessage { - pub op: UserOperation, - pub response: Response, - pub my_id: Option, +impl actix::Message for StandardMessage { + type Result = String; } -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendUserRoomMessage { - pub op: UserOperation, - pub response: Response, - pub recipient_id: UserId, - pub my_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendCommunityRoomMessage { - pub op: UserOperation, - pub response: Response, - pub community_id: CommunityId, - pub my_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendPost { - pub op: UserOperation, - pub post: PostResponse, - pub my_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendComment { - pub op: UserOperation, - pub comment: CommentResponse, - pub my_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct JoinUserRoom { - pub user_id: UserId, - pub id: ConnectionId, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct JoinCommunityRoom { - pub community_id: CommunityId, - pub id: ConnectionId, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct JoinPostRoom { - pub post_id: PostId, - pub id: ConnectionId, -} - -#[derive(Message)] -#[rtype(usize)] -pub struct GetUsersOnline; - -#[derive(Message)] -#[rtype(usize)] -pub struct GetPostUsersOnline { - pub post_id: PostId, -} - -#[derive(Message)] -#[rtype(usize)] -pub struct GetCommunityUsersOnline { - pub community_id: CommunityId, +#[derive(Debug)] +pub struct RateLimitBucket { + last_checked: SystemTime, + allowance: f64, } pub struct SessionInfo { pub addr: Recipient, - pub ip: IPAddr, + pub ip: String, } /// `ChatServer` manages chat rooms and responsible for coordinating chat -/// session. +/// session. implementation is super primitive pub struct ChatServer { - /// A map from generated random ID to session addr - pub sessions: HashMap, - - /// A map from post_id to set of connectionIDs - pub post_rooms: HashMap>, - - /// A map from community to set of connectionIDs - pub community_rooms: HashMap>, - - /// A map from user id to its connection ID for joined users. Remember a user can have multiple - /// sessions (IE clients) - user_rooms: HashMap>, - + sessions: HashMap, // A map from generated random ID to session addr + rate_limits: HashMap, + rooms: HashMap>, // A map from room / post name to set of connectionIDs rng: ThreadRng, +} - /// The DB Pool - pool: Pool>, +impl Default for ChatServer { + fn default() -> ChatServer { + // default room + let rooms = HashMap::new(); - /// Rate limiting based on rate type and IP addr - rate_limiter: RateLimit, + ChatServer { + sessions: HashMap::new(), + rate_limits: HashMap::new(), + rooms: rooms, + rng: rand::thread_rng(), + } + } } impl ChatServer { - pub fn startup( - pool: Pool>, - rate_limiter: RateLimit, - ) -> ChatServer { - ChatServer { - sessions: HashMap::new(), - post_rooms: HashMap::new(), - community_rooms: HashMap::new(), - user_rooms: HashMap::new(), - rng: rand::thread_rng(), - pool, - rate_limiter, + /// Send message to all users in the room + fn send_room_message(&self, room: &i32, message: &str, skip_id: usize) { + if let Some(sessions) = self.rooms.get(room) { + for id in sessions { + if *id != skip_id { + if let Some(info) = self.sessions.get(id) { + let _ = info.addr.do_send(WSMessage(message.to_owned())); + } + } + } } } - pub fn join_community_room(&mut self, community_id: CommunityId, id: ConnectionId) { + fn join_room(&mut self, room_id: i32, id: usize) { // remove session from all rooms - for sessions in self.community_rooms.values_mut() { - sessions.remove(&id); - } - - // Also leave all post rooms - // This avoids double messages - for sessions in self.post_rooms.values_mut() { + for (_n, sessions) in &mut self.rooms { sessions.remove(&id); } // If the room doesn't exist yet - if self.community_rooms.get_mut(&community_id).is_none() { - self.community_rooms.insert(community_id, HashSet::new()); + if self.rooms.get_mut(&room_id).is_none() { + self.rooms.insert(room_id, HashSet::new()); } - self - .community_rooms - .get_mut(&community_id) - .unwrap() - .insert(id); + &self.rooms.get_mut(&room_id).unwrap().insert(id); } - pub fn join_post_room(&mut self, post_id: PostId, id: ConnectionId) { - // remove session from all rooms - for sessions in self.post_rooms.values_mut() { - sessions.remove(&id); - } - - // Also leave all communities - // This avoids double messages - for sessions in self.community_rooms.values_mut() { - sessions.remove(&id); - } - - // If the room doesn't exist yet - if self.post_rooms.get_mut(&post_id).is_none() { - self.post_rooms.insert(post_id, HashSet::new()); - } - - self.post_rooms.get_mut(&post_id).unwrap().insert(id); - } - - pub fn join_user_room(&mut self, user_id: UserId, id: ConnectionId) { - // remove session from all rooms - for sessions in self.user_rooms.values_mut() { - sessions.remove(&id); - } - - // If the room doesn't exist yet - if self.user_rooms.get_mut(&user_id).is_none() { - self.user_rooms.insert(user_id, HashSet::new()); - } - - self.user_rooms.get_mut(&user_id).unwrap().insert(id); - } - - fn send_post_room_message( + fn send_community_message( &self, - op: &UserOperation, - response: &Response, - post_id: PostId, - my_id: Option, - ) -> Result<(), Error> - where - Response: Serialize, - { - let res_str = &to_json_string(op, response)?; - if let Some(sessions) = self.post_rooms.get(&post_id) { - for id in sessions { - if let Some(my_id) = my_id { - if *id == my_id { - continue; - } - } - self.sendit(res_str, *id); - } - } - Ok(()) - } - - pub fn send_community_room_message( - &self, - op: &UserOperation, - response: &Response, - community_id: CommunityId, - my_id: Option, - ) -> Result<(), Error> - where - Response: Serialize, - { - let res_str = &to_json_string(op, response)?; - if let Some(sessions) = self.community_rooms.get(&community_id) { - for id in sessions { - if let Some(my_id) = my_id { - if *id == my_id { - continue; - } - } - self.sendit(res_str, *id); - } - } - Ok(()) - } - - pub fn send_all_message( - &self, - op: &UserOperation, - response: &Response, - my_id: Option, - ) -> Result<(), Error> - where - Response: Serialize, - { - let res_str = &to_json_string(op, response)?; - for id in self.sessions.keys() { - if let Some(my_id) = my_id { - if *id == my_id { - continue; - } - } - self.sendit(res_str, *id); - } - Ok(()) - } - - pub fn send_user_room_message( - &self, - op: &UserOperation, - response: &Response, - recipient_id: UserId, - my_id: Option, - ) -> Result<(), Error> - where - Response: Serialize, - { - let res_str = &to_json_string(op, response)?; - if let Some(sessions) = self.user_rooms.get(&recipient_id) { - for id in sessions { - if let Some(my_id) = my_id { - if *id == my_id { - continue; - } - } - self.sendit(res_str, *id); - } - } - Ok(()) - } - - pub fn send_comment( - &self, - user_operation: &UserOperation, - comment: &CommentResponse, - my_id: Option, + community_id: &i32, + message: &str, + skip_id: usize, ) -> Result<(), Error> { - let mut comment_reply_sent = comment.clone(); - comment_reply_sent.comment.my_vote = None; - comment_reply_sent.comment.user_id = None; + use crate::db::post_view::*; + use crate::db::*; + let conn = establish_connection(); - let mut comment_post_sent = comment_reply_sent.clone(); - comment_post_sent.recipient_ids = Vec::new(); + let posts = PostQueryBuilder::create(&conn) + .listing_type(ListingType::Community) + .sort(&SortType::New) + .for_community_id(*community_id) + .limit(9999) + .list()?; - // Send it to the post room - self.send_post_room_message( - user_operation, - &comment_post_sent, - comment_post_sent.comment.post_id, - my_id, - )?; - - // Send it to the recipient(s) including the mentioned users - for recipient_id in &comment_reply_sent.recipient_ids { - self.send_user_room_message(user_operation, &comment_reply_sent, *recipient_id, my_id)?; + for post in posts { + self.send_room_message(&post.id, message, skip_id); } - // Send it to the community too - self.send_community_room_message(user_operation, &comment_post_sent, 0, my_id)?; - self.send_community_room_message( - user_operation, - &comment_post_sent, - comment.comment.community_id, - my_id, - )?; - Ok(()) } - pub fn send_post( - &self, - user_operation: &UserOperation, - post: &PostResponse, - my_id: Option, - ) -> Result<(), Error> { - let community_id = post.post.community_id; - - // Don't send my data with it - let mut post_sent = post.clone(); - post_sent.post.my_vote = None; - post_sent.post.user_id = None; - - // Send it to /c/all and that community - self.send_community_room_message(user_operation, &post_sent, 0, my_id)?; - self.send_community_room_message(user_operation, &post_sent, community_id, my_id)?; - - // Send it to the post room - self.send_post_room_message(user_operation, &post_sent, post.post.id, my_id)?; - - Ok(()) + fn check_rate_limit_register(&mut self, id: usize) -> Result<(), Error> { + self.check_rate_limit_full( + id, + Settings::get().rate_limit.register, + Settings::get().rate_limit.register_per_second, + ) } - fn sendit(&self, message: &str, id: ConnectionId) { + fn check_rate_limit_post(&mut self, id: usize) -> Result<(), Error> { + self.check_rate_limit_full( + id, + Settings::get().rate_limit.post, + Settings::get().rate_limit.post_per_second, + ) + } + + fn check_rate_limit_message(&mut self, id: usize) -> Result<(), Error> { + self.check_rate_limit_full( + id, + Settings::get().rate_limit.message, + Settings::get().rate_limit.message_per_second, + ) + } + + fn check_rate_limit_full(&mut self, id: usize, rate: i32, per: i32) -> Result<(), Error> { if let Some(info) = self.sessions.get(&id) { - let _ = info.addr.do_send(WSMessage(message.to_owned())); - } - } + if let Some(rate_limit) = self.rate_limits.get_mut(&info.ip) { + // The initial value + if rate_limit.allowance == -2f64 { + rate_limit.allowance = rate as f64; + }; - fn parse_json_message( - &mut self, - msg: StandardMessage, - ctx: &mut Context, - ) -> impl Future> { - let addr = ctx.address(); - let pool = self.pool.clone(); - let rate_limiter = self.rate_limiter.clone(); - - let ip: IPAddr = match self.sessions.get(&msg.id) { - Some(info) => info.ip.to_owned(), - None => "blank_ip".to_string(), - }; - - async move { - let msg = msg; - let json: Value = serde_json::from_str(&msg.msg)?; - let data = &json["data"].to_string(); - let op = &json["op"].as_str().ok_or(APIError { - message: "Unknown op type".to_string(), - })?; - - let user_operation: UserOperation = UserOperation::from_str(&op)?; - - match user_operation { - // User ops - UserOperation::Login => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::Register => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetUserDetails => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetReplies => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::AddAdmin => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::BanUser => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetUserMentions => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditUserMention => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::MarkAllAsRead => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::DeleteAccount => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::PasswordReset => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::PasswordChange => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::CreatePrivateMessage => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditPrivateMessage => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetPrivateMessages => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::UserJoin => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::SaveUserSettings => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await + let current = SystemTime::now(); + let time_passed = current.duration_since(rate_limit.last_checked)?.as_secs() as f64; + rate_limit.last_checked = current; + rate_limit.allowance += time_passed * (rate as f64 / per as f64); + if rate_limit.allowance > rate as f64 { + rate_limit.allowance = rate as f64; } - // Site ops - UserOperation::GetModlog => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::CreateSite => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditSite => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetSite => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetSiteConfig => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::SaveSiteConfig => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::Search => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::TransferCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::TransferSite => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::ListCategories => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - - // Community ops - UserOperation::GetCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::ListCommunities => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::CreateCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::FollowCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetFollowedCommunities => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::BanFromCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::AddModToCommunity => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - - // Post ops - UserOperation::CreatePost => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetPost => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::GetPosts => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::EditPost => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - UserOperation::CreatePostLike => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::SavePost => { - do_user_operation::(pool, rate_limiter, addr, msg.id, ip, user_operation, data) - .await - } - - // Comment ops - UserOperation::CreateComment => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::EditComment => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::SaveComment => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::GetComments => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await - } - UserOperation::CreateCommentLike => { - do_user_operation::( - pool, - rate_limiter, - addr, - msg.id, - ip, - user_operation, - data, - ) - .await + if rate_limit.allowance < 1.0 { + println!( + "Rate limited IP: {}, time_passed: {}, allowance: {}", + &info.ip, time_passed, rate_limit.allowance + ); + Err(APIError { + op: "Rate Limit".to_string(), + message: format!("Too many requests. {} per {} seconds", rate, per), + })? + } else { + rate_limit.allowance -= 1.0; + Ok(()) } + } else { + Ok(()) } + } else { + Ok(()) } } } -async fn do_user_operation<'a, Data>( - pool: Pool>, - rate_limiter: RateLimit, - chatserver: Addr, - id: ConnectionId, - ip: IPAddr, - op: UserOperation, - data: &str, -) -> Result -where - for<'de> Data: Deserialize<'de> + 'a, - Oper: Perform, -{ - let ws_info = WebsocketInfo { - chatserver, - id: Some(id), - }; - - let data = data.to_string(); - let op2 = op.clone(); - - let fut = async move { - actix_web::web::block(move || { - let parsed_data: Data = serde_json::from_str(&data)?; - let res = Oper::new(parsed_data).perform(pool, Some(ws_info))?; - to_json_string(&op, &res) - }) - .await - .map_err(|e| match e { - actix_web::error::BlockingError::Error(e) => e, - _ => APIError::err("Operation canceled").into(), - }) - }; - - match op2 { - UserOperation::Register => rate_limiter.register().wrap(ip, fut).await, - UserOperation::CreatePost => rate_limiter.post().wrap(ip, fut).await, - UserOperation::CreateCommunity => rate_limiter.register().wrap(ip, fut).await, - _ => rate_limiter.message().wrap(ip, fut).await, - } -} - /// Make actor from `ChatServer` impl Actor for ChatServer { /// We are going to use simple Context, we just need ability to communicate @@ -932,18 +225,31 @@ impl Handler for ChatServer { type Result = usize; fn handle(&mut self, msg: Connect, _ctx: &mut Context) -> Self::Result { + // notify all users in same room + // self.send_room_message(&"Main".to_owned(), "Someone joined", 0); + // register session with random id let id = self.rng.gen::(); - info!("{} joined", &msg.ip); + println!("{} joined", &msg.ip); self.sessions.insert( id, SessionInfo { addr: msg.addr, - ip: msg.ip, + ip: msg.ip.to_owned(), }, ); + if self.rate_limits.get(&msg.ip).is_none() { + self.rate_limits.insert( + msg.ip, + RateLimitBucket { + last_checked: SystemTime::now(), + allowance: -2f64, + }, + ); + } + id } } @@ -953,18 +259,15 @@ impl Handler for ChatServer { type Result = (); fn handle(&mut self, msg: Disconnect, _: &mut Context) { - // Remove connections from sessions and all 3 scopes + // let mut rooms: Vec = Vec::new(); + + // remove address if self.sessions.remove(&msg.id).is_some() { - for sessions in self.user_rooms.values_mut() { - sessions.remove(&msg.id); - } - - for sessions in self.post_rooms.values_mut() { - sessions.remove(&msg.id); - } - - for sessions in self.community_rooms.values_mut() { - sessions.remove(&msg.id); + // remove session from all rooms + for (_id, sessions) in &mut self.rooms { + if sessions.remove(&msg.id) { + // rooms.push(*id); + } } } } @@ -972,149 +275,267 @@ impl Handler for ChatServer { /// Handler for Message message. impl Handler for ChatServer { - type Result = ResponseFuture>; + type Result = MessageResult; - fn handle(&mut self, msg: StandardMessage, ctx: &mut Context) -> Self::Result { - let fut = self.parse_json_message(msg, ctx); - Box::pin(async move { - match fut.await { - Ok(m) => { - info!("Message Sent: {}", m); - Ok(m) - } - Err(e) => { - error!("Error during message handling {}", e); - Ok(e.to_string()) - } + fn handle(&mut self, msg: StandardMessage, _: &mut Context) -> Self::Result { + let msg_out = match parse_json_message(self, msg) { + Ok(m) => m, + Err(e) => e.to_string(), + }; + + MessageResult(msg_out) + } +} + +fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result { + let json: Value = serde_json::from_str(&msg.msg)?; + let data = &json["data"].to_string(); + let op = &json["op"].as_str().ok_or(APIError { + op: "Unknown op type".to_string(), + message: format!("Unknown op type"), + })?; + + let user_operation: UserOperation = UserOperation::from_str(&op)?; + + match user_operation { + UserOperation::Login => { + let login: Login = serde_json::from_str(data)?; + let res = Oper::new(user_operation, login).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::Register => { + let register: Register = serde_json::from_str(data)?; + let res = Oper::new(user_operation, register).perform(); + if res.is_ok() { + chat.check_rate_limit_register(msg.id)?; } - }) - } -} - -impl Handler> for ChatServer -where - Response: Serialize, -{ - type Result = (); - - fn handle(&mut self, msg: SendAllMessage, _: &mut Context) { - self - .send_all_message(&msg.op, &msg.response, msg.my_id) - .unwrap(); - } -} - -impl Handler> for ChatServer -where - Response: Serialize, -{ - type Result = (); - - fn handle(&mut self, msg: SendUserRoomMessage, _: &mut Context) { - self - .send_user_room_message(&msg.op, &msg.response, msg.recipient_id, msg.my_id) - .unwrap(); - } -} - -impl Handler> for ChatServer -where - Response: Serialize, -{ - type Result = (); - - fn handle(&mut self, msg: SendCommunityRoomMessage, _: &mut Context) { - self - .send_community_room_message(&msg.op, &msg.response, msg.community_id, msg.my_id) - .unwrap(); - } -} - -impl Handler for ChatServer { - type Result = (); - - fn handle(&mut self, msg: SendPost, _: &mut Context) { - self.send_post(&msg.op, &msg.post, msg.my_id).unwrap(); - } -} - -impl Handler for ChatServer { - type Result = (); - - fn handle(&mut self, msg: SendComment, _: &mut Context) { - self.send_comment(&msg.op, &msg.comment, msg.my_id).unwrap(); - } -} - -impl Handler for ChatServer { - type Result = (); - - fn handle(&mut self, msg: JoinUserRoom, _: &mut Context) { - self.join_user_room(msg.user_id, msg.id); - } -} - -impl Handler for ChatServer { - type Result = (); - - fn handle(&mut self, msg: JoinCommunityRoom, _: &mut Context) { - self.join_community_room(msg.community_id, msg.id); - } -} - -impl Handler for ChatServer { - type Result = (); - - fn handle(&mut self, msg: JoinPostRoom, _: &mut Context) { - self.join_post_room(msg.post_id, msg.id); - } -} - -impl Handler for ChatServer { - type Result = usize; - - fn handle(&mut self, _msg: GetUsersOnline, _: &mut Context) -> Self::Result { - self.sessions.len() - } -} - -impl Handler for ChatServer { - type Result = usize; - - fn handle(&mut self, msg: GetPostUsersOnline, _: &mut Context) -> Self::Result { - if let Some(users) = self.post_rooms.get(&msg.post_id) { - users.len() - } else { - 0 + Ok(serde_json::to_string(&res?)?) + } + UserOperation::GetUserDetails => { + let get_user_details: GetUserDetails = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_user_details).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::SaveUserSettings => { + let save_user_settings: SaveUserSettings = serde_json::from_str(data)?; + let res = Oper::new(user_operation, save_user_settings).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::AddAdmin => { + let add_admin: AddAdmin = serde_json::from_str(data)?; + let res = Oper::new(user_operation, add_admin).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::BanUser => { + let ban_user: BanUser = serde_json::from_str(data)?; + let res = Oper::new(user_operation, ban_user).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::GetReplies => { + let get_replies: GetReplies = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_replies).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::GetUserMentions => { + let get_user_mentions: GetUserMentions = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_user_mentions).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::EditUserMention => { + let edit_user_mention: EditUserMention = serde_json::from_str(data)?; + let res = Oper::new(user_operation, edit_user_mention).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::MarkAllAsRead => { + let mark_all_as_read: MarkAllAsRead = serde_json::from_str(data)?; + let res = Oper::new(user_operation, mark_all_as_read).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::GetCommunity => { + let get_community: GetCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_community).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::ListCommunities => { + let list_communities: ListCommunities = serde_json::from_str(data)?; + let res = Oper::new(user_operation, list_communities).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::CreateCommunity => { + chat.check_rate_limit_register(msg.id)?; + let create_community: CreateCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_community).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::EditCommunity => { + let edit_community: EditCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, edit_community).perform()?; + let mut community_sent: CommunityResponse = res.clone(); + community_sent.community.user_id = None; + community_sent.community.subscribed = None; + let community_sent_str = serde_json::to_string(&community_sent)?; + chat.send_community_message(&community_sent.community.id, &community_sent_str, msg.id)?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::FollowCommunity => { + let follow_community: FollowCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, follow_community).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::GetFollowedCommunities => { + let followed_communities: GetFollowedCommunities = serde_json::from_str(data)?; + let res = Oper::new(user_operation, followed_communities).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::BanFromCommunity => { + let ban_from_community: BanFromCommunity = serde_json::from_str(data)?; + let community_id = ban_from_community.community_id; + let res = Oper::new(user_operation, ban_from_community).perform()?; + let res_str = serde_json::to_string(&res)?; + chat.send_community_message(&community_id, &res_str, msg.id)?; + Ok(res_str) + } + UserOperation::AddModToCommunity => { + let mod_add_to_community: AddModToCommunity = serde_json::from_str(data)?; + let community_id = mod_add_to_community.community_id; + let res = Oper::new(user_operation, mod_add_to_community).perform()?; + let res_str = serde_json::to_string(&res)?; + chat.send_community_message(&community_id, &res_str, msg.id)?; + Ok(res_str) + } + UserOperation::ListCategories => { + let list_categories: ListCategories = ListCategories; + let res = Oper::new(user_operation, list_categories).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::CreatePost => { + chat.check_rate_limit_post(msg.id)?; + let create_post: CreatePost = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_post).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::GetPost => { + let get_post: GetPost = serde_json::from_str(data)?; + chat.join_room(get_post.id, msg.id); + let res = Oper::new(user_operation, get_post).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::GetPosts => { + let get_posts: GetPosts = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_posts).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::CreatePostLike => { + chat.check_rate_limit_message(msg.id)?; + let create_post_like: CreatePostLike = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_post_like).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::EditPost => { + let edit_post: EditPost = serde_json::from_str(data)?; + let res = Oper::new(user_operation, edit_post).perform()?; + let mut post_sent = res.clone(); + post_sent.post.my_vote = None; + let post_sent_str = serde_json::to_string(&post_sent)?; + chat.send_room_message(&post_sent.post.id, &post_sent_str, msg.id); + Ok(serde_json::to_string(&res)?) + } + UserOperation::SavePost => { + let save_post: SavePost = serde_json::from_str(data)?; + let res = Oper::new(user_operation, save_post).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::CreateComment => { + chat.check_rate_limit_message(msg.id)?; + let create_comment: CreateComment = serde_json::from_str(data)?; + let post_id = create_comment.post_id; + let res = Oper::new(user_operation, create_comment).perform()?; + let mut comment_sent = res.clone(); + comment_sent.comment.my_vote = None; + comment_sent.comment.user_id = None; + let comment_sent_str = serde_json::to_string(&comment_sent)?; + chat.send_room_message(&post_id, &comment_sent_str, msg.id); + Ok(serde_json::to_string(&res)?) + } + UserOperation::EditComment => { + let edit_comment: EditComment = serde_json::from_str(data)?; + let post_id = edit_comment.post_id; + let res = Oper::new(user_operation, edit_comment).perform()?; + let mut comment_sent = res.clone(); + comment_sent.comment.my_vote = None; + comment_sent.comment.user_id = None; + let comment_sent_str = serde_json::to_string(&comment_sent)?; + chat.send_room_message(&post_id, &comment_sent_str, msg.id); + Ok(serde_json::to_string(&res)?) + } + UserOperation::SaveComment => { + let save_comment: SaveComment = serde_json::from_str(data)?; + let res = Oper::new(user_operation, save_comment).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::CreateCommentLike => { + chat.check_rate_limit_message(msg.id)?; + let create_comment_like: CreateCommentLike = serde_json::from_str(data)?; + let post_id = create_comment_like.post_id; + let res = Oper::new(user_operation, create_comment_like).perform()?; + let mut comment_sent = res.clone(); + comment_sent.comment.my_vote = None; + comment_sent.comment.user_id = None; + let comment_sent_str = serde_json::to_string(&comment_sent)?; + chat.send_room_message(&post_id, &comment_sent_str, msg.id); + Ok(serde_json::to_string(&res)?) + } + UserOperation::GetModlog => { + let get_modlog: GetModlog = serde_json::from_str(data)?; + let res = Oper::new(user_operation, get_modlog).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::CreateSite => { + let create_site: CreateSite = serde_json::from_str(data)?; + let res = Oper::new(user_operation, create_site).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::EditSite => { + let edit_site: EditSite = serde_json::from_str(data)?; + let res = Oper::new(user_operation, edit_site).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::GetSite => { + let online: usize = chat.sessions.len(); + let get_site: GetSite = serde_json::from_str(data)?; + let mut res = Oper::new(user_operation, get_site).perform()?; + res.online = online; + Ok(serde_json::to_string(&res)?) + } + UserOperation::Search => { + let search: Search = serde_json::from_str(data)?; + let res = Oper::new(user_operation, search).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::TransferCommunity => { + let transfer_community: TransferCommunity = serde_json::from_str(data)?; + let res = Oper::new(user_operation, transfer_community).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::TransferSite => { + let transfer_site: TransferSite = serde_json::from_str(data)?; + let res = Oper::new(user_operation, transfer_site).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::DeleteAccount => { + let delete_account: DeleteAccount = serde_json::from_str(data)?; + let res = Oper::new(user_operation, delete_account).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::PasswordReset => { + let password_reset: PasswordReset = serde_json::from_str(data)?; + let res = Oper::new(user_operation, password_reset).perform()?; + Ok(serde_json::to_string(&res)?) + } + UserOperation::PasswordChange => { + let password_change: PasswordChange = serde_json::from_str(data)?; + let res = Oper::new(user_operation, password_change).perform()?; + Ok(serde_json::to_string(&res)?) } } } - -impl Handler for ChatServer { - type Result = usize; - - fn handle(&mut self, msg: GetCommunityUsersOnline, _: &mut Context) -> Self::Result { - if let Some(users) = self.community_rooms.get(&msg.community_id) { - users.len() - } else { - 0 - } - } -} - -#[derive(Serialize)] -struct WebsocketResponse { - op: String, - data: T, -} - -fn to_json_string(op: &UserOperation, data: &Response) -> Result -where - Response: Serialize, -{ - let response = WebsocketResponse { - op: op.to_string(), - data, - }; - Ok(serde_json::to_string(&response)?) -} diff --git a/ui/.eslintignore b/ui/.eslintignore deleted file mode 100644 index aa6ed178..00000000 --- a/ui/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -fuse.js -translation_report.ts diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json index a4aecfa0..bee9e538 100644 --- a/ui/.eslintrc.json +++ b/ui/.eslintrc.json @@ -38,7 +38,6 @@ "inferno/no-direct-mutation-state": 0, "inferno/no-unknown-property": 0, "max-statements": 0, - "max-params": 0, "new-cap": 0, "no-console": 0, "no-duplicate-imports": 0, diff --git a/ui/assets/css/main.css b/ui/assets/css/main.css index 8b51dcd9..e8477910 100644 --- a/ui/assets/css/main.css +++ b/ui/assets/css/main.css @@ -27,51 +27,21 @@ margin-top: -10px; } -.md-div p { - overflow: hidden; - text-overflow: ellipsis; -} - .md-div p:last-child { margin-bottom: 0px; } .md-div img { - max-height: 90vh; max-width: 100%; height: auto; } -.md-div h1, -.md-div h2, -.md-div h3, -.md-div h4, -.md-div h5 { +.md-div h1,h2,h3,h4,h5 { font-size:1.171875rem; } -.md-div table { - border-collapse: collapse; - width: 100%; - margin-bottom: 1rem; - border: 1px solid var(--dark); -} - -.md-div table th, -.md-div table td { - padding: 0.3rem; - vertical-align: top; - border-top: 1px solid var(--dark); - border: 1px solid var(--dark); -} - -.md-div table thead th { - vertical-align: bottom; - border-bottom: 2px solid var(--dark); -} - -.md-div table tbody + tbody { - border-top: 2px solid var(--dark); +.comment-node { + margin-bottom: 10px; } .vote-bar { @@ -91,17 +61,8 @@ fill: currentColor; vertical-align: middle; align-self: center; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; } -.icon-inline { - margin-bottom: 2px; -} .spin { animation: spins 2s linear infinite; @@ -117,7 +78,7 @@ } blockquote { - border-left: 2px solid var(--secondary); + border-left: 3px solid #ccc; margin: 0.5em 5px; padding: 0.1em 5px; } @@ -128,18 +89,16 @@ blockquote { .new-comments { max-height: 50vh; - overflow-x: hidden; + overflow: hidden; +} + +.new-comments:hover { overflow-y: auto; } .thumbnail { - object-fit: cover; - max-height: 80px; - width: 100%; -} - -svg.thumbnail { - height: 40px; + max-height: 62px; + max-width: 400px; } .no-s-hows { @@ -149,103 +108,9 @@ svg.thumbnail { } hr { - border-top: 1px solid var(--light); + border-top: 1px solid var(--secondary); } .emoji { - max-height: 1.2em !important; -} - -.text-wrap-truncate { - overflow: hidden; - text-overflow: ellipsis -} - -#app { - display: flex; - flex-direction: column; - min-height: 100vh; -} - -.fl-1 { - flex: 1; -} - -.img-blur { - filter: blur(10px); - -webkit-filter: blur(10px); - -moz-filter: blur(10px); - -o-filter: blur(10px); - -ms-filter: blur(10px); -} - -.img-expanded { - max-height: 90vh; -} - -.btn-animate:active { - transform: scale(1.2); - -webkit-transform: scale(1.2); - -ms-transform: scale(1.2); -} - -.selectr-selected, .selectr-options-container { - background-color: var(--secondary); - color: var(--white); - border: unset; -} - -.mini-overlay { - position: absolute; - top: 0; - right: 0; - padding: 2px; - height: 1.5em; - width: 1.5em; - background: rgba(0,0,0,.4); - border-bottom-left-radius: 0.25rem !important; - border-top-right-radius: 0.25rem !important; -} - -.link-overlay:hover { - transition: .1s; - opacity: 1; -} - -.link-overlay { - transition: opacity .1s ease-in-out; - position: absolute; - opacity: 0; - left: 0; - height: 100%; - width: 100%; - padding: 10px; - background: rgba(0,0,0,.6); -} - -.placeholder { - height: 50px; - width: 50px; -} - -.unselectable { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.list-inline-item-action { - display: inline-block; -} - -.list-inline-item-action:not(:last-child) { - margin-right: 1.2rem; -} - -pre { - white-space: pre-wrap; - word-break: keep-all; + height: 1.2em !important; } diff --git a/ui/assets/css/selectr.min.css b/ui/assets/css/selectr.min.css deleted file mode 100644 index 78bab83f..00000000 --- a/ui/assets/css/selectr.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Selectr 2.4.13 - * http://mobius.ovh/docs/selectr - * - * Released under the MIT license - */ -.selectr-container li,.selectr-option,.selectr-tag{list-style:none}.selectr-container{position:relative}.selectr-hidden{position:absolute;overflow:hidden;clip:rect(0,0,0,0);width:1px;height:1px;margin:-1px;padding:0;border:0}.selectr-visible{position:absolute;left:0;top:0;width:100%;height:100%;opacity:0;z-index:11}.selectr-desktop.multiple .selectr-visible{display:none}.selectr-desktop.multiple.native-open .selectr-visible{top:100%;min-height:200px!important;height:auto;opacity:1;display:block}.selectr-container.multiple.selectr-mobile .selectr-selected{z-index:0}.selectr-selected{position:relative;z-index:1;box-sizing:border-box;width:100%;padding:7px 28px 7px 14px;cursor:pointer;border:1px solid #999;border-radius:3px;}.selectr-selected::before{position:absolute;top:50%;right:10px;width:0;height:0;content:'';-o-transform:rotate(0) translate3d(0,-50%,0);-ms-transform:rotate(0) translate3d(0,-50%,0);-moz-transform:rotate(0) translate3d(0,-50%,0);-webkit-transform:rotate(0) translate3d(0,-50%,0);transform:rotate(0) translate3d(0,-50%,0);border-width:4px 4px 0;border-style:solid;border-color:#6c7a86 transparent transparent}.selectr-container.native-open .selectr-selected::before,.selectr-container.open .selectr-selected::before{border-width:0 4px 4px;border-style:solid;border-color:transparent transparent #6c7a86}.selectr-label{display:none;overflow:hidden;width:100%;white-space:nowrap;text-overflow:ellipsis}.selectr-placeholder{color:#6c7a86}.selectr-tags{margin:0;padding:0;white-space:normal}.has-selected .selectr-tags{margin:0 0 -2px}.selectr-tag{position:relative;float:left;padding:2px 25px 2px 8px;margin:0 2px 2px 0;cursor:default;color:#fff;border:none;border-radius:10px;background:#acb7bf}.selectr-container.multiple.has-selected .selectr-selected{padding:5px 28px 5px 5px}.selectr-options-container{position:absolute;z-index:10000;top:calc(100% - 1px);left:0;display:none;box-sizing:border-box;width:100%;border-width:0 1px 1px;border-style:solid;border-color:transparent #999 #999;border-radius:0 0 3px 3px;}.selectr-container.open .selectr-options-container{display:block}.selectr-input-container{position:relative;display:none}.selectr-clear,.selectr-input-clear,.selectr-tag-remove{position:absolute;top:50%;right:22px;width:20px;height:20px;padding:0;cursor:pointer;-o-transform:translate3d(0,-50%,0);-ms-transform:translate3d(0,-50%,0);-moz-transform:translate3d(0,-50%,0);-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);border:none;background-color:transparent;z-index:11}.selectr-clear,.selectr-input-clear{display:none}.selectr-container.has-selected .selectr-clear,.selectr-input-container.active,.selectr-input-container.active .selectr-clear,.selectr-input-container.active .selectr-input-clear{display:block}.selectr-selected .selectr-tag-remove{right:2px}.selectr-clear::after,.selectr-clear::before,.selectr-input-clear::after,.selectr-input-clear::before,.selectr-tag-remove::after,.selectr-tag-remove::before{position:absolute;top:5px;left:9px;width:2px;height:10px;content:' ';background-color:#6c7a86}.selectr-tag-remove::after,.selectr-tag-remove::before{top:4px;width:3px;height:12px;}.selectr-clear:before,.selectr-input-clear::before,.selectr-tag-remove::before{-o-transform:rotate(45deg);-ms-transform:rotate(45deg);-moz-transform:rotate(45deg);-webkit-transform:rotate(45deg);transform:rotate(45deg)}.selectr-clear:after,.selectr-input-clear::after,.selectr-tag-remove::after{-o-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.selectr-input{top:5px;left:5px;box-sizing:border-box;width:calc(100% - 30px);margin:10px 15px;padding:7px 30px 7px 9px;border:1px solid #999;border-radius:3px}.selectr-notice{display:none;box-sizing:border-box;width:100%;padding:8px 16px;border-top:1px solid #999;border-radius:0 0 3px 3px;}.input-tag,.taggable .selectr-label{width:auto}.selectr-container.notice .selectr-notice{display:block}.selectr-container.notice .selectr-selected{border-radius:3px 3px 0 0}.selectr-options{position:relative;top:calc(100% + 2px);display:none;overflow-x:auto;overflow-y:scroll;max-height:200px;margin:0;padding:0}.selectr-container.notice .selectr-options-container,.selectr-container.open .selectr-input-container,.selectr-container.open .selectr-options{display:block}.selectr-option{position:relative;display:block;padding:5px 20px;cursor:pointer;font-weight:400}.has-selected .selectr-placeholder,.selectr-empty,.selectr-option.excluded{display:none}.selectr-options.optgroups>.selectr-option{padding-left:25px}.selectr-optgroup{font-weight:700;padding:0}.selectr-optgroup--label{font-weight:700;margin-top:10px;padding:5px 15px}.selectr-match{text-decoration:underline}.selectr-option.selected{background-color:#ddd}.selectr-option.active{color:#fff;background-color:#5897fb}.selectr-option.disabled{opacity:.4}.selectr-container.open .selectr-selected{border-color:#999 #999 transparent;border-radius:3px 3px 0 0}.selectr-container.open .selectr-selected::after{-o-transform:rotate(180deg) translate3d(0,50%,0);-ms-transform:rotate(180deg) translate3d(0,50%,0);-moz-transform:rotate(180deg) translate3d(0,50%,0);-webkit-transform:rotate(180deg) translate3d(0,50%,0);transform:rotate(180deg) translate3d(0,50%,0)}.selectr-disabled{opacity:.6}.has-selected .selectr-label{display:block}.taggable .selectr-selected{padding:4px 28px 4px 4px}.taggable .selectr-selected::after{display:table;content:" ";clear:both}.taggable .selectr-tags{float:left;display:block}.taggable .selectr-placeholder{display:none}.input-tag{float:left;min-width:90px}.selectr-tag-input{border:none;padding:3px 10px;width:100%;font-family:inherit;font-weight:inherit;font-size:inherit}.selectr-input-container.loading::after{position:absolute;top:50%;right:20px;width:20px;height:20px;content:'';-o-transform:translate3d(0,-50%,0);-ms-transform:translate3d(0,-50%,0);-moz-transform:translate3d(0,-50%,0);-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);-o-transform-origin:50% 0 0;-ms-transform-origin:50% 0 0;-moz-transform-origin:50% 0 0;-webkit-transform-origin:50% 0 0;transform-origin:50% 0 0;-moz-animation:.5s linear 0s normal forwards infinite running spin;-webkit-animation:.5s linear 0s normal forwards infinite running spin;animation:.5s linear 0s normal forwards infinite running spin;border-width:3px;border-style:solid;border-color:#aaa #ddd #ddd;border-radius:50%}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0) translate3d(0,-50%,0);transform:rotate(0) translate3d(0,-50%,0)}100%{-webkit-transform:rotate(360deg) translate3d(0,-50%,0);transform:rotate(360deg) translate3d(0,-50%,0)}}@keyframes spin{0%{-webkit-transform:rotate(0) translate3d(0,-50%,0);transform:rotate(0) translate3d(0,-50%,0)}100%{-webkit-transform:rotate(360deg) translate3d(0,-50%,0);transform:rotate(360deg) translate3d(0,-50%,0)}}.selectr-container.open.inverted .selectr-selected{border-color:transparent #999 #999;border-radius:0 0 3px 3px}.selectr-container.inverted .selectr-options-container{border-width:1px 1px 0;border-color:#999 #999 transparent;border-radius:3px 3px 0 0;top:auto;bottom:calc(100% - 1px)}.selectr-container ::-webkit-input-placeholder{color:#6c7a86;opacity:1}.selectr-container ::-moz-placeholder{color:#6c7a86;opacity:1}.selectr-container :-ms-input-placeholder{color:#6c7a86;opacity:1}.selectr-container ::placeholder{color:#6c7a86;opacity:1} diff --git a/ui/assets/css/themes/_variables.bootstra_386-tmp.scss b/ui/assets/css/themes/_variables.bootstra_386-tmp.scss deleted file mode 100644 index c08fab26..00000000 --- a/ui/assets/css/themes/_variables.bootstra_386-tmp.scss +++ /dev/null @@ -1,902 +0,0 @@ -// -// Variables -// -------------------------------------------------- - - -//== Colors -// -//## Gray and brand colors for use across Bootstrap. - -//// colors from bs-2 -// Grays -// ------------------------- -$black: #000; -$grayDark: #555; -$gray: #bbb; -$grayLight: #bbb; -$white: #FFF; - - -// Accent colors -// ------------------------- -$blue: #5555Ff; -$cyan: #55FFFF; -$cyanDark: #00AAAA; -$blueDark: #000084; -$green: #55FF55; -$greenDark: #00AA00; -$magenta: #FF55FF; -$magentaDark: #AA00AA; -$red: #FF5555; -$redDark: #AA0000; -$yellow: #FEFE54; -$brown: #AA5500; -$orange: #A85400; -$pink: #FE54FE; -$purple: #FE5454; - -// end colors - -$gray-base: $gray; -$gray-darker: $grayDark; -$gray-dark: $grayDark; -$gray-light: $grayLight; -$gray-lighter: $grayLight; - -$brand-primary: $gray; -$brand-primary-bg: $cyanDark; -$brand-success: $greenDark; -$brand-info: $brown; -$brand-warning: $magentaDark; -$brand-danger: $redDark; - - -//== Scaffolding -// -//## Settings for some of the most global styles. - -//** Background color for ``. -$body-bg: $blueDark; -//** Global text color on ``. -$text-color: $gray-light; - -//** Global textual link color. -$link-color: $brand-primary; -//** Link hover color set via `darken()` function. -$link-hover-color: $white; -//** Link hover decoration. -$link-hover-decoration: none; - - -//== Typography -// -//## Font, line-height, and color for body text, headings, and more. - -$font-family-sans-serif: DOS, Monaco, Menlo, Consolas, "Courier New", monospace; -$font-family-serif: DOS, Monaco, Menlo, Consolas, "Courier New", monospace; -//** Default monospace fonts for ``, ``, and `
`.
-$font-family-monospace:   DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
-$font-family-base:        $font-family-sans-serif;
-
-$baseWidth:               10px;
-$font-size-base:          18px;
-$font-size-large:         $font-size-base;
-$font-size-small:         $font-size-base;
-
-$font-size-h1:            $font-size-base;
-$font-size-h2:            $font-size-base;
-$font-size-h3:            $font-size-base;
-$font-size-h4:            $font-size-base;
-$font-size-h5:            $font-size-base;
-$font-size-h6:            $font-size-base;
-
-//** Unit-less `line-height` for use in components like buttons.
-$baseLineHeight:          19px;
-$line-height-base:        $baseLineHeight;
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-$line-height-computed:    $line-height-base;
-
-//** By default, this inherits from the ``.
-$headings-font-family:    inherit;
-$headings-font-weight:    normal;
-$headings-line-height:    $line-height-base; 
-$headings-color:          inherit;
-
-$space:                 $baseWidth;
-$halfbaseLineHeight:    ($baseLineHeight / 2);
-$borderWidth:           2px;
-$baseLineWidth:         ($baseLineHeight / 2);
-$halfSpace:             ($baseWidth / 2);
-$lhsNB:                 ($baseWidth / 2 + 1);
-$rhsNB:                 ($baseWidth / 2 - 1);
-$lhs:                   ($lhsNB - ($borderWidth));
-$rhs:                   ($rhsNB - ($borderWidth / 2));
-$tsNB:                  ($baseLineHeight / 2);
-$bsNB:                  $tsNB;
-$ts:                    ($tsNB - ($borderWidth / 2));
-$bs:                    $ts;
-$tsMargin:              3px;
-
-
-//== Iconography
-//
-//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
-
-//** Load fonts from this directory.
-$icon-font-path:          "../fonts/";
-//** File name for all font files.
-$icon-font-name:          "glyphicons-halflings-regular";
-//** Element ID within SVG icon file.
-$icon-font-svg-id:        "glyphicons_halflingsregular";
-
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-$padding-base-vertical:     0px;
-$padding-base-horizontal:   0px;
-
-$padding-large-vertical:    0px;
-$padding-large-horizontal:  $halfSpace;
-
-$padding-small-vertical:    0px;
-$padding-small-horizontal:  0px;
-
-$padding-xs-vertical:       0px;
-$padding-xs-horizontal:     0px;
-
-$line-height-large:         $baseLineHeight;
-$line-height-small:         $baseLineHeight;
-
-$border-radius-base:        0;
-$border-radius-large:       0;
-$border-radius-small:       0;
-
-//** Global color for active items (e.g., navs or dropdowns).
-$component-active-color:    $white;
-//** Global background color for active items (e.g., navs or dropdowns).
-$component-active-bg:       $black;
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-$caret-width-base:          4px;
-//** Carets increase slightly in size for larger components.
-$caret-width-large:         5px;
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for ``s and ``s.
-$table-cell-padding:            $ts $rhs $bs $lhs; 
-//** Padding for cells in `.table-condensed`.
-$table-condensed-cell-padding:  $ts $rhs $bs $lhs;
-
-//** Default background color used for all tables.
-$table-bg:                      transparent;
-//** Background color used for `.table-striped`.
-$table-bg-accent:               $black;
-//** Background color used for `.table-hover`.
-$table-bg-hover:                #f5f5f5;
-$table-bg-active:               $table-bg-hover;
-
-//** Border color for table and cell borders.
-$table-border-color:            $gray;
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-$btn-font-weight:                normal;
-
-$btn-default-color:              $black;
-$btn-default-bg:                 $grayLight;
-$btn-default-border:             $grayLight;
-
-$btn-primary-color:              $black;
-$btn-primary-bg:                 $cyanDark;
-$btn-primary-border:             $grayLight;
-
-$btn-success-color:              #fff;
-$btn-success-bg:                 $brand-success;
-$btn-success-border:             $btn-success-bg;
-
-$btn-info-color:                 #fff;
-$btn-info-bg:                    $brand-info;
-$btn-info-border:                $btn-info-bg;
-
-$btn-warning-color:              #fff;
-$btn-warning-bg:                 $brand-warning;
-$btn-warning-border:             $btn-warning-bg;
-
-$btn-danger-color:               #fff;
-$btn-danger-bg:                  $brand-danger;
-$btn-danger-border:              $btn-danger-bg;
-
-$btn-link-disabled-color:        $gray-light;
-
-
-//== Forms
-//
-//##
-
-//** `` background color
-$input-bg:                       $cyanDark;
-//** `` background color
-$input-bg-disabled:              $gray-lighter;
-
-//** Text color for ``s
-$input-color:                    $white;
-//** `` border color
-$input-border:                   #ccc;
-
-// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
-//** Default `.form-control` border radius
-// This has no effect on ``s in CSS.
-$input-border-radius:            $border-radius-base;
-//** Large `.form-control` border radius
-$input-border-radius-large:      $border-radius-large;
-//** Small `.form-control` border radius
-$input-border-radius-small:      $border-radius-small;
-
-//** Border color for inputs on focus
-$input-border-focus:             $black;
-
-//** Placeholder text color
-$input-color-placeholder:        $black;
-
-//** Default `.form-control` height
-$input-height-base:              $line-height-computed;
-//** Large `.form-control` height
-$input-height-large:             $input-height-base;
-//** Small `.form-control` height
-$input-height-small:             $input-height-base;
-
-$legend-color:                   $gray-dark;
-$legend-border-color:            #e5e5e5;
-
-//** Background color for textual input addons
-$input-group-addon-bg:           $gray-lighter;
-//** Border color for textual input addons
-$input-group-addon-border-color: $input-border;
-
-//** Disabled cursor for form controls and buttons.
-$cursor-disabled:                not-allowed;
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-$dropdown-bg:                    $gray;
-//** Dropdown menu `border-color`.
-$dropdown-border:                rgb(0,0,0);
-//** Dropdown menu `border-color` **for IE8**.
-$dropdown-fallback-border:       #ccc;
-//** Divider color for between dropdown items.
-$dropdown-divider-bg:            $black;
-
-//** Dropdown link text color.
-$dropdown-link-color:            $black;
-//** Hover color for dropdown links.
-$dropdown-link-hover-color:      $gray;
-//** Hover background for dropdown links.
-$dropdown-link-hover-bg:         $black;
-
-//** Active dropdown menu item text color.
-$dropdown-link-active-color:     $component-active-color;
-//** Active dropdown menu item background color.
-$dropdown-link-active-bg:        $component-active-bg;
-
-//** Disabled dropdown menu item background color.
-$dropdown-link-disabled-color:   $gray-light;
-
-//** Text color for headers within dropdown menus.
-$dropdown-header-color:          $black;
-
-//** Deprecated `$dropdown-caret-color` as of v3.1.0
-$dropdown-caret-color:           #000;
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-$zindex-navbar:            1000;
-$zindex-dropdown:          1000;
-$zindex-popover:           1060;
-$zindex-tooltip:           1070;
-$zindex-navbar-fixed:      1030;
-$zindex-modal:             1040;
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-//** Deprecated `$screen-xs` as of v3.0.1
-$screen-xs:                  480px;
-//** Deprecated `$screen-xs-min` as of v3.2.0
-$screen-xs-min:              $screen-xs;
-//** Deprecated `$screen-phone` as of v3.0.1
-$screen-phone:               $screen-xs-min;
-
-// Small screen / tablet
-//** Deprecated `$screen-sm` as of v3.0.1
-$screen-sm:                  768px;
-$screen-sm-min:              $screen-sm;
-//** Deprecated `$screen-tablet` as of v3.0.1
-$screen-tablet:              $screen-sm-min;
-
-// Medium screen / desktop
-//** Deprecated `$screen-md` as of v3.0.1
-$screen-md:                  992px;
-$screen-md-min:              $screen-md;
-//** Deprecated `$screen-desktop` as of v3.0.1
-$screen-desktop:             $screen-md-min;
-
-// Large screen / wide desktop
-//** Deprecated `$screen-lg` as of v3.0.1
-$screen-lg:                  1200px;
-$screen-lg-min:              $screen-lg;
-//** Deprecated `$screen-lg-desktop` as of v3.0.1
-$screen-lg-desktop:          $screen-lg-min;
-
-// So media queries don't overlap when required, provide a maximum
-$screen-xs-max:              ($screen-sm-min - 1);
-$screen-sm-max:              ($screen-md-min - 1);
-$screen-md-max:              ($screen-lg-min - 1);
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-$grid-columns:              12;
-//** Padding between columns. Gets divided in half for the left and right.
-$grid-gutter-width:         ($baseWidth * 2);
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-$grid-float-breakpoint:     $screen-sm-min;
-//** Point at which the navbar begins collapsing.
-$grid-float-breakpoint-max: ($grid-float-breakpoint);
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-$container-tablet:             (720px + $grid-gutter-width);
-//** For `$screen-sm-min` and up.
-$container-sm:                 $container-tablet;
-
-// Medium screen / desktop
-$container-desktop:            (940px + $grid-gutter-width);
-//** For `$screen-md-min` and up.
-$container-md:                 $container-desktop;
-
-// Large screen / wide desktop
-$container-large-desktop:      (1140px + $grid-gutter-width);
-//** For `$screen-lg-min` and up.
-$container-lg:                 $container-large-desktop;
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-$navbar-height:                    0px;
-$navbar-margin-bottom:             $line-height-computed;
-$navbar-border-radius:             $border-radius-base;
-$navbar-padding-horizontal:        ($baseWidth * 2);
-$navbar-padding-vertical:          0;
-$navbar-collapse-max-height:       340px;
-
-$navbar-default-color:             $black;
-$navbar-default-bg:                $grayLight;
-$navbar-default-border:            $navbar-default-bg;
-
-// Navbar links
-$navbar-default-link-color:                $black;
-$navbar-default-link-hover-color:          $white;
-$navbar-default-link-hover-bg:             $black;
-$navbar-default-link-active-color:         $white;
-$navbar-default-link-active-bg:            $black;
-$navbar-default-link-disabled-color:       $gray;
-$navbar-default-link-disabled-bg:          transparent;
-
-// Navbar brand label
-$navbar-default-brand-color:               $navbar-default-link-color;
-$navbar-default-brand-hover-color:         $navbar-default-brand-color;
-$navbar-default-brand-hover-bg:            transparent;
-
-// Navbar toggle
-$navbar-default-toggle-hover-bg:           #ddd;
-$navbar-default-toggle-icon-bar-bg:        #888;
-$navbar-default-toggle-border-color:       #ddd;
-
-
-// Inverted navbar
-// Reset inverted navbar basics
-$navbar-inverse-color:                      $gray;
-$navbar-inverse-bg:                         $black;
-$navbar-inverse-border:                     $navbar-inverse-bg;
-
-// Inverted navbar links
-$navbar-inverse-link-color:                 $gray-light;
-$navbar-inverse-link-hover-color:           $black;
-$navbar-inverse-link-hover-bg:              $grayLight;
-$navbar-inverse-link-active-color:          $white;
-$navbar-inverse-link-active-bg:             $grayDark;
-$navbar-inverse-link-disabled-color:        $gray;
-$navbar-inverse-link-disabled-bg:           transparent;
-
-// Inverted navbar brand label
-$navbar-inverse-brand-color:                $navbar-inverse-link-color;
-$navbar-inverse-brand-hover-color:          #fff;
-$navbar-inverse-brand-hover-bg:             transparent;
-
-// Inverted navbar toggle
-$navbar-inverse-toggle-hover-bg:            $grayLight;
-$navbar-inverse-toggle-icon-bar-bg:         #fff;
-$navbar-inverse-toggle-border-color:        #333;
-
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-$nav-link-padding:                          0 $baseWidth;
-$nav-link-hover-bg:                         $gray-lighter;
-
-$nav-disabled-link-color:                   $gray-light;
-$nav-disabled-link-hover-color:             $gray-light;
-
-//== Tabs
-$nav-tabs-border-color:                     #ddd;
-
-$nav-tabs-link-hover-border-color:          $gray-lighter;
-
-$nav-tabs-active-link-hover-bg:             $black;
-$nav-tabs-active-link-hover-color:          $white;
-
-$nav-tabs-justified-active-link-border-color:     $body-bg;
-
-//== Pills
-$nav-pills-border-radius:                   $border-radius-base;
-$nav-pills-active-link-hover-bg:            $component-active-bg;
-$nav-pills-active-link-hover-color:         $component-active-color;
-
-
-//== Pagination
-//
-//##
-
-$pagination-color:                     $black;
-$pagination-bg:                        $gray;
-$pagination-border:                    #ddd;
-
-$pagination-hover-color:               $link-hover-color;
-$pagination-hover-bg:                  $gray-lighter;
-$pagination-hover-border:              #ddd;
-
-$pagination-active-color:              #fff;
-$pagination-active-bg:                 $brand-primary;
-$pagination-active-border:             $brand-primary;
-
-$pagination-disabled-color:            $gray-light;
-$pagination-disabled-bg:               #fff;
-$pagination-disabled-border:           #ddd;
-
-
-//== Pager
-//
-//##
-
-$pager-bg:                             $pagination-bg;
-$pager-border:                         $pagination-border;
-$pager-border-radius:                  0;
-
-$pager-hover-bg:                       $pagination-hover-bg;
-
-$pager-active-bg:                      $pagination-active-bg;
-$pager-active-color:                   $pagination-active-color;
-
-$pager-disabled-color:                 $pagination-disabled-color;
-
-
-//== Jumbotron
-//
-//##
-
-$jumbotron-padding:              ($ts) ($rhs + $baseWidth) ($bs) ($lhs + $baseWidth);
-$jumbotron-color:                $white;
-$jumbotron-bg:                   transparent;
-$jumbotron-heading-color:        inherit;
-$jumbotron-font-size:            $font-size-base;
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-$state-success-text:             $green;
-$state-success-bg:               $greenDark;
-$state-success-border:           $state-success-bg;
-
-$state-info-text:                $yellow;
-$state-info-bg:                  $brown;
-$state-info-border:              $state-info-bg;
-
-$state-warning-text:             $magenta;
-$state-warning-bg:               $magentaDark;
-$state-warning-border:           $state-warning-bg;
-
-$state-danger-text:              $red;
-$state-danger-bg:                $black;
-$state-danger-border:            $state-danger-bg;
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-$tooltip-max-width:           ($baseWidth * 25);
-//** Tooltip text color
-$tooltip-color:               $white;
-//** Tooltip background color
-$tooltip-bg:                  $grayDark;
-$tooltip-opacity:             1;
-
-//** Tooltip arrow width
-$tooltip-arrow-width:         0px;
-//** Tooltip arrow color
-$tooltip-arrow-color:         $tooltip-bg;
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-$popover-bg:                          $gray;
-//** Popover maximum width
-$popover-max-width:                   ($baseWidth * 20);
-//** Popover border color
-$popover-border-color:                rgb(0,0,0);
-//** Popover fallback border color
-$popover-fallback-border-color:       #ccc;
-
-//** Popover title background color
-$popover-title-bg:                    $greenDark;
-
-//** Popover arrow width
-$popover-arrow-width:                 10px;
-//** Popover arrow color
-$popover-arrow-color:                 $popover-bg;
-
-//** Popover outer arrow width
-$popover-arrow-outer-width:           ($popover-arrow-width + 1);
-//** Popover outer arrow color
-$popover-arrow-outer-color:           $popover-border-color;
-//** Popover outer arrow fallback color
-$popover-arrow-outer-fallback-color:  $popover-fallback-border-color;
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-$label-default-bg:            $gray-light;
-//** Primary label background color
-$label-primary-bg:            $brand-primary-bg;
-//** Success label background color
-$label-success-bg:            $brand-success;
-//** Info label background color
-$label-info-bg:               $brand-info;
-//** Warning label background color
-$label-warning-bg:            $brand-warning;
-//** Danger label background color
-$label-danger-bg:             $brand-danger;
-
-//** Default label text color
-$label-color:                 #fff;
-//** Default text color of a linked label
-$label-link-hover-color:      #fff;
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-$modal-inner-padding:         0 $baseWidth;
-
-//** Padding applied to the modal title
-$modal-title-padding:         0 $baseWidth;
-//** Modal title line-height
-$modal-title-line-height:     $line-height-base;
-
-//** Background color of modal content area
-$modal-content-bg:                             $gray;
-//** Modal content border color
-$modal-content-border-color:                   rgb(0,0,0);
-//** Modal content border color **for IE8**
-$modal-content-fallback-border-color:          #999;
-
-//** Modal backdrop background color
-$modal-backdrop-bg:           #000;
-//** Modal backdrop opacity
-// $modal-backdrop-opacity:      @include 5;
-//** Modal header border color
-$modal-header-border-color:   #e5e5e5;
-//** Modal footer border color
-$modal-footer-border-color:   $modal-header-border-color;
-
-$modal-lg:                    900px;
-$modal-md:                    600px;
-$modal-sm:                    300px;
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-$alert-padding:               $line-height-base ($baseWidth * 2);
-$alert-border-radius:         $border-radius-base;
-$alert-link-font-weight:      normal;
-
-$alert-success-bg:            $state-success-bg;
-$alert-success-text:          $state-success-text;
-$alert-success-border:        $state-success-border;
-
-$alert-info-bg:               $state-info-bg;
-$alert-info-text:             $state-info-text;
-$alert-info-border:           $state-info-border;
-
-$alert-warning-bg:            $state-warning-bg;
-$alert-warning-text:          $state-warning-text;
-$alert-warning-border:        $state-warning-border;
-
-$alert-danger-bg:             $state-danger-bg;
-$alert-danger-text:           $state-danger-text;
-$alert-danger-border:         $state-danger-border;
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-$progress-bg:                 $black;
-//** Progress bar text color
-$progress-bar-color:          $black;
-//** Variable for setting rounded corners on progress bar.
-$progress-border-radius:      $border-radius-base;
-
-//** Default progress bar color
-$progress-bar-bg:             $brand-primary;
-//** Success progress bar color
-$progress-bar-success-bg:     $brand-success;
-//** Warning progress bar color
-$progress-bar-warning-bg:     $brand-warning;
-//** Danger progress bar color
-$progress-bar-danger-bg:      $brand-danger;
-//** Info progress bar color
-$progress-bar-info-bg:        $brand-info;
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-$list-group-bg:                 $gray;
-//** `.list-group-item` border color
-$list-group-border:             #ddd;
-//** List group border radius
-$list-group-border-radius:      $border-radius-base;
-
-//** Background color of single list items on hover
-$list-group-hover-bg:           $black;
-//** Text color of active list items
-$list-group-active-color:       $component-active-color;
-//** Background color of active list items
-$list-group-active-bg:          $component-active-bg;
-//** Border color of active list elements
-$list-group-active-border:      $list-group-active-bg;
-//** Text color for content within active list items
-$list-group-active-text-color:  $component-active-color;
-
-//** Text color of disabled list items
-$list-group-disabled-color:      $gray-dark;
-//** Background color of disabled list items
-$list-group-disabled-bg:         $gray-lighter;
-//** Text color for content within disabled list items
-$list-group-disabled-text-color: $list-group-disabled-color;
-
-$list-group-link-color:         $black;
-$list-group-link-hover-color:   $list-group-link-color;
-$list-group-link-heading-color: #333;
-
-
-//== Panels
-//
-//##
-
-$panel-bg:                    $gray;
-$panel-body-padding:          0 $rhsNB 0 $lhsNB;
-$panel-heading-padding:       0 $rhsNB 0 $lhsNB;
-$panel-footer-padding:        $panel-heading-padding;
-$panel-border-radius:         $border-radius-base;
-
-//** Border color for elements within panels
-$panel-inner-border:          #ddd;
-$panel-footer-bg:             #f5f5f5;
-
-$panel-default-text:          $white;
-$panel-default-border:        #ddd;
-$panel-default-heading-bg:    $grayDark;
-
-$panel-primary-text:          $white;
-$panel-primary-border:        $brand-primary;
-$panel-primary-heading-bg:    $cyanDark;
-
-$panel-success-text:          $state-success-text;
-$panel-success-border:        $state-success-border;
-$panel-success-heading-bg:    $state-success-bg;
-
-$panel-info-text:             $state-info-text;
-$panel-info-border:           $state-info-border;
-$panel-info-heading-bg:       $state-info-bg;
-
-$panel-warning-text:          $state-warning-text;
-$panel-warning-border:        $state-warning-border;
-$panel-warning-heading-bg:    $state-warning-bg;
-
-$panel-danger-text:           $state-danger-text;
-$panel-danger-border:         $state-danger-border;
-$panel-danger-heading-bg:     $state-danger-bg;
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-$thumbnail-padding:           4px;
-//** Thumbnail background color
-$thumbnail-bg:                $body-bg;
-//** Thumbnail border color
-$thumbnail-border:            #ddd;
-//** Thumbnail border radius
-$thumbnail-border-radius:     $border-radius-base;
-
-//** Custom text color for thumbnail captions
-$thumbnail-caption-color:     $text-color;
-//** Padding around the thumbnail caption
-$thumbnail-caption-padding:   9px;
-
-
-//== Wells
-//
-//##
-
-$well-bg:                     $greenDark;
-$well-border:                 $well-bg;
-
-
-//== Badges
-//
-//##
-
-$badge-color:                 $black;
-//** Linked badge text color on hover
-$badge-link-hover-color:      #fff;
-$badge-bg:                    $gray-light;
-
-//** Badge text color in active nav link
-$badge-active-color:          $link-color;
-//** Badge background color in active nav link
-$badge-active-bg:             $black;
-
-$badge-font-weight:           normal;
-$badge-line-height:           $line-height-base;
-$badge-border-radius:         0;
-
-
-//== Breadcrumbs
-//
-//##
-
-$breadcrumb-padding-vertical:   8px;
-$breadcrumb-padding-horizontal: 15px;
-//** Breadcrumb background color
-$breadcrumb-bg:                 #f5f5f5;
-//** Breadcrumb text color
-$breadcrumb-color:              #ccc;
-//** Text color of current page in the breadcrumb
-$breadcrumb-active-color:       $gray-light;
-//** Textual separator for between breadcrumb elements
-$breadcrumb-separator:          "/";
-
-
-//== Carousel
-//
-//##
-
-$carousel-text-shadow:                        none;
-
-$carousel-control-color:                      #fff;
-$carousel-control-width:                      15%;
-$carousel-control-opacity:                    1;
-$carousel-control-font-size:                  $font-size-base;
-
-$carousel-indicator-active-bg:                #fff;
-$carousel-indicator-border-color:             #fff;
-
-$carousel-caption-color:                      #fff;
-
-
-//== Close
-//
-//##
-
-$close-font-weight:           normal;
-$close-color:                 #000;
-$close-text-shadow:           none;
-
-
-//== Code
-//
-//##
-
-$code-color:                  #c7254e;
-$code-bg:                     #f9f2f4;
-
-$kbd-color:                   #fff;
-$kbd-bg:                      #333;
-
-$pre-bg:                      #f5f5f5;
-$pre-color:                   $gray-dark;
-$pre-border-color:            #ccc;
-$pre-scrollable-max-height:   340px;
-
-
-//== Type
-//
-//##
-
-//** Horizontal offset for forms and lists.
-$component-offset-horizontal: 180px;
-//** Text muted color
-$text-muted:                  $gray-dark;
-//** Abbreviations and acronyms border color
-$abbr-border-color:           $gray-light;
-//** Headings small color
-$headings-small-color:        $gray-light;
-//** Blockquote small color
-$blockquote-small-color:      $gray-light;
-//** Blockquote font size
-$blockquote-font-size:        $font-size-base;
-//** Blockquote border color
-$blockquote-border-color:     $gray-lighter;
-//** Page header border color
-$page-header-border-color:    $gray-lighter;
-//** Width of horizontal description list titles
-$dl-horizontal-offset:        $component-offset-horizontal;
-//** Horizontal line color.
-$hr-border:                   $black;
diff --git a/ui/assets/css/themes/_variables.i386.scss b/ui/assets/css/themes/_variables.i386.scss
deleted file mode 100644
index aadc3c0e..00000000
--- a/ui/assets/css/themes/_variables.i386.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-
-$blue: #5555Ff;
-$cyan: #55FFFF;
-$green: #55FF55;
-$indigo: #FF55FF;
-$red: #FF5555;
-$yellow: #FEFE54;
-$orange: #A85400;
-$pink: #FE54FE;
-$purple: #FE5454;
-$primary: #FEFE54;
-$body-bg: #000084;
-$gray-300: #bbb;
-$body-color: $gray-300;
-$link-hover-color: $white;
-$font-family-sans-serif: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
-$font-family-monospace: DOS, Monaco, Menlo, Consolas, "Courier New", monospace;
-$navbar-dark-color: $gray-300;
-$navbar-light-brand-color: $gray-300;
-$success: #00AA00;
-$danger: #AA0000;
-$info: #00AAAA;
-$warning: #AA00AA;
-$navbar-dark-active-color: $gray-100;
-$enable-rounded: false;
-$input-color: $white;
-$input-bg: rgb(102, 102, 102);
-$input-disabled-bg: $gray-800;
-$nav-tabs-link-active-color: $gray-100;
-$navbar-dark-hover-color: rgba($gray-300, .75);
-$light: $gray-800;
-$navbar-light-disabled-color: $gray-800;
-$navbar-light-active-color: $gray-100;
-$navbar-light-hover-color: $gray-200;
-$navbar-light-color: $gray-300;
-$card-bg: $gray-800;
-$card-border-color: $white;
-$input-placeholder-color: $gray-500;
-$mark-bg: #463b00;
-$secondary: $gray-900;
\ No newline at end of file
diff --git a/ui/assets/css/themes/i386.min.css b/ui/assets/css/themes/i386.min.css
deleted file mode 100644
index 689469ef..00000000
--- a/ui/assets/css/themes/i386.min.css
+++ /dev/null
@@ -1 +0,0 @@
-:root{--blue:#5555Ff;--indigo:#FF55FF;--purple:#FE5454;--pink:#FE54FE;--red:#FF5555;--orange:#A85400;--yellow:#FEFE54;--green:#55FF55;--teal:#20c997;--cyan:#55FFFF;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#FEFE54;--secondary:#212529;--success:#00AA00;--info:#00AAAA;--warning:#AA00AA;--danger:#AA0000;--light:#343a40;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:DOS,Monaco,Menlo,Consolas,"Courier New",monospace;--font-family-monospace:DOS,Monaco,Menlo,Consolas,"Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:DOS,Monaco,Menlo,Consolas,"Courier New",monospace;font-size:1rem;font-weight:400;line-height:1.5;color:#bbb;text-align:left;background-color:#000084}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#fefe54;text-decoration:none;background-color:transparent}a:hover{color:#fff;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:DOS,Monaco,Menlo,Consolas,"Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#463b00}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#000084;border:1px solid #bbb;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#fe54fe;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:flex;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{flex-basis:0;flex-grow:1;max-width:100%}.col-auto{flex:0 0 auto;width:auto;max-width:100%}.col-1{flex:0 0 8.33333%;max-width:8.33333%}.col-2{flex:0 0 16.66667%;max-width:16.66667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333%;max-width:33.33333%}.col-5{flex:0 0 41.66667%;max-width:41.66667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333%;max-width:58.33333%}.col-8{flex:0 0 66.66667%;max-width:66.66667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333%;max-width:83.33333%}.col-11{flex:0 0 91.66667%;max-width:91.66667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-5{margin-left:41.66667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333%}.offset-8{margin-left:66.66667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333%}.offset-11{margin-left:91.66667%}@media (min-width:576px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.col-sm-auto{flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{flex:0 0 8.33333%;max-width:8.33333%}.col-sm-2{flex:0 0 16.66667%;max-width:16.66667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333%;max-width:33.33333%}.col-sm-5{flex:0 0 41.66667%;max-width:41.66667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333%;max-width:58.33333%}.col-sm-8{flex:0 0 66.66667%;max-width:66.66667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333%;max-width:83.33333%}.col-sm-11{flex:0 0 91.66667%;max-width:91.66667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333%}.offset-sm-2{margin-left:16.66667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333%}.offset-sm-5{margin-left:41.66667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333%}.offset-sm-8{margin-left:66.66667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333%}.offset-sm-11{margin-left:91.66667%}}@media (min-width:768px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.col-md-auto{flex:0 0 auto;width:auto;max-width:100%}.col-md-1{flex:0 0 8.33333%;max-width:8.33333%}.col-md-2{flex:0 0 16.66667%;max-width:16.66667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333%;max-width:33.33333%}.col-md-5{flex:0 0 41.66667%;max-width:41.66667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333%;max-width:58.33333%}.col-md-8{flex:0 0 66.66667%;max-width:66.66667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333%;max-width:83.33333%}.col-md-11{flex:0 0 91.66667%;max-width:91.66667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333%}.offset-md-2{margin-left:16.66667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333%}.offset-md-5{margin-left:41.66667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333%}.offset-md-8{margin-left:66.66667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333%}.offset-md-11{margin-left:91.66667%}}@media (min-width:992px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.col-lg-auto{flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{flex:0 0 8.33333%;max-width:8.33333%}.col-lg-2{flex:0 0 16.66667%;max-width:16.66667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333%;max-width:33.33333%}.col-lg-5{flex:0 0 41.66667%;max-width:41.66667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333%;max-width:58.33333%}.col-lg-8{flex:0 0 66.66667%;max-width:66.66667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333%;max-width:83.33333%}.col-lg-11{flex:0 0 91.66667%;max-width:91.66667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333%}.offset-lg-2{margin-left:16.66667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333%}.offset-lg-5{margin-left:41.66667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333%}.offset-lg-8{margin-left:66.66667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333%}.offset-lg-11{margin-left:91.66667%}}@media (min-width:1200px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.col-xl-auto{flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{flex:0 0 8.33333%;max-width:8.33333%}.col-xl-2{flex:0 0 16.66667%;max-width:16.66667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333%;max-width:33.33333%}.col-xl-5{flex:0 0 41.66667%;max-width:41.66667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333%;max-width:58.33333%}.col-xl-8{flex:0 0 66.66667%;max-width:66.66667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333%;max-width:83.33333%}.col-xl-11{flex:0 0 91.66667%;max-width:91.66667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333%}.offset-xl-2{margin-left:16.66667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333%}.offset-xl-5{margin-left:41.66667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333%}.offset-xl-8{margin-left:66.66667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333%}.offset-xl-11{margin-left:91.66667%}}.table{width:100%;margin-bottom:1rem;color:#bbb}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #bbb}.table thead th{vertical-align:bottom;border-bottom:2px solid #bbb}.table tbody+tbody{border-top:2px solid #bbb}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #bbb}.table-bordered td,.table-bordered th{border:1px solid #bbb}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#bbb;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#ffffcf}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#fefea6}.table-hover .table-primary:hover{background-color:#ffffb6}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#ffffb6}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#c1c2c3}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#8c8e90}.table-hover .table-secondary:hover{background-color:#b4b5b6}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#b4b5b6}.table-success,.table-success>td,.table-success>th{background-color:#b8e7b8}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#7ad37a}.table-hover .table-success:hover{background-color:#a5e1a5}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#a5e1a5}.table-info,.table-info>td,.table-info>th{background-color:#b8e7e7}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#7ad3d3}.table-hover .table-info:hover{background-color:#a5e1e1}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#a5e1e1}.table-warning,.table-warning>td,.table-warning>th{background-color:#e7b8e7}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#d37ad3}.table-hover .table-warning:hover{background-color:#e1a5e1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#e1a5e1}.table-danger,.table-danger>td,.table-danger>th{background-color:#e7b8b8}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#d37a7a}.table-hover .table-danger:hover{background-color:#e1a5a5}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#e1a5a5}.table-light,.table-light>td,.table-light>th{background-color:#c6c8ca}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#95999c}.table-hover .table-light:hover{background-color:#b9bbbe}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#b9bbbe}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#bbb}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#fff;background-color:#666;background-clip:padding-box;border:1px solid #ced4da;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#fff;background-color:#666;border-color:#ffffd3;outline:0;box-shadow:0 0 0 .2rem rgba(254,254,84,.25)}.form-control::placeholder{color:#adb5bd;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#343a40;opacity:1}select.form-control:focus::-ms-value{color:#fff;background-color:#666}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#bbb;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:inline-flex;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#0a0}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(0,170,0,.9)}.form-control.is-valid,.was-validated .form-control:valid{border-color:#0a0;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2300AA00' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#0a0;box-shadow:0 0 0 .2rem rgba(0,170,0,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#0a0;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2300AA00' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #666 no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#0a0;box-shadow:0 0 0 .2rem rgba(0,170,0,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#0a0}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#0a0}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#0a0}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#0d0;background-color:#0d0}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,170,0,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#0a0}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#0a0}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#0a0;box-shadow:0 0 0 .2rem rgba(0,170,0,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#a00}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(170,0,0,.9)}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#a00;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23AA0000' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23AA0000' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#a00;box-shadow:0 0 0 .2rem rgba(170,0,0,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#a00;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23AA0000' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23AA0000' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #666 no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#a00;box-shadow:0 0 0 .2rem rgba(170,0,0,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#a00}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#a00}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#a00}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#d00;background-color:#d00}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(170,0,0,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#a00}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#a00}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#a00;box-shadow:0 0 0 .2rem rgba(170,0,0,.25)}.form-inline{display:flex;flex-flow:row wrap;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:flex;align-items:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:flex;flex:0 0 auto;flex-flow:row wrap;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:flex;align-items:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#bbb;text-align:center;vertical-align:middle;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#bbb;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(254,254,84,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#212529;background-color:#fefe54;border-color:#fefe54}.btn-primary:hover{color:#212529;background-color:#fefe2e;border-color:#fefe21}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(221,221,78,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#212529;background-color:#fefe54;border-color:#fefe54}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#212529;background-color:#fefe21;border-color:#fefe15}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(221,221,78,.5)}.btn-secondary{color:#fff;background-color:#212529;border-color:#212529}.btn-secondary:hover{color:#fff;background-color:#101214;border-color:#0a0c0d}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(66,70,73,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#0a0c0d;border-color:#050506}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(66,70,73,.5)}.btn-success{color:#fff;background-color:#0a0;border-color:#0a0}.btn-success:hover{color:#fff;background-color:#008400;border-color:#070}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(38,183,38,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#0a0;border-color:#0a0}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#070;border-color:#006a00}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,183,38,.5)}.btn-info{color:#fff;background-color:#0aa;border-color:#0aa}.btn-info:hover{color:#fff;background-color:#008484;border-color:#077}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(38,183,183,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#0aa;border-color:#0aa}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#077;border-color:#006a6a}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,183,183,.5)}.btn-warning{color:#fff;background-color:#a0a;border-color:#a0a}.btn-warning:hover{color:#fff;background-color:#840084;border-color:#707}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(183,38,183,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#a0a;border-color:#a0a}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#707;border-color:#6a006a}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(183,38,183,.5)}.btn-danger{color:#fff;background-color:#a00;border-color:#a00}.btn-danger:hover{color:#fff;background-color:#840000;border-color:#700}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(183,38,38,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#a00;border-color:#a00}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#700;border-color:#6a0000}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(183,38,38,.5)}.btn-light{color:#fff;background-color:#343a40;border-color:#343a40}.btn-light:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-light.disabled,.btn-light:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#fefe54;border-color:#fefe54}.btn-outline-primary:hover{color:#212529;background-color:#fefe54;border-color:#fefe54}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(254,254,84,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#fefe54;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#212529;background-color:#fefe54;border-color:#fefe54}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(254,254,84,.5)}.btn-outline-secondary{color:#212529;border-color:#212529}.btn-outline-secondary:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(33,37,41,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#212529;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(33,37,41,.5)}.btn-outline-success{color:#0a0;border-color:#0a0}.btn-outline-success:hover{color:#fff;background-color:#0a0;border-color:#0a0}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(0,170,0,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#0a0;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#0a0;border-color:#0a0}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,170,0,.5)}.btn-outline-info{color:#0aa;border-color:#0aa}.btn-outline-info:hover{color:#fff;background-color:#0aa;border-color:#0aa}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(0,170,170,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0aa;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#0aa;border-color:#0aa}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,170,170,.5)}.btn-outline-warning{color:#a0a;border-color:#a0a}.btn-outline-warning:hover{color:#fff;background-color:#a0a;border-color:#a0a}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(170,0,170,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#a0a;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#a0a;border-color:#a0a}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(170,0,170,.5)}.btn-outline-danger{color:#a00;border-color:#a00}.btn-outline-danger:hover{color:#fff;background-color:#a00;border-color:#a00}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(170,0,0,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#a00;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#a00;border-color:#a00}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(170,0,0,.5)}.btn-outline-light{color:#343a40;border-color:#343a40}.btn-outline-light:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#343a40;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#fefe54;text-decoration:none}.btn-link:hover{color:#fff;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:0}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:0}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#bbb;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15)}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#fefe54}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-file{display:flex;align-items:center}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#fff;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#fefe54;background-color:#fefe54}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(254,254,84,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#ffffd3}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#fff;border-color:#fff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#343a40}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#666;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#fefe54;background-color:#fefe54}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(254,254,84,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(254,254,84,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(254,254,84,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#666;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(254,254,84,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#fff;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#666;border:1px solid #ced4da;border-radius:0;appearance:none}.custom-select:focus{border-color:#ffffd3;outline:0;box-shadow:0 0 0 .2rem rgba(254,254,84,.25)}.custom-select:focus::-ms-value{color:#fff;background-color:#666}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#ffffd3;box-shadow:0 0 0 .2rem rgba(254,254,84,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#343a40}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#fff;background-color:#666;border:1px solid #ced4da}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#fff;content:"Browse";background-color:#e9ecef;border-left:inherit}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #000084,0 0 0 .2rem rgba(254,254,84,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #000084,0 0 0 .2rem rgba(254,254,84,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #000084,0 0 0 .2rem rgba(254,254,84,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#fefe54;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#fff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#bbb;border-color:transparent}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#fefe54;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#fff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#bbb;border-color:transparent}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#fefe54;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#fff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#bbb}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#bbb}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #bbb}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #bbb}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#f8f9fa;background-color:#000084;border-color:#bbb #bbb #000084}.nav-tabs .dropdown-menu{margin-top:-1px}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#fefe54}.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:#bbb}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:#f8f9fa}.navbar-light .navbar-nav .nav-link{color:#bbb}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:#e9ecef}.navbar-light .navbar-nav .nav-link.disabled{color:#343a40}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:#f8f9fa}.navbar-light .navbar-toggler{color:#bbb;border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='%23bbb' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#bbb}.navbar-light .navbar-text a{color:#f8f9fa}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:#f8f9fa}.navbar-dark .navbar-brand{color:#f8f9fa}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#f8f9fa}.navbar-dark .navbar-nav .nav-link{color:#bbb}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(187,187,187,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#f8f9fa}.navbar-dark .navbar-toggler{color:#bbb;border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='%23bbb' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#bbb}.navbar-dark .navbar-text a{color:#f8f9fa}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#f8f9fa}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#343a40;background-clip:border-box;border:1px solid #fff}.card>hr{margin-right:0;margin-left:0}.card-body{flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid #fff}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid #fff}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%}.card-img-top{width:100%}.card-img-bottom{width:100%}.card-deck{display:flex;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:flex;flex:1 0 0%;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:flex;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{column-count:3;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0}.accordion>.card:first-of-type{border-bottom:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:flex;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#fefe54;background-color:#fff;border:1px solid #bbb}.page-link:hover{z-index:2;color:#fff;text-decoration:none;background-color:#e9ecef;border-color:#bbb}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(254,254,84,.25)}.page-item:first-child .page-link{margin-left:0}.page-item.active .page-link{z-index:1;color:#fff;background-color:#fefe54;border-color:#fefe54}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#bbb}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em}.badge-primary{color:#212529;background-color:#fefe54}a.badge-primary:focus,a.badge-primary:hover{color:#212529;background-color:#fefe21}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(254,254,84,.5)}.badge-secondary{color:#fff;background-color:#212529}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#0a0c0d}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(33,37,41,.5)}.badge-success{color:#fff;background-color:#0a0}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#070}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,170,0,.5)}.badge-info{color:#fff;background-color:#0aa}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#077}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,170,170,.5)}.badge-warning{color:#fff;background-color:#a0a}a.badge-warning:focus,a.badge-warning:hover{color:#fff;background-color:#707}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(170,0,170,.5)}.badge-danger{color:#fff;background-color:#a00}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#700}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(170,0,0,.5)}.badge-light{color:#fff;background-color:#343a40}a.badge-light:focus,a.badge-light:hover{color:#fff;background-color:#1d2124}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#84842c;background-color:#ffd;border-color:#ffffcf}.alert-primary hr{border-top-color:#ffffb6}.alert-primary .alert-link{color:#5e5e1f}.alert-secondary{color:#111315;background-color:#d3d3d4;border-color:#c1c2c3}.alert-secondary hr{border-top-color:#b4b5b6}.alert-secondary .alert-link{color:#000}.alert-success{color:#005800;background-color:#cec;border-color:#b8e7b8}.alert-success hr{border-top-color:#a5e1a5}.alert-success .alert-link{color:#002500}.alert-info{color:#005858;background-color:#cee;border-color:#b8e7e7}.alert-info hr{border-top-color:#a5e1e1}.alert-info .alert-link{color:#002525}.alert-warning{color:#580058;background-color:#ece;border-color:#e7b8e7}.alert-warning hr{border-top-color:#e1a5e1}.alert-warning .alert-link{color:#250025}.alert-danger{color:#580000;background-color:#ecc;border-color:#e7b8b8}.alert-danger hr{border-top-color:#e1a5a5}.alert-danger .alert-link{color:#250000}.alert-light{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-light hr{border-top-color:#b9bbbe}.alert-light .alert-link{color:#040505}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef}.progress-bar{display:flex;flex-direction:column;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#fefe54;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{display:flex;align-items:flex-start}.media-body{flex:1}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#bbb;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:last-child{margin-bottom:0}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#fefe54;border-color:#fefe54}.list-group-horizontal{flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:last-child{margin-right:0}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0}}.list-group-flush .list-group-item{border-right:0;border-left:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#84842c;background-color:#ffffcf}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#84842c;background-color:#ffffb6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#84842c;border-color:#84842c}.list-group-item-secondary{color:#111315;background-color:#c1c2c3}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#111315;background-color:#b4b5b6}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#111315;border-color:#111315}.list-group-item-success{color:#005800;background-color:#b8e7b8}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#005800;background-color:#a5e1a5}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#005800;border-color:#005800}.list-group-item-info{color:#005858;background-color:#b8e7e7}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#005858;background-color:#a5e1e1}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#005858;border-color:#005858}.list-group-item-warning{color:#580058;background-color:#e7b8e7}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#580058;background-color:#e1a5e1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#580058;border-color:#580058}.list-group-item-danger{color:#580000;background-color:#e7b8b8}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#580000;background-color:#e1a5a5}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#580000;border-color:#580000}.list-group-item-light{color:#1b1e21;background-color:#c6c8ca}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);backdrop-filter:blur(10px);opacity:0}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:flex;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #bbb}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;align-items:center;justify-content:flex-end;padding:1rem;border-top:1px solid #bbb}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:DOS,Monaco,Menlo,Consolas,"Courier New",monospace;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:DOS,Monaco,Menlo,Consolas,"Courier New",monospace;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2)}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#bbb}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:flex;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#fefe54!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#fefe21!important}.bg-secondary{background-color:#212529!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#0a0c0d!important}.bg-success{background-color:#0a0!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#070!important}.bg-info{background-color:#0aa!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#077!important}.bg-warning{background-color:#a0a!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#707!important}.bg-danger{background-color:#a00!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#700!important}.bg-light{background-color:#343a40!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#1d2124!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #bbb!important}.border-top{border-top:1px solid #bbb!important}.border-right{border-right:1px solid #bbb!important}.border-bottom{border-bottom:1px solid #bbb!important}.border-left{border-left:1px solid #bbb!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#fefe54!important}.border-secondary{border-color:#212529!important}.border-success{border-color:#0a0!important}.border-info{border-color:#0aa!important}.border-warning{border-color:#a0a!important}.border-danger{border-color:#a00!important}.border-light{border-color:#343a40!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.85714%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:576px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:768px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:DOS,Monaco,Menlo,Consolas,"Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#fefe54!important}a.text-primary:focus,a.text-primary:hover{color:#fefe08!important}.text-secondary{color:#212529!important}a.text-secondary:focus,a.text-secondary:hover{color:#000!important}.text-success{color:#0a0!important}a.text-success:focus,a.text-success:hover{color:#005e00!important}.text-info{color:#0aa!important}a.text-info:focus,a.text-info:hover{color:#005e5e!important}.text-warning{color:#a0a!important}a.text-warning:focus,a.text-warning:hover{color:#5e005e!important}.text-danger{color:#a00!important}a.text-danger:focus,a.text-danger:hover{color:#5e0000!important}.text-light{color:#343a40!important}a.text-light:focus,a.text-light:hover{color:#121416!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#bbb!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #bbb!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#bbb}.table .thead-dark th{color:inherit;border-color:#bbb}}.mark{background-color:#463b00}
\ No newline at end of file
diff --git a/ui/assets/css/themes/materia.min.css b/ui/assets/css/themes/materia.min.css
deleted file mode 100644
index 97110535..00000000
--- a/ui/assets/css/themes/materia.min.css
+++ /dev/null
@@ -1,12 +0,0 @@
-/*!
- * Bootswatch v4.4.1
- * Homepage: https://bootswatch.com
- * Copyright 2012-2019 Thomas Park
- * Licensed under MIT
- * Based on Bootstrap
-*//*!
- * Bootstrap v4.4.1 (https://getbootstrap.com/)
- * Copyright 2011-2019 The Bootstrap Authors
- * Copyright 2011-2019 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- */@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700");:root{--blue: #2196F3;--indigo: #6610f2;--purple: #6f42c1;--pink: #e83e8c;--red: #e51c23;--orange: #fd7e14;--yellow: #ff9800;--green: #4CAF50;--teal: #20c997;--cyan: #9C27B0;--white: #fff;--gray: #666;--gray-dark: #222;--primary: #2196F3;--secondary: #fff;--success: #4CAF50;--info: #9C27B0;--warning: #ff9800;--danger: #e51c23;--light: #fff;--dark: #222;--breakpoint-xs: 0;--breakpoint-sm: 576px;--breakpoint-md: 768px;--breakpoint-lg: 992px;--breakpoint-xl: 1200px;--font-family-sans-serif: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}*,*::before,*::after{-webkit-box-sizing:border-box;box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-size:0.8125rem;font-weight:400;line-height:1.5;color:#444;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0 !important}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0.5rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-original-title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#2196F3;text-decoration:none;background-color:transparent}a:hover{color:#0a6ebd;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:0.75rem;padding-bottom:0.75rem;color:#666;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:0.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button:not(:disabled),[type="button"]:not(:disabled),[type="reset"]:not(:disabled),[type="submit"]:not(:disabled){cursor:pointer}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{padding:0;border-style:none}input[type="radio"],input[type="checkbox"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{outline-offset:-2px;-webkit-appearance:none}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none !important}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{margin-bottom:0.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:2.03125rem}h2,.h2{font-size:1.625rem}h3,.h3{font-size:1.421875rem}h4,.h4{font-size:1.21875rem}h5,.h5{font-size:1.015625rem}h6,.h6{font-size:0.8125rem}.lead{font-size:1.015625rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,0.1)}small,.small{font-size:80%;font-weight:400}mark,.mark{padding:0.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:0.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.015625rem}.blockquote-footer{display:block;font-size:80%;color:#666}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:0.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:0.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:0.5rem;line-height:1}.figure-caption{font-size:90%;color:#666}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:0.2rem 0.4rem;font-size:87.5%;color:#fff;background-color:#212121;border-radius:0.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212121}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}.container-fluid,.container-sm,.container-md,.container-lg,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width: 576px){.container,.container-sm{max-width:540px}}@media (min-width: 768px){.container,.container-sm,.container-md{max-width:720px}}@media (min-width: 992px){.container,.container-sm,.container-md,.container-lg{max-width:960px}}@media (min-width: 1200px){.container,.container-sm,.container-md,.container-lg,.container-xl{max-width:1140px}}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*="col-"]{padding-right:0;padding-left:0}.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col,.col-auto,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm,.col-sm-auto,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md,.col-md-auto,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg,.col-lg-auto,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-4>*{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-5{margin-left:41.6666666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333333333%}.offset-8{margin-left:66.6666666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333333333%}.offset-11{margin-left:91.6666666667%}@media (min-width: 576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-sm-4>*{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.3333333333%}.offset-sm-2{margin-left:16.6666666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333333333%}.offset-sm-5{margin-left:41.6666666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333333333%}.offset-sm-8{margin-left:66.6666666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333333333%}.offset-sm-11{margin-left:91.6666666667%}}@media (min-width: 768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-md-4>*{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.3333333333%}.offset-md-2{margin-left:16.6666666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333333333%}.offset-md-5{margin-left:41.6666666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333333333%}.offset-md-8{margin-left:66.6666666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333333333%}.offset-md-11{margin-left:91.6666666667%}}@media (min-width: 992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-lg-4>*{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.3333333333%}.offset-lg-2{margin-left:16.6666666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333333333%}.offset-lg-5{margin-left:41.6666666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333333333%}.offset-lg-8{margin-left:66.6666666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333333333%}.offset-lg-11{margin-left:91.6666666667%}}@media (min-width: 1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-xl-4>*{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-webkit-box-flex:0;-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.3333333333%;flex:0 0 8.3333333333%;max-width:8.3333333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.6666666667%;flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.3333333333%;flex:0 0 33.3333333333%;max-width:33.3333333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.6666666667%;flex:0 0 41.6666666667%;max-width:41.6666666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.3333333333%;flex:0 0 58.3333333333%;max-width:58.3333333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.6666666667%;flex:0 0 66.6666666667%;max-width:66.6666666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.3333333333%;flex:0 0 83.3333333333%;max-width:83.3333333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.6666666667%;flex:0 0 91.6666666667%;max-width:91.6666666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.3333333333%}.offset-xl-2{margin-left:16.6666666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333333333%}.offset-xl-5{margin-left:41.6666666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333333333%}.offset-xl-8{margin-left:66.6666666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333333333%}.offset-xl-11{margin-left:91.6666666667%}}.table{width:100%;margin-bottom:1rem;color:#444}.table th,.table td{padding:0.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm th,.table-sm td{padding:0.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered th,.table-bordered td{border:1px solid #dee2e6}.table-bordered thead th,.table-bordered thead td{border-bottom-width:2px}.table-borderless th,.table-borderless td,.table-borderless thead th,.table-borderless tbody+tbody{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,0.05)}.table-hover tbody tr:hover{color:#444;background-color:rgba(0,0,0,0.075)}.table-primary,.table-primary>th,.table-primary>td{background-color:#c1e2fc}.table-primary th,.table-primary td,.table-primary thead th,.table-primary tbody+tbody{border-color:#8cc8f9}.table-hover .table-primary:hover{background-color:#a9d7fb}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#a9d7fb}.table-secondary,.table-secondary>th,.table-secondary>td{background-color:white}.table-secondary th,.table-secondary td,.table-secondary thead th,.table-secondary tbody+tbody{border-color:white}.table-hover .table-secondary:hover{background-color:#f2f2f2}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#f2f2f2}.table-success,.table-success>th,.table-success>td{background-color:#cde9ce}.table-success th,.table-success td,.table-success thead th,.table-success tbody+tbody{border-color:#a2d5a4}.table-hover .table-success:hover{background-color:#bbe1bd}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#bbe1bd}.table-info,.table-info>th,.table-info>td{background-color:#e3c3e9}.table-info th,.table-info td,.table-info thead th,.table-info tbody+tbody{border-color:#cc8fd6}.table-hover .table-info:hover{background-color:#dab0e2}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#dab0e2}.table-warning,.table-warning>th,.table-warning>td{background-color:#ffe2b8}.table-warning th,.table-warning td,.table-warning thead th,.table-warning tbody+tbody{border-color:#ffc97a}.table-hover .table-warning:hover{background-color:#ffd89f}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffd89f}.table-danger,.table-danger>th,.table-danger>td{background-color:#f8bfc1}.table-danger th,.table-danger td,.table-danger thead th,.table-danger tbody+tbody{border-color:#f1898d}.table-hover .table-danger:hover{background-color:#f5a8ab}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f5a8ab}.table-light,.table-light>th,.table-light>td{background-color:white}.table-light th,.table-light td,.table-light thead th,.table-light tbody+tbody{border-color:white}.table-hover .table-light:hover{background-color:#f2f2f2}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#f2f2f2}.table-dark,.table-dark>th,.table-dark>td{background-color:#c1c1c1}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#8c8c8c}.table-hover .table-dark:hover{background-color:#b4b4b4}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b4b4b4}.table-active,.table-active>th,.table-active>td{background-color:rgba(0,0,0,0.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,0.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,0.075)}.table .thead-dark th{color:#fff;background-color:#222;border-color:#353535}.table .thead-light th{color:#444;background-color:#eee;border-color:#dee2e6}.table-dark{color:#fff;background-color:#222}.table-dark th,.table-dark td,.table-dark thead th{border-color:#353535}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,0.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,0.075)}@media (max-width: 575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width: 767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width: 991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width: 1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + 2rem);padding:1rem 0;font-size:0.8125rem;font-weight:400;line-height:1.5;color:#666;background-color:transparent;background-clip:padding-box;border:0rem solid transparent;border-radius:0;-webkit-transition:border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control{-webkit-transition:none;transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #666}.form-control:focus{color:#666;background-color:transparent;border-color:#9acffa;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25);box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25)}.form-control::-webkit-input-placeholder{color:rgba(0,0,0,0.4);opacity:1}.form-control::-ms-input-placeholder{color:rgba(0,0,0,0.4);opacity:1}.form-control::placeholder{color:rgba(0,0,0,0.4);opacity:1}.form-control:disabled,.form-control[readonly]{background-color:transparent;opacity:1}select.form-control:focus::-ms-value{color:#666;background-color:transparent}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:1rem;padding-bottom:1rem;margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:1.015625rem;padding-bottom:1.015625rem;font-size:1.015625rem;line-height:1.5}.col-form-label-sm{padding-top:0rem;padding-bottom:0rem;font-size:0.7109375rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:1rem 0;margin-bottom:0;font-size:0.8125rem;line-height:1.5;color:#444;background-color:transparent;border:solid transparent;border-width:0rem 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + 0rem);padding:0 0;font-size:0.7109375rem;line-height:1.5;border-radius:0}.form-control-lg{height:calc(1.5em + 2.03125rem);padding:1.015625rem 0;font-size:1.015625rem;line-height:1.5;border-radius:0}select.form-control[size],select.form-control[multiple]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:0.25rem}.form-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*="col-"]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:0.3rem;margin-left:-1.25rem}.form-check-input[disabled] ~ .form-check-label,.form-check-input:disabled ~ .form-check-label{color:#666}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:0.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:0.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:80%;color:#4CAF50}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:0.25rem 0.5rem;margin-top:.1rem;font-size:0.7109375rem;line-height:1.5;color:#fff;background-color:rgba(76,175,80,0.9);border-radius:0.25rem}.was-validated :valid ~ .valid-feedback,.was-validated :valid ~ .valid-tooltip,.is-valid ~ .valid-feedback,.is-valid ~ .valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#4CAF50;padding-right:calc(1.5em + 2rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%234CAF50' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.5rem) center;background-size:calc(0.75em + 1rem) calc(0.75em + 1rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#4CAF50;-webkit-box-shadow:0 0 0 0.2rem rgba(76,175,80,0.25);box-shadow:0 0 0 0.2rem rgba(76,175,80,0.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 2rem);background-position:top calc(0.375em + 0.5rem) right calc(0.375em + 0.5rem)}.was-validated .custom-select:valid,.custom-select.is-valid{border-color:#4CAF50;padding-right:calc(0.75em + 2.5rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23222' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0 center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%234CAF50' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") transparent no-repeat center right 1rem/calc(0.75em + 1rem) calc(0.75em + 1rem)}.was-validated .custom-select:valid:focus,.custom-select.is-valid:focus{border-color:#4CAF50;-webkit-box-shadow:0 0 0 0.2rem rgba(76,175,80,0.25);box-shadow:0 0 0 0.2rem rgba(76,175,80,0.25)}.was-validated .form-check-input:valid ~ .form-check-label,.form-check-input.is-valid ~ .form-check-label{color:#4CAF50}.was-validated .form-check-input:valid ~ .valid-feedback,.was-validated .form-check-input:valid ~ .valid-tooltip,.form-check-input.is-valid ~ .valid-feedback,.form-check-input.is-valid ~ .valid-tooltip{display:block}.was-validated .custom-control-input:valid ~ .custom-control-label,.custom-control-input.is-valid ~ .custom-control-label{color:#4CAF50}.was-validated .custom-control-input:valid ~ .custom-control-label::before,.custom-control-input.is-valid ~ .custom-control-label::before{border-color:#4CAF50}.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before,.custom-control-input.is-valid:checked ~ .custom-control-label::before{border-color:#6ec071;background:#6ec071 -webkit-gradient(linear, left top, left bottom, from(#84c987), to(#6ec071)) repeat-x;background:#6ec071 linear-gradient(180deg, #84c987, #6ec071) repeat-x}.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before,.custom-control-input.is-valid:focus ~ .custom-control-label::before{-webkit-box-shadow:0 0 0 0.2rem rgba(76,175,80,0.25);box-shadow:0 0 0 0.2rem rgba(76,175,80,0.25)}.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before,.custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before{border-color:#4CAF50}.was-validated .custom-file-input:valid ~ .custom-file-label,.custom-file-input.is-valid ~ .custom-file-label{border-color:#4CAF50}.was-validated .custom-file-input:valid:focus ~ .custom-file-label,.custom-file-input.is-valid:focus ~ .custom-file-label{border-color:#4CAF50;-webkit-box-shadow:0 0 0 0.2rem rgba(76,175,80,0.25);box-shadow:0 0 0 0.2rem rgba(76,175,80,0.25)}.invalid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:80%;color:#e51c23}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:0.25rem 0.5rem;margin-top:.1rem;font-size:0.7109375rem;line-height:1.5;color:#fff;background-color:rgba(229,28,35,0.9);border-radius:0.25rem}.was-validated :invalid ~ .invalid-feedback,.was-validated :invalid ~ .invalid-tooltip,.is-invalid ~ .invalid-feedback,.is-invalid ~ .invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#e51c23;padding-right:calc(1.5em + 2rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23e51c23' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e51c23' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.5rem) center;background-size:calc(0.75em + 1rem) calc(0.75em + 1rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#e51c23;-webkit-box-shadow:0 0 0 0.2rem rgba(229,28,35,0.25);box-shadow:0 0 0 0.2rem rgba(229,28,35,0.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 2rem);background-position:top calc(0.375em + 0.5rem) right calc(0.375em + 0.5rem)}.was-validated .custom-select:invalid,.custom-select.is-invalid{border-color:#e51c23;padding-right:calc(0.75em + 2.5rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23222' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0 center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23e51c23' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e51c23' stroke='none'/%3e%3c/svg%3e") transparent no-repeat center right 1rem/calc(0.75em + 1rem) calc(0.75em + 1rem)}.was-validated .custom-select:invalid:focus,.custom-select.is-invalid:focus{border-color:#e51c23;-webkit-box-shadow:0 0 0 0.2rem rgba(229,28,35,0.25);box-shadow:0 0 0 0.2rem rgba(229,28,35,0.25)}.was-validated .form-check-input:invalid ~ .form-check-label,.form-check-input.is-invalid ~ .form-check-label{color:#e51c23}.was-validated .form-check-input:invalid ~ .invalid-feedback,.was-validated .form-check-input:invalid ~ .invalid-tooltip,.form-check-input.is-invalid ~ .invalid-feedback,.form-check-input.is-invalid ~ .invalid-tooltip{display:block}.was-validated .custom-control-input:invalid ~ .custom-control-label,.custom-control-input.is-invalid ~ .custom-control-label{color:#e51c23}.was-validated .custom-control-input:invalid ~ .custom-control-label::before,.custom-control-input.is-invalid ~ .custom-control-label::before{border-color:#e51c23}.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before,.custom-control-input.is-invalid:checked ~ .custom-control-label::before{border-color:#ea4a4f;background:#ea4a4f -webkit-gradient(linear, left top, left bottom, from(#ed656a), to(#ea4a4f)) repeat-x;background:#ea4a4f linear-gradient(180deg, #ed656a, #ea4a4f) repeat-x}.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before,.custom-control-input.is-invalid:focus ~ .custom-control-label::before{-webkit-box-shadow:0 0 0 0.2rem rgba(229,28,35,0.25);box-shadow:0 0 0 0.2rem rgba(229,28,35,0.25)}.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before,.custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before{border-color:#e51c23}.was-validated .custom-file-input:invalid ~ .custom-file-label,.custom-file-input.is-invalid ~ .custom-file-label{border-color:#e51c23}.was-validated .custom-file-input:invalid:focus ~ .custom-file-label,.custom-file-input.is-invalid:focus ~ .custom-file-label{border-color:#e51c23;-webkit-box-shadow:0 0 0 0.2rem rgba(229,28,35,0.25);box-shadow:0 0 0 0.2rem rgba(229,28,35,0.25)}.form-inline{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width: 576px){.form-inline label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group,.form-inline .custom-select{width:auto}.form-inline .form-check{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:0.25rem;margin-left:0}.form-inline .custom-control{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#444;text-align:center;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:0.8rem 1rem;font-size:0.8125rem;line-height:1.5;border-radius:0.25rem;-webkit-transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.btn{-webkit-transition:none;transition:none}}.btn:hover{color:#444;text-decoration:none}.btn:focus,.btn.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25);box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25)}.btn.disabled,.btn:disabled{opacity:0.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background:#2196F3 -webkit-gradient(linear, left top, left bottom, from(#42a6f5), to(#2196F3)) repeat-x;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x;border-color:#2196F3}.btn-primary:hover{color:#fff;background:#0c83e2 -webkit-gradient(linear, left top, left bottom, from(#3196e6), to(#0c83e2)) repeat-x;background:#0c83e2 linear-gradient(180deg, #3196e6, #0c83e2) repeat-x;border-color:#0c7cd5}.btn-primary:focus,.btn-primary.focus{color:#fff;background:#0c83e2 -webkit-gradient(linear, left top, left bottom, from(#3196e6), to(#0c83e2)) repeat-x;background:#0c83e2 linear-gradient(180deg, #3196e6, #0c83e2) repeat-x;border-color:#0c7cd5;-webkit-box-shadow:0 0 0 0.2rem rgba(66,166,245,0.5);box-shadow:0 0 0 0.2rem rgba(66,166,245,0.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#2196F3;border-color:#2196F3;background-image:none}.btn-primary:not(:disabled):not(.disabled):active,.btn-primary:not(:disabled):not(.disabled).active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0c7cd5;background-image:none;border-color:#0b75c9}.btn-primary:not(:disabled):not(.disabled):active:focus,.btn-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-primary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(66,166,245,0.5);box-shadow:0 0 0 0.2rem rgba(66,166,245,0.5)}.btn-secondary{color:#212121;background:#fff -webkit-gradient(linear, left top, left bottom, from(white), to(#fff)) repeat-x;background:#fff linear-gradient(180deg, white, #fff) repeat-x;border-color:#fff}.btn-secondary:hover{color:#212121;background:#ececec -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#ececec)) repeat-x;background:#ececec linear-gradient(180deg, #efefef, #ececec) repeat-x;border-color:#e6e5e5}.btn-secondary:focus,.btn-secondary.focus{color:#212121;background:#ececec -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#ececec)) repeat-x;background:#ececec linear-gradient(180deg, #efefef, #ececec) repeat-x;border-color:#e6e5e5;-webkit-box-shadow:0 0 0 0.2rem rgba(222,222,222,0.5);box-shadow:0 0 0 0.2rem rgba(222,222,222,0.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#212121;background-color:#fff;border-color:#fff;background-image:none}.btn-secondary:not(:disabled):not(.disabled):active,.btn-secondary:not(:disabled):not(.disabled).active,.show>.btn-secondary.dropdown-toggle{color:#212121;background-color:#e6e5e5;background-image:none;border-color:#dfdfdf}.btn-secondary:not(:disabled):not(.disabled):active:focus,.btn-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-secondary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(222,222,222,0.5);box-shadow:0 0 0 0.2rem rgba(222,222,222,0.5)}.btn-success{color:#fff;background:#4CAF50 -webkit-gradient(linear, left top, left bottom, from(#67bb6a), to(#4CAF50)) repeat-x;background:#4CAF50 linear-gradient(180deg, #67bb6a, #4CAF50) repeat-x;border-color:#4CAF50}.btn-success:hover{color:#fff;background:#409444 -webkit-gradient(linear, left top, left bottom, from(#5da460), to(#409444)) repeat-x;background:#409444 linear-gradient(180deg, #5da460, #409444) repeat-x;border-color:#3d8b40}.btn-success:focus,.btn-success.focus{color:#fff;background:#409444 -webkit-gradient(linear, left top, left bottom, from(#5da460), to(#409444)) repeat-x;background:#409444 linear-gradient(180deg, #5da460, #409444) repeat-x;border-color:#3d8b40;-webkit-box-shadow:0 0 0 0.2rem rgba(103,187,106,0.5);box-shadow:0 0 0 0.2rem rgba(103,187,106,0.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#4CAF50;border-color:#4CAF50;background-image:none}.btn-success:not(:disabled):not(.disabled):active,.btn-success:not(:disabled):not(.disabled).active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#3d8b40;background-image:none;border-color:#39833c}.btn-success:not(:disabled):not(.disabled):active:focus,.btn-success:not(:disabled):not(.disabled).active:focus,.show>.btn-success.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(103,187,106,0.5);box-shadow:0 0 0 0.2rem rgba(103,187,106,0.5)}.btn-info{color:#fff;background:#9C27B0 -webkit-gradient(linear, left top, left bottom, from(#ab47bc), to(#9C27B0)) repeat-x;background:#9C27B0 linear-gradient(180deg, #ab47bc, #9C27B0) repeat-x;border-color:#9C27B0}.btn-info:hover{color:#fff;background:#802091 -webkit-gradient(linear, left top, left bottom, from(#9342a1), to(#802091)) repeat-x;background:#802091 linear-gradient(180deg, #9342a1, #802091) repeat-x;border-color:#771e86}.btn-info:focus,.btn-info.focus{color:#fff;background:#802091 -webkit-gradient(linear, left top, left bottom, from(#9342a1), to(#802091)) repeat-x;background:#802091 linear-gradient(180deg, #9342a1, #802091) repeat-x;border-color:#771e86;-webkit-box-shadow:0 0 0 0.2rem rgba(171,71,188,0.5);box-shadow:0 0 0 0.2rem rgba(171,71,188,0.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#9C27B0;border-color:#9C27B0;background-image:none}.btn-info:not(:disabled):not(.disabled):active,.btn-info:not(:disabled):not(.disabled).active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#771e86;background-image:none;border-color:#6e1b7c}.btn-info:not(:disabled):not(.disabled):active:focus,.btn-info:not(:disabled):not(.disabled).active:focus,.show>.btn-info.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(171,71,188,0.5);box-shadow:0 0 0 0.2rem rgba(171,71,188,0.5)}.btn-warning{color:#fff;background:#ff9800 -webkit-gradient(linear, left top, left bottom, from(#ffa726), to(#ff9800)) repeat-x;background:#ff9800 linear-gradient(180deg, #ffa726, #ff9800) repeat-x;border-color:#ff9800}.btn-warning:hover{color:#fff;background:#d98100 -webkit-gradient(linear, left top, left bottom, from(#de9426), to(#d98100)) repeat-x;background:#d98100 linear-gradient(180deg, #de9426, #d98100) repeat-x;border-color:#cc7a00}.btn-warning:focus,.btn-warning.focus{color:#fff;background:#d98100 -webkit-gradient(linear, left top, left bottom, from(#de9426), to(#d98100)) repeat-x;background:#d98100 linear-gradient(180deg, #de9426, #d98100) repeat-x;border-color:#cc7a00;-webkit-box-shadow:0 0 0 0.2rem rgba(255,167,38,0.5);box-shadow:0 0 0 0.2rem rgba(255,167,38,0.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#ff9800;border-color:#ff9800;background-image:none}.btn-warning:not(:disabled):not(.disabled):active,.btn-warning:not(:disabled):not(.disabled).active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#cc7a00;background-image:none;border-color:#bf7200}.btn-warning:not(:disabled):not(.disabled):active:focus,.btn-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-warning.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(255,167,38,0.5);box-shadow:0 0 0 0.2rem rgba(255,167,38,0.5)}.btn-danger{color:#fff;background:#e51c23 -webkit-gradient(linear, left top, left bottom, from(#e93e44), to(#e51c23)) repeat-x;background:#e51c23 linear-gradient(180deg, #e93e44, #e51c23) repeat-x;border-color:#e51c23}.btn-danger:hover{color:#fff;background:#c4161d -webkit-gradient(linear, left top, left bottom, from(#cd393f), to(#c4161d)) repeat-x;background:#c4161d linear-gradient(180deg, #cd393f, #c4161d) repeat-x;border-color:#b9151b}.btn-danger:focus,.btn-danger.focus{color:#fff;background:#c4161d -webkit-gradient(linear, left top, left bottom, from(#cd393f), to(#c4161d)) repeat-x;background:#c4161d linear-gradient(180deg, #cd393f, #c4161d) repeat-x;border-color:#b9151b;-webkit-box-shadow:0 0 0 0.2rem rgba(233,62,68,0.5);box-shadow:0 0 0 0.2rem rgba(233,62,68,0.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#e51c23;border-color:#e51c23;background-image:none}.btn-danger:not(:disabled):not(.disabled):active,.btn-danger:not(:disabled):not(.disabled).active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b9151b;background-image:none;border-color:#ad1419}.btn-danger:not(:disabled):not(.disabled):active:focus,.btn-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-danger.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(233,62,68,0.5);box-shadow:0 0 0 0.2rem rgba(233,62,68,0.5)}.btn-light{color:#212121;background:#fff -webkit-gradient(linear, left top, left bottom, from(white), to(#fff)) repeat-x;background:#fff linear-gradient(180deg, white, #fff) repeat-x;border-color:#fff}.btn-light:hover{color:#212121;background:#ececec -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#ececec)) repeat-x;background:#ececec linear-gradient(180deg, #efefef, #ececec) repeat-x;border-color:#e6e5e5}.btn-light:focus,.btn-light.focus{color:#212121;background:#ececec -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#ececec)) repeat-x;background:#ececec linear-gradient(180deg, #efefef, #ececec) repeat-x;border-color:#e6e5e5;-webkit-box-shadow:0 0 0 0.2rem rgba(222,222,222,0.5);box-shadow:0 0 0 0.2rem rgba(222,222,222,0.5)}.btn-light.disabled,.btn-light:disabled{color:#212121;background-color:#fff;border-color:#fff;background-image:none}.btn-light:not(:disabled):not(.disabled):active,.btn-light:not(:disabled):not(.disabled).active,.show>.btn-light.dropdown-toggle{color:#212121;background-color:#e6e5e5;background-image:none;border-color:#dfdfdf}.btn-light:not(:disabled):not(.disabled):active:focus,.btn-light:not(:disabled):not(.disabled).active:focus,.show>.btn-light.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(222,222,222,0.5);box-shadow:0 0 0 0.2rem rgba(222,222,222,0.5)}.btn-dark{color:#fff;background:#222 -webkit-gradient(linear, left top, left bottom, from(#434343), to(#222)) repeat-x;background:#222 linear-gradient(180deg, #434343, #222) repeat-x;border-color:#222}.btn-dark:hover{color:#fff;background:#0f0f0f -webkit-gradient(linear, left top, left bottom, from(#333), to(#0f0f0f)) repeat-x;background:#0f0f0f linear-gradient(180deg, #333, #0f0f0f) repeat-x;border-color:#090808}.btn-dark:focus,.btn-dark.focus{color:#fff;background:#0f0f0f -webkit-gradient(linear, left top, left bottom, from(#333), to(#0f0f0f)) repeat-x;background:#0f0f0f linear-gradient(180deg, #333, #0f0f0f) repeat-x;border-color:#090808;-webkit-box-shadow:0 0 0 0.2rem rgba(67,67,67,0.5);box-shadow:0 0 0 0.2rem rgba(67,67,67,0.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#222;border-color:#222;background-image:none}.btn-dark:not(:disabled):not(.disabled):active,.btn-dark:not(:disabled):not(.disabled).active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#090808;background-image:none;border-color:#020202}.btn-dark:not(:disabled):not(.disabled):active:focus,.btn-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-dark.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(67,67,67,0.5);box-shadow:0 0 0 0.2rem rgba(67,67,67,0.5)}.btn-outline-primary{color:#2196F3;border-color:#2196F3}.btn-outline-primary:hover{color:#fff;background-color:#2196F3;border-color:#2196F3}.btn-outline-primary:focus,.btn-outline-primary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(33,150,243,0.5);box-shadow:0 0 0 0.2rem rgba(33,150,243,0.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#2196F3;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled):active,.btn-outline-primary:not(:disabled):not(.disabled).active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#2196F3;border-color:#2196F3}.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(33,150,243,0.5);box-shadow:0 0 0 0.2rem rgba(33,150,243,0.5)}.btn-outline-secondary{color:#fff;border-color:#fff}.btn-outline-secondary:hover{color:#212121;background-color:#fff;border-color:#fff}.btn-outline-secondary:focus,.btn-outline-secondary.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5);box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#fff;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled):active,.btn-outline-secondary:not(:disabled):not(.disabled).active,.show>.btn-outline-secondary.dropdown-toggle{color:#212121;background-color:#fff;border-color:#fff}.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5);box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5)}.btn-outline-success{color:#4CAF50;border-color:#4CAF50}.btn-outline-success:hover{color:#fff;background-color:#4CAF50;border-color:#4CAF50}.btn-outline-success:focus,.btn-outline-success.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(76,175,80,0.5);box-shadow:0 0 0 0.2rem rgba(76,175,80,0.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#4CAF50;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled):active,.btn-outline-success:not(:disabled):not(.disabled).active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#4CAF50;border-color:#4CAF50}.btn-outline-success:not(:disabled):not(.disabled):active:focus,.btn-outline-success:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-success.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(76,175,80,0.5);box-shadow:0 0 0 0.2rem rgba(76,175,80,0.5)}.btn-outline-info{color:#9C27B0;border-color:#9C27B0}.btn-outline-info:hover{color:#fff;background-color:#9C27B0;border-color:#9C27B0}.btn-outline-info:focus,.btn-outline-info.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(156,39,176,0.5);box-shadow:0 0 0 0.2rem rgba(156,39,176,0.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#9C27B0;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled):active,.btn-outline-info:not(:disabled):not(.disabled).active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#9C27B0;border-color:#9C27B0}.btn-outline-info:not(:disabled):not(.disabled):active:focus,.btn-outline-info:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-info.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(156,39,176,0.5);box-shadow:0 0 0 0.2rem rgba(156,39,176,0.5)}.btn-outline-warning{color:#ff9800;border-color:#ff9800}.btn-outline-warning:hover{color:#fff;background-color:#ff9800;border-color:#ff9800}.btn-outline-warning:focus,.btn-outline-warning.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(255,152,0,0.5);box-shadow:0 0 0 0.2rem rgba(255,152,0,0.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ff9800;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled):active,.btn-outline-warning:not(:disabled):not(.disabled).active,.show>.btn-outline-warning.dropdown-toggle{color:#fff;background-color:#ff9800;border-color:#ff9800}.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(255,152,0,0.5);box-shadow:0 0 0 0.2rem rgba(255,152,0,0.5)}.btn-outline-danger{color:#e51c23;border-color:#e51c23}.btn-outline-danger:hover{color:#fff;background-color:#e51c23;border-color:#e51c23}.btn-outline-danger:focus,.btn-outline-danger.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(229,28,35,0.5);box-shadow:0 0 0 0.2rem rgba(229,28,35,0.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#e51c23;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled):active,.btn-outline-danger:not(:disabled):not(.disabled).active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#e51c23;border-color:#e51c23}.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(229,28,35,0.5);box-shadow:0 0 0 0.2rem rgba(229,28,35,0.5)}.btn-outline-light{color:#fff;border-color:#fff}.btn-outline-light:hover{color:#212121;background-color:#fff;border-color:#fff}.btn-outline-light:focus,.btn-outline-light.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5);box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#fff;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled):active,.btn-outline-light:not(:disabled):not(.disabled).active,.show>.btn-outline-light.dropdown-toggle{color:#212121;background-color:#fff;border-color:#fff}.btn-outline-light:not(:disabled):not(.disabled):active:focus,.btn-outline-light:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-light.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5);box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5)}.btn-outline-dark{color:#222;border-color:#222}.btn-outline-dark:hover{color:#fff;background-color:#222;border-color:#222}.btn-outline-dark:focus,.btn-outline-dark.focus{-webkit-box-shadow:0 0 0 0.2rem rgba(34,34,34,0.5);box-shadow:0 0 0 0.2rem rgba(34,34,34,0.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#222;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled):active,.btn-outline-dark:not(:disabled):not(.disabled).active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#222;border-color:#222}.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{-webkit-box-shadow:0 0 0 0.2rem rgba(34,34,34,0.5);box-shadow:0 0 0 0.2rem rgba(34,34,34,0.5)}.btn-link{font-weight:400;color:#2196F3;text-decoration:none}.btn-link:hover{color:#0a6ebd;text-decoration:underline}.btn-link:focus,.btn-link.focus{text-decoration:underline;-webkit-box-shadow:none;box-shadow:none}.btn-link:disabled,.btn-link.disabled{color:#666;pointer-events:none}.btn-lg,.btn-group-lg>.btn{padding:0.5rem 1rem;font-size:1.015625rem;line-height:1.5;border-radius:0.3rem}.btn-sm,.btn-group-sm>.btn{padding:0.25rem 0.5rem;font-size:0.7109375rem;line-height:1.5;border-radius:0.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:0.5rem}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{-webkit-transition:opacity 0.15s linear;transition:opacity 0.15s linear}@media (prefers-reduced-motion: reduce){.fade{-webkit-transition:none;transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;transition:height 0.35s ease}@media (prefers-reduced-motion: reduce){.collapsing{-webkit-transition:none;transition:none}}.dropup,.dropright,.dropdown,.dropleft{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid;border-right:0.3em solid transparent;border-bottom:0;border-left:0.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:0.5rem 0;margin:0.125rem 0 0;font-size:0.8125rem;color:#444;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.15);border-radius:0.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width: 576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width: 768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width: 992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width: 1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:0.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0;border-right:0.3em solid transparent;border-bottom:0.3em solid;border-left:0.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:0.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid transparent;border-right:0;border-bottom:0.3em solid transparent;border-left:0.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:0.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:0.255em;vertical-align:0.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:0.255em;vertical-align:0.255em;content:"";border-top:0.3em solid transparent;border-right:0.3em solid;border-bottom:0.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^="top"],.dropdown-menu[x-placement^="right"],.dropdown-menu[x-placement^="bottom"],.dropdown-menu[x-placement^="left"]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:0.5rem 0;overflow:hidden;border-top:1px solid #eee}.dropdown-item{display:block;width:100%;padding:0.25rem 1.5rem;clear:both;font-weight:400;color:#212121;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#141414;text-decoration:none;background:#f8f9fa -webkit-gradient(linear, left top, left bottom, from(#f9fafb), to(#f8f9fa)) repeat-x;background:#f8f9fa linear-gradient(180deg, #f9fafb, #f8f9fa) repeat-x}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background:#2196F3 -webkit-gradient(linear, left top, left bottom, from(#42a6f5), to(#2196F3)) repeat-x;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x}.dropdown-item.disabled,.dropdown-item:disabled{color:#666;pointer-events:none;background-color:transparent;background-image:none}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:0.5rem 1.5rem;margin-bottom:0;font-size:0.7109375rem;color:#666;white-space:nowrap}.dropdown-item-text{display:block;padding:0.25rem 1.5rem;color:#212121}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover{z-index:1}.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:0.75rem;padding-left:0.75rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:0.375rem;padding-left:0.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:0.75rem;padding-left:0.75rem}.btn-group-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type="radio"],.btn-group-toggle>.btn input[type="checkbox"],.btn-group-toggle>.btn-group>.btn input[type="radio"],.btn-group-toggle>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-control-plaintext,.input-group>.custom-select,.input-group>.custom-file{position:relative;-webkit-box-flex:1;-ms-flex:1 1 0%;flex:1 1 0%;min-width:0;margin-bottom:0}.input-group>.form-control+.form-control,.input-group>.form-control+.custom-select,.input-group>.form-control+.custom-file,.input-group>.form-control-plaintext+.form-control,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.custom-file,.input-group>.custom-select+.form-control,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.custom-file,.input-group>.custom-file+.form-control,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.custom-file{margin-left:0rem}.input-group>.form-control:focus,.input-group>.custom-select:focus,.input-group>.custom-file .custom-file-input:focus ~ .custom-file-label{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.form-control:not(:last-child),.input-group>.custom-select:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.form-control:not(:first-child),.input-group>.custom-select:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-prepend,.input-group-append{display:-webkit-box;display:-ms-flexbox;display:flex}.input-group-prepend .btn,.input-group-append .btn{position:relative;z-index:2}.input-group-prepend .btn:focus,.input-group-append .btn:focus{z-index:3}.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.input-group-text,.input-group-append .input-group-text+.btn{margin-left:0rem}.input-group-prepend{margin-right:0rem}.input-group-append{margin-left:0rem}.input-group-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:1rem 0;margin-bottom:0;font-size:0.8125rem;font-weight:400;line-height:1.5;color:#666;text-align:center;white-space:nowrap;background-color:transparent;border:0rem solid transparent;border-radius:0}.input-group-text input[type="radio"],.input-group-text input[type="checkbox"]{margin-top:0}.input-group-lg>.form-control:not(textarea),.input-group-lg>.custom-select{height:calc(1.5em + 2.03125rem)}.input-group-lg>.form-control,.input-group-lg>.custom-select,.input-group-lg>.input-group-prepend>.input-group-text,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-append>.btn{padding:1.015625rem 0;font-size:1.015625rem;line-height:1.5;border-radius:0}.input-group-sm>.form-control:not(textarea),.input-group-sm>.custom-select{height:calc(1.5em + 0rem)}.input-group-sm>.form-control,.input-group-sm>.custom-select,.input-group-sm>.input-group-prepend>.input-group-text,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-append>.btn{padding:0 0;font-size:0.7109375rem;line-height:1.5;border-radius:0}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1rem}.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text,.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.21875rem;padding-left:3rem}.custom-control-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1.25rem;height:1.234375rem;opacity:0}.custom-control-input:checked ~ .custom-control-label::before{color:#fff;border-color:#2196F3;background:#2196F3 -webkit-gradient(linear, left top, left bottom, from(#42a6f5), to(#2196F3)) repeat-x;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x}.custom-control-input:focus ~ .custom-control-label::before{-webkit-box-shadow:0;box-shadow:0}.custom-control-input:focus:not(:checked) ~ .custom-control-label::before{border-color:#9acffa}.custom-control-input:not(:disabled):active ~ .custom-control-label::before{color:#fff;background-color:#cae6fc;border-color:#cae6fc}.custom-control-input[disabled] ~ .custom-control-label,.custom-control-input:disabled ~ .custom-control-label{color:#666}.custom-control-input[disabled] ~ .custom-control-label::before,.custom-control-input:disabled ~ .custom-control-label::before{background-color:#f8f9fa}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:-0.015625rem;left:-3rem;display:block;width:1.25rem;height:1.25rem;pointer-events:none;content:"";background-color:#fff;border:#bbb solid 0rem}.custom-control-label::after{position:absolute;top:-0.015625rem;left:-3rem;display:block;width:1.25rem;height:1.25rem;content:"";background:no-repeat 50% / 50% 50%}.custom-checkbox .custom-control-label::before{border-radius:2px}.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before{border-color:#2196F3;background:#2196F3 -webkit-gradient(linear, left top, left bottom, from(#42a6f5), to(#2196F3)) repeat-x;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x}.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(33,150,243,0.5)}.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before{background-color:rgba(33,150,243,0.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked ~ .custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='4' fill='%232196F3'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(33,150,243,0.5)}.custom-switch{padding-left:3.9375rem}.custom-switch .custom-control-label::before{left:-3.9375rem;width:2.1875rem;pointer-events:all;border-radius:0.625rem}.custom-switch .custom-control-label::after{top:-0.015625rem;left:-3.9375rem;width:1.25rem;height:1.25rem;background-color:#bbb;border-radius:0.625rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.custom-switch .custom-control-label::after{-webkit-transition:none;transition:none}}.custom-switch .custom-control-input:checked ~ .custom-control-label::after{background-color:#fff;-webkit-transform:translateX(0.9375rem);transform:translateX(0.9375rem)}.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before{background-color:rgba(33,150,243,0.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + 2rem);padding:1rem 1rem 1rem 0;font-size:0.8125rem;font-weight:400;line-height:1.5;color:#666;vertical-align:middle;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23222' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0 center/8px 10px;border:0rem solid transparent;border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#9acffa;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25);box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25)}.custom-select:focus::-ms-value{color:#666;background-color:transparent}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:0;background-image:none}.custom-select:disabled{color:#666;background-color:#eee}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #666}.custom-select-sm{height:calc(1.5em + 0rem);padding-top:0;padding-bottom:0;padding-left:0;font-size:0.7109375rem}.custom-select-lg{height:calc(1.5em + 2.03125rem);padding-top:1.015625rem;padding-bottom:1.015625rem;padding-left:0;font-size:1.015625rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + 2rem);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + 2rem);margin:0;opacity:0}.custom-file-input:focus ~ .custom-file-label{border-color:#9acffa;-webkit-box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25);box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25)}.custom-file-input[disabled] ~ .custom-file-label,.custom-file-input:disabled ~ .custom-file-label{background-color:transparent}.custom-file-input:lang(en) ~ .custom-file-label::after{content:"Browse"}.custom-file-input ~ .custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + 2rem);padding:1rem 0;font-weight:400;line-height:1.5;color:#666;background-color:transparent;border:0rem solid transparent;border-radius:0}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + 2rem);padding:1rem 0;line-height:1.5;color:#666;content:"Browse";background:transparent -webkit-gradient(linear, left top, left bottom, from(rgba(255,255,255,0.15)), to(transparent)) repeat-x;background:transparent linear-gradient(180deg, rgba(255,255,255,0.15), transparent) repeat-x;border-left:inherit;border-radius:0 0 0 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:none}.custom-range:focus::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 1px #fff,0 0 0 0.2rem rgba(33,150,243,0.25);box-shadow:0 0 0 1px #fff,0 0 0 0.2rem rgba(33,150,243,0.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 0.2rem rgba(33,150,243,0.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 0.2rem rgba(33,150,243,0.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background:#2196F3 -webkit-gradient(linear, left top, left bottom, from(#42a6f5), to(#2196F3)) repeat-x;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background:#cae6fc -webkit-gradient(linear, left top, left bottom, from(#d2eafd), to(#cae6fc)) repeat-x;background:#cae6fc linear-gradient(180deg, #d2eafd, #cae6fc) repeat-x}.custom-range::-webkit-slider-runnable-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-moz-range-thumb{-webkit-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background:#cae6fc linear-gradient(180deg, #d2eafd, #cae6fc) repeat-x}.custom-range::-moz-range-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:0.2rem;margin-left:0.2rem;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x;border:0;border-radius:1rem;-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.custom-range::-ms-thumb{-webkit-transition:none;transition:none}}.custom-range::-ms-thumb:active{background:#cae6fc linear-gradient(180deg, #d2eafd, #cae6fc) repeat-x}.custom-range::-ms-track{width:100%;height:0.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:0.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#bbb}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#bbb}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#bbb}.custom-control-label::before,.custom-file-label,.custom-select{-webkit-transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.custom-control-label::before,.custom-file-label,.custom-select{-webkit-transition:none;transition:none}}.nav{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:0.5rem 1rem}.nav-link:hover,.nav-link:focus{text-decoration:none}.nav-link.disabled{color:#bbb;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid transparent}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#eee #eee transparent}.nav-tabs .nav-link.disabled{color:#bbb;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#444;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:0.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#2196F3}.nav-fill .nav-item{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-sm,.navbar .container-md,.navbar .container-lg,.navbar .container-xl{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:0.34765625rem;padding-bottom:0.34765625rem;margin-right:1rem;font-size:1.015625rem;line-height:inherit;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:0.5rem;padding-bottom:0.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:0.25rem 0.75rem;font-size:1.015625rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:0.25rem}.navbar-toggler:hover,.navbar-toggler:focus{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width: 575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width: 576px){.navbar-expand-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width: 767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-md,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width: 768px){.navbar-expand-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-md,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width: 991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width: 992px){.navbar-expand-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width: 1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width: 1200px){.navbar-expand-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-sm,.navbar-expand>.container-md,.navbar-expand>.container-lg,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-sm,.navbar-expand>.container-md,.navbar-expand>.container-lg,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,0.9)}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:rgba(0,0,0,0.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,0.5)}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(0,0,0,0.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,0.3)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .nav-link.active{color:rgba(0,0,0,0.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,0.5);border-color:rgba(0,0,0,0.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,0.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,0.9)}.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:rgba(0,0,0,0.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,0.75)}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:#fff}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,0.25)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .nav-link.active{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,0.75);border-color:rgba(255,255,255,0.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 0.75)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,0.75)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fff}.card{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:0 solid transparent;border-radius:0.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:0.25rem;border-bottom-left-radius:0.25rem}.card-body{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:0.75rem}.card-subtitle{margin-top:-0.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:0.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,0.03);border-bottom:0 solid transparent}.card-header:first-child{border-radius:0.25rem 0.25rem 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:0.75rem 1.25rem;background-color:rgba(0,0,0,0.03);border-top:0 solid transparent}.card-footer:last-child{border-radius:0 0 0.25rem 0.25rem}.card-header-tabs{margin-right:-0.625rem;margin-bottom:-0.75rem;margin-left:-0.625rem;border-bottom:0}.card-header-pills{margin-right:-0.625rem;margin-left:-0.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img,.card-img-top,.card-img-bottom{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.card-img,.card-img-bottom{border-bottom-right-radius:0.25rem;border-bottom-left-radius:0.25rem}.card-deck .card{margin-bottom:15px}@media (min-width: 576px){.card-deck{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width: 576px){.card-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:0.75rem}@media (min-width: 576px){.card-columns{-webkit-column-count:3;column-count:3;-webkit-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:0}.breadcrumb{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#eee;border-radius:0.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:0.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:0.5rem;color:#666;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#666}.pagination{display:-webkit-box;display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:0.25rem}.page-link{position:relative;display:block;padding:0.5rem 0.75rem;margin-left:-1px;line-height:1.25;color:#2196F3;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0a6ebd;text-decoration:none;background-color:#eee;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25);box-shadow:0 0 0 0.2rem rgba(33,150,243,0.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:0.25rem;border-bottom-left-radius:0.25rem}.page-item:last-child .page-link{border-top-right-radius:0.25rem;border-bottom-right-radius:0.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#2196F3;border-color:#2196F3}.page-item.disabled .page-link{color:#666;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:0.75rem 1.5rem;font-size:1.015625rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:0.3rem;border-bottom-left-radius:0.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:0.3rem;border-bottom-right-radius:0.3rem}.pagination-sm .page-link{padding:0.25rem 0.5rem;font-size:0.7109375rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:0.2rem;border-bottom-left-radius:0.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:0.2rem;border-bottom-right-radius:0.2rem}.badge{display:inline-block;padding:0.25em 0.4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:0.25rem;-webkit-transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out}@media (prefers-reduced-motion: reduce){.badge{-webkit-transition:none;transition:none}}a.badge:hover,a.badge:focus{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:0.6em;padding-left:0.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#2196F3}a.badge-primary:hover,a.badge-primary:focus{color:#fff;background-color:#0c7cd5}a.badge-primary:focus,a.badge-primary.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(33,150,243,0.5);box-shadow:0 0 0 0.2rem rgba(33,150,243,0.5)}.badge-secondary{color:#212121;background-color:#fff}a.badge-secondary:hover,a.badge-secondary:focus{color:#212121;background-color:#e6e5e5}a.badge-secondary:focus,a.badge-secondary.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5);box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5)}.badge-success{color:#fff;background-color:#4CAF50}a.badge-success:hover,a.badge-success:focus{color:#fff;background-color:#3d8b40}a.badge-success:focus,a.badge-success.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(76,175,80,0.5);box-shadow:0 0 0 0.2rem rgba(76,175,80,0.5)}.badge-info{color:#fff;background-color:#9C27B0}a.badge-info:hover,a.badge-info:focus{color:#fff;background-color:#771e86}a.badge-info:focus,a.badge-info.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(156,39,176,0.5);box-shadow:0 0 0 0.2rem rgba(156,39,176,0.5)}.badge-warning{color:#fff;background-color:#ff9800}a.badge-warning:hover,a.badge-warning:focus{color:#fff;background-color:#cc7a00}a.badge-warning:focus,a.badge-warning.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(255,152,0,0.5);box-shadow:0 0 0 0.2rem rgba(255,152,0,0.5)}.badge-danger{color:#fff;background-color:#e51c23}a.badge-danger:hover,a.badge-danger:focus{color:#fff;background-color:#b9151b}a.badge-danger:focus,a.badge-danger.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(229,28,35,0.5);box-shadow:0 0 0 0.2rem rgba(229,28,35,0.5)}.badge-light{color:#212121;background-color:#fff}a.badge-light:hover,a.badge-light:focus{color:#212121;background-color:#e6e5e5}a.badge-light:focus,a.badge-light.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5);box-shadow:0 0 0 0.2rem rgba(255,255,255,0.5)}.badge-dark{color:#fff;background-color:#222}a.badge-dark:hover,a.badge-dark:focus{color:#fff;background-color:#090808}a.badge-dark:focus,a.badge-dark.focus{outline:0;-webkit-box-shadow:0 0 0 0.2rem rgba(34,34,34,0.5);box-shadow:0 0 0 0.2rem rgba(34,34,34,0.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#eee;border-radius:0.3rem}@media (min-width: 576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:0.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:0.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4.625rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:0.75rem 1.25rem;color:inherit}.alert-primary{color:#114e7e;background:#d3eafd -webkit-gradient(linear, left top, left bottom, from(#daedfd), to(#d3eafd)) repeat-x;background:#d3eafd linear-gradient(180deg, #daedfd, #d3eafd) repeat-x;border-color:#c1e2fc}.alert-primary hr{border-top-color:#a9d7fb}.alert-primary .alert-link{color:#0b3251}.alert-secondary{color:#858585;background:white -webkit-gradient(linear, left top, left bottom, from(white), to(white)) repeat-x;background:white linear-gradient(180deg, white, white) repeat-x;border-color:white}.alert-secondary hr{border-top-color:#f2f2f2}.alert-secondary .alert-link{color:#6c6b6b}.alert-success{color:#285b2a;background:#dbefdc -webkit-gradient(linear, left top, left bottom, from(#e0f1e1), to(#dbefdc)) repeat-x;background:#dbefdc linear-gradient(180deg, #e0f1e1, #dbefdc) repeat-x;border-color:#cde9ce}.alert-success hr{border-top-color:#bbe1bd}.alert-success .alert-link{color:#18381a}.alert-info{color:#51145c;background:#ebd4ef -webkit-gradient(linear, left top, left bottom, from(#eedaf1), to(#ebd4ef)) repeat-x;background:#ebd4ef linear-gradient(180deg, #eedaf1, #ebd4ef) repeat-x;border-color:#e3c3e9}.alert-info hr{border-top-color:#dab0e2}.alert-info .alert-link{color:#2c0b32}.alert-warning{color:#854f00;background:#ffeacc -webkit-gradient(linear, left top, left bottom, from(#ffedd4), to(#ffeacc)) repeat-x;background:#ffeacc linear-gradient(180deg, #ffedd4, #ffeacc) repeat-x;border-color:#ffe2b8}.alert-warning hr{border-top-color:#ffd89f}.alert-warning .alert-link{color:#523100}.alert-danger{color:#770f12;background:#fad2d3 -webkit-gradient(linear, left top, left bottom, from(#fbd9da), to(#fad2d3)) repeat-x;background:#fad2d3 linear-gradient(180deg, #fbd9da, #fad2d3) repeat-x;border-color:#f8bfc1}.alert-danger hr{border-top-color:#f5a8ab}.alert-danger .alert-link{color:#4a090b}.alert-light{color:#858585;background:white -webkit-gradient(linear, left top, left bottom, from(white), to(white)) repeat-x;background:white linear-gradient(180deg, white, white) repeat-x;border-color:white}.alert-light hr{border-top-color:#f2f2f2}.alert-light .alert-link{color:#6c6b6b}.alert-dark{color:#121212;background:lightgray -webkit-gradient(linear, left top, left bottom, from(#dadada), to(lightgray)) repeat-x;background:lightgray linear-gradient(180deg, #dadada, lightgray) repeat-x;border-color:#c1c1c1}.alert-dark hr{border-top-color:#b4b4b4}.alert-dark .alert-link{color:black}@-webkit-keyframes progress-bar-stripes{from{background-position:0.375rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:0.375rem 0}to{background-position:0 0}}.progress{display:-webkit-box;display:-ms-flexbox;display:flex;height:0.375rem;overflow:hidden;font-size:0.609375rem;background-color:#eee;border-radius:0}.progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#2196F3;-webkit-transition:width 0.6s ease;transition:width 0.6s ease}@media (prefers-reduced-motion: reduce){.progress-bar{-webkit-transition:none;transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-size:0.375rem 0.375rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion: reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.media-body{-webkit-box-flex:1;-ms-flex:1;flex:1}.list-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#444;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#444;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#444;background-color:#eee}.list-group-item{position:relative;display:block;padding:0.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,0.125)}.list-group-item:first-child{border-top-left-radius:0.25rem;border-top-right-radius:0.25rem}.list-group-item:last-child{border-bottom-right-radius:0.25rem;border-bottom-left-radius:0.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#666;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#2196F3;border-color:#2196F3}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item:first-child{border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{border-top-right-radius:0.25rem;border-bottom-left-radius:0}.list-group-horizontal .list-group-item.active{margin-top:0}.list-group-horizontal .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width: 576px){.list-group-horizontal-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item:first-child{border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{border-top-right-radius:0.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm .list-group-item.active{margin-top:0}.list-group-horizontal-sm .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 768px){.list-group-horizontal-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item:first-child{border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{border-top-right-radius:0.25rem;border-bottom-left-radius:0}.list-group-horizontal-md .list-group-item.active{margin-top:0}.list-group-horizontal-md .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 992px){.list-group-horizontal-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item:first-child{border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{border-top-right-radius:0.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg .list-group-item.active{margin-top:0}.list-group-horizontal-lg .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 1200px){.list-group-horizontal-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item:first-child{border-bottom-left-radius:0.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{border-top-right-radius:0.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl .list-group-item.active{margin-top:0}.list-group-horizontal-xl .list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl .list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush .list-group-item{border-right-width:0;border-left-width:0;border-radius:0}.list-group-flush .list-group-item:first-child{border-top-width:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#114e7e;background-color:#c1e2fc}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#114e7e;background-color:#a9d7fb}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#114e7e;border-color:#114e7e}.list-group-item-secondary{color:#858585;background-color:white}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#858585;background-color:#f2f2f2}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#858585;border-color:#858585}.list-group-item-success{color:#285b2a;background-color:#cde9ce}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#285b2a;background-color:#bbe1bd}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#285b2a;border-color:#285b2a}.list-group-item-info{color:#51145c;background-color:#e3c3e9}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#51145c;background-color:#dab0e2}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#51145c;border-color:#51145c}.list-group-item-warning{color:#854f00;background-color:#ffe2b8}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#854f00;background-color:#ffd89f}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#854f00;border-color:#854f00}.list-group-item-danger{color:#770f12;background-color:#f8bfc1}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#770f12;background-color:#f5a8ab}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#770f12;border-color:#770f12}.list-group-item-light{color:#858585;background-color:white}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#858585;background-color:#f2f2f2}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#858585;border-color:#858585}.list-group-item-dark{color:#121212;background-color:#c1c1c1}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#121212;background-color:#b4b4b4}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#121212;border-color:#121212}.close{float:right;font-size:2.125rem;font-weight:300;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):hover,.close:not(:disabled):not(.disabled):focus{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:0.875rem;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border:1px solid rgba(0,0,0,0.1);-webkit-box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.1);box-shadow:0 0.25rem 0.75rem rgba(0,0,0,0.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:0.25rem}.toast:not(:last-child){margin-bottom:0.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0.25rem 0.75rem;color:#666;background-color:rgba(255,255,255,0.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,0.05)}.toast-body{padding:0.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:0.5rem;pointer-events:none}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform 0.3s ease-out;transition:-webkit-transform 0.3s ease-out;transition:transform 0.3s ease-out;transition:transform 0.3s ease-out, -webkit-transform 0.3s ease-out;-webkit-transform:translate(0, -50px);transform:translate(0, -50px)}@media (prefers-reduced-motion: reduce){.modal.fade .modal-dialog{-webkit-transition:none;transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-webkit-box;display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-header,.modal-dialog-scrollable .modal-footer{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid transparent;border-radius:0.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:0.5}.modal-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:0.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(0.3rem - 1px);border-bottom-left-radius:calc(0.3rem - 1px)}.modal-footer>*{margin:0.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width: 1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.7109375rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:0.9}.tooltip .arrow{position:absolute;display:block;width:0.8rem;height:0.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[x-placement^="top"]{padding:0.4rem 0}.bs-tooltip-top .arrow,.bs-tooltip-auto[x-placement^="top"] .arrow{bottom:0}.bs-tooltip-top .arrow::before,.bs-tooltip-auto[x-placement^="top"] .arrow::before{top:0;border-width:0.4rem 0.4rem 0;border-top-color:#444}.bs-tooltip-right,.bs-tooltip-auto[x-placement^="right"]{padding:0 0.4rem}.bs-tooltip-right .arrow,.bs-tooltip-auto[x-placement^="right"] .arrow{left:0;width:0.4rem;height:0.8rem}.bs-tooltip-right .arrow::before,.bs-tooltip-auto[x-placement^="right"] .arrow::before{right:0;border-width:0.4rem 0.4rem 0.4rem 0;border-right-color:#444}.bs-tooltip-bottom,.bs-tooltip-auto[x-placement^="bottom"]{padding:0.4rem 0}.bs-tooltip-bottom .arrow,.bs-tooltip-auto[x-placement^="bottom"] .arrow{top:0}.bs-tooltip-bottom .arrow::before,.bs-tooltip-auto[x-placement^="bottom"] .arrow::before{bottom:0;border-width:0 0.4rem 0.4rem;border-bottom-color:#444}.bs-tooltip-left,.bs-tooltip-auto[x-placement^="left"]{padding:0 0.4rem}.bs-tooltip-left .arrow,.bs-tooltip-auto[x-placement^="left"] .arrow{right:0;width:0.4rem;height:0.8rem}.bs-tooltip-left .arrow::before,.bs-tooltip-auto[x-placement^="left"] .arrow::before{left:0;border-width:0.4rem 0 0.4rem 0.4rem;border-left-color:#444}.tooltip-inner{max-width:200px;padding:0.25rem 0.5rem;color:#fff;text-align:center;background-color:#444;border-radius:0.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.7109375rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:0.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:0.5rem;margin:0 0.3rem}.popover .arrow::before,.popover .arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top,.bs-popover-auto[x-placement^="top"]{margin-bottom:0.5rem}.bs-popover-top>.arrow,.bs-popover-auto[x-placement^="top"]>.arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.arrow::before,.bs-popover-auto[x-placement^="top"]>.arrow::before{bottom:0;border-width:0.5rem 0.5rem 0;border-top-color:rgba(0,0,0,0.25)}.bs-popover-top>.arrow::after,.bs-popover-auto[x-placement^="top"]>.arrow::after{bottom:1px;border-width:0.5rem 0.5rem 0;border-top-color:#fff}.bs-popover-right,.bs-popover-auto[x-placement^="right"]{margin-left:0.5rem}.bs-popover-right>.arrow,.bs-popover-auto[x-placement^="right"]>.arrow{left:calc(-0.5rem - 1px);width:0.5rem;height:1rem;margin:0.3rem 0}.bs-popover-right>.arrow::before,.bs-popover-auto[x-placement^="right"]>.arrow::before{left:0;border-width:0.5rem 0.5rem 0.5rem 0;border-right-color:rgba(0,0,0,0.25)}.bs-popover-right>.arrow::after,.bs-popover-auto[x-placement^="right"]>.arrow::after{left:1px;border-width:0.5rem 0.5rem 0.5rem 0;border-right-color:#fff}.bs-popover-bottom,.bs-popover-auto[x-placement^="bottom"]{margin-top:0.5rem}.bs-popover-bottom>.arrow,.bs-popover-auto[x-placement^="bottom"]>.arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.arrow::before,.bs-popover-auto[x-placement^="bottom"]>.arrow::before{top:0;border-width:0 0.5rem 0.5rem 0.5rem;border-bottom-color:rgba(0,0,0,0.25)}.bs-popover-bottom>.arrow::after,.bs-popover-auto[x-placement^="bottom"]>.arrow::after{top:1px;border-width:0 0.5rem 0.5rem 0.5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[x-placement^="bottom"] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-left,.bs-popover-auto[x-placement^="left"]{margin-right:0.5rem}.bs-popover-left>.arrow,.bs-popover-auto[x-placement^="left"]>.arrow{right:calc(-0.5rem - 1px);width:0.5rem;height:1rem;margin:0.3rem 0}.bs-popover-left>.arrow::before,.bs-popover-auto[x-placement^="left"]>.arrow::before{right:0;border-width:0.5rem 0 0.5rem 0.5rem;border-left-color:rgba(0,0,0,0.25)}.bs-popover-left>.arrow::after,.bs-popover-auto[x-placement^="left"]>.arrow::after{right:1px;border-width:0.5rem 0 0.5rem 0.5rem;border-left-color:#fff}.popover-header{padding:0.5rem 0.75rem;margin-bottom:0;font-size:0.8125rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:0.5rem 0.75rem;color:#444}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transition:-webkit-transform 0.6s ease-in-out;transition:-webkit-transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out;transition:transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out}@media (prefers-reduced-motion: reduce){.carousel-item{-webkit-transition:none;transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-left),.active.carousel-item-right{-webkit-transform:translateX(100%);transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-right),.active.carousel-item-left{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;-webkit-transition-property:opacity;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;-webkit-transition:opacity 0s 0.6s;transition:opacity 0s 0.6s}@media (prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{-webkit-transition:none;transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:0.5;-webkit-transition:opacity 0.15s ease;transition:opacity 0.15s ease}@media (prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{-webkit-transition:none;transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:0.9}.carousel-control-prev{left:0;background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.25)), to(rgba(0,0,0,0.001)));background-image:linear-gradient(90deg, rgba(0,0,0,0.25), rgba(0,0,0,0.001))}.carousel-control-next{right:0;background-image:-webkit-gradient(linear, right top, left top, from(rgba(0,0,0,0.25)), to(rgba(0,0,0,0.001)));background-image:linear-gradient(270deg, rgba(0,0,0,0.25), rgba(0,0,0,0.001))}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50% / 100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;-webkit-transition:opacity 0.6s ease;transition:opacity 0.6s ease}@media (prefers-reduced-motion: reduce){.carousel-indicators li{-webkit-transition:none;transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:0.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:0.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.bg-primary{background-color:#2196F3 !important}a.bg-primary:hover,a.bg-primary:focus,button.bg-primary:hover,button.bg-primary:focus{background-color:#0c7cd5 !important}.bg-secondary{background-color:#fff !important}a.bg-secondary:hover,a.bg-secondary:focus,button.bg-secondary:hover,button.bg-secondary:focus{background-color:#e6e5e5 !important}.bg-success{background-color:#4CAF50 !important}a.bg-success:hover,a.bg-success:focus,button.bg-success:hover,button.bg-success:focus{background-color:#3d8b40 !important}.bg-info{background-color:#9C27B0 !important}a.bg-info:hover,a.bg-info:focus,button.bg-info:hover,button.bg-info:focus{background-color:#771e86 !important}.bg-warning{background-color:#ff9800 !important}a.bg-warning:hover,a.bg-warning:focus,button.bg-warning:hover,button.bg-warning:focus{background-color:#cc7a00 !important}.bg-danger{background-color:#e51c23 !important}a.bg-danger:hover,a.bg-danger:focus,button.bg-danger:hover,button.bg-danger:focus{background-color:#b9151b !important}.bg-light{background-color:#fff !important}a.bg-light:hover,a.bg-light:focus,button.bg-light:hover,button.bg-light:focus{background-color:#e6e5e5 !important}.bg-dark{background-color:#222 !important}a.bg-dark:hover,a.bg-dark:focus,button.bg-dark:hover,button.bg-dark:focus{background-color:#090808 !important}.bg-gradient-primary{background:#2196F3 -webkit-gradient(linear, left top, left bottom, from(#42a6f5), to(#2196F3)) repeat-x !important;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x !important}.bg-gradient-secondary{background:#fff -webkit-gradient(linear, left top, left bottom, from(white), to(#fff)) repeat-x !important;background:#fff linear-gradient(180deg, white, #fff) repeat-x !important}.bg-gradient-success{background:#4CAF50 -webkit-gradient(linear, left top, left bottom, from(#67bb6a), to(#4CAF50)) repeat-x !important;background:#4CAF50 linear-gradient(180deg, #67bb6a, #4CAF50) repeat-x !important}.bg-gradient-info{background:#9C27B0 -webkit-gradient(linear, left top, left bottom, from(#ab47bc), to(#9C27B0)) repeat-x !important;background:#9C27B0 linear-gradient(180deg, #ab47bc, #9C27B0) repeat-x !important}.bg-gradient-warning{background:#ff9800 -webkit-gradient(linear, left top, left bottom, from(#ffa726), to(#ff9800)) repeat-x !important;background:#ff9800 linear-gradient(180deg, #ffa726, #ff9800) repeat-x !important}.bg-gradient-danger{background:#e51c23 -webkit-gradient(linear, left top, left bottom, from(#e93e44), to(#e51c23)) repeat-x !important;background:#e51c23 linear-gradient(180deg, #e93e44, #e51c23) repeat-x !important}.bg-gradient-light{background:#fff -webkit-gradient(linear, left top, left bottom, from(white), to(#fff)) repeat-x !important;background:#fff linear-gradient(180deg, white, #fff) repeat-x !important}.bg-gradient-dark{background:#222 -webkit-gradient(linear, left top, left bottom, from(#434343), to(#222)) repeat-x !important;background:#222 linear-gradient(180deg, #434343, #222) repeat-x !important}.bg-white{background-color:#fff !important}.bg-transparent{background-color:transparent !important}.border{border:1px solid #dee2e6 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-right{border-right:1px solid #dee2e6 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-left{border-left:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.border-primary{border-color:#2196F3 !important}.border-secondary{border-color:#fff !important}.border-success{border-color:#4CAF50 !important}.border-info{border-color:#9C27B0 !important}.border-warning{border-color:#ff9800 !important}.border-danger{border-color:#e51c23 !important}.border-light{border-color:#fff !important}.border-dark{border-color:#222 !important}.border-white{border-color:#fff !important}.rounded-sm{border-radius:0.2rem !important}.rounded{border-radius:0.25rem !important}.rounded-top{border-top-left-radius:0.25rem !important;border-top-right-radius:0.25rem !important}.rounded-right{border-top-right-radius:0.25rem !important;border-bottom-right-radius:0.25rem !important}.rounded-bottom{border-bottom-right-radius:0.25rem !important;border-bottom-left-radius:0.25rem !important}.rounded-left{border-top-left-radius:0.25rem !important;border-bottom-left-radius:0.25rem !important}.rounded-lg{border-radius:0.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-0{border-radius:0 !important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}@media (min-width: 576px){.d-sm-none{display:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-sm-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 768px){.d-md-none{display:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-md-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 992px){.d-lg-none{display:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-lg-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media (min-width: 1200px){.d-xl-none{display:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-xl-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}@media print{.d-print-none{display:none !important}.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:-webkit-box !important;display:-ms-flexbox !important;display:flex !important}.d-print-inline-flex{display:-webkit-inline-box !important;display:-ms-inline-flexbox !important;display:inline-flex !important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.8571428571%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}@media (min-width: 576px){.flex-sm-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-sm-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-sm-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-sm-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-sm-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-sm-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-sm-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-sm-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-sm-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-sm-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-sm-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-sm-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-sm-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-sm-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-sm-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-sm-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-sm-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-sm-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-sm-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-sm-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-sm-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-sm-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-sm-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-sm-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-sm-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-sm-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-sm-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-sm-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-sm-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-sm-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-sm-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-sm-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-sm-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 768px){.flex-md-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-md-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-md-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-md-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-md-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-md-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-md-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-md-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-md-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-md-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-md-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-md-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-md-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-md-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-md-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-md-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-md-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-md-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-md-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-md-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-md-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-md-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-md-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-md-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-md-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-md-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-md-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-md-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-md-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-md-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-md-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-md-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-md-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 992px){.flex-lg-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-lg-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-lg-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-lg-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-lg-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-lg-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-lg-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-lg-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-lg-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-lg-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-lg-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-lg-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-lg-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-lg-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-lg-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-lg-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-lg-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-lg-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-lg-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-lg-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-lg-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-lg-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-lg-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-lg-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-lg-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-lg-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-lg-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-lg-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-lg-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-lg-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-lg-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-lg-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-lg-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}@media (min-width: 1200px){.flex-xl-row{-webkit-box-orient:horizontal !important;-webkit-box-direction:normal !important;-ms-flex-direction:row !important;flex-direction:row !important}.flex-xl-column{-webkit-box-orient:vertical !important;-webkit-box-direction:normal !important;-ms-flex-direction:column !important;flex-direction:column !important}.flex-xl-row-reverse{-webkit-box-orient:horizontal !important;-webkit-box-direction:reverse !important;-ms-flex-direction:row-reverse !important;flex-direction:row-reverse !important}.flex-xl-column-reverse{-webkit-box-orient:vertical !important;-webkit-box-direction:reverse !important;-ms-flex-direction:column-reverse !important;flex-direction:column-reverse !important}.flex-xl-wrap{-ms-flex-wrap:wrap !important;flex-wrap:wrap !important}.flex-xl-nowrap{-ms-flex-wrap:nowrap !important;flex-wrap:nowrap !important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse !important;flex-wrap:wrap-reverse !important}.flex-xl-fill{-webkit-box-flex:1 !important;-ms-flex:1 1 auto !important;flex:1 1 auto !important}.flex-xl-grow-0{-webkit-box-flex:0 !important;-ms-flex-positive:0 !important;flex-grow:0 !important}.flex-xl-grow-1{-webkit-box-flex:1 !important;-ms-flex-positive:1 !important;flex-grow:1 !important}.flex-xl-shrink-0{-ms-flex-negative:0 !important;flex-shrink:0 !important}.flex-xl-shrink-1{-ms-flex-negative:1 !important;flex-shrink:1 !important}.justify-content-xl-start{-webkit-box-pack:start !important;-ms-flex-pack:start !important;justify-content:flex-start !important}.justify-content-xl-end{-webkit-box-pack:end !important;-ms-flex-pack:end !important;justify-content:flex-end !important}.justify-content-xl-center{-webkit-box-pack:center !important;-ms-flex-pack:center !important;justify-content:center !important}.justify-content-xl-between{-webkit-box-pack:justify !important;-ms-flex-pack:justify !important;justify-content:space-between !important}.justify-content-xl-around{-ms-flex-pack:distribute !important;justify-content:space-around !important}.align-items-xl-start{-webkit-box-align:start !important;-ms-flex-align:start !important;align-items:flex-start !important}.align-items-xl-end{-webkit-box-align:end !important;-ms-flex-align:end !important;align-items:flex-end !important}.align-items-xl-center{-webkit-box-align:center !important;-ms-flex-align:center !important;align-items:center !important}.align-items-xl-baseline{-webkit-box-align:baseline !important;-ms-flex-align:baseline !important;align-items:baseline !important}.align-items-xl-stretch{-webkit-box-align:stretch !important;-ms-flex-align:stretch !important;align-items:stretch !important}.align-content-xl-start{-ms-flex-line-pack:start !important;align-content:flex-start !important}.align-content-xl-end{-ms-flex-line-pack:end !important;align-content:flex-end !important}.align-content-xl-center{-ms-flex-line-pack:center !important;align-content:center !important}.align-content-xl-between{-ms-flex-line-pack:justify !important;align-content:space-between !important}.align-content-xl-around{-ms-flex-line-pack:distribute !important;align-content:space-around !important}.align-content-xl-stretch{-ms-flex-line-pack:stretch !important;align-content:stretch !important}.align-self-xl-auto{-ms-flex-item-align:auto !important;align-self:auto !important}.align-self-xl-start{-ms-flex-item-align:start !important;align-self:flex-start !important}.align-self-xl-end{-ms-flex-item-align:end !important;align-self:flex-end !important}.align-self-xl-center{-ms-flex-item-align:center !important;align-self:center !important}.align-self-xl-baseline{-ms-flex-item-align:baseline !important;align-self:baseline !important}.align-self-xl-stretch{-ms-flex-item-align:stretch !important;align-self:stretch !important}}.float-left{float:left !important}.float-right{float:right !important}.float-none{float:none !important}@media (min-width: 576px){.float-sm-left{float:left !important}.float-sm-right{float:right !important}.float-sm-none{float:none !important}}@media (min-width: 768px){.float-md-left{float:left !important}.float-md-right{float:right !important}.float-md-none{float:none !important}}@media (min-width: 992px){.float-lg-left{float:left !important}.float-lg-right{float:right !important}.float-lg-none{float:none !important}}@media (min-width: 1200px){.float-xl-left{float:left !important}.float-xl-right{float:right !important}.float-xl-none{float:none !important}}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:-webkit-sticky !important;position:sticky !important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports (position: -webkit-sticky) or (position: sticky){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{-webkit-box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important;box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important}.shadow{-webkit-box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important;box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.shadow-lg{-webkit-box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important;box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important}.shadow-none{-webkit-box-shadow:none !important;box-shadow:none !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.min-vw-100{min-width:100vw !important}.min-vh-100{min-height:100vh !important}.vw-100{width:100vw !important}.vh-100{height:100vh !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0 !important}.mt-0,.my-0{margin-top:0 !important}.mr-0,.mx-0{margin-right:0 !important}.mb-0,.my-0{margin-bottom:0 !important}.ml-0,.mx-0{margin-left:0 !important}.m-1{margin:0.25rem !important}.mt-1,.my-1{margin-top:0.25rem !important}.mr-1,.mx-1{margin-right:0.25rem !important}.mb-1,.my-1{margin-bottom:0.25rem !important}.ml-1,.mx-1{margin-left:0.25rem !important}.m-2{margin:0.5rem !important}.mt-2,.my-2{margin-top:0.5rem !important}.mr-2,.mx-2{margin-right:0.5rem !important}.mb-2,.my-2{margin-bottom:0.5rem !important}.ml-2,.mx-2{margin-left:0.5rem !important}.m-3{margin:1rem !important}.mt-3,.my-3{margin-top:1rem !important}.mr-3,.mx-3{margin-right:1rem !important}.mb-3,.my-3{margin-bottom:1rem !important}.ml-3,.mx-3{margin-left:1rem !important}.m-4{margin:1.5rem !important}.mt-4,.my-4{margin-top:1.5rem !important}.mr-4,.mx-4{margin-right:1.5rem !important}.mb-4,.my-4{margin-bottom:1.5rem !important}.ml-4,.mx-4{margin-left:1.5rem !important}.m-5{margin:3rem !important}.mt-5,.my-5{margin-top:3rem !important}.mr-5,.mx-5{margin-right:3rem !important}.mb-5,.my-5{margin-bottom:3rem !important}.ml-5,.mx-5{margin-left:3rem !important}.p-0{padding:0 !important}.pt-0,.py-0{padding-top:0 !important}.pr-0,.px-0{padding-right:0 !important}.pb-0,.py-0{padding-bottom:0 !important}.pl-0,.px-0{padding-left:0 !important}.p-1{padding:0.25rem !important}.pt-1,.py-1{padding-top:0.25rem !important}.pr-1,.px-1{padding-right:0.25rem !important}.pb-1,.py-1{padding-bottom:0.25rem !important}.pl-1,.px-1{padding-left:0.25rem !important}.p-2{padding:0.5rem !important}.pt-2,.py-2{padding-top:0.5rem !important}.pr-2,.px-2{padding-right:0.5rem !important}.pb-2,.py-2{padding-bottom:0.5rem !important}.pl-2,.px-2{padding-left:0.5rem !important}.p-3{padding:1rem !important}.pt-3,.py-3{padding-top:1rem !important}.pr-3,.px-3{padding-right:1rem !important}.pb-3,.py-3{padding-bottom:1rem !important}.pl-3,.px-3{padding-left:1rem !important}.p-4{padding:1.5rem !important}.pt-4,.py-4{padding-top:1.5rem !important}.pr-4,.px-4{padding-right:1.5rem !important}.pb-4,.py-4{padding-bottom:1.5rem !important}.pl-4,.px-4{padding-left:1.5rem !important}.p-5{padding:3rem !important}.pt-5,.py-5{padding-top:3rem !important}.pr-5,.px-5{padding-right:3rem !important}.pb-5,.py-5{padding-bottom:3rem !important}.pl-5,.px-5{padding-left:3rem !important}.m-n1{margin:-0.25rem !important}.mt-n1,.my-n1{margin-top:-0.25rem !important}.mr-n1,.mx-n1{margin-right:-0.25rem !important}.mb-n1,.my-n1{margin-bottom:-0.25rem !important}.ml-n1,.mx-n1{margin-left:-0.25rem !important}.m-n2{margin:-0.5rem !important}.mt-n2,.my-n2{margin-top:-0.5rem !important}.mr-n2,.mx-n2{margin-right:-0.5rem !important}.mb-n2,.my-n2{margin-bottom:-0.5rem !important}.ml-n2,.mx-n2{margin-left:-0.5rem !important}.m-n3{margin:-1rem !important}.mt-n3,.my-n3{margin-top:-1rem !important}.mr-n3,.mx-n3{margin-right:-1rem !important}.mb-n3,.my-n3{margin-bottom:-1rem !important}.ml-n3,.mx-n3{margin-left:-1rem !important}.m-n4{margin:-1.5rem !important}.mt-n4,.my-n4{margin-top:-1.5rem !important}.mr-n4,.mx-n4{margin-right:-1.5rem !important}.mb-n4,.my-n4{margin-bottom:-1.5rem !important}.ml-n4,.mx-n4{margin-left:-1.5rem !important}.m-n5{margin:-3rem !important}.mt-n5,.my-n5{margin-top:-3rem !important}.mr-n5,.mx-n5{margin-right:-3rem !important}.mb-n5,.my-n5{margin-bottom:-3rem !important}.ml-n5,.mx-n5{margin-left:-3rem !important}.m-auto{margin:auto !important}.mt-auto,.my-auto{margin-top:auto !important}.mr-auto,.mx-auto{margin-right:auto !important}.mb-auto,.my-auto{margin-bottom:auto !important}.ml-auto,.mx-auto{margin-left:auto !important}@media (min-width: 576px){.m-sm-0{margin:0 !important}.mt-sm-0,.my-sm-0{margin-top:0 !important}.mr-sm-0,.mx-sm-0{margin-right:0 !important}.mb-sm-0,.my-sm-0{margin-bottom:0 !important}.ml-sm-0,.mx-sm-0{margin-left:0 !important}.m-sm-1{margin:0.25rem !important}.mt-sm-1,.my-sm-1{margin-top:0.25rem !important}.mr-sm-1,.mx-sm-1{margin-right:0.25rem !important}.mb-sm-1,.my-sm-1{margin-bottom:0.25rem !important}.ml-sm-1,.mx-sm-1{margin-left:0.25rem !important}.m-sm-2{margin:0.5rem !important}.mt-sm-2,.my-sm-2{margin-top:0.5rem !important}.mr-sm-2,.mx-sm-2{margin-right:0.5rem !important}.mb-sm-2,.my-sm-2{margin-bottom:0.5rem !important}.ml-sm-2,.mx-sm-2{margin-left:0.5rem !important}.m-sm-3{margin:1rem !important}.mt-sm-3,.my-sm-3{margin-top:1rem !important}.mr-sm-3,.mx-sm-3{margin-right:1rem !important}.mb-sm-3,.my-sm-3{margin-bottom:1rem !important}.ml-sm-3,.mx-sm-3{margin-left:1rem !important}.m-sm-4{margin:1.5rem !important}.mt-sm-4,.my-sm-4{margin-top:1.5rem !important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem !important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem !important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem !important}.m-sm-5{margin:3rem !important}.mt-sm-5,.my-sm-5{margin-top:3rem !important}.mr-sm-5,.mx-sm-5{margin-right:3rem !important}.mb-sm-5,.my-sm-5{margin-bottom:3rem !important}.ml-sm-5,.mx-sm-5{margin-left:3rem !important}.p-sm-0{padding:0 !important}.pt-sm-0,.py-sm-0{padding-top:0 !important}.pr-sm-0,.px-sm-0{padding-right:0 !important}.pb-sm-0,.py-sm-0{padding-bottom:0 !important}.pl-sm-0,.px-sm-0{padding-left:0 !important}.p-sm-1{padding:0.25rem !important}.pt-sm-1,.py-sm-1{padding-top:0.25rem !important}.pr-sm-1,.px-sm-1{padding-right:0.25rem !important}.pb-sm-1,.py-sm-1{padding-bottom:0.25rem !important}.pl-sm-1,.px-sm-1{padding-left:0.25rem !important}.p-sm-2{padding:0.5rem !important}.pt-sm-2,.py-sm-2{padding-top:0.5rem !important}.pr-sm-2,.px-sm-2{padding-right:0.5rem !important}.pb-sm-2,.py-sm-2{padding-bottom:0.5rem !important}.pl-sm-2,.px-sm-2{padding-left:0.5rem !important}.p-sm-3{padding:1rem !important}.pt-sm-3,.py-sm-3{padding-top:1rem !important}.pr-sm-3,.px-sm-3{padding-right:1rem !important}.pb-sm-3,.py-sm-3{padding-bottom:1rem !important}.pl-sm-3,.px-sm-3{padding-left:1rem !important}.p-sm-4{padding:1.5rem !important}.pt-sm-4,.py-sm-4{padding-top:1.5rem !important}.pr-sm-4,.px-sm-4{padding-right:1.5rem !important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem !important}.pl-sm-4,.px-sm-4{padding-left:1.5rem !important}.p-sm-5{padding:3rem !important}.pt-sm-5,.py-sm-5{padding-top:3rem !important}.pr-sm-5,.px-sm-5{padding-right:3rem !important}.pb-sm-5,.py-sm-5{padding-bottom:3rem !important}.pl-sm-5,.px-sm-5{padding-left:3rem !important}.m-sm-n1{margin:-0.25rem !important}.mt-sm-n1,.my-sm-n1{margin-top:-0.25rem !important}.mr-sm-n1,.mx-sm-n1{margin-right:-0.25rem !important}.mb-sm-n1,.my-sm-n1{margin-bottom:-0.25rem !important}.ml-sm-n1,.mx-sm-n1{margin-left:-0.25rem !important}.m-sm-n2{margin:-0.5rem !important}.mt-sm-n2,.my-sm-n2{margin-top:-0.5rem !important}.mr-sm-n2,.mx-sm-n2{margin-right:-0.5rem !important}.mb-sm-n2,.my-sm-n2{margin-bottom:-0.5rem !important}.ml-sm-n2,.mx-sm-n2{margin-left:-0.5rem !important}.m-sm-n3{margin:-1rem !important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem !important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem !important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem !important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem !important}.m-sm-n4{margin:-1.5rem !important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem !important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem !important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem !important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem !important}.m-sm-n5{margin:-3rem !important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem !important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem !important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem !important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem !important}.m-sm-auto{margin:auto !important}.mt-sm-auto,.my-sm-auto{margin-top:auto !important}.mr-sm-auto,.mx-sm-auto{margin-right:auto !important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto !important}.ml-sm-auto,.mx-sm-auto{margin-left:auto !important}}@media (min-width: 768px){.m-md-0{margin:0 !important}.mt-md-0,.my-md-0{margin-top:0 !important}.mr-md-0,.mx-md-0{margin-right:0 !important}.mb-md-0,.my-md-0{margin-bottom:0 !important}.ml-md-0,.mx-md-0{margin-left:0 !important}.m-md-1{margin:0.25rem !important}.mt-md-1,.my-md-1{margin-top:0.25rem !important}.mr-md-1,.mx-md-1{margin-right:0.25rem !important}.mb-md-1,.my-md-1{margin-bottom:0.25rem !important}.ml-md-1,.mx-md-1{margin-left:0.25rem !important}.m-md-2{margin:0.5rem !important}.mt-md-2,.my-md-2{margin-top:0.5rem !important}.mr-md-2,.mx-md-2{margin-right:0.5rem !important}.mb-md-2,.my-md-2{margin-bottom:0.5rem !important}.ml-md-2,.mx-md-2{margin-left:0.5rem !important}.m-md-3{margin:1rem !important}.mt-md-3,.my-md-3{margin-top:1rem !important}.mr-md-3,.mx-md-3{margin-right:1rem !important}.mb-md-3,.my-md-3{margin-bottom:1rem !important}.ml-md-3,.mx-md-3{margin-left:1rem !important}.m-md-4{margin:1.5rem !important}.mt-md-4,.my-md-4{margin-top:1.5rem !important}.mr-md-4,.mx-md-4{margin-right:1.5rem !important}.mb-md-4,.my-md-4{margin-bottom:1.5rem !important}.ml-md-4,.mx-md-4{margin-left:1.5rem !important}.m-md-5{margin:3rem !important}.mt-md-5,.my-md-5{margin-top:3rem !important}.mr-md-5,.mx-md-5{margin-right:3rem !important}.mb-md-5,.my-md-5{margin-bottom:3rem !important}.ml-md-5,.mx-md-5{margin-left:3rem !important}.p-md-0{padding:0 !important}.pt-md-0,.py-md-0{padding-top:0 !important}.pr-md-0,.px-md-0{padding-right:0 !important}.pb-md-0,.py-md-0{padding-bottom:0 !important}.pl-md-0,.px-md-0{padding-left:0 !important}.p-md-1{padding:0.25rem !important}.pt-md-1,.py-md-1{padding-top:0.25rem !important}.pr-md-1,.px-md-1{padding-right:0.25rem !important}.pb-md-1,.py-md-1{padding-bottom:0.25rem !important}.pl-md-1,.px-md-1{padding-left:0.25rem !important}.p-md-2{padding:0.5rem !important}.pt-md-2,.py-md-2{padding-top:0.5rem !important}.pr-md-2,.px-md-2{padding-right:0.5rem !important}.pb-md-2,.py-md-2{padding-bottom:0.5rem !important}.pl-md-2,.px-md-2{padding-left:0.5rem !important}.p-md-3{padding:1rem !important}.pt-md-3,.py-md-3{padding-top:1rem !important}.pr-md-3,.px-md-3{padding-right:1rem !important}.pb-md-3,.py-md-3{padding-bottom:1rem !important}.pl-md-3,.px-md-3{padding-left:1rem !important}.p-md-4{padding:1.5rem !important}.pt-md-4,.py-md-4{padding-top:1.5rem !important}.pr-md-4,.px-md-4{padding-right:1.5rem !important}.pb-md-4,.py-md-4{padding-bottom:1.5rem !important}.pl-md-4,.px-md-4{padding-left:1.5rem !important}.p-md-5{padding:3rem !important}.pt-md-5,.py-md-5{padding-top:3rem !important}.pr-md-5,.px-md-5{padding-right:3rem !important}.pb-md-5,.py-md-5{padding-bottom:3rem !important}.pl-md-5,.px-md-5{padding-left:3rem !important}.m-md-n1{margin:-0.25rem !important}.mt-md-n1,.my-md-n1{margin-top:-0.25rem !important}.mr-md-n1,.mx-md-n1{margin-right:-0.25rem !important}.mb-md-n1,.my-md-n1{margin-bottom:-0.25rem !important}.ml-md-n1,.mx-md-n1{margin-left:-0.25rem !important}.m-md-n2{margin:-0.5rem !important}.mt-md-n2,.my-md-n2{margin-top:-0.5rem !important}.mr-md-n2,.mx-md-n2{margin-right:-0.5rem !important}.mb-md-n2,.my-md-n2{margin-bottom:-0.5rem !important}.ml-md-n2,.mx-md-n2{margin-left:-0.5rem !important}.m-md-n3{margin:-1rem !important}.mt-md-n3,.my-md-n3{margin-top:-1rem !important}.mr-md-n3,.mx-md-n3{margin-right:-1rem !important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem !important}.ml-md-n3,.mx-md-n3{margin-left:-1rem !important}.m-md-n4{margin:-1.5rem !important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem !important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem !important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem !important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem !important}.m-md-n5{margin:-3rem !important}.mt-md-n5,.my-md-n5{margin-top:-3rem !important}.mr-md-n5,.mx-md-n5{margin-right:-3rem !important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem !important}.ml-md-n5,.mx-md-n5{margin-left:-3rem !important}.m-md-auto{margin:auto !important}.mt-md-auto,.my-md-auto{margin-top:auto !important}.mr-md-auto,.mx-md-auto{margin-right:auto !important}.mb-md-auto,.my-md-auto{margin-bottom:auto !important}.ml-md-auto,.mx-md-auto{margin-left:auto !important}}@media (min-width: 992px){.m-lg-0{margin:0 !important}.mt-lg-0,.my-lg-0{margin-top:0 !important}.mr-lg-0,.mx-lg-0{margin-right:0 !important}.mb-lg-0,.my-lg-0{margin-bottom:0 !important}.ml-lg-0,.mx-lg-0{margin-left:0 !important}.m-lg-1{margin:0.25rem !important}.mt-lg-1,.my-lg-1{margin-top:0.25rem !important}.mr-lg-1,.mx-lg-1{margin-right:0.25rem !important}.mb-lg-1,.my-lg-1{margin-bottom:0.25rem !important}.ml-lg-1,.mx-lg-1{margin-left:0.25rem !important}.m-lg-2{margin:0.5rem !important}.mt-lg-2,.my-lg-2{margin-top:0.5rem !important}.mr-lg-2,.mx-lg-2{margin-right:0.5rem !important}.mb-lg-2,.my-lg-2{margin-bottom:0.5rem !important}.ml-lg-2,.mx-lg-2{margin-left:0.5rem !important}.m-lg-3{margin:1rem !important}.mt-lg-3,.my-lg-3{margin-top:1rem !important}.mr-lg-3,.mx-lg-3{margin-right:1rem !important}.mb-lg-3,.my-lg-3{margin-bottom:1rem !important}.ml-lg-3,.mx-lg-3{margin-left:1rem !important}.m-lg-4{margin:1.5rem !important}.mt-lg-4,.my-lg-4{margin-top:1.5rem !important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem !important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem !important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem !important}.m-lg-5{margin:3rem !important}.mt-lg-5,.my-lg-5{margin-top:3rem !important}.mr-lg-5,.mx-lg-5{margin-right:3rem !important}.mb-lg-5,.my-lg-5{margin-bottom:3rem !important}.ml-lg-5,.mx-lg-5{margin-left:3rem !important}.p-lg-0{padding:0 !important}.pt-lg-0,.py-lg-0{padding-top:0 !important}.pr-lg-0,.px-lg-0{padding-right:0 !important}.pb-lg-0,.py-lg-0{padding-bottom:0 !important}.pl-lg-0,.px-lg-0{padding-left:0 !important}.p-lg-1{padding:0.25rem !important}.pt-lg-1,.py-lg-1{padding-top:0.25rem !important}.pr-lg-1,.px-lg-1{padding-right:0.25rem !important}.pb-lg-1,.py-lg-1{padding-bottom:0.25rem !important}.pl-lg-1,.px-lg-1{padding-left:0.25rem !important}.p-lg-2{padding:0.5rem !important}.pt-lg-2,.py-lg-2{padding-top:0.5rem !important}.pr-lg-2,.px-lg-2{padding-right:0.5rem !important}.pb-lg-2,.py-lg-2{padding-bottom:0.5rem !important}.pl-lg-2,.px-lg-2{padding-left:0.5rem !important}.p-lg-3{padding:1rem !important}.pt-lg-3,.py-lg-3{padding-top:1rem !important}.pr-lg-3,.px-lg-3{padding-right:1rem !important}.pb-lg-3,.py-lg-3{padding-bottom:1rem !important}.pl-lg-3,.px-lg-3{padding-left:1rem !important}.p-lg-4{padding:1.5rem !important}.pt-lg-4,.py-lg-4{padding-top:1.5rem !important}.pr-lg-4,.px-lg-4{padding-right:1.5rem !important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem !important}.pl-lg-4,.px-lg-4{padding-left:1.5rem !important}.p-lg-5{padding:3rem !important}.pt-lg-5,.py-lg-5{padding-top:3rem !important}.pr-lg-5,.px-lg-5{padding-right:3rem !important}.pb-lg-5,.py-lg-5{padding-bottom:3rem !important}.pl-lg-5,.px-lg-5{padding-left:3rem !important}.m-lg-n1{margin:-0.25rem !important}.mt-lg-n1,.my-lg-n1{margin-top:-0.25rem !important}.mr-lg-n1,.mx-lg-n1{margin-right:-0.25rem !important}.mb-lg-n1,.my-lg-n1{margin-bottom:-0.25rem !important}.ml-lg-n1,.mx-lg-n1{margin-left:-0.25rem !important}.m-lg-n2{margin:-0.5rem !important}.mt-lg-n2,.my-lg-n2{margin-top:-0.5rem !important}.mr-lg-n2,.mx-lg-n2{margin-right:-0.5rem !important}.mb-lg-n2,.my-lg-n2{margin-bottom:-0.5rem !important}.ml-lg-n2,.mx-lg-n2{margin-left:-0.5rem !important}.m-lg-n3{margin:-1rem !important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem !important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem !important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem !important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem !important}.m-lg-n4{margin:-1.5rem !important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem !important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem !important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem !important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem !important}.m-lg-n5{margin:-3rem !important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem !important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem !important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem !important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem !important}.m-lg-auto{margin:auto !important}.mt-lg-auto,.my-lg-auto{margin-top:auto !important}.mr-lg-auto,.mx-lg-auto{margin-right:auto !important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto !important}.ml-lg-auto,.mx-lg-auto{margin-left:auto !important}}@media (min-width: 1200px){.m-xl-0{margin:0 !important}.mt-xl-0,.my-xl-0{margin-top:0 !important}.mr-xl-0,.mx-xl-0{margin-right:0 !important}.mb-xl-0,.my-xl-0{margin-bottom:0 !important}.ml-xl-0,.mx-xl-0{margin-left:0 !important}.m-xl-1{margin:0.25rem !important}.mt-xl-1,.my-xl-1{margin-top:0.25rem !important}.mr-xl-1,.mx-xl-1{margin-right:0.25rem !important}.mb-xl-1,.my-xl-1{margin-bottom:0.25rem !important}.ml-xl-1,.mx-xl-1{margin-left:0.25rem !important}.m-xl-2{margin:0.5rem !important}.mt-xl-2,.my-xl-2{margin-top:0.5rem !important}.mr-xl-2,.mx-xl-2{margin-right:0.5rem !important}.mb-xl-2,.my-xl-2{margin-bottom:0.5rem !important}.ml-xl-2,.mx-xl-2{margin-left:0.5rem !important}.m-xl-3{margin:1rem !important}.mt-xl-3,.my-xl-3{margin-top:1rem !important}.mr-xl-3,.mx-xl-3{margin-right:1rem !important}.mb-xl-3,.my-xl-3{margin-bottom:1rem !important}.ml-xl-3,.mx-xl-3{margin-left:1rem !important}.m-xl-4{margin:1.5rem !important}.mt-xl-4,.my-xl-4{margin-top:1.5rem !important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem !important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem !important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem !important}.m-xl-5{margin:3rem !important}.mt-xl-5,.my-xl-5{margin-top:3rem !important}.mr-xl-5,.mx-xl-5{margin-right:3rem !important}.mb-xl-5,.my-xl-5{margin-bottom:3rem !important}.ml-xl-5,.mx-xl-5{margin-left:3rem !important}.p-xl-0{padding:0 !important}.pt-xl-0,.py-xl-0{padding-top:0 !important}.pr-xl-0,.px-xl-0{padding-right:0 !important}.pb-xl-0,.py-xl-0{padding-bottom:0 !important}.pl-xl-0,.px-xl-0{padding-left:0 !important}.p-xl-1{padding:0.25rem !important}.pt-xl-1,.py-xl-1{padding-top:0.25rem !important}.pr-xl-1,.px-xl-1{padding-right:0.25rem !important}.pb-xl-1,.py-xl-1{padding-bottom:0.25rem !important}.pl-xl-1,.px-xl-1{padding-left:0.25rem !important}.p-xl-2{padding:0.5rem !important}.pt-xl-2,.py-xl-2{padding-top:0.5rem !important}.pr-xl-2,.px-xl-2{padding-right:0.5rem !important}.pb-xl-2,.py-xl-2{padding-bottom:0.5rem !important}.pl-xl-2,.px-xl-2{padding-left:0.5rem !important}.p-xl-3{padding:1rem !important}.pt-xl-3,.py-xl-3{padding-top:1rem !important}.pr-xl-3,.px-xl-3{padding-right:1rem !important}.pb-xl-3,.py-xl-3{padding-bottom:1rem !important}.pl-xl-3,.px-xl-3{padding-left:1rem !important}.p-xl-4{padding:1.5rem !important}.pt-xl-4,.py-xl-4{padding-top:1.5rem !important}.pr-xl-4,.px-xl-4{padding-right:1.5rem !important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem !important}.pl-xl-4,.px-xl-4{padding-left:1.5rem !important}.p-xl-5{padding:3rem !important}.pt-xl-5,.py-xl-5{padding-top:3rem !important}.pr-xl-5,.px-xl-5{padding-right:3rem !important}.pb-xl-5,.py-xl-5{padding-bottom:3rem !important}.pl-xl-5,.px-xl-5{padding-left:3rem !important}.m-xl-n1{margin:-0.25rem !important}.mt-xl-n1,.my-xl-n1{margin-top:-0.25rem !important}.mr-xl-n1,.mx-xl-n1{margin-right:-0.25rem !important}.mb-xl-n1,.my-xl-n1{margin-bottom:-0.25rem !important}.ml-xl-n1,.mx-xl-n1{margin-left:-0.25rem !important}.m-xl-n2{margin:-0.5rem !important}.mt-xl-n2,.my-xl-n2{margin-top:-0.5rem !important}.mr-xl-n2,.mx-xl-n2{margin-right:-0.5rem !important}.mb-xl-n2,.my-xl-n2{margin-bottom:-0.5rem !important}.ml-xl-n2,.mx-xl-n2{margin-left:-0.5rem !important}.m-xl-n3{margin:-1rem !important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem !important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem !important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem !important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem !important}.m-xl-n4{margin:-1.5rem !important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem !important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem !important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem !important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem !important}.m-xl-n5{margin:-3rem !important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem !important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem !important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem !important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem !important}.m-xl-auto{margin:auto !important}.mt-xl-auto,.my-xl-auto{margin-top:auto !important}.mr-xl-auto,.mx-xl-auto{margin-right:auto !important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto !important}.ml-xl-auto,.mx-xl-auto{margin-left:auto !important}}.text-monospace{font-family:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important}.text-justify{text-align:justify !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left !important}.text-right{text-align:right !important}.text-center{text-align:center !important}@media (min-width: 576px){.text-sm-left{text-align:left !important}.text-sm-right{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.text-md-left{text-align:left !important}.text-md-right{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.text-lg-left{text-align:left !important}.text-lg-right{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.text-xl-left{text-align:left !important}.text-xl-right{text-align:right !important}.text-xl-center{text-align:center !important}}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.font-weight-light{font-weight:300 !important}.font-weight-lighter{font-weight:lighter !important}.font-weight-normal{font-weight:400 !important}.font-weight-bold{font-weight:700 !important}.font-weight-bolder{font-weight:bolder !important}.font-italic{font-style:italic !important}.text-white{color:#fff !important}.text-primary{color:#2196F3 !important}a.text-primary:hover,a.text-primary:focus{color:#0a6ebd !important}.text-secondary{color:#fff !important}a.text-secondary:hover,a.text-secondary:focus{color:#d9d9d9 !important}.text-success{color:#4CAF50 !important}a.text-success:hover,a.text-success:focus{color:#357a38 !important}.text-info{color:#9C27B0 !important}a.text-info:hover,a.text-info:focus{color:#641971 !important}.text-warning{color:#ff9800 !important}a.text-warning:hover,a.text-warning:focus{color:#b36a00 !important}.text-danger{color:#e51c23 !important}a.text-danger:hover,a.text-danger:focus{color:#a21318 !important}.text-light{color:#fff !important}a.text-light:hover,a.text-light:focus{color:#d9d9d9 !important}.text-dark{color:#222 !important}a.text-dark:hover,a.text-dark:focus{color:black !important}.text-body{color:#444 !important}.text-muted{color:#666 !important}.text-black-50{color:rgba(0,0,0,0.5) !important}.text-white-50{color:rgba(255,255,255,0.5) !important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none !important}.text-break{word-break:break-word !important;overflow-wrap:break-word !important}.text-reset{color:inherit !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media print{*,*::before,*::after{text-shadow:none !important;-webkit-box-shadow:none !important;box-shadow:none !important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap !important}pre,blockquote{border:1px solid #bbb;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px !important}.container{min-width:992px !important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #dee2e6 !important}.table-dark{color:inherit}.table-dark th,.table-dark td,.table-dark thead th,.table-dark tbody+tbody{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}}.navbar{border:none;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.3);box-shadow:0 1px 2px rgba(0,0,0,0.3)}.navbar-brand{font-size:24px}.navbar-nav .nav-link{padding-top:0.9rem;padding-bottom:0.9rem}.navbar.bg-dark input[type=text],.navbar.bg-dark input[type=password],.navbar.bg-dark input[type=email],.navbar.bg-dark input[type=number],.navbar.bg-dark input[type=tel],.navbar.bg-primary input[type=text],.navbar.bg-primary input[type=password],.navbar.bg-primary input[type=email],.navbar.bg-primary input[type=number],.navbar.bg-primary input[type=tel]{color:#fff;-webkit-box-shadow:inset 0 -1px 0 rgba(255,255,255,0.5);box-shadow:inset 0 -1px 0 rgba(255,255,255,0.5)}.navbar.bg-dark input[type=text]:focus,.navbar.bg-dark input[type=password]:focus,.navbar.bg-dark input[type=email]:focus,.navbar.bg-dark input[type=number]:focus,.navbar.bg-dark input[type=tel]:focus,.navbar.bg-primary input[type=text]:focus,.navbar.bg-primary input[type=password]:focus,.navbar.bg-primary input[type=email]:focus,.navbar.bg-primary input[type=number]:focus,.navbar.bg-primary input[type=tel]:focus{-webkit-box-shadow:inset 0 -2px 0 #fff;box-shadow:inset 0 -2px 0 #fff}.navbar.bg-dark input[type=text]::-webkit-input-placeholder,.navbar.bg-dark input[type=password]::-webkit-input-placeholder,.navbar.bg-dark input[type=email]::-webkit-input-placeholder,.navbar.bg-dark input[type=number]::-webkit-input-placeholder,.navbar.bg-dark input[type=tel]::-webkit-input-placeholder,.navbar.bg-primary input[type=text]::-webkit-input-placeholder,.navbar.bg-primary input[type=password]::-webkit-input-placeholder,.navbar.bg-primary input[type=email]::-webkit-input-placeholder,.navbar.bg-primary input[type=number]::-webkit-input-placeholder,.navbar.bg-primary input[type=tel]::-webkit-input-placeholder{color:rgba(255,255,255,0.5)}.navbar.bg-dark input[type=text]::-ms-input-placeholder,.navbar.bg-dark input[type=password]::-ms-input-placeholder,.navbar.bg-dark input[type=email]::-ms-input-placeholder,.navbar.bg-dark input[type=number]::-ms-input-placeholder,.navbar.bg-dark input[type=tel]::-ms-input-placeholder,.navbar.bg-primary input[type=text]::-ms-input-placeholder,.navbar.bg-primary input[type=password]::-ms-input-placeholder,.navbar.bg-primary input[type=email]::-ms-input-placeholder,.navbar.bg-primary input[type=number]::-ms-input-placeholder,.navbar.bg-primary input[type=tel]::-ms-input-placeholder{color:rgba(255,255,255,0.5)}.navbar.bg-dark input[type=text]::placeholder,.navbar.bg-dark input[type=password]::placeholder,.navbar.bg-dark input[type=email]::placeholder,.navbar.bg-dark input[type=number]::placeholder,.navbar.bg-dark input[type=tel]::placeholder,.navbar.bg-primary input[type=text]::placeholder,.navbar.bg-primary input[type=password]::placeholder,.navbar.bg-primary input[type=email]::placeholder,.navbar.bg-primary input[type=number]::placeholder,.navbar.bg-primary input[type=tel]::placeholder{color:rgba(255,255,255,0.5)}.btn-primary{position:relative}.btn-primary:focus{background-color:#2196F3;-webkit-box-shadow:0 0 0 2px rgba(204,204,204,0.5);box-shadow:0 0 0 2px rgba(204,204,204,0.5)}.btn-primary:hover,.btn-primary:active:hover{background-color:#0d87e9}.btn-primary:active{-webkit-box-shadow:2px 2px 4px rgba(0,0,0,0.4);box-shadow:2px 2px 4px rgba(0,0,0,0.4)}.btn-primary:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-primary:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-primary.disabled:after,.btn-primary[disabled]:after{display:none}.btn-outline-primary{position:relative}.btn-outline-primary:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-outline-primary:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-outline-primary.disabled:after,.btn-outline-primary[disabled]:after{display:none}.btn-secondary{position:relative}.btn-secondary:focus{background-color:#fff;-webkit-box-shadow:0 0 0 2px rgba(204,204,204,0.5);box-shadow:0 0 0 2px rgba(204,204,204,0.5)}.btn-secondary:hover,.btn-secondary:active:hover{background-color:#f0f0f0}.btn-secondary:active{-webkit-box-shadow:2px 2px 4px rgba(0,0,0,0.4);box-shadow:2px 2px 4px rgba(0,0,0,0.4)}.btn-secondary:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #bbb 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-secondary:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-secondary.disabled:after,.btn-secondary[disabled]:after{display:none}.btn-outline-secondary{position:relative}.btn-outline-secondary:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #bbb 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-outline-secondary:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-outline-secondary.disabled:after,.btn-outline-secondary[disabled]:after{display:none}.btn-success{position:relative}.btn-success:focus{background-color:#4CAF50;-webkit-box-shadow:0 0 0 2px rgba(204,204,204,0.5);box-shadow:0 0 0 2px rgba(204,204,204,0.5)}.btn-success:hover,.btn-success:active:hover{background-color:#439a46}.btn-success:active{-webkit-box-shadow:2px 2px 4px rgba(0,0,0,0.4);box-shadow:2px 2px 4px rgba(0,0,0,0.4)}.btn-success:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-success:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-success.disabled:after,.btn-success[disabled]:after{display:none}.btn-outline-success{position:relative}.btn-outline-success:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-outline-success:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-outline-success.disabled:after,.btn-outline-success[disabled]:after{display:none}.btn-info{position:relative}.btn-info:focus{background-color:#9C27B0;-webkit-box-shadow:0 0 0 2px rgba(204,204,204,0.5);box-shadow:0 0 0 2px rgba(204,204,204,0.5)}.btn-info:hover,.btn-info:active:hover{background-color:#862197}.btn-info:active{-webkit-box-shadow:2px 2px 4px rgba(0,0,0,0.4);box-shadow:2px 2px 4px rgba(0,0,0,0.4)}.btn-info:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-info:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-info.disabled:after,.btn-info[disabled]:after{display:none}.btn-outline-info{position:relative}.btn-outline-info:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-outline-info:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-outline-info.disabled:after,.btn-outline-info[disabled]:after{display:none}.btn-warning{position:relative}.btn-warning:focus{background-color:#ff9800;-webkit-box-shadow:0 0 0 2px rgba(204,204,204,0.5);box-shadow:0 0 0 2px rgba(204,204,204,0.5)}.btn-warning:hover,.btn-warning:active:hover{background-color:#e08600}.btn-warning:active{-webkit-box-shadow:2px 2px 4px rgba(0,0,0,0.4);box-shadow:2px 2px 4px rgba(0,0,0,0.4)}.btn-warning:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-warning:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-warning.disabled:after,.btn-warning[disabled]:after{display:none}.btn-outline-warning{position:relative}.btn-outline-warning:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-outline-warning:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-outline-warning.disabled:after,.btn-outline-warning[disabled]:after{display:none}.btn-danger{position:relative}.btn-danger:focus{background-color:#e51c23;-webkit-box-shadow:0 0 0 2px rgba(204,204,204,0.5);box-shadow:0 0 0 2px rgba(204,204,204,0.5)}.btn-danger:hover,.btn-danger:active:hover{background-color:#cb171e}.btn-danger:active{-webkit-box-shadow:2px 2px 4px rgba(0,0,0,0.4);box-shadow:2px 2px 4px rgba(0,0,0,0.4)}.btn-danger:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-danger:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-danger.disabled:after,.btn-danger[disabled]:after{display:none}.btn-outline-danger{position:relative}.btn-outline-danger:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-outline-danger:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-outline-danger.disabled:after,.btn-outline-danger[disabled]:after{display:none}.btn-dark{position:relative}.btn-dark:focus{background-color:#222;-webkit-box-shadow:0 0 0 2px rgba(204,204,204,0.5);box-shadow:0 0 0 2px rgba(204,204,204,0.5)}.btn-dark:hover,.btn-dark:active:hover{background-color:#131313}.btn-dark:active{-webkit-box-shadow:2px 2px 4px rgba(0,0,0,0.4);box-shadow:2px 2px 4px rgba(0,0,0,0.4)}.btn-dark:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-dark:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-dark.disabled:after,.btn-dark[disabled]:after{display:none}.btn-outline-dark{position:relative}.btn-outline-dark:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-outline-dark:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-outline-dark.disabled:after,.btn-outline-dark[disabled]:after{display:none}.btn-light{position:relative}.btn-light:focus{background-color:#fff;-webkit-box-shadow:0 0 0 2px rgba(204,204,204,0.5);box-shadow:0 0 0 2px rgba(204,204,204,0.5)}.btn-light:hover,.btn-light:active:hover{background-color:#f0f0f0}.btn-light:active{-webkit-box-shadow:2px 2px 4px rgba(0,0,0,0.4);box-shadow:2px 2px 4px rgba(0,0,0,0.4)}.btn-light:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-light:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-light.disabled:after,.btn-light[disabled]:after{display:none}.btn-outline-light{position:relative}.btn-outline-light:after{content:"";display:block;position:absolute;width:100%;height:100%;top:0;left:0;margin-left:0;background-image:radial-gradient(circle, #fff 10%, transparent 10.01%);background-repeat:no-repeat;background-size:1000% 1000%;background-position:50%;border:none;opacity:0;pointer-events:none;-webkit-transition:background .5s, opacity 1s;transition:background .5s, opacity 1s}.btn-outline-light:active:after{background-size:0 0;opacity:.2;-webkit-transition:0s;transition:0s}.btn-outline-light.disabled:after,.btn-outline-light[disabled]:after{display:none}.btn{text-transform:uppercase;border:none;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.4);box-shadow:0 1px 4px rgba(0,0,0,0.4);-webkit-transition:all 0.4s;transition:all 0.4s}.btn-link{-webkit-box-shadow:none;box-shadow:none;color:#2196F3}.btn-link:hover,.btn-link:focus{-webkit-box-shadow:none;box-shadow:none;color:#0a6ebd;text-decoration:underline}.btn-link.disabled:hover,.btn-link.disabled:active:hover,.btn-link[disabled]:hover,.btn-link[disabled]:active:hover,fieldset[disabled] .btn-link:hover,fieldset[disabled] .btn-link:active:hover{color:#666;text-decoration:none}.btn-secondary.disabled,.btn-secondary[disabled],fieldset[disabled] .btn-secondary{background-color:rgba(0,0,0,0.1);color:rgba(0,0,0,0.4);opacity:1}.btn-secondary.disabled:hover,.btn-secondary.disabled:focus,.btn-secondary[disabled]:hover,.btn-secondary[disabled]:focus,fieldset[disabled] .btn-secondary:hover,fieldset[disabled] .btn-secondary:focus{background-color:rgba(0,0,0,0.1)}.btn-outline-secondary{border-color:#eee;color:#dee2e6}.btn-warning{color:#fff}.btn.dropdown-toggle::before{display:inline-block;width:0;height:0;margin-left:0.3em;vertical-align:middle;content:"";border-top:0.3em solid;border-right:0.3em solid transparent;border-left:0.3em solid transparent}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:0}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group>.dropdown-toggle{-webkit-box-shadow:1px 1px 4px rgba(0,0,0,0.4);box-shadow:1px 1px 4px rgba(0,0,0,0.4)}.border-secondary{border:1px solid #dfdfdf !important}body,input,button{-webkit-font-smoothing:antialiased;letter-spacing:.1px}p{margin:0 0 1em}a{-webkit-transition:all 0.2s;transition:all 0.2s}.text-secondary{color:#bbb !important}.table-hover>tbody>tr,.table-hover>tbody>tr>th,.table-hover>tbody>tr>td{-webkit-transition:all 0.2s;transition:all 0.2s}.thead-inverse th{background-color:#2196F3;color:#fff}.col-form-label{font-size:16px}.col-form-label-sm{font-size:0.7109375rem}.col-form-label-lg{font-size:1.015625rem}textarea,textarea.form-control,input.form-control,input[type=text],input[type=password],input[type=email],input[type=number],[type=text].form-control,[type=password].form-control,[type=email].form-control,[type=tel].form-control,[contenteditable].form-control{-webkit-box-shadow:inset 0 -1px 0 #ddd;box-shadow:inset 0 -1px 0 #ddd;-webkit-transition:-webkit-box-shadow 0.2s;transition:-webkit-box-shadow 0.2s;transition:box-shadow 0.2s;transition:box-shadow 0.2s, -webkit-box-shadow 0.2s}textarea:focus,textarea.form-control:focus,input.form-control:focus,input[type=text]:focus,input[type=password]:focus,input[type=email]:focus,input[type=number]:focus,[type=text].form-control:focus,[type=password].form-control:focus,[type=email].form-control:focus,[type=tel].form-control:focus,[contenteditable].form-control:focus{-webkit-box-shadow:inset 0 -2px 0 #2196F3;box-shadow:inset 0 -2px 0 #2196F3}textarea[disabled],textarea[readonly],textarea.form-control[disabled],textarea.form-control[readonly],input.form-control[disabled],input.form-control[readonly],input[type=text][disabled],input[type=text][readonly],input[type=password][disabled],input[type=password][readonly],input[type=email][disabled],input[type=email][readonly],input[type=number][disabled],input[type=number][readonly],[type=text].form-control[disabled],[type=text].form-control[readonly],[type=password].form-control[disabled],[type=password].form-control[readonly],[type=email].form-control[disabled],[type=email].form-control[readonly],[type=tel].form-control[disabled],[type=tel].form-control[readonly],[contenteditable].form-control[disabled],[contenteditable].form-control[readonly]{-webkit-box-shadow:none;box-shadow:none;border-bottom:1px dotted #ddd}textarea[disabled],textarea[disabled]::-webkit-input-placeholder,textarea.form-control[disabled],textarea.form-control[disabled]::-webkit-input-placeholder,input.form-control[disabled],input.form-control[disabled]::-webkit-input-placeholder,input[type=text][disabled],input[type=text][disabled]::-webkit-input-placeholder,input[type=password][disabled],input[type=password][disabled]::-webkit-input-placeholder,input[type=email][disabled],input[type=email][disabled]::-webkit-input-placeholder,input[type=number][disabled],input[type=number][disabled]::-webkit-input-placeholder,[type=text].form-control[disabled],[type=text].form-control[disabled]::-webkit-input-placeholder,[type=password].form-control[disabled],[type=password].form-control[disabled]::-webkit-input-placeholder,[type=email].form-control[disabled],[type=email].form-control[disabled]::-webkit-input-placeholder,[type=tel].form-control[disabled],[type=tel].form-control[disabled]::-webkit-input-placeholder,[contenteditable].form-control[disabled],[contenteditable].form-control[disabled]::-webkit-input-placeholder{color:#ddd}textarea[disabled],textarea[disabled]::-ms-input-placeholder,textarea.form-control[disabled],textarea.form-control[disabled]::-ms-input-placeholder,input.form-control[disabled],input.form-control[disabled]::-ms-input-placeholder,input[type=text][disabled],input[type=text][disabled]::-ms-input-placeholder,input[type=password][disabled],input[type=password][disabled]::-ms-input-placeholder,input[type=email][disabled],input[type=email][disabled]::-ms-input-placeholder,input[type=number][disabled],input[type=number][disabled]::-ms-input-placeholder,[type=text].form-control[disabled],[type=text].form-control[disabled]::-ms-input-placeholder,[type=password].form-control[disabled],[type=password].form-control[disabled]::-ms-input-placeholder,[type=email].form-control[disabled],[type=email].form-control[disabled]::-ms-input-placeholder,[type=tel].form-control[disabled],[type=tel].form-control[disabled]::-ms-input-placeholder,[contenteditable].form-control[disabled],[contenteditable].form-control[disabled]::-ms-input-placeholder{color:#ddd}textarea[disabled],textarea[disabled]::placeholder,textarea.form-control[disabled],textarea.form-control[disabled]::placeholder,input.form-control[disabled],input.form-control[disabled]::placeholder,input[type=text][disabled],input[type=text][disabled]::placeholder,input[type=password][disabled],input[type=password][disabled]::placeholder,input[type=email][disabled],input[type=email][disabled]::placeholder,input[type=number][disabled],input[type=number][disabled]::placeholder,[type=text].form-control[disabled],[type=text].form-control[disabled]::placeholder,[type=password].form-control[disabled],[type=password].form-control[disabled]::placeholder,[type=email].form-control[disabled],[type=email].form-control[disabled]::placeholder,[type=tel].form-control[disabled],[type=tel].form-control[disabled]::placeholder,[contenteditable].form-control[disabled],[contenteditable].form-control[disabled]::placeholder{color:#ddd}textarea::-ms-clear,textarea.form-control::-ms-clear,input.form-control::-ms-clear,input[type=text]::-ms-clear,input[type=password]::-ms-clear,input[type=email]::-ms-clear,input[type=number]::-ms-clear,[type=text].form-control::-ms-clear,[type=password].form-control::-ms-clear,[type=email].form-control::-ms-clear,[type=tel].form-control::-ms-clear,[contenteditable].form-control::-ms-clear{display:none}select,select.form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0.5rem 0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEVmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmaP/QSjAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=);background-size:13px;background-repeat:no-repeat;background-position:right center;-webkit-box-shadow:inset 0 -1px 0 #ddd;box-shadow:inset 0 -1px 0 #ddd}select::-ms-expand,select.form-control::-ms-expand{display:none}select.input-sm,select.form-control.input-sm{font-size:0.7109375rem}select.input-lg,select.form-control.input-lg{font-size:1.015625rem}select:focus,select.form-control:focus{-webkit-box-shadow:inset 0 -2px 0 #2196F3;box-shadow:inset 0 -2px 0 #2196F3;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEUhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISF8S9ewAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=)}select[multiple],select.form-control[multiple]{background:none}.custom-control{min-height:1.5rem}.custom-control-label::before{top:0;border:2px solid #ced4da}.custom-control-label::after{top:0}.custom-control-input:checked ~ .custom-control-label::before{border-color:#2196F3}.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after{-webkit-transform:scale(1.5);transform:scale(1.5)}.custom-radio .custom-control-input:checked ~ .custom-control-label::before{background:#fff}.custom-radio .custom-control-input:checked ~ .custom-control-label::after{-webkit-transform:scale(1.125);transform:scale(1.125);-webkit-transition:240ms;transition:240ms}.custom-radio .custom-control-input ~ .custom-control-label::after{-webkit-transform:scale(0.75);transform:scale(0.75)}.custom-switch .custom-control-label::before{top:0.125rem;height:0.875rem;background:#bbb;border:none}.custom-switch .custom-control-input:disabled ~ .custom-control-label{opacity:0.7}.custom-switch .custom-control-input:disabled ~ .custom-control-label::before{background:#bbb}.custom-switch .custom-control-label::after{background-color:#fff;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3);box-shadow:0 1px 4px rgba(0,0,0,0.3)}.custom-switch .custom-control-input:checked ~ .custom-control-label::before{opacity:0.38}.custom-switch .custom-control-input:checked ~ .custom-control-label::after{background-color:#2196F3}.custom-switch .custom-control-input:hover:not(:disabled) ~ .custom-control-label::after{-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(0,0,0,0.05);box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(0,0,0,0.05)}.custom-switch .custom-control-input:focus:not(:disabled) ~ .custom-control-label::after{-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(0,0,0,0.09);box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(0,0,0,0.09)}.custom-switch .custom-control-input:active:not(:disabled) ~ .custom-control-label::after{-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(0,0,0,0.16);box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(0,0,0,0.16)}.custom-switch .custom-control-input:hover:checked:not(:disabled) ~ .custom-control-label::after{-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(33,150,243,0.05);box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(33,150,243,0.05)}.custom-switch .custom-control-input:focus:checked:not(:disabled) ~ .custom-control-label::after{-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(33,150,243,0.09);box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(33,150,243,0.09)}.custom-switch .custom-control-input:active:checked:not(:disabled) ~ .custom-control-label::after{-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(33,150,243,0.16);box-shadow:0 1px 4px rgba(0,0,0,0.3),0 0 0 9px rgba(33,150,243,0.16)}.custom-switch .custom-control-input:active:not(:checked) ~ .custom-control-label::before{background-color:#bbb}.custom-switch .custom-control-input:active:not(:checked) ~ .custom-control-label::before{background-color:#bbb}.has-warning input:not([type=checkbox]),.has-warning .form-control,.has-warning input.form-control[readonly],.has-warning input[type=text][readonly],.has-warning [type=text].form-control[readonly],.has-warning input:not([type=checkbox]):focus,.has-warning .form-control:focus{border-bottom:none;-webkit-box-shadow:inset 0 -2px 0 #ff9800;box-shadow:inset 0 -2px 0 #ff9800}.has-danger input:not([type=checkbox]),.has-danger .form-control,.has-danger input.form-control[readonly],.has-danger input[type=text][readonly],.has-danger [type=text].form-control[readonly],.has-danger input:not([type=checkbox]):focus,.has-danger .form-control:focus{border-bottom:none;-webkit-box-shadow:inset 0 -2px 0 #e51c23;box-shadow:inset 0 -2px 0 #e51c23}.has-success input:not([type=checkbox]),.has-success .form-control,.has-success input.form-control[readonly],.has-success input[type=text][readonly],.has-success [type=text].form-control[readonly],.has-success input:not([type=checkbox]):focus,.has-success .form-control:focus{border-bottom:none;-webkit-box-shadow:inset 0 -2px 0 #4CAF50;box-shadow:inset 0 -2px 0 #4CAF50}.has-warning .input-group-addon,.has-danger .input-group-addon,.has-success .input-group-addon{color:#666;border-color:transparent;background-color:transparent}.input-group>.input-group-prepend>.input-group-text{padding-right:0.25rem}.input-group>.input-group-append>.input-group-text{padding-left:0.25rem}.input-group .form-control{padding-left:0.25rem;padding-right:0.25rem}.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-append>.btn{padding:0.25rem 0.5rem}.input-group-sm>.input-group-prepend>.input-group-text{padding:0.25rem 0.5rem 0.25rem 0}.input-group-sm>.input-group-append>.input-group-text{padding:0.25rem 0 0.25rem 0.5rem}.input-group-sm>.input-group-prepend>.btn{border-top-left-radius:0.2rem;border-bottom-left-radius:0.2rem}.input-group-sm>.input-group-append>.btn{border-top-right-radius:0.2rem;border-bottom-right-radius:0.2rem}.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-append>.btn{padding:0.5rem 1rem}.input-group-lg>.input-group-prepend>.input-group-text{padding:0.5rem 1rem 0.5rem 0}.input-group-lg>.input-group-append>.input-group-text{padding:0.5rem 0 0.5rem 1rem}.input-group-lg>.input-group-prepend>.btn{border-top-left-radius:0.3rem;border-bottom-left-radius:0.3rem}.input-group-lg>.input-group-append>.btn{border-top-right-radius:0.3rem;border-bottom-right-radius:0.3rem}.form-group-lg select,.form-group-lg select.form-control{line-height:1.5}.nav-tabs .nav-item+.nav-item{margin-left:0}.nav-tabs .nav-link,.nav-tabs .nav-link:focus{margin-right:0;background-color:transparent;border:none;color:#444;-webkit-box-shadow:inset 0 -1px 0 #ddd;box-shadow:inset 0 -1px 0 #ddd;-webkit-transition:all 0.2s;transition:all 0.2s}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus:hover{background-color:transparent;-webkit-box-shadow:inset 0 -2px 0 #2196F3;box-shadow:inset 0 -2px 0 #2196F3;color:#2196F3}.nav-tabs .nav-link.active,.nav-tabs .nav-link.active:focus{border:none;-webkit-box-shadow:inset 0 -2px 0 #2196F3;box-shadow:inset 0 -2px 0 #2196F3;color:#2196F3}.nav-tabs .nav-link.active:hover,.nav-tabs .nav-link.active:focus:hover{border:none;color:#2196F3}.nav-tabs .nav-link.disabled{-webkit-box-shadow:inset 0 -1px 0 #ddd;box-shadow:inset 0 -1px 0 #ddd}.nav-tabs.nav-justified .nav-link,.nav-tabs.nav-justified .nav-link:hover,.nav-tabs.nav-justified .nav-link:focus,.nav-tabs.nav-justified .nav-link.active,.nav-tabs.nav-justified .nav-link.active:hover,.nav-tabs.nav-justified .nav-link.active:focus{border:none}.nav-tabs .dropdown-menu{margin-top:0}.dropdown-menu{margin-top:0;border:none;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3);box-shadow:0 1px 4px rgba(0,0,0,0.3)}.alert{padding-right:2.5rem;border:none}.alert,.alert h1,.alert h2,.alert h3,.alert h4,.alert h5,.alert h6{color:#fff}.alert-primary{background:#2196F3 -webkit-gradient(linear, left top, left bottom, from(#42a6f5), to(#2196F3)) repeat-x;background:#2196F3 linear-gradient(180deg, #42a6f5, #2196F3) repeat-x}.alert-secondary{background:#fff -webkit-gradient(linear, left top, left bottom, from(white), to(#fff)) repeat-x;background:#fff linear-gradient(180deg, white, #fff) repeat-x}.alert-success{background:#4CAF50 -webkit-gradient(linear, left top, left bottom, from(#67bb6a), to(#4CAF50)) repeat-x;background:#4CAF50 linear-gradient(180deg, #67bb6a, #4CAF50) repeat-x}.alert-info{background:#9C27B0 -webkit-gradient(linear, left top, left bottom, from(#ab47bc), to(#9C27B0)) repeat-x;background:#9C27B0 linear-gradient(180deg, #ab47bc, #9C27B0) repeat-x}.alert-warning{background:#ff9800 -webkit-gradient(linear, left top, left bottom, from(#ffa726), to(#ff9800)) repeat-x;background:#ff9800 linear-gradient(180deg, #ffa726, #ff9800) repeat-x}.alert-danger{background:#e51c23 -webkit-gradient(linear, left top, left bottom, from(#e93e44), to(#e51c23)) repeat-x;background:#e51c23 linear-gradient(180deg, #e93e44, #e51c23) repeat-x}.alert-light{background:#fff -webkit-gradient(linear, left top, left bottom, from(white), to(#fff)) repeat-x;background:#fff linear-gradient(180deg, white, #fff) repeat-x}.alert-dark{background:#222 -webkit-gradient(linear, left top, left bottom, from(#434343), to(#222)) repeat-x;background:#222 linear-gradient(180deg, #434343, #222) repeat-x}.alert a:not(.btn),.alert .alert-link{color:#fff;font-weight:bold}.alert .close{color:#fff}.alert-secondary,.alert-secondary a:not(.btn),.alert-secondary .alert-link,.alert-light,.alert-light a:not(.btn),.alert-light .alert-link{color:#444}.badge-secondary{background-color:#bbb;color:#fff}.badge-light{background-color:#eee}.badge-warning{color:#fff}.tag{padding:4px 6px 4px}.close{line-height:0.5;opacity:0.6;-webkit-transition:all 0.2s;transition:all 0.2s}.close:hover{opacity:1}.card{-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.4);box-shadow:0 1px 4px rgba(0,0,0,0.4)}.card.border-primary,.card.border-secondary,.card.border-success,.card.border-info,.card.border-warning,.card.border-danger,.card.border-light,.card.border-dark{border-width:1px}.alert-dismissible .close{padding:0.5rem 0.75rem}.list-group-item-action.active h1,.list-group-item-action.active h2,.list-group-item-action.active h3,.list-group-item-action.active h4,.list-group-item-action.active h5,.list-group-item-action.active h6{color:#fff}.modal-content{border-radius:0.2rem;-webkit-box-shadow:0 6px 36px rgba(0,0,0,0.3);box-shadow:0 6px 36px rgba(0,0,0,0.3)}.popover{border:none;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.3);box-shadow:0 1px 4px rgba(0,0,0,0.3)}.carousel-caption h1,.carousel-caption h2,.carousel-caption h3,.carousel-caption h4,.carousel-caption h5,.carousel-caption h6{color:inherit}
diff --git a/ui/assets/css/tippy.css b/ui/assets/css/tippy.css
deleted file mode 100644
index ff0a3132..00000000
--- a/ui/assets/css/tippy.css
+++ /dev/null
@@ -1 +0,0 @@
-.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}.tippy-iOS{cursor:pointer!important;-webkit-tap-highlight-color:transparent}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-width:8px 8px 0;border-top-color:#333;bottom:-7px;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;border-width:0 8px 8px;border-bottom-color:#333;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:#333;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:#333;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
\ No newline at end of file
diff --git a/ui/assets/css/toastify.css b/ui/assets/css/toastify.css
deleted file mode 100644
index 8804e229..00000000
--- a/ui/assets/css/toastify.css
+++ /dev/null
@@ -1,78 +0,0 @@
-/*!
- * Toastify js 1.6.2
- * https://github.com/apvarun/toastify-js
- * @license MIT licensed
- *
- * Copyright (C) 2018 Varun A P
- */
-
-.toastify {
-    padding: 12px 20px;
-    color: #ffffff;
-    display: inline-block;
-    box-shadow: 0 3px 6px -1px rgba(0, 0, 0, 0.12), 0 10px 36px -4px rgba(77, 96, 232, 0.3);
-    background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5);
-    background: linear-gradient(135deg, #73a5ff, #5477f5);
-    position: fixed;
-    opacity: 0;
-    transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
-    border-radius: 2px;
-    cursor: pointer;
-    text-decoration: none;
-    max-width: calc(50% - 20px);
-    z-index: 2147483647;
-}
-
-.toastify.on {
-    opacity: 1;
-}
-
-.toast-close {
-    opacity: 0.4;
-    padding: 0 5px;
-}
-
-.toastify-right {
-    right: 15px;
-}
-
-.toastify-left {
-    left: 15px;
-}
-
-.toastify-top {
-    top: -150px;
-}
-
-.toastify-bottom {
-    bottom: -150px;
-}
-
-.toastify-rounded {
-    border-radius: 25px;
-}
-
-.toastify-avatar {
-    width: 1.5em;
-    height: 1.5em;
-    margin: 0 5px;
-    border-radius: 2px;
-}
-
-.toastify-center {
-    margin-left: auto;
-    margin-right: auto;
-    left: 0;
-    right: 0;
-    max-width: fit-content;
-}
-
-@media only screen and (max-width: 360px) {
-    .toastify-right, .toastify-left {
-        margin-left: auto;
-        margin-right: auto;
-        left: 0;
-        right: 0;
-        max-width: fit-content;
-    }
-}
diff --git a/ui/fuse.js b/ui/fuse.js
index 00d28caf..85eb75e2 100644
--- a/ui/fuse.js
+++ b/ui/fuse.js
@@ -4,7 +4,7 @@ const {
   EnvPlugin,
   CSSPlugin,
   WebIndexPlugin,
-  QuantumPlugin,
+  QuantumPlugin
 } = require('fuse-box');
 // const transformInferno = require('../../dist').default
 const transformInferno = require('ts-transform-inferno').default;
@@ -25,22 +25,22 @@ Sparky.task('config', _ => {
       before: [transformClasscat(), transformInferno()],
     },
     alias: {
-      locale: 'moment/locale',
-    },
+      'locale': 'moment/locale'
+		},
     plugins: [
       EnvPlugin({ NODE_ENV: isProduction ? 'production' : 'development' }),
       CSSPlugin(),
       WebIndexPlugin({
         title: 'Inferno Typescript FuseBox Example',
         template: 'src/index.html',
-        path: isProduction ? '/static' : '/',
+        path: isProduction ? "/static" : "/"
       }),
       isProduction &&
-        QuantumPlugin({
-          bakeApiIntoBundle: 'app',
-          treeshake: true,
-          uglify: true,
-        }),
+      QuantumPlugin({
+        bakeApiIntoBundle: 'app',
+        treeshake: true,
+        uglify: true,
+      }),
     ],
   });
   app = fuse.bundle('app').instructions('>index.tsx');
@@ -48,9 +48,7 @@ Sparky.task('config', _ => {
 // Sparky.task('version', _ => setVersion());
 Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/'));
 Sparky.task('env', _ => (isProduction = true));
-Sparky.task('copy-assets', () =>
-  Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static')
-);
+Sparky.task('copy-assets', () => Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static'));
 Sparky.task('dev', ['clean', 'config', 'copy-assets'], _ => {
   fuse.dev();
   app.hmr().watch();
diff --git a/ui/generate_translations.js b/ui/generate_translations.js
deleted file mode 100644
index ee55731b..00000000
--- a/ui/generate_translations.js
+++ /dev/null
@@ -1,25 +0,0 @@
-fs = require('fs');
-
-fs.mkdirSync('src/translations/', { recursive: true });
-fs.readdir('translations', (err, files) => {
-  files.forEach(filename => {
-    const lang = filename.split('.')[0];
-    try {
-      const json = JSON.parse(
-        fs.readFileSync('translations/' + filename, 'utf8')
-      );
-      var data = `export const ${lang} = {\n  translation: {`;
-      for (var key in json) {
-        if (key in json) {
-          const value = json[key].replace(/"/g, '\\"');
-          data = `${data}\n    ${key}: "${value}",`;
-        }
-      }
-      data += '\n  },\n};';
-      const target = 'src/translations/' + lang + '.ts';
-      fs.writeFileSync(target, data);
-    } catch (err) {
-      console.error(err);
-    }
-  });
-});
diff --git a/ui/package.json b/ui/package.json
index 21458f0d..f816b4f6 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,64 +1,56 @@
 {
   "name": "lemmy",
-  "description": "The official Lemmy UI",
+  "description": "A simple UI for lemmy",
   "version": "1.0.0",
   "author": "Dessalines",
-  "license": "AGPL-3.0-or-later",
+  "license": "GPL-2.0-or-later",
   "main": "index.js",
   "scripts": {
     "build": "node fuse prod",
-    "lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src",
-    "prebuild": "node generate_translations.js",
-    "prestart": "node generate_translations.js",
+    "lint": "eslint --report-unused-disable-directives --ext .js,.ts,.tsx src",
     "start": "node fuse dev"
   },
   "keywords": [],
   "dependencies": {
-    "@joeattardi/emoji-button": "^2.12.1",
     "@types/autosize": "^3.0.6",
-    "@types/js-cookie": "^2.2.6",
+    "@types/js-cookie": "^2.2.1",
     "@types/jwt-decode": "^2.2.1",
-    "@types/markdown-it": "^10.0.0",
+    "@types/markdown-it": "^0.0.7",
     "@types/markdown-it-container": "^2.0.2",
-    "@types/node": "^13.11.1",
     "autosize": "^4.0.2",
     "bootswatch": "^4.3.1",
-    "classcat": "^4.0.2",
-    "dotenv": "^8.2.0",
-    "emoji-short-name": "^1.0.0",
-    "husky": "^4.2.5",
-    "i18next": "^19.4.1",
-    "inferno": "^7.4.2",
+    "classcat": "^1.1.3",
+    "dotenv": "^6.1.0",
+    "emoji-short-name": "^0.1.0",
+    "husky": "^3.0.9",
+    "i18next": "^17.0.9",
+    "inferno": "^7.0.1",
     "inferno-i18next": "nimbusec-oss/inferno-i18next",
-    "inferno-router": "^7.4.2",
+    "inferno-router": "^7.0.1",
     "js-cookie": "^2.2.0",
     "jwt-decode": "^2.2.0",
-    "markdown-it": "^10.0.0",
+    "markdown-it": "^8.4.2",
     "markdown-it-container": "^2.0.0",
     "markdown-it-emoji": "^1.4.0",
-    "mobius1-selectr": "^2.4.13",
     "moment": "^2.24.0",
-    "prettier": "^2.0.4",
-    "reconnecting-websocket": "^4.4.0",
-    "rxjs": "^6.5.5",
-    "terser": "^4.6.11",
-    "tippy.js": "^6.1.1",
-    "toastify-js": "^1.7.0",
-    "tributejs": "^5.1.3",
+    "prettier": "^1.18.2",
+    "rxjs": "^6.4.0",
+    "terser": "^3.17.0",
+    "tributejs": "3.7.2",
     "twemoji": "^12.1.2",
-    "ws": "^7.2.3"
+    "ws": "^7.0.0"
   },
   "devDependencies": {
+    "@types/i18next": "^12.1.0",
     "eslint": "^6.5.1",
     "eslint-plugin-inferno": "^7.14.3",
-    "eslint-plugin-jane": "^7.2.1",
+    "eslint-plugin-jane": "^7.0.0",
     "fuse-box": "^3.1.3",
-    "lint-staged": "^10.1.3",
-    "sortpack": "^2.1.4",
-    "ts-node": "^8.8.2",
-    "ts-transform-classcat": "^1.0.0",
-    "ts-transform-inferno": "^4.0.3",
-    "typescript": "^3.8.3"
+    "lint-staged": "^9.4.2",
+    "sortpack": "^2.0.1",
+    "ts-transform-classcat": "^0.0.2",
+    "ts-transform-inferno": "^4.0.2",
+    "typescript": "^3.5.3"
   },
   "engines": {
     "node": ">=8.9.0"
@@ -66,19 +58,18 @@
   "engineStrict": true,
   "husky": {
     "hooks": {
-      "pre-commit": "cargo clippy --manifest-path ../server/Cargo.toml --all-targets --all-features -- -D warnings && lint-staged"
+      "pre-commit": "lint-staged"
     }
   },
   "lint-staged": {
     "*.{ts,tsx,js}": [
       "prettier --write",
-      "eslint --fix"
-    ],
-    "../server/src/**/*.rs": [
-      "rustfmt --config-path ../server/.rustfmt.toml"
+      "eslint --fix",
+      "git add"
     ],
     "package.json": [
-      "sortpack"
+      "sortpack",
+      "git add"
     ]
   }
 }
diff --git a/ui/src/components/admin-settings.tsx b/ui/src/components/admin-settings.tsx
deleted file mode 100644
index 56af7114..00000000
--- a/ui/src/components/admin-settings.tsx
+++ /dev/null
@@ -1,241 +0,0 @@
-import { Component, linkEvent } from 'inferno';
-import { Subscription } from 'rxjs';
-import { retryWhen, delay, take } from 'rxjs/operators';
-import {
-  UserOperation,
-  SiteResponse,
-  GetSiteResponse,
-  SiteConfigForm,
-  GetSiteConfigResponse,
-  WebSocketJsonResponse,
-} from '../interfaces';
-import { WebSocketService } from '../services';
-import { wsJsonToRes, capitalizeFirstLetter, toast, randomStr } from '../utils';
-import autosize from 'autosize';
-import { SiteForm } from './site-form';
-import { UserListing } from './user-listing';
-import { i18n } from '../i18next';
-
-interface AdminSettingsState {
-  siteRes: GetSiteResponse;
-  siteConfigRes: GetSiteConfigResponse;
-  siteConfigForm: SiteConfigForm;
-  loading: boolean;
-  siteConfigLoading: boolean;
-}
-
-export class AdminSettings extends Component {
-  private siteConfigTextAreaId = `site-config-${randomStr()}`;
-  private subscription: Subscription;
-  private emptyState: AdminSettingsState = {
-    siteRes: {
-      site: {
-        id: null,
-        name: null,
-        creator_id: null,
-        creator_name: null,
-        published: null,
-        number_of_users: null,
-        number_of_posts: null,
-        number_of_comments: null,
-        number_of_communities: null,
-        enable_downvotes: null,
-        open_registration: null,
-        enable_nsfw: null,
-      },
-      admins: [],
-      banned: [],
-      online: null,
-    },
-    siteConfigForm: {
-      config_hjson: null,
-      auth: null,
-    },
-    siteConfigRes: {
-      config_hjson: null,
-    },
-    loading: true,
-    siteConfigLoading: null,
-  };
-
-  constructor(props: any, context: any) {
-    super(props, context);
-
-    this.state = this.emptyState;
-
-    this.subscription = WebSocketService.Instance.subject
-      .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
-      .subscribe(
-        msg => this.parseMessage(msg),
-        err => console.error(err),
-        () => console.log('complete')
-      );
-
-    WebSocketService.Instance.getSite();
-    WebSocketService.Instance.getSiteConfig();
-  }
-
-  componentWillUnmount() {
-    this.subscription.unsubscribe();
-  }
-
-  render() {
-    return (
-      
- {this.state.loading ? ( -
- - - -
- ) : ( -
-
- - {this.admins()} - {this.bannedUsers()} -
-
{this.adminSettings()}
-
- )} -
- ); - } - - admins() { - return ( - <> -
{capitalizeFirstLetter(i18n.t('admins'))}
-
    - {this.state.siteRes.admins.map(admin => ( -
  • - -
  • - ))} -
- - ); - } - - bannedUsers() { - return ( - <> -
{i18n.t('banned_users')}
-
    - {this.state.siteRes.banned.map(banned => ( -
  • - -
  • - ))} -
- - ); - } - - adminSettings() { - return ( -
-
{i18n.t('admin_settings')}
-
-
- -
-