diff --git a/.credo.exs b/.credo.exs index 46d45d015..b85898af3 100644 --- a/.credo.exs +++ b/.credo.exs @@ -25,7 +25,7 @@ # # If you create your own checks, you must specify the source files for # them here, so they can be loaded by Credo before running the analysis. - requires: [], + requires: ["./test/credo/check/consistency/file_location.ex"], # # Credo automatically checks for updates, like e.g. Hex does. # You can disable this behaviour below: @@ -71,7 +71,6 @@ # set this value to 0 (zero). {Credo.Check.Design.TagTODO, exit_status: 0}, {Credo.Check.Design.TagFIXME, exit_status: 0}, - {Credo.Check.Readability.FunctionNames}, {Credo.Check.Readability.LargeNumbers}, {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 100}, @@ -91,7 +90,6 @@ {Credo.Check.Readability.VariableNames}, {Credo.Check.Readability.Semicolons}, {Credo.Check.Readability.SpaceAfterCommas}, - {Credo.Check.Refactor.DoubleBooleanNegation}, {Credo.Check.Refactor.CondStatements}, {Credo.Check.Refactor.CyclomaticComplexity}, @@ -102,7 +100,6 @@ {Credo.Check.Refactor.Nesting}, {Credo.Check.Refactor.PipeChainStart}, {Credo.Check.Refactor.UnlessWithElse}, - {Credo.Check.Warning.BoolOperationOnSameValues}, {Credo.Check.Warning.IExPry}, {Credo.Check.Warning.IoInspect}, @@ -131,6 +128,7 @@ # Custom checks can be created using `mix credo.gen.check`. # + {Credo.Check.Consistency.FileLocation} ] } ] diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dc953a929..e65cae9d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,6 +25,8 @@ before_script: - apt-get update && apt-get install -y cmake - mix local.hex --force - mix local.rebar --force + - apt-get -qq update + - apt-get install -y libmagic-dev build: stage: build @@ -59,7 +61,7 @@ unit-testing: alias: postgres command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] script: - - apt-get update && apt-get install -y libimage-exiftool-perl + - apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg - mix deps.get - mix ecto.create - mix ecto.migrate @@ -93,7 +95,7 @@ unit-testing-rum: <<: *global_variables RUM_ENABLED: "true" script: - - apt-get update && apt-get install -y libimage-exiftool-perl + - apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg - mix deps.get - mix ecto.create - mix ecto.migrate diff --git a/CC-BY-4.0 b/CC-BY-4.0 new file mode 100644 index 000000000..4ea99c213 --- /dev/null +++ b/CC-BY-4.0 @@ -0,0 +1,395 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f85cc302..051050a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,70 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [2.2.0] - 2020-11-12 + +### Security +- Fixed the possibility of using file uploads to spoof posts. + +### Changed + +- **Breaking** Requires `libmagic` (or `file`) to guess file types. +- **Breaking:** App metrics endpoint (`/api/pleroma/app_metrics`) is disabled by default, check `docs/API/prometheus.md` on enabling and configuring. +- **Breaking:** Pleroma Admin API: emoji packs and files routes changed. +- **Breaking:** Sensitive/NSFW statuses no longer disable link previews. +- Search: Users are now findable by their urls. +- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated. +- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated. +- The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false. +- Users with the `discoverable` field set to false will not show up in searches. +- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option). +- Introduced optional dependencies on `ffmpeg`, `ImageMagick`, `exiftool` software packages. Please refer to `docs/installation/optional/media_graphics_packages.md`. +-
+ API Changes +- API: Empty parameter values for integer parameters are now ignored in non-strict validaton mode. +
+ +### Removed + +- **Breaking:** `Pleroma.Workers.Cron.StatsWorker` setting from Oban `:crontab` (moved to a simpler implementation). +- **Breaking:** `Pleroma.Workers.Cron.ClearOauthTokenWorker` setting from Oban `:crontab` (moved to scheduled jobs). +- **Breaking:** `Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker` setting from Oban `:crontab` (moved to scheduled jobs). +- Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were +switched to a new configuration mechanism, however it was not officially removed until now. + +### Added +- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details). +- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`) +- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`) +- Mix task option for force-unfollowing relays +- App metrics: ability to restrict access to specified IP whitelist. +-
+ API Changes + +- Admin API: Importing emoji from a zip file +- Pleroma API: Importing the mutes users from CSV files. +- Pleroma API: Pagination for remote/local packs and emoji. + +
+ + +### Fixed + +- Add documented-but-missing chat pagination. +- Allow sending out emails again. +- Allow sending chat messages to yourself +- OStatus / static FE endpoints: fixed inaccessibility for anonymous users on non-federating instances, switched to handling per `:restrict_unauthenticated` setting. +- Fix remote users with a whitespace name. + +### Upgrade notes + +1. Install libmagic and development headers (`libmagic-dev` on Ubuntu/Debian, `file-dev` on Alpine Linux) +2. Run database migrations (inside Pleroma directory): + - OTP: `./bin/pleroma_ctl migrate` + - From Source: `mix ecto.migrate` +3. Restart Pleroma + + ## [2.1.2] - 2020-09-17 ### Security @@ -40,6 +104,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Rich media failure tracking (along with `:failure_backoff` option). +
+ Admin API Changes + +- Add `PATCH /api/pleroma/admin/instance_document/:document_name` to modify the Terms of Service and Instance Panel HTML pages via Admin API +
+ ### Fixed - Default HTTP adapter not respecting pool setting, leading to possible OOM. - Fixed uploading webp images when the Exiftool Upload Filter is enabled by skipping them diff --git a/Dockerfile b/Dockerfile index aa50e27ec..c210cf79c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ LABEL maintainer="ops@pleroma.social" \ ARG HOME=/opt/pleroma ARG DATA=/var/lib/pleroma -RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ +RUN echo "https://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ apk update &&\ apk add exiftool imagemagick ncurses postgresql-client &&\ adduser --system --shell /bin/false --home ${HOME} pleroma &&\ diff --git a/README.md b/README.md index 6ca3118fb..7a05b9e48 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,16 @@ If you are running Linux (glibc or musl) on x86/arm, the recommended way to inst ### From Source If your platform is not supported, or you just want to be able to edit the source code easily, you may install Pleroma from source. -- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/) -- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/) - [Alpine Linux](https://docs-develop.pleroma.social/backend/installation/alpine_linux_en/) - [Arch Linux](https://docs-develop.pleroma.social/backend/installation/arch_linux_en/) +- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/) +- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/) +- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/) +- [FreeBSD](https://docs-develop.pleroma.social/backend/installation/freebsd_en/) - [Gentoo Linux](https://docs-develop.pleroma.social/backend/installation/gentoo_en/) - [NetBSD](https://docs-develop.pleroma.social/backend/installation/netbsd_en/) - [OpenBSD](https://docs-develop.pleroma.social/backend/installation/openbsd_en/) - [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/) -- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/) ### OS/Distro packages Currently Pleroma is not packaged by any OS/Distros, but if you want to package it for one, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**. diff --git a/SECURITY.md b/SECURITY.md index c212a2505..c009d21d9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ Currently, Pleroma offers bugfixes and security patches only for the latest mino | Version | Support |---------| -------- -| 2.0 | Bugfixes and security patches +| 2.2 | Bugfixes and security patches ## Reporting a vulnerability diff --git a/config/benchmark.exs b/config/benchmark.exs index e867253eb..5567ff26e 100644 --- a/config/benchmark.exs +++ b/config/benchmark.exs @@ -59,8 +59,6 @@ "BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4", private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA" -config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock - config :pleroma, Pleroma.ScheduledActivity, daily_user_limit: 2, total_user_limit: 3, diff --git a/config/config.exs b/config/config.exs index 88f6125e5..99c33010f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -123,13 +123,15 @@ # Configures the endpoint config :pleroma, Pleroma.Web.Endpoint, - instrumenters: [Pleroma.Web.Endpoint.Instrumenter], url: [host: "localhost"], http: [ ip: {127, 0, 0, 1}, dispatch: [ {:_, [ + # FedSockets are commented out of the dispatch table on stable because they can't even + # fail properly when they are disabled. They will hang the connection instead of returning a 404. + # {"/api/fedsocket/v1", Pleroma.Web.FedSockets.IncomingHandler, []}, {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, {"/websocket", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, @@ -142,12 +144,22 @@ secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl", signing_salt: "CqaoopA2", render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)], - pubsub: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2], + pubsub_server: Pleroma.PubSub, secure_cookie_flag: true, extra_cookie_attrs: [ "SameSite=Lax" ] +config :pleroma, :fed_sockets, + enabled: false, + connection_duration: :timer.hours(8), + rejection_duration: :timer.minutes(15), + fed_socket_fetches: [ + default: 12_000, + interval: 3_000, + lazy: false + ] + # Configures Elixir's Logger config :logger, :console, level: :debug, @@ -216,7 +228,6 @@ allow_relay: true, public: true, quarantined_instances: [], - managed_config: true, static_dir: "instance/static/", allowed_post_formats: [ "text/plain", @@ -424,6 +435,8 @@ proxy_opts: [ redirect_on_failure: false, max_body_length: 25 * 1_048_576, + # Note: max_read_duration defaults to Pleroma.ReverseProxy.max_read_duration_default/1 + max_read_duration: 30_000, http: [ follow_redirect: true, pool: :media @@ -438,6 +451,14 @@ config :pleroma, Pleroma.Web.MediaProxy.Invalidation.Script, script_path: nil +# Note: media preview proxy depends on media proxy to be enabled +config :pleroma, :media_preview_proxy, + enabled: false, + thumbnail_max_width: 600, + thumbnail_max_height: 600, + image_quality: 85, + min_content_length: 100 * 1024 + config :pleroma, :chat, enabled: true config :phoenix, :format_encoders, json: Jason @@ -530,8 +551,10 @@ log: false, queues: [ activity_expiration: 10, + token_expiration: 5, federator_incoming: 50, federator_outgoing: 50, + ingestion_queue: 50, web_push: 50, mailer: 10, transmogrifier: 20, @@ -543,9 +566,6 @@ ], plugins: [Oban.Plugins.Pruner], crontab: [ - {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, - {"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, - {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker}, {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} ] @@ -617,7 +637,12 @@ config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: false -config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics" +config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, + enabled: false, + auth: false, + ip_whitelist: [], + path: "/api/pleroma/app_metrics", + format: :text config :pleroma, Pleroma.ScheduledActivity, daily_user_limit: 25, @@ -656,9 +681,20 @@ account_confirmation_resend: {8_640_000, 5}, ap_routes: {60_000, 15} -config :pleroma, Pleroma.ActivityExpiration, enabled: true +config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600 -config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true +config :pleroma, Pleroma.Web.Plugs.RemoteIp, + enabled: true, + headers: ["x-forwarded-for"], + proxies: [], + reserved: [ + "127.0.0.0/8", + "::1/128", + "fc00::/7", + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16" + ] config :pleroma, :static_fe, enabled: false @@ -670,7 +706,7 @@ # With no frontend configuration, the bundled files from the `static` directory will # be used. # -# config :pleroma, :frontends, +# config :pleroma, :frontends, # primary: %{"name" => "pleroma-fe", "ref" => "develop"}, # admin: %{"name" => "admin-fe", "ref" => "stable"}, # available: %{...} @@ -734,28 +770,28 @@ max_connections: 250, max_idle_time: 30_000, retry: 0, - await_up_timeout: 5_000 + connect_timeout: 5_000 config :pleroma, :pools, federation: [ size: 50, max_waiting: 10, - timeout: 10_000 + recv_timeout: 10_000 ], media: [ size: 50, - max_waiting: 10, - timeout: 10_000 + max_waiting: 20, + recv_timeout: 15_000 ], upload: [ size: 25, max_waiting: 5, - timeout: 15_000 + recv_timeout: 15_000 ], default: [ size: 10, max_waiting: 2, - timeout: 5_000 + recv_timeout: 5_000 ] config :pleroma, :hackney_pools, @@ -772,6 +808,8 @@ timeout: 300_000 ] +config :pleroma, :majic_pool, size: 2 + private_instance? = :if_instance_is_private config :pleroma, :restrict_unauthenticated, @@ -790,6 +828,8 @@ config :ex_aws, http_client: Pleroma.HTTP.ExAws +config :web_push_encryption, http_client: Pleroma.HTTP.WebPush + config :pleroma, :instances_favicons, enabled: false config :floki, :html_parser, Floki.HTMLParser.FastHtml diff --git a/config/description.exs b/config/description.exs index 5e08ba109..71b12326f 100644 --- a/config/description.exs +++ b/config/description.exs @@ -44,11 +44,13 @@ }, %{ key: "git", + label: "Git Repository URL", type: :string, description: "URL of the git repository of the frontend" }, %{ key: "build_url", + label: "Build URL", type: :string, description: "Either an url to a zip file containing the frontend or a template to build it by inserting the `ref`. The string `${ref}` will be replaced by the configured `ref`.", @@ -56,6 +58,7 @@ }, %{ key: "build_dir", + label: "Build directory", type: :string, description: "The directory inside the zip file " } @@ -764,12 +767,6 @@ "*.quarantined.com" ] }, - %{ - key: :managed_config, - type: :boolean, - description: - "Whenether the config for pleroma-fe is configured in this config or in static/config.json" - }, %{ key: :static_dir, type: :string, @@ -1880,6 +1877,7 @@ suggestions: [ redirect_on_failure: false, max_body_length: 25 * 1_048_576, + max_read_duration: 30_000, http: [ follow_redirect: true, pool: :media @@ -1900,6 +1898,11 @@ "Limits the content length to be approximately the " <> "specified length. It is validated with the `content-length` header and also verified when proxying." }, + %{ + key: :max_read_duration, + type: :integer, + description: "Timeout (in milliseconds) of GET request to remote URI." + }, %{ key: :http, label: "HTTP", @@ -1946,6 +1949,43 @@ } ] }, + %{ + group: :pleroma, + key: :media_preview_proxy, + type: :group, + description: "Media preview proxy", + children: [ + %{ + key: :enabled, + type: :boolean, + description: + "Enables proxying of remote media preview to the instance's proxy. Requires enabled media proxy." + }, + %{ + key: :thumbnail_max_width, + type: :integer, + description: + "Max width of preview thumbnail for images (video preview always has original dimensions)." + }, + %{ + key: :thumbnail_max_height, + type: :integer, + description: + "Max height of preview thumbnail for images (video preview always has original dimensions)." + }, + %{ + key: :image_quality, + type: :integer, + description: "Quality of the output. Ranges from 0 (min quality) to 100 (max quality)." + }, + %{ + key: :min_content_length, + type: :integer, + description: + "Min content length to perform preview, in bytes. If greater than 0, media smaller in size will be served as is, without thumbnailing." + } + ] + }, %{ group: :pleroma, key: Pleroma.Web.MediaProxy.Invalidation.Http, @@ -2290,9 +2330,6 @@ type: {:list, :tuple}, description: "Settings for cron background jobs", suggestions: [ - {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, - {"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, - {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker}, {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} ] @@ -2398,7 +2435,7 @@ %{ group: :pleroma, key: Pleroma.Formatter, - label: "Auto Linker", + label: "Linkify", type: :group, description: "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.", @@ -2475,14 +2512,20 @@ }, %{ group: :pleroma, - key: Pleroma.ActivityExpiration, + key: Pleroma.Workers.PurgeExpiredActivity, type: :group, - description: "Expired activity settings", + description: "Expired activities settings", children: [ %{ key: :enabled, type: :boolean, - description: "Whether expired activities will be sent to the job queue to be deleted" + description: "Enables expired activities addition & deletion" + }, + %{ + key: :min_lifetime, + type: :integer, + description: "Minimum lifetime for ephemeral activity (in seconds)", + suggestions: [600] } ] }, @@ -3194,10 +3237,10 @@ }, %{ group: :pleroma, - key: Pleroma.Plugs.RemoteIp, + key: Pleroma.Web.Plugs.RemoteIp, type: :group, description: """ - `Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. + `Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. **If your instance is not behind at least one reverse proxy, you should not enable this plug.** """, children: [ @@ -3209,20 +3252,22 @@ %{ key: :headers, type: {:list, :string}, - description: - "A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Default: `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`." + description: """ + A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `["x-forwarded-for"]`. + """ }, %{ key: :proxies, type: {:list, :string}, description: - "A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Default: `[]`." + "A list of upstream proxy IP subnets in CIDR notation from which we will parse the content of `headers`. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128." }, %{ key: :reserved, type: {:list, :string}, - description: - "Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network)." + description: """ + A list of reserved IP subnets in CIDR notation which should be ignored if found in `headers`. Defaults to `["127.0.0.0/8", "::1/128", "fc00::/7", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]` + """ } ] }, @@ -3378,7 +3423,7 @@ suggestions: [250] }, %{ - key: :await_up_timeout, + key: :connect_timeout, type: :integer, description: "Timeout while `gun` will wait until connection is up. Default: 5000ms.", suggestions: [5000] @@ -3416,6 +3461,12 @@ description: "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errrors when a new request is made", suggestions: [10] + }, + %{ + key: :recv_timeout, + type: :integer, + description: "Timeout for the pool while gun will wait for response", + suggestions: [10_000] } ] } @@ -3622,9 +3673,7 @@ type: :map, description: "A map containing available frontends and parameters for their installation.", - children: [ - frontend_options - ] + children: frontend_options } ] }, @@ -3646,5 +3695,56 @@ ] } ] + }, + %{ + group: :pleroma, + key: :majic_pool, + type: :group, + description: "Majic/libmagic configuration", + children: [ + %{ + key: :size, + type: :integer, + description: "Number of majic workers to start.", + suggestions: [2] + } + ] + }, + %{ + group: :prometheus, + key: Pleroma.Web.Endpoint.MetricsExporter, + type: :group, + description: "Prometheus app metrics endpoint configuration", + children: [ + %{ + key: :enabled, + type: :boolean, + description: "[Pleroma extension] Enables app metrics endpoint." + }, + %{ + key: :ip_whitelist, + type: [{:list, :string}, {:list, :charlist}, {:list, :tuple}], + description: + "[Pleroma extension] If non-empty, restricts access to app metrics endpoint to specified IP addresses." + }, + %{ + key: :auth, + type: [:boolean, :tuple], + description: "Enables HTTP Basic Auth for app metrics endpoint.", + suggestion: [false, {:basic, "myusername", "mypassword"}] + }, + %{ + key: :path, + type: :string, + description: "App metrics endpoint URI path.", + suggestions: ["/api/pleroma/app_metrics"] + }, + %{ + key: :format, + type: :atom, + description: "App metrics endpoint output format.", + suggestions: [:text, :protobuf] + } + ] } ] diff --git a/config/test.exs b/config/test.exs index e9c2273e8..7cc660e3c 100644 --- a/config/test.exs +++ b/config/test.exs @@ -19,6 +19,11 @@ level: :warn, format: "\n[$level] $message\n" +config :pleroma, :fed_sockets, + enabled: false, + connection_duration: 5, + rejection_duration: 5 + config :pleroma, :auth, oauth_consumer_strategies: [] config :pleroma, Pleroma.Upload, @@ -78,8 +83,6 @@ "BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4", private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA" -config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock - config :pleroma, Oban, queues: false, crontab: false, @@ -110,12 +113,10 @@ config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true -config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false +config :pleroma, Pleroma.Web.Plugs.RemoteIp, enabled: false config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true -config :pleroma, :instances_favicons, enabled: false - config :pleroma, Pleroma.Uploaders.S3, bucket: nil, streaming_enabled: true, diff --git a/coveralls.json b/coveralls.json index 75e845ade..8652591ef 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,6 +1,7 @@ { "skip_files": [ "test/support", - "lib/mix/tasks/pleroma/benchmark.ex" + "lib/mix/tasks/pleroma/benchmark.ex", + "lib/credo/check/consistency/file_location.ex" ] } \ No newline at end of file diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index c0ea074f0..7bf13daef 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -349,9 +349,9 @@ Response: ### Unfollow a Relay -Params: - -* `relay_url` +- Params: + - `relay_url` + - *optional* `force`: forcefully unfollow a relay even when the relay is not available. (default is `false`) Response: @@ -1334,3 +1334,166 @@ Loads json generated from `config/descriptions.exs`. { } ``` + +## GET /api/pleroma/admin/users/:nickname/chats + +### List a user's chats + +- Params: None + +- Response: + +```json +[ + { + "sender": { + "id": "someflakeid", + "username": "somenick", + ... + }, + "receiver": { + "id": "someflakeid", + "username": "somenick", + ... + }, + "id" : "1", + "unread" : 2, + "last_message" : {...}, // The last message in that chat + "updated_at": "2020-04-21T15:11:46.000Z" + } +] +``` + +## GET /api/pleroma/admin/chats/:chat_id + +### View a single chat + +- Params: None + +- Response: + +```json +{ + "sender": { + "id": "someflakeid", + "username": "somenick", + ... + }, + "receiver": { + "id": "someflakeid", + "username": "somenick", + ... + }, + "id" : "1", + "unread" : 2, + "last_message" : {...}, // The last message in that chat + "updated_at": "2020-04-21T15:11:46.000Z" +} +``` + +## GET /api/pleroma/admin/chats/:chat_id/messages + +### List the messages in a chat + +- Params: `max_id`, `min_id` + +- Response: + +```json +[ + { + "account_id": "someflakeid", + "chat_id": "1", + "content": "Check this out :firefox:", + "created_at": "2020-04-21T15:11:46.000Z", + "emojis": [ + { + "shortcode": "firefox", + "static_url": "https://dontbulling.me/emoji/Firefox.gif", + "url": "https://dontbulling.me/emoji/Firefox.gif", + "visible_in_picker": false + } + ], + "id": "13", + "unread": true + }, + { + "account_id": "someflakeid", + "chat_id": "1", + "content": "Whats' up?", + "created_at": "2020-04-21T15:06:45.000Z", + "emojis": [], + "id": "12", + "unread": false + } +] +``` + +## DELETE /api/pleroma/admin/chats/:chat_id/messages/:message_id + +### Delete a single message + +- Params: None + +- Response: + +```json +{ + "account_id": "someflakeid", + "chat_id": "1", + "content": "Check this out :firefox:", + "created_at": "2020-04-21T15:11:46.000Z", + "emojis": [ + { + "shortcode": "firefox", + "static_url": "https://dontbulling.me/emoji/Firefox.gif", + "url": "https://dontbulling.me/emoji/Firefox.gif", + "visible_in_picker": false + } + ], + "id": "13", + "unread": false +} +``` + +## `GET /api/pleroma/admin/instance_document/:document_name` + +### Get an instance document + +- Authentication: required + +- Response: + +Returns the content of the document + +```html +

Instance panel

+``` + +## `PATCH /api/pleroma/admin/instance_document/:document_name` +- Params: + - `file` (the file to be uploaded, using multipart form data.) + +### Update an instance document + +- Authentication: required + +- Response: + +``` json +{ + "url": "https://example.com/instance/panel.html" +} +``` + +## `DELETE /api/pleroma/admin/instance_document/:document_name` + +### Delete an instance document + +- Response: + +``` json +{ + "url": "https://example.com/instance/panel.html" +} +``` diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 4e97d26c0..3fd141bd2 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -44,6 +44,22 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * Response: HTTP 200 on success, 500 on error * Note: Users that can't be followed are silently skipped. +## `/api/pleroma/blocks_import` +### Imports your blocks. +* Method: `POST` +* Authentication: required +* Params: + * `list`: STRING or FILE containing a whitespace-separated list of accounts to block +* Response: HTTP 200 on success, 500 on error + +## `/api/pleroma/mutes_import` +### Imports your mutes. +* Method: `POST` +* Authentication: required +* Params: + * `list`: STRING or FILE containing a whitespace-separated list of accounts to mute +* Response: HTTP 200 on success, 500 on error + ## `/api/pleroma/captcha` ### Get a new captcha * Method: `GET` @@ -362,44 +378,43 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa * Params: None * Response: JSON, returns a list of Mastodon Conversation entities that were marked as read (200 - healthy, 503 unhealthy). -## `GET /api/pleroma/emoji/packs/import` -### Imports packs from filesystem +## `GET /api/pleroma/emoji/pack?name=:name` + +### Get pack.json for the pack + * Method `GET` -* Authentication: required -* Params: None -* Response: JSON, returns a list of imported packs. - -## `GET /api/pleroma/emoji/packs/remote` -### Make request to another instance for packs list -* Method `GET` -* Authentication: required +* Authentication: not required * Params: - * `url`: url of the instance to get packs from -* Response: JSON with the pack list, hashmap with pack name and pack contents + * `page`: page number for files (default 1) + * `page_size`: page size for files (default 30) +* Response: JSON, pack json with `files`, `files_count` and `pack` keys with 200 status or 404 if the pack does not exist. -## `POST /api/pleroma/emoji/packs/download` -### Download pack from another instance -* Method `POST` -* Authentication: required -* Params: - * `url`: url of the instance to download from - * `name`: pack to download from that instance - * `as`: (*optional*) name how to save pack -* Response: JSON, "ok" with 200 status if the pack was downloaded, or 500 if there were - errors downloading the pack +```json +{ + "files": {...}, + "files_count": 0, // emoji count in pack + "pack": {...} +} +``` + +## `POST /api/pleroma/emoji/pack?name=:name` -## `POST /api/pleroma/emoji/packs/:name` ### Creates an empty pack + * Method `POST` -* Authentication: required -* Params: None +* Authentication: required (admin) +* Params: + * `name`: pack name * Response: JSON, "ok" and 200 status or 409 if the pack with that name already exists -## `PATCH /api/pleroma/emoji/packs/:name` +## `PATCH /api/pleroma/emoji/pack?name=:name` + ### Updates (replaces) pack metadata + * Method `PATCH` -* Authentication: required +* Authentication: required (admin) * Params: + * `name`: pack name * `metadata`: metadata to replace the old one * `license`: Pack license * `homepage`: Pack home page url @@ -410,39 +425,85 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa * Response: JSON, updated "metadata" section of the pack and 200 status or 400 if there was a problem with the new metadata (the error is specified in the "error" part of the response JSON) -## `DELETE /api/pleroma/emoji/packs/:name` +## `DELETE /api/pleroma/emoji/pack?name=:name` + ### Delete a custom emoji pack + * Method `DELETE` -* Authentication: required -* Params: None +* Authentication: required (admin) +* Params: + * `name`: pack name * Response: JSON, "ok" and 200 status or 500 if there was an error deleting the pack -## `POST /api/pleroma/emoji/packs/:name/files` -### Add new file to the pack -* Method `POST` -* Authentication: required +## `GET /api/pleroma/emoji/packs/import` + +### Imports packs from filesystem + +* Method `GET` +* Authentication: required (admin) +* Params: None +* Response: JSON, returns a list of imported packs. + +## `GET /api/pleroma/emoji/packs/remote` + +### Make request to another instance for packs list + +* Method `GET` +* Authentication: required (admin) * Params: + * `url`: url of the instance to get packs from + * `page`: page number for packs (default 1) + * `page_size`: page size for packs (default 50) +* Response: JSON with the pack list, hashmap with pack name and pack contents + +## `POST /api/pleroma/emoji/packs/download` + +### Download pack from another instance + +* Method `POST` +* Authentication: required (admin) +* Params: + * `url`: url of the instance to download from + * `name`: pack to download from that instance + * `as`: (*optional*) name how to save pack +* Response: JSON, "ok" with 200 status if the pack was downloaded, or 500 if there were + errors downloading the pack + +## `POST /api/pleroma/emoji/packs/files?name=:name` + +### Add new file to the pack + +* Method `POST` +* Authentication: required (admin) +* Params: + * `name`: pack name * `file`: file needs to be uploaded with the multipart request or link to remote file. * `shortcode`: (*optional*) shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename. * `filename`: (*optional*) new emoji file name. If not specified will be taken from original filename. * Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. -## `PATCH /api/pleroma/emoji/packs/:name/files` +## `PATCH /api/pleroma/emoji/packs/files?name=:name` + ### Update emoji file from pack + * Method `PATCH` -* Authentication: required +* Authentication: required (admin) * Params: + * `name`: pack name * `shortcode`: emoji file shortcode * `new_shortcode`: new emoji file shortcode * `new_filename`: new filename for emoji file * `force`: (*optional*) with true value to overwrite existing emoji with new shortcode * Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. -## `DELETE /api/pleroma/emoji/packs/:name/files` +## `DELETE /api/pleroma/emoji/packs/files?name=:name` + ### Delete emoji file from pack + * Method `DELETE` -* Authentication: required +* Authentication: required (admin) * Params: + * `name`: pack name * `shortcode`: emoji file shortcode * Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. @@ -467,30 +528,14 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa } ``` -## `GET /api/pleroma/emoji/packs/:name` +## `GET /api/pleroma/emoji/packs/archive?name=:name` -### Get pack.json for the pack +### Requests a local pack archive from the instance * Method `GET` * Authentication: not required * Params: - * `page`: page number for files (default 1) - * `page_size`: page size for files (default 30) -* Response: JSON, pack json with `files`, `files_count` and `pack` keys with 200 status or 404 if the pack does not exist. - -```json -{ - "files": {...}, - "files_count": 0, // emoji count in pack - "pack": {...} -} -``` - -## `GET /api/pleroma/emoji/packs/:name/archive` -### Requests a local pack archive from the instance -* Method `GET` -* Authentication: not required -* Params: None + * `name`: pack name * Response: the archive of the pack with a 200 status code, 403 if the pack is not set as shared, 404 if the pack does not exist diff --git a/docs/API/prometheus.md b/docs/API/prometheus.md index 19c564e3c..a5158d905 100644 --- a/docs/API/prometheus.md +++ b/docs/API/prometheus.md @@ -2,15 +2,37 @@ Pleroma includes support for exporting metrics via the [prometheus_ex](https://github.com/deadtrickster/prometheus.ex) library. +Config example: + +``` +config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, + enabled: true, + auth: {:basic, "myusername", "mypassword"}, + ip_whitelist: ["127.0.0.1"], + path: "/api/pleroma/app_metrics", + format: :text +``` + +* `enabled` (Pleroma extension) enables the endpoint +* `ip_whitelist` (Pleroma extension) could be used to restrict access only to specified IPs +* `auth` sets the authentication (`false` for no auth; configurable to HTTP Basic Auth, see [prometheus-plugs](https://github.com/deadtrickster/prometheus-plugs#exporting) documentation) +* `format` sets the output format (`:text` or `:protobuf`) +* `path` sets the path to app metrics page + + ## `/api/pleroma/app_metrics` + ### Exports Prometheus application metrics + * Method: `GET` -* Authentication: not required +* Authentication: not required by default (see configuration options above) * Params: none -* Response: JSON +* Response: text ## Grafana + ### Config example + The following is a config example to use with [Grafana](https://grafana.com) ``` diff --git a/docs/administration/CLI_tasks/email.md b/docs/administration/CLI_tasks/email.md index 00d2e74f8..d9aa0e71b 100644 --- a/docs/administration/CLI_tasks/email.md +++ b/docs/administration/CLI_tasks/email.md @@ -1,4 +1,4 @@ -# Managing emails +# EMail administration tasks {! backend/administration/CLI_tasks/general_cli_task_info.include !} @@ -30,3 +30,17 @@ Example: ```sh mix pleroma.email test --to root@example.org ``` + +## Send confirmation emails to all unconfirmed user accounts + +=== "OTP" + + ```sh + ./bin/pleroma_ctl email send_confirmation_mails + ``` + +=== "From Source" + + ```sh + mix pleroma.email send_confirmation_mails + ``` diff --git a/docs/administration/CLI_tasks/instance.md b/docs/administration/CLI_tasks/instance.md index 989ecc55d..d6913280a 100644 --- a/docs/administration/CLI_tasks/instance.md +++ b/docs/administration/CLI_tasks/instance.md @@ -37,3 +37,6 @@ If any of the options are left unspecified, you will be prompted interactively. - `--static-dir ` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.) - `--listen-ip ` - the ip the app should listen to, defaults to 127.0.0.1 - `--listen-port ` - the port the app should listen to, defaults to 4000 +- `--strip-uploads ` - use ExifTool to strip uploads of sensitive location data +- `--anonymize-uploads ` - randomize uploaded filenames +- `--dedupe-uploads ` - store files based on their hash to reduce data storage requirements if duplicates are uploaded with different filenames diff --git a/docs/administration/CLI_tasks/user.md b/docs/administration/CLI_tasks/user.md index 3e7f028ba..c64ed4f22 100644 --- a/docs/administration/CLI_tasks/user.md +++ b/docs/administration/CLI_tasks/user.md @@ -224,9 +224,10 @@ ``` ### Options +- `--admin`/`--no-admin` - whether the user should be an admin +- `--confirmed`/`--no-confirmed` - whether the user account is confirmed - `--locked`/`--no-locked` - whether the user should be locked - `--moderator`/`--no-moderator` - whether the user should be a moderator -- `--admin`/`--no-admin` - whether the user should be an admin ## Add tags to a user @@ -271,3 +272,33 @@ ```sh mix pleroma.user toggle_confirmed ``` + +## Set confirmation status for all regular active users +*Admins and moderators are excluded* + +=== "OTP" + + ```sh + ./bin/pleroma_ctl user confirm_all + ``` + +=== "From Source" + + ```sh + mix pleroma.user confirm_all + ``` + +## Revoke confirmation status for all regular active users +*Admins and moderators are excluded* + +=== "OTP" + + ```sh + ./bin/pleroma_ctl user unconfirm_all + ``` + +=== "From Source" + + ```sh + mix pleroma.user unconfirm_all + ``` diff --git a/docs/administration/backup.md b/docs/administration/backup.md index be57bf74a..5f279ab97 100644 --- a/docs/administration/backup.md +++ b/docs/administration/backup.md @@ -5,20 +5,25 @@ 1. Stop the Pleroma service. 2. Go to the working directory of Pleroma (default is `/opt/pleroma`) 3. Run `sudo -Hu postgres pg_dump -d --format=custom -f ` (make sure the postgres user has write access to the destination file) -4. Copy `pleroma.pgdump`, `config/prod.secret.exs` and the `uploads` folder to your backup destination. If you have other modifications, copy those changes too. +4. Copy `pleroma.pgdump`, `config/prod.secret.exs`, `config/setup_db.psql` (if still available) and the `uploads` folder to your backup destination. If you have other modifications, copy those changes too. 5. Restart the Pleroma service. ## Restore/Move -1. Optionally reinstall Pleroma (either on the same server or on another server if you want to move servers). Try to use the same database name. +1. Optionally reinstall Pleroma (either on the same server or on another server if you want to move servers). 2. Stop the Pleroma service. 3. Go to the working directory of Pleroma (default is `/opt/pleroma`) 4. Copy the above mentioned files back to their original position. -5. Drop the existing database and recreate an empty one `sudo -Hu postgres psql -c 'DROP DATABASE ;';` `sudo -Hu postgres psql -c 'CREATE DATABASE ;';` -6. Run `sudo -Hu postgres pg_restore -d -v -1 ` -7. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any. -8. Restart the Pleroma service. -9. Run `sudo -Hu postgres vacuumdb --all --analyze-in-stages`. This will quickly generate the statistics so that postgres can properly plan queries. +5. Drop the existing database and user if restoring in-place. `sudo -Hu postgres psql -c 'DROP DATABASE ;';` `sudo -Hu postgres psql -c 'DROP USER ;'` +6. Restore the database schema and pleroma postgres role the with the original `setup_db.psql` if you have it: `sudo -Hu postgres psql -f config/setup_db.psql`. + + Alternatively, run the `mix pleroma.instance gen` task again. You can ignore most of the questions, but make the database user, name, and password the same as found in your backup of `config/prod.secret.exs`. Then run the restoration of the pleroma role and schema with of the generated `config/setup_db.psql` as instructed above. You may delete the `config/generated_config.exs` file as it is not needed. + +7. Now restore the Pleroma instance's data into the empty database schema: `sudo -Hu postgres pg_restore -d -v -1 ` +8. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any. +9. Restart the Pleroma service. +10. Run `sudo -Hu postgres vacuumdb --all --analyze-in-stages`. This will quickly generate the statistics so that postgres can properly plan queries. +11. If setting up on a new server configure Nginx by using the `installation/pleroma.nginx` config sample or reference the Pleroma installation guide for your OS which contains the Nginx configuration instructions. [^1]: Prefix with `MIX_ENV=prod` to run it using the production config file. @@ -31,6 +36,6 @@ 3. Disable pleroma from systemd `systemctl disable pleroma` 4. Remove the files and folders you created during installation (see installation guide). This includes the pleroma, nginx and systemd files and folders. 5. Reload nginx now that the configuration is removed `systemctl reload nginx` -6. Remove the database and database user `sudo -Hu postgres psql -c 'DROP DATABASE ;';` `sudo -Hu postgres psql -c 'DROP USER ;';` +6. Remove the database and database user `sudo -Hu postgres psql -c 'DROP DATABASE ;';` `sudo -Hu postgres psql -c 'DROP USER ;'` 7. Remove the system user `userdel pleroma` 8. Remove the dependencies that you don't need anymore (see installation guide). Make sure you don't remove packages that are still needed for other software that you have running! diff --git a/docs/administration/updating.md b/docs/administration/updating.md index c994f3f16..ef2c9218c 100644 --- a/docs/administration/updating.md +++ b/docs/administration/updating.md @@ -18,9 +18,10 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" 1. Go to the working directory of Pleroma (default is `/opt/pleroma`) 2. Run `git pull`. This pulls the latest changes from upstream. -3. Run `mix deps.get`. This pulls in any new dependencies. +3. Run `mix deps.get` [^1]. This pulls in any new dependencies. 4. Stop the Pleroma service. -5. Run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any. +5. Run `mix ecto.migrate` [^1] [^2]. This task performs database migrations, if there were any. 6. Start the Pleroma service. -[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file. +[^1]: Depending on which install guide you followed (for example on Debian/Ubuntu), you want to run `mix` tasks as `pleroma` user by adding `sudo -Hu pleroma` before the command. +[^2]: Prefix with `MIX_ENV=prod` to run it using the production config file. diff --git a/docs/clients.md b/docs/clients.md index f84295b1f..1e2c14f1b 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -71,7 +71,7 @@ Feel free to contact us to be added to this list! ### Indigenous - Homepage: - Source Code: -- Contact: [@realize.be@realize.be](@realize.be@realize.be) +- Contact: [@swentel@realize.be](https://realize.be) - Platforms: Android - Features: No Streaming diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index b2980793d..2c41ee932 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -18,7 +18,7 @@ To add configuration to your config file, you can copy it from the base config. * `notify_email`: Email used for notifications. * `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``. * `limit`: Posts character limit (CW/Subject included in the counter). -* `discription_limit`: The character limit for image descriptions. +* `description_limit`: The character limit for image descriptions. * `chat_limit`: Character limit of the instance chat messages. * `remote_limit`: Hard character limit beyond which remote posts will be dropped. * `upload_limit`: File size limit of uploads (except for avatar, background, banner). @@ -40,7 +40,6 @@ To add configuration to your config file, you can copy it from the base config. * `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance. * `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details. * `quarantined_instances`: List of ActivityPub instances where private (DMs, followers-only) activities will not be send. -* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``. * `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML). * `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with older software for theses nicknames. @@ -114,7 +113,8 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)). * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)). * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). - * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.ActivityExpiration` to be enabled for processing the scheduled delections. + * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections. + * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. @@ -219,12 +219,6 @@ config :pleroma, :mrf_user_allowlist, %{ * `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`) * `enabled`: whether scheduled activities are sent to the job queue to be executed -## Pleroma.ActivityExpiration - -Enables the worker which processes posts scheduled for deletion. Pinned posts are exempt from expiration. - -* `enabled`: whether expired activities will be sent to the job queue to be deleted - ## Frontends ### :frontend_configurations @@ -314,6 +308,14 @@ This section describe PWA manifest instance-specific values. Currently this opti * `enabled`: Enables purge cache * `provider`: Which one of the [purge cache strategy](#purge-cache-strategy) to use. +## :media_preview_proxy + +* `enabled`: Enables proxying of remote media preview to the instance’s proxy. Requires enabled media proxy (`media_proxy/enabled`). +* `thumbnail_max_width`: Max width of preview thumbnail for images (video preview always has original dimensions). +* `thumbnail_max_height`: Max height of preview thumbnail for images (video preview always has original dimensions). +* `image_quality`: Quality of the output. Ranges from 0 (min quality) to 100 (max quality). +* `min_content_length`: Min content length to perform preview, in bytes. If greater than 0, media smaller in size will be served as is, without thumbnailing. + ### Purge cache strategy #### Pleroma.Web.MediaProxy.Invalidation.Script @@ -398,25 +400,25 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start * ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`. * ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header. -### Pleroma.Plugs.RemoteIp +### Pleroma.Web.Plugs.RemoteIp !!! warning If your instance is not behind at least one reverse proxy, you should not enable this plug. -`Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. +`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. Available options: * `enabled` - Enable/disable the plug. Defaults to `false`. -* `headers` - A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `["x-forwarded-for"]`. -* `proxies` - A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`. -* `reserved` - Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network). +* `headers` - A list of strings naming the HTTP headers to use when deriving the true client IP address. Defaults to `["x-forwarded-for"]`. +* `proxies` - A list of upstream proxy IP subnets in CIDR notation from which we will parse the content of `headers`. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128. +* `reserved` - A list of reserved IP subnets in CIDR notation which should be ignored if found in `headers`. Defaults to `["127.0.0.0/8", "::1/128", "fc00::/7", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]`. ### :rate_limit !!! note - If your instance is behind a reverse proxy ensure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default). + If your instance is behind a reverse proxy ensure [`Pleroma.Web.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default). A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where: @@ -496,7 +498,7 @@ Settings for HTTP connection pool. * `:connection_acquisition_wait` - Timeout to acquire a connection from pool.The total max time is this value multiplied by the number of retries. * `connection_acquisition_retries` - Number of attempts to acquire the connection from the pool if it is overloaded. Each attempt is timed `:connection_acquisition_wait` apart. * `:max_connections` - Maximum number of connections in the pool. -* `:await_up_timeout` - Timeout to connect to the host. +* `:connect_timeout` - Timeout to connect to the host. * `:reclaim_multiplier` - Multiplied by `:max_connections` this will be the maximum number of idle connections that will be reclaimed in case the pool is overloaded. ### :pools @@ -515,7 +517,7 @@ There are four pools used: For each pool, the options are: * `:size` - limit to how much requests can be concurrently executed. -* `:timeout` - timeout while `gun` will wait for response +* `:recv_timeout` - timeout while `gun` will wait for response * `:max_waiting` - limit to how much requests can be waiting for others to finish, after this is reached, subsequent requests will be dropped. ## Captcha @@ -690,9 +692,8 @@ Pleroma has the following queues: Pleroma has these periodic job workers: -`Pleroma.Workers.Cron.ClearOauthTokenWorker` - a job worker to cleanup expired oauth tokens. - -Example: +* `Pleroma.Workers.Cron.DigestEmailsWorker` - digest emails for users with new mentions and follows +* `Pleroma.Workers.Cron.NewUsersDigestWorker` - digest emails for admins with new registrations ```elixir config :pleroma, Oban, @@ -704,7 +705,8 @@ config :pleroma, Oban, federator_outgoing: 50 ], crontab: [ - {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker} + {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, + {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} ] ``` @@ -971,7 +973,7 @@ Configure OAuth 2 provider capabilities: * `token_expires_in` - The lifetime in seconds of the access token. * `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token. -* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`. Interval settings sets in configuration periodic jobs [`Oban.Cron`](#obancron) +* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`. ## Link parsing @@ -1090,3 +1092,10 @@ config :pleroma, :frontends, ``` This would serve the frontend from the the folder at `$instance_static/frontends/pleroma/stable`. You have to copy the frontend into this folder yourself. You can choose the name and ref any way you like, but they will be used by mix tasks to automate installation in the future, the name referring to the project and the ref referring to a commit. + +## Ephemeral activities (Pleroma.Workers.PurgeExpiredActivity) + +Settings to enable and configure expiration for ephemeral activities + +* `:enabled` - enables ephemeral activities creation +* `:min_lifetime` - minimum lifetime for ephemeral activities (in seconds). Default: 10 minutes. diff --git a/docs/dev.md b/docs/dev.md index 9c749c17c..22e0691f1 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -6,7 +6,7 @@ This document contains notes and guidelines for Pleroma developers. * Pleroma supports hierarchical OAuth scopes, just like Mastodon but with added granularity of admin scopes. For a reference, see [Mastodon OAuth scopes](https://docs.joinmastodon.org/api/oauth-scopes/). -* It is important to either define OAuth scope restrictions or explicitly mark OAuth scope check as skipped, for every controller action. To define scopes, call `plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: [...]})`. To explicitly set OAuth scopes check skipped, call `plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug )`. +* It is important to either define OAuth scope restrictions or explicitly mark OAuth scope check as skipped, for every controller action. To define scopes, call `plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: [...]})`. To explicitly set OAuth scopes check skipped, call `plug(:skip_plug, Pleroma.Web.Plugs.OAuthScopesPlug )`. * In controllers, `use Pleroma.Web, :controller` will result in `action/2` (see `Pleroma.Web.controller/0` for definition) be called prior to actual controller action, and it'll perform security / privacy checks before passing control to actual controller action. @@ -16,7 +16,7 @@ This document contains notes and guidelines for Pleroma developers. ## [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) -* With HTTP Basic Auth, OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways). `Pleroma.Plugs.AuthenticationPlug` and `Pleroma.Plugs.LegacyAuthenticationPlug` both call `Pleroma.Plugs.OAuthScopesPlug.skip_plug(conn)` when password is provided. +* With HTTP Basic Auth, OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways). `Pleroma.Web.Plugs.AuthenticationPlug` and `Pleroma.Web.Plugs.LegacyAuthenticationPlug` both call `Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug(conn)` when password is provided. ## Auth-related configuration, OAuth consumer mode etc. diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md index a5683f18c..62f2fb778 100644 --- a/docs/installation/alpine_linux_en.md +++ b/docs/installation/alpine_linux_en.md @@ -13,6 +13,7 @@ It assumes that you have administrative rights, either as root or a user with [s * `erlang-parsetools` * `erlang-xmerl` * `git` +* `file-dev` * Development Tools * `cmake` @@ -20,6 +21,9 @@ It assumes that you have administrative rights, either as root or a user with [s * `nginx` (preferred, example configs for other reverse proxies can be found in the repo) * `certbot` (or any other ACME client for Let’s Encrypt certificates) +* `ImageMagick` +* `ffmpeg` +* `exiftool` ### Prepare the system @@ -29,7 +33,6 @@ It assumes that you have administrative rights, either as root or a user with [s awk 'NR==2' /etc/apk/repositories | sed 's/main/community/' | tee -a /etc/apk/repositories ``` - * Then update the system, if not already done: ```shell @@ -40,7 +43,7 @@ sudo apk upgrade * Install some tools, which are needed later: ```shell -sudo apk add git build-base cmake +sudo apk add git build-base cmake file-dev ``` ### Install Elixir and Erlang @@ -56,6 +59,7 @@ sudo apk add erlang erlang-runtime-tools erlang-xmerl elixir ```shell sudo apk add erlang-eldap ``` + ### Install PostgreSQL * Install Postgresql server: @@ -76,6 +80,12 @@ sudo /etc/init.d/postgresql start sudo rc-update add postgresql ``` +### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md)) + +```shell +sudo apk add ffmpeg imagemagick exiftool +``` + ### Install PleromaBE * Add a new system user for the Pleroma service: diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md index 7fb69dd60..0eb6d2d5f 100644 --- a/docs/installation/arch_linux_en.md +++ b/docs/installation/arch_linux_en.md @@ -10,11 +10,15 @@ This guide will assume that you have administrative rights, either as root or a * `git` * `base-devel` * `cmake` +* `file` #### Optional packages used in this guide * `nginx` (preferred, example configs for other reverse proxies can be found in the repo) * `certbot` (or any other ACME client for Let’s Encrypt certificates) +* `ImageMagick` +* `ffmpeg` +* `exiftool` ### Prepare the system @@ -27,7 +31,7 @@ sudo pacman -Syu * Install some of the above mentioned programs: ```shell -sudo pacman -S git base-devel elixir cmake +sudo pacman -S git base-devel elixir cmake file ``` ### Install PostgreSQL @@ -52,6 +56,12 @@ sudo -iu postgres initdb -D /var/lib/postgres/data sudo systemctl enable --now postgresql.service ``` +### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md)) + +```shell +sudo pacman -S ffmpeg imagemagick perl-image-exiftool +``` + ### Install PleromaBE * Add a new system user for the Pleroma service: diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md index 60c2f47e5..6a9026d94 100644 --- a/docs/installation/debian_based_en.md +++ b/docs/installation/debian_based_en.md @@ -10,6 +10,7 @@ This guide will assume you are on Debian Stretch. This guide should also work wi * `elixir` (1.8+, Follow the guide to install from the Erlang Solutions repo or use [asdf](https://github.com/asdf-vm/asdf) as the pleroma user) * `erlang-dev` * `erlang-nox` +* `libmagic-dev` * `git` * `build-essential` * `cmake` @@ -18,6 +19,9 @@ This guide will assume you are on Debian Stretch. This guide should also work wi * `nginx` (preferred, example configs for other reverse proxies can be found in the repo) * `certbot` (or any other ACME client for Let’s Encrypt certificates) +* `ImageMagick` +* `ffmpeg` +* `exiftool` ### Prepare the system @@ -31,7 +35,7 @@ sudo apt full-upgrade * Install some of the above mentioned programs: ```shell -sudo apt install git build-essential postgresql postgresql-contrib cmake +sudo apt install git build-essential postgresql postgresql-contrib cmake libmagic-devel ``` ### Install Elixir and Erlang @@ -50,6 +54,12 @@ sudo apt update sudo apt install elixir erlang-dev erlang-nox ``` +### Optional packages: [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md) + +```shell +sudo apt install imagemagick ffmpeg libimage-exiftool-perl +``` + ### Install PleromaBE * Add a new system user for the Pleroma service: diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md index c2dd840d3..94e22325c 100644 --- a/docs/installation/debian_based_jp.md +++ b/docs/installation/debian_based_jp.md @@ -17,11 +17,15 @@ - `git` - `build-essential` - `cmake` +- `libmagic-dev` #### このガイドで利用している追加パッケージ - `nginx` (おすすめです。他のリバースプロキシを使う場合は、参考となる設定をこのリポジトリから探してください) - `certbot` (または何らかのLet's Encrypt向けACMEクライアント) +- `ImageMagick` +- `ffmpeg` +- `exiftool` ### システムを準備する @@ -33,10 +37,9 @@ sudo apt full-upgrade * 上記に挙げたパッケージをインストールしておきます。 ``` -sudo apt install git build-essential postgresql postgresql-contrib cmake +sudo apt install git build-essential postgresql postgresql-contrib cmake ffmpeg imagemagick libmagic-dev ``` - ### ElixirとErlangをインストールします * Erlangのリポジトリをダウンロードおよびインストールします。 @@ -51,6 +54,12 @@ sudo apt update sudo apt install elixir erlang-dev erlang-nox ``` +### オプションパッケージ: [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md) + +```shell +sudo apt install imagemagick ffmpeg libimage-exiftool-perl +``` + ### Pleroma BE (バックエンド) をインストールします * Pleroma用に新しいユーザーを作ります。 diff --git a/docs/installation/freebsd_en.md b/docs/installation/freebsd_en.md index ca2575d9b..fdcb06c53 100644 --- a/docs/installation/freebsd_en.md +++ b/docs/installation/freebsd_en.md @@ -26,6 +26,12 @@ Setup the required services to automatically start at boot, using `sysrc(8)`. # service postgresql start ``` +### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md)) + +```shell +# pkg install imagemagick ffmpeg p5-Image-ExifTool +``` + ## Configuring Pleroma Create a user for Pleroma: diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md index 5a676380c..f2380ab72 100644 --- a/docs/installation/gentoo_en.md +++ b/docs/installation/gentoo_en.md @@ -29,12 +29,16 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i * `dev-lang/elixir` * `dev-vcs/git` * `dev-util/cmake` +* `sys-apps/file` #### Optional ebuilds used in this guide * `www-servers/nginx` (preferred, example configs for other reverse proxies can be found in the repo) * `app-crypt/certbot` (or any other ACME client for Let’s Encrypt certificates) * `app-crypt/certbot-nginx` (nginx certbot plugin that allows use of the all-powerful `--nginx` flag on certbot) +* `media-gfx/imagemagick` +* `media-video/ffmpeg` +* `media-libs/exiftool` ### Prepare the system @@ -47,7 +51,7 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i * Emerge all required the required and suggested software in one go: ```shell - # emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake + # emerge --ask dev-db/postgresql dev-lang/elixir dev-vcs/git www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx dev-util/cmake sys-apps/file ``` If you would not like to install the optional packages, remove them from this line. @@ -87,6 +91,12 @@ If you do not plan to make any modifications to your Pleroma instance, cloning d Not only does this make it much easier to deploy changes you make, as you can commit and pull from upstream and all that good stuff from the comfort of your local machine then simply `git pull` on your instance server when you're ready to deploy, it also ensures you are compliant with the Affero General Public Licence that Pleroma is licenced under, which stipulates that all network services provided with modified AGPL code must publish their changes on a publicly available internet service and for free. It also makes it much easier to ask for help from and provide help to your fellow Pleroma admins if your public repo always reflects what you are running because it is part of your deployment procedure. +### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md)) + +```shell +# emerge --ask media-video/ffmpeg media-gfx/imagemagick media-libs/exiftool +``` + ### Install PleromaBE * Add a new system user for the Pleroma service and set up default directories: diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md index 6ad0de2f6..d5fa04fdf 100644 --- a/docs/installation/netbsd_en.md +++ b/docs/installation/netbsd_en.md @@ -10,7 +10,7 @@ Pleroma uses. The `mksh` shell is needed to run the Elixir `mix` script. -`# pkgin install acmesh elixir git-base git-docs mksh nginx postgresql11-server postgresql11-client postgresql11-contrib sudo` +`# pkgin install acmesh elixir git-base git-docs mksh nginx postgresql11-server postgresql11-client postgresql11-contrib sudo ffmpeg4 ImageMagick` You can also build these packages using pkgsrc: ``` @@ -44,6 +44,10 @@ pgsql=YES First, run `# /etc/rc.d/pgsql start`. Then, `$ sudo -Hu pgsql -g pgsql createdb`. +### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md)) + +`# pkgin install ImageMagick ffmpeg4 p5-Image-ExifTool` + ## Configuring Pleroma Create a user for Pleroma: diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md index eee452845..8092ac379 100644 --- a/docs/installation/openbsd_en.md +++ b/docs/installation/openbsd_en.md @@ -10,20 +10,34 @@ The following packages need to be installed: * elixir * gmake - * ImageMagick * git * postgresql-server * postgresql-contrib * cmake + * ffmpeg + * ImageMagick To install them, run the following command (with doas or as root): ``` -pkg_add elixir gmake ImageMagick git postgresql-server postgresql-contrib cmake +pkg_add elixir gmake git postgresql-server postgresql-contrib cmake ffmpeg ImageMagick ``` Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt. +#### Optional software + +Per [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md): + * ImageMagick + * ffmpeg + * exiftool + +To install the above: + +``` +pkg_add ImageMagick ffmpeg p5-Image-ExifTool +``` + #### Creating the pleroma user Pleroma will be run by a dedicated user, \_pleroma. Before creating it, insert the following lines in login.conf: ``` diff --git a/docs/installation/openbsd_fi.md b/docs/installation/openbsd_fi.md index b5b5056a9..01cf34ab4 100644 --- a/docs/installation/openbsd_fi.md +++ b/docs/installation/openbsd_fi.md @@ -16,7 +16,18 @@ Matrix-kanava #freenode_#pleroma:matrix.org ovat hyviä paikkoja löytää apua Asenna tarvittava ohjelmisto: -`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3 cmake` +`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3 cmake ffmpeg ImageMagick` + +#### Optional software + +[`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md): + * ImageMagick + * ffmpeg + * exiftool + +Asenna tarvittava ohjelmisto: + +`# pkg_add ImageMagick ffmpeg p5-Image-ExifTool` Luo postgresql-tietokanta: diff --git a/docs/installation/optional/media_graphics_packages.md b/docs/installation/optional/media_graphics_packages.md new file mode 100644 index 000000000..cb3d71188 --- /dev/null +++ b/docs/installation/optional/media_graphics_packages.md @@ -0,0 +1,32 @@ +# Optional software packages needed for specific functionality + +For specific Pleroma functionality (which is disabled by default) some or all of the below packages are required: + * `ImageMagic` + * `ffmpeg` + * `exiftool` + +Please refer to documentation in `docs/installation` on how to install them on specific OS. + +Note: the packages are not required with the current default settings of Pleroma. + +## `ImageMagick` + +`ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images. + +It is required for the following Pleroma features: + * `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`) + * Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`) + +## `ffmpeg` + +`ffmpeg` is software to record, convert and stream audio and video. + +It is required for the following Pleroma features: + * Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`) + +## `exiftool` + +`exiftool` is media files metadata reader/writer. + +It is required for the following Pleroma features: + * `Pleroma.Upload.Filters.Exiftool` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`) diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index b7e3bb2ac..62d4c8a72 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -27,17 +27,37 @@ Other than things bundled in the OTP release Pleroma depends on: * PostgreSQL (also utilizes extensions in postgresql-contrib) * nginx (could be swapped with another reverse proxy but this guide covers only it) * certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it) +* libmagic/file === "Alpine" ``` echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories apk update - apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot + apk add curl unzip ncurses postgresql postgresql-contrib nginx certbot file-dev ``` === "Debian/Ubuntu" ``` - apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot + apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot libmagic-dev + ``` + +### Installing optional packages + +Per [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md): + * ImageMagick + * ffmpeg + * exiftool + +=== "Alpine" + ``` + echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories + apk update + apk add imagemagick ffmpeg exiftool + ``` + +=== "Debian/Ubuntu" + ``` + apt install imagemagick ffmpeg libimage-exiftool-perl ``` ## Setup @@ -82,6 +102,8 @@ It is encouraged to check [Optimizing your PostgreSQL performance](../configurat If you are using PostgreSQL 12 or higher, add this to your Ecto database configuration ```elixir +# +config :pleroma, Pleroma.Repo, prepare: :named, parameters: [ plan_cache_mode: "force_custom_plan" diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index d301ca615..d613befd2 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -9,6 +9,12 @@ proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cache:10m max_size=10g inactive=720m use_temp_path=off; +# this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only +# and `localhost.` resolves to [::0] on some systems: see issue #930 +upstream phoenix { + server 127.0.0.1:4000 max_fails=5 fail_timeout=60s; +} + server { server_name example.tld; @@ -63,19 +69,16 @@ server { # the nginx default is 1m, not enough for large media uploads client_max_body_size 16m; + ignore_invalid_headers off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only - # and `localhost.` resolves to [::0] on some systems: see issue #930 - proxy_pass http://127.0.0.1:4000; - - client_max_body_size 16m; + proxy_pass http://phoenix; } location ~ ^/(media|proxy) { @@ -83,12 +86,16 @@ server { slice 1m; proxy_cache_key $host$uri$is_args$args$slice_range; proxy_set_header Range $slice_range; - proxy_http_version 1.1; proxy_cache_valid 200 206 301 304 1h; proxy_cache_lock on; proxy_ignore_client_abort on; proxy_buffering on; chunked_transfer_encoding on; - proxy_pass http://127.0.0.1:4000; + proxy_pass http://phoenix; + } + + location /api/fedsocket/v1 { + proxy_request_buffering off; + proxy_pass http://phoenix/api/fedsocket/v1; } } diff --git a/installation/pleroma.vcl b/installation/pleroma.vcl index 154747aa6..13dad784c 100644 --- a/installation/pleroma.vcl +++ b/installation/pleroma.vcl @@ -1,3 +1,4 @@ +# Recommended varnishncsa logging format: '%h %l %u %t "%m %{X-Forwarded-Proto}i://%{Host}i%U%q %H" %s %b "%{Referer}i" "%{User-agent}i"' vcl 4.1; import std; @@ -14,8 +15,11 @@ acl purge { sub vcl_recv { # Redirect HTTP to HTTPS if (std.port(server.ip) != 443) { + set req.http.X-Forwarded-Proto = "http"; set req.http.x-redir = "https://" + req.http.host + req.url; return (synth(750, "")); + } else { + set req.http.X-Forwarded-Proto = "https"; } # CHUNKED SUPPORT @@ -105,7 +109,7 @@ sub vcl_hash { sub vcl_backend_fetch { # Be more lenient for slow servers on the fediverse - if bereq.url ~ "^/proxy/" { + if (bereq.url ~ "^/proxy/") { set bereq.first_byte_timeout = 300s; } diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index fe9b0d16c..49ba2aae4 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -18,6 +18,7 @@ defmodule Mix.Pleroma do @doc "Common functions to be reused in mix tasks" def start_pleroma do Pleroma.Config.Holder.save_default() + Pleroma.Config.Oban.warn() Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) if Pleroma.Config.get(:env) != :test do diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index dd2b9c8f2..a607d5d4f 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -91,20 +91,17 @@ def run(["adapters"]) do "Without conn and without pool" => fn -> {:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [pool: :no_pool, receive_conn: false] + pool: :no_pool, + receive_conn: false ) end, "Without conn and with pool" => fn -> {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [receive_conn: false] - ) + Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], receive_conn: false) end, "With reused conn and without pool" => fn -> {:ok, %Tesla.Env{}} = - Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], - adapter: [pool: :no_pool] - ) + Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500", [], pool: :no_pool) end, "With reused conn and with pool" => fn -> {:ok, %Tesla.Env{}} = Pleroma.HTTP.get("https://httpbin.org/stream-bytes/1500") diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 904c5a74b..18f99318d 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -32,7 +32,8 @@ def run(["migrate_from_db" | options]) do @spec migrate_to_db(Path.t() | nil) :: any() def migrate_to_db(file_path \\ nil) do - if Pleroma.Config.get([:configurable_from_database]) do + with true <- Pleroma.Config.get([:configurable_from_database]), + :ok <- Pleroma.Config.DeprecationWarnings.warn() do config_file = if file_path do file_path @@ -46,7 +47,8 @@ def migrate_to_db(file_path \\ nil) do do_migrate_to_db(config_file) else - migration_error() + :error -> deprecation_error() + _ -> migration_error() end end @@ -120,6 +122,10 @@ defp migration_error do ) end + defp deprecation_error do + shell_error("Migration is not allowed until all deprecation warnings have been resolved.") + end + if Code.ensure_loaded?(Config.Reader) do defp config_header, do: "import Config\r\n\r\n" defp read_file(config_file), do: Config.Reader.read_imports!(config_file) diff --git a/lib/mix/tasks/pleroma/count_statuses.ex b/lib/mix/tasks/pleroma/count_statuses.ex index e1e8195dd..8761d8f17 100644 --- a/lib/mix/tasks/pleroma/count_statuses.ex +++ b/lib/mix/tasks/pleroma/count_statuses.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.CountStatuses do @shortdoc "Re-counts statuses for all users" diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 7d8f00b08..a01c36ece 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -99,7 +99,7 @@ def run(["fix_likes_collections"]) do where: fragment("(?)->>'likes' is not null", object.data), select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)} ) - |> Pleroma.RepoStreamer.chunk_stream(100) + |> Pleroma.Repo.chunk_stream(100, :batches) |> Stream.each(fn objects -> ids = objects @@ -133,8 +133,7 @@ def run(["ensure_expiration"]) do days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365) Pleroma.Activity - |> join(:left, [a], u in assoc(a, :expiration)) - |> join(:inner, [a, _u], o in Object, + |> join(:inner, [a], o in Object, on: fragment( "(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')", @@ -144,14 +143,20 @@ def run(["ensure_expiration"]) do ) ) |> where(local: true) - |> where([a, u], is_nil(u)) |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data)) - |> where([_a, _u, o], fragment("?->>'type' = 'Note'", o.data)) - |> Pleroma.RepoStreamer.chunk_stream(100) + |> where([_a, o], fragment("?->>'type' = 'Note'", o.data)) + |> Pleroma.Repo.chunk_stream(100, :batches) |> Stream.each(fn activities -> Enum.each(activities, fn activity -> - expires_at = Timex.shift(activity.inserted_at, days: days) - Pleroma.ActivityExpiration.create(activity, expires_at, false) + expires_at = + activity.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: days) + + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: expires_at + }) end) end) |> Stream.run() diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex index 3595f912d..cac148b88 100644 --- a/lib/mix/tasks/pleroma/digest.ex +++ b/lib/mix/tasks/pleroma/digest.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.Digest do use Mix.Task import Mix.Pleroma diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index 6088fc71d..ad5c37fc9 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.Docs do use Mix.Task import Mix.Pleroma diff --git a/lib/mix/tasks/pleroma/ecto/ecto.ex b/lib/mix/tasks/pleroma/ecto.ex similarity index 100% rename from lib/mix/tasks/pleroma/ecto/ecto.ex rename to lib/mix/tasks/pleroma/ecto.ex diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex index d3fac6ec8..bc5facc09 100644 --- a/lib/mix/tasks/pleroma/email.ex +++ b/lib/mix/tasks/pleroma/email.ex @@ -1,12 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.Email do use Mix.Task import Mix.Pleroma - @shortdoc "Simple Email test" + @shortdoc "Email administrative tasks" @moduledoc File.read!("docs/administration/CLI_tasks/email.md") def run(["test" | args]) do - Mix.Pleroma.start_pleroma() + start_pleroma() {options, [], []} = OptionParser.parse( @@ -21,4 +25,20 @@ def run(["test" | args]) do shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}") end + + def run(["resend_confirmation_emails"]) do + start_pleroma() + + shell_info("Sending emails to all unconfirmed users") + + Pleroma.User.Query.build(%{ + local: true, + deactivated: false, + confirmation_pending: true, + invisible: false + }) + |> Pleroma.Repo.chunk_stream(500) + |> Stream.each(&Pleroma.User.try_send_confirmation_email(&1)) + |> Stream.run() + end end diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 8f52ee98d..1750373f9 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -183,7 +183,7 @@ def run(["gen-pack" | args]) do IO.puts("Downloading the pack and generating SHA256") - binary_archive = Tesla.get!(client(), src).body + {:ok, %{body: binary_archive}} = Pleroma.HTTP.get(src) archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16() IO.puts("SHA256 is #{archive_sha}") @@ -252,7 +252,7 @@ defp fetch_and_decode!(from) do end defp fetch("http" <> _ = from) do - with {:ok, %{body: body}} <- Tesla.get(client(), from) do + with {:ok, %{body: body}} <- Pleroma.HTTP.get(from) do {:ok, body} end end @@ -271,13 +271,5 @@ defp parse_global_opts(args) do ) end - defp client do - middleware = [ - {Tesla.Middleware.FollowRedirects, [max_redirects: 3]} - ] - - Tesla.client(middleware) - end - defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest]) end diff --git a/lib/mix/tasks/pleroma/frontend.ex b/lib/mix/tasks/pleroma/frontend.ex index 484af6da7..cbce81ab9 100644 --- a/lib/mix/tasks/pleroma/frontend.ex +++ b/lib/mix/tasks/pleroma/frontend.ex @@ -69,7 +69,7 @@ def run(["install", frontend | args]) do fe_label = "#{frontend} (#{ref})" - tmp_dir = Path.join(dest, "tmp") + tmp_dir = Path.join([instance_static_dir, "frontends", "tmp"]) with {_, :ok} <- {:download_or_unzip, download_or_unzip(frontend_info, tmp_dir, options[:file])}, @@ -124,9 +124,7 @@ defp download_build(frontend_info, dest) do url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"]) with {:ok, %{status: 200, body: zip_body}} <- - Pleroma.HTTP.get(url, [], - adapter: [pool: :media, timeout: 120_000, recv_timeout: 120_000] - ) do + Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do unzip(zip_body, dest) else e -> {:error, e} @@ -135,6 +133,7 @@ defp download_build(frontend_info, dest) do defp install_frontend(frontend_info, source, dest) do from = frontend_info["build_dir"] || "dist" + File.rm_rf!(dest) File.mkdir_p!(dest) File.cp_r!(Path.join([source, from]), dest) :ok diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 91440b453..fc21ae062 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -33,7 +33,10 @@ def run(["gen" | rest]) do uploads_dir: :string, static_dir: :string, listen_ip: :string, - listen_port: :string + listen_port: :string, + strip_uploads: :string, + anonymize_uploads: :string, + dedupe_uploads: :string ], aliases: [ o: :output, @@ -158,6 +161,30 @@ def run(["gen" | rest]) do ) |> Path.expand() + strip_uploads = + get_option( + options, + :strip_uploads, + "Do you want to strip location (GPS) data from uploaded images? (y/n)", + "y" + ) === "y" + + anonymize_uploads = + get_option( + options, + :anonymize_uploads, + "Do you want to anonymize the filenames of uploads? (y/n)", + "n" + ) === "y" + + dedupe_uploads = + get_option( + options, + :dedupe_uploads, + "Do you want to deduplicate uploaded files? (y/n)", + "n" + ) === "y" + Config.put([:instance, :static_dir], static_dir) secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) @@ -188,7 +215,13 @@ def run(["gen" | rest]) do uploads_dir: uploads_dir, rum_enabled: rum_enabled, listen_ip: listen_ip, - listen_port: listen_port + listen_port: listen_port, + upload_filters: + upload_filters(%{ + strip: strip_uploads, + anonymize: anonymize_uploads, + dedupe: dedupe_uploads + }) ) result_psql = @@ -247,4 +280,31 @@ defp write_robots_txt(static_dir, indexable, template_dir) do File.write(robots_txt_path, robots_txt) shell_info("Writing #{robots_txt_path}.") end + + defp upload_filters(filters) when is_map(filters) do + enabled_filters = + if filters.strip do + [Pleroma.Upload.Filter.ExifTool] + else + [] + end + + enabled_filters = + if filters.anonymize do + enabled_filters ++ [Pleroma.Upload.Filter.AnonymizeFilename] + else + enabled_filters + end + + enabled_filters = + if filters.dedupe do + enabled_filters ++ [Pleroma.Upload.Filter.Dedupe] + else + enabled_filters + end + + enabled_filters + end + + defp upload_filters(_), do: [] end diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex index 00f5ba7bf..f99275de1 100644 --- a/lib/mix/tasks/pleroma/notification_settings.ex +++ b/lib/mix/tasks/pleroma/notification_settings.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.NotificationSettings do @shortdoc "Enable&Disable privacy option for push notifications" @moduledoc """ diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex index a6d8d6c1c..bb808ca47 100644 --- a/lib/mix/tasks/pleroma/relay.ex +++ b/lib/mix/tasks/pleroma/relay.ex @@ -21,10 +21,19 @@ def run(["follow", target]) do end end - def run(["unfollow", target]) do + def run(["unfollow", target | rest]) do start_pleroma() - with {:ok, _activity} <- Relay.unfollow(target) do + {options, [], []} = + OptionParser.parse( + rest, + strict: [force: :boolean], + aliases: [f: :force] + ) + + force = Keyword.get(options, :force, false) + + with {:ok, _activity} <- Relay.unfollow(target, %{force: force}) do # put this task to sleep to allow the genserver to push out the messages :timer.sleep(500) else diff --git a/lib/mix/tasks/pleroma/robotstxt.ex b/lib/mix/tasks/pleroma/robots_txt.ex similarity index 100% rename from lib/mix/tasks/pleroma/robotstxt.ex rename to lib/mix/tasks/pleroma/robots_txt.ex diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 01824aa18..a8d251411 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -179,7 +179,7 @@ def run(["deactivate_all_from_instance", instance]) do start_pleroma() Pleroma.User.Query.build(%{nickname: "@#{instance}"}) - |> Pleroma.RepoStreamer.chunk_stream(500) + |> Pleroma.Repo.chunk_stream(500, :batches) |> Stream.each(fn users -> users |> Enum.each(fn user -> @@ -196,17 +196,24 @@ def run(["set", nickname | rest]) do OptionParser.parse( rest, strict: [ - moderator: :boolean, admin: :boolean, - locked: :boolean + confirmed: :boolean, + locked: :boolean, + moderator: :boolean ] ) with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do user = - case Keyword.get(options, :moderator) do + case Keyword.get(options, :admin) do nil -> user - value -> set_moderator(user, value) + value -> set_admin(user, value) + end + + user = + case Keyword.get(options, :confirmed) do + nil -> user + value -> set_confirmed(user, value) end user = @@ -216,9 +223,9 @@ def run(["set", nickname | rest]) do end _user = - case Keyword.get(options, :admin) do + case Keyword.get(options, :moderator) do nil -> user - value -> set_admin(user, value) + value -> set_moderator(user, value) end else _ -> @@ -353,6 +360,42 @@ def run(["toggle_confirmed", nickname]) do end end + def run(["confirm_all"]) do + start_pleroma() + + Pleroma.User.Query.build(%{ + local: true, + deactivated: false, + is_moderator: false, + is_admin: false, + invisible: false + }) + |> Pleroma.Repo.chunk_stream(500, :batches) + |> Stream.each(fn users -> + users + |> Enum.each(fn user -> User.need_confirmation(user, false) end) + end) + |> Stream.run() + end + + def run(["unconfirm_all"]) do + start_pleroma() + + Pleroma.User.Query.build(%{ + local: true, + deactivated: false, + is_moderator: false, + is_admin: false, + invisible: false + }) + |> Pleroma.Repo.chunk_stream(500, :batches) + |> Stream.each(fn users -> + users + |> Enum.each(fn user -> User.need_confirmation(user, true) end) + end) + |> Stream.run() + end + def run(["sign_out", nickname]) do start_pleroma() @@ -370,13 +413,13 @@ def run(["list"]) do start_pleroma() Pleroma.User.Query.build(%{local: true}) - |> Pleroma.RepoStreamer.chunk_stream(500) + |> Pleroma.Repo.chunk_stream(500, :batches) |> Stream.each(fn users -> users |> Enum.each(fn user -> shell_info( "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{ - user.locked + user.is_locked }, deactivated: #{user.deactivated}" ) end) @@ -404,10 +447,17 @@ defp set_admin(user, value) do defp set_locked(user, value) do {:ok, user} = user - |> Changeset.change(%{locked: value}) + |> Changeset.change(%{is_locked: value}) |> User.update_and_set_cache() - shell_info("Locked status of #{user.nickname}: #{user.locked}") + shell_info("Locked status of #{user.nickname}: #{user.is_locked}") + user + end + + defp set_confirmed(user, value) do + {:ok, user} = User.need_confirmation(user, !value) + + shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}") user end end diff --git a/lib/transports.ex b/lib/phoenix/transports/web_socket/raw.ex similarity index 90% rename from lib/transports.ex rename to lib/phoenix/transports/web_socket/raw.ex index aab7fad99..c3665bebe 100644 --- a/lib/transports.ex +++ b/lib/phoenix/transports/web_socket/raw.ex @@ -31,7 +31,12 @@ def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do case conn do %{halted: false} = conn -> - case Transport.connect(endpoint, handler, transport, __MODULE__, nil, conn.params) do + case handler.connect(%{ + endpoint: endpoint, + transport: transport, + options: [serializer: nil], + params: conn.params + }) do {:ok, socket} -> {:ok, conn, {__MODULE__, {socket, opts}}} diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 97feebeaa..17af04257 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Activity do alias Pleroma.Activity alias Pleroma.Activity.Queries - alias Pleroma.ActivityExpiration alias Pleroma.Bookmark alias Pleroma.Notification alias Pleroma.Object @@ -60,8 +59,6 @@ defmodule Pleroma.Activity do # typical case. has_one(:object, Object, on_delete: :nothing, foreign_key: :id) - has_one(:expiration, ActivityExpiration, on_delete: :delete_all) - timestamps() end @@ -304,14 +301,14 @@ def all_by_actor_and_id(actor, status_ids) do |> Repo.all() end - def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do + def follow_requests_for_actor(%User{ap_id: ap_id}) do ap_id |> Queries.by_object_id() |> Queries.by_type("Follow") |> where([a], fragment("? ->> 'state' = 'pending'", a.data)) end - def following_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do + def following_requests_for_actor(%User{ap_id: ap_id}) do Queries.by_type("Follow") |> where([a], fragment("?->>'state' = 'pending'", a.data)) |> where([a], a.actor == ^ap_id) diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex deleted file mode 100644 index 955f0578e..000000000 --- a/lib/pleroma/activity_expiration.ex +++ /dev/null @@ -1,74 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ActivityExpiration do - use Ecto.Schema - - alias Pleroma.Activity - alias Pleroma.ActivityExpiration - alias Pleroma.Repo - - import Ecto.Changeset - import Ecto.Query - - @type t :: %__MODULE__{} - @min_activity_lifetime :timer.hours(1) - - schema "activity_expirations" do - belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) - field(:scheduled_at, :naive_datetime) - end - - def changeset(%ActivityExpiration{} = expiration, attrs, validate_scheduled_at) do - expiration - |> cast(attrs, [:scheduled_at]) - |> validate_required([:scheduled_at]) - |> validate_scheduled_at(validate_scheduled_at) - end - - def get_by_activity_id(activity_id) do - ActivityExpiration - |> where([exp], exp.activity_id == ^activity_id) - |> Repo.one() - end - - def create(%Activity{} = activity, scheduled_at, validate_scheduled_at \\ true) do - %ActivityExpiration{activity_id: activity.id} - |> changeset(%{scheduled_at: scheduled_at}, validate_scheduled_at) - |> Repo.insert() - end - - def due_expirations(offset \\ 0) do - naive_datetime = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(offset, :millisecond) - - ActivityExpiration - |> where([exp], exp.scheduled_at < ^naive_datetime) - |> limit(50) - |> preload(:activity) - |> Repo.all() - |> Enum.reject(fn %{activity: activity} -> - Activity.pinned_by_actor?(activity) - end) - end - - def validate_scheduled_at(changeset, false), do: changeset - - def validate_scheduled_at(changeset, true) do - validate_change(changeset, :scheduled_at, fn _, scheduled_at -> - if not expires_late_enough?(scheduled_at) do - [scheduled_at: "an ephemeral activity must live for at least one hour"] - else - [] - end - end) - end - - def expires_late_enough?(scheduled_at) do - now = NaiveDateTime.utc_now() - diff = NaiveDateTime.diff(scheduled_at, now, :millisecond) - diff > @min_activity_lifetime - end -end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index c0b5db9f1..51e9dda3b 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -22,13 +22,18 @@ def named_version, do: @name <> " " <> @version def repository, do: @repository def user_agent do - case Config.get([:http, :user_agent], :default) do - :default -> - info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>" - named_version() <> "; " <> info + if Process.whereis(Pleroma.Web.Endpoint) do + case Config.get([:http, :user_agent], :default) do + :default -> + info = "#{Pleroma.Web.base_url()} <#{Config.get([:instance, :email], "")}>" + named_version() <> "; " <> info - custom -> - custom + custom -> + custom + end + else + # fallback, if endpoint is not started yet + "Pleroma Data Loader" end end @@ -39,15 +44,18 @@ def start(_type, _args) do # every time the application is restarted, so we disable module # conflicts at runtime Code.compiler_options(ignore_module_conflict: true) + # Disable warnings_as_errors at runtime, it breaks Phoenix live reload + # due to protocol consolidation warnings + Code.compiler_options(warnings_as_errors: false) Pleroma.Telemetry.Logger.attach() Config.Holder.save_default() Pleroma.HTML.compile_scrubbers() + Pleroma.Config.Oban.warn() Config.DeprecationWarnings.warn() - Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled() + Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled() Pleroma.ApplicationRequirements.verify!() setup_instrumenters() load_custom_modules() - check_system_commands() Pleroma.Docs.JSON.compile() adapter = Application.get_env(:tesla, :adapter) @@ -80,18 +88,19 @@ def start(_type, _args) do Pleroma.Repo, Config.TransferTask, Pleroma.Emoji, - Pleroma.Plugs.RateLimiter.Supervisor + Pleroma.Web.Plugs.RateLimiter.Supervisor ] ++ cachex_children() ++ http_children(adapter, @env) ++ [ Pleroma.Stats, Pleroma.JobQueueMonitor, + {Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]}, {Oban, Config.get(Oban)} ] ++ task_children(@env) ++ - streamer_child(@env) ++ - chat_child(@env, chat_enabled?()) ++ + dont_run_in_test(@env) ++ + chat_child(chat_enabled?()) ++ [ Pleroma.Web.Endpoint, Pleroma.Gopher.Server @@ -142,7 +151,10 @@ defp setup_instrumenters do Pleroma.Web.Endpoint.MetricsExporter.setup() Pleroma.Web.Endpoint.PipelineInstrumenter.setup() - Pleroma.Web.Endpoint.Instrumenter.setup() + + # Note: disabled until prometheus-phx is integrated into prometheus-phoenix: + # Pleroma.Web.Endpoint.Instrumenter.setup() + PrometheusPhx.setup() end defp cachex_children do @@ -179,24 +191,28 @@ def build_cachex(type, opts), defp chat_enabled?, do: Config.get([:chat, :enabled]) - defp streamer_child(env) when env in [:test, :benchmark], do: [] + defp dont_run_in_test(env) when env in [:test, :benchmark], do: [] - defp streamer_child(_) do + defp dont_run_in_test(_) do [ {Registry, [ name: Pleroma.Web.Streamer.registry(), keys: :duplicate, partitions: System.schedulers_online() - ]} + ]}, + Pleroma.Web.FedSockets.Supervisor ] end - defp chat_child(_env, true) do - [Pleroma.Web.ChatChannel.ChatChannelState] + defp chat_child(true) do + [ + Pleroma.Web.ChatChannel.ChatChannelState, + {Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]} + ] end - defp chat_child(_, _), do: [] + defp chat_child(_), do: [] defp task_children(:test) do [ @@ -250,21 +266,4 @@ defp http_children(Tesla.Adapter.Gun, _) do end defp http_children(_, _), do: [] - - defp check_system_commands do - filters = Config.get([Pleroma.Upload, :filters]) - - check_filter = fn filter, command_required -> - with true <- filter in filters, - false <- Pleroma.Utils.command_available?(command_required) do - Logger.error( - "#{filter} is specified in list of Pleroma.Upload filters, but the #{command_required} command is not found" - ) - end - end - - check_filter.(Pleroma.Upload.Filters.Exiftool, "exiftool") - check_filter.(Pleroma.Upload.Filters.Mogrify, "mogrify") - check_filter.(Pleroma.Upload.Filters.Mogrifun, "mogrify") - end end diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 16f62b6f5..b977257a3 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -9,6 +9,9 @@ defmodule Pleroma.ApplicationRequirements do defmodule VerifyError, do: defexception([:message]) + alias Pleroma.Config + alias Pleroma.Helpers.MediaHelper + import Ecto.Query require Logger @@ -16,7 +19,8 @@ defmodule VerifyError, do: defexception([:message]) @spec verify!() :: :ok | VerifyError.t() def verify! do :ok - |> check_confirmation_accounts! + |> check_system_commands!() + |> check_confirmation_accounts!() |> check_migrations_applied!() |> check_welcome_message_config!() |> check_rum!() @@ -48,7 +52,9 @@ def check_confirmation_accounts!(:ok) do if Pleroma.Config.get([:instance, :account_activation_required]) && not Pleroma.Config.get([Pleroma.Emails.Mailer, :enabled]) do Logger.error( - "Account activation enabled, but no Mailer settings enabled.\nPlease set config :pleroma, :instance, account_activation_required: false\nOtherwise setup and enable Mailer." + "Account activation enabled, but no Mailer settings enabled.\n" <> + "Please set config :pleroma, :instance, account_activation_required: false\n" <> + "Otherwise setup and enable Mailer." ) {:error, @@ -81,7 +87,9 @@ def check_migrations_applied!(:ok) do Enum.map(down_migrations, fn {:down, id, name} -> "- #{name} (#{id})\n" end) Logger.error( - "The following migrations were not applied:\n#{down_migrations_text}If you want to start Pleroma anyway, set\nconfig :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true" + "The following migrations were not applied:\n#{down_migrations_text}" <> + "If you want to start Pleroma anyway, set\n" <> + "config :pleroma, :i_am_aware_this_may_cause_data_loss, disable_migration_check: true" ) {:error, "Unapplied Migrations detected"} @@ -124,14 +132,22 @@ defp do_check_rum!(setting, migrate) do case {setting, migrate} do {true, false} -> Logger.error( - "Use `RUM` index is enabled, but were not applied migrations for it.\nIf you want to start Pleroma anyway, set\nconfig :pleroma, :database, rum_enabled: false\nOtherwise apply the following migrations:\n`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`" + "Use `RUM` index is enabled, but were not applied migrations for it.\n" <> + "If you want to start Pleroma anyway, set\n" <> + "config :pleroma, :database, rum_enabled: false\n" <> + "Otherwise apply the following migrations:\n" <> + "`mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/`" ) {:error, "Unapplied RUM Migrations detected"} {false, true} -> Logger.error( - "Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\nIf you want to use `RUM`, set\nconfig :pleroma, :database, rum_enabled: true\nOtherwise roll `RUM` migrations back.\n`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`" + "Detected applied migrations to use `RUM` index, but `RUM` isn't enable in settings.\n" <> + "If you want to use `RUM`, set\n" <> + "config :pleroma, :database, rum_enabled: true\n" <> + "Otherwise roll `RUM` migrations back.\n" <> + "`mix ecto.rollback --migrations-path priv/repo/optional_migrations/rum_indexing/`" ) {:error, "RUM Migrations detected"} @@ -140,4 +156,50 @@ defp do_check_rum!(setting, migrate) do :ok end end + + defp check_system_commands!(:ok) do + filter_commands_statuses = [ + check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"), + check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"), + check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify") + ] + + preview_proxy_commands_status = + if !Config.get([:media_preview_proxy, :enabled]) or + MediaHelper.missing_dependencies() == [] do + true + else + Logger.error( + "The following dependencies required by Media preview proxy " <> + "(which is currently enabled) are not installed: " <> + inspect(MediaHelper.missing_dependencies()) + ) + + false + end + + if Enum.all?([preview_proxy_commands_status | filter_commands_statuses], & &1) do + :ok + else + {:error, + "System commands missing. Check logs and see `docs/installation` for more details."} + end + end + + defp check_system_commands!(result), do: result + + defp check_filter(filter, command_required) do + filters = Config.get([Pleroma.Upload, :filters]) + + if filter in filters and not Pleroma.Utils.command_available?(command_required) do + Logger.error( + "#{filter} is specified in list of Pleroma.Upload filters, but the " <> + "#{command_required} command is not found" + ) + + false + else + true + end + end end diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex index 815de7002..83ebb756d 100644 --- a/lib/pleroma/bbs/authenticator.ex +++ b/lib/pleroma/bbs/authenticator.ex @@ -4,8 +4,8 @@ defmodule Pleroma.BBS.Authenticator do use Sshd.PasswordAuthenticator - alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.User + alias Pleroma.Web.Plugs.AuthenticationPlug def authenticate(username, password) do username = to_string(username) diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha.ex similarity index 100% rename from lib/pleroma/captcha/captcha.ex rename to lib/pleroma/captcha.ex diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 337506647..201b55ab4 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Captcha.Kocaptcha do def new do endpoint = Pleroma.Config.get!([__MODULE__, :endpoint]) - case Tesla.get(endpoint <> "/new") do + case Pleroma.HTTP.get(endpoint <> "/new") do {:error, _} -> %{error: :kocaptcha_service_unavailable} diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/service.ex similarity index 100% rename from lib/pleroma/captcha/captcha_service.ex rename to lib/pleroma/captcha/service.ex diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex index 24a86371e..28007cd9f 100644 --- a/lib/pleroma/chat.ex +++ b/lib/pleroma/chat.ex @@ -6,7 +6,9 @@ defmodule Pleroma.Chat do use Ecto.Schema import Ecto.Changeset + import Ecto.Query + alias Pleroma.Chat alias Pleroma.Repo alias Pleroma.User @@ -16,6 +18,7 @@ defmodule Pleroma.Chat do It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages. """ + @type t :: %__MODULE__{} @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} schema "chats" do @@ -39,16 +42,28 @@ def changeset(struct, params) do |> unique_constraint(:user_id, name: :chats_user_id_recipient_index) end + @spec get_by_user_and_id(User.t(), FlakeId.Ecto.CompatType.t()) :: + {:ok, t()} | {:error, :not_found} + def get_by_user_and_id(%User{id: user_id}, id) do + from(c in __MODULE__, + where: c.id == ^id, + where: c.user_id == ^user_id + ) + |> Repo.find_resource() + end + + @spec get_by_id(FlakeId.Ecto.CompatType.t()) :: t() | nil def get_by_id(id) do - __MODULE__ - |> Repo.get(id) + Repo.get(__MODULE__, id) end + @spec get(FlakeId.Ecto.CompatType.t(), String.t()) :: t() | nil def get(user_id, recipient) do - __MODULE__ - |> Repo.get_by(user_id: user_id, recipient: recipient) + Repo.get_by(__MODULE__, user_id: user_id, recipient: recipient) end + @spec get_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: + {:ok, t()} | {:error, Ecto.Changeset.t()} def get_or_create(user_id, recipient) do %__MODULE__{} |> changeset(%{user_id: user_id, recipient: recipient}) @@ -60,6 +75,8 @@ def get_or_create(user_id, recipient) do ) end + @spec bump_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: + {:ok, t()} | {:error, Ecto.Changeset.t()} def bump_or_create(user_id, recipient) do %__MODULE__{} |> changeset(%{user_id: user_id, recipient: recipient}) @@ -69,4 +86,12 @@ def bump_or_create(user_id, recipient) do conflict_target: [:user_id, :recipient] ) end + + @spec for_user_query(FlakeId.Ecto.CompatType.t()) :: Ecto.Query.t() + def for_user_query(user_id) do + from(c in Chat, + where: c.user_id == ^user_id, + order_by: [desc: c.updated_at] + ) + end end diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 0f52eb210..59c6b0f58 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Config.DeprecationWarnings do require Logger alias Pleroma.Config - @type config_namespace() :: [atom()] + @type config_namespace() :: atom() | [atom()] @type config_map() :: {config_namespace(), config_namespace(), String.t()} @mrf_config_map [ @@ -26,36 +26,26 @@ def check_hellthread_threshold do !!!DEPRECATION WARNING!!! You are using the old configuration mechanism for the hellthread filter. Please check config.md. """) - end - end - def mrf_user_allowlist do - config = Config.get(:mrf_user_allowlist) - - if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do - rewritten = - Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc -> - Map.put(acc, to_string(k), v) - end) - - Config.put(:mrf_user_allowlist, rewritten) - - Logger.error(""" - !!!DEPRECATION WARNING!!! - As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format. - Pleroma 2.1 will remove support for the old format. Please change your configuration to match this: - - config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)} - """) + :error + else + :ok end end def warn do - check_hellthread_threshold() - mrf_user_allowlist() - check_old_mrf_config() - check_media_proxy_whitelist_config() - check_welcome_message_config() + with :ok <- check_hellthread_threshold(), + :ok <- check_old_mrf_config(), + :ok <- check_media_proxy_whitelist_config(), + :ok <- check_welcome_message_config(), + :ok <- check_gun_pool_options(), + :ok <- check_activity_expiration_config(), + :ok <- check_remote_ip_plug_name() do + :ok + else + _ -> + :error + end end def check_welcome_message_config do @@ -68,10 +58,14 @@ def check_welcome_message_config do if use_old_config do Logger.error(""" !!!DEPRECATION WARNING!!! - Your config is using the old namespace for Welcome messages configuration. You need to change to the new namespace: - \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname` - \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message` + Your config is using the old namespace for Welcome messages configuration. You need to convert to the new namespace. e.g., + \n* `config :pleroma, :instance, welcome_user_nickname` and `config :pleroma, :instance, welcome_message` are now equal to: + \n* `config :pleroma, :welcome, direct_message: [enabled: true, sender_nickname: "NICKNAME", message: "Your welcome message"]`" """) + + :error + else + :ok end end @@ -99,8 +93,11 @@ def move_namespace_and_warn(config_map, warning_preface) do end end) - if warning != "" do + if warning == "" do + :ok + else Logger.warn(warning_preface <> warning) + :error end end @@ -113,6 +110,87 @@ def check_media_proxy_whitelist_config do !!!DEPRECATION WARNING!!! Your config is using old format (only domain) for MediaProxy whitelist option. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. """) + + :error + else + :ok end end + + def check_gun_pool_options do + pool_config = Config.get(:connections_pool) + + if timeout = pool_config[:await_up_timeout] do + Logger.warn(""" + !!!DEPRECATION WARNING!!! + Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`. Please change to `config :pleroma, :connections_pool, connect_timeout` to ensure compatibility with future releases. + """) + + Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout)) + end + + pools_configs = Config.get(:pools) + + warning_preface = """ + !!!DEPRECATION WARNING!!! + Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later. + """ + + updated_config = + Enum.reduce(pools_configs, [], fn {pool_name, config}, acc -> + if timeout = config[:timeout] do + Keyword.put(acc, pool_name, Keyword.put_new(config, :recv_timeout, timeout)) + else + acc + end + end) + + if updated_config != [] do + pool_warnings = + updated_config + |> Keyword.keys() + |> Enum.map(fn pool_name -> + "\n* `:timeout` options in #{pool_name} pool is now `:recv_timeout`" + end) + + Logger.warn(Enum.join([warning_preface | pool_warnings])) + + Config.put(:pools, updated_config) + :error + else + :ok + end + end + + @spec check_activity_expiration_config() :: :ok | nil + def check_activity_expiration_config do + warning_preface = """ + !!!DEPRECATION WARNING!!! + Your config is using old namespace for activity expiration configuration. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later: + """ + + move_namespace_and_warn( + [ + {Pleroma.ActivityExpiration, Pleroma.Workers.PurgeExpiredActivity, + "\n* `config :pleroma, Pleroma.ActivityExpiration` is now `config :pleroma, Pleroma.Workers.PurgeExpiredActivity`"} + ], + warning_preface + ) + end + + @spec check_remote_ip_plug_name() :: :ok | nil + def check_remote_ip_plug_name do + warning_preface = """ + !!!DEPRECATION WARNING!!! + Your config is using old namespace for RemoteIp Plug. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later: + """ + + move_namespace_and_warn( + [ + {Pleroma.Plugs.RemoteIp, Pleroma.Web.Plugs.RemoteIp, + "\n* `config :pleroma, Pleroma.Plugs.RemoteIp` is now `config :pleroma, Pleroma.Web.Plugs.RemoteIp`"} + ], + warning_preface + ) + end end diff --git a/lib/pleroma/config/oban.ex b/lib/pleroma/config/oban.ex new file mode 100644 index 000000000..8e0351d52 --- /dev/null +++ b/lib/pleroma/config/oban.ex @@ -0,0 +1,38 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Config.Oban do + require Logger + + def warn do + oban_config = Pleroma.Config.get(Oban) + + crontab = + [ + Pleroma.Workers.Cron.StatsWorker, + Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker, + Pleroma.Workers.Cron.ClearOauthTokenWorker + ] + |> Enum.reduce(oban_config[:crontab], fn removed_worker, acc -> + with acc when is_list(acc) <- acc, + setting when is_tuple(setting) <- + Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do + """ + !!!OBAN CONFIG WARNING!!! + You are using old workers in Oban crontab settings, which were removed. + Please, remove setting from crontab in your config file (prod.secret.exs): #{ + inspect(setting) + } + """ + |> Logger.warn() + + List.delete(acc, setting) + else + _ -> acc + end + end) + + Pleroma.Config.put(Oban, Keyword.put(oban_config, :crontab, crontab)) + end +end diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config_db.ex similarity index 100% rename from lib/pleroma/config/config_db.ex rename to lib/pleroma/config_db.ex diff --git a/lib/pleroma/conversation/participation_recipient_ship.ex b/lib/pleroma/conversation/participation/recipient_ship.ex similarity index 100% rename from lib/pleroma/conversation/participation_recipient_ship.ex rename to lib/pleroma/conversation/participation/recipient_ship.ex diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex index a671a6278..a70f83b73 100644 --- a/lib/pleroma/docs/generator.ex +++ b/lib/pleroma/docs/generator.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Docs.Generator do @callback process(keyword()) :: {:ok, String.t()} diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex index feeb4320e..13618b509 100644 --- a/lib/pleroma/docs/json.ex +++ b/lib/pleroma/docs/json.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Docs.JSON do @behaviour Pleroma.Docs.Generator @external_resource "config/description.exs" diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex index da3f20f43..eac0789a6 100644 --- a/lib/pleroma/docs/markdown.ex +++ b/lib/pleroma/docs/markdown.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Docs.Markdown do @behaviour Pleroma.Docs.Generator diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex new file mode 100644 index 000000000..4aacc5c88 --- /dev/null +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/emoji.ex @@ -0,0 +1,34 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.Emoji do + use Ecto.Type + + def type, do: :map + + def cast(data) when is_map(data) do + has_invalid_emoji? = + Enum.find(data, fn + {name, uri} when is_binary(name) and is_binary(uri) -> + # based on ObjectValidators.Uri.cast() + case URI.parse(uri) do + %URI{host: nil} -> true + %URI{host: ""} -> true + %URI{scheme: scheme} when scheme in ["https", "http"] -> false + _ -> true + end + + {_name, _uri} -> + true + end) + + if has_invalid_emoji?, do: :error, else: {:ok, data} + end + + def cast(_data), do: :error + + def dump(data), do: {:ok, data} + + def load(data), do: {:ok, data} +end diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index c27ad1065..8979db2f8 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -88,7 +88,7 @@ def new_unapproved_registration(to, account) do html_body = """

New account for review: @#{account.nickname}

#{HTML.strip_tags(account.registration_reason)}
- Visit AdminFE + Visit AdminFE """ new() diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 8b1bdef75..5108c71c8 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -35,6 +35,11 @@ def perform(:deliver_async, email, config), do: deliver(email, config) def deliver(email, config \\ []) def deliver(email, config) do + # temporary hackney fix until hackney max_connections bug is fixed + # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 + email = + Swoosh.Email.put_private(email, :hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) + case enabled?() do true -> Swoosh.Mailer.deliver(email, parse_config(config)) false -> {:error, :deliveries_disabled} diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index f6016d73f..04936155b 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -56,6 +56,9 @@ def get(name) do end end + @spec exist?(String.t()) :: boolean() + def exist?(name), do: not is_nil(get(name)) + @doc "Returns all the emojos!!" @spec get_all() :: list({String.t(), String.t(), String.t()}) def get_all do diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index d076ae312..ca58e5432 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Emoji.Pack do @derive {Jason.Encoder, only: [:files, :pack, :files_count]} defstruct files: %{}, @@ -17,6 +21,7 @@ defmodule Pleroma.Emoji.Pack do } alias Pleroma.Emoji + alias Pleroma.Emoji.Pack @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} def create(name) do @@ -64,24 +69,93 @@ def delete(name) do end end - @spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) :: - {:ok, t()} | {:error, File.posix() | atom()} - def add_file(name, shortcode, filename, file) do - with :ok <- validate_not_empty([name, shortcode, filename]), + @spec unpack_zip_emojies(list(tuple())) :: list(map()) + defp unpack_zip_emojies(zip_files) do + Enum.reduce(zip_files, [], fn + {_, path, s, _, _, _}, acc when elem(s, 2) == :regular -> + with( + filename <- Path.basename(path), + shortcode <- Path.basename(filename, Path.extname(filename)), + false <- Emoji.exist?(shortcode) + ) do + [%{path: path, filename: path, shortcode: shortcode} | acc] + else + _ -> acc + end + + _, acc -> + acc + end) + end + + @spec add_file(t(), String.t(), Path.t(), Plug.Upload.t()) :: + {:ok, t()} + | {:error, File.posix() | atom()} + def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do + with {:ok, zip_files} <- :zip.table(to_charlist(file.path)), + [_ | _] = emojies <- unpack_zip_emojies(zip_files), + {:ok, tmp_dir} <- Pleroma.Utils.tmp_dir("emoji") do + try do + {:ok, _emoji_files} = + :zip.unzip( + to_charlist(file.path), + [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}] + ) + + {_, updated_pack} = + Enum.map_reduce(emojies, pack, fn item, emoji_pack -> + emoji_file = %Plug.Upload{ + filename: item[:filename], + path: Path.join(tmp_dir, item[:path]) + } + + {:ok, updated_pack} = + do_add_file( + emoji_pack, + item[:shortcode], + to_string(item[:filename]), + emoji_file + ) + + {item, updated_pack} + end) + + Emoji.reload() + + {:ok, updated_pack} + after + File.rm_rf(tmp_dir) + end + else + {:error, _} = error -> + error + + _ -> + {:ok, pack} + end + end + + def add_file(%Pack{} = pack, shortcode, filename, %Plug.Upload{} = file) do + with :ok <- validate_not_empty([shortcode, filename]), :ok <- validate_emoji_not_exists(shortcode), - {:ok, pack} <- load_pack(name), - :ok <- save_file(file, pack, filename), - {:ok, updated_pack} <- pack |> put_emoji(shortcode, filename) |> save_pack() do + {:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do Emoji.reload() {:ok, updated_pack} end end - @spec delete_file(String.t(), String.t()) :: + defp do_add_file(pack, shortcode, filename, file) do + with :ok <- save_file(file, pack, filename) do + pack + |> put_emoji(shortcode, filename) + |> save_pack() + end + end + + @spec delete_file(t(), String.t()) :: {:ok, t()} | {:error, File.posix() | atom()} - def delete_file(name, shortcode) do - with :ok <- validate_not_empty([name, shortcode]), - {:ok, pack} <- load_pack(name), + def delete_file(%Pack{} = pack, shortcode) do + with :ok <- validate_not_empty([shortcode]), :ok <- remove_file(pack, shortcode), {:ok, updated_pack} <- pack |> delete_emoji(shortcode) |> save_pack() do Emoji.reload() @@ -89,11 +163,10 @@ def delete_file(name, shortcode) do end end - @spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) :: + @spec update_file(t(), String.t(), String.t(), String.t(), boolean()) :: {:ok, t()} | {:error, File.posix() | atom()} - def update_file(name, shortcode, new_shortcode, new_filename, force) do - with :ok <- validate_not_empty([name, shortcode, new_shortcode, new_filename]), - {:ok, pack} <- load_pack(name), + def update_file(%Pack{} = pack, shortcode, new_shortcode, new_filename, force) do + with :ok <- validate_not_empty([shortcode, new_shortcode, new_filename]), {:ok, filename} <- get_filename(pack, shortcode), :ok <- validate_emoji_not_exists(new_shortcode, force), :ok <- rename_file(pack, filename, new_filename), @@ -129,13 +202,13 @@ def import_from_filesystem do end end - @spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()} - def list_remote(url) do - uri = url |> String.trim() |> URI.parse() + @spec list_remote(keyword()) :: {:ok, map()} | {:error, atom()} + def list_remote(opts) do + uri = opts[:url] |> String.trim() |> URI.parse() with :ok <- validate_shareable_packs_available(uri) do uri - |> URI.merge("/api/pleroma/emoji/packs") + |> URI.merge("/api/pleroma/emoji/packs?page=#{opts[:page]}&page_size=#{opts[:page_size]}") |> http_get() end end @@ -175,7 +248,8 @@ def download(name, url, as) do uri = url |> String.trim() |> URI.parse() with :ok <- validate_shareable_packs_available(uri), - {:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(), + {:ok, remote_pack} <- + uri |> URI.merge("/api/pleroma/emoji/pack?name=#{name}") |> http_get(), {:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name), {:ok, archive} <- download_archive(url, sha), pack <- copy_as(remote_pack, as || name), @@ -243,9 +317,10 @@ defp validate_emoji_not_exists(shortcode, force \\ false) defp validate_emoji_not_exists(_shortcode, true), do: :ok defp validate_emoji_not_exists(shortcode, _) do - case Emoji.get(shortcode) do - nil -> :ok - _ -> {:error, :already_exists} + if Emoji.exist?(shortcode) do + {:error, :already_exists} + else + :ok end end @@ -386,25 +461,18 @@ defp validate_not_empty(list) do end end - defp save_file(file, pack, filename) do + defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do file_path = Path.join(pack.path, filename) create_subdirs(file_path) - case file do - %Plug.Upload{path: upload_path} -> - # Copy the uploaded file from the temporary directory - with {:ok, _} <- File.copy(upload_path, file_path), do: :ok - - url when is_binary(url) -> - # Download and write the file - file_contents = Tesla.get!(url).body - File.write(file_path, file_contents) + with {:ok, _} <- File.copy(upload_path, file_path) do + :ok end end defp put_emoji(pack, shortcode, filename) do files = Map.put(pack.files, shortcode, filename) - %{pack | files: files} + %{pack | files: files, files_count: length(Map.keys(files))} end defp delete_emoji(pack, shortcode) do @@ -460,7 +528,7 @@ defp get_filename(pack, shortcode) do defp http_get(%URI{} = url), do: url |> to_string() |> http_get() defp http_get(url) do - with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do + with {:ok, %{body: body}} <- Pleroma.HTTP.get(url, [], pool: :default) do Jason.decode(body) end end @@ -509,7 +577,7 @@ defp fetch_pack_info(remote_pack, uri, name) do {:ok, %{ sha: sha, - url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string() + url: URI.merge(uri, "/api/pleroma/emoji/packs/archive?name=#{name}") |> to_string() }} %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> @@ -526,7 +594,7 @@ defp fetch_pack_info(remote_pack, uri, name) do end defp download_archive(url, sha) do - with {:ok, %{body: archive}} <- Tesla.get(url) do + with {:ok, %{body: archive}} <- Pleroma.HTTP.get(url) do if Base.decode16!(sha) == :crypto.hash(:sha256, archive) do {:ok, archive} else @@ -549,7 +617,7 @@ defp fallback_sha_changed?(pack, data) do end defp update_sha_and_save_metadata(pack, data) do - with {:ok, %{body: zip}} <- Tesla.get(data[:"fallback-src"]), + with {:ok, %{body: zip}} <- Pleroma.HTTP.get(data[:"fallback-src"]), :ok <- validate_has_all_files(pack, zip) do fallback_sha = :sha256 |> :crypto.hash(zip) |> Base.encode16() diff --git a/lib/pleroma/gun/gun.ex b/lib/pleroma/gun.ex similarity index 100% rename from lib/pleroma/gun/gun.ex rename to lib/pleroma/gun.ex diff --git a/lib/pleroma/gun/conn.ex b/lib/pleroma/gun/conn.ex index a3f75a4bb..477e19c6e 100644 --- a/lib/pleroma/gun/conn.ex +++ b/lib/pleroma/gun/conn.ex @@ -13,7 +13,7 @@ def open(%URI{} = uri, opts) do opts = opts |> Enum.into(%{}) - |> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000) + |> Map.put_new(:connect_timeout, pool_opts[:connect_timeout] || 5_000) |> Map.put_new(:supervise, false) |> maybe_add_tls_opts(uri) @@ -50,10 +50,10 @@ defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do with open_opts <- Map.delete(opts, :tls_opts), {:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]), + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]), stream <- Gun.connect(conn, connect_opts), {:response, :fin, 200, _} <- Gun.await(conn, stream) do - {:ok, conn} + {:ok, conn, protocol} else error -> Logger.warn( @@ -88,8 +88,8 @@ defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do |> Map.put(:socks_opts, socks_opts) with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do - {:ok, conn} + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do + {:ok, conn, protocol} else error -> Logger.warn( @@ -106,8 +106,8 @@ defp do_open(%URI{host: host, port: port} = uri, opts) do host = Pleroma.HTTP.AdapterHelper.parse_host(host) with {:ok, conn} <- Gun.open(host, port, opts), - {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do - {:ok, conn} + {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do + {:ok, conn, protocol} else error -> Logger.warn( diff --git a/lib/pleroma/gun/connection_pool.ex b/lib/pleroma/gun/connection_pool.ex index f34602b73..e322f192a 100644 --- a/lib/pleroma/gun/connection_pool.ex +++ b/lib/pleroma/gun/connection_pool.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Gun.ConnectionPool do @registry __MODULE__ diff --git a/lib/pleroma/gun/connection_pool/reclaimer.ex b/lib/pleroma/gun/connection_pool/reclaimer.ex index cea800882..241e8b04f 100644 --- a/lib/pleroma/gun/connection_pool/reclaimer.ex +++ b/lib/pleroma/gun/connection_pool/reclaimer.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Gun.ConnectionPool.Reclaimer do use GenServer, restart: :temporary diff --git a/lib/pleroma/gun/connection_pool/worker.ex b/lib/pleroma/gun/connection_pool/worker.ex index c36332817..b71816bed 100644 --- a/lib/pleroma/gun/connection_pool/worker.ex +++ b/lib/pleroma/gun/connection_pool/worker.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Gun.ConnectionPool.Worker do alias Pleroma.Gun use GenServer, restart: :temporary @@ -15,7 +19,7 @@ def init([_key, _uri, _opts, _client_pid] = opts) do @impl true def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do - with {:ok, conn_pid} <- Gun.Conn.open(uri, opts), + with {:ok, conn_pid, protocol} <- Gun.Conn.open(uri, opts), Process.link(conn_pid) do time = :erlang.monotonic_time(:millisecond) @@ -27,8 +31,12 @@ def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do send(client_pid, {:conn_pid, conn_pid}) {:noreply, - %{key: key, timer: nil, client_monitors: %{client_pid => Process.monitor(client_pid)}}, - :hibernate} + %{ + key: key, + timer: nil, + client_monitors: %{client_pid => Process.monitor(client_pid)}, + protocol: protocol + }, :hibernate} else err -> {:stop, {:shutdown, err}, nil} @@ -53,14 +61,20 @@ def handle_cast({:remove_client, client_pid}, state) do end @impl true - def handle_call(:add_client, {client_pid, _}, %{key: key} = state) do + def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} = state) do time = :erlang.monotonic_time(:millisecond) - {{conn_pid, _, _, _}, _} = + {{conn_pid, used_by, _, _}, _} = Registry.update_value(@registry, key, fn {conn_pid, used_by, crf, last_reference} -> {conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time} end) + :telemetry.execute( + [:pleroma, :connection_pool, :client, :add], + %{client_pid: client_pid, clients: used_by}, + %{key: state.key, protocol: protocol} + ) + state = if state.timer != nil do Process.cancel_timer(state[:timer]) @@ -83,25 +97,18 @@ def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do end) {ref, state} = pop_in(state.client_monitors[client_pid]) - # DOWN message can receive right after `remove_client` call and cause worker to terminate - state = - if is_nil(ref) do - state + + Process.demonitor(ref, [:flush]) + + timer = + if used_by == [] do + max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) + Process.send_after(self(), :idle_close, max_idle) else - Process.demonitor(ref) - - timer = - if used_by == [] do - max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000) - Process.send_after(self(), :idle_close, max_idle) - else - nil - end - - %{state | timer: timer} + nil end - {:reply, :ok, state, :hibernate} + {:reply, :ok, %{state | timer: timer}, :hibernate} end @impl true @@ -131,7 +138,7 @@ def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams}, state) d @impl true def handle_info({:DOWN, _ref, :process, pid, reason}, state) do :telemetry.execute( - [:pleroma, :connection_pool, :client_death], + [:pleroma, :connection_pool, :client, :dead], %{client_pid: pid, reason: reason}, %{key: state.key} ) diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex index 39615c956..4c23bcbd9 100644 --- a/lib/pleroma/gun/connection_pool/worker_supervisor.ex +++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do @moduledoc "Supervisor for pool workers. Does not do anything except enforce max connection limit" diff --git a/lib/pleroma/helpers/inet_helper.ex b/lib/pleroma/helpers/inet_helper.ex new file mode 100644 index 000000000..126f82381 --- /dev/null +++ b/lib/pleroma/helpers/inet_helper.ex @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.InetHelper do + def parse_address(ip) when is_tuple(ip) do + {:ok, ip} + end + + def parse_address(ip) when is_binary(ip) do + ip + |> String.to_charlist() + |> parse_address() + end + + def parse_address(ip) do + :inet.parse_address(ip) + end +end diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex new file mode 100644 index 000000000..6b799173e --- /dev/null +++ b/lib/pleroma/helpers/media_helper.ex @@ -0,0 +1,162 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.MediaHelper do + @moduledoc """ + Handles common media-related operations. + """ + + alias Pleroma.HTTP + + require Logger + + def missing_dependencies do + Enum.reduce([imagemagick: "convert", ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc -> + if Pleroma.Utils.command_available?(executable) do + acc + else + [sym | acc] + end + end) + end + + def image_resize(url, options) do + with executable when is_binary(executable) <- System.find_executable("convert"), + {:ok, args} <- prepare_image_resize_args(options), + {:ok, env} <- HTTP.get(url, [], pool: :media), + {:ok, fifo_path} <- mkfifo() do + args = List.flatten([fifo_path, args]) + run_fifo(fifo_path, env, executable, args) + else + nil -> {:error, {:convert, :command_not_found}} + {:error, _} = error -> error + end + end + + defp prepare_image_resize_args( + %{max_width: max_width, max_height: max_height, format: "png"} = options + ) do + quality = options[:quality] || 85 + resize = Enum.join([max_width, "x", max_height, ">"]) + + args = [ + "-resize", + resize, + "-quality", + to_string(quality), + "png:-" + ] + + {:ok, args} + end + + defp prepare_image_resize_args(%{max_width: max_width, max_height: max_height} = options) do + quality = options[:quality] || 85 + resize = Enum.join([max_width, "x", max_height, ">"]) + + args = [ + "-interlace", + "Plane", + "-resize", + resize, + "-quality", + to_string(quality), + "jpg:-" + ] + + {:ok, args} + end + + defp prepare_image_resize_args(_), do: {:error, :missing_options} + + # Note: video thumbnail is intentionally not resized (always has original dimensions) + def video_framegrab(url) do + with executable when is_binary(executable) <- System.find_executable("ffmpeg"), + {:ok, env} <- HTTP.get(url, [], pool: :media), + {:ok, fifo_path} <- mkfifo(), + args = [ + "-y", + "-i", + fifo_path, + "-vframes", + "1", + "-f", + "mjpeg", + "-loglevel", + "error", + "-" + ] do + run_fifo(fifo_path, env, executable, args) + else + nil -> {:error, {:ffmpeg, :command_not_found}} + {:error, _} = error -> error + end + end + + defp run_fifo(fifo_path, env, executable, args) do + pid = + Port.open({:spawn_executable, executable}, [ + :use_stdio, + :stream, + :exit_status, + :binary, + args: args + ]) + + fifo = Port.open(to_charlist(fifo_path), [:eof, :binary, :stream, :out]) + fix = Pleroma.Helpers.QtFastStart.fix(env.body) + true = Port.command(fifo, fix) + :erlang.port_close(fifo) + loop_recv(pid) + after + File.rm(fifo_path) + end + + defp mkfifo do + path = Path.join(System.tmp_dir!(), "pleroma-media-preview-pipe-#{Ecto.UUID.generate()}") + + case System.cmd("mkfifo", [path]) do + {_, 0} -> + spawn(fifo_guard(path)) + {:ok, path} + + {_, err} -> + {:error, {:fifo_failed, err}} + end + end + + defp fifo_guard(path) do + pid = self() + + fn -> + ref = Process.monitor(pid) + + receive do + {:DOWN, ^ref, :process, ^pid, _} -> + File.rm(path) + end + end + end + + defp loop_recv(pid) do + loop_recv(pid, <<>>) + end + + defp loop_recv(pid, acc) do + receive do + {^pid, {:data, data}} -> + loop_recv(pid, acc <> data) + + {^pid, {:exit_status, 0}} -> + {:ok, acc} + + {^pid, {:exit_status, status}} -> + {:error, status} + after + 5000 -> + :erlang.port_close(pid) + {:error, :timeout} + end + end +end diff --git a/lib/pleroma/helpers/qt_fast_start.ex b/lib/pleroma/helpers/qt_fast_start.ex new file mode 100644 index 000000000..bb93224b5 --- /dev/null +++ b/lib/pleroma/helpers/qt_fast_start.ex @@ -0,0 +1,131 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.QtFastStart do + @moduledoc """ + (WIP) Converts a "slow start" (data before metadatas) mov/mp4 file to a "fast start" one (metadatas before data). + """ + + # TODO: Cleanup and optimizations + # Inspirations: https://www.ffmpeg.org/doxygen/3.4/qt-faststart_8c_source.html + # https://github.com/danielgtaylor/qtfaststart/blob/master/qtfaststart/processor.py + # ISO/IEC 14496-12:2015, ISO/IEC 15444-12:2015 + # Paracetamol + + def fix(<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::bits>> = binary) do + index = fix(binary, 0, nil, nil, []) + + case index do + :abort -> binary + [{"ftyp", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) + [{"ftyp", _, _, _, _}, {"free", _, _, _, _}, {"mdat", _, _, _, _} | _] -> faststart(index) + _ -> binary + end + end + + def fix(binary) do + binary + end + + # MOOV have been seen before MDAT- abort + defp fix(<<_::bits>>, _, true, false, _) do + :abort + end + + defp fix( + <>, + pos, + got_moov, + got_mdat, + acc + ) do + full_size = (size - 8) * 8 + <> = rest + + acc = [ + {fourcc, pos, pos + size, size, + <>} + | acc + ] + + fix(rest, pos + size, got_moov || fourcc == "moov", got_mdat || fourcc == "mdat", acc) + end + + defp fix(<<>>, _pos, _, _, acc) do + :lists.reverse(acc) + end + + defp faststart(index) do + {{_ftyp, _, _, _, ftyp}, index} = List.keytake(index, "ftyp", 0) + + # Skip re-writing the free fourcc as it's kind of useless. + # Why stream useless bytes when you can do without? + {free_size, index} = + case List.keytake(index, "free", 0) do + {{_, _, _, size, _}, index} -> {size, index} + _ -> {0, index} + end + + {{_moov, _, _, moov_size, moov}, index} = List.keytake(index, "moov", 0) + offset = -free_size + moov_size + rest = for {_, _, _, _, data} <- index, do: data, into: [] + <> = moov + [ftyp, moov_head, fix_moov(moov_data, offset, []), rest] + end + + defp fix_moov( + <>, + offset, + acc + ) do + full_size = (size - 8) * 8 + <> = rest + + data = + cond do + fourcc in ["trak", "mdia", "minf", "stbl"] -> + # Theses contains sto or co64 part + [<>, fix_moov(data, offset, [])] + + fourcc in ["stco", "co64"] -> + # fix the damn thing + <> = data + + entry_size = + case fourcc do + "stco" -> 32 + "co64" -> 64 + end + + [ + <>, + rewrite_entries(entry_size, offset, rest, []) + ] + + true -> + [<>, data] + end + + acc = [acc | data] + fix_moov(rest, offset, acc) + end + + defp fix_moov(<<>>, _, acc), do: acc + + for size <- [32, 64] do + defp rewrite_entries( + unquote(size), + offset, + <>, + acc + ) do + rewrite_entries(unquote(size), offset, rest, [ + acc | <> + ]) + end + end + + defp rewrite_entries(_, _, <<>>, acc), do: acc +end diff --git a/lib/pleroma/helpers/uri_helper.ex b/lib/pleroma/helpers/uri_helper.ex index 6d205a636..f1301f055 100644 --- a/lib/pleroma/helpers/uri_helper.ex +++ b/lib/pleroma/helpers/uri_helper.ex @@ -3,18 +3,22 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Helpers.UriHelper do - def append_uri_params(uri, appended_params) do + def modify_uri_params(uri, overridden_params, deleted_params \\ []) do uri = URI.parse(uri) - appended_params = for {k, v} <- appended_params, into: %{}, do: {to_string(k), v} - existing_params = URI.query_decoder(uri.query || "") |> Enum.into(%{}) - updated_params_keys = Enum.uniq(Map.keys(existing_params) ++ Map.keys(appended_params)) + + existing_params = URI.query_decoder(uri.query || "") |> Map.new() + overridden_params = Map.new(overridden_params, fn {k, v} -> {to_string(k), v} end) + deleted_params = Enum.map(deleted_params, &to_string/1) updated_params = - for k <- updated_params_keys, do: {k, appended_params[k] || existing_params[k]} + existing_params + |> Map.merge(overridden_params) + |> Map.drop(deleted_params) uri |> Map.put(:query, URI.encode_query(updated_params)) |> URI.to_string() + |> String.replace_suffix("?", "") end def maybe_add_base("/" <> uri, base), do: Path.join([base, uri]) diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http.ex similarity index 78% rename from lib/pleroma/http/http.ex rename to lib/pleroma/http.ex index b37b3fa89..052597191 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http.ex @@ -60,30 +60,23 @@ def post(url, body, headers \\ [], options \\ []), {:ok, Env.t()} | {:error, any()} def request(method, url, body, headers, options) when is_binary(url) do uri = URI.parse(url) - adapter_opts = AdapterHelper.options(uri, options[:adapter] || []) + adapter_opts = AdapterHelper.options(uri, options || []) - case AdapterHelper.get_conn(uri, adapter_opts) do - {:ok, adapter_opts} -> - options = put_in(options[:adapter], adapter_opts) - params = options[:params] || [] - request = build_request(method, headers, options, url, body, params) + options = put_in(options[:adapter], adapter_opts) + params = options[:params] || [] + request = build_request(method, headers, options, url, body, params) - adapter = Application.get_env(:tesla, :adapter) + adapter = Application.get_env(:tesla, :adapter) - client = Tesla.client(adapter_middlewares(adapter), adapter) + client = Tesla.client(adapter_middlewares(adapter), adapter) - maybe_limit( - fn -> - request(client, request) - end, - adapter, - adapter_opts - ) - - # Connection release is handled in a custom FollowRedirects middleware - err -> - err - end + maybe_limit( + fn -> + request(client, request) + end, + adapter, + adapter_opts + ) end @spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()} @@ -110,7 +103,7 @@ defp maybe_limit(fun, _, _) do end defp adapter_middlewares(Tesla.Adapter.Gun) do - [Pleroma.HTTP.Middleware.FollowRedirects] + [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] end defp adapter_middlewares(_), do: [] diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index 0728cbaa2..08b51578a 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -6,7 +6,7 @@ defmodule Pleroma.HTTP.AdapterHelper do @moduledoc """ Configure Tesla.Client with default and customized adapter options. """ - @defaults [pool: :federation] + @defaults [pool: :federation, connect_timeout: 5_000, recv_timeout: 5_000] @type proxy_type() :: :socks4 | :socks5 @type host() :: charlist() | :inet.ip_address() @@ -19,7 +19,6 @@ defmodule Pleroma.HTTP.AdapterHelper do | {Connection.proxy_type(), Connection.host(), pos_integer()} @callback options(keyword(), URI.t()) :: keyword() - @callback get_conn(URI.t(), keyword()) :: {:ok, term()} | {:error, term()} @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil def format_proxy(nil), do: nil @@ -47,9 +46,6 @@ def options(%URI{} = uri, opts \\ []) do |> adapter_helper().options(uri) end - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} - def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts) - defp adapter, do: Application.get_env(:tesla, :adapter) defp adapter_helper do diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex index e13441316..8567a616b 100644 --- a/lib/pleroma/http/adapter_helper/default.ex +++ b/lib/pleroma/http/adapter_helper/default.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.HTTP.AdapterHelper.Default do alias Pleroma.HTTP.AdapterHelper diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index 02e20f2d1..1dbb71362 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -6,18 +6,13 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do @behaviour Pleroma.HTTP.AdapterHelper alias Pleroma.Config - alias Pleroma.Gun.ConnectionPool alias Pleroma.HTTP.AdapterHelper require Logger @defaults [ - connect_timeout: 5_000, - domain_lookup_timeout: 5_000, - tls_handshake_timeout: 5_000, retry: 1, - retry_timeout: 1000, - await_up_timeout: 5_000 + retry_timeout: 1_000 ] @type pool() :: :federation | :upload | :media | :default @@ -46,23 +41,17 @@ defp add_scheme_opts(opts, %{scheme: "https"}) do end defp put_timeout(opts) do + {recv_timeout, opts} = Keyword.pop(opts, :recv_timeout, pool_timeout(opts[:pool])) # this is the timeout to receive a message from Gun - Keyword.put_new(opts, :timeout, pool_timeout(opts[:pool])) + # `:timeout` key is used in Tesla + Keyword.put(opts, :timeout, recv_timeout) end @spec pool_timeout(pool()) :: non_neg_integer() def pool_timeout(pool) do - default = Config.get([:pools, :default, :timeout], 5_000) + default = Config.get([:pools, :default, :recv_timeout], 5_000) - Config.get([:pools, pool, :timeout], default) - end - - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | {:error, atom()} - def get_conn(uri, opts) do - case ConnectionPool.get_conn(uri, opts) do - {:ok, conn_pid} -> {:ok, Keyword.merge(opts, conn: conn_pid, close_conn: false)} - err -> err - end + Config.get([:pools, pool, :recv_timeout], default) end @prefix Pleroma.Gun.ConnectionPool diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex index 62bd42485..ff60513fd 100644 --- a/lib/pleroma/http/adapter_helper/hackney.ex +++ b/lib/pleroma/http/adapter_helper/hackney.ex @@ -1,12 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.HTTP.AdapterHelper.Hackney do @behaviour Pleroma.HTTP.AdapterHelper @defaults [ - connect_timeout: 10_000, - recv_timeout: 20_000, follow_redirect: true, - force_redirect: true, - pool: :federation + force_redirect: true ] @spec options(keyword(), URI.t()) :: keyword() @@ -19,6 +20,7 @@ def options(connection_opts \\ [], %URI{} = uri) do |> Keyword.merge(config_opts) |> Keyword.merge(connection_opts) |> add_scheme_opts(uri) + |> maybe_add_with_body() |> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy) end @@ -28,6 +30,11 @@ defp add_scheme_opts(opts, %URI{scheme: "https"}) do defp add_scheme_opts(opts, _), do: opts - @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} - def get_conn(_uri, opts), do: {:ok, opts} + defp maybe_add_with_body(opts) do + if opts[:max_body] do + Keyword.put(opts, :with_body, true) + else + opts + end + end end diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex index c3f335c73..5cac3532f 100644 --- a/lib/pleroma/http/ex_aws.ex +++ b/lib/pleroma/http/ex_aws.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.ExAws do @impl true def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do - http_opts = Keyword.put_new(http_opts, :adapter, pool: :upload) + http_opts = Keyword.put_new(http_opts, :pool, :upload) case HTTP.request(method, url, body, headers, http_opts) do {:ok, env} -> diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex index 4539ac359..09cfdadf7 100644 --- a/lib/pleroma/http/tzdata.ex +++ b/lib/pleroma/http/tzdata.ex @@ -11,7 +11,7 @@ defmodule Pleroma.HTTP.Tzdata do @impl true def get(url, headers, options) do - options = Keyword.put_new(options, :adapter, pool: :default) + options = Keyword.put_new(options, :pool, :default) with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do {:ok, {env.status, env.headers, env.body}} @@ -20,7 +20,7 @@ def get(url, headers, options) do @impl true def head(url, headers, options) do - options = Keyword.put_new(options, :adapter, pool: :default) + options = Keyword.put_new(options, :pool, :default) with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do {:ok, {env.status, env.headers}} diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex new file mode 100644 index 000000000..78148a12e --- /dev/null +++ b/lib/pleroma/http/web_push.ex @@ -0,0 +1,12 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.WebPush do + @moduledoc false + + def post(url, payload, headers) do + list_headers = Map.to_list(headers) + Pleroma.HTTP.post(url, payload, list_headers) + end +end diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 8bf53c090..f0f601469 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -156,16 +156,12 @@ def get_or_update_favicon(%URI{host: host} = instance_uri) do defp scrape_favicon(%URI{} = instance_uri) do try do with {:ok, %Tesla.Env{body: html}} <- - Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], - adapter: [pool: :media] - ), - favicon_rel <- - html - |> Floki.parse_document!() - |> Floki.attribute("link[rel=icon]", "href") - |> List.first(), - favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(), - true <- is_binary(favicon) do + Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], pool: :media), + {_, [favicon_rel | _]} when is_binary(favicon_rel) <- + {:parse, + html |> Floki.parse_document!() |> Floki.attribute("link[rel=icon]", "href")}, + {_, favicon} when is_binary(favicon) <- + {:merge, URI.merge(instance_uri, favicon_rel) |> to_string()} do favicon else _ -> nil diff --git a/lib/pleroma/jwt.ex b/lib/pleroma/jwt.ex index 10102ff5d..faeb77781 100644 --- a/lib/pleroma/jwt.ex +++ b/lib/pleroma/jwt.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.JWT do use Joken.Config diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex index 0b2449971..69b64c0e8 100644 --- a/lib/pleroma/mfa/token.ex +++ b/lib/pleroma/mfa/token.ex @@ -10,10 +10,11 @@ defmodule Pleroma.MFA.Token do alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.Authorization - alias Pleroma.Web.OAuth.Token, as: OAuthToken @expires 300 + @type t() :: %__MODULE__{} + schema "mfa_tokens" do field(:token, :string) field(:valid_until, :naive_datetime_usec) @@ -24,6 +25,7 @@ defmodule Pleroma.MFA.Token do timestamps() end + @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(token) do from( t in __MODULE__, @@ -33,33 +35,40 @@ def get_by_token(token) do |> Repo.find_resource() end - def validate(token) do - with {:fetch_token, {:ok, token}} <- {:fetch_token, get_by_token(token)}, - {:expired, false} <- {:expired, is_expired?(token)} do + @spec validate(String.t()) :: {:ok, t()} | {:error, :not_found} | {:error, :expired_token} + def validate(token_str) do + with {:ok, token} <- get_by_token(token_str), + false <- expired?(token) do {:ok, token} - else - {:expired, _} -> {:error, :expired_token} - {:fetch_token, _} -> {:error, :not_found} - error -> {:error, error} end end - def create_token(%User{} = user) do - %__MODULE__{} - |> change - |> assign_user(user) - |> put_token - |> put_valid_until - |> Repo.insert() + defp expired?(%__MODULE__{valid_until: valid_until}) do + with true <- NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 do + {:error, :expired_token} + end end - def create_token(user, authorization) do + @spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()} + def create(user, authorization \\ nil) do + with {:ok, token} <- do_create(user, authorization) do + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: __MODULE__ + }) + + {:ok, token} + end + end + + defp do_create(user, authorization) do %__MODULE__{} - |> change + |> change() |> assign_user(user) - |> assign_authorization(authorization) - |> put_token - |> put_valid_until + |> maybe_assign_authorization(authorization) + |> put_token() + |> put_valid_until() |> Repo.insert() end @@ -69,15 +78,19 @@ defp assign_user(changeset, user) do |> validate_required([:user]) end - defp assign_authorization(changeset, authorization) do + defp maybe_assign_authorization(changeset, %Authorization{} = authorization) do changeset |> put_assoc(:authorization, authorization) |> validate_required([:authorization]) end + defp maybe_assign_authorization(changeset, _), do: changeset + defp put_token(changeset) do + token = Pleroma.Web.OAuth.Token.Utils.generate_token() + changeset - |> change(%{token: OAuthToken.Utils.generate_token()}) + |> change(%{token: token}) |> validate_required([:token]) |> unique_constraint(:token) end @@ -89,18 +102,4 @@ defp put_valid_until(changeset) do |> change(%{valid_until: expires_in}) |> validate_required([:valid_until]) end - - def is_expired?(%__MODULE__{valid_until: valid_until}) do - NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 - end - - def is_expired?(_), do: false - - def delete_expired_tokens do - from( - q in __MODULE__, - where: fragment("?", q.valid_until) < ^Timex.now() - ) - |> Repo.delete_all() - end end diff --git a/lib/pleroma/migration_helper/notification_backfill.ex b/lib/pleroma/migration_helper/notification_backfill.ex index d260e62ca..24f4733fe 100644 --- a/lib/pleroma/migration_helper/notification_backfill.ex +++ b/lib/pleroma/migration_helper/notification_backfill.ex @@ -19,13 +19,13 @@ def fill_in_notification_types do query |> Repo.chunk_stream(100) |> Enum.each(fn notification -> - type = - notification.activity - |> type_from_activity() + if notification.activity do + type = type_from_activity(notification.activity) - notification - |> Ecto.Changeset.change(%{type: type}) - |> Repo.update() + notification + |> Ecto.Changeset.change(%{type: type}) + |> Repo.update() + end end) end @@ -72,8 +72,7 @@ defp type_from_activity(%{data: %{"type" => type}} = activity) do "pleroma:emoji_reaction" "Create" -> - activity - |> type_from_activity_object() + type_from_activity_object(activity) t -> raise "No notification type for activity type #{t}" diff --git a/lib/pleroma/mime.ex b/lib/pleroma/mime.ex deleted file mode 100644 index 6ee055f50..000000000 --- a/lib/pleroma/mime.ex +++ /dev/null @@ -1,120 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.MIME do - @moduledoc """ - Returns the mime-type of a binary and optionally a normalized file-name. - """ - @default "application/octet-stream" - @read_bytes 35 - - @spec file_mime_type(String.t(), String.t()) :: - {:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error - def file_mime_type(path, filename) do - with {:ok, content_type} <- file_mime_type(path), - filename <- fix_extension(filename, content_type) do - {:ok, content_type, filename} - end - end - - @spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error - def file_mime_type(filename) do - File.open(filename, [:read], fn f -> - check_mime_type(IO.binread(f, @read_bytes)) - end) - end - - def bin_mime_type(binary, filename) do - with {:ok, content_type} <- bin_mime_type(binary), - filename <- fix_extension(filename, content_type) do - {:ok, content_type, filename} - end - end - - @spec bin_mime_type(binary()) :: {:ok, String.t()} | :error - def bin_mime_type(<>) do - {:ok, check_mime_type(head)} - end - - def bin_mime_type(_), do: :error - - def mime_type(<<_::binary>>), do: {:ok, @default} - - defp fix_extension(filename, content_type) do - parts = String.split(filename, ".") - - new_filename = - if length(parts) > 1 do - Enum.drop(parts, -1) |> Enum.join(".") - else - Enum.join(parts) - end - - cond do - content_type == "application/octet-stream" -> - filename - - ext = List.first(MIME.extensions(content_type)) -> - new_filename <> "." <> ext - - true -> - Enum.join([new_filename, String.split(content_type, "/") |> List.last()], ".") - end - end - - defp check_mime_type(<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _::binary>>) do - "image/png" - end - - defp check_mime_type(<<0x47, 0x49, 0x46, 0x38, _, 0x61, _::binary>>) do - "image/gif" - end - - defp check_mime_type(<<0xFF, 0xD8, 0xFF, _::binary>>) do - "image/jpeg" - end - - defp check_mime_type(<<0x1A, 0x45, 0xDF, 0xA3, _::binary>>) do - "video/webm" - end - - defp check_mime_type(<<0x00, 0x00, 0x00, _, 0x66, 0x74, 0x79, 0x70, _::binary>>) do - "video/mp4" - end - - defp check_mime_type(<<0x49, 0x44, 0x33, _::binary>>) do - "audio/mpeg" - end - - defp check_mime_type(<<255, 251, _, 68, 0, 0, 0, 0, _::binary>>) do - "audio/mpeg" - end - - defp check_mime_type( - <<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::size(160), 0x80, 0x74, 0x68, 0x65, - 0x6F, 0x72, 0x61, _::binary>> - ) do - "video/ogg" - end - - defp check_mime_type(<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, _::binary>>) do - "audio/ogg" - end - - defp check_mime_type(<<"RIFF", _::binary-size(4), "WAVE", _::binary>>) do - "audio/wav" - end - - defp check_mime_type(<<"RIFF", _::binary-size(4), "WEBP", _::binary>>) do - "image/webp" - end - - defp check_mime_type(<<"RIFF", _::binary-size(4), "AVI.", _::binary>>) do - "video/avi" - end - - defp check_mime_type(_) do - @default - end -end diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 31c9afe2a..38a863443 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.ModerationLog do use Ecto.Schema @@ -320,6 +324,19 @@ def insert_log(%{ |> insert_log_entry_with_message() end + @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) :: + {:ok, ModerationLog} | {:error, any} + def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do + %ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor.nickname}, + "action" => "chat_message_delete", + "subject_id" => subject_id + } + } + |> insert_log_entry_with_message() + end + @spec insert_log_entry_with_message(ModerationLog) :: {:ok, ModerationLog} | {:error, any} defp insert_log_entry_with_message(entry) do entry.data["message"] @@ -627,6 +644,17 @@ def get_log_entry_message(%ModerationLog{ "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}" end + @spec get_log_entry_message(ModerationLog) :: String.t() + def get_log_entry_message(%ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "chat_message_delete", + "subject_id" => subject_id + } + }) do + "@#{actor_nickname} deleted chat message ##{subject_id}" + end + defp nicknames_to_string(nicknames) do nicknames |> Enum.map(&"@#{&1}") diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index c1825f810..8868a910e 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -648,4 +648,16 @@ def for_user_and_activity(user, activity) do ) |> Repo.one() end + + @spec mark_context_as_read(User.t(), String.t()) :: {integer(), nil | [term()]} + def mark_context_as_read(%User{id: id}, context) do + from( + n in Notification, + join: a in assoc(n, :activity), + where: n.user_id == ^id, + where: n.seen == false, + where: fragment("?->>'context'", a.data) == ^context + ) + |> Repo.update_all(set: [seen: true]) + end end diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index bc88e8a0c..29cb3d672 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -44,13 +44,6 @@ def get_object(_) do nil end - # TODO: We explicitly allow 'tag' URIs through, due to references to legacy OStatus - # objects being present in the test suite environment. Once these objects are - # removed, please also remove this. - if Mix.env() == :test do - defp compare_uris(_, %URI{scheme: "tag"}), do: :ok - end - defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok defp compare_uris(_id_uri, _other_uri), do: :error diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 374d8704a..ae4301738 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator + alias Pleroma.Web.FedSockets require Logger require Pleroma.Constants @@ -36,8 +37,7 @@ defp maybe_reinject_internal_fields(_, new_data), do: new_data defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do Logger.debug("Reinjecting object #{new_data["id"]}") - with new_data <- Transmogrifier.fix_object(new_data), - data <- maybe_reinject_internal_fields(object, new_data), + with data <- maybe_reinject_internal_fields(object, new_data), {:ok, data, _} <- ObjectValidator.validate(data, %{}), changeset <- Object.change(object, %{data: data}), changeset <- touch_changeset(changeset), @@ -99,8 +99,8 @@ def fetch_object_from_id(id, options \\ []) do {:containment, _} -> {:error, "Object containment failed."} - {:transmogrifier, {:error, {:reject, nil}}} -> - {:reject, nil} + {:transmogrifier, {:error, {:reject, e}}} -> + {:reject, e} {:transmogrifier, _} = e -> {:error, e} @@ -183,27 +183,20 @@ defp maybe_date_fetch(headers, date) do end end - def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do + def fetch_and_contain_remote_object_from_id(prm, opts \\ []) + + def fetch_and_contain_remote_object_from_id(%{"id" => id}, opts), + do: fetch_and_contain_remote_object_from_id(id, opts) + + def fetch_and_contain_remote_object_from_id(id, opts) when is_binary(id) do Logger.debug("Fetching object #{id} via AP") - date = Pleroma.Signature.signed_date() - - headers = - [{"accept", "application/activity+json"}] - |> maybe_date_fetch(date) - |> sign_fetch(id, date) - - Logger.debug("Fetch headers: #{inspect(headers)}") - with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, - {:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers), - {:ok, data} <- Jason.decode(body), + {:ok, body} <- get_object(id, opts), + {:ok, data} <- safe_json_decode(body), :ok <- Containment.contain_origin_from_id(id, data) do {:ok, data} else - {:ok, %{status: code}} when code in [404, 410] -> - {:error, "Object has been deleted"} - {:scheme, _} -> {:error, "Unsupported URI scheme"} @@ -215,8 +208,60 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do end end - def fetch_and_contain_remote_object_from_id(%{"id" => id}), - do: fetch_and_contain_remote_object_from_id(id) + def fetch_and_contain_remote_object_from_id(_id, _opts), + do: {:error, "id must be a string"} - def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"} + defp get_object(id, opts) do + with false <- Keyword.get(opts, :force_http, false), + {:ok, fedsocket} <- FedSockets.get_or_create_fed_socket(id) do + Logger.debug("fetching via fedsocket - #{inspect(id)}") + FedSockets.fetch(fedsocket, id) + else + _other -> + Logger.debug("fetching via http - #{inspect(id)}") + get_object_http(id) + end + end + + defp get_object_http(id) do + date = Pleroma.Signature.signed_date() + + headers = + [{"accept", "application/activity+json"}] + |> maybe_date_fetch(date) + |> sign_fetch(id, date) + + case HTTP.get(id, headers) do + {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 -> + case List.keyfind(headers, "content-type", 0) do + {_, content_type} -> + case Plug.Conn.Utils.media_type(content_type) do + {:ok, "application", "activity+json", _} -> + {:ok, body} + + {:ok, "application", "ld+json", + %{"profile" => "https://www.w3.org/ns/activitystreams"}} -> + {:ok, body} + + _ -> + {:error, {:content_type, content_type}} + end + + _ -> + {:error, {:content_type, nil}} + end + + {:ok, %{status: code}} when code in [404, 410] -> + {:error, "Object has been deleted"} + + {:error, e} -> + {:error, e} + + e -> + {:error, e} + end + end + + defp safe_json_decode(nil), do: {:ok, nil} + defp safe_json_decode(json), do: Jason.decode(json) end diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex deleted file mode 100644 index 0ac9050d0..000000000 --- a/lib/pleroma/plugs/remote_ip.ex +++ /dev/null @@ -1,54 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Plugs.RemoteIp do - @moduledoc """ - This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. - """ - - import Plug.Conn - - @behaviour Plug - - @headers ~w[ - x-forwarded-for - ] - - # https://en.wikipedia.org/wiki/Localhost - # https://en.wikipedia.org/wiki/Private_network - @reserved ~w[ - 127.0.0.0/8 - ::1/128 - fc00::/7 - 10.0.0.0/8 - 172.16.0.0/12 - 192.168.0.0/16 - ] - - def init(_), do: nil - - def call(%{remote_ip: original_remote_ip} = conn, _) do - config = Pleroma.Config.get(__MODULE__, []) - - if Keyword.get(config, :enabled, false) do - %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config)) - assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) - else - conn - end - end - - defp remote_ip_opts(config) do - headers = config |> Keyword.get(:headers, @headers) |> MapSet.new() - reserved = Keyword.get(config, :reserved, @reserved) - - proxies = - config - |> Keyword.get(:proxies, []) - |> Enum.concat(reserved) - |> Enum.map(&InetCidr.parse/1) - - {headers, proxies} - end -end diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex index f317e4d58..4524bd5e2 100644 --- a/lib/pleroma/repo.ex +++ b/lib/pleroma/repo.ex @@ -49,7 +49,21 @@ def get_assoc(resource, association) do end end - def chunk_stream(query, chunk_size) do + @doc """ + Returns a lazy enumerable that emits all entries from the data store matching the given query. + + `returns_as` use to group records. use the `batches` option to fetch records in bulk. + + ## Examples + + # fetch records one-by-one + iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500) + + # fetch records in bulk + iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches) + """ + @spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t() + def chunk_stream(query, chunk_size, returns_as \\ :one) do # We don't actually need start and end funcitons of resource streaming, # but it seems to be the only way to not fetch records one-by-one and # have individual records be the elements of the stream, instead of @@ -69,7 +83,12 @@ def chunk_stream(query, chunk_size) do records -> last_id = List.last(records).id - {records, last_id} + + if returns_as == :one do + {records, last_id} + else + {[records], last_id} + end end end, fn _ -> :ok end diff --git a/lib/pleroma/repo_streamer.ex b/lib/pleroma/repo_streamer.ex deleted file mode 100644 index cb4d7bb7a..000000000 --- a/lib/pleroma/repo_streamer.ex +++ /dev/null @@ -1,34 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.RepoStreamer do - alias Pleroma.Repo - import Ecto.Query - - def chunk_stream(query, chunk_size) do - Stream.unfold(0, fn - :halt -> - {[], :halt} - - last_id -> - query - |> order_by(asc: :id) - |> where([r], r.id > ^last_id) - |> limit(^chunk_size) - |> Repo.all() - |> case do - [] -> - {[], :halt} - - records -> - last_id = List.last(records).id - {records, last_id} - end - end) - |> Stream.take_while(fn - [] -> false - _ -> true - end) - end -end diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex similarity index 98% rename from lib/pleroma/reverse_proxy/reverse_proxy.ex rename to lib/pleroma/reverse_proxy.ex index 0de4e2309..8ae1157df 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -17,6 +17,9 @@ defmodule Pleroma.ReverseProxy do @failed_request_ttl :timer.seconds(60) @methods ~w(GET HEAD) + def max_read_duration_default, do: @max_read_duration + def default_cache_control_header, do: @default_cache_control_header + @moduledoc """ A reverse proxy. @@ -391,6 +394,8 @@ defp body_size_constraint(size, limit) when is_integer(limit) and limit > 0 and defp body_size_constraint(_, _), do: :ok + defp check_read_duration(nil = _duration, max), do: check_read_duration(@max_read_duration, max) + defp check_read_duration(duration, max) when is_integer(duration) and is_integer(max) and max > 0 do if duration > max do diff --git a/lib/pleroma/reverse_proxy/client/tesla.ex b/lib/pleroma/reverse_proxy/client/tesla.ex index d5a339681..4b118eec2 100644 --- a/lib/pleroma/reverse_proxy/client/tesla.ex +++ b/lib/pleroma/reverse_proxy/client/tesla.ex @@ -28,7 +28,7 @@ def request(method, url, headers, body, opts \\ []) do url, body, headers, - Keyword.put(opts, :adapter, opts) + opts ) do if is_map(response.body) and method != :head do {:ok, response.status, response.headers, response.body} diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 3aa6909d2..e388993b7 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -39,7 +39,7 @@ def key_id_to_actor_id(key_id) do def fetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), {:ok, actor_id} <- key_id_to_actor_id(kid), - {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do + {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do {:ok, public_key} else e -> @@ -50,8 +50,8 @@ def fetch_public_key(conn) do def refetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), {:ok, actor_id} <- key_id_to_actor_id(kid), - {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), - {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do + {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id, force_http: true), + {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id, force_http: true) do {:ok, public_key} else e -> diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index 9a03f01db..e5c9c668b 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -3,12 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Stats do + use GenServer + import Ecto.Query + alias Pleroma.CounterCache alias Pleroma.Repo alias Pleroma.User - use GenServer + @interval :timer.seconds(60) def start_link(_) do GenServer.start_link( @@ -18,6 +21,12 @@ def start_link(_) do ) end + @impl true + def init(_args) do + if Pleroma.Config.get(:env) == :test, do: :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo) + {:ok, nil, {:continue, :calculate_stats}} + end + @doc "Performs update stats" def force_update do GenServer.call(__MODULE__, :force_update) @@ -29,7 +38,11 @@ def do_collect do end @doc "Returns stats data" - @spec get_stats() :: %{domain_count: integer(), status_count: integer(), user_count: integer()} + @spec get_stats() :: %{ + domain_count: non_neg_integer(), + status_count: non_neg_integer(), + user_count: non_neg_integer() + } def get_stats do %{stats: stats} = GenServer.call(__MODULE__, :get_state) @@ -44,25 +57,14 @@ def get_peers do peers end - def init(_args) do - {:ok, calculate_stat_data()} - end - - def handle_call(:force_update, _from, _state) do - new_stats = calculate_stat_data() - {:reply, new_stats, new_stats} - end - - def handle_call(:get_state, _from, state) do - {:reply, state, state} - end - - def handle_cast(:run_update, _state) do - new_stats = calculate_stat_data() - - {:noreply, new_stats} - end - + @spec calculate_stat_data() :: %{ + peers: list(), + stats: %{ + domain_count: non_neg_integer(), + status_count: non_neg_integer(), + user_count: non_neg_integer() + } + } def calculate_stat_data do peers = from( @@ -97,6 +99,7 @@ def calculate_stat_data do } end + @spec get_status_visibility_count(String.t() | nil) :: map() def get_status_visibility_count(instance \\ nil) do if is_nil(instance) do CounterCache.get_sum() @@ -104,4 +107,36 @@ def get_status_visibility_count(instance \\ nil) do CounterCache.get_by_instance(instance) end end + + @impl true + def handle_continue(:calculate_stats, _) do + stats = calculate_stat_data() + Process.send_after(self(), :run_update, @interval) + {:noreply, stats} + end + + @impl true + def handle_call(:force_update, _from, _state) do + new_stats = calculate_stat_data() + {:reply, new_stats, new_stats} + end + + @impl true + def handle_call(:get_state, _from, state) do + {:reply, state, state} + end + + @impl true + def handle_cast(:run_update, _state) do + new_stats = calculate_stat_data() + + {:noreply, new_stats} + end + + @impl true + def handle_info(:run_update, _) do + new_stats = calculate_stat_data() + Process.send_after(self(), :run_update, @interval) + {:noreply, new_stats} + end end diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 4cacae02f..003079cf3 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Telemetry.Logger do @moduledoc "Transforms Pleroma telemetry events to logs" @@ -7,7 +11,8 @@ defmodule Pleroma.Telemetry.Logger do [:pleroma, :connection_pool, :reclaim, :start], [:pleroma, :connection_pool, :reclaim, :stop], [:pleroma, :connection_pool, :provision_failure], - [:pleroma, :connection_pool, :client_death] + [:pleroma, :connection_pool, :client, :dead], + [:pleroma, :connection_pool, :client, :add] ] def attach do :telemetry.attach_many("pleroma-logger", @events, &handle_event/4, []) @@ -62,7 +67,7 @@ def handle_event( end def handle_event( - [:pleroma, :connection_pool, :client_death], + [:pleroma, :connection_pool, :client, :dead], %{client_pid: client_pid, reason: reason}, %{key: key}, _ @@ -73,4 +78,17 @@ def handle_event( }" end) end + + def handle_event( + [:pleroma, :connection_pool, :client, :add], + %{clients: [_, _ | _] = clients}, + %{key: key, protocol: :http}, + _ + ) do + Logger.info(fn -> + "Pool worker for #{key}: #{length(clients)} clients are using an HTTP1 connection at the same time, head-of-line blocking might occur." + end) + end + + def handle_event([:pleroma, :connection_pool, :client, :add], _, _, _), do: :ok end diff --git a/lib/pleroma/tesla/middleware/connection_pool.ex b/lib/pleroma/tesla/middleware/connection_pool.ex new file mode 100644 index 000000000..056e736ce --- /dev/null +++ b/lib/pleroma/tesla/middleware/connection_pool.ex @@ -0,0 +1,50 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Tesla.Middleware.ConnectionPool do + @moduledoc """ + Middleware to get/release connections from `Pleroma.Gun.ConnectionPool` + """ + + @behaviour Tesla.Middleware + + alias Pleroma.Gun.ConnectionPool + + @impl Tesla.Middleware + def call(%Tesla.Env{url: url, opts: opts} = env, next, _) do + uri = URI.parse(url) + + # Avoid leaking connections when the middleware is called twice + # with body_as: :chunks. We assume only the middleware can set + # opts[:adapter][:conn] + if opts[:adapter][:conn] do + ConnectionPool.release_conn(opts[:adapter][:conn]) + end + + case ConnectionPool.get_conn(uri, opts[:adapter]) do + {:ok, conn_pid} -> + adapter_opts = Keyword.merge(opts[:adapter], conn: conn_pid, close_conn: false) + opts = Keyword.put(opts, :adapter, adapter_opts) + env = %{env | opts: opts} + + case Tesla.run(env, next) do + {:ok, env} -> + unless opts[:adapter][:body_as] == :chunks do + ConnectionPool.release_conn(conn_pid) + {_, res} = pop_in(env.opts[:adapter][:conn]) + {:ok, res} + else + {:ok, env} + end + + err -> + ConnectionPool.release_conn(conn_pid) + err + end + + err -> + err + end + end +end diff --git a/lib/pleroma/tesla/middleware/follow_redirects.ex b/lib/pleroma/tesla/middleware/follow_redirects.ex deleted file mode 100644 index 5a7032215..000000000 --- a/lib/pleroma/tesla/middleware/follow_redirects.ex +++ /dev/null @@ -1,110 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2015-2020 Tymon Tobolski -# Copyright © 2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.HTTP.Middleware.FollowRedirects do - @moduledoc """ - Pool-aware version of https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex - - Follow 3xx redirects - ## Options - - `:max_redirects` - limit number of redirects (default: `5`) - """ - - alias Pleroma.Gun.ConnectionPool - - @behaviour Tesla.Middleware - - @max_redirects 5 - @redirect_statuses [301, 302, 303, 307, 308] - - @impl Tesla.Middleware - def call(env, next, opts \\ []) do - max = Keyword.get(opts, :max_redirects, @max_redirects) - - redirect(env, next, max) - end - - defp redirect(env, next, left) do - opts = env.opts[:adapter] - - case Tesla.run(env, next) do - {:ok, %{status: status} = res} when status in @redirect_statuses and left > 0 -> - release_conn(opts) - - case Tesla.get_header(res, "location") do - nil -> - {:ok, res} - - location -> - location = parse_location(location, res) - - case get_conn(location, opts) do - {:ok, opts} -> - %{env | opts: Keyword.put(env.opts, :adapter, opts)} - |> new_request(res.status, location) - |> redirect(next, left - 1) - - e -> - e - end - end - - {:ok, %{status: status}} when status in @redirect_statuses -> - release_conn(opts) - {:error, {__MODULE__, :too_many_redirects}} - - {:error, _} = e -> - release_conn(opts) - e - - other -> - unless opts[:body_as] == :chunks do - release_conn(opts) - end - - other - end - end - - defp get_conn(location, opts) do - uri = URI.parse(location) - - case ConnectionPool.get_conn(uri, opts) do - {:ok, conn} -> - {:ok, Keyword.merge(opts, conn: conn)} - - e -> - e - end - end - - defp release_conn(opts) do - ConnectionPool.release_conn(opts[:conn]) - end - - # The 303 (See Other) redirect was added in HTTP/1.1 to indicate that the originally - # requested resource is not available, however a related resource (or another redirect) - # available via GET is available at the specified location. - # https://tools.ietf.org/html/rfc7231#section-6.4.4 - defp new_request(env, 303, location), do: %{env | url: location, method: :get, query: []} - - # The 307 (Temporary Redirect) status code indicates that the target - # resource resides temporarily under a different URI and the user agent - # MUST NOT change the request method (...) - # https://tools.ietf.org/html/rfc7231#section-6.4.7 - defp new_request(env, 307, location), do: %{env | url: location} - - defp new_request(env, _, location), do: %{env | url: location, query: []} - - defp parse_location("https://" <> _rest = location, _env), do: location - defp parse_location("http://" <> _rest = location, _env), do: location - - defp parse_location(location, env) do - env.url - |> URI.parse() - |> URI.merge(location) - |> URI.to_string() - end -end diff --git a/lib/pleroma/tests/auth_test_controller.ex b/lib/pleroma/tests/auth_test_controller.ex index fb04411d9..b30d83567 100644 --- a/lib/pleroma/tests/auth_test_controller.ex +++ b/lib/pleroma/tests/auth_test_controller.ex @@ -8,9 +8,9 @@ defmodule Pleroma.Tests.AuthTestController do use Pleroma.Web, :controller - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug # Serves only with proper OAuth token (:api and :authenticated_api) # Skipping EnsurePublicOrAuthenticatedPlug has no effect in this case diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 015c87593..db2cc1dae 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -66,6 +66,7 @@ defp get_description(opts, upload) do end @spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()} + @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct." def store(upload, opts \\ []) do opts = get_opts(opts) @@ -139,14 +140,13 @@ defp get_opts(opts) do end defp prepare_upload(%Plug.Upload{} = file, opts) do - with :ok <- check_file_size(file.path, opts.size_limit), - {:ok, content_type, name} <- Pleroma.MIME.file_mime_type(file.path, file.filename) do + with :ok <- check_file_size(file.path, opts.size_limit) do {:ok, %__MODULE__{ id: UUID.generate(), - name: name, + name: file.filename, tempfile: file.path, - content_type: content_type + content_type: file.content_type }} end end @@ -154,16 +154,17 @@ defp prepare_upload(%Plug.Upload{} = file, opts) do defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do parsed = Regex.named_captures(~r/(?jpeg|png|gif);base64,(?.*)/, image_data) data = Base.decode64!(parsed["data"], ignore: :whitespace) - hash = String.downcase(Base.encode16(:crypto.hash(:sha256, data))) + hash = Base.encode16(:crypto.hash(:sha256, data), lower: true) with :ok <- check_binary_size(data, opts.size_limit), tmp_path <- tempfile_for_image(data), - {:ok, content_type, name} <- - Pleroma.MIME.bin_mime_type(data, hash <> "." <> parsed["filetype"]) do + {:ok, %{mime_type: content_type}} <- + Majic.perform({:bytes, data}, pool: Pleroma.MajicPool), + [ext | _] <- MIME.extensions(content_type) do {:ok, %__MODULE__{ id: UUID.generate(), - name: name, + name: hash <> "." <> ext, tempfile: tmp_path, content_type: content_type }} @@ -172,7 +173,7 @@ defp prepare_upload(%{img: "data:image/" <> image_data}, opts) do # For Mix.Tasks.MigrateLocalUploads defp prepare_upload(%__MODULE__{tempfile: path} = upload, _opts) do - with {:ok, content_type} <- Pleroma.MIME.file_mime_type(path) do + with {:ok, %{mime_type: content_type}} <- Majic.perform(path, pool: Pleroma.MajicPool) do {:ok, %__MODULE__{upload | content_type: content_type}} end end diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex index 9a94534e9..6249eceb1 100644 --- a/lib/pleroma/uploaders/uploader.ex +++ b/lib/pleroma/uploaders/uploader.ex @@ -12,7 +12,7 @@ defmodule Pleroma.Uploaders.Uploader do @doc """ Instructs how to get the file from the backend. - Used by `Pleroma.Plugs.UploadedMedia`. + Used by `Pleroma.Web.Plugs.UploadedMedia`. """ @type get_method :: {:static_dir, directory :: String.t()} | {:url, url :: String.t()} @callback get_file(file :: String.t()) :: {:ok, get_method()} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index ce5f4bc4a..b56a5dfe2 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -25,7 +25,6 @@ defmodule Pleroma.User do alias Pleroma.Object alias Pleroma.Registration alias Pleroma.Repo - alias Pleroma.RepoStreamer alias Pleroma.User alias Pleroma.UserRelationship alias Pleroma.Web @@ -83,7 +82,7 @@ defmodule Pleroma.User do ] schema "users" do - field(:bio, :string) + field(:bio, :string, default: "") field(:raw_bio, :string) field(:email, :string) field(:name, :string) @@ -108,7 +107,7 @@ defmodule Pleroma.User do field(:note_count, :integer, default: 0) field(:follower_count, :integer, default: 0) field(:following_count, :integer, default: 0) - field(:locked, :boolean, default: false) + field(:is_locked, :boolean, default: false) field(:confirmation_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false) field(:approval_pending, :boolean, default: false) @@ -276,9 +275,9 @@ def binary_id(%User{} = user), do: binary_id(user.id) @spec account_status(User.t()) :: account_status() def account_status(%User{deactivated: true}), do: :deactivated def account_status(%User{password_reset_pending: true}), do: :password_reset_pending - def account_status(%User{approval_pending: true}), do: :approval_pending + def account_status(%User{local: true, approval_pending: true}), do: :approval_pending - def account_status(%User{confirmation_pending: true}) do + def account_status(%User{local: true, confirmation_pending: true}) do if Config.get([:instance, :account_activation_required]) do :confirmation_pending else @@ -427,7 +426,6 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do params, [ :bio, - :name, :emoji, :ap_id, :inbox, @@ -437,7 +435,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do :avatar, :ap_enabled, :banner, - :locked, + :is_locked, :last_refreshed_at, :uri, :follower_address, @@ -456,7 +454,9 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do :accepts_chat_messages ] ) - |> validate_required([:name, :ap_id]) + |> cast(params, [:name], empty_values: []) + |> validate_required([:ap_id]) + |> validate_required([:name], trim: false) |> unique_constraint(:nickname) |> validate_format(:nickname, @email_regex) |> validate_length(:bio, max: bio_limit) @@ -480,7 +480,7 @@ def update_changeset(struct, params \\ %{}) do :public_key, :inbox, :shared_inbox, - :locked, + :is_locked, :no_rich_text, :default_scope, :banner, @@ -814,7 +814,8 @@ def send_welcome_email(%User{email: email} = user) when is_binary(email) do def send_welcome_email(_), do: {:ok, :noop} @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop} - def try_send_confirmation_email(%User{confirmation_pending: true} = user) do + def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user) + when is_binary(email) do if Config.get([:instance, :account_activation_required]) do send_confirmation_email(user) {:ok, :enqueued} @@ -847,7 +848,7 @@ def needs_update?(_), do: true @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()} # "Locked" (self-locked) users demand explicit authorization of follow requests - def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do + def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do follow(follower, followed, :follow_pending) end @@ -915,9 +916,7 @@ defp do_unfollow(%User{} = follower, %User{} = followed) do FollowingRelationship.unfollow(follower, followed) {:ok, followed} = update_follower_count(followed) - {:ok, follower} = - follower - |> update_following_count() + {:ok, follower} = update_following_count(follower) {:ok, follower, followed} @@ -956,7 +955,7 @@ def get_follow_state( end def locked?(%User{} = user) do - user.locked || false + user.is_locked || false end def get_by_id(id) do @@ -1587,7 +1586,7 @@ def purge_user_changeset(user) do # "Right to be forgotten" # https://gdpr.eu/right-to-be-forgotten/ change(user, %{ - bio: nil, + bio: "", raw_bio: nil, email: nil, name: nil, @@ -1603,7 +1602,7 @@ def purge_user_changeset(user) do note_count: 0, follower_count: 0, following_count: 0, - locked: false, + is_locked: false, confirmation_pending: false, password_reset_pending: false, approval_pending: false, @@ -1686,42 +1685,6 @@ def perform(:delete, %User{} = user) do def perform(:deactivate_async, user, status), do: deactivate(user, status) - @spec perform(atom(), User.t(), list()) :: list() | {:error, any()} - def perform(:blocks_import, %User{} = blocker, blocked_identifiers) - when is_list(blocked_identifiers) do - Enum.map( - blocked_identifiers, - fn blocked_identifier -> - with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier), - {:ok, _block} <- CommonAPI.block(blocker, blocked) do - blocked - else - err -> - Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}") - err - end - end - ) - end - - def perform(:follow_import, %User{} = follower, followed_identifiers) - when is_list(followed_identifiers) do - Enum.map( - followed_identifiers, - fn followed_identifier -> - with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier), - {:ok, follower} <- maybe_direct_follow(follower, followed), - {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do - followed - else - err -> - Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}") - err - end - end - ) - end - @spec external_users_query() :: Ecto.Query.t() def external_users_query do User.Query.build(%{ @@ -1750,21 +1713,6 @@ def external_users(opts \\ []) do Repo.all(query) end - def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do - BackgroundWorker.enqueue("blocks_import", %{ - "blocker_id" => blocker.id, - "blocked_identifiers" => blocked_identifiers - }) - end - - def follow_import(%User{} = follower, followed_identifiers) - when is_list(followed_identifiers) do - BackgroundWorker.enqueue("follow_import", %{ - "follower_id" => follower.id, - "followed_identifiers" => followed_identifiers - }) - end - def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do Notification |> join(:inner, [n], activity in assoc(n, :activity)) @@ -1775,7 +1723,7 @@ def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do def delete_user_activities(%User{ap_id: ap_id} = user) do ap_id |> Activity.Queries.by_actor() - |> RepoStreamer.chunk_stream(50) + |> Repo.chunk_stream(50, :batches) |> Stream.each(fn activities -> Enum.each(activities, fn activity -> delete_activity(activity, user) end) end) @@ -1821,12 +1769,12 @@ def html_filter_policy(%User{no_rich_text: true}) do def html_filter_policy(_), do: Config.get([:markup, :scrub_policy]) - def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id) + def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts) - def get_or_fetch_by_ap_id(ap_id) do + def get_or_fetch_by_ap_id(ap_id, opts \\ []) do cached_user = get_cached_by_ap_id(ap_id) - maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id) + maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts) case {cached_user, maybe_fetched_user} do {_, {:ok, %User{} = user}} -> @@ -1899,8 +1847,8 @@ def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do def public_key(_), do: {:error, "key not found"} - def get_public_key_for_ap_id(ap_id) do - with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id), + def get_public_key_for_ap_id(ap_id, opts \\ []) do + with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id, opts), {:ok, public_key} <- public_key(user) do {:ok, public_key} else @@ -2123,6 +2071,13 @@ def toggle_confirmation(users) do Enum.map(users, &toggle_confirmation/1) end + @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} + def need_confirmation(%User{} = user, bool) do + user + |> confirmation_changeset(need_confirmation: bool) + |> update_and_set_cache() + end + def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do mascot end @@ -2315,6 +2270,11 @@ def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0) params = %{pinned_activities: user.pinned_activities ++ [id]} + # if pinned activity was scheduled for deletion, we remove job + if expiration = Pleroma.Workers.PurgeExpiredActivity.get_expiration(id) do + Oban.cancel_job(expiration.id) + end + user |> cast(params, [:pinned_activities]) |> validate_length(:pinned_activities, @@ -2327,9 +2287,21 @@ def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do |> update_and_set_cache() end - def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do + def remove_pinnned_activity(user, %Pleroma.Activity{id: id, data: data}) do params = %{pinned_activities: List.delete(user.pinned_activities, id)} + # if pinned activity was scheduled for deletion, we reschedule it for deletion + if data["expires_at"] do + # MRF.ActivityExpirationPolicy used UTC timestamps for expires_at in original implementation + {:ok, expires_at} = + data["expires_at"] |> Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast() + + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: id, + expires_at: expires_at + }) + end + user |> cast(params, [:pinned_activities]) |> update_and_set_cache() diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex new file mode 100644 index 000000000..e458021c8 --- /dev/null +++ b/lib/pleroma/user/import.ex @@ -0,0 +1,85 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.Import do + use Ecto.Schema + + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Workers.BackgroundWorker + + require Logger + + @spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()} + def perform(:mutes_import, %User{} = user, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = muted_user} <- User.get_or_fetch(identifier), + {:ok, _} <- User.mute(user, muted_user) do + muted_user + else + error -> handle_error(:mutes_import, identifier, error) + end + end + ) + end + + def perform(:blocks_import, %User{} = blocker, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = blocked} <- User.get_or_fetch(identifier), + {:ok, _block} <- CommonAPI.block(blocker, blocked) do + blocked + else + error -> handle_error(:blocks_import, identifier, error) + end + end + ) + end + + def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do + Enum.map( + identifiers, + fn identifier -> + with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), + {:ok, follower} <- User.maybe_direct_follow(follower, followed), + {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do + followed + else + error -> handle_error(:follow_import, identifier, error) + end + end + ) + end + + def perform(_, _, _), do: :ok + + defp handle_error(op, user_id, error) do + Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}") + error + end + + def blocks_import(%User{} = blocker, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "blocks_import", + %{"user_id" => blocker.id, "identifiers" => identifiers} + ) + end + + def follow_import(%User{} = follower, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "follow_import", + %{"user_id" => follower.id, "identifiers" => identifiers} + ) + end + + def mutes_import(%User{} = user, [_ | _] = identifiers) do + BackgroundWorker.enqueue( + "mutes_import", + %{"user_id" => user.id, "identifiers" => identifiers} + ) + end +end diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index d618432ff..2440bf890 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -47,6 +47,7 @@ defmodule Pleroma.User.Query do is_moderator: boolean(), super_users: boolean(), invisible: boolean(), + internal: boolean(), followers: User.t(), friends: User.t(), recipients_from_activity: [String.t()], @@ -80,7 +81,9 @@ defp base_query do end defp prepare_query(query, criteria) do - Enum.reduce(criteria, query, &compose_query/2) + criteria + |> Map.put_new(:internal, false) + |> Enum.reduce(query, &compose_query/2) end defp compose_query({key, value}, query) @@ -107,12 +110,12 @@ defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 where(query, [u], fragment("? && ?", u.tags, ^tags)) end - defp compose_query({:is_admin, _}, query) do - where(query, [u], u.is_admin) + defp compose_query({:is_admin, bool}, query) do + where(query, [u], u.is_admin == ^bool) end - defp compose_query({:is_moderator, _}, query) do - where(query, [u], u.is_moderator) + defp compose_query({:is_moderator, bool}, query) do + where(query, [u], u.is_moderator == ^bool) end defp compose_query({:super_users, _}, query) do @@ -129,14 +132,12 @@ defp compose_query({:external, _}, query), do: location_query(query, false) defp compose_query({:active, _}, query) do User.restrict_deactivated(query) - |> where([u], not is_nil(u.nickname)) |> where([u], u.approval_pending == false) end defp compose_query({:legacy_active, _}, query) do query |> where([u], fragment("not (?->'deactivated' @> 'true')", u.info)) - |> where([u], not is_nil(u.nickname)) end defp compose_query({:deactivated, false}, query) do @@ -145,7 +146,10 @@ defp compose_query({:deactivated, false}, query) do defp compose_query({:deactivated, true}, query) do where(query, [u], u.deactivated == ^true) - |> where([u], not is_nil(u.nickname)) + end + + defp compose_query({:confirmation_pending, bool}, query) do + where(query, [u], u.confirmation_pending == ^bool) end defp compose_query({:need_approval, _}, query) do @@ -199,10 +203,15 @@ defp compose_query({:limit, limit}, query) do limit(query, ^limit) end + defp compose_query({:internal, false}, query) do + query + |> where([u], not is_nil(u.nickname)) + |> where([u], not like(u.nickname, "internal.%")) + end + defp compose_query(_unsupported_param, query), do: query defp location_query(query, local) do where(query, [u], u.local == ^local) - |> where([u], not is_nil(u.nickname)) end end diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 7babd47ea..35a828008 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -3,8 +3,10 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.User.Search do + alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType alias Pleroma.Pagination alias Pleroma.User + import Ecto.Query @limit 20 @@ -19,16 +21,47 @@ def search(query_string, opts \\ []) do query_string = format_query(query_string) - maybe_resolve(resolve, for_user, query_string) + # If this returns anything, it should bounce to the top + maybe_resolved = maybe_resolve(resolve, for_user, query_string) + + top_user_ids = + [] + |> maybe_add_resolved(maybe_resolved) + |> maybe_add_ap_id_match(query_string) + |> maybe_add_uri_match(query_string) results = query_string - |> search_query(for_user, following) + |> search_query(for_user, following, top_user_ids) |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset) results end + defp maybe_add_resolved(list, {:ok, %User{} = user}) do + [user.id | list] + end + + defp maybe_add_resolved(list, _), do: list + + defp maybe_add_ap_id_match(list, query) do + if user = User.get_cached_by_ap_id(query) do + [user.id | list] + else + list + end + end + + defp maybe_add_uri_match(list, query) do + with {:ok, query} <- UriType.cast(query), + q = from(u in User, where: u.uri == ^query, select: u.id), + users = Pleroma.Repo.all(q) do + users ++ list + else + _ -> list + end + end + defp format_query(query_string) do # Strip the beginning @ off if there is a query query_string = String.trim_leading(query_string, "@") @@ -47,21 +80,29 @@ defp format_query(query_string) do end end - defp search_query(query_string, for_user, following) do + defp search_query(query_string, for_user, following, top_user_ids) do for_user |> base_query(following) |> filter_blocked_user(for_user) |> filter_invisible_users() + |> filter_discoverable_users() |> filter_internal_users() |> filter_blocked_domains(for_user) |> fts_search(query_string) + |> select_top_users(top_user_ids) |> trigram_rank(query_string) - |> boost_search_rank(for_user) + |> boost_search_rank(for_user, top_user_ids) |> subquery() |> order_by(desc: :search_rank) |> maybe_restrict_local(for_user) end + defp select_top_users(query, top_user_ids) do + from(u in query, + or_where: u.id in ^top_user_ids + ) + end + defp fts_search(query, query_string) do query_string = to_tsquery(query_string) @@ -122,6 +163,10 @@ defp filter_invisible_users(query) do from(q in query, where: q.invisible == false) end + defp filter_discoverable_users(query) do + from(q in query, where: q.discoverable == true) + end + defp filter_internal_users(query) do from(q in query, where: q.actor_type != "Application") end @@ -175,7 +220,7 @@ defp restrict_local(q), do: where(q, [u], u.local == true) defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) - defp boost_search_rank(query, %User{} = for_user) do + defp boost_search_rank(query, %User{} = for_user, top_user_ids) do friends_ids = User.get_friends_ids(for_user) followers_ids = User.get_followers_ids(for_user) @@ -187,6 +232,7 @@ defp boost_search_rank(query, %User{} = for_user) do CASE WHEN (?) THEN (?) * 1.5 WHEN (?) THEN (?) * 1.3 WHEN (?) THEN (?) * 1.1 + WHEN (?) THEN 9001 ELSE (?) END """, u.id in ^friends_ids and u.id in ^followers_ids, @@ -195,11 +241,26 @@ defp boost_search_rank(query, %User{} = for_user) do u.search_rank, u.id in ^followers_ids, u.search_rank, + u.id in ^top_user_ids, u.search_rank ) } ) end - defp boost_search_rank(query, _for_user), do: query + defp boost_search_rank(query, _for_user, top_user_ids) do + from(u in subquery(query), + select_merge: %{ + search_rank: + fragment( + """ + CASE WHEN (?) THEN 9001 + ELSE (?) END + """, + u.id in ^top_user_ids, + u.search_rank + ) + } + ) + end end diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex index 21d1159be..e95766223 100644 --- a/lib/pleroma/utils.ex +++ b/lib/pleroma/utils.ex @@ -24,4 +24,24 @@ def compile_dir(dir) when is_binary(dir) do def command_available?(command) do match?({_output, 0}, System.cmd("sh", ["-c", "command -v #{command}"])) end + + @doc "creates the uniq temporary directory" + @spec tmp_dir(String.t()) :: {:ok, String.t()} | {:error, :file.posix()} + def tmp_dir(prefix \\ "") do + sub_dir = + [ + prefix, + Timex.to_unix(Timex.now()), + :os.getpid(), + String.downcase(Integer.to_string(:rand.uniform(0x100000000), 36)) + ] + |> Enum.join("-") + + tmp_dir = Path.join(System.tmp_dir!(), sub_dir) + + case File.mkdir(tmp_dir) do + :ok -> {:ok, tmp_dir} + error -> error + end + end end diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web.ex similarity index 93% rename from lib/pleroma/web/web.ex rename to lib/pleroma/web.ex index 4f9281851..6ed19d3dd 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web.ex @@ -2,11 +2,6 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.Plug do - # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug` - @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t() -end - defmodule Pleroma.Web do @moduledoc """ A module that keeps using definitions for controllers, @@ -25,12 +20,12 @@ defmodule Pleroma.Web do below. """ - alias Pleroma.Plugs.EnsureAuthenticatedPlug - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug - alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.PlugHelper + alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug + alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.PlugHelper def controller do quote do @@ -177,7 +172,7 @@ def router do def channel do quote do # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse - use Phoenix.Channel + import Phoenix.Channel import Pleroma.Web.Gettext end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index e4eafc8ac..3543f7f73 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity alias Pleroma.Activity.Ir.Topics - alias Pleroma.ActivityExpiration alias Pleroma.Config alias Pleroma.Constants alias Pleroma.Conversation @@ -85,7 +84,7 @@ defp increase_replies_count_if_reply(%{ defp increase_replies_count_if_reply(_create_data), do: :noop - @object_types ~w[ChatMessage Question Answer Audio Event] + @object_types ~w[ChatMessage Question Answer Audio Video Event Article] @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()} def persist(%{"type" => type} = object, meta) when type in @object_types do with {:ok, object} <- Object.create(object) do @@ -102,7 +101,9 @@ def persist(object, meta) do local: local, recipients: recipients, actor: object["actor"] - }) do + }), + # TODO: add tests for expired activities, when Note type will be supported in new pipeline + {:ok, _} <- maybe_create_activity_expiration(activity) do {:ok, activity, meta} end end @@ -111,23 +112,14 @@ def persist(object, meta) do def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map, fake), - true <- bypass_actor_check || check_actor_is_active(map["actor"]), - {_, true} <- {:remote_limit_error, check_remote_limit(map)}, + {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])}, + {_, true} <- {:remote_limit_pass, check_remote_limit(map)}, {:ok, map} <- MRF.filter(map), {recipients, _, _} = get_recipients(map), {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, {:containment, :ok} <- {:containment, Containment.contain_child(map)}, - {:ok, map, object} <- insert_full_object(map) do - {:ok, activity} = - %Activity{ - data: map, - local: local, - actor: map["actor"], - recipients: recipients - } - |> Repo.insert() - |> maybe_create_activity_expiration() - + {:ok, map, object} <- insert_full_object(map), + {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do # Splice in the child object if we have one. activity = Maps.put_if_present(activity, :object, object) @@ -138,6 +130,15 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when %Activity{} = activity -> {:ok, activity} + {:actor_check, _} -> + {:error, false} + + {:containment, _} = error -> + error + + {:error, _} = error -> + error + {:fake, true, map, recipients} -> activity = %Activity{ data: map, @@ -150,8 +151,24 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) {:ok, activity} - error -> - {:error, error} + {:remote_limit_pass, _} -> + {:error, :remote_limit} + + {:reject, _} = e -> + {:error, e} + end + end + + defp insert_activity_with_expiration(data, local, recipients) do + struct = %Activity{ + data: data, + local: local, + actor: data["actor"], + recipients: recipients + } + + with {:ok, activity} <- Repo.insert(struct) do + maybe_create_activity_expiration(activity) end end @@ -164,13 +181,19 @@ def notify_and_stream(activity) do stream_out_participations(participations) end - defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do - with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do + defp maybe_create_activity_expiration( + %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity + ) do + with {:ok, _job} <- + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: expires_at + }) do {:ok, activity} end end - defp maybe_create_activity_expiration(result), do: result + defp maybe_create_activity_expiration(activity), do: {:ok, activity} defp create_or_bump_conversation(activity, actor) do with {:ok, conversation} <- Conversation.create_or_bump_for(activity), @@ -767,7 +790,17 @@ defp restrict_replies(query, %{ [activity, object] in query, where: fragment( - "?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?", + """ + ?->>'type' != 'Create' -- This isn't a Create + OR ?->>'inReplyTo' is null -- this isn't a reply + OR ? && array_remove(?, ?) -- The recipient is us or one of our friends, + -- unless they are the author (because authors + -- are also part of the recipients). This leads + -- to a bug that self-replies by friends won't + -- show up. + OR ? = ? -- The actor is us + """, + activity.data, object.data, ^[user.ap_id | User.get_cached_user_friends_ap_ids(user)], activity.recipients, @@ -818,7 +851,14 @@ defp restrict_blocked(query, %{blocking_user: %User{} = user} = opts) do from( [activity, object: o] in query, where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids), - where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids), + where: + fragment( + "((not (? && ?)) or ? = ?)", + activity.recipients, + ^blocked_ap_ids, + activity.actor, + ^user.ap_id + ), where: fragment( "recipients_contain_blocked_domains(?, ?) = false", @@ -1188,7 +1228,7 @@ defp object_to_user_data(data) do {String.trim(name, ":"), url} end) - locked = data["manuallyApprovesFollowers"] || false + is_locked = data["manuallyApprovesFollowers"] || false capabilities = data["capabilities"] || %{} accepts_chat_messages = capabilities["acceptsChatMessages"] data = Transmogrifier.maybe_fix_user_object(data) @@ -1217,14 +1257,14 @@ defp object_to_user_data(data) do banner: banner, fields: fields, emoji: emojis, - locked: locked, + is_locked: is_locked, discoverable: discoverable, invisible: invisible, avatar: avatar, name: data["name"], follower_address: data["followers"], following_address: data["following"], - bio: data["summary"], + bio: data["summary"] || "", actor_type: actor_type, also_known_as: Map.get(data, "alsoKnownAs", []), public_key: public_key, @@ -1247,10 +1287,12 @@ defp object_to_user_data(data) do def fetch_follow_information_for_user(user) do with {:ok, following_data} <- - Fetcher.fetch_and_contain_remote_object_from_id(user.following_address), + Fetcher.fetch_and_contain_remote_object_from_id(user.following_address, + force_http: true + ), {:ok, hide_follows} <- collection_private(following_data), {:ok, followers_data} <- - Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address), + Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address, force_http: true), {:ok, hide_followers} <- collection_private(followers_data) do {:ok, %{ @@ -1324,11 +1366,12 @@ def user_data_from_user_object(data) do end end - def fetch_and_prepare_user_from_ap_id(ap_id) do - with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id), + def fetch_and_prepare_user_from_ap_id(ap_id, opts \\ []) do + with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id, opts), {:ok, data} <- user_data_from_user_object(data) do {:ok, maybe_update_follow_information(data)} else + # If this has been deleted, only log a debug and not an error {:error, "Object has been deleted" = e} -> Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}") {:error, e} @@ -1367,13 +1410,13 @@ def maybe_handle_clashing_nickname(data) do end end - def make_user_from_ap_id(ap_id) do + def make_user_from_ap_id(ap_id, opts \\ []) do user = User.get_cached_by_ap_id(ap_id) if user && !User.ap_enabled?(user) do Transmogrifier.upgrade_user_from_ap_id(ap_id) else - with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do + with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, opts) do if user do user |> User.remote_user_changeset(data) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 732c44271..44f09be75 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Delivery alias Pleroma.Object alias Pleroma.Object.Fetcher - alias Pleroma.Plugs.EnsureAuthenticatedPlug alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder @@ -23,8 +22,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ControllerHelper alias Pleroma.Web.Endpoint - alias Pleroma.Web.FederatingPlug alias Pleroma.Web.Federator + alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug + alias Pleroma.Web.Plugs.FederatingPlug require Logger @@ -45,8 +45,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do when action in [:read_inbox, :update_outbox, :whoami, :upload_media] ) + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media]) + plug( - Pleroma.Plugs.Cache, + Pleroma.Web.Plugs.Cache, [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2] when action in [:activity, :object] ) @@ -412,7 +414,7 @@ defp handle_user_activity( object = object |> Map.merge(Map.take(params, ["to", "cc"])) - |> Map.put("attributedTo", user.ap_id()) + |> Map.put("attributedTo", user.ap_id) |> Transmogrifier.fix_object() ActivityPub.create(%{ @@ -456,7 +458,7 @@ def update_outbox( %{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{"nickname" => nickname} = params ) do - actor = user.ap_id() + actor = user.ap_id params = params diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 9a7b7d9de..298aff6b7 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.ActivityPub.Builder do @moduledoc """ This module builds the objects. Meant to be used for creating local objects. diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex index 7b4c78e0f..bee47b4ed 100644 --- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex @@ -31,10 +31,10 @@ defp note?(activity) do defp maybe_add_expiration(activity) do days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365) - expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days) + expires_at = DateTime.utc_now() |> Timex.shift(days: days) with %{"expires_at" => existing_expires_at} <- activity, - :lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do + :lt <- DateTime.compare(existing_expires_at, expires_at) do activity else _ -> Map.put(activity, "expires_at", expires_at) diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex new file mode 100644 index 000000000..ea9c3d3f5 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do + alias Pleroma.User + @behaviour Pleroma.Web.ActivityPub.MRF + @moduledoc "Remove bot posts from federated timeline" + + require Pleroma.Constants + + defp check_by_actor_type(user), do: user.actor_type in ["Application", "Service"] + defp check_by_nickname(user), do: Regex.match?(~r/bot@|ebooks@/i, user.nickname) + + defp check_if_bot(user), do: check_by_actor_type(user) or check_by_nickname(user) + + @impl true + def filter( + %{ + "type" => "Create", + "to" => to, + "cc" => cc, + "actor" => actor, + "object" => object + } = message + ) do + user = User.get_cached_by_ap_id(actor) + isbot = check_if_bot(user) + + if isbot and Enum.member?(to, Pleroma.Constants.as_public()) do + to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address] + cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()] + + object = + object + |> Map.put("to", to) + |> Map.put("cc", cc) + + message = + message + |> Map.put("to", to) + |> Map.put("cc", cc) + |> Map.put("object", object) + + {:ok, message} + else + {:ok, message} + end + end + + @impl true + def filter(message), do: {:ok, message} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index dfab105a3..0fb05d3c4 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -12,23 +12,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do require Logger - @options [ - pool: :media + @adapter_options [ + pool: :media, + recv_timeout: 10_000 ] def perform(:prefetch, url) do - Logger.debug("Prefetching #{inspect(url)}") + # Fetching only proxiable resources + if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do + # If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests) + prefetch_url = MediaProxy.preview_url(url) - opts = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.put(@options, :recv_timeout, 10_000) - else - @options - end + Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}") - url - |> MediaProxy.url() - |> HTTP.get([], adapter: opts) + HTTP.get(prefetch_url, [], @adapter_options) + end end def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index b77c06395..bd0a2a8dc 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -12,11 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Activity alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object + alias Pleroma.Object.Containment alias Pleroma.User alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator @@ -149,10 +151,20 @@ def validate(%{"type" => "Question"} = object, meta) do end end - def validate(%{"type" => "Audio"} = object, meta) do + def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do with {:ok, object} <- object - |> AudioValidator.cast_and_validate() + |> AudioVideoValidator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + object = stringify_keys(object) + {:ok, object, meta} + end + end + + def validate(%{"type" => "Article"} = object, meta) do + with {:ok, object} <- + object + |> ArticleNoteValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object) {:ok, object, meta} @@ -198,7 +210,7 @@ def validate( %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, meta ) - when objtype in ~w[Question Answer Audio Event] do + when objtype in ~w[Question Answer Audio Video Event Article] do with {:ok, object_data} <- cast_and_apply(object), meta = Keyword.put(meta, :object_data, object_data |> stringify_keys), {:ok, create_activity} <- @@ -232,14 +244,18 @@ def cast_and_apply(%{"type" => "Answer"} = object) do AnswerValidator.cast_and_apply(object) end - def cast_and_apply(%{"type" => "Audio"} = object) do - AudioValidator.cast_and_apply(object) + def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do + AudioVideoValidator.cast_and_apply(object) end def cast_and_apply(%{"type" => "Event"} = object) do EventValidator.cast_and_apply(object) end + def cast_and_apply(%{"type" => "Article"} = object) do + ArticleNoteValidator.cast_and_apply(object) + end + def cast_and_apply(o), do: {:error, {:validator_not_set, o}} # is_struct/1 isn't present in Elixir 1.8.x @@ -262,7 +278,8 @@ def stringify_keys(object) when is_list(object) do def stringify_keys(object), do: object def fetch_actor(object) do - with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do + with actor <- Containment.get_actor(object), + {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do User.get_or_fetch_by_ap_id(actor) end end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex similarity index 78% rename from lib/pleroma/web/activity_pub/object_validators/audio_validator.ex rename to lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex index d1869f188..5b7dad517 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex @@ -2,13 +2,14 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do use Ecto.Schema alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -24,29 +25,31 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do # TODO: Write type field(:tag, {:array, :map}, default: []) field(:type, :string) + + field(:name, :string) + field(:summary, :string) field(:content, :string) + field(:context, :string) + # short identifier for PleromaFE to group statuses by context + field(:context_id, :integer) # TODO: Remove actor on objects field(:actor, ObjectValidators.ObjectID) field(:attributedTo, ObjectValidators.ObjectID) - field(:summary, :string) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) field(:like_count, :integer, default: 0) field(:announcement_count, :integer, default: 0) - field(:inReplyTo, :string) + field(:inReplyTo, ObjectValidators.ObjectID) field(:url, ObjectValidators.Uri) - # short identifier for PleromaFE to group statuses by context - field(:context_id, :integer) - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) end def cast_and_apply(data) do @@ -62,19 +65,14 @@ def cast_and_validate(data) do end def cast_data(data) do + data = fix(data) + %__MODULE__{} |> changeset(data) end - defp fix_url(%{"url" => url} = data) when is_list(url) do - attachment = - Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end) - - link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end) - - data - |> Map.put("attachment", [attachment]) - |> Map.put("url", link_element["href"]) + defp fix_url(%{"url" => url} = data) when is_map(url) do + Map.put(data, "url", url["href"]) end defp fix_url(data), do: data @@ -83,7 +81,9 @@ defp fix(data) do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> CommonFixes.fix_actor() |> fix_url() + |> Transmogrifier.fix_emoji() end def changeset(struct, data) do @@ -96,8 +96,8 @@ def changeset(struct, data) do def validate_data(data_cng) do data_cng - |> validate_inclusion(:type, ["Audio"]) - |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment]) + |> validate_inclusion(:type, ["Article", "Note"]) + |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id]) |> CommonValidations.validate_any_presence([:cc, :to]) |> CommonValidations.validate_fields_match([:actor, :attributedTo]) |> CommonValidations.validate_actor_presence() diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex index c8b148280..df102a134 100644 --- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do use Ecto.Schema + alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator import Ecto.Changeset @@ -15,7 +16,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do field(:mediaType, :string, default: "application/octet-stream") field(:name, :string) - embeds_many(:url, UrlObjectValidator) + embeds_many :url, UrlObjectValidator, primary_key: false do + field(:type, :string) + field(:href, ObjectValidators.Uri) + field(:mediaType, :string, default: "application/octet-stream") + end end def cast_and_validate(data) do @@ -37,7 +42,18 @@ def changeset(struct, data) do struct |> cast(data, [:type, :mediaType, :name]) - |> cast_embed(:url, required: true) + |> cast_embed(:url, with: &url_changeset/2) + |> validate_inclusion(:type, ~w[Link Document Audio Image Video]) + |> validate_required([:type, :mediaType, :url]) + end + + def url_changeset(struct, data) do + data = fix_media_type(data) + + struct + |> cast(data, [:type, :href, :mediaType]) + |> validate_inclusion(:type, ["Link"]) + |> validate_required([:type, :href, :mediaType]) end def fix_media_type(data) do @@ -75,6 +91,7 @@ defp fix_url(data) do def validate_data(cng) do cng + |> validate_inclusion(:type, ~w[Document Audio Image Video]) |> validate_required([:mediaType, :url, :type]) end end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex new file mode 100644 index 000000000..16973e5db --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex @@ -0,0 +1,134 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do + use Ecto.Schema + + alias Pleroma.EarmarkRenderer + alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier + + import Ecto.Changeset + + @primary_key false + @derive Jason.Encoder + + embedded_schema do + field(:id, ObjectValidators.ObjectID, primary_key: true) + field(:to, ObjectValidators.Recipients, default: []) + field(:cc, ObjectValidators.Recipients, default: []) + field(:bto, ObjectValidators.Recipients, default: []) + field(:bcc, ObjectValidators.Recipients, default: []) + # TODO: Write type + field(:tag, {:array, :map}, default: []) + field(:type, :string) + + field(:name, :string) + field(:summary, :string) + field(:content, :string) + + field(:context, :string) + # short identifier for PleromaFE to group statuses by context + field(:context_id, :integer) + + # TODO: Remove actor on objects + field(:actor, ObjectValidators.ObjectID) + + field(:attributedTo, ObjectValidators.ObjectID) + field(:published, ObjectValidators.DateTime) + field(:emoji, ObjectValidators.Emoji, default: %{}) + field(:sensitive, :boolean, default: false) + embeds_many(:attachment, AttachmentValidator) + field(:replies_count, :integer, default: 0) + field(:like_count, :integer, default: 0) + field(:announcement_count, :integer, default: 0) + field(:inReplyTo, ObjectValidators.ObjectID) + field(:url, ObjectValidators.Uri) + + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) + end + + def cast_and_apply(data) do + data + |> cast_data + |> apply_action(:insert) + end + + def cast_and_validate(data) do + data + |> cast_data() + |> validate_data() + end + + def cast_data(data) do + %__MODULE__{} + |> changeset(data) + end + + defp fix_url(%{"url" => url} = data) when is_list(url) do + attachment = + Enum.find(url, fn x -> + mime_type = x["mimeType"] || x["mediaType"] || "" + + is_map(x) and String.starts_with?(mime_type, ["video/", "audio/"]) + end) + + link_element = + Enum.find(url, fn x -> + mime_type = x["mimeType"] || x["mediaType"] || "" + + is_map(x) and mime_type == "text/html" + end) + + data + |> Map.put("attachment", [attachment]) + |> Map.put("url", link_element["href"]) + end + + defp fix_url(data), do: data + + defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data) + when is_binary(content) do + content = + content + |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) + |> Pleroma.HTML.filter_tags() + + Map.put(data, "content", content) + end + + defp fix_content(data), do: data + + defp fix(data) do + data + |> CommonFixes.fix_defaults() + |> CommonFixes.fix_attribution() + |> CommonFixes.fix_actor() + |> Transmogrifier.fix_emoji() + |> fix_url() + |> fix_content() + end + + def changeset(struct, data) do + data = fix(data) + + struct + |> cast(data, __schema__(:fields) -- [:attachment]) + |> cast_embed(:attachment) + end + + def validate_data(data_cng) do + data_cng + |> validate_inclusion(:type, ["Audio", "Video"]) + |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment]) + |> CommonValidations.validate_any_presence([:cc, :to]) + |> CommonValidations.validate_fields_match([:actor, :attributedTo]) + |> CommonValidations.validate_actor_presence() + |> CommonValidations.validate_host_match() + end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex index 91b475393..6acd4a771 100644 --- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do field(:content, ObjectValidators.SafeText) field(:actor, ObjectValidators.ObjectID) field(:published, ObjectValidators.DateTime) - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) embeds_one(:attachment, AttachmentValidator) end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 721749de0..b3638cfc7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do + alias Pleroma.Object.Containment alias Pleroma.Web.ActivityPub.Utils # based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults @@ -11,12 +12,20 @@ def fix_defaults(data) do Utils.create_context(data["context"] || data["conversation"]) data - |> Map.put_new("context", context) - |> Map.put_new("context_id", context_id) + |> Map.put("context", context) + |> Map.put("context_id", context_id) end def fix_attribution(data) do data |> Map.put_new("actor", data["attributedTo"]) end + + def fix_actor(data) do + actor = Containment.get_actor(data) + + data + |> Map.put("actor", actor) + |> Map.put("attributedTo", actor) + end end diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex index b3dbeea57..422ee07be 100644 --- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex @@ -10,9 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations import Ecto.Changeset - import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @primary_key false @@ -75,14 +76,15 @@ defp fix(data, meta) do data |> fix_context(meta) |> fix_addressing(meta) + |> CommonFixes.fix_actor() end def validate_data(cng, meta \\ []) do cng |> validate_required([:actor, :type, :object]) |> validate_inclusion(:type, ["Create"]) - |> validate_actor_presence() - |> validate_any_presence([:to, :cc]) + |> CommonValidations.validate_actor_presence() + |> CommonValidations.validate_any_presence([:to, :cc]) |> validate_actors_match(meta) |> validate_context_match(meta) |> validate_object_nonexistence() diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex index 07e4821a4..0b4c99dc0 100644 --- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -39,8 +40,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do field(:attributedTo, ObjectValidators.ObjectID) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) @@ -74,6 +74,7 @@ defp fix(data) do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> Transmogrifier.fix_emoji() end def changeset(struct, data) do diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex deleted file mode 100644 index 20e735619..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex +++ /dev/null @@ -1,66 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do - use Ecto.Schema - - alias Pleroma.EctoType.ActivityPub.ObjectValidators - - import Ecto.Changeset - - @primary_key false - - embedded_schema do - field(:id, ObjectValidators.ObjectID, primary_key: true) - field(:to, ObjectValidators.Recipients, default: []) - field(:cc, ObjectValidators.Recipients, default: []) - field(:bto, ObjectValidators.Recipients, default: []) - field(:bcc, ObjectValidators.Recipients, default: []) - # TODO: Write type - field(:tag, {:array, :map}, default: []) - field(:type, :string) - - field(:name, :string) - field(:summary, :string) - field(:content, :string) - - field(:context, :string) - # short identifier for PleromaFE to group statuses by context - field(:context_id, :integer) - - field(:actor, ObjectValidators.ObjectID) - field(:attributedTo, ObjectValidators.ObjectID) - field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) - field(:sensitive, :boolean, default: false) - # TODO: Write type - field(:attachment, {:array, :map}, default: []) - field(:replies_count, :integer, default: 0) - field(:like_count, :integer, default: 0) - field(:announcement_count, :integer, default: 0) - field(:inReplyTo, ObjectValidators.ObjectID) - field(:url, ObjectValidators.Uri) - - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) - end - - def cast_and_validate(data) do - data - |> cast_data() - |> validate_data() - end - - def cast_data(data) do - %__MODULE__{} - |> cast(data, __schema__(:fields)) - end - - def validate_data(data_cng) do - data_cng - |> validate_inclusion(:type, ["Note"]) - |> validate_required([:id, :actor, :to, :cc, :type, :content, :context]) - end -end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 712047424..9310485dc 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator + alias Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Changeset @@ -35,8 +36,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do field(:attributedTo, ObjectValidators.ObjectID) field(:summary, :string) field(:published, ObjectValidators.DateTime) - # TODO: Write type - field(:emoji, :map, default: %{}) + field(:emoji, ObjectValidators.Emoji, default: %{}) field(:sensitive, :boolean, default: false) embeds_many(:attachment, AttachmentValidator) field(:replies_count, :integer, default: 0) @@ -47,8 +47,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do # short identifier for PleromaFE to group statuses by context field(:context_id, :integer) - field(:likes, {:array, :string}, default: []) - field(:announcements, {:array, :string}, default: []) + field(:likes, {:array, ObjectValidators.ObjectID}, default: []) + field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) field(:closed, ObjectValidators.DateTime) field(:voters, {:array, ObjectValidators.ObjectID}, default: []) @@ -85,6 +85,7 @@ defp fix(data) do data |> CommonFixes.fix_defaults() |> CommonFixes.fix_attribution() + |> Transmogrifier.fix_emoji() |> fix_closed() end diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex deleted file mode 100644 index 881030f38..000000000 --- a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do - use Ecto.Schema - - alias Pleroma.EctoType.ActivityPub.ObjectValidators - - import Ecto.Changeset - @primary_key false - - embedded_schema do - field(:type, :string) - field(:href, ObjectValidators.Uri) - field(:mediaType, :string, default: "application/octet-stream") - end - - def changeset(struct, data) do - struct - |> cast(data, __schema__(:fields)) - |> validate_required([:type, :href, :mediaType]) - end -end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index d88f7f3ee..a2930c1cd 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.FedSockets require Pleroma.Constants @@ -50,15 +51,35 @@ def is_representable?(%Activity{} = activity) do def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do Logger.debug("Federating #{id} to #{inbox}") - uri = URI.parse(inbox) + case FedSockets.get_or_create_fed_socket(inbox) do + {:ok, fedsocket} -> + Logger.debug("publishing via fedsockets - #{inspect(inbox)}") + FedSockets.publish(fedsocket, json) + _ -> + Logger.debug("publishing via http - #{inspect(inbox)}") + http_publish(inbox, actor, json, params) + end + end + + def publish_one(%{actor_id: actor_id} = params) do + actor = User.get_cached_by_id(actor_id) + + params + |> Map.delete(:actor_id) + |> Map.put(:actor, actor) + |> publish_one() + end + + defp http_publish(inbox, actor, json, params) do + uri = %{path: path} = URI.parse(inbox) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) date = Pleroma.Signature.signed_date() signature = Pleroma.Signature.sign(actor, %{ - "(request-target)": "post #{uri.path}", + "(request-target)": "post #{path}", host: signature_host(uri), "content-length": byte_size(json), digest: digest, @@ -89,15 +110,6 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa end end - def publish_one(%{actor_id: actor_id} = params) do - actor = User.get_cached_by_id(actor_id) - - params - |> Map.delete(:actor_id) - |> Map.put(:actor, actor) - |> publish_one() - end - defp signature_host(%URI{port: port, scheme: scheme, host: host}) do if port == URI.default_port(scheme) do host @@ -230,9 +242,7 @@ def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity) end) end - @doc """ - Publishes an activity to all relevant peers. - """ + # Publishes an activity to all relevant peers. def publish(%User{} = actor, %Activity{} = activity) do public = is_public?(activity) diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index b65710a94..6606e1780 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -30,12 +30,16 @@ def follow(target_instance) do end end - @spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()} - def unfollow(target_instance) do + @spec unfollow(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()} + def unfollow(target_instance, opts \\ %{}) do with %User{} = local_user <- get_actor(), - {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance), + {:ok, target_user} <- fetch_target_user(target_instance, opts), {:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do - User.unfollow(local_user, target_user) + case target_user.id do + nil -> User.update_following_count(local_user) + _ -> User.unfollow(local_user, target_user) + end + Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}") {:ok, activity} else @@ -43,6 +47,14 @@ def unfollow(target_instance) do end end + defp fetch_target_user(ap_id, opts) do + case {opts[:force], User.get_or_fetch_by_ap_id(ap_id)} do + {_, {:ok, %User{} = user}} -> {:ok, user} + {true, _} -> {:ok, %User{ap_id: ap_id}} + {_, error} -> error + end + end + @spec publish(any()) :: {:ok, Activity.t()} | {:error, any()} def publish(%Activity{data: %{"type" => "Create"}} = activity) do with %User{} = user <- get_actor(), diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index a5e2323bd..0fff5faf2 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.ActivityPub.SideEffects do @moduledoc """ This module looks at an inserted object and executes the side effects that it @@ -7,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do """ alias Pleroma.Activity alias Pleroma.Activity.Ir.Topics - alias Pleroma.ActivityExpiration alias Pleroma.Chat alias Pleroma.Chat.MessageReference alias Pleroma.FollowingRelationship @@ -99,7 +102,7 @@ def handle( %User{} = followed <- User.get_cached_by_ap_id(followed_user), {_, {:ok, _}, _, _} <- {:following, User.follow(follower, followed, :follow_pending), follower, followed} do - if followed.local && !followed.locked do + if followed.local && !followed.is_locked do {:ok, accept_data, _} = Builder.accept(followed, object) {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true) end @@ -188,10 +191,6 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do Object.increase_replies_count(in_reply_to) end - if expires_at = activity.data["expires_at"] do - ActivityExpiration.create(activity, expires_at) - end - BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id}) meta = @@ -307,6 +306,7 @@ def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do streamables = [[actor, recipient], [recipient, actor]] + |> Enum.uniq() |> Enum.map(fn [user, other_user] -> if user.local do {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id) @@ -341,7 +341,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do end def handle_object_creation(%{"type" => objtype} = object, meta) - when objtype in ~w[Audio Question Event] do + when objtype in ~w[Audio Video Question Event Article] do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do {:ok, object, meta} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 63dd227c1..39c8f7e39 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do A module to handle coding from internal to wire ActivityPub and back. """ alias Pleroma.Activity - alias Pleroma.EarmarkRenderer alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Maps alias Pleroma.Object @@ -41,11 +40,11 @@ def fix_object(object, options \\ []) do |> fix_in_reply_to(options) |> fix_emoji |> fix_tag + |> set_sensitive |> fix_content_map |> fix_addressing |> fix_summary |> fix_type(options) - |> fix_content end def fix_summary(%{"summary" => nil} = object) do @@ -168,7 +167,6 @@ def fix_in_reply_to(object, options \\ []) def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) when not is_nil(in_reply_to) do in_reply_to_id = prepare_in_reply_to(in_reply_to) - object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) depth = (options[:depth] || 0) + 1 if Federator.allowed_thread_distance?(depth) do @@ -176,9 +174,8 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) %Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do object |> Map.put("inReplyTo", replied_object.data["id"]) - |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) |> Map.put("context", replied_object.data["context"] || object["conversation"]) - |> Map.drop(["conversation"]) + |> Map.drop(["conversation", "inReplyToAtomUri"]) else e -> Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}") @@ -276,24 +273,7 @@ def fix_url(%{"url" => url} = object) when is_map(url) do Map.put(object, "url", url["href"]) end - def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do - attachment = - Enum.find(url, fn x -> - media_type = x["mediaType"] || x["mimeType"] || "" - - is_map(x) and String.starts_with?(media_type, "video/") - end) - - link_element = - Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end) - - object - |> Map.put("attachment", [attachment]) - |> Map.put("url", link_element["href"]) - end - - def fix_url(%{"type" => object_type, "url" => url} = object) - when object_type != "Video" and is_list(url) do + def fix_url(%{"url" => url} = object) when is_list(url) do first_element = Enum.at(url, 0) url_string = @@ -318,9 +298,6 @@ def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do Map.put(mapping, name, data["icon"]["url"]) end) - # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats - emoji = Map.merge(object["emoji"] || %{}, emoji) - Map.put(object, "emoji", emoji) end @@ -337,19 +314,21 @@ def fix_tag(%{"tag" => tag} = object) when is_list(tag) do tags = tag |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end) - |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end) + |> Enum.map(fn %{"name" => name} -> + name + |> String.slice(1..-1) + |> String.downcase() + end) Map.put(object, "tag", tag ++ tags) end - def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do - combined = [tag, String.slice(hashtag, 1..-1)] - - Map.put(object, "tag", combined) + def fix_tag(%{"tag" => %{} = tag} = object) do + object + |> Map.put("tag", [tag]) + |> fix_tag end - def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag]) - def fix_tag(object), do: object # content map usually only has one language so this will do for now. @@ -376,18 +355,6 @@ def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) def fix_type(object, _), do: object - defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object) - when is_binary(content) do - html_content = - content - |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) - |> Pleroma.HTML.filter_tags() - - Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"}) - end - - defp fix_content(object), do: object - # Reduce the object list to find the reported user. defp get_reported(objects) do Enum.reduce_while(objects, nil, fn ap_id, _ -> @@ -460,7 +427,7 @@ def handle_incoming( %{"type" => "Create", "object" => %{"type" => objtype} = object} = data, options ) - when objtype in ~w{Article Note Video Page} do + when objtype in ~w{Note Page} do actor = Containment.get_actor(data) with nil <- Activity.get_create_by_object_ap_id(object["id"]), @@ -551,13 +518,19 @@ def handle_incoming( end def handle_incoming( - %{"type" => "Create", "object" => %{"type" => objtype}} = data, + %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data, _options ) - when objtype in ~w{Question Answer ChatMessage Audio Event} do + when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do + data = Map.put(data, "object", strip_internal_fields(data["object"])) + with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), + nil <- Activity.get_create_by_object_ap_id(obj_id), {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} + else + %Activity{} = activity -> {:ok, activity} + e -> e end end @@ -957,7 +930,7 @@ def set_conversation(object) do Map.put(object, "conversation", object["context"]) end - def set_sensitive(%{"sensitive" => true} = object) do + def set_sensitive(%{"sensitive" => _} = object) do object end @@ -1034,7 +1007,7 @@ def perform(:user_upgrade, user) do def upgrade_user_from_ap_id(ap_id) do with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id), - {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id), + {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id, force_http: true), {:ok, user} <- update_user(user, data) do TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id}) {:ok, user} diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 3a4564912..c6dee61db 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -101,7 +101,7 @@ def render("user.json", %{user: user}) do "name" => user.name, "summary" => user.bio, "url" => user.ap_id, - "manuallyApprovesFollowers" => user.locked, + "manuallyApprovesFollowers" => user.is_locked, "publicKey" => %{ "id" => "#{user.ap_id}#main-key", "owner" => user.ap_id, diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 5c349bb7a..76bd54a42 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -44,29 +44,30 @@ def is_direct?(activity) do def is_list?(%{data: %{"listMessage" => _}}), do: true def is_list?(_), do: false - @spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean() - def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true + @spec visible_for_user?(Activity.t() | nil, User.t() | nil) :: boolean() + def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true def visible_for_user?(nil, _), do: false - def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false + def visible_for_user?(%Activity{data: %{"listMessage" => _}}, nil), do: false - def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do + def visible_for_user?( + %Activity{data: %{"listMessage" => list_ap_id}} = activity, + %User{} = user + ) do user.ap_id in activity.data["to"] || list_ap_id |> Pleroma.List.get_by_ap_id() |> Pleroma.List.member?(user) end - def visible_for_user?(%{local: local} = activity, nil) do - cfg_key = if local, do: :local, else: :remote - - if Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key), + def visible_for_user?(%Activity{} = activity, nil) do + if restrict_unauthenticated_access?(activity), do: false, else: is_public?(activity) end - def visible_for_user?(activity, user) do + def visible_for_user?(%Activity{} = activity, user) do x = [user.ap_id | User.following(user)] y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || []) is_public?(activity) || Enum.any?(x, &(&1 in y)) @@ -82,6 +83,26 @@ def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do result end + def restrict_unauthenticated_access?(%Activity{local: local}) do + restrict_unauthenticated_access_to_activity?(local) + end + + def restrict_unauthenticated_access?(%Object{} = object) do + object + |> Object.local?() + |> restrict_unauthenticated_access_to_activity?() + end + + def restrict_unauthenticated_access?(%User{} = user) do + User.visible_for(user, _reading_user = nil) + end + + defp restrict_unauthenticated_access_to_activity?(local?) when is_boolean(local?) do + cfg_key = if local?, do: :local, else: :remote + + Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key) + end + def get_visibility(object) do to = object.data["to"] || [] cc = object.data["cc"] || [] diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index f5e4d49f9..bdd3e195d 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Config alias Pleroma.MFA alias Pleroma.ModerationLog - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -21,10 +20,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.AdminAPI.ModerationLogView alias Pleroma.Web.AdminAPI.Search alias Pleroma.Web.Endpoint + alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Router - require Logger - @users_page_size 50 plug( @@ -68,6 +66,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do when action in [:list_user_statuses, :list_instance_statuses] ) + plug( + OAuthScopesPlug, + %{scopes: ["read:chats"], admin: true} + when action in [:list_user_chats] + ) + plug( OAuthScopesPlug, %{scopes: ["read"], admin: true} @@ -256,6 +260,20 @@ def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickna end end + def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do + with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do + chats = + Pleroma.Chat.for_user_query(user_id) + |> Pleroma.Repo.all() + + conn + |> put_view(AdminAPI.ChatView) + |> render("index.json", chats: chats) + else + _ -> {:error, :not_found} + end + end + def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do user = User.get_cached_by_nickname(nickname) diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex new file mode 100644 index 000000000..af8ff8292 --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex @@ -0,0 +1,85 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ChatController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.Chat + alias Pleroma.Chat.MessageReference + alias Pleroma.ModerationLog + alias Pleroma.Pagination + alias Pleroma.Web.AdminAPI + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView + alias Pleroma.Web.Plugs.OAuthScopesPlug + + require Logger + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + OAuthScopesPlug, + %{scopes: ["read:chats"], admin: true} when action in [:show, :messages] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write:chats"], admin: true} when action in [:delete_message] + ) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ChatOperation + + def delete_message(%{assigns: %{user: user}} = conn, %{ + message_id: message_id, + id: chat_id + }) do + with %MessageReference{object: %{data: %{"id" => object_ap_id}}} = cm_ref <- + MessageReference.get_by_id(message_id), + ^chat_id <- to_string(cm_ref.chat_id), + %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object_ap_id), + {:ok, _} <- CommonAPI.delete(activity_id, user) do + ModerationLog.insert_log(%{ + action: "chat_message_delete", + actor: user, + subject_id: message_id + }) + + conn + |> put_view(MessageReferenceView) + |> render("show.json", chat_message_reference: cm_ref) + else + _e -> + {:error, :could_not_delete} + end + end + + def messages(conn, %{id: id} = params) do + with %Chat{} = chat <- Chat.get_by_id(id) do + cm_refs = + chat + |> MessageReference.for_chat_query() + |> Pagination.fetch_paginated(params) + + conn + |> put_view(MessageReferenceView) + |> render("index.json", chat_message_references: cm_refs) + else + _ -> + conn + |> put_status(:not_found) + |> json(%{error: "not found"}) + end + end + + def show(conn, %{id: id}) do + with %Chat{} = chat <- Chat.get_by_id(id) do + conn + |> put_view(AdminAPI.ChatView) + |> render("show.json", chat: chat) + end + end +end diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex index 0df13007f..5d155af3d 100644 --- a/lib/pleroma/web/admin_api/controllers/config_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do alias Pleroma.Config alias Pleroma.ConfigDB - alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update) diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex new file mode 100644 index 000000000..37dbfeb72 --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do + use Pleroma.Web, :controller + + alias Pleroma.Web.InstanceDocument + alias Pleroma.Web.Plugs.InstanceStatic + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation + + plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :show) + plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [:update, :delete]) + + def show(conn, %{name: document_name}) do + with {:ok, url} <- InstanceDocument.get(document_name), + {:ok, content} <- File.read(InstanceStatic.file_path(url)) do + conn + |> put_resp_content_type("text/html") + |> send_resp(200, content) + end + end + + def update(%{body_params: %{file: file}} = conn, %{name: document_name}) do + with {:ok, url} <- InstanceDocument.put(document_name, file.path) do + json(conn, %{"url" => url}) + end + end + + def delete(conn, %{name: document_name}) do + with :ok <- InstanceDocument.delete(document_name) do + json(conn, %{}) + end + end +end diff --git a/lib/pleroma/web/admin_api/controllers/invite_controller.ex b/lib/pleroma/web/admin_api/controllers/invite_controller.ex index 7d169b8d2..6a9b4038a 100644 --- a/lib/pleroma/web/admin_api/controllers/invite_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/invite_controller.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.AdminAPI.InviteController do import Pleroma.Web.ControllerHelper, only: [json_response: 3] alias Pleroma.Config - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.UserInviteToken + alias Pleroma.Web.Plugs.OAuthScopesPlug require Logger diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex index 131e22d78..6d92e9f7f 100644 --- a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do use Pleroma.Web, :controller - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.ApiSpec.Admin, as: Spec alias Pleroma.Web.MediaProxy + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex similarity index 97% rename from lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex rename to lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex index dca23ea73..116a05a4d 100644 --- a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppController do import Pleroma.Web.ControllerHelper, only: [json_response: 3] - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.OAuth.App + alias Pleroma.Web.Plugs.OAuthScopesPlug require Logger diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex index 95d06dde7..611388447 100644 --- a/lib/pleroma/web/admin_api/controllers/relay_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/relay_controller.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Web.AdminAPI.RelayController do use Pleroma.Web, :controller alias Pleroma.ModerationLog - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.ActivityPub.Relay + alias Pleroma.Web.Plugs.OAuthScopesPlug require Logger @@ -33,11 +33,7 @@ def index(conn, _params) do def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do with {:ok, _message} <- Relay.follow(target) do - ModerationLog.insert_log(%{ - action: "relay_follow", - actor: admin, - target: target - }) + ModerationLog.insert_log(%{action: "relay_follow", actor: admin, target: target}) json(conn, %{actor: target, followed_back: target in Relay.following()}) else @@ -48,13 +44,9 @@ def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, end end - def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do - with {:ok, _message} <- Relay.unfollow(target) do - ModerationLog.insert_log(%{ - action: "relay_unfollow", - actor: admin, - target: target - }) + def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target} = params} = conn, _) do + with {:ok, _message} <- Relay.unfollow(target, %{force: params[:force]}) do + ModerationLog.insert_log(%{action: "relay_unfollow", actor: admin, target: target}) json(conn, target) else diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex index 4c011e174..86da93893 100644 --- a/lib/pleroma/web/admin_api/controllers/report_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/report_controller.ex @@ -9,12 +9,12 @@ defmodule Pleroma.Web.AdminAPI.ReportController do alias Pleroma.Activity alias Pleroma.ModerationLog - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.ReportNote alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug require Logger diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex index bc48cc527..2bb437cfe 100644 --- a/lib/pleroma/web/admin_api/controllers/status_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex @@ -7,10 +7,10 @@ defmodule Pleroma.Web.AdminAPI.StatusController do alias Pleroma.Activity alias Pleroma.ModerationLog - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug require Logger diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index 9c477feab..bda7ea19c 100644 --- a/lib/pleroma/web/admin_api/views/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -39,7 +39,7 @@ def render("credentials.json", %{user: user, for: for_user}) do :fields, :name, :nickname, - :locked, + :is_locked, :no_rich_text, :default_scope, :hide_follows, diff --git a/lib/pleroma/web/admin_api/views/chat_view.ex b/lib/pleroma/web/admin_api/views/chat_view.ex new file mode 100644 index 000000000..847df1423 --- /dev/null +++ b/lib/pleroma/web/admin_api/views/chat_view.ex @@ -0,0 +1,30 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ChatView do + use Pleroma.Web, :view + + alias Pleroma.Chat + alias Pleroma.User + alias Pleroma.Web.MastodonAPI + alias Pleroma.Web.PleromaAPI + + def render("index.json", %{chats: chats} = opts) do + render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats)) + end + + def render("show.json", %{chat: %Chat{user_id: user_id}} = opts) do + user = User.get_by_id(user_id) + sender = MastodonAPI.AccountView.render("show.json", user: user, skip_visibility_check: true) + + serialized_chat = PleromaAPI.ChatView.render("show.json", opts) + + serialized_chat + |> Map.put(:sender, sender) + |> Map.put(:receiver, serialized_chat[:account]) + |> Map.delete(:account) + end + + def render(view, opts), do: PleromaAPI.ChatView.render(view, opts) +end diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 773f798fe..535556370 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -52,7 +52,7 @@ def render("show.json", %{report: report, user: user, account: account, statuses end def render("index_notes.json", %{notes: notes}) when is_list(notes) do - Enum.map(notes, &render(__MODULE__, "show_note.json", &1)) + Enum.map(notes, &render(__MODULE__, "show_note.json", Map.from_struct(&1))) end def render("index_notes.json", _), do: [] diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex index 500800be2..6042a22b6 100644 --- a/lib/pleroma/web/admin_api/views/status_view.ex +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do require Pleroma.Constants alias Pleroma.Web.AdminAPI + alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI defdelegate merge_account_views(user), to: AdminAPI.AccountView @@ -17,7 +18,7 @@ def render("index.json", opts) do end def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do - user = MastodonAPI.StatusView.get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) MastodonAPI.StatusView.render("show.json", opts) |> Map.merge(%{account: merge_account_views(user)}) diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 79fd5f871..93a5273e3 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -13,10 +13,15 @@ defmodule Pleroma.Web.ApiSpec do @impl OpenApi def spec do %OpenApi{ - servers: [ - # Populate the Server info from a phoenix endpoint - OpenApiSpex.Server.from_endpoint(Endpoint) - ], + servers: + if Phoenix.Endpoint.server?(:pleroma, Endpoint) do + [ + # Populate the Server info from a phoenix endpoint + OpenApiSpex.Server.from_endpoint(Endpoint) + ] + else + [] + end, info: %OpenApiSpex.Info{ title: "Pleroma", description: Application.spec(:pleroma, :description) |> to_string(), diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index fbfc27d6f..6d1a7ebbc 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -115,6 +115,10 @@ defp cast_and_validate(spec, operation, conn, content_type, false = _strict) do %{reason: :unexpected_field, name: name, path: [name]}, params -> Map.delete(params, name) + # Filter out empty params + %{reason: :invalid_type, path: [name_atom], value: ""}, params -> + Map.delete(params, to_string(name_atom)) + %{reason: :invalid_enum, name: nil, path: path, value: value}, params -> path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string() update_in(params, path, &List.delete(&1, value)) diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 2a7f1a706..34de2ed57 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -72,7 +72,11 @@ def empty_object_response do end def empty_array_response do - Operation.response("Empty array", "application/json", %Schema{type: :array, example: []}) + Operation.response("Empty array", "application/json", %Schema{ + type: :array, + items: %Schema{type: :object, example: %{}}, + example: [] + }) end def no_content_response do diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index aaebc9b5c..d90ddb787 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -372,6 +372,10 @@ def identity_proofs_operation do tags: ["accounts"], summary: "Identity proofs", operationId: "AccountController.identity_proofs", + # Validators complains about unused path params otherwise + parameters: [ + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"} + ], description: "Not implemented", responses: %{ 200 => empty_array_response() @@ -469,7 +473,6 @@ defp create_response do identifier: %Schema{type: :string}, message: %Schema{type: :string} }, - required: [], # Note: example of successful registration with failed login response: # example: %{ # "identifier" => "missing_confirmed_email", @@ -530,7 +533,7 @@ defp update_credentials_request do nullable: true, oneOf: [ %Schema{type: :array, items: attribute_field()}, - %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}} + %Schema{type: :object, additionalProperties: attribute_field()} ] }, # NOTE: `source` field is not supported diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex new file mode 100644 index 000000000..d3e5dfc1c --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex @@ -0,0 +1,96 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do + alias OpenApiSpex.Operation + alias Pleroma.Web.ApiSpec.Schemas.Chat + alias Pleroma.Web.ApiSpec.Schemas.ChatMessage + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def delete_message_operation do + %Operation{ + tags: ["admin", "chat"], + summary: "Delete an individual chat message", + operationId: "AdminAPI.ChatController.delete_message", + parameters: [ + Operation.parameter(:id, :path, :string, "The ID of the Chat"), + Operation.parameter(:message_id, :path, :string, "The ID of the message") + ], + responses: %{ + 200 => + Operation.response( + "The deleted ChatMessage", + "application/json", + ChatMessage + ) + }, + security: [ + %{ + "oAuth" => ["write:chats"] + } + ] + } + end + + def messages_operation do + %Operation{ + tags: ["admin", "chat"], + summary: "Get the most recent messages of the chat", + operationId: "AdminAPI.ChatController.messages", + parameters: + [Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++ + pagination_params(), + responses: %{ + 200 => + Operation.response( + "The messages in the chat", + "application/json", + Pleroma.Web.ApiSpec.ChatOperation.chat_messages_response() + ) + }, + security: [ + %{ + "oAuth" => ["read:chats"] + } + ] + } + end + + def show_operation do + %Operation{ + tags: ["chat"], + summary: "Create a chat", + operationId: "AdminAPI.ChatController.show", + parameters: [ + Operation.parameter( + :id, + :path, + :string, + "The id of the chat", + required: true, + example: "1234" + ) + ], + responses: %{ + 200 => + Operation.response( + "The existing chat", + "application/json", + Chat + ) + }, + security: [ + %{ + "oAuth" => ["read"] + } + ] + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex new file mode 100644 index 000000000..a120ff4e8 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex @@ -0,0 +1,115 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def show_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Get the instance document", + operationId: "AdminAPI.InstanceDocumentController.show", + security: [%{"oAuth" => ["read"]}], + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => document_content(), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Update the instance document", + operationId: "AdminAPI.InstanceDocumentController.update", + security: [%{"oAuth" => ["write"]}], + requestBody: Helpers.request_body("Parameters", update_request()), + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => Operation.response("InstanceDocument", "application/json", instance_document()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp update_request do + %Schema{ + title: "UpdateRequest", + description: "POST body for uploading the file", + type: :object, + required: [:file], + properties: %{ + file: %Schema{ + type: :string, + format: :binary, + description: "The file to be uploaded, using multipart form data." + } + } + } + end + + def delete_operation do + %Operation{ + tags: ["Admin", "InstanceDocument"], + summary: "Get the instance document", + operationId: "AdminAPI.InstanceDocumentController.delete", + security: [%{"oAuth" => ["write"]}], + parameters: [ + Operation.parameter(:name, :path, %Schema{type: :string}, "The document name", + required: true + ) + | Helpers.admin_api_params() + ], + responses: %{ + 200 => Operation.response("InstanceDocument", "application/json", instance_document()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp instance_document do + %Schema{ + title: "InstanceDocument", + type: :object, + properties: %{ + url: %Schema{type: :string} + }, + example: %{ + "url" => "https://example.com/static/terms-of-service.html" + } + } + end + + defp document_content do + Operation.response("InstanceDocumentContent", "text/html", %Schema{ + type: :string, + example: "

Instance panel

" + }) + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex similarity index 100% rename from lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex rename to lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex index e06b2d164..f754bb9f5 100644 --- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex @@ -56,7 +56,7 @@ def unfollow_operation do operationId: "AdminAPI.RelayController.unfollow", security: [%{"oAuth" => ["write:follows"]}], parameters: admin_api_params(), - requestBody: request_body("Parameters", relay_url()), + requestBody: request_body("Parameters", relay_unfollow()), responses: %{ 200 => Operation.response("Status", "application/json", %Schema{ @@ -91,4 +91,14 @@ defp relay_url do } } end + + defp relay_unfollow do + %Schema{ + type: :object, + properties: %{ + relay_url: %Schema{type: :string, format: :uri}, + force: %Schema{type: :boolean, default: false} + } + } + end end diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex index 56554d5b4..0dcfdb354 100644 --- a/lib/pleroma/web/api_spec/operations/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -158,7 +158,8 @@ def messages_operation do "The messages in the chat", "application/json", chat_messages_response() - ) + ), + 404 => Operation.response("Not Found", "application/json", ApiError) }, security: [ %{ diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex index 2f812ac77..5ff263ceb 100644 --- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex +++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex @@ -69,7 +69,7 @@ defp custom_emoji do type: :object, properties: %{ category: %Schema{type: :string}, - tags: %Schema{type: :array} + tags: %Schema{type: :array, items: %Schema{type: :string}} } } ], diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex index 1a49fece0..745d41f88 100644 --- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex +++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex @@ -23,7 +23,7 @@ def index_operation do parameters: [ Operation.parameter(:id, :path, FlakeID, "Status ID", required: true), Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji", - required: false + required: nil ) ], security: [%{"oAuth" => ["read:statuses"]}], diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex index c88ed5dd0..f6e73968a 100644 --- a/lib/pleroma/web/api_spec/operations/list_operation.ex +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -114,7 +114,7 @@ def add_to_list_operation do description: "Add accounts to the given list.", operationId: "ListController.add_to_list", parameters: [id_param()], - requestBody: add_remove_accounts_request(), + requestBody: add_remove_accounts_request(true), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) @@ -127,8 +127,16 @@ def remove_from_list_operation do tags: ["Lists"], summary: "Remove accounts from list", operationId: "ListController.remove_from_list", - parameters: [id_param()], - requestBody: add_remove_accounts_request(), + parameters: [ + id_param(), + Operation.parameter( + :account_ids, + :query, + %Schema{type: :array, items: %Schema{type: :string}}, + "Array of account IDs" + ) + ], + requestBody: add_remove_accounts_request(false), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) @@ -171,7 +179,7 @@ defp create_update_request do ) end - defp add_remove_accounts_request do + defp add_remove_accounts_request(required) when is_boolean(required) do request_body( "Parameters", %Schema{ @@ -179,10 +187,9 @@ defp add_remove_accounts_request do type: :object, properties: %{ account_ids: %Schema{type: :array, description: "Array of account IDs", items: FlakeID} - }, - required: [:account_ids] + } }, - required: true + required: required ) end end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex new file mode 100644 index 000000000..a56641426 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -0,0 +1,139 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def create_operation do + %Operation{ + tags: ["Emoji Packs"], + summary: "Add new file to the pack", + operationId: "PleromaAPI.EmojiPackController.add_file", + security: [%{"oAuth" => ["write"]}], + requestBody: request_body("Parameters", create_request(), required: true), + parameters: [name_param()], + responses: %{ + 200 => Operation.response("Files Object", "application/json", files_object()), + 422 => Operation.response("Unprocessable Entity", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 409 => Operation.response("Conflict", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + type: :object, + required: [:file], + properties: %{ + file: %Schema{ + description: + "File needs to be uploaded with the multipart request or link to remote file", + anyOf: [ + %Schema{type: :string, format: :binary}, + %Schema{type: :string, format: :uri} + ] + }, + shortcode: %Schema{ + type: :string, + description: + "Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename." + }, + filename: %Schema{ + type: :string, + description: + "New emoji file name. If not specified will be taken from original filename." + } + } + } + end + + def update_operation do + %Operation{ + tags: ["Emoji Packs"], + summary: "Add new file to the pack", + operationId: "PleromaAPI.EmojiPackController.update_file", + security: [%{"oAuth" => ["write"]}], + requestBody: request_body("Parameters", update_request(), required: true), + parameters: [name_param()], + responses: %{ + 200 => Operation.response("Files Object", "application/json", files_object()), + 404 => Operation.response("Not Found", "application/json", ApiError), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 409 => Operation.response("Conflict", "application/json", ApiError), + 422 => Operation.response("Unprocessable Entity", "application/json", ApiError) + } + } + end + + defp update_request do + %Schema{ + type: :object, + required: [:shortcode, :new_shortcode, :new_filename], + properties: %{ + shortcode: %Schema{ + type: :string, + description: "Emoji file shortcode" + }, + new_shortcode: %Schema{ + type: :string, + description: "New emoji file shortcode" + }, + new_filename: %Schema{ + type: :string, + description: "New filename for emoji file" + }, + force: %Schema{ + type: :boolean, + description: "With true value to overwrite existing emoji with new shortcode", + default: false + } + } + } + end + + def delete_operation do + %Operation{ + tags: ["Emoji Packs"], + summary: "Delete emoji file from pack", + operationId: "PleromaAPI.EmojiPackController.delete_file", + security: [%{"oAuth" => ["write"]}], + parameters: [ + name_param(), + Operation.parameter(:shortcode, :query, :string, "File shortcode", + example: "cofe", + required: true + ) + ], + responses: %{ + 200 => Operation.response("Files Object", "application/json", files_object()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError), + 422 => Operation.response("Unprocessable Entity", "application/json", ApiError) + } + } + end + + defp name_param do + Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true) + end + + defp files_object do + %Schema{ + type: :object, + additionalProperties: %Schema{type: :string}, + description: "Object with emoji names as keys and filenames as values" + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index b2b4f8713..79f52dcb3 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -19,7 +19,21 @@ def remote_operation do tags: ["Emoji Packs"], summary: "Make request to another instance for emoji packs list", security: [%{"oAuth" => ["write"]}], - parameters: [url_param()], + parameters: [ + url_param(), + Operation.parameter( + :page, + :query, + %Schema{type: :integer, default: 1}, + "Page" + ), + Operation.parameter( + :page_size, + :query, + %Schema{type: :integer, default: 30}, + "Number of emoji to return" + ) + ], operationId: "PleromaAPI.EmojiPackController.remote", responses: %{ 200 => emoji_packs_response(), @@ -175,111 +189,6 @@ def update_operation do } end - def add_file_operation do - %Operation{ - tags: ["Emoji Packs"], - summary: "Add new file to the pack", - operationId: "PleromaAPI.EmojiPackController.add_file", - security: [%{"oAuth" => ["write"]}], - requestBody: request_body("Parameters", add_file_request(), required: true), - parameters: [name_param()], - responses: %{ - 200 => Operation.response("Files Object", "application/json", files_object()), - 400 => Operation.response("Bad Request", "application/json", ApiError), - 409 => Operation.response("Conflict", "application/json", ApiError) - } - } - end - - defp add_file_request do - %Schema{ - type: :object, - required: [:file], - properties: %{ - file: %Schema{ - description: - "File needs to be uploaded with the multipart request or link to remote file", - anyOf: [ - %Schema{type: :string, format: :binary}, - %Schema{type: :string, format: :uri} - ] - }, - shortcode: %Schema{ - type: :string, - description: - "Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename." - }, - filename: %Schema{ - type: :string, - description: - "New emoji file name. If not specified will be taken from original filename." - } - } - } - end - - def update_file_operation do - %Operation{ - tags: ["Emoji Packs"], - summary: "Add new file to the pack", - operationId: "PleromaAPI.EmojiPackController.update_file", - security: [%{"oAuth" => ["write"]}], - requestBody: request_body("Parameters", update_file_request(), required: true), - parameters: [name_param()], - responses: %{ - 200 => Operation.response("Files Object", "application/json", files_object()), - 400 => Operation.response("Bad Request", "application/json", ApiError), - 409 => Operation.response("Conflict", "application/json", ApiError) - } - } - end - - defp update_file_request do - %Schema{ - type: :object, - required: [:shortcode, :new_shortcode, :new_filename], - properties: %{ - shortcode: %Schema{ - type: :string, - description: "Emoji file shortcode" - }, - new_shortcode: %Schema{ - type: :string, - description: "New emoji file shortcode" - }, - new_filename: %Schema{ - type: :string, - description: "New filename for emoji file" - }, - force: %Schema{ - type: :boolean, - description: "With true value to overwrite existing emoji with new shortcode", - default: false - } - } - } - end - - def delete_file_operation do - %Operation{ - tags: ["Emoji Packs"], - summary: "Delete emoji file from pack", - operationId: "PleromaAPI.EmojiPackController.delete_file", - security: [%{"oAuth" => ["write"]}], - parameters: [ - name_param(), - Operation.parameter(:shortcode, :query, :string, "File shortcode", - example: "cofe", - required: true - ) - ], - responses: %{ - 200 => Operation.response("Files Object", "application/json", files_object()), - 400 => Operation.response("Bad Request", "application/json", ApiError) - } - } - end - def import_from_filesystem_operation do %Operation{ tags: ["Emoji Packs"], @@ -297,7 +206,7 @@ def import_from_filesystem_operation do end defp name_param do - Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) + Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true) end defp url_param do diff --git a/lib/pleroma/web/api_spec/operations/user_import_operation.ex b/lib/pleroma/web/api_spec/operations/user_import_operation.ex new file mode 100644 index 000000000..a50314fb7 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/user_import_operation.ex @@ -0,0 +1,80 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.UserImportOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + import Pleroma.Web.ApiSpec.Helpers + + @spec open_api_operation(atom) :: Operation.t() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def follow_operation do + %Operation{ + tags: ["follow_import"], + summary: "Imports your follows.", + operationId: "UserImportController.follow", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:follow"]}] + } + end + + def blocks_operation do + %Operation{ + tags: ["blocks_import"], + summary: "Imports your blocks.", + operationId: "UserImportController.blocks", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:blocks"]}] + } + end + + def mutes_operation do + %Operation{ + tags: ["mutes_import"], + summary: "Imports your mutes.", + operationId: "UserImportController.mutes", + requestBody: request_body("Parameters", import_request(), required: true), + responses: %{ + 200 => ok_response(), + 500 => Operation.response("Error", "application/json", ApiError) + }, + security: [%{"oAuth" => ["write:mutes"]}] + } + end + + defp import_request do + %Schema{ + type: :object, + required: [:list], + properties: %{ + list: %Schema{ + description: + "STRING or FILE containing a whitespace-separated list of accounts to import.", + anyOf: [ + %Schema{type: :string, format: :binary}, + %Schema{type: :string} + ] + } + } + } + end + + defp ok_response do + Operation.response("Ok", "application/json", %Schema{type: :string, example: "ok"}) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/chat.ex b/lib/pleroma/web/api_spec/schemas/chat.ex index b4986b734..65f908e33 100644 --- a/lib/pleroma/web/api_spec/schemas/chat.ex +++ b/lib/pleroma/web/api_spec/schemas/chat.ex @@ -50,7 +50,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Chat do "fields" => [] }, "statuses_count" => 1, - "locked" => false, + "is_locked" => false, "created_at" => "2020-04-16T13:40:15.000Z", "display_name" => "lain", "fields" => [], diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex index bbf2a4427..9d2799618 100644 --- a/lib/pleroma/web/api_spec/schemas/chat_message.ex +++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Emoji require OpenApiSpex @@ -18,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do chat_id: %Schema{type: :string}, content: %Schema{type: :string, nullable: true}, created_at: %Schema{type: :string, format: :"date-time"}, - emojis: %Schema{type: :array}, + emojis: %Schema{type: :array, items: Emoji}, attachment: %Schema{type: :object, nullable: true}, card: %Schema{ type: :object, diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex index 0520d0848..addefa9d3 100644 --- a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -27,9 +27,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}}, sensitive: %Schema{type: :boolean, nullable: true}, spoiler_text: %Schema{type: :string, nullable: true}, - visibility: %Schema{type: VisibilityScope, nullable: true}, + visibility: %Schema{allOf: [VisibilityScope], nullable: true}, scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true}, - poll: %Schema{type: Poll, nullable: true}, + poll: %Schema{allOf: [Poll], nullable: true}, in_reply_to_id: %Schema{type: :string, nullable: true} } } diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 947e42890..e6890df2d 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -252,7 +252,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do "header" => "http://localhost:4001/images/banner.png", "header_static" => "http://localhost:4001/images/banner.png", "id" => "9toJCsKN7SmSf3aj5c", - "locked" => false, + "is_locked" => false, "note" => "Tester Number 6", "pleroma" => %{ "background_image" => nil, diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 200ca03dc..d6d2a8d06 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -3,10 +3,10 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Auth.PleromaAuthenticator do - alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.Registration alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.Plugs.AuthenticationPlug import Pleroma.Web.Auth.Authenticator, only: [fetch_credentials: 1, fetch_user: 1] @@ -68,7 +68,7 @@ def create_from_registration( nickname = value([registration_attrs["nickname"], Registration.nickname(registration)]) email = value([registration_attrs["email"], Registration.email(registration)]) name = value([registration_attrs["name"], Registration.name(registration)]) || nickname - bio = value([registration_attrs["bio"], Registration.description(registration)]) + bio = value([registration_attrs["bio"], Registration.description(registration)]) || "" random_password = :crypto.strong_rand_bytes(64) |> Base.encode64() diff --git a/lib/pleroma/web/auth/totp_authenticator.ex b/lib/pleroma/web/auth/totp_authenticator.ex index 1794e407c..edc9871ea 100644 --- a/lib/pleroma/web/auth/totp_authenticator.ex +++ b/lib/pleroma/web/auth/totp_authenticator.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.Auth.TOTPAuthenticator do alias Pleroma.MFA alias Pleroma.MFA.TOTP - alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.User + alias Pleroma.Web.Plugs.AuthenticationPlug @doc "Verify code or check backup code." @spec verify(String.t(), User.t()) :: diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api.ex similarity index 96% rename from lib/pleroma/web/common_api/common_api.ex rename to lib/pleroma/web/common_api.ex index 3c7b9e794..60a50b027 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -4,7 +4,6 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity - alias Pleroma.ActivityExpiration alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object @@ -384,9 +383,9 @@ def get_replied_to_visibility(activity) do def check_expiry_date({:ok, nil} = res), do: res def check_expiry_date({:ok, in_seconds}) do - expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds) + expiry = DateTime.add(DateTime.utc_now(), in_seconds) - if ActivityExpiration.expires_late_enough?(expiry) do + if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do {:ok, expiry} else {:error, "Expiry date is too soon"} @@ -455,7 +454,8 @@ def unpin(id, user) do end def add_mute(user, activity) do - with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do + with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]), + _ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do {:ok, activity} else {:error, _} -> {:error, dgettext("errors", "conversation is already muted")} @@ -553,4 +553,21 @@ def hide_reblogs(%User{} = user, %User{} = target) do def show_reblogs(%User{} = user, %User{} = target) do UserRelationship.delete_reblog_mute(user, target) end + + def get_user(ap_id, fake_record_fallback \\ true) do + cond do + user = User.get_cached_by_ap_id(ap_id) -> + user + + user = User.get_by_guessed_nickname(ap_id) -> + user + + fake_record_fallback -> + # TODO: refactor (fake records is never a good idea) + User.error_user(ap_id) + + true -> + nil + end + end end diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index f849b2e01..548f76609 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -202,7 +202,7 @@ defp changes(draft) do additional = case draft.expires_at do - %NaiveDateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at) + %DateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at) _ -> additional end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 9d7b24eb2..3b71adf0e 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -12,12 +12,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object - alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.MediaProxy + alias Pleroma.Web.Plugs.AuthenticationPlug require Logger require Pleroma.Constants @@ -274,7 +274,7 @@ defp build_attachment_link(_), do: "" def format_input(text, format, options \\ []) @doc """ - Formatting text to plain text. + Formatting text to plain text, BBCode, HTML, or Markdown """ def format_input(text, "text/plain", options) do text @@ -285,9 +285,6 @@ def format_input(text, "text/plain", options) do end).() end - @doc """ - Formatting text as BBCode. - """ def format_input(text, "text/bbcode", options) do text |> String.replace(~r/\r/, "") @@ -297,18 +294,12 @@ def format_input(text, "text/bbcode", options) do |> Formatter.linkify(options) end - @doc """ - Formatting text to html. - """ def format_input(text, "text/html", options) do text |> Formatter.html_escape("text/html") |> Formatter.linkify(options) end - @doc """ - Formatting text to markdown. - """ def format_input(text, "text/markdown", options) do text |> Formatter.mentions_escape(options) diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 6445966e0..69188a882 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -48,13 +48,13 @@ defp param_to_integer(val, default) when is_binary(val) do defp param_to_integer(_, default), do: default - def add_link_headers(conn, activities, extra_params \\ %{}) + def add_link_headers(conn, entries, extra_params \\ %{}) - def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _activities, _extra_params), + def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _entries, _extra_params), do: conn - def add_link_headers(conn, activities, extra_params) do - case get_pagination_fields(conn, activities, extra_params) do + def add_link_headers(conn, entries, extra_params) do + case get_pagination_fields(conn, entries, extra_params) do %{"next" => next_url, "prev" => prev_url} -> put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"") @@ -78,19 +78,15 @@ defp build_pagination_fields(conn, min_id, max_id, extra_params) do } end - def get_pagination_fields(conn, activities, extra_params \\ %{}) do - case List.last(activities) do + def get_pagination_fields(conn, entries, extra_params \\ %{}) do + case List.last(entries) do %{pagination_id: max_id} when not is_nil(max_id) -> - %{pagination_id: min_id} = - activities - |> List.first() + %{pagination_id: min_id} = List.first(entries) build_pagination_fields(conn, min_id, max_id, extra_params) %{id: max_id} -> - %{id: min_id} = - activities - |> List.first() + %{id: min_id} = List.first(entries) build_pagination_fields(conn, min_id, max_id, extra_params) diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 8b153763d..f26542e88 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -7,19 +7,23 @@ defmodule Pleroma.Web.Endpoint do require Pleroma.Constants + alias Pleroma.Config + socket("/socket", Pleroma.Web.UserSocket) - plug(Pleroma.Plugs.SetLocalePlug) + plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint]) + + plug(Pleroma.Web.Plugs.SetLocalePlug) plug(CORSPlug) - plug(Pleroma.Plugs.HTTPSecurityPlug) - plug(Pleroma.Plugs.UploadedMedia) + plug(Pleroma.Web.Plugs.HTTPSecurityPlug) + plug(Pleroma.Web.Plugs.UploadedMedia) @static_cache_control "public, no-cache" # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well # Cache-control headers are duplicated in case we turn off etags in the future - plug(Pleroma.Plugs.InstanceStatic, + plug(Pleroma.Web.Plugs.InstanceStatic, at: "/", gzip: true, cache_control_for_etags: @static_cache_control, @@ -29,7 +33,7 @@ defmodule Pleroma.Web.Endpoint do ) # Careful! No `only` restriction here, as we don't know what frontends contain. - plug(Pleroma.Plugs.FrontendStatic, + plug(Pleroma.Web.Plugs.FrontendStatic, at: "/", frontend_type: :primary, gzip: true, @@ -41,7 +45,7 @@ defmodule Pleroma.Web.Endpoint do plug(Plug.Static.IndexHtml, at: "/pleroma/admin/") - plug(Pleroma.Plugs.FrontendStatic, + plug(Pleroma.Web.Plugs.FrontendStatic, at: "/pleroma/admin", frontend_type: :admin, gzip: true, @@ -79,26 +83,26 @@ defmodule Pleroma.Web.Endpoint do plug(Phoenix.CodeReloader) end - plug(Pleroma.Plugs.TrailingFormatPlug) + plug(Pleroma.Web.Plugs.TrailingFormatPlug) plug(Plug.RequestId) plug(Plug.Logger, log: :debug) plug(Plug.Parsers, parsers: [ :urlencoded, - {:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}}, + {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}}, :json ], pass: ["*/*"], json_decoder: Jason, - length: Pleroma.Config.get([:instance, :upload_limit]), + length: Config.get([:instance, :upload_limit]), body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []} ) plug(Plug.MethodOverride) plug(Plug.Head) - secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag]) + secure_cookies = Config.get([__MODULE__, :secure_cookie_flag]) cookie_name = if secure_cookies, @@ -106,7 +110,7 @@ defmodule Pleroma.Web.Endpoint do else: "pleroma_key" extra = - Pleroma.Config.get([__MODULE__, :extra_cookie_attrs]) + Config.get([__MODULE__, :extra_cookie_attrs]) |> Enum.join(";") # The session will be stored in the cookie and signed, @@ -116,13 +120,13 @@ defmodule Pleroma.Web.Endpoint do Plug.Session, store: :cookie, key: cookie_name, - signing_salt: Pleroma.Config.get([__MODULE__, :signing_salt], "CqaoopA2"), + signing_salt: Config.get([__MODULE__, :signing_salt], "CqaoopA2"), http_only: true, secure: secure_cookies, extra: extra ) - plug(Pleroma.Plugs.RemoteIp) + plug(Pleroma.Web.Plugs.RemoteIp) defmodule Instrumenter do use Prometheus.PhoenixInstrumenter @@ -136,8 +140,34 @@ defmodule MetricsExporter do use Prometheus.PlugExporter end + defmodule MetricsExporterCaller do + @behaviour Plug + + def init(opts), do: opts + + def call(conn, opts) do + prometheus_config = Application.get_env(:prometheus, MetricsExporter, []) + ip_whitelist = List.wrap(prometheus_config[:ip_whitelist]) + + cond do + !prometheus_config[:enabled] -> + conn + + ip_whitelist != [] and + !Enum.find(ip_whitelist, fn ip -> + Pleroma.Helpers.InetHelper.parse_address(ip) == {:ok, conn.remote_ip} + end) -> + conn + + true -> + MetricsExporter.call(conn, opts) + end + end + end + plug(PipelineInstrumenter) - plug(MetricsExporter) + + plug(MetricsExporterCaller) plug(Pleroma.Web.Router) diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex similarity index 95% rename from lib/pleroma/web/fallback_redirect_controller.ex rename to lib/pleroma/web/fallback/redirect_controller.ex index 431ad5485..6f759d559 100644 --- a/lib/pleroma/web/fallback_redirect_controller.ex +++ b/lib/pleroma/web/fallback/redirect_controller.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Fallback.RedirectController do +defmodule Pleroma.Web.Fallback.RedirectController do use Pleroma.Web, :controller require Logger @@ -75,7 +75,7 @@ def empty(conn, _params) do end defp index_file_path do - Pleroma.Plugs.InstanceStatic.file_path("index.html") + Pleroma.Web.Plugs.InstanceStatic.file_path("index.html") end defp build_tags(conn, params) do diff --git a/lib/pleroma/web/fed_sockets.ex b/lib/pleroma/web/fed_sockets.ex new file mode 100644 index 000000000..1fd5899c8 --- /dev/null +++ b/lib/pleroma/web/fed_sockets.ex @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets do + @moduledoc """ + This documents the FedSockets framework. A framework for federating + ActivityPub objects between servers via persistant WebSocket connections. + + FedSockets allow servers to authenticate on first contact and maintain that + connection, eliminating the need to authenticate every time data needs to be shared. + + ## Protocol + FedSockets currently support 2 types of data transfer: + * `publish` method which doesn't require a response + * `fetch` method requires a response be sent + + ### Publish + The publish operation sends a json encoded map of the shape: + %{action: :publish, data: json} + and accepts (but does not require) a reply of form: + %{"action" => "publish_reply"} + + The outgoing params represent + * data: ActivityPub object encoded into json + + + ### Fetch + The fetch operation sends a json encoded map of the shape: + %{action: :fetch, data: id, uuid: fetch_uuid} + and requires a reply of form: + %{"action" => "fetch_reply", "uuid" => uuid, "data" => data} + + The outgoing params represent + * id: an ActivityPub object URI + * uuid: a unique uuid generated by the sender + + The reply params represent + * data: an ActivityPub object encoded into json + * uuid: the uuid sent along with the fetch request + + ## Examples + Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module. + + A typical publish operation can be performed through the following code, and a fetch operation in a similar manner. + + case FedSockets.get_or_create_fed_socket(inbox) do + {:ok, fedsocket} -> + FedSockets.publish(fedsocket, json) + + _ -> + alternative_publish(inbox, actor, json, params) + end + + ## Configuration + FedSockets have the following config settings + + config :pleroma, :fed_sockets, + enabled: true, + ping_interval: :timer.seconds(15), + connection_duration: :timer.hours(1), + rejection_duration: :timer.hours(1), + fed_socket_fetches: [ + default: 12_000, + interval: 3_000, + lazy: false + ] + * enabled - turn FedSockets on or off with this flag. Can be toggled at runtime. + * connection_duration - How long a FedSocket can sit idle before it's culled. + * rejection_duration - After failing to make a FedSocket connection a host will be excluded + from further connections for this amount of time + * fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry + * fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry + + Cachex options are + * default: the minimum amount of time a fetch can wait before it times out. + * interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed + * lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement + + """ + require Logger + + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + @doc """ + returns a FedSocket for the given origin. Will reuse an existing one or create a new one. + + address is expected to be a fully formed URL such as: + "http://www.example.com" or "http://www.example.com:8080" + + It can and usually does include additional path parameters, + but these are ignored as the FedSockets are organized by host and port info alone. + """ + def get_or_create_fed_socket(address) do + with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)}, + {:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)}, + {:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do + Logger.debug("fedsocket created for - #{inspect(address)}") + {:ok, fed_socket} + else + {:cache, {:ok, socket}} -> + Logger.debug("fedsocket found in cache - #{inspect(address)}") + {:ok, socket} + + {:cache, {:error, :rejected} = e} -> + e + + {:connect, {:error, _host}} -> + Logger.debug("set host rejected for - #{inspect(address)}") + FedRegistry.set_host_rejected(address) + {:error, :rejected} + + {_, {:error, :disabled}} -> + {:error, :disabled} + + {_, {:error, reason}} -> + Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}") + {:error, reason} + end + end + + @doc """ + returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist. + + address is expected to be a fully formed URL such as: + "http://www.example.com" or "http://www.example.com:8080" + """ + def get_fed_socket(address) do + origin = SocketInfo.origin(address) + + with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)}, + {:ok, socket} <- FedRegistry.get_fed_socket(origin) do + {:ok, socket} + else + {:config, _} -> + {:error, :disabled} + + {:error, :rejected} -> + Logger.debug("FedSocket previously rejected - #{inspect(origin)}") + {:error, :rejected} + + {:error, reason} -> + {:error, reason} + end + end + + @doc """ + Sends the supplied data via the publish protocol. + It will not block waiting for a reply. + Returns :ok but this is not an indication of a successful transfer. + + the data is expected to be JSON encoded binary data. + """ + def publish(%SocketInfo{} = fed_socket, json) do + FedSocket.publish(fed_socket, json) + end + + @doc """ + Sends the supplied data via the fetch protocol. + It will block waiting for a reply or timeout. + + Returns {:ok, object} where object is the requested object (or nil) + {:error, :timeout} in the event the message was not responded to + + the id is expected to be the URI of an ActivityPub object. + """ + def fetch(%SocketInfo{} = fed_socket, id) do + FedSocket.fetch(fed_socket, id) + end + + @doc """ + Disconnect all and restart FedSockets. + This is mainly used in development and testing but could be useful in production. + """ + def reset do + FedRegistry + |> Process.whereis() + |> Process.exit(:testing) + end + + def uri_for_origin(origin), + do: "ws://#{origin}/api/fedsocket/v1" +end diff --git a/lib/pleroma/web/fed_sockets/fed_registry.ex b/lib/pleroma/web/fed_sockets/fed_registry.ex new file mode 100644 index 000000000..e00ea69c0 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fed_registry.ex @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FedRegistry do + @moduledoc """ + The FedRegistry stores the active FedSockets for quick retrieval. + + The storage and retrieval portion of the FedRegistry is done in process through + elixir's `Registry` module for speed and its ability to monitor for terminated processes. + + Dropped connections will be caught by `Registry` and deleted. Since the next + message will initiate a new connection there is no reason to try and reconnect at that point. + + Normally outside modules should have no need to call or use the FedRegistry themselves. + """ + + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + require Logger + + @default_rejection_duration 15 * 60 * 1000 + @rejections :fed_socket_rejections + + @doc """ + Retrieves a FedSocket from the Registry given it's origin. + + The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080" + + Will return: + * {:ok, fed_socket} for working FedSockets + * {:error, :rejected} for origins that have been tried and refused within the rejection duration interval + * {:error, some_reason} usually :missing for unknown origins + """ + def get_fed_socket(origin) do + case get_registry_data(origin) do + {:error, reason} -> + {:error, reason} + + {:ok, %{state: :connected} = socket_info} -> + {:ok, socket_info} + end + end + + @doc """ + Adds a connected FedSocket to the Registry. + + Always returns {:ok, fed_socket} + """ + def add_fed_socket(origin, pid \\ nil) do + origin + |> SocketInfo.build(pid) + |> SocketInfo.connect() + |> add_socket_info + end + + defp add_socket_info(%{origin: origin, state: :connected} = socket_info) do + case Registry.register(FedSockets.Registry, origin, socket_info) do + {:ok, _owner} -> + clear_prior_rejection(origin) + Logger.debug("fedsocket added: #{inspect(origin)}") + + {:ok, socket_info} + + {:error, {:already_registered, _pid}} -> + FedSocket.close(socket_info) + existing_socket_info = Registry.lookup(FedSockets.Registry, origin) + + {:ok, existing_socket_info} + + _ -> + {:error, :error_adding_socket} + end + end + + @doc """ + Mark this origin as having rejected a connection attempt. + This will keep it from getting additional connection attempts + for a period of time specified in the config. + + Always returns {:ok, new_reg_data} + """ + def set_host_rejected(uri) do + new_reg_data = + uri + |> SocketInfo.origin() + |> get_or_create_registry_data() + |> set_to_rejected() + |> save_registry_data() + + {:ok, new_reg_data} + end + + @doc """ + Retrieves the FedRegistryData from the Registry given it's origin. + + The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080" + + Will return: + * {:ok, fed_registry_data} for known origins + * {:error, :missing} for uniknown origins + * {:error, :cache_error} indicating some low level runtime issues + """ + def get_registry_data(origin) do + case Registry.lookup(FedSockets.Registry, origin) do + [] -> + if is_rejected?(origin) do + Logger.debug("previously rejected fedsocket requested") + {:error, :rejected} + else + {:error, :missing} + end + + [{_pid, %{state: :connected} = socket_info}] -> + {:ok, socket_info} + + _ -> + {:error, :cache_error} + end + end + + @doc """ + Retrieves a map of all sockets from the Registry. The keys are the origins and the values are the corresponding SocketInfo + """ + def list_all do + (list_all_connected() ++ list_all_rejected()) + |> Enum.into(%{}) + end + + defp list_all_connected do + FedSockets.Registry + |> Registry.select([{{:"$1", :_, :"$3"}, [], [{{:"$1", :"$3"}}]}]) + end + + defp list_all_rejected do + {:ok, keys} = Cachex.keys(@rejections) + + {:ok, registry_data} = + Cachex.execute(@rejections, fn worker -> + Enum.map(keys, fn k -> {k, Cachex.get!(worker, k)} end) + end) + + registry_data + end + + defp clear_prior_rejection(origin), + do: Cachex.del(@rejections, origin) + + defp is_rejected?(origin) do + case Cachex.get(@rejections, origin) do + {:ok, nil} -> + false + + {:ok, _} -> + true + end + end + + defp get_or_create_registry_data(origin) do + case get_registry_data(origin) do + {:error, :missing} -> + %SocketInfo{origin: origin} + + {:ok, socket_info} -> + socket_info + end + end + + defp save_registry_data(%SocketInfo{origin: origin, state: :connected} = socket_info) do + {:ok, true} = Registry.update_value(FedSockets.Registry, origin, fn _ -> socket_info end) + socket_info + end + + defp save_registry_data(%SocketInfo{origin: origin, state: :rejected} = socket_info) do + rejection_expiration = + Pleroma.Config.get([:fed_sockets, :rejection_duration], @default_rejection_duration) + + {:ok, true} = Cachex.put(@rejections, origin, socket_info, ttl: rejection_expiration) + socket_info + end + + defp set_to_rejected(%SocketInfo{} = socket_info), + do: %SocketInfo{socket_info | state: :rejected} +end diff --git a/lib/pleroma/web/fed_sockets/fed_socket.ex b/lib/pleroma/web/fed_sockets/fed_socket.ex new file mode 100644 index 000000000..98d64e65a --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fed_socket.ex @@ -0,0 +1,137 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FedSocket do + @moduledoc """ + The FedSocket module abstracts the actions to be taken taken on connections regardless of + whether the connection started as inbound or outbound. + + + Normally outside modules will have no need to call the FedSocket module directly. + """ + + alias Pleroma.Object + alias Pleroma.Object.Containment + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectView + alias Pleroma.Web.ActivityPub.UserView + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.FedSockets.FetchRegistry + alias Pleroma.Web.FedSockets.IngesterWorker + alias Pleroma.Web.FedSockets.OutgoingHandler + alias Pleroma.Web.FedSockets.SocketInfo + + require Logger + + @shake "61dd18f7-f1e6-49a4-939a-a749fcdc1103" + + def connect_to_host(uri) do + case OutgoingHandler.start_link(uri) do + {:ok, pid} -> + {:ok, pid} + + error -> + {:error, error} + end + end + + def close(%SocketInfo{pid: socket_pid}), + do: Process.send(socket_pid, :close, []) + + def publish(%SocketInfo{pid: socket_pid}, json) do + %{action: :publish, data: json} + |> Jason.encode!() + |> send_packet(socket_pid) + end + + def fetch(%SocketInfo{pid: socket_pid}, id) do + fetch_uuid = FetchRegistry.register_fetch(id) + + %{action: :fetch, data: id, uuid: fetch_uuid} + |> Jason.encode!() + |> send_packet(socket_pid) + + wait_for_fetch_to_return(fetch_uuid, 0) + end + + def receive_package(%SocketInfo{} = fed_socket, json) do + json + |> Jason.decode!() + |> process_package(fed_socket) + end + + defp wait_for_fetch_to_return(uuid, cntr) do + case FetchRegistry.check_fetch(uuid) do + {:error, :waiting} -> + Process.sleep(:math.pow(cntr, 3) |> Kernel.trunc()) + wait_for_fetch_to_return(uuid, cntr + 1) + + {:error, :missing} -> + Logger.error("FedSocket fetch timed out - #{inspect(uuid)}") + {:error, :timeout} + + {:ok, _fr} -> + FetchRegistry.pop_fetch(uuid) + end + end + + defp process_package(%{"action" => "publish", "data" => data}, %{origin: origin} = _fed_socket) do + if Containment.contain_origin(origin, data) do + IngesterWorker.enqueue("ingest", %{"object" => data}) + end + + {:reply, %{"action" => "publish_reply", "status" => "processed"}} + end + + defp process_package(%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}, _fed_socket) do + FetchRegistry.register_fetch_received(uuid, data) + {:noreply, nil} + end + + defp process_package(%{"action" => "fetch", "uuid" => uuid, "data" => ap_id}, _fed_socket) do + {:ok, data} = render_fetched_data(ap_id, uuid) + {:reply, data} + end + + defp process_package(%{"action" => "publish_reply"}, _fed_socket) do + {:noreply, nil} + end + + defp process_package(other, _fed_socket) do + Logger.warn("unknown json packages received #{inspect(other)}") + {:noreply, nil} + end + + defp render_fetched_data(ap_id, uuid) do + {:ok, + %{ + "action" => "fetch_reply", + "status" => "processed", + "uuid" => uuid, + "data" => represent_item(ap_id) + }} + end + + defp represent_item(ap_id) do + case User.get_by_ap_id(ap_id) do + nil -> + object = Object.get_cached_by_ap_id(ap_id) + + if Visibility.is_public?(object) do + Phoenix.View.render_to_string(ObjectView, "object.json", object: object) + else + nil + end + + user -> + Phoenix.View.render_to_string(UserView, "user.json", user: user) + end + end + + defp send_packet(data, socket_pid) do + Process.send(socket_pid, {:send, data}, []) + end + + def shake, do: @shake +end diff --git a/lib/pleroma/web/fed_sockets/fetch_registry.ex b/lib/pleroma/web/fed_sockets/fetch_registry.ex new file mode 100644 index 000000000..7897f0fc6 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/fetch_registry.ex @@ -0,0 +1,151 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FetchRegistry do + @moduledoc """ + The FetchRegistry acts as a broker for fetch requests and return values. + This allows calling processes to block while waiting for a reply. + It doesn't impose it's own process instead using `Cachex` to handle fetches in process, allowing + multi threaded processes to avoid bottlenecking. + + Normally outside modules will have no need to call or use the FetchRegistry themselves. + + The `Cachex` parameters can be controlled from the config. Since exact timeout intervals + aren't necessary the following settings are used by default: + + config :pleroma, :fed_sockets, + fed_socket_fetches: [ + default: 12_000, + interval: 3_000, + lazy: false + ] + + """ + + defmodule FetchRegistryData do + defstruct uuid: nil, + sent_json: nil, + received_json: nil, + sent_at: nil, + received_at: nil + end + + alias Ecto.UUID + + require Logger + + @fetches :fed_socket_fetches + + @doc """ + Registers a json request wth the FetchRegistry and returns the identifying UUID. + """ + def register_fetch(json) do + %FetchRegistryData{uuid: uuid} = + json + |> new_registry_data + |> save_registry_data + + uuid + end + + @doc """ + Reports on the status of a Fetch given the identifying UUID. + + Will return + * {:ok, fetched_object} if a fetch has completed + * {:error, :waiting} if a fetch is still pending + * {:error, other_error} usually :missing to indicate a fetch that has timed out + """ + def check_fetch(uuid) do + case get_registry_data(uuid) do + {:ok, %FetchRegistryData{received_at: nil}} -> + {:error, :waiting} + + {:ok, %FetchRegistryData{} = reg_data} -> + {:ok, reg_data} + + e -> + e + end + end + + @doc """ + Retrieves the response to a fetch given the identifying UUID. + The completed fetch will be deleted from the FetchRegistry + + Will return + * {:ok, fetched_object} if a fetch has completed + * {:error, :waiting} if a fetch is still pending + * {:error, other_error} usually :missing to indicate a fetch that has timed out + """ + def pop_fetch(uuid) do + case check_fetch(uuid) do + {:ok, %FetchRegistryData{received_json: received_json}} -> + delete_registry_data(uuid) + {:ok, received_json} + + e -> + e + end + end + + @doc """ + This is called to register a fetch has returned. + It expects the result data along with the UUID that was sent in the request + + Will return the fetched object or :error + """ + def register_fetch_received(uuid, data) do + case get_registry_data(uuid) do + {:ok, %FetchRegistryData{received_at: nil} = reg_data} -> + reg_data + |> set_fetch_received(data) + |> save_registry_data() + + {:ok, %FetchRegistryData{} = reg_data} -> + Logger.warn("tried to add fetched data twice - #{uuid}") + reg_data + + {:error, _} -> + Logger.warn("Error adding fetch to registry - #{uuid}") + :error + end + end + + defp new_registry_data(json) do + %FetchRegistryData{ + uuid: UUID.generate(), + sent_json: json, + sent_at: :erlang.monotonic_time(:millisecond) + } + end + + defp get_registry_data(origin) do + case Cachex.get(@fetches, origin) do + {:ok, nil} -> + {:error, :missing} + + {:ok, reg_data} -> + {:ok, reg_data} + + _ -> + {:error, :cache_error} + end + end + + defp set_fetch_received(%FetchRegistryData{} = reg_data, data), + do: %FetchRegistryData{ + reg_data + | received_at: :erlang.monotonic_time(:millisecond), + received_json: data + } + + defp save_registry_data(%FetchRegistryData{uuid: uuid} = reg_data) do + {:ok, true} = Cachex.put(@fetches, uuid, reg_data) + reg_data + end + + defp delete_registry_data(origin), + do: {:ok, true} = Cachex.del(@fetches, origin) +end diff --git a/lib/pleroma/web/fed_sockets/incoming_handler.ex b/lib/pleroma/web/fed_sockets/incoming_handler.ex new file mode 100644 index 000000000..49d0d9d84 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/incoming_handler.ex @@ -0,0 +1,88 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.IncomingHandler do + require Logger + + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + import HTTPSignatures, only: [validate_conn: 1, split_signature: 1] + + @behaviour :cowboy_websocket + + def init(req, state) do + shake = FedSocket.shake() + + with true <- Pleroma.Config.get([:fed_sockets, :enabled]), + sec_protocol <- :cowboy_req.header("sec-websocket-protocol", req, nil), + headers = %{"(request-target)" => ^shake} <- :cowboy_req.headers(req), + true <- validate_conn(%{req_headers: headers}), + %{"keyId" => origin} <- split_signature(headers["signature"]) do + req = + if is_nil(sec_protocol) do + req + else + :cowboy_req.set_resp_header("sec-websocket-protocol", sec_protocol, req) + end + + {:cowboy_websocket, req, %{origin: origin}, %{}} + else + _ -> + {:ok, req, state} + end + end + + def websocket_init(%{origin: origin}) do + case FedRegistry.add_fed_socket(origin) do + {:ok, socket_info} -> + {:ok, socket_info} + + e -> + Logger.error("FedSocket websocket_init failed - #{inspect(e)}") + {:error, inspect(e)} + end + end + + # Use the ping to check if the connection should be expired + def websocket_handle(:ping, socket_info) do + if SocketInfo.expired?(socket_info) do + {:stop, socket_info} + else + {:ok, socket_info, :hibernate} + end + end + + def websocket_handle({:text, data}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + case FedSocket.receive_package(socket_info, data) do + {:noreply, _} -> + {:ok, socket_info} + + {:reply, reply} -> + {:reply, {:text, Jason.encode!(reply)}, socket_info} + + {:error, reason} -> + Logger.error("incoming error - receive_package: #{inspect(reason)}") + {:ok, socket_info} + end + end + + def websocket_info({:send, message}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + {:reply, {:text, message}, socket_info} + end + + def websocket_info(:close, state) do + {:stop, state} + end + + def websocket_info(message, state) do + Logger.debug("#{__MODULE__} unknown message #{inspect(message)}") + {:ok, state} + end +end diff --git a/lib/pleroma/web/fed_sockets/ingester_worker.ex b/lib/pleroma/web/fed_sockets/ingester_worker.ex new file mode 100644 index 000000000..325f2a4ab --- /dev/null +++ b/lib/pleroma/web/fed_sockets/ingester_worker.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.IngesterWorker do + use Pleroma.Workers.WorkerHelper, queue: "ingestion_queue" + require Logger + + alias Pleroma.Web.Federator + + @impl Oban.Worker + def perform(%Job{args: %{"op" => "ingest", "object" => ingestee}}) do + try do + ingestee + |> Jason.decode!() + |> do_ingestion() + rescue + e -> + Logger.error("IngesterWorker error - #{inspect(e)}") + e + end + end + + defp do_ingestion(params) do + case Federator.incoming_ap_doc(params) do + {:error, reason} -> + {:error, reason} + + {:ok, object} -> + {:ok, object} + end + end +end diff --git a/lib/pleroma/web/fed_sockets/outgoing_handler.ex b/lib/pleroma/web/fed_sockets/outgoing_handler.ex new file mode 100644 index 000000000..e235a7c43 --- /dev/null +++ b/lib/pleroma/web/fed_sockets/outgoing_handler.ex @@ -0,0 +1,151 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.OutgoingHandler do + use GenServer + + require Logger + + alias Pleroma.Application + alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.FedSockets + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.FedSocket + alias Pleroma.Web.FedSockets.SocketInfo + + def start_link(uri) do + GenServer.start_link(__MODULE__, %{uri: uri}) + end + + def init(%{uri: uri}) do + case initiate_connection(uri) do + {:ok, ws_origin, conn_pid} -> + FedRegistry.add_fed_socket(ws_origin, conn_pid) + + {:error, reason} -> + Logger.debug("Outgoing connection failed - #{inspect(reason)}") + :ignore + end + end + + def handle_info({:gun_ws, conn_pid, _ref, {:text, data}}, socket_info) do + socket_info = SocketInfo.touch(socket_info) + + case FedSocket.receive_package(socket_info, data) do + {:noreply, _} -> + {:noreply, socket_info} + + {:reply, reply} -> + :gun.ws_send(conn_pid, {:text, Jason.encode!(reply)}) + {:noreply, socket_info} + + {:error, reason} -> + Logger.error("incoming error - receive_package: #{inspect(reason)}") + {:noreply, socket_info} + end + end + + def handle_info(:close, state) do + Logger.debug("Sending close frame !!!!!!!") + {:close, state} + end + + def handle_info({:gun_down, _pid, _prot, :closed, _}, state) do + {:stop, :normal, state} + end + + def handle_info({:send, data}, %{conn_pid: conn_pid} = socket_info) do + socket_info = SocketInfo.touch(socket_info) + :gun.ws_send(conn_pid, {:text, data}) + {:noreply, socket_info} + end + + def handle_info({:gun_ws, _, _, :pong}, state) do + {:noreply, state, :hibernate} + end + + def handle_info(msg, state) do + Logger.debug("#{__MODULE__} unhandled event #{inspect(msg)}") + {:noreply, state} + end + + def terminate(reason, state) do + Logger.debug( + "#{__MODULE__} terminating outgoing connection for #{inspect(state)} for #{inspect(reason)}" + ) + + {:ok, state} + end + + def initiate_connection(uri) do + ws_uri = + uri + |> SocketInfo.origin() + |> FedSockets.uri_for_origin() + + %{host: host, port: port, path: path} = URI.parse(ws_uri) + + with {:ok, conn_pid} <- :gun.open(to_charlist(host), port, %{protocols: [:http]}), + {:ok, _} <- :gun.await_up(conn_pid), + reference <- + :gun.get(conn_pid, to_charlist(path), [ + {'user-agent', to_charlist(Application.user_agent())} + ]), + {:response, :fin, 204, _} <- :gun.await(conn_pid, reference), + headers <- build_headers(uri), + ref <- :gun.ws_upgrade(conn_pid, to_charlist(path), headers, %{silence_pings: false}) do + receive do + {:gun_upgrade, ^conn_pid, ^ref, [<<"websocket">>], _} -> + {:ok, ws_uri, conn_pid} + after + 15_000 -> + Logger.debug("Fedsocket timeout connecting to #{inspect(uri)}") + {:error, :timeout} + end + else + {:response, :nofin, 404, _} -> + {:error, :fedsockets_not_supported} + + e -> + Logger.debug("Fedsocket error connecting to #{inspect(uri)}") + {:error, e} + end + end + + defp build_headers(uri) do + host_for_sig = uri |> URI.parse() |> host_signature() + + shake = FedSocket.shake() + digest = "SHA-256=" <> (:crypto.hash(:sha256, shake) |> Base.encode64()) + date = Pleroma.Signature.signed_date() + shake_size = byte_size(shake) + + signature_opts = %{ + "(request-target)": shake, + "content-length": to_charlist("#{shake_size}"), + date: date, + digest: digest, + host: host_for_sig + } + + signature = Pleroma.Signature.sign(InternalFetchActor.get_actor(), signature_opts) + + [ + {'signature', to_charlist(signature)}, + {'date', date}, + {'digest', to_charlist(digest)}, + {'content-length', to_charlist("#{shake_size}")}, + {to_charlist("(request-target)"), to_charlist(shake)}, + {'user-agent', to_charlist(Application.user_agent())} + ] + end + + defp host_signature(%{host: host, scheme: scheme, port: port}) do + if port == URI.default_port(scheme) do + host + else + "#{host}:#{port}" + end + end +end diff --git a/lib/pleroma/web/fed_sockets/socket_info.ex b/lib/pleroma/web/fed_sockets/socket_info.ex new file mode 100644 index 000000000..d6fdffe1a --- /dev/null +++ b/lib/pleroma/web/fed_sockets/socket_info.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.SocketInfo do + defstruct origin: nil, + pid: nil, + conn_pid: nil, + state: :default, + connected_until: nil + + alias Pleroma.Web.FedSockets.SocketInfo + @default_connection_duration 15 * 60 * 1000 + + def build(uri, conn_pid \\ nil) do + uri + |> build_origin() + |> build_pids(conn_pid) + |> touch() + end + + def touch(%SocketInfo{} = socket_info), + do: %{socket_info | connected_until: new_ttl()} + + def connect(%SocketInfo{} = socket_info), + do: %{socket_info | state: :connected} + + def expired?(%{connected_until: connected_until}), + do: connected_until < :erlang.monotonic_time(:millisecond) + + def origin(uri), + do: build_origin(uri).origin + + defp build_pids(socket_info, conn_pid), + do: struct(socket_info, pid: self(), conn_pid: conn_pid) + + defp build_origin(uri) when is_binary(uri), + do: uri |> URI.parse() |> build_origin + + defp build_origin(%{host: host, port: nil, scheme: scheme}), + do: build_origin(%{host: host, port: URI.default_port(scheme)}) + + defp build_origin(%{host: host, port: port}), + do: %SocketInfo{origin: "#{host}:#{port}"} + + defp new_ttl do + connection_duration = + Pleroma.Config.get([:fed_sockets, :connection_duration], @default_connection_duration) + + :erlang.monotonic_time(:millisecond) + connection_duration + end +end diff --git a/lib/pleroma/web/fed_sockets/supervisor.ex b/lib/pleroma/web/fed_sockets/supervisor.ex new file mode 100644 index 000000000..a5f4bebfb --- /dev/null +++ b/lib/pleroma/web/fed_sockets/supervisor.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.Supervisor do + use Supervisor + import Cachex.Spec + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(args) do + children = [ + build_cache(:fed_socket_fetches, args), + build_cache(:fed_socket_rejections, args), + {Registry, keys: :unique, name: FedSockets.Registry, meta: [rejected: %{}]} + ] + + opts = [strategy: :one_for_all, name: Pleroma.Web.Streamer.Supervisor] + Supervisor.init(children, opts) + end + + defp build_cache(name, args) do + opts = get_opts(name, args) + + %{ + id: String.to_atom("#{name}_cache"), + start: {Cachex, :start_link, [name, opts]}, + type: :worker + } + end + + defp get_opts(cache_name, args) + when cache_name in [:fed_socket_fetches, :fed_socket_rejections] do + default = get_opts_or_config(args, cache_name, :default, 15_000) + interval = get_opts_or_config(args, cache_name, :interval, 3_000) + lazy = get_opts_or_config(args, cache_name, :lazy, false) + + [expiration: expiration(default: default, interval: interval, lazy: lazy)] + end + + defp get_opts(name, args) do + Keyword.get(args, name, []) + end + + defp get_opts_or_config(args, name, key, default) do + args + |> Keyword.get(name, []) + |> Keyword.get(key) + |> case do + nil -> + Pleroma.Config.get([:fed_sockets, name, key], default) + + value -> + value + end + end +end diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator.ex similarity index 89% rename from lib/pleroma/web/federator/federator.ex rename to lib/pleroma/web/federator.ex index f5803578d..130654145 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -66,14 +66,17 @@ def perform(:publish, activity) do def perform(:incoming_ap_doc, params) do Logger.debug("Handling incoming AP activity") - params = Utils.normalize_params(params) + actor = + params + |> Map.get("actor") + |> Utils.get_ap_id() # NOTE: we use the actor ID to do the containment, this is fine because an # actor shouldn't be acting on objects outside their own AP server. - with {:ok, _user} <- ap_enabled_actor(params["actor"]), + with {_, {:ok, _user}} <- {:actor, ap_enabled_actor(actor)}, nil <- Activity.normalize(params["id"]), {_, :ok} <- - {:correct_origin?, Containment.contain_origin_from_id(params["actor"], params)}, + {:correct_origin?, Containment.contain_origin_from_id(actor, params)}, {:ok, activity} <- Transmogrifier.handle_incoming(params) do {:ok, activity} else @@ -85,10 +88,13 @@ def perform(:incoming_ap_doc, params) do Logger.debug("Already had #{params["id"]}") {:error, :already_present} + {:actor, e} -> + Logger.debug("Unhandled actor #{actor}, #{inspect(e)}") + {:error, e} + e -> # Just drop those for now - Logger.debug("Unhandled activity") - Logger.debug(Jason.encode!(params, pretty: true)) + Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end) {:error, e} end end diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex index 93a8294b7..218cdbdf3 100644 --- a/lib/pleroma/web/feed/tag_controller.ex +++ b/lib/pleroma/web/feed/tag_controller.ex @@ -10,14 +10,14 @@ defmodule Pleroma.Web.Feed.TagController do alias Pleroma.Web.Feed.FeedView def feed(conn, params) do - unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do + if Config.get!([:instance, :public]) do render_feed(conn, params) else render_error(conn, :not_found, "Not found") end end - def render_feed(conn, %{"tag" => raw_tag} = params) do + defp render_feed(conn, %{"tag" => raw_tag} = params) do {format, tag} = parse_tag(raw_tag) activities = @@ -36,12 +36,13 @@ def render_feed(conn, %{"tag" => raw_tag} = params) do end @spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()} - defp parse_tag(raw_tag) when is_binary(raw_tag) do - case Enum.reverse(String.split(raw_tag, ".")) do - [format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")} - _ -> {"rss", raw_tag} + defp parse_tag(raw_tag) do + case is_binary(raw_tag) && Enum.reverse(String.split(raw_tag, ".")) do + [format | tag] when format in ["rss", "atom"] -> + {format, Enum.join(tag, ".")} + + _ -> + {"atom", raw_tag} end end - - defp parse_tag(raw_tag), do: {"rss", raw_tag} end diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index 71eb1ea7e..a5013d2c0 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -5,30 +5,25 @@ defmodule Pleroma.Web.Feed.UserController do use Pleroma.Web, :controller - alias Fallback.RedirectController + alias Pleroma.Config alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.Feed.FeedView - plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect]) + plug(Pleroma.Web.Plugs.SetFormatPlug when action in [:feed_redirect]) action_fallback(:errors) def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do - RedirectController.redirector_with_meta(conn, %{user: user}) + Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user}) end end def feed_redirect(%{assigns: %{format: format}} = conn, _params) when format in ["json", "activity+json"] do - with %{halted: false} = conn <- - Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn, - unless_func: &Pleroma.Web.FederatingPlug.federating?/1 - ) do - ActivityPubController.call(conn, :user) - end + ActivityPubController.call(conn, :user) end def feed_redirect(conn, %{"nickname" => nickname}) do @@ -37,25 +32,18 @@ def feed_redirect(conn, %{"nickname" => nickname}) do end end - def feed(conn, params) do - unless Pleroma.Config.restrict_unauthenticated_access?(:profiles, :local) do - render_feed(conn, params) - else - errors(conn, {:error, :not_found}) - end - end - - def render_feed(conn, %{"nickname" => nickname} = params) do + def feed(conn, %{"nickname" => nickname} = params) do format = get_format(conn) format = - if format in ["rss", "atom"] do + if format in ["atom", "rss"] do format else "atom" end - with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do + with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)}, + {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do activities = %{ type: ["Create"], @@ -70,7 +58,7 @@ def render_feed(conn, %{"nickname" => nickname} = params) do |> render("user.#{format}", user: user, activities: activities, - feed_config: Pleroma.Config.get([:feed]) + feed_config: Config.get([:feed]) ) end end @@ -82,6 +70,8 @@ def errors(conn, {:error, :not_found}) do def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found}) def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) + def errors(conn, {:visibility, _}), do: errors(conn, {:error, :not_found}) + def errors(conn, _) do render_error(conn, :internal_server_error, "Something went wrong") end diff --git a/lib/pleroma/web/instance_document.ex b/lib/pleroma/web/instance_document.ex new file mode 100644 index 000000000..df5caebf0 --- /dev/null +++ b/lib/pleroma/web/instance_document.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.InstanceDocument do + alias Pleroma.Config + alias Pleroma.Web.Endpoint + + @instance_documents %{ + "terms-of-service" => "/static/terms-of-service.html", + "instance-panel" => "/instance/panel.html" + } + + @spec get(String.t()) :: {:ok, String.t()} | {:error, atom()} + def get(document_name) do + case Map.fetch(@instance_documents, document_name) do + {:ok, path} -> {:ok, path} + _ -> {:error, :not_found} + end + end + + @spec put(String.t(), String.t()) :: {:ok, String.t()} | {:error, atom()} + def put(document_name, origin_path) do + with {_, {:ok, destination_path}} <- + {:instance_document, Map.fetch(@instance_documents, document_name)}, + :ok <- put_file(origin_path, destination_path) do + {:ok, Path.join(Endpoint.url(), destination_path)} + else + {:instance_document, :error} -> {:error, :not_found} + error -> error + end + end + + @spec delete(String.t()) :: :ok | {:error, atom()} + def delete(document_name) do + with {_, {:ok, path}} <- {:instance_document, Map.fetch(@instance_documents, document_name)}, + instance_static_dir_path <- instance_static_dir(path), + :ok <- File.rm(instance_static_dir_path) do + :ok + else + {:instance_document, :error} -> {:error, :not_found} + {:error, :enoent} -> {:error, :not_found} + error -> error + end + end + + defp put_file(origin_path, destination_path) do + with destination <- instance_static_dir(destination_path), + {_, :ok} <- {:mkdir_p, File.mkdir_p(Path.dirname(destination))}, + {_, {:ok, _}} <- {:copy, File.copy(origin_path, destination)} do + :ok + else + {error, _} -> {:error, error} + end + end + + defp instance_static_dir(filename) do + [:instance, :static_dir] + |> Config.get!() + |> Path.join(filename) + end +end diff --git a/lib/pleroma/web/mailer/subscription_controller.ex b/lib/pleroma/web/mailer/subscription_controller.ex index 478a83518..ace44afd1 100644 --- a/lib/pleroma/web/mailer/subscription_controller.ex +++ b/lib/pleroma/web/mailer/subscription_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.Mailer.SubscriptionController do use Pleroma.Web, :controller diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 43ec70021..08f92d55f 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.MastoFEController do use Pleroma.Web, :controller - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 95d8452df..97858a93c 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -15,9 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do ] alias Pleroma.Maps - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.RateLimiter alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Builder @@ -29,6 +26,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthView + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.RateLimiter alias Pleroma.Web.TwitterAPI.TwitterAPI plug(Pleroma.Web.ApiSpec.CastAndValidate) @@ -177,7 +177,6 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p user_params = [ :no_rich_text, - :locked, :hide_followers_count, :hide_follows_count, :hide_followers, @@ -210,6 +209,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p if bot, do: {:ok, "Service"}, else: {:ok, "Person"} end) |> Maps.put_if_present(:actor_type, params[:actor_type]) + |> Maps.put_if_present(:is_locked, params[:locked]) # What happens here: # diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index a516b6c20..143dcf80c 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -5,12 +5,12 @@ defmodule Pleroma.Web.MastodonAPI.AppController do use Pleroma.Web, :controller - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Repo alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Scopes alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug action_fallback(Pleroma.Web.MastodonAPI.FallbackController) diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index 9f09550e1..9cc3984d0 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization @@ -13,7 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - plug(Pleroma.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset) + plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset) @local_mastodon_name "Mastodon-Local" @@ -22,7 +24,7 @@ def login(%{assigns: %{user: %User{}}} = conn, _params) do redirect(conn, to: local_mastodon_root_path(conn)) end - @doc "Local Mastodon FE login init action" + # Local Mastodon FE login init action def login(conn, %{"code" => auth_token}) do with {:ok, app} <- get_or_make_app(), {:ok, auth} <- Authorization.get_by_token(app, auth_token), @@ -33,7 +35,7 @@ def login(conn, %{"code" => auth_token}) do end end - @doc "Local Mastodon FE callback action" + # Local Mastodon FE callback action def login(conn, _) do with {:ok, app} <- get_or_make_app() do path = @@ -61,9 +63,7 @@ def password_reset(conn, params) do TwitterAPI.password_reset(nickname_or_email) - conn - |> put_status(:no_content) - |> json("") + json_response(conn, :no_content, "") end defp local_mastodon_root_path(conn) do diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex index f35ec3596..61347d8db 100644 --- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] alias Pleroma.Conversation.Participation - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Repo + alias Pleroma.Web.Plugs.OAuthScopesPlug action_fallback(Pleroma.Web.MastodonAPI.FallbackController) diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex index c5f47c5df..872cb1f4d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do plug( :skip_plug, - [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] + [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug] when action == :index ) diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex index 9c2d093cd..503bd7d5f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do use Pleroma.Web, :controller - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex index abbf0ce02..c71a34b15 100644 --- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do use Pleroma.Web, :controller alias Pleroma.Filter - alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug @oauth_read_actions [:show, :index] diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 748b6b475..f8cd7fa9f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do use Pleroma.Web, :controller - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index d8859731d..07a32491a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do plug( :skip_plug, - [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] + [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug] when action in [:show, :peers] ) diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index acdc76fd2..f6b51bf02 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.MastodonAPI.ListController do use Pleroma.Web, :controller - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.Plugs.OAuthScopesPlug @oauth_read_actions [:index, :show, :list_accounts] @@ -74,7 +74,7 @@ def add_to_list(%{assigns: %{list: list}, body_params: %{account_ids: account_id # DELETE /api/v1/lists/:id/accounts def remove_from_list( - %{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, + %{assigns: %{list: list}, params: %{account_ids: account_ids}} = conn, _ ) do Enum.each(account_ids, fn account_id -> @@ -86,6 +86,10 @@ def remove_from_list( json(conn, %{}) end + def remove_from_list(%{body_params: params} = conn, _) do + remove_from_list(%{conn | params: params}, %{}) + end + defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do case Pleroma.List.get(id, user) do %Pleroma.List{} = list -> assign(conn, :list, list) diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index 85310edfa..0628b2b49 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do use Pleroma.Web, :controller - alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index e7767de4e..9cf682c7b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do plug( :skip_plug, - [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] + [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug] when action in [:empty_array, :empty_object] ) diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex index 513de279f..161193134 100644 --- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -6,11 +6,12 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do use Pleroma.Web, :controller alias Pleroma.Object - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.Plugs.OAuthScopesPlug action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2]) plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index e25cef30b..c3c8606f2 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] alias Pleroma.Notification - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.MastodonAPI.MastodonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug @oauth_read_actions [:show, :index] diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex index db46ffcfc..3dcd1c44f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -9,9 +9,9 @@ defmodule Pleroma.Web.MastodonAPI.PollController do alias Pleroma.Activity alias Pleroma.Object - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug action_fallback(Pleroma.Web.MastodonAPI.FallbackController) diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index 405167108..156544f40 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -3,14 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.ReportController do - alias Pleroma.Plugs.OAuthScopesPlug - use Pleroma.Web, :controller action_fallback(Pleroma.Web.MastodonAPI.FallbackController) plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) + plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex index 1719c67ea..322a46497 100644 --- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.ScheduledActivity alias Pleroma.Web.MastodonAPI.MastodonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug @oauth_read_actions [:show, :index] diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 5a983db39..0043c3a56 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -6,14 +6,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller alias Pleroma.Activity - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.RateLimiter alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ControllerHelper alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.RateLimiter require Logger diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index ecfa38489..6848adace 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -13,8 +13,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Activity alias Pleroma.Bookmark alias Pleroma.Object - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.RateLimiter alias Pleroma.Repo alias Pleroma.ScheduledActivity alias Pleroma.User @@ -23,9 +21,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.ScheduledActivityView + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.RateLimiter plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]) + + plug( + :skip_plug, + Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show] + ) @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} @@ -123,9 +127,8 @@ def index(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do @doc """ POST /api/v1/statuses - - Creates a scheduled status when `scheduled_at` param is present and it's far enough """ + # Creates a scheduled status when `scheduled_at` param is present and it's far enough def create( %{ assigns: %{user: user}, @@ -156,11 +159,7 @@ def create( end end - @doc """ - POST /api/v1/statuses - - Creates a regular status - """ + # Creates a regular status def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id]) diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex index 34eac97c5..20138908c 100644 --- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:restrict_push_enabled) - plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]}) + plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["push"]}) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SubscriptionOperation diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex index f91df9ab7..5765271cf 100644 --- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do require Logger plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index) + plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index) def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 5272790d3..7a5c80e01 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -10,11 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do alias Pleroma.Config alias Pleroma.Pagination - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.RateLimiter alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.RateLimiter plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag]) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 864c0417f..d54cae732 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -181,8 +181,10 @@ defp do_render("show.json", %{user: user} = opts) do user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) display_name = user.name || user.nickname - image = User.avatar_url(user) |> MediaProxy.url() + avatar = User.avatar_url(user) |> MediaProxy.url() + avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true) header = User.banner_url(user) |> MediaProxy.url() + header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true) following_count = if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do @@ -240,17 +242,17 @@ defp do_render("show.json", %{user: user} = opts) do username: username_from_nickname(user.nickname), acct: user.nickname, display_name: display_name, - locked: user.locked, + locked: user.is_locked, created_at: Utils.to_masto_date(user.inserted_at), followers_count: followers_count, following_count: following_count, statuses_count: user.note_count, - note: user.bio || "", + note: user.bio, url: user.uri || user.ap_id, - avatar: image, - avatar_static: image, + avatar: avatar, + avatar_static: avatar_static, header: header, - header_static: header, + header_static: header_static, emojis: emojis, fields: user.fields, bot: bot, diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3fe1967be..435bcde15 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do require Pleroma.Constants alias Pleroma.Activity - alias Pleroma.ActivityExpiration alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo @@ -56,23 +55,6 @@ defp get_replied_to_activities(activities) do end) end - def get_user(ap_id, fake_record_fallback \\ true) do - cond do - user = User.get_cached_by_ap_id(ap_id) -> - user - - user = User.get_by_guessed_nickname(ap_id) -> - user - - fake_record_fallback -> - # TODO: refactor (fake records is never a good idea) - User.error_user(ap_id) - - true -> - nil - end - end - defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id), do: context_id @@ -120,7 +102,7 @@ def render("index.json", opts) do # Note: unresolved users are filtered out actors = (activities ++ parent_activities) - |> Enum.map(&get_user(&1.data["actor"], false)) + |> Enum.map(&CommonAPI.get_user(&1.data["actor"], false)) |> Enum.filter(& &1) UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes) @@ -139,7 +121,7 @@ def render( "show.json", %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts ) do - user = get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) activity_object = Object.normalize(activity) @@ -212,7 +194,7 @@ def render( def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do object = Object.normalize(activity) - user = get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) user_follower_address = user.follower_address like_count = object.data["like_count"] || 0 @@ -245,8 +227,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} expires_at = with true <- client_posted_this_activity, - %ActivityExpiration{scheduled_at: scheduled_at} <- - ActivityExpiration.get_by_activity_id(activity.id) do + %Oban.Job{scheduled_at: scheduled_at} <- + Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) do scheduled_at else _ -> nil @@ -266,7 +248,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} reply_to = get_reply_to(activity, opts) - reply_to_user = reply_to && get_user(reply_to.data["actor"]) + reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"]) content = object @@ -433,6 +415,7 @@ def render("attachment.json", %{attachment: attachment}) do [attachment_url | _] = attachment["url"] media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image" href = attachment_url["href"] |> MediaProxy.url() + href_preview = attachment_url["href"] |> MediaProxy.preview_url() type = cond do @@ -448,7 +431,7 @@ def render("attachment.json", %{attachment: attachment}) do id: to_string(attachment["id"] || hash_id), url: href, remote_url: href, - preview_url: href, + preview_url: href_preview, text_url: href, type: type, description: attachment["name"], diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index cf923ded8..439cdd716 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -23,8 +23,8 @@ def init(%{qs: qs} = req, state) do with params <- Enum.into(:cow_qs.parse_qs(qs), %{}), sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), access_token <- Map.get(params, "access_token"), - {:ok, user} <- authenticate_request(access_token, sec_websocket), - {:ok, topic} <- Streamer.get_topic(Map.get(params, "stream"), user, params) do + {:ok, user, oauth_token} <- authenticate_request(access_token, sec_websocket), + {:ok, topic} <- Streamer.get_topic(params["stream"], user, oauth_token, params) do req = if sec_websocket do :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) @@ -117,7 +117,7 @@ def terminate(reason, _req, state) do # Public streams without authentication. defp authenticate_request(nil, nil) do - {:ok, nil} + {:ok, nil, nil} end # Authenticated streams. @@ -125,9 +125,9 @@ defp authenticate_request(access_token, sec_websocket) do token = access_token || sec_websocket with true <- is_bitstring(token), - %Token{user_id: user_id} <- Repo.get_by(Token, token: token), + oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: token), user = %User{} <- User.get_cached_by_id(user_id) do - {:ok, user} + {:ok, user, oauth_token} else _ -> {:error, :unauthorized} end diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy.ex similarity index 57% rename from lib/pleroma/web/media_proxy/media_proxy.ex rename to lib/pleroma/web/media_proxy.ex index e18dd8224..8656b8cad 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.MediaProxy do alias Pleroma.Config + alias Pleroma.Helpers.UriHelper alias Pleroma.Upload alias Pleroma.Web alias Pleroma.Web.MediaProxy.Invalidation @@ -40,27 +41,35 @@ def url(url) when is_nil(url) or url == "", do: nil def url("/" <> _ = url), do: url def url(url) do - if disabled?() or not url_proxiable?(url) do - url - else + if enabled?() and url_proxiable?(url) do encode_url(url) + else + url end end @spec url_proxiable?(String.t()) :: boolean() def url_proxiable?(url) do - if local?(url) or whitelisted?(url) do - false + not local?(url) and not whitelisted?(url) + end + + def preview_url(url, preview_params \\ []) do + if preview_enabled?() do + encode_preview_url(url, preview_params) else - true + url(url) end end - defp disabled?, do: !Config.get([:media_proxy, :enabled], false) + def enabled?, do: Config.get([:media_proxy, :enabled], false) - defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) + # Note: media proxy must be enabled for media preview proxy in order to load all + # non-local non-whitelisted URLs through it and be sure that body size constraint is preserved. + def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled]) - defp whitelisted?(url) do + def local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) + + def whitelisted?(url) do %{host: domain} = URI.parse(url) mediaproxy_whitelist_domains = @@ -85,17 +94,29 @@ defp maybe_get_domain_from_url("http" <> _ = url) do defp maybe_get_domain_from_url(domain), do: domain - def encode_url(url) do + defp base64_sig64(url) do base64 = Base.url_encode64(url, @base64_opts) sig64 = base64 - |> signed_url + |> signed_url() |> Base.url_encode64(@base64_opts) + {base64, sig64} + end + + def encode_url(url) do + {base64, sig64} = base64_sig64(url) + build_url(sig64, base64, filename(url)) end + def encode_preview_url(url, preview_params \\ []) do + {base64, sig64} = base64_sig64(url) + + build_preview_url(sig64, base64, filename(url), preview_params) + end + def decode_url(sig, url) do with {:ok, sig} <- Base.url_decode64(sig, @base64_opts), signature when signature == sig <- signed_url(url) do @@ -113,10 +134,14 @@ def filename(url_or_path) do if path = URI.parse(url_or_path).path, do: Path.basename(path) end - def build_url(sig_base64, url_base64, filename \\ nil) do + def base_url do + Config.get([:media_proxy, :base_url], Web.base_url()) + end + + defp proxy_url(path, sig_base64, url_base64, filename) do [ - Config.get([:media_proxy, :base_url], Web.base_url()), - "proxy", + base_url(), + path, sig_base64, url_base64, filename @@ -124,4 +149,38 @@ def build_url(sig_base64, url_base64, filename \\ nil) do |> Enum.filter(& &1) |> Path.join() end + + def build_url(sig_base64, url_base64, filename \\ nil) do + proxy_url("proxy", sig_base64, url_base64, filename) + end + + def build_preview_url(sig_base64, url_base64, filename \\ nil, preview_params \\ []) do + uri = proxy_url("proxy/preview", sig_base64, url_base64, filename) + + UriHelper.modify_uri_params(uri, preview_params) + end + + def verify_request_path_and_url( + %Plug.Conn{params: %{"filename" => _}, request_path: request_path}, + url + ) do + verify_request_path_and_url(request_path, url) + end + + def verify_request_path_and_url(request_path, url) when is_binary(request_path) do + filename = filename(url) + + if filename && not basename_matches?(request_path, filename) do + {:wrong_filename, filename} + else + :ok + end + end + + def verify_request_path_and_url(_, _), do: :ok + + defp basename_matches?(path, filename) do + basename = Path.basename(path) + basename == filename or URI.decode(basename) == filename or URI.encode(basename) == filename + end end diff --git a/lib/pleroma/web/media_proxy/invalidation.ex b/lib/pleroma/web/media_proxy/invalidation.ex index 5808861e6..4f4340478 100644 --- a/lib/pleroma/web/media_proxy/invalidation.ex +++ b/lib/pleroma/web/media_proxy/invalidation.ex @@ -33,6 +33,8 @@ defp do_purge(urls) do def prepare_urls(urls) do urls |> List.wrap() - |> Enum.map(&MediaProxy.url/1) + |> Enum.map(fn url -> [MediaProxy.url(url), MediaProxy.preview_url(url)] end) + |> List.flatten() + |> Enum.uniq() end end diff --git a/lib/pleroma/web/media_proxy/invalidations/http.ex b/lib/pleroma/web/media_proxy/invalidation/http.ex similarity index 97% rename from lib/pleroma/web/media_proxy/invalidations/http.ex rename to lib/pleroma/web/media_proxy/invalidation/http.ex index bb81d8888..0b0cde68c 100644 --- a/lib/pleroma/web/media_proxy/invalidations/http.ex +++ b/lib/pleroma/web/media_proxy/invalidation/http.ex @@ -30,7 +30,7 @@ defp do_purge(method, url, headers, options) do {:ok, %{status: status} = env} when 400 <= status and status < 500 -> {:error, env} - {:error, error} = error -> + {:error, _} = error -> error _ -> diff --git a/lib/pleroma/web/media_proxy/invalidations/script.ex b/lib/pleroma/web/media_proxy/invalidation/script.ex similarity index 100% rename from lib/pleroma/web/media_proxy/invalidations/script.ex rename to lib/pleroma/web/media_proxy/invalidation/script.ex diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 9a64b0ef3..90651ed9b 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -5,44 +5,201 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do use Pleroma.Web, :controller + alias Pleroma.Config + alias Pleroma.Helpers.MediaHelper + alias Pleroma.Helpers.UriHelper alias Pleroma.ReverseProxy alias Pleroma.Web.MediaProxy + alias Plug.Conn - @default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]] - - def remote(conn, %{"sig" => sig64, "url" => url64} = params) do - with config <- Pleroma.Config.get([:media_proxy], []), - true <- Keyword.get(config, :enabled, false), + def remote(conn, %{"sig" => sig64, "url" => url64}) do + with {_, true} <- {:enabled, MediaProxy.enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), {_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)}, - :ok <- filename_matches(params, conn.request_path, url) do - ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts)) + :ok <- MediaProxy.verify_request_path_and_url(conn, url) do + ReverseProxy.call(conn, url, media_proxy_opts()) else - error when error in [false, {:in_banned_urls, true}] -> - send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404)) + {:enabled, false} -> + send_resp(conn, 404, Conn.Status.reason_phrase(404)) + + {:in_banned_urls, true} -> + send_resp(conn, 404, Conn.Status.reason_phrase(404)) {:error, :invalid_signature} -> - send_resp(conn, 403, Plug.Conn.Status.reason_phrase(403)) + send_resp(conn, 403, Conn.Status.reason_phrase(403)) {:wrong_filename, filename} -> redirect(conn, external: MediaProxy.build_url(sig64, url64, filename)) end end - def filename_matches(%{"filename" => _} = _, path, url) do - filename = MediaProxy.filename(url) - - if filename && does_not_match(path, filename) do - {:wrong_filename, filename} + def preview(%Conn{} = conn, %{"sig" => sig64, "url" => url64}) do + with {_, true} <- {:enabled, MediaProxy.preview_enabled?()}, + {:ok, url} <- MediaProxy.decode_url(sig64, url64), + :ok <- MediaProxy.verify_request_path_and_url(conn, url) do + handle_preview(conn, url) else - :ok + {:enabled, false} -> + send_resp(conn, 404, Conn.Status.reason_phrase(404)) + + {:error, :invalid_signature} -> + send_resp(conn, 403, Conn.Status.reason_phrase(403)) + + {:wrong_filename, filename} -> + redirect(conn, external: MediaProxy.build_preview_url(sig64, url64, filename)) end end - def filename_matches(_, _, _), do: :ok + defp handle_preview(conn, url) do + media_proxy_url = MediaProxy.url(url) - defp does_not_match(path, filename) do - basename = Path.basename(path) - basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename + with {:ok, %{status: status} = head_response} when status in 200..299 <- + Pleroma.HTTP.request("head", media_proxy_url, [], [], pool: :media) do + content_type = Tesla.get_header(head_response, "content-type") + content_length = Tesla.get_header(head_response, "content-length") + content_length = content_length && String.to_integer(content_length) + static = conn.params["static"] in ["true", true] + + cond do + static and content_type == "image/gif" -> + handle_jpeg_preview(conn, media_proxy_url) + + static -> + drop_static_param_and_redirect(conn) + + content_type == "image/gif" -> + redirect(conn, external: media_proxy_url) + + min_content_length_for_preview() > 0 and content_length > 0 and + content_length < min_content_length_for_preview() -> + redirect(conn, external: media_proxy_url) + + true -> + handle_preview(content_type, conn, media_proxy_url) + end + else + # If HEAD failed, redirecting to media proxy URI doesn't make much sense; returning an error + {_, %{status: status}} -> + send_resp(conn, :failed_dependency, "Can't fetch HTTP headers (HTTP #{status}).") + + {:error, :recv_response_timeout} -> + send_resp(conn, :failed_dependency, "HEAD request timeout.") + + _ -> + send_resp(conn, :failed_dependency, "Can't fetch HTTP headers.") + end + end + + defp handle_preview("image/png" <> _ = _content_type, conn, media_proxy_url) do + handle_png_preview(conn, media_proxy_url) + end + + defp handle_preview("image/" <> _ = _content_type, conn, media_proxy_url) do + handle_jpeg_preview(conn, media_proxy_url) + end + + defp handle_preview("video/" <> _ = _content_type, conn, media_proxy_url) do + handle_video_preview(conn, media_proxy_url) + end + + defp handle_preview(_unsupported_content_type, conn, media_proxy_url) do + fallback_on_preview_error(conn, media_proxy_url) + end + + defp handle_png_preview(conn, media_proxy_url) do + quality = Config.get!([:media_preview_proxy, :image_quality]) + {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions() + + with {:ok, thumbnail_binary} <- + MediaHelper.image_resize( + media_proxy_url, + %{ + max_width: thumbnail_max_width, + max_height: thumbnail_max_height, + quality: quality, + format: "png" + } + ) do + conn + |> put_preview_response_headers(["image/png", "preview.png"]) + |> send_resp(200, thumbnail_binary) + else + _ -> + fallback_on_preview_error(conn, media_proxy_url) + end + end + + defp handle_jpeg_preview(conn, media_proxy_url) do + quality = Config.get!([:media_preview_proxy, :image_quality]) + {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions() + + with {:ok, thumbnail_binary} <- + MediaHelper.image_resize( + media_proxy_url, + %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality} + ) do + conn + |> put_preview_response_headers() + |> send_resp(200, thumbnail_binary) + else + _ -> + fallback_on_preview_error(conn, media_proxy_url) + end + end + + defp handle_video_preview(conn, media_proxy_url) do + with {:ok, thumbnail_binary} <- + MediaHelper.video_framegrab(media_proxy_url) do + conn + |> put_preview_response_headers() + |> send_resp(200, thumbnail_binary) + else + _ -> + fallback_on_preview_error(conn, media_proxy_url) + end + end + + defp drop_static_param_and_redirect(conn) do + uri_without_static_param = + conn + |> current_url() + |> UriHelper.modify_uri_params(%{}, ["static"]) + + redirect(conn, external: uri_without_static_param) + end + + defp fallback_on_preview_error(conn, media_proxy_url) do + redirect(conn, external: media_proxy_url) + end + + defp put_preview_response_headers( + conn, + [content_type, filename] = _content_info \\ ["image/jpeg", "preview.jpg"] + ) do + conn + |> put_resp_header("content-type", content_type) + |> put_resp_header("content-disposition", "inline; filename=\"#{filename}\"") + |> put_resp_header("cache-control", ReverseProxy.default_cache_control_header()) + end + + defp thumbnail_max_dimensions do + config = media_preview_proxy_config() + + thumbnail_max_width = Keyword.fetch!(config, :thumbnail_max_width) + thumbnail_max_height = Keyword.fetch!(config, :thumbnail_max_height) + + {thumbnail_max_width, thumbnail_max_height} + end + + defp min_content_length_for_preview do + Keyword.get(media_preview_proxy_config(), :min_content_length, 0) + end + + defp media_preview_proxy_config do + Config.get!([:media_preview_proxy]) + end + + defp media_proxy_opts do + Config.get([:media_proxy, :proxy_opts], []) end end diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex similarity index 100% rename from lib/pleroma/web/metadata/feed.ex rename to lib/pleroma/web/metadata/providers/feed.ex diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex similarity index 98% rename from lib/pleroma/web/metadata/opengraph.ex rename to lib/pleroma/web/metadata/providers/open_graph.ex index 68c871e71..bb1b23208 100644 --- a/lib/pleroma/web/metadata/opengraph.ex +++ b/lib/pleroma/web/metadata/providers/open_graph.ex @@ -61,7 +61,7 @@ def build_tags(%{ @impl Provider def build_tags(%{user: user}) do - with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do + with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do [ {:meta, [ diff --git a/lib/pleroma/web/metadata/provider.ex b/lib/pleroma/web/metadata/providers/provider.ex similarity index 100% rename from lib/pleroma/web/metadata/provider.ex rename to lib/pleroma/web/metadata/providers/provider.ex diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex similarity index 100% rename from lib/pleroma/web/metadata/rel_me.ex rename to lib/pleroma/web/metadata/providers/rel_me.ex diff --git a/lib/pleroma/web/metadata/restrict_indexing.ex b/lib/pleroma/web/metadata/providers/restrict_indexing.ex similarity index 81% rename from lib/pleroma/web/metadata/restrict_indexing.ex rename to lib/pleroma/web/metadata/providers/restrict_indexing.ex index f15607896..a1dcb6e15 100644 --- a/lib/pleroma/web/metadata/restrict_indexing.ex +++ b/lib/pleroma/web/metadata/providers/restrict_indexing.ex @@ -10,7 +10,9 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do """ @impl true - def build_tags(%{user: %{local: false}}) do + def build_tags(%{user: %{local: true, discoverable: true}}), do: [] + + def build_tags(_) do [ {:meta, [ @@ -19,7 +21,4 @@ def build_tags(%{user: %{local: false}}) do ], []} ] end - - @impl true - def build_tags(%{user: %{local: true}}), do: [] end diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex similarity index 97% rename from lib/pleroma/web/metadata/twitter_card.ex rename to lib/pleroma/web/metadata/providers/twitter_card.ex index 5d08ce422..df34b033f 100644 --- a/lib/pleroma/web/metadata/twitter_card.ex +++ b/lib/pleroma/web/metadata/providers/twitter_card.ex @@ -40,7 +40,7 @@ def build_tags(%{activity_id: id, object: object, user: user}) do @impl Provider def build_tags(%{user: user}) do - with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do + with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do [ title_tag(user), {:meta, [property: "twitter:description", content: truncated_bio], []}, diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 2f0dfb474..8a206e019 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -38,7 +38,7 @@ def scrub_html(content) when is_binary(content) do def scrub_html(content), do: content def attachment_url(url) do - MediaProxy.url(url) + MediaProxy.preview_url(url) end def user_name_string(user) do diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex similarity index 93% rename from lib/pleroma/web/mongooseim/mongoose_im_controller.ex rename to lib/pleroma/web/mongoose_im/mongoose_im_controller.ex index 6cbbe8fd8..2a5c7c356 100644 --- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex +++ b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex @@ -5,10 +5,10 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do use Pleroma.Web, :controller - alias Pleroma.Plugs.AuthenticationPlug - alias Pleroma.Plugs.RateLimiter alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.Plugs.AuthenticationPlug + alias Pleroma.Web.Plugs.RateLimiter plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password]) plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password) diff --git a/lib/pleroma/web/oauth.ex b/lib/pleroma/web/o_auth.ex similarity index 100% rename from lib/pleroma/web/oauth.ex rename to lib/pleroma/web/o_auth.ex diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/o_auth/app.ex similarity index 100% rename from lib/pleroma/web/oauth/app.ex rename to lib/pleroma/web/o_auth/app.ex diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex similarity index 100% rename from lib/pleroma/web/oauth/authorization.ex rename to lib/pleroma/web/o_auth/authorization.ex diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/o_auth/fallback_controller.ex similarity index 100% rename from lib/pleroma/web/oauth/fallback_controller.ex rename to lib/pleroma/web/o_auth/fallback_controller.ex diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex similarity index 100% rename from lib/pleroma/web/oauth/mfa_controller.ex rename to lib/pleroma/web/o_auth/mfa_controller.ex diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/o_auth/mfa_view.ex similarity index 100% rename from lib/pleroma/web/oauth/mfa_view.ex rename to lib/pleroma/web/o_auth/mfa_view.ex diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex similarity index 98% rename from lib/pleroma/web/oauth/oauth_controller.ex rename to lib/pleroma/web/o_auth/o_auth_controller.ex index 06b116368..d2f9d1ceb 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do alias Pleroma.Helpers.UriHelper alias Pleroma.Maps alias Pleroma.MFA - alias Pleroma.Plugs.RateLimiter alias Pleroma.Registration alias Pleroma.Repo alias Pleroma.User @@ -23,6 +22,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken + alias Pleroma.Web.Plugs.RateLimiter require Logger @@ -31,7 +31,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do plug(:fetch_session) plug(:fetch_flash) - plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]) + plug(:skip_plug, [ + Pleroma.Web.Plugs.OAuthScopesPlug, + Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + ]) plug(RateLimiter, [name: :authentication] when action == :create_authorization) @@ -119,7 +122,7 @@ defp handle_existing_authorization( redirect_uri = redirect_uri(conn, redirect_uri) url_params = %{access_token: token.token} url_params = Maps.put_if_present(url_params, :state, params["state"]) - url = UriHelper.append_uri_params(redirect_uri, url_params) + url = UriHelper.modify_uri_params(redirect_uri, url_params) redirect(conn, external: url) else conn @@ -161,7 +164,7 @@ def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{ redirect_uri = redirect_uri(conn, redirect_uri) url_params = %{code: auth.token} url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"]) - url = UriHelper.append_uri_params(redirect_uri, url_params) + url = UriHelper.modify_uri_params(redirect_uri, url_params) redirect(conn, external: url) else conn @@ -200,7 +203,7 @@ defp handle_create_authorization_error( {:mfa_required, user, auth, _}, params ) do - {:ok, token} = MFA.Token.create_token(user, auth) + {:ok, token} = MFA.Token.create(user, auth) data = %{ "mfa_token" => token.token, @@ -582,7 +585,7 @@ defp put_session_registration_id(%Plug.Conn{} = conn, registration_id), do: put_session(conn, :registration_id, registration_id) defp build_and_response_mfa_token(user, auth) do - with {:ok, token} <- MFA.Token.create_token(user, auth) do + with {:ok, token} <- MFA.Token.create(user, auth) do MFAView.render("mfa_response.json", %{token: token, user: user}) end end diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex similarity index 100% rename from lib/pleroma/web/oauth/oauth_view.ex rename to lib/pleroma/web/o_auth/o_auth_view.ex diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/o_auth/scopes.ex similarity index 97% rename from lib/pleroma/web/oauth/scopes.ex rename to lib/pleroma/web/o_auth/scopes.ex index 6f06f1431..90b9a0471 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/o_auth/scopes.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.OAuth.Scopes do Functions for dealing with scopes. """ - alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug @doc """ Fetch scopes from request params. diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/o_auth/token.ex similarity index 86% rename from lib/pleroma/web/oauth/token.ex rename to lib/pleroma/web/o_auth/token.ex index 08bb7326d..de37998f2 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/o_auth/token.ex @@ -50,7 +50,7 @@ def exchange_token(app, auth) do true <- auth.app_id == app.id do user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{} - create_token( + create( app, user, %{scopes: auth.scopes} @@ -83,8 +83,22 @@ defp put_valid_until(changeset, attrs) do |> validate_required([:valid_until]) end - @spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} - def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do + @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()} + def create(%App{} = app, %User{} = user, attrs \\ %{}) do + with {:ok, token} <- do_create(app, user, attrs) do + if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: __MODULE__ + }) + end + + {:ok, token} + end + end + + defp do_create(app, user, attrs) do %__MODULE__{user_id: user.id, app_id: app.id} |> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes]) |> validate_required([:scopes, :app_id]) @@ -105,11 +119,6 @@ def delete_user_token(%User{id: user_id}, token_id) do |> Repo.delete_all() end - def delete_expired_tokens do - Query.get_expired_tokens() - |> Repo.delete_all() - end - def get_user_tokens(%User{id: user_id}) do Query.get_by_user(user_id) |> Query.preload([:app]) diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/o_auth/token/query.ex similarity index 85% rename from lib/pleroma/web/oauth/token/query.ex rename to lib/pleroma/web/o_auth/token/query.ex index 93d6e26ed..fd6d9b112 100644 --- a/lib/pleroma/web/oauth/token/query.ex +++ b/lib/pleroma/web/o_auth/token/query.ex @@ -33,12 +33,6 @@ def get_by_id(query \\ Token, id) do from(q in query, where: q.id == ^id) end - @spec get_expired_tokens(query, DateTime.t() | nil) :: query - def get_expired_tokens(query \\ Token, date \\ nil) do - expired_date = date || Timex.now() - from(q in query, where: fragment("?", q.valid_until) < ^expired_date) - end - @spec get_by_user(query, String.t()) :: query def get_by_user(query \\ Token, user_id) do from(q in query, where: q.user_id == ^user_id) diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex similarity index 94% rename from lib/pleroma/web/oauth/token/strategy/refresh_token.ex rename to lib/pleroma/web/o_auth/token/strategy/refresh_token.ex index debc29b0b..625b0fde2 100644 --- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex +++ b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex @@ -46,7 +46,7 @@ defp revoke_access_token(token) do defp create_access_token({:error, error}, _), do: {:error, error} defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do - Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token)) + Token.create(app, user, add_refresh_token(token_params, token.refresh_token)) end defp add_refresh_token(params, token) do diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/o_auth/token/strategy/revoke.ex similarity index 100% rename from lib/pleroma/web/oauth/token/strategy/revoke.ex rename to lib/pleroma/web/o_auth/token/strategy/revoke.ex diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/o_auth/token/utils.ex similarity index 100% rename from lib/pleroma/web/oauth/token/utils.ex rename to lib/pleroma/web/o_auth/token/utils.ex diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex similarity index 89% rename from lib/pleroma/web/ostatus/ostatus_controller.ex rename to lib/pleroma/web/o_status/o_status_controller.ex index de1b0b3f0..668ae0ea4 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -5,28 +5,24 @@ defmodule Pleroma.Web.OStatus.OStatusController do use Pleroma.Web, :controller - alias Fallback.RedirectController alias Pleroma.Activity alias Pleroma.Object - alias Pleroma.Plugs.RateLimiter alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Endpoint + alias Pleroma.Web.Fallback.RedirectController alias Pleroma.Web.Metadata.PlayerView + alias Pleroma.Web.Plugs.RateLimiter alias Pleroma.Web.Router - plug(Pleroma.Plugs.EnsureAuthenticatedPlug, - unless_func: &Pleroma.Web.FederatingPlug.federating?/1 - ) - plug( RateLimiter, [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity] ) plug( - Pleroma.Plugs.SetFormatPlug + Pleroma.Web.Plugs.SetFormatPlug when action in [:object, :activity, :notice] ) @@ -37,14 +33,12 @@ def object(%{assigns: %{format: format}} = conn, _params) ActivityPubController.call(conn, :object) end - def object(%{assigns: %{format: format}} = conn, _params) do + def object(conn, _params) do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)} do - case format do - _ -> redirect(conn, to: "/notice/#{activity.id}") - end + redirect(conn, to: "/notice/#{activity.id}") else reason when reason in [{:public?, false}, {:activity, nil}] -> {:error, :not_found} @@ -59,13 +53,11 @@ def activity(%{assigns: %{format: format}} = conn, _params) ActivityPubController.call(conn, :activity) end - def activity(%{assigns: %{format: format}} = conn, _params) do + def activity(conn, _params) do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)} do - case format do - _ -> redirect(conn, to: "/notice/#{activity.id}") - end + redirect(conn, to: "/notice/#{activity.id}") else reason when reason in [{:public?, false}, {:activity, nil}] -> {:error, :not_found} @@ -119,6 +111,7 @@ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do def notice_player(conn, %{"id" => id}) do with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.is_public?(activity), + {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)}, %Object{} = object <- Object.normalize(activity), %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object, true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex deleted file mode 100644 index e3aa4eb7e..000000000 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ /dev/null @@ -1,38 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Token.CleanWorker do - @moduledoc """ - The module represents functions to clean an expired OAuth and MFA tokens. - """ - use GenServer - - @ten_seconds 10_000 - @one_day 86_400_000 - - alias Pleroma.MFA - alias Pleroma.Web.OAuth - alias Pleroma.Workers.BackgroundWorker - - def start_link(_), do: GenServer.start_link(__MODULE__, %{}) - - def init(_) do - Process.send_after(self(), :perform, @ten_seconds) - {:ok, nil} - end - - @doc false - def handle_info(:perform, state) do - BackgroundWorker.enqueue("clean_expired_tokens", %{}) - interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day) - - Process.send_after(self(), :perform, interval) - {:noreply, state} - end - - def perform(:clean) do - OAuth.Token.delete_expired_tokens() - MFA.Token.delete_expired_tokens() - end -end diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index 563edded7..30cf83567 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -8,15 +8,20 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do import Pleroma.Web.ControllerHelper, only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2] - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.RateLimiter alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.RateLimiter require Pleroma.Constants + plug( + Majic.Plug, + [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner] + ) + plug( OpenApiSpex.Plug.PutApiSpec, [module: Pleroma.Web.ApiSpec] when action == :confirmation_resend diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index ea0921c77..6357148d0 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -4,17 +4,19 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + alias Pleroma.Activity alias Pleroma.Chat alias Pleroma.Chat.MessageReference alias Pleroma.Object alias Pleroma.Pagination - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView alias Pleroma.Web.PleromaAPI.ChatView + alias Pleroma.Web.Plugs.OAuthScopesPlug import Ecto.Query @@ -47,7 +49,7 @@ def delete_message(%{assigns: %{user: %{id: user_id} = user}} = conn, %{ }) do with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), - ^chat_id <- cm_ref.chat_id |> to_string(), + ^chat_id <- to_string(cm_ref.chat_id), %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id), {:ok, _} <- remove_or_delete(cm_ref, user) do conn @@ -68,18 +70,13 @@ defp remove_or_delete( end end - defp remove_or_delete(cm_ref, _) do - cm_ref - |> MessageReference.delete() - end + defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref) def post_chat_message( - %{body_params: params, assigns: %{user: %{id: user_id} = user}} = conn, - %{ - id: id - } + %{body_params: params, assigns: %{user: user}} = conn, + %{id: id} ) do - with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), + with {:ok, chat} <- Chat.get_by_user_and_id(user, id), %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient), {:ok, activity} <- CommonAPI.post_chat_message(user, recipient, params[:content], @@ -103,13 +100,12 @@ def post_chat_message( end end - def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{ - id: chat_id, - message_id: message_id - }) do - with %MessageReference{} = cm_ref <- - MessageReference.get_by_id(message_id), - ^chat_id <- cm_ref.chat_id |> to_string(), + def mark_message_as_read( + %{assigns: %{user: %{id: user_id}}} = conn, + %{id: chat_id, message_id: message_id} + ) do + with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), + ^chat_id <- to_string(cm_ref.chat_id), %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id), {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do conn @@ -119,36 +115,28 @@ def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{ end def mark_as_read( - %{ - body_params: %{last_read_id: last_read_id}, - assigns: %{user: %{id: user_id}} - } = conn, + %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn, %{id: id} ) do - with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), - {_n, _} <- - MessageReference.set_all_seen_for_chat(chat, last_read_id) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id), + {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do conn |> put_view(ChatView) |> render("show.json", chat: chat) end end - def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do - with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do - cm_refs = + def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do + chat_message_refs = chat |> MessageReference.for_chat_query() |> Pagination.fetch_paginated(params) conn + |> add_link_headers(chat_message_refs) |> put_view(MessageReferenceView) - |> render("index.json", chat_message_references: cm_refs) - else - _ -> - conn - |> put_status(:not_found) - |> json(%{error: "not found"}) + |> render("index.json", chat_message_references: chat_message_refs) end end @@ -156,13 +144,8 @@ def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do blocked_ap_ids = User.blocked_users_ap_ids(user) chats = - from(c in Chat, - where: c.user_id == ^user_id, - where: c.recipient not in ^blocked_ap_ids, - order_by: [desc: c.updated_at], - inner_join: u in User, - on: u.ap_id == c.recipient - ) + Chat.for_user_query(user_id) + |> where([c], c.recipient not in ^blocked_ap_ids) |> Repo.all() conn @@ -170,8 +153,8 @@ def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do |> render("index.json", chats: chats) end - def create(%{assigns: %{user: user}} = conn, params) do - with %User{ap_id: recipient} <- User.get_by_id(params[:id]), + def create(%{assigns: %{user: user}} = conn, %{id: id}) do + with %User{ap_id: recipient} <- User.get_cached_by_id(id), {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do conn |> put_view(ChatView) @@ -179,8 +162,8 @@ def create(%{assigns: %{user: user}} = conn, params) do end end - def show(%{assigns: %{user: user}} = conn, params) do - with %Chat{} = chat <- Repo.get_by(Chat, user_id: user.id, id: params[:id]) do + def show(%{assigns: %{user: user}} = conn, %{id: id}) do + with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do conn |> put_view(ChatView) |> render("show.json", chat: chat) diff --git a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex index 3d007f324..df52b7566 100644 --- a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex @@ -8,9 +8,9 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] alias Pleroma.Conversation.Participation - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView) diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex new file mode 100644 index 000000000..428c97de6 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -0,0 +1,137 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EmojiFileController do + use Pleroma.Web, :controller + + alias Pleroma.Emoji.Pack + alias Pleroma.Web.ApiSpec + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + Pleroma.Web.Plugs.OAuthScopesPlug, + %{scopes: ["write"], admin: true} + when action in [ + :create, + :update, + :delete + ] + ) + + defdelegate open_api_operation(action), to: ApiSpec.PleromaEmojiFileOperation + + def create(%{body_params: params} = conn, %{name: pack_name}) do + filename = params[:filename] || get_filename(params[:file]) + shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) + + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, file} <- get_file(params[:file]), + {:ok, pack} <- Pack.add_file(pack, shortcode, filename, file) do + json(conn, pack.files) + else + {:error, :already_exists} -> + conn + |> put_status(:conflict) + |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) + + {:error, :empty_values} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: "pack name, shortcode or filename cannot be empty"}) + + {:error, _} = error -> + handle_error(conn, error, %{pack_name: pack_name}) + end + end + + def update(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: pack_name}) do + new_shortcode = params[:new_shortcode] + new_filename = params[:new_filename] + force = params[:force] + + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, pack} <- Pack.update_file(pack, shortcode, new_shortcode, new_filename, force) do + json(conn, pack.files) + else + {:error, :already_exists} -> + conn + |> put_status(:conflict) + |> json(%{ + error: + "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" + }) + + {:error, :empty_values} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + + {:error, _} = error -> + handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) + end + end + + def delete(conn, %{name: pack_name, shortcode: shortcode}) do + with {:ok, pack} <- Pack.load_pack(pack_name), + {:ok, pack} <- Pack.delete_file(pack, shortcode) do + json(conn, pack.files) + else + {:error, :empty_values} -> + conn + |> put_status(:unprocessable_entity) + |> json(%{error: "pack name or shortcode cannot be empty"}) + + {:error, _} = error -> + handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) + end + end + + defp handle_error(conn, {:error, :doesnt_exist}, %{code: emoji_code}) do + conn + |> put_status(:bad_request) + |> json(%{error: "Emoji \"#{emoji_code}\" does not exist"}) + end + + defp handle_error(conn, {:error, :not_found}, %{pack_name: pack_name}) do + conn + |> put_status(:not_found) + |> json(%{error: "pack \"#{pack_name}\" is not found"}) + end + + defp handle_error(conn, {:error, _}, _) do + render_error( + conn, + :internal_server_error, + "Unexpected error occurred while adding file to pack." + ) + end + + defp get_filename(%Plug.Upload{filename: filename}), do: filename + defp get_filename(url) when is_binary(url), do: Path.basename(url) + + def get_file(%Plug.Upload{} = file), do: {:ok, file} + + def get_file(url) when is_binary(url) do + with {:ok, %Tesla.Env{body: body, status: code, headers: headers}} + when code in 200..299 <- Pleroma.HTTP.get(url) do + path = Plug.Upload.random_file!("emoji") + + content_type = + case List.keyfind(headers, "content-type", 0) do + {"content-type", value} -> value + nil -> nil + end + + File.write(path, body) + + {:ok, + %Plug.Upload{ + filename: Path.basename(url), + path: path, + content_type: content_type + }} + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index 657f46324..a9accc5af 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.PleromaAPI.EmojiPackController do use Pleroma.Web, :controller @@ -6,7 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do plug(Pleroma.Web.ApiSpec.CastAndValidate) plug( - Pleroma.Plugs.OAuthScopesPlug, + Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [ :import_from_filesystem, @@ -14,20 +18,21 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do :download, :create, :update, - :delete, - :add_file, - :update_file, - :delete_file + :delete ] ) - @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug] - plug(:skip_plug, @skip_plugs when action in [:index, :show, :archive]) + @skip_plugs [ + Pleroma.Web.Plugs.OAuthScopesPlug, + Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + ] + plug(:skip_plug, @skip_plugs when action in [:index, :archive, :show]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation - def remote(conn, %{url: url}) do - with {:ok, packs} <- Pack.list_remote(url) do + def remote(conn, params) do + with {:ok, packs} <- + Pack.list_remote(url: params.url, page_size: params.page_size, page: params.page) do json(conn, packs) else {:error, :not_shareable} -> @@ -184,105 +189,6 @@ def update(%{body_params: %{metadata: metadata}} = conn, %{name: name}) do end end - def add_file(%{body_params: params} = conn, %{name: name}) do - filename = params[:filename] || get_filename(params[:file]) - shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) - - with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params[:file]) do - json(conn, pack.files) - else - {:error, :already_exists} -> - conn - |> put_status(:conflict) - |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) - - {:error, :not_found} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack \"#{name}\" is not found"}) - - {:error, :empty_values} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack name, shortcode or filename cannot be empty"}) - - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while adding file to pack." - ) - end - end - - def update_file(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: name}) do - new_shortcode = params[:new_shortcode] - new_filename = params[:new_filename] - force = params[:force] - - with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do - json(conn, pack.files) - else - {:error, :doesnt_exist} -> - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - - {:error, :already_exists} -> - conn - |> put_status(:conflict) - |> json(%{ - error: - "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" - }) - - {:error, :not_found} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack \"#{name}\" is not found"}) - - {:error, :empty_values} -> - conn - |> put_status(:bad_request) - |> json(%{error: "new_shortcode or new_filename cannot be empty"}) - - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while updating file in pack." - ) - end - end - - def delete_file(conn, %{name: name, shortcode: shortcode}) do - with {:ok, pack} <- Pack.delete_file(name, shortcode) do - json(conn, pack.files) - else - {:error, :doesnt_exist} -> - conn - |> put_status(:bad_request) - |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - - {:error, :not_found} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack \"#{name}\" is not found"}) - - {:error, :empty_values} -> - conn - |> put_status(:bad_request) - |> json(%{error: "pack name or shortcode cannot be empty"}) - - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while removing file from pack." - ) - end - end - def import_from_filesystem(conn, _params) do with {:ok, names} <- Pack.import_from_filesystem() do json(conn, names) @@ -298,7 +204,4 @@ def import_from_filesystem(conn, _params) do |> json(%{error: "Error accessing emoji pack directory"}) end end - - defp get_filename(%Plug.Upload{filename: filename}), do: filename - defp get_filename(url) when is_binary(url), do: Path.basename(url) end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex index 7f9254c13..ae199a50f 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do alias Pleroma.Activity alias Pleroma.Object - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action in [:create, :delete]) diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex index df6c50ca5..15210f1e6 100644 --- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -5,10 +5,11 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do use Pleroma.Web, :controller - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.Plugs.OAuthScopesPlug + plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update]) plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show) @@ -22,14 +23,15 @@ def show(%{assigns: %{user: user}} = conn, _params) do @doc "PUT /api/v1/pleroma/mascot" def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do - with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), - # Reject if not an image - %{type: "image"} = attachment <- render_attachment(object) do + with {:content_type, "image" <> _} <- {:content_type, file.content_type}, + {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do + attachment = render_attachment(object) {:ok, _user} = User.mascot_update(user, attachment) json(conn, attachment) else - %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images") + {:content_type, _} -> + render_error(conn, :unsupported_media_type, "mascots can only be images") end end diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex index 3ed8bd294..fa32aaa84 100644 --- a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex @@ -6,10 +6,14 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do use Pleroma.Web, :controller alias Pleroma.Notification - alias Pleroma.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :mark_as_read) + + plug( + Pleroma.Web.Plugs.OAuthScopesPlug, + %{scopes: ["write:notifications"]} when action == :mark_as_read + ) + plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex index e9a4fba92..632d65434 100644 --- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -7,10 +7,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex index b86791d09..eba452300 100644 --- a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex @@ -10,8 +10,8 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController do alias Pleroma.MFA alias Pleroma.MFA.TOTP - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(OAuthScopesPlug, %{scopes: ["read:security"]} when action in [:settings]) diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex new file mode 100644 index 000000000..7f089af1c --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.UserImportController do + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.User + alias Pleroma.Web.ApiSpec + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action == :follow) + plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks) + plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes) + + plug(OpenApiSpex.Plug.CastAndValidate) + defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation + + def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + follow(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def follow(%{assigns: %{user: follower}, body_params: %{list: list}} = conn, _) do + identifiers = + list + |> String.split("\n") + |> Enum.map(&(&1 |> String.split(",") |> List.first())) + |> List.delete("Account address") + |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) + |> Enum.reject(&(&1 == "")) + + User.Import.follow_import(follower, identifiers) + json(conn, "job started") + end + + def blocks(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + blocks(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def blocks(%{assigns: %{user: blocker}, body_params: %{list: list}} = conn, _) do + User.Import.blocks_import(blocker, prepare_user_identifiers(list)) + json(conn, "job started") + end + + def mutes(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do + mutes(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{}) + end + + def mutes(%{assigns: %{user: user}, body_params: %{list: list}} = conn, _) do + User.Import.mutes_import(user, prepare_user_identifiers(list)) + json(conn, "job started") + end + + defp prepare_user_identifiers(list) do + list + |> String.split() + |> Enum.map(&String.trim_leading(&1, "@")) + end +end diff --git a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex index bbff93abe..95bd4c368 100644 --- a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex +++ b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex @@ -10,14 +10,14 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do alias Pleroma.Activity alias Pleroma.HTML alias Pleroma.Object + alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView - alias Pleroma.Web.MastodonAPI.StatusView def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do object = Object.normalize(activity) - user = StatusView.get_user(activity.data["actor"]) + user = CommonAPI.get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) %{ diff --git a/lib/pleroma/web/plug.ex b/lib/pleroma/web/plug.ex new file mode 100644 index 000000000..840b35072 --- /dev/null +++ b/lib/pleroma/web/plug.ex @@ -0,0 +1,8 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plug do + # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug` + @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t() +end diff --git a/lib/pleroma/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex similarity index 89% rename from lib/pleroma/plugs/admin_secret_authentication_plug.ex rename to lib/pleroma/web/plugs/admin_secret_authentication_plug.ex index 2e54df47a..d7d4e4092 100644 --- a/lib/pleroma/plugs/admin_secret_authentication_plug.ex +++ b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex @@ -2,12 +2,12 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do +defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do import Plug.Conn - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.RateLimiter alias Pleroma.User + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.RateLimiter def init(options) do options diff --git a/lib/pleroma/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex similarity index 94% rename from lib/pleroma/plugs/authentication_plug.ex rename to lib/pleroma/web/plugs/authentication_plug.ex index 057ea42f1..e2a8b1b69 100644 --- a/lib/pleroma/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -2,8 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.AuthenticationPlug do - alias Pleroma.Plugs.OAuthScopesPlug +defmodule Pleroma.Web.Plugs.AuthenticationPlug do alias Pleroma.User import Plug.Conn @@ -65,7 +64,7 @@ def call( conn |> assign(:user, auth_user) - |> OAuthScopesPlug.skip_plug() + |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug() else conn end diff --git a/lib/pleroma/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex similarity index 92% rename from lib/pleroma/plugs/basic_auth_decoder_plug.ex rename to lib/pleroma/web/plugs/basic_auth_decoder_plug.ex index af7ecb0d8..4dadfb000 100644 --- a/lib/pleroma/plugs/basic_auth_decoder_plug.ex +++ b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.BasicAuthDecoderPlug do +defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do import Plug.Conn def init(options) do diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex similarity index 96% rename from lib/pleroma/plugs/cache.ex rename to lib/pleroma/web/plugs/cache.ex index f65c2a189..6de01804a 100644 --- a/lib/pleroma/plugs/cache.ex +++ b/lib/pleroma/web/plugs/cache.ex @@ -2,19 +2,19 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.Cache do +defmodule Pleroma.Web.Plugs.Cache do @moduledoc """ Caches successful GET responses. To enable the cache add the plug to a router pipeline or controller: - plug(Pleroma.Plugs.Cache) + plug(Pleroma.Web.Plugs.Cache) ## Configuration To configure the plug you need to pass settings as the second argument to the `plug/2` macro: - plug(Pleroma.Plugs.Cache, [ttl: nil, query_params: true]) + plug(Pleroma.Web.Plugs.Cache, [ttl: nil, query_params: true]) Available options: diff --git a/lib/pleroma/plugs/digest.ex b/lib/pleroma/web/plugs/digest_plug.ex similarity index 100% rename from lib/pleroma/plugs/digest.ex rename to lib/pleroma/web/plugs/digest_plug.ex diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex similarity index 94% rename from lib/pleroma/plugs/ensure_authenticated_plug.ex rename to lib/pleroma/web/plugs/ensure_authenticated_plug.ex index 3fe550806..ea2af6881 100644 --- a/lib/pleroma/plugs/ensure_authenticated_plug.ex +++ b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do +defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlug do import Plug.Conn import Pleroma.Web.TranslationHelpers diff --git a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex similarity index 91% rename from lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex rename to lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex index 7265bb87a..3bebdac6d 100644 --- a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex +++ b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do +defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug do import Pleroma.Web.TranslationHelpers import Plug.Conn diff --git a/lib/pleroma/plugs/ensure_user_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_key_plug.ex similarity index 87% rename from lib/pleroma/plugs/ensure_user_key_plug.ex rename to lib/pleroma/web/plugs/ensure_user_key_plug.ex index 9795cdbde..70d3091f0 100644 --- a/lib/pleroma/plugs/ensure_user_key_plug.ex +++ b/lib/pleroma/web/plugs/ensure_user_key_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsureUserKeyPlug do +defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do import Plug.Conn def init(opts) do diff --git a/lib/pleroma/plugs/expect_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex similarity index 71% rename from lib/pleroma/plugs/expect_authenticated_check_plug.ex rename to lib/pleroma/web/plugs/expect_authenticated_check_plug.ex index 66b8d5de5..0925ded4d 100644 --- a/lib/pleroma/plugs/expect_authenticated_check_plug.ex +++ b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex @@ -2,9 +2,9 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.ExpectAuthenticatedCheckPlug do +defmodule Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug do @moduledoc """ - Marks `Pleroma.Plugs.EnsureAuthenticatedPlug` as expected to be executed later in plug chain. + Marks `Pleroma.Web.Plugs.EnsureAuthenticatedPlug` as expected to be executed later in plug chain. No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`). """ diff --git a/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex similarity index 70% rename from lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex rename to lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex index ba0ef76bd..ace512a78 100644 --- a/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex +++ b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex @@ -2,9 +2,9 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug do +defmodule Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug do @moduledoc """ - Marks `Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug` as expected to be executed later in plug + Marks `Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug` as expected to be executed later in plug chain. No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`). diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/web/plugs/federating_plug.ex similarity index 93% rename from lib/pleroma/plugs/federating_plug.ex rename to lib/pleroma/web/plugs/federating_plug.ex index 09038f3c6..3c90a7644 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/web/plugs/federating_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.FederatingPlug do +defmodule Pleroma.Web.Plugs.FederatingPlug do import Plug.Conn def init(options) do diff --git a/lib/pleroma/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex similarity index 62% rename from lib/pleroma/plugs/frontend_static.ex rename to lib/pleroma/web/plugs/frontend_static.ex index 11a0d5382..1b0b36813 100644 --- a/lib/pleroma/plugs/frontend_static.ex +++ b/lib/pleroma/web/plugs/frontend_static.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.FrontendStatic do +defmodule Pleroma.Web.Plugs.FrontendStatic do require Pleroma.Constants @moduledoc """ @@ -34,22 +34,26 @@ def init(opts) do end def call(conn, opts) do - frontend_type = Map.get(opts, :frontend_type, :primary) - path = file_path("", frontend_type) - - if path do - conn - |> call_static(opts, path) + with false <- invalid_path?(conn.path_info), + frontend_type <- Map.get(opts, :frontend_type, :primary), + path when not is_nil(path) <- file_path("", frontend_type) do + call_static(conn, opts, path) else - conn + _ -> + conn end end - defp call_static(conn, opts, from) do - opts = - opts - |> Map.put(:from, from) + defp invalid_path?(list) do + invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"])) + end + defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true + defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t) + defp invalid_path?([], _match), do: false + + defp call_static(conn, opts, from) do + opts = Map.put(opts, :from, from) Plug.Static.call(conn, opts) end end diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex similarity index 99% rename from lib/pleroma/plugs/http_security_plug.ex rename to lib/pleroma/web/plugs/http_security_plug.ex index c363b193b..45aaf188e 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.HTTPSecurityPlug do +defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do alias Pleroma.Config import Plug.Conn diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/web/plugs/http_signature_plug.ex similarity index 100% rename from lib/pleroma/plugs/http_signature.ex rename to lib/pleroma/web/plugs/http_signature_plug.ex diff --git a/lib/pleroma/plugs/idempotency_plug.ex b/lib/pleroma/web/plugs/idempotency_plug.ex similarity index 97% rename from lib/pleroma/plugs/idempotency_plug.ex rename to lib/pleroma/web/plugs/idempotency_plug.ex index f41397075..254a790b0 100644 --- a/lib/pleroma/plugs/idempotency_plug.ex +++ b/lib/pleroma/web/plugs/idempotency_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.IdempotencyPlug do +defmodule Pleroma.Web.Plugs.IdempotencyPlug do import Phoenix.Controller, only: [json: 2] import Plug.Conn diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex similarity index 91% rename from lib/pleroma/plugs/instance_static.ex rename to lib/pleroma/web/plugs/instance_static.ex index 0fb57e422..54b9175df 100644 --- a/lib/pleroma/plugs/instance_static.ex +++ b/lib/pleroma/web/plugs/instance_static.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.InstanceStatic do +defmodule Pleroma.Web.Plugs.InstanceStatic do require Pleroma.Constants @moduledoc """ @@ -16,7 +16,7 @@ def file_path(path) do instance_path = Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path) - frontend_path = Pleroma.Plugs.FrontendStatic.file_path(path, :primary) + frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, :primary) (File.exists?(instance_path) && instance_path) || (frontend_path && File.exists?(frontend_path) && frontend_path) || diff --git a/lib/pleroma/plugs/legacy_authentication_plug.ex b/lib/pleroma/web/plugs/legacy_authentication_plug.ex similarity index 87% rename from lib/pleroma/plugs/legacy_authentication_plug.ex rename to lib/pleroma/web/plugs/legacy_authentication_plug.ex index d346e01a6..2a54d0b59 100644 --- a/lib/pleroma/plugs/legacy_authentication_plug.ex +++ b/lib/pleroma/web/plugs/legacy_authentication_plug.ex @@ -2,10 +2,9 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.LegacyAuthenticationPlug do +defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlug do import Plug.Conn - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User def init(options) do @@ -29,7 +28,7 @@ def call( conn |> assign(:auth_user, user) |> assign(:user, user) - |> OAuthScopesPlug.skip_plug() + |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug() else _ -> conn diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex similarity index 100% rename from lib/pleroma/plugs/mapped_signature_to_identity_plug.ex rename to lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex similarity index 98% rename from lib/pleroma/plugs/oauth_plug.ex rename to lib/pleroma/web/plugs/o_auth_plug.ex index 6fa71ef47..c7b58d90f 100644 --- a/lib/pleroma/plugs/oauth_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.OAuthPlug do +defmodule Pleroma.Web.Plugs.OAuthPlug do import Plug.Conn import Ecto.Query diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex similarity index 94% rename from lib/pleroma/plugs/oauth_scopes_plug.ex rename to lib/pleroma/web/plugs/o_auth_scopes_plug.ex index efc25b79f..cfc30837c 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.OAuthScopesPlug do +defmodule Pleroma.Web.Plugs.OAuthScopesPlug do import Plug.Conn import Pleroma.Web.Gettext @@ -53,7 +53,7 @@ def drop_auth_info(conn) do |> assign(:token, nil) end - @doc "Filters descendants of supported scopes" + @doc "Keeps those of `scopes` which are descendants of `supported_scopes`" def filter_descendants(scopes, supported_scopes) do Enum.filter( scopes, diff --git a/lib/pleroma/plugs/plug_helper.ex b/lib/pleroma/web/plugs/plug_helper.ex similarity index 97% rename from lib/pleroma/plugs/plug_helper.ex rename to lib/pleroma/web/plugs/plug_helper.ex index 9c67be8ef..b314e7596 100644 --- a/lib/pleroma/plugs/plug_helper.ex +++ b/lib/pleroma/web/plugs/plug_helper.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.PlugHelper do +defmodule Pleroma.Web.Plugs.PlugHelper do @moduledoc "Pleroma Plug helper" @called_plugs_list_id :called_plugs diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/web/plugs/rate_limiter.ex similarity index 93% rename from lib/pleroma/plugs/rate_limiter/rate_limiter.ex rename to lib/pleroma/web/plugs/rate_limiter.ex index c51e2c634..a589610d1 100644 --- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex +++ b/lib/pleroma/web/plugs/rate_limiter.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.RateLimiter do +defmodule Pleroma.Web.Plugs.RateLimiter do @moduledoc """ ## Configuration @@ -35,8 +35,8 @@ defmodule Pleroma.Plugs.RateLimiter do AllowedSyntax: - plug(Pleroma.Plugs.RateLimiter, name: :limiter_name) - plug(Pleroma.Plugs.RateLimiter, options) # :name is a required option + plug(Pleroma.Web.Plugs.RateLimiter, name: :limiter_name) + plug(Pleroma.Web.Plugs.RateLimiter, options) # :name is a required option Allowed options: @@ -46,11 +46,11 @@ defmodule Pleroma.Plugs.RateLimiter do Inside a controller: - plug(Pleroma.Plugs.RateLimiter, [name: :one] when action == :one) - plug(Pleroma.Plugs.RateLimiter, [name: :two] when action in [:two, :three]) + plug(Pleroma.Web.Plugs.RateLimiter, [name: :one] when action == :one) + plug(Pleroma.Web.Plugs.RateLimiter, [name: :two] when action in [:two, :three]) plug( - Pleroma.Plugs.RateLimiter, + Pleroma.Web.Plugs.RateLimiter, [name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]] when action in ~w(fav_status unfav_status)a ) @@ -59,7 +59,7 @@ defmodule Pleroma.Plugs.RateLimiter do pipeline :api do ... - plug(Pleroma.Plugs.RateLimiter, name: :one) + plug(Pleroma.Web.Plugs.RateLimiter, name: :one) ... end """ @@ -67,8 +67,8 @@ defmodule Pleroma.Plugs.RateLimiter do import Plug.Conn alias Pleroma.Config - alias Pleroma.Plugs.RateLimiter.LimiterSupervisor alias Pleroma.User + alias Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor require Logger diff --git a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex similarity index 83% rename from lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex rename to lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex index 884268d96..5642bb205 100644 --- a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex +++ b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex @@ -1,4 +1,8 @@ -defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor do use DynamicSupervisor import Cachex.Spec diff --git a/lib/pleroma/plugs/rate_limiter/supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex similarity index 51% rename from lib/pleroma/plugs/rate_limiter/supervisor.ex rename to lib/pleroma/web/plugs/rate_limiter/supervisor.ex index 9672f7876..a1c84063d 100644 --- a/lib/pleroma/plugs/rate_limiter/supervisor.ex +++ b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex @@ -1,4 +1,8 @@ -defmodule Pleroma.Plugs.RateLimiter.Supervisor do +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.RateLimiter.Supervisor do use Supervisor def start_link(opts) do @@ -7,7 +11,7 @@ def start_link(opts) do def init(_args) do children = [ - Pleroma.Plugs.RateLimiter.LimiterSupervisor + Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor ] opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] diff --git a/lib/pleroma/web/plugs/remote_ip.ex b/lib/pleroma/web/plugs/remote_ip.ex new file mode 100644 index 000000000..401e2cbfa --- /dev/null +++ b/lib/pleroma/web/plugs/remote_ip.ex @@ -0,0 +1,48 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.RemoteIp do + @moduledoc """ + This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. + """ + + alias Pleroma.Config + import Plug.Conn + + @behaviour Plug + + def init(_), do: nil + + def call(%{remote_ip: original_remote_ip} = conn, _) do + if Config.get([__MODULE__, :enabled]) do + %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts()) + assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip) + else + conn + end + end + + defp remote_ip_opts do + headers = Config.get([__MODULE__, :headers], []) |> MapSet.new() + reserved = Config.get([__MODULE__, :reserved], []) + + proxies = + Config.get([__MODULE__, :proxies], []) + |> Enum.concat(reserved) + |> Enum.map(&maybe_add_cidr/1) + + {headers, proxies} + end + + defp maybe_add_cidr(proxy) when is_binary(proxy) do + proxy = + cond do + "/" in String.codepoints(proxy) -> proxy + InetCidr.v4?(InetCidr.parse_address!(proxy)) -> proxy <> "/32" + InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128" + end + + InetCidr.parse(proxy, true) + end +end diff --git a/lib/pleroma/plugs/session_authentication_plug.ex b/lib/pleroma/web/plugs/session_authentication_plug.ex similarity index 89% rename from lib/pleroma/plugs/session_authentication_plug.ex rename to lib/pleroma/web/plugs/session_authentication_plug.ex index 0f83a5e53..6e176d553 100644 --- a/lib/pleroma/plugs/session_authentication_plug.ex +++ b/lib/pleroma/web/plugs/session_authentication_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SessionAuthenticationPlug do +defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do import Plug.Conn def init(options) do diff --git a/lib/pleroma/plugs/set_format_plug.ex b/lib/pleroma/web/plugs/set_format_plug.ex similarity index 92% rename from lib/pleroma/plugs/set_format_plug.ex rename to lib/pleroma/web/plugs/set_format_plug.ex index c03fcb28d..c16d2f81d 100644 --- a/lib/pleroma/plugs/set_format_plug.ex +++ b/lib/pleroma/web/plugs/set_format_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SetFormatPlug do +defmodule Pleroma.Web.Plugs.SetFormatPlug do import Plug.Conn, only: [assign: 3, fetch_query_params: 1] def init(_), do: nil diff --git a/lib/pleroma/plugs/set_locale_plug.ex b/lib/pleroma/web/plugs/set_locale_plug.ex similarity index 97% rename from lib/pleroma/plugs/set_locale_plug.ex rename to lib/pleroma/web/plugs/set_locale_plug.ex index 9a21d0a9d..d9d24b93f 100644 --- a/lib/pleroma/plugs/set_locale_plug.ex +++ b/lib/pleroma/web/plugs/set_locale_plug.ex @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only # NOTE: this module is based on https://github.com/smeevil/set_locale -defmodule Pleroma.Plugs.SetLocalePlug do +defmodule Pleroma.Web.Plugs.SetLocalePlug do import Plug.Conn, only: [get_req_header: 2, assign: 3] def init(_), do: nil diff --git a/lib/pleroma/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex similarity index 87% rename from lib/pleroma/plugs/set_user_session_id_plug.ex rename to lib/pleroma/web/plugs/set_user_session_id_plug.ex index 730c4ac74..e520159e4 100644 --- a/lib/pleroma/plugs/set_user_session_id_plug.ex +++ b/lib/pleroma/web/plugs/set_user_session_id_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SetUserSessionIdPlug do +defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do import Plug.Conn alias Pleroma.User diff --git a/lib/pleroma/plugs/static_fe_plug.ex b/lib/pleroma/web/plugs/static_fe_plug.ex similarity index 93% rename from lib/pleroma/plugs/static_fe_plug.ex rename to lib/pleroma/web/plugs/static_fe_plug.ex index 143665c71..658a1052e 100644 --- a/lib/pleroma/plugs/static_fe_plug.ex +++ b/lib/pleroma/web/plugs/static_fe_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.StaticFEPlug do +defmodule Pleroma.Web.Plugs.StaticFEPlug do import Plug.Conn alias Pleroma.Web.StaticFE.StaticFEController diff --git a/lib/pleroma/plugs/trailing_format_plug.ex b/lib/pleroma/web/plugs/trailing_format_plug.ex similarity index 95% rename from lib/pleroma/plugs/trailing_format_plug.ex rename to lib/pleroma/web/plugs/trailing_format_plug.ex index 8b4d5fc9f..e3f57c14a 100644 --- a/lib/pleroma/plugs/trailing_format_plug.ex +++ b/lib/pleroma/web/plugs/trailing_format_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.TrailingFormatPlug do +defmodule Pleroma.Web.Plugs.TrailingFormatPlug do @moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers." @behaviour Plug diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex similarity index 98% rename from lib/pleroma/plugs/uploaded_media.ex rename to lib/pleroma/web/plugs/uploaded_media.ex index 40984cfc0..402a8bb34 100644 --- a/lib/pleroma/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UploadedMedia do +defmodule Pleroma.Web.Plugs.UploadedMedia do @moduledoc """ """ diff --git a/lib/pleroma/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex similarity index 90% rename from lib/pleroma/plugs/user_enabled_plug.ex rename to lib/pleroma/web/plugs/user_enabled_plug.ex index 23e800a74..fa28ee48b 100644 --- a/lib/pleroma/plugs/user_enabled_plug.ex +++ b/lib/pleroma/web/plugs/user_enabled_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserEnabledPlug do +defmodule Pleroma.Web.Plugs.UserEnabledPlug do import Plug.Conn alias Pleroma.User diff --git a/lib/pleroma/plugs/user_fetcher_plug.ex b/lib/pleroma/web/plugs/user_fetcher_plug.ex similarity index 91% rename from lib/pleroma/plugs/user_fetcher_plug.ex rename to lib/pleroma/web/plugs/user_fetcher_plug.ex index 235c77d85..4039600da 100644 --- a/lib/pleroma/plugs/user_fetcher_plug.ex +++ b/lib/pleroma/web/plugs/user_fetcher_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserFetcherPlug do +defmodule Pleroma.Web.Plugs.UserFetcherPlug do alias Pleroma.User import Plug.Conn diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/web/plugs/user_is_admin_plug.ex similarity index 91% rename from lib/pleroma/plugs/user_is_admin_plug.ex rename to lib/pleroma/web/plugs/user_is_admin_plug.ex index 488a61d1d..531c965f0 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/web/plugs/user_is_admin_plug.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserIsAdminPlug do +defmodule Pleroma.Web.Plugs.UserIsAdminPlug do import Pleroma.Web.TranslationHelpers import Plug.Conn diff --git a/lib/pleroma/web/preload/instance.ex b/lib/pleroma/web/preload/providers/instance.ex similarity index 79% rename from lib/pleroma/web/preload/instance.ex rename to lib/pleroma/web/preload/providers/instance.ex index 50d1f3382..a549bb1eb 100644 --- a/lib/pleroma/web/preload/instance.ex +++ b/lib/pleroma/web/preload/providers/instance.ex @@ -3,15 +3,17 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Preload.Providers.Instance do - alias Pleroma.Plugs.InstanceStatic alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.Nodeinfo.Nodeinfo + alias Pleroma.Web.Plugs.InstanceStatic alias Pleroma.Web.Preload.Providers.Provider + alias Pleroma.Web.TwitterAPI.UtilView @behaviour Provider @instance_url "/api/v1/instance" @panel_url "/instance/panel.html" @nodeinfo_url "/nodeinfo/2.0.json" + @fe_config_url "/api/pleroma/frontend_configurations" @impl Provider def generate_terms(_params) do @@ -19,6 +21,7 @@ def generate_terms(_params) do |> build_info_tag() |> build_panel_tag() |> build_nodeinfo_tag() + |> build_fe_config_tag() end defp build_info_tag(acc) do @@ -47,4 +50,10 @@ defp build_nodeinfo_tag(acc) do Map.put(acc, @nodeinfo_url, nodeinfo_data) end end + + defp build_fe_config_tag(acc) do + fe_data = UtilView.render("frontend_configurations.json", %{}) + + Map.put(acc, @fe_config_url, fe_data) + end end diff --git a/lib/pleroma/web/preload/provider.ex b/lib/pleroma/web/preload/providers/provider.ex similarity index 100% rename from lib/pleroma/web/preload/provider.ex rename to lib/pleroma/web/preload/providers/provider.ex diff --git a/lib/pleroma/web/preload/timelines.ex b/lib/pleroma/web/preload/providers/timelines.ex similarity index 100% rename from lib/pleroma/web/preload/timelines.ex rename to lib/pleroma/web/preload/providers/timelines.ex diff --git a/lib/pleroma/web/preload/user.ex b/lib/pleroma/web/preload/providers/user.ex similarity index 100% rename from lib/pleroma/web/preload/user.ex rename to lib/pleroma/web/preload/providers/user.ex diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push.ex similarity index 100% rename from lib/pleroma/web/push/push.ex rename to lib/pleroma/web/push.ex diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 16368485e..da535aa68 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.Push.Impl do @types ["Create", "Follow", "Announce", "Like", "Move"] @doc "Performs sending notifications for user subscriptions" - @spec perform(Notification.t()) :: list(any) | :error + @spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type} def perform( %{ activity: %{data: %{"type" => activity_type}} = activity, @@ -64,20 +64,20 @@ def perform(_) do @doc "Push message to web" def push_message(body, sub, api_key, subscription) do case WebPushEncryption.send_web_push(body, sub, api_key) do - {:ok, %{status_code: code}} when 400 <= code and code < 500 -> + {:ok, %{status: code}} when code in 400..499 -> Logger.debug("Removing subscription record") Repo.delete!(subscription) :ok - {:ok, %{status_code: code}} when 200 <= code and code < 300 -> + {:ok, %{status: code}} when code in 200..299 -> :ok - {:ok, %{status_code: code}} -> + {:ok, %{status: code}} -> Logger.error("Web Push Notification failed with code: #{code}") :error - _ -> - Logger.error("Web Push Notification failed with unknown error") + error -> + Logger.error("Web Push Notification failed with #{inspect(error)}") :error end end diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index 8e2b51508..28f75b18d 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -5,7 +5,8 @@ defmodule Pleroma.Web.RelMe do @options [ pool: :media, - max_body: 2_000_000 + max_body: 2_000_000, + recv_timeout: 2_000 ] if Pleroma.Config.get(:env) == :test do @@ -23,18 +24,8 @@ def parse(url) when is_binary(url) do def parse(_), do: {:error, "No URL provided"} defp parse_url(url) do - opts = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.merge(@options, - recv_timeout: 2_000, - with_body: true - ) - else - @options - end - with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <- - Pleroma.HTTP.get(url, [], adapter: opts), + Pleroma.HTTP.get(url, [], @options), {:ok, html_tree} <- Floki.parse_document(html), data <- Floki.attribute(html_tree, "link[rel~=me]", "href") ++ diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index b7852c6e3..d67b594b5 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -9,14 +9,15 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser - @rich_media_options [ + @options [ pool: :media, - max_body: 2_000_000 + max_body: 2_000_000, + recv_timeout: 2_000 ] @spec validate_page_url(URI.t() | binary()) :: :ok | :error defp validate_page_url(page_url) when is_binary(page_url) do - validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld]) + validate_tld = Config.get([Pleroma.Formatter, :validate_tld]) page_url |> Linkify.Parser.url?(validate_tld: validate_tld) @@ -56,7 +57,6 @@ defp get_tld(host) do def fetch_data_for_object(object) do with true <- Config.get([:rich_media, :enabled]), - false <- object.data["sensitive"] || false, {:ok, page_url} <- HTML.extract_first_external_url_from_object(object), :ok <- validate_page_url(page_url), @@ -86,18 +86,8 @@ def perform(:fetch, %Activity{} = activity) do def rich_media_get(url) do headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] - options = - if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do - Keyword.merge(@rich_media_options, - recv_timeout: 2_000, - with_body: true - ) - else - @rich_media_options - end - head_check = - case Pleroma.HTTP.head(url, headers, adapter: options) do + case Pleroma.HTTP.head(url, headers, @options) do # If the HEAD request didn't reach the server for whatever reason, # we assume the GET that comes right after won't either {:error, _} = e -> @@ -112,7 +102,7 @@ def rich_media_get(url) do :ok end - with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, adapter: options) + with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, @options) end defp check_content_type(headers) do @@ -128,7 +118,7 @@ defp check_content_type(headers) do end end - @max_body @rich_media_options[:max_body] + @max_body @options[:max_body] defp check_content_length(headers) do case List.keyfind(headers, "content-length", 0) do {_, maybe_content_length} -> diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 569249f51..c70d2fdba 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -20,31 +20,61 @@ def parse(url) do with {:ok, data} <- get_cached_or_parse(url), {:ok, _} <- set_ttl_based_on_image(data, url) do {:ok, data} - else - error -> - Logger.error(fn -> "Rich media error: #{inspect(error)}" end) end end defp get_cached_or_parse(url) do - case Cachex.fetch!(:rich_media_cache, url, fn _ -> {:commit, parse_url(url)} end) do - {:ok, _data} = res -> - res + case Cachex.fetch(:rich_media_cache, url, fn -> + case parse_url(url) do + {:ok, _} = res -> + {:commit, res} - {:error, :body_too_large} = e -> - e + {:error, reason} = e -> + # Unfortunately we have to log errors here, instead of doing that + # along with ttl setting at the bottom. Otherwise we can get log spam + # if more than one process was waiting for the rich media card + # while it was generated. Ideally we would set ttl here as well, + # so we don't override it number_of_waiters_on_generation + # times, but one, obviously, can't set ttl for not-yet-created entry + # and Cachex doesn't support returning ttl from the fetch callback. + log_error(url, reason) + {:commit, e} + end + end) do + {action, res} when action in [:commit, :ok] -> + case res do + {:ok, _data} = res -> + res - {:error, {:content_type, _}} = e -> - e + {:error, reason} = e -> + if action == :commit, do: set_error_ttl(url, reason) + e + end - # The TTL is not set for the errors above, since they are unlikely to change - # with time - {:error, _} = e -> - ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) - Cachex.expire(:rich_media_cache, url, ttl) - e + {:error, e} -> + {:error, {:cachex_error, e}} end end + + defp set_error_ttl(_url, :body_too_large), do: :ok + defp set_error_ttl(_url, {:content_type, _}), do: :ok + + # The TTL is not set for the errors above, since they are unlikely to change + # with time + + defp set_error_ttl(url, _reason) do + ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) + Cachex.expire(:rich_media_cache, url, ttl) + :ok + end + + defp log_error(url, {:invalid_metadata, data}) do + Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) + end + + defp log_error(url, reason) do + Logger.warn(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) + end end @doc """ @@ -98,7 +128,7 @@ defp get_ttl_from_image(data, url) do end) end - defp parse_url(url) do + def parse_url(url) do with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url), {:ok, html} <- Floki.parse_document(html) do html @@ -124,7 +154,7 @@ defp check_parsed_data(%{"title" => title} = data) end defp check_parsed_data(data) do - {:error, "Found metadata was invalid or incomplete: #{inspect(data)}"} + {:error, {:invalid_metadata, data}} end defp clean_parsed_data(data) do diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex new file mode 100644 index 000000000..8353f0fff --- /dev/null +++ b/lib/pleroma/web/rich_media/parser/ttl.ex @@ -0,0 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Parser.TTL do + @callback ttl(Map.t(), String.t()) :: Integer.t() | nil +end diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex similarity index 87% rename from lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex rename to lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex index c5aaea2d4..fc4ef79c0 100644 --- a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex +++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex @@ -1,7 +1,11 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do @behaviour Pleroma.Web.RichMedia.Parser.TTL - @impl Pleroma.Web.RichMedia.Parser.TTL + @impl true def ttl(data, _url) do image = Map.get(data, :image) diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex similarity index 100% rename from lib/pleroma/web/rich_media/parsers/oembed_parser.ex rename to lib/pleroma/web/rich_media/parsers/o_embed.ex diff --git a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex deleted file mode 100644 index 6b3ec6d30..000000000 --- a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Pleroma.Web.RichMedia.Parser.TTL do - @callback ttl(Map.t(), String.t()) :: {:ok, Integer.t()} | {:error, String.t()} -end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c6433cc53..07a574f35 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -5,6 +5,26 @@ defmodule Pleroma.Web.Router do use Pleroma.Web, :router + pipeline :accepts_html do + plug(:accepts, ["html"]) + end + + pipeline :accepts_html_xml do + plug(:accepts, ["html", "xml", "rss", "atom"]) + end + + pipeline :accepts_html_json do + plug(:accepts, ["html", "activity+json", "json"]) + end + + pipeline :accepts_html_xml_json do + plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"]) + end + + pipeline :accepts_xml_rss_atom do + plug(:accepts, ["xml", "rss", "atom"]) + end + pipeline :browser do plug(:accepts, ["html"]) plug(:fetch_session) @@ -12,31 +32,31 @@ defmodule Pleroma.Web.Router do pipeline :oauth do plug(:fetch_session) - plug(Pleroma.Plugs.OAuthPlug) - plug(Pleroma.Plugs.UserEnabledPlug) + plug(Pleroma.Web.Plugs.OAuthPlug) + plug(Pleroma.Web.Plugs.UserEnabledPlug) end pipeline :expect_authentication do - plug(Pleroma.Plugs.ExpectAuthenticatedCheckPlug) + plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug) end pipeline :expect_public_instance_or_authentication do - plug(Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug) + plug(Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug) end pipeline :authenticate do - plug(Pleroma.Plugs.OAuthPlug) - plug(Pleroma.Plugs.BasicAuthDecoderPlug) - plug(Pleroma.Plugs.UserFetcherPlug) - plug(Pleroma.Plugs.SessionAuthenticationPlug) - plug(Pleroma.Plugs.LegacyAuthenticationPlug) - plug(Pleroma.Plugs.AuthenticationPlug) + plug(Pleroma.Web.Plugs.OAuthPlug) + plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug) + plug(Pleroma.Web.Plugs.UserFetcherPlug) + plug(Pleroma.Web.Plugs.SessionAuthenticationPlug) + plug(Pleroma.Web.Plugs.LegacyAuthenticationPlug) + plug(Pleroma.Web.Plugs.AuthenticationPlug) end pipeline :after_auth do - plug(Pleroma.Plugs.UserEnabledPlug) - plug(Pleroma.Plugs.SetUserSessionIdPlug) - plug(Pleroma.Plugs.EnsureUserKeyPlug) + plug(Pleroma.Web.Plugs.UserEnabledPlug) + plug(Pleroma.Web.Plugs.SetUserSessionIdPlug) + plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) end pipeline :base_api do @@ -50,25 +70,25 @@ defmodule Pleroma.Web.Router do plug(:expect_public_instance_or_authentication) plug(:base_api) plug(:after_auth) - plug(Pleroma.Plugs.IdempotencyPlug) + plug(Pleroma.Web.Plugs.IdempotencyPlug) end pipeline :authenticated_api do plug(:expect_authentication) plug(:base_api) plug(:after_auth) - plug(Pleroma.Plugs.EnsureAuthenticatedPlug) - plug(Pleroma.Plugs.IdempotencyPlug) + plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug) + plug(Pleroma.Web.Plugs.IdempotencyPlug) end pipeline :admin_api do plug(:expect_authentication) plug(:base_api) - plug(Pleroma.Plugs.AdminSecretAuthenticationPlug) + plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug) plug(:after_auth) - plug(Pleroma.Plugs.EnsureAuthenticatedPlug) - plug(Pleroma.Plugs.UserIsAdminPlug) - plug(Pleroma.Plugs.IdempotencyPlug) + plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug) + plug(Pleroma.Web.Plugs.UserIsAdminPlug) + plug(Pleroma.Web.Plugs.IdempotencyPlug) end pipeline :mastodon_html do @@ -80,7 +100,7 @@ defmodule Pleroma.Web.Router do pipeline :pleroma_html do plug(:browser) plug(:authenticate) - plug(Pleroma.Plugs.EnsureUserKeyPlug) + plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) end pipeline :well_known do @@ -178,9 +198,14 @@ defmodule Pleroma.Web.Router do get("/users", AdminAPIController, :list_users) get("/users/:nickname", AdminAPIController, :user_show) get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses) + get("/users/:nickname/chats", AdminAPIController, :list_user_chats) get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses) + get("/instance_document/:name", InstanceDocumentController, :show) + patch("/instance_document/:name", InstanceDocumentController, :update) + delete("/instance_document/:name", InstanceDocumentController, :delete) + patch("/users/confirm_email", AdminAPIController, :confirm_email) patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email) @@ -214,9 +239,27 @@ defmodule Pleroma.Web.Router do get("/media_proxy_caches", MediaProxyCacheController, :index) post("/media_proxy_caches/delete", MediaProxyCacheController, :delete) post("/media_proxy_caches/purge", MediaProxyCacheController, :purge) + + get("/chats/:id", ChatController, :show) + get("/chats/:id/messages", ChatController, :messages) + delete("/chats/:id/messages/:message_id", ChatController, :delete_message) end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do + scope "/pack" do + pipe_through(:admin_api) + + post("/", EmojiPackController, :create) + patch("/", EmojiPackController, :update) + delete("/", EmojiPackController, :delete) + end + + scope "/pack" do + pipe_through(:api) + + get("/", EmojiPackController, :show) + end + # Modifying packs scope "/packs" do pipe_through(:admin_api) @@ -225,21 +268,17 @@ defmodule Pleroma.Web.Router do get("/remote", EmojiPackController, :remote) post("/download", EmojiPackController, :download) - post("/:name", EmojiPackController, :create) - patch("/:name", EmojiPackController, :update) - delete("/:name", EmojiPackController, :delete) - - post("/:name/files", EmojiPackController, :add_file) - patch("/:name/files", EmojiPackController, :update_file) - delete("/:name/files", EmojiPackController, :delete_file) + post("/files", EmojiFileController, :create) + patch("/files", EmojiFileController, :update) + delete("/files", EmojiFileController, :delete) end # Pack info / downloading scope "/packs" do pipe_through(:api) + get("/", EmojiPackController, :index) - get("/:name", EmojiPackController, :show) - get("/:name/archive", EmojiPackController, :archive) + get("/archive", EmojiPackController, :archive) end end @@ -260,14 +299,15 @@ defmodule Pleroma.Web.Router do post("/delete_account", UtilController, :delete_account) put("/notification_settings", UtilController, :update_notificaton_settings) post("/disable_account", UtilController, :disable_account) - - post("/blocks_import", UtilController, :blocks_import) - post("/follow_import", UtilController, :follow_import) end scope "/api/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:authenticated_api) + post("/mutes_import", UserImportController, :mutes) + post("/blocks_import", UserImportController, :blocks) + post("/follow_import", UserImportController, :follow) + get("/accounts/mfa", TwoFactorAuthenticationController, :settings) get("/accounts/mfa/backup_codes", TwoFactorAuthenticationController, :backup_codes) get("/accounts/mfa/setup/:method", TwoFactorAuthenticationController, :setup) @@ -546,30 +586,43 @@ defmodule Pleroma.Web.Router do ) end - pipeline :ostatus do - plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"]) - plug(Pleroma.Plugs.StaticFEPlug) - end - - pipeline :oembed do - plug(:accepts, ["json", "xml"]) - end - scope "/", Pleroma.Web do - pipe_through([:ostatus, :http_signature]) + # Note: html format is supported only if static FE is enabled + # Note: http signature is only considered for json requests (no auth for non-json requests) + pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug]) get("/objects/:uuid", OStatus.OStatusController, :object) get("/activities/:uuid", OStatus.OStatusController, :activity) get("/notice/:id", OStatus.OStatusController, :notice) - get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player) # Mastodon compatibility routes get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object) get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity) + end + + scope "/", Pleroma.Web do + # Note: html format is supported only if static FE is enabled + # Note: http signature is only considered for json requests (no auth for non-json requests) + pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug]) + + # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones + get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed) + end + + scope "/", Pleroma.Web do + # Note: html format is supported only if static FE is enabled + pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug]) get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed) - get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed) + end + scope "/", Pleroma.Web do + pipe_through(:accepts_html) + get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player) + end + + scope "/", Pleroma.Web do + pipe_through(:accepts_xml_rss_atom) get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed) end @@ -670,6 +723,8 @@ defmodule Pleroma.Web.Router do end scope "/proxy/", Pleroma.Web.MediaProxy do + get("/preview/:sig/:url", MediaProxyController, :preview) + get("/preview/:sig/:url/:filename", MediaProxyController, :preview) get("/:sig/:url", MediaProxyController, :remote) get("/:sig/:url/:filename", MediaProxyController, :remote) end @@ -715,7 +770,7 @@ defmodule Pleroma.Web.Router do get("/check_password", MongooseIMController, :check_password) end - scope "/", Fallback do + scope "/", Pleroma.Web.Fallback do get("/registration/:token", RedirectController, :registration_page) get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) get("/api*path", RedirectController, :api_not_implemented) diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index a7a891b13..bdec0897a 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -17,12 +17,96 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do plug(:put_view, Pleroma.Web.StaticFE.StaticFEView) plug(:assign_id) - plug(Pleroma.Plugs.EnsureAuthenticatedPlug, - unless_func: &Pleroma.Web.FederatingPlug.federating?/1 - ) - @page_keys ["max_id", "min_id", "limit", "since_id", "order"] + @doc "Renders requested local public activity or public activities of requested user" + def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do + with %Activity{local: true} = activity <- + Activity.get_by_id_with_object(notice_id), + true <- Visibility.is_public?(activity.object), + {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)}, + %User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do + meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user}) + + timeline = + activity.object.data["context"] + |> ActivityPub.fetch_activities_for_context(%{}) + |> Enum.reverse() + |> Enum.map(&represent(&1, &1.object.id == activity.object.id)) + + render(conn, "conversation.html", %{activities: timeline, meta: meta}) + else + %Activity{object: %Object{data: data}} -> + conn + |> put_status(:found) + |> redirect(external: data["url"] || data["external_url"] || data["id"]) + + _ -> + not_found(conn, "Post not found.") + end + end + + def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do + with {_, %User{local: true} = user} <- + {:fetch_user, User.get_cached_by_nickname_or_id(username_or_id)}, + {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do + meta = Metadata.build_tags(%{user: user}) + + params = + params + |> Map.take(@page_keys) + |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end) + + timeline = + user + |> ActivityPub.fetch_user_activities(_reading_user = nil, params) + |> Enum.map(&represent/1) + + prev_page_id = + (params["min_id"] || params["max_id"]) && + List.first(timeline) && List.first(timeline).id + + next_page_id = List.last(timeline) && List.last(timeline).id + + render(conn, "profile.html", %{ + user: User.sanitize_html(user), + timeline: timeline, + prev_page_id: prev_page_id, + next_page_id: next_page_id, + meta: meta + }) + else + _ -> + not_found(conn, "User not found.") + end + end + + def show(%{assigns: %{object_id: _}} = conn, _params) do + url = Helpers.url(conn) <> conn.request_path + + case Activity.get_create_by_object_ap_id_with_object(url) do + %Activity{} = activity -> + to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity) + redirect(conn, to: to) + + _ -> + not_found(conn, "Post not found.") + end + end + + def show(%{assigns: %{activity_id: _}} = conn, _params) do + url = Helpers.url(conn) <> conn.request_path + + case Activity.get_by_ap_id(url) do + %Activity{} = activity -> + to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity) + redirect(conn, to: to) + + _ -> + not_found(conn, "Post not found.") + end + end + defp get_title(%Object{data: %{"name" => name}}) when is_binary(name), do: name @@ -81,91 +165,6 @@ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do } end - def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do - with %Activity{local: true} = activity <- - Activity.get_by_id_with_object(notice_id), - true <- Visibility.is_public?(activity.object), - %User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do - meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user}) - - timeline = - activity.object.data["context"] - |> ActivityPub.fetch_activities_for_context(%{}) - |> Enum.reverse() - |> Enum.map(&represent(&1, &1.object.id == activity.object.id)) - - render(conn, "conversation.html", %{activities: timeline, meta: meta}) - else - %Activity{object: %Object{data: data}} -> - conn - |> put_status(:found) - |> redirect(external: data["url"] || data["external_url"] || data["id"]) - - _ -> - not_found(conn, "Post not found.") - end - end - - def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do - case User.get_cached_by_nickname_or_id(username_or_id) do - %User{} = user -> - meta = Metadata.build_tags(%{user: user}) - - params = - params - |> Map.take(@page_keys) - |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end) - - timeline = - user - |> ActivityPub.fetch_user_activities(nil, params) - |> Enum.map(&represent/1) - - prev_page_id = - (params["min_id"] || params["max_id"]) && - List.first(timeline) && List.first(timeline).id - - next_page_id = List.last(timeline) && List.last(timeline).id - - render(conn, "profile.html", %{ - user: User.sanitize_html(user), - timeline: timeline, - prev_page_id: prev_page_id, - next_page_id: next_page_id, - meta: meta - }) - - _ -> - not_found(conn, "User not found.") - end - end - - def show(%{assigns: %{object_id: _}} = conn, _params) do - url = Helpers.url(conn) <> conn.request_path - - case Activity.get_create_by_object_ap_id_with_object(url) do - %Activity{} = activity -> - to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity) - redirect(conn, to: to) - - _ -> - not_found(conn, "Post not found.") - end - end - - def show(%{assigns: %{activity_id: _}} = conn, _params) do - url = Helpers.url(conn) <> conn.request_path - - case Activity.get_by_ap_id(url) do - %Activity{} = activity -> - to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity) - redirect(conn, to: to) - - _ -> - not_found(conn, "Post not found.") - end - end - defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts), do: assign(conn, :notice_id, notice_id) diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer.ex similarity index 80% rename from lib/pleroma/web/streamer/streamer.ex rename to lib/pleroma/web/streamer.ex index d1d70e556..d618dfe54 100644 --- a/lib/pleroma/web/streamer/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -15,6 +15,8 @@ defmodule Pleroma.Web.Streamer do alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.StreamerView @mix_env Mix.env() @@ -26,53 +28,87 @@ def registry, do: @registry @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"] @doc "Expands and authorizes a stream, and registers the process for streaming." - @spec get_topic_and_add_socket(stream :: String.t(), User.t() | nil, Map.t() | nil) :: + @spec get_topic_and_add_socket( + stream :: String.t(), + User.t() | nil, + Token.t() | nil, + Map.t() | nil + ) :: {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} - def get_topic_and_add_socket(stream, user, params \\ %{}) do - case get_topic(stream, user, params) do + def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do + case get_topic(stream, user, oauth_token, params) do {:ok, topic} -> add_socket(topic, user) error -> error end end @doc "Expand and authorizes a stream" - @spec get_topic(stream :: String.t(), User.t() | nil, Map.t()) :: + @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) :: {:ok, topic :: String.t()} | {:error, :bad_topic} - def get_topic(stream, user, params \\ %{}) + def get_topic(stream, user, oauth_token, params \\ %{}) # Allow all public steams. - def get_topic(stream, _, _) when stream in @public_streams do + def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do {:ok, stream} end # Allow all hashtags streams. - def get_topic("hashtag", _, %{"tag" => tag}) do + def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do {:ok, "hashtag:" <> tag} end # Expand user streams. - def get_topic(stream, %User{} = user, _) when stream in @user_streams do - {:ok, stream <> ":" <> to_string(user.id)} + def get_topic( + stream, + %User{id: user_id} = user, + %Token{user_id: token_user_id} = oauth_token, + _params + ) + when stream in @user_streams and user_id == token_user_id do + # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope) + required_scopes = + if stream == "user:notification" do + ["read:notifications"] + else + ["read:statuses"] + end + + if OAuthScopesPlug.filter_descendants(required_scopes, oauth_token.scopes) == [] do + {:error, :unauthorized} + else + {:ok, stream <> ":" <> to_string(user.id)} + end end - def get_topic(stream, _, _) when stream in @user_streams do + def get_topic(stream, _user, _oauth_token, _params) when stream in @user_streams do {:error, :unauthorized} end # List streams. - def get_topic("list", %User{} = user, %{"list" => id}) do - if Pleroma.List.get(id, user) do - {:ok, "list:" <> to_string(id)} - else - {:error, :bad_topic} + def get_topic( + "list", + %User{id: user_id} = user, + %Token{user_id: token_user_id} = oauth_token, + %{"list" => id} + ) + when user_id == token_user_id do + cond do + OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] -> + {:error, :unauthorized} + + Pleroma.List.get(id, user) -> + {:ok, "list:" <> to_string(id)} + + true -> + {:error, :bad_topic} end end - def get_topic("list", _, _) do + def get_topic("list", _user, _oauth_token, _params) do {:error, :unauthorized} end - def get_topic(_, _, _) do + def get_topic(_stream, _user, _oauth_token, _params) do {:error, :bad_topic} end diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 51603fe0c..3f28f1920 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -228,7 +228,7 @@

<%= Pleroma.Config.get([:instance, :name]) %>

- <%= render @view_module, @view_template, assigns %> + <%= @inner_content %>
diff --git a/lib/pleroma/web/templates/layout/email_styled.html.eex b/lib/pleroma/web/templates/layout/email_styled.html.eex index ca2caaf4d..82cabd889 100644 --- a/lib/pleroma/web/templates/layout/email_styled.html.eex +++ b/lib/pleroma/web/templates/layout/email_styled.html.eex @@ -181,7 +181,7 @@ <% end %> - <%= render @view_module, @view_template, assigns %> + <%= @inner_content %> diff --git a/lib/pleroma/web/templates/layout/metadata_player.html.eex b/lib/pleroma/web/templates/layout/metadata_player.html.eex index 460f28094..c00f6fa21 100644 --- a/lib/pleroma/web/templates/layout/metadata_player.html.eex +++ b/lib/pleroma/web/templates/layout/metadata_player.html.eex @@ -10,7 +10,7 @@ video, audio { } -<%= render @view_module, @view_template, assigns %> +<%= @inner_content %> diff --git a/lib/pleroma/web/templates/layout/static_fe.html.eex b/lib/pleroma/web/templates/layout/static_fe.html.eex index dc0ee2a5c..e6adb526b 100644 --- a/lib/pleroma/web/templates/layout/static_fe.html.eex +++ b/lib/pleroma/web/templates/layout/static_fe.html.eex @@ -9,7 +9,7 @@
- <%= render @view_module, @view_template, assigns %> + <%= @inner_content %>
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/controller.ex similarity index 96% rename from lib/pleroma/web/twitter_api/twitter_api_controller.ex rename to lib/pleroma/web/twitter_api/controller.ex index c2de26b0b..f42dba442 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/controller.ex @@ -6,10 +6,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do use Pleroma.Web, :controller alias Pleroma.Notification - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.TwitterAPI.TokenView require Logger diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index 521dc9322..4480a4922 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do alias Pleroma.Activity alias Pleroma.MFA alias Pleroma.Object.Fetcher - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.Auth.Authenticator alias Pleroma.Web.Auth.TOTPAuthenticator @@ -18,11 +17,11 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] - plug(Pleroma.Web.FederatingPlug) + plug(Pleroma.Web.Plugs.FederatingPlug) # Note: follower can submit the form (with password auth) not being signed in (having no token) plug( - OAuthScopesPlug, + Pleroma.Web.Plugs.OAuthScopesPlug, %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} when action in [:do_follow] ) @@ -135,7 +134,7 @@ defp handle_follow_error(conn, {:verify_mfa_code, followee, token, _} = _) do end defp handle_follow_error(conn, {:mfa_required, followee, user, _} = _) do - {:ok, %{token: token}} = MFA.Token.create_token(user) + {:ok, %{token: token}} = MFA.Token.create(user) render(conn, "follow_mfa.html", %{followee: followee, mfa_token: token, error: false}) end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index f02c4075c..9ead0d626 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -11,20 +11,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Emoji alias Pleroma.Healthcheck alias Pleroma.Notification - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.WebFinger - plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe) - - plug( - OAuthScopesPlug, - %{scopes: ["follow", "write:follows"]} - when action == :follow_import - ) - - plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import) + plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe) plug( OAuthScopesPlug, @@ -82,11 +74,7 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_ end def frontend_configurations(conn, _params) do - config = - Config.get(:frontend_configurations, %{}) - |> Enum.into(%{}) - - json(conn, config) + render(conn, "frontend_configurations.json") end def emoji(conn, _params) do @@ -104,33 +92,6 @@ def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do end end - def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do - follow_import(conn, %{"list" => File.read!(listfile.path)}) - end - - def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do - followed_identifiers = - list - |> String.split("\n") - |> Enum.map(&(&1 |> String.split(",") |> List.first())) - |> List.delete("Account address") - |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) - |> Enum.reject(&(&1 == "")) - - User.follow_import(follower, followed_identifiers) - json(conn, "job started") - end - - def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do - blocks_import(conn, %{"list" => File.read!(listfile.path)}) - end - - def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do - blocked_identifiers = list |> String.split() |> Enum.map(&String.trim_leading(&1, "@")) - User.blocks_import(blocker, blocked_identifiers) - json(conn, "job started") - end - def change_password(%{assigns: %{user: user}} = conn, params) do case CommonAPI.Utils.confirm_current_password(user, params["password"]) do {:ok, user} -> diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex index d3bdb4f62..98eea1d18 100644 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ b/lib/pleroma/web/twitter_api/views/util_view.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do use Pleroma.Web, :view import Phoenix.HTML.Form + alias Pleroma.Config alias Pleroma.Web def status_net_config(instance) do @@ -19,4 +20,9 @@ def status_net_config(instance) do """ end + + def render("frontend_configurations.json", _) do + Config.get(:frontend_configurations, %{}) + |> Enum.into(%{}) + end end diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex index 6b0fbe61e..bcdee6571 100644 --- a/lib/pleroma/web/views/email_view.ex +++ b/lib/pleroma/web/views/email_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.EmailView do use Pleroma.Web, :view import Phoenix.HTML diff --git a/lib/pleroma/web/views/mailer/subscription_view.ex b/lib/pleroma/web/views/mailer/subscription_view.ex index fc3d20816..4562a9d6c 100644 --- a/lib/pleroma/web/views/mailer/subscription_view.ex +++ b/lib/pleroma/web/views/mailer/subscription_view.ex @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.Mailer.SubscriptionView do use Pleroma.Web, :view end diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger.ex similarity index 100% rename from lib/pleroma/web/web_finger/web_finger.ex rename to lib/pleroma/web/web_finger.ex diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex index 7077b20d2..9f0938fc0 100644 --- a/lib/pleroma/web/web_finger/web_finger_controller.ex +++ b/lib/pleroma/web/web_finger/web_finger_controller.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do alias Pleroma.Web.WebFinger - plug(Pleroma.Plugs.SetFormatPlug) - plug(Pleroma.Web.FederatingPlug) + plug(Pleroma.Web.Plugs.SetFormatPlug) + plug(Pleroma.Web.Plugs.FederatingPlug) def host_meta(conn, _params) do xml = WebFinger.host_meta() diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml.ex similarity index 100% rename from lib/pleroma/web/xml/xml.ex rename to lib/pleroma/web/xml.ex diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index cec5a7462..55b5a13d9 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -26,26 +26,10 @@ def perform(%Job{args: %{"op" => "force_password_reset", "user_id" => user_id}}) User.perform(:force_password_reset, user) end - def perform(%Job{ - args: %{ - "op" => "blocks_import", - "blocker_id" => blocker_id, - "blocked_identifiers" => blocked_identifiers - } - }) do - blocker = User.get_cached_by_id(blocker_id) - {:ok, User.perform(:blocks_import, blocker, blocked_identifiers)} - end - - def perform(%Job{ - args: %{ - "op" => "follow_import", - "follower_id" => follower_id, - "followed_identifiers" => followed_identifiers - } - }) do - follower = User.get_cached_by_id(follower_id) - {:ok, User.perform(:follow_import, follower, followed_identifiers)} + def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) + when op in ["blocks_import", "follow_import", "mutes_import"] do + user = User.get_cached_by_id(user_id) + {:ok, User.Import.perform(String.to_atom(op), user, identifiers)} end def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex deleted file mode 100644 index 276f47efc..000000000 --- a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex +++ /dev/null @@ -1,23 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do - @moduledoc """ - The worker to cleanup expired oAuth tokens. - """ - - use Oban.Worker, queue: "background" - - alias Pleroma.Config - alias Pleroma.Web.OAuth.Token - - @impl Oban.Worker - def perform(_job) do - if Config.get([:oauth2, :clean_expired_tokens], false) do - Token.delete_expired_tokens() - end - - :ok - end -end diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex deleted file mode 100644 index 6549207fc..000000000 --- a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex +++ /dev/null @@ -1,48 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do - @moduledoc """ - The worker to purge expired activities. - """ - - use Oban.Worker, queue: "activity_expiration" - - alias Pleroma.Activity - alias Pleroma.ActivityExpiration - alias Pleroma.Config - alias Pleroma.User - alias Pleroma.Web.CommonAPI - - require Logger - - @interval :timer.minutes(1) - - @impl Oban.Worker - def perform(_job) do - if Config.get([ActivityExpiration, :enabled]) do - Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1) - end - after - :ok - end - - def delete_activity(%ActivityExpiration{activity_id: activity_id}) do - with {:activity, %Activity{} = activity} <- - {:activity, Activity.get_by_id_with_object(activity_id)}, - {:user, %User{} = user} <- {:user, User.get_by_ap_id(activity.object.data["actor"])} do - CommonAPI.delete(activity.id, user) - else - {:activity, _} -> - Logger.error( - "#{__MODULE__} Couldn't delete expired activity: not found activity ##{activity_id}" - ) - - {:user, _} -> - Logger.error( - "#{__MODULE__} Couldn't delete expired activity: not found actor of ##{activity_id}" - ) - end - end -end diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex deleted file mode 100644 index 6a79540bc..000000000 --- a/lib/pleroma/workers/cron/stats_worker.ex +++ /dev/null @@ -1,17 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.StatsWorker do - @moduledoc """ - The worker to update peers statistics. - """ - - use Oban.Worker, queue: "background" - - @impl Oban.Worker - def perform(_job) do - Pleroma.Stats.do_collect() - :ok - end -end diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex new file mode 100644 index 000000000..c168890a2 --- /dev/null +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -0,0 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredActivity do + @moduledoc """ + Worker which purges expired activity. + """ + + use Oban.Worker, queue: :activity_expiration, max_attempts: 1 + + import Ecto.Query + + alias Pleroma.Activity + + @spec enqueue(map()) :: + {:ok, Oban.Job.t()} + | {:error, :expired_activities_disabled} + | {:error, :expiration_too_close} + def enqueue(args) do + with true <- enabled?() do + {scheduled_at, args} = Map.pop(args, :expires_at) + + args + |> new(scheduled_at: scheduled_at) + |> Oban.insert() + end + end + + @impl true + def perform(%Oban.Job{args: %{"activity_id" => id}}) do + with %Activity{} = activity <- find_activity(id), + %Pleroma.User{} = user <- find_user(activity.object.data["actor"]) do + Pleroma.Web.CommonAPI.delete(activity.id, user) + end + end + + defp enabled? do + with false <- Pleroma.Config.get([__MODULE__, :enabled], false) do + {:error, :expired_activities_disabled} + end + end + + defp find_activity(id) do + with nil <- Activity.get_by_id_with_object(id) do + {:error, :activity_not_found} + end + end + + defp find_user(ap_id) do + with nil <- Pleroma.User.get_by_ap_id(ap_id) do + {:error, :user_not_found} + end + end + + def get_expiration(id) do + from(j in Oban.Job, + where: j.state == "scheduled", + where: j.queue == "activity_expiration", + where: fragment("?->>'activity_id' = ?", j.args, ^id) + ) + |> Pleroma.Repo.one() + end + + @spec expires_late_enough?(DateTime.t()) :: boolean() + def expires_late_enough?(scheduled_at) do + now = DateTime.utc_now() + diff = DateTime.diff(scheduled_at, now, :millisecond) + min_lifetime = Pleroma.Config.get([__MODULE__, :min_lifetime], 600) + diff > :timer.seconds(min_lifetime) + end +end diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex new file mode 100644 index 000000000..a81e0cd28 --- /dev/null +++ b/lib/pleroma/workers/purge_expired_token.ex @@ -0,0 +1,29 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredToken do + @moduledoc """ + Worker which purges expired OAuth tokens + """ + + use Oban.Worker, queue: :token_expiration, max_attempts: 1 + + @spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) :: + {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} + def enqueue(args) do + {scheduled_at, args} = Map.pop(args, :valid_until) + + args + |> __MODULE__.new(scheduled_at: scheduled_at) + |> Oban.insert() + end + + @impl true + def perform(%Oban.Job{args: %{"token_id" => id, "mod" => module}}) do + module + |> String.to_existing_atom() + |> Pleroma.Repo.get(id) + |> Pleroma.Repo.delete() + end +end diff --git a/lib/xml_builder.ex b/lib/pleroma/xml_builder.ex similarity index 100% rename from lib/xml_builder.ex rename to lib/pleroma/xml_builder.ex diff --git a/mix.exs b/mix.exs index c88bf392d..f91f57644 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.1.2"), + version: version("2.2.0"), elixir: "~> 1.9", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), @@ -114,15 +114,15 @@ defp oauth_deps do # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "~> 1.4.17"}, + {:phoenix, "~> 1.5.5"}, {:tzdata, "~> 1.0.3"}, {:plug_cowboy, "~> 2.3"}, - {:phoenix_pubsub, "~> 1.1"}, + {:phoenix_pubsub, "~> 2.0"}, {:phoenix_ecto, "~> 4.0"}, {:ecto_enum, "~> 1.4"}, {:ecto_sql, "~> 3.4.4"}, {:postgrex, ">= 0.15.5"}, - {:oban, "~> 2.0.0"}, + {:oban, "~> 2.1.0"}, {:gettext, "~> 0.18"}, {:pbkdf2_elixir, "~> 1.2"}, {:bcrypt_elixir, "~> 2.2"}, @@ -134,7 +134,9 @@ defp deps do {:cachex, "~> 3.2"}, {:poison, "~> 3.0", override: true}, {:tesla, - github: "teamon/tesla", ref: "af3707078b10793f6a534938e56b963aff82fe3c", override: true}, + git: "https://github.com/teamon/tesla/", + ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", + override: true}, {:castore, "~> 0.1"}, {:cowlib, "~> 2.9", override: true}, {:gun, @@ -163,9 +165,16 @@ defp deps do {:telemetry, "~> 0.3"}, {:poolboy, "~> 1.5"}, {:prometheus, "~> 4.6"}, - {:prometheus_ex, "~> 3.0"}, + {:prometheus_ex, + git: "https://git.pleroma.social/pleroma/elixir-libraries/prometheus.ex.git", + ref: "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5", + override: true}, {:prometheus_plugs, "~> 1.1"}, {:prometheus_phoenix, "~> 1.3"}, + # Note: once `prometheus_phx` is integrated into `prometheus_phoenix`, remove the former: + {:prometheus_phx, + git: "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", + branch: "no-logging"}, {:prometheus_ecto, "~> 1.4"}, {:recon, "~> 2.5"}, {:quack, "~> 0.1.1"}, @@ -178,7 +187,7 @@ defp deps do {:flake_id, "~> 0.1.0"}, {:concurrent_limiter, git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", - ref: "55e92f84b4ed531bd487952a71040a9c69dc2807"}, + ref: "d81be41024569330f296fc472e24198d7499ba78"}, {:remote_ip, git: "https://git.pleroma.social/pleroma/remote_ip.git", ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, @@ -186,6 +195,8 @@ defp deps do git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, {:restarter, path: "./restarter"}, + {:majic, + git: "https://git.pleroma.social/pleroma/elixir-libraries/majic", branch: "develop"}, {:open_api_spex, git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"}, diff --git a/mix.lock b/mix.lock index b97dd6342..07238f550 100644 --- a/mix.lock +++ b/mix.lock @@ -14,24 +14,26 @@ "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, - "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "55e92f84b4ed531bd487952a71040a9c69dc2807", [ref: "55e92f84b4ed531bd487952a71040a9c69dc2807"]}, + "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cors_plug": {:hex, :cors_plug, "2.0.2", "2b46083af45e4bc79632bd951550509395935d3e7973275b2b743bd63cc942ce", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0d0e13f71c51fd4ef8b2c7e051388e4dfb267522a83a22392c856de7e46465f"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.0", "69fdb5cf92df6373e15675eb4018cf629f5d8e35e74841bb637d6596cb797bbc", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "42868c229d9a2900a1501c5d0355bfd46e24c862c322b0b4f5a6f14fe0216753"}, "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, - "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, + "credo": {:hex, :credo, "1.4.1", "16392f1edd2cdb1de9fe4004f5ab0ae612c92e230433968eab00aafd976282fc", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "155f8a2989ad77504de5d8291fa0d41320fdcaa6a1030472e9967f285f8c7692"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "crypt": {:git, "https://github.com/msantos/crypt.git", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, - "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"}, - "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"}, + "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"}, - "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"}, + "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, + "elixir_make": {:hex, :elixir_make, "0.6.1", "8faa29a5597faba999aeeb72bbb9c91694ef8068f0131192fb199f98d32994ef", [:mix], [], "hexpm", "35d33270680f8d839a4003c3e9f43afb595310a592405a00afc12de4c7f55a18"}, "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, @@ -58,12 +60,13 @@ "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, - "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, "linkify": {:hex, :linkify, "0.2.0", "2518bbbea21d2caa9d372424e1ad845b640c6630e2d016f1bd1f518f9ebcca28", [:mix], [], "hexpm", "b8ca8a68b79e30b7938d6c996085f3db14939f29538a59ca5101988bb7f917f6"}, + "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic", "4c692e544b28d1f5e543fb8a44be090f8cd96f80", [branch: "develop"]}, "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, @@ -78,27 +81,29 @@ "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, - "oban": {:hex, :oban, "2.0.0", "e6ce70d94dd46815ec0882a1ffb7356df9a9d5b8a40a64ce5c2536617a447379", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cf574813bd048b98a698aa587c21367d2e06842d4e1b1993dcd6a696e9e633bd"}, + "oban": {:hex, :oban, "2.1.0", "034144686f7e76a102b5d67731f098d98a9e4a52b07c25ad580a01f83a7f1cf5", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c6f067fa3b308ed9e0e6beb2b34277c9c4e48bf95338edabd8f4a757a26e04c2"}, "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]}, + "p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, - "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, + "phoenix": {:hex, :phoenix, "1.5.6", "8298cdb4e0f943242ba8410780a6a69cbbe972fef199b341a36898dd751bdd66", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0dc4d39af1306b6aa5122729b0a95ca779e42c708c6fe7abbb3d336d5379e956"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"}, "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, - "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.0", "2acfa0db038a7649e0a4614eee970e6ed9a39d191ccd79a03583b51d0da98165", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "b8bbae4b59a676de6b8bd8675eda37bc8b4424812ae429d6fdcb2b039e00003b"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, + "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.2", "43d3518349a22b8b1910ea28b4dd5119926d5017b3187db3fbd1a1e05769a851", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "3e2ac4e883db7af0702d75ba00c19901760e8342b91f8f66e13941de552e777f"}, "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"}, - "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.4.0", "e936ef151751f386804c51f87f7300f5aaae6893cdad726559c3930c6c032948", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e25ddcfc06b1b76e55af79d078b03cbc86bbcb99ce4e5e0a5e4a8114ee039be6"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"}, + "postgrex": {:hex, :postgrex, "0.15.6", "a464c72010a56e3214fe2b99c1a76faab4c2bb0255cabdef30dea763a3569aa2", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "f99268325ac8f66ffd6c4964faab9e70fbf721234ab2ad238c00f9530b8cdd55"}, "pot": {:hex, :pot, "0.11.0", "61bad869a94534739dd4614a25a619bc5c47b9970e9a0ea5bef4628036fc7a16", [:rebar3], [], "hexpm", "57ee6ee6bdeb639661ffafb9acefe3c8f966e45394de6a766813bb9e1be4e54b"}, "prometheus": {:hex, :prometheus, "4.6.0", "20510f381db1ccab818b4cf2fac5fa6ab5cc91bc364a154399901c001465f46f", [:mix, :rebar3], [], "hexpm", "4905fd2992f8038eccd7aa0cd22f40637ed618c0bed1f75c05aacec15b7545de"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, - "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"}, + "prometheus_ex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus.ex.git", "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5", [ref: "a4e9beb3c1c479d14b352fd9d6dd7b1f6d7deee5"]}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"}, + "prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "d736bfa7444112eb840027bb887832a0e403a4a3437f48028c3b29a2dbbd2543"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, @@ -107,13 +112,13 @@ "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, - "swoosh": {:hex, :swoosh, "1.0.0", "c547cfc83f30e12d5d1fdcb623d7de2c2e29a5becfc68bf8f42ba4d23d2c2756", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "b3b08e463f876cb6167f7168e9ad99a069a724e124bcee61847e0e1ed13f4a0d"}, + "swoosh": {:hex, :swoosh, "1.0.6", "6765e334c67dacabe721f0d701c7e5a6f06e4595c90df6f91e73ebd54d555833", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "7c50ef78e4acfd1cbd4907dc1fa87b5540675a6be9dc979d04890f49d7ec1830"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, - "tesla": {:git, "https://github.com/teamon/tesla.git", "af3707078b10793f6a534938e56b963aff82fe3c", [ref: "af3707078b10793f6a534938e56b963aff82fe3c"]}, + "tesla": {:git, "https://github.com/teamon/tesla/", "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30", [ref: "9f7261ca49f9f901ceb73b60219ad6f8a9f6aa30"]}, "timex": {:hex, :timex, "3.6.2", "845cdeb6119e2fef10751c0b247b6c59d86d78554c83f78db612e3290f819bc2", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "26030b46199d02a590be61c2394b37ea25a3664c02fafbeca0b24c972025d47a"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, - "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"}, + "tzdata": {:hex, :tzdata, "1.0.4", "a3baa4709ea8dba552dca165af6ae97c624a2d6ac14bd265165eaa8e8af94af6", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "b02637db3df1fd66dd2d3c4f194a81633d0e4b44308d36c1b2fdfd1e4e6f169b"}, "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, diff --git a/priv/gettext/es/LC_MESSAGES/errors.po b/priv/gettext/es/LC_MESSAGES/errors.po new file mode 100644 index 000000000..0a6fceaad --- /dev/null +++ b/priv/gettext/es/LC_MESSAGES/errors.po @@ -0,0 +1,586 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-09 09:49+0000\n" +"PO-Revision-Date: 2020-09-11 21:26+0000\n" +"Last-Translator: tarteka \n" +"Language-Team: Spanish \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.0.4\n" + +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "no puede estar en blanco" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "ya está en uso" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "es inválido" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "el formato no es válido" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "tiene una entrada inválida" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "está reservado" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "la confirmación no coincide" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "todavía está asociado con esta entrada" + +msgid "are still associated with this entry" +msgstr "todavía están asociados con esta entrada" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "debe tener %{count} carácter" +msgstr[1] "debe tener %{count} caracteres" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "" +msgstr[1] "" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "" +msgstr[1] "" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "" + +msgid "must be greater than %{number}" +msgstr "debe ser mayor que %{number}" + +msgid "must be less than or equal to %{number}" +msgstr "debe ser menor o igual que %{number}" + +msgid "must be greater than or equal to %{number}" +msgstr "deber ser mayor o igual que %{number}" + +msgid "must be equal to %{number}" +msgstr "deber ser igual a %{number}" + +#: lib/pleroma/web/common_api/common_api.ex:505 +#, elixir-format +msgid "Account not found" +msgstr "Cuenta no encontrada" + +#: lib/pleroma/web/common_api/common_api.ex:339 +#, elixir-format +msgid "Already voted" +msgstr "Ya has votado" + +#: lib/pleroma/web/oauth/oauth_controller.ex:359 +#, elixir-format +msgid "Bad request" +msgstr "Solicitud incorrecta" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426 +#, elixir-format +msgid "Can't delete object" +msgstr "No se puede eliminar el objeto" + +#: lib/pleroma/web/controller_helper.ex:105 +#: lib/pleroma/web/controller_helper.ex:111 +#, elixir-format +msgid "Can't display this activity" +msgstr "No se puede mostrar esta actividad" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285 +#, elixir-format +msgid "Can't find user" +msgstr "No se puede encontrar al usuario" + +#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61 +#, elixir-format +msgid "Can't get favorites" +msgstr "No se puede obtener los favoritos" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438 +#, elixir-format +msgid "Can't like object" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:563 +#, elixir-format +msgid "Cannot post an empty status without attachments" +msgstr "No se puede publicar un estado vacío y sin archivos adjuntos" + +#: lib/pleroma/web/common_api/utils.ex:511 +#, elixir-format +msgid "Comment must be up to %{max_size} characters" +msgstr "" + +#: lib/pleroma/config/config_db.ex:191 +#, elixir-format +msgid "Config with params %{params} not found" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:181 +#: lib/pleroma/web/common_api/common_api.ex:185 +#, elixir-format +msgid "Could not delete" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:231 +#, elixir-format +msgid "Could not favorite" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:453 +#, elixir-format +msgid "Could not pin" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:278 +#, elixir-format +msgid "Could not unfavorite" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:463 +#, elixir-format +msgid "Could not unpin" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:216 +#, elixir-format +msgid "Could not unrepeat" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:512 +#: lib/pleroma/web/common_api/common_api.ex:521 +#, elixir-format +msgid "Could not update state" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207 +#, elixir-format +msgid "Error." +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:106 +#, elixir-format +msgid "Invalid CAPTCHA" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116 +#: lib/pleroma/web/oauth/oauth_controller.ex:568 +#, elixir-format +msgid "Invalid credentials" +msgstr "" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 +#, elixir-format +msgid "Invalid credentials." +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:355 +#, elixir-format +msgid "Invalid indices" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid parameters" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:414 +#, elixir-format +msgid "Invalid password." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 +#, elixir-format +msgid "Invalid request" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:109 +#, elixir-format +msgid "Kocaptcha service unavailable" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 +#, elixir-format +msgid "Missing parameters" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:547 +#, elixir-format +msgid "No such conversation" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 +#, elixir-format +msgid "No such permission_group" +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:84 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 +#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 +#, elixir-format +msgid "Not found" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:331 +#, elixir-format +msgid "Poll's author can't vote" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306 +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 +#, elixir-format +msgid "Record not found" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 +#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 +#, elixir-format +msgid "Something went wrong" +msgstr "" + +#: lib/pleroma/web/common_api/activity_draft.ex:107 +#, elixir-format +msgid "The message visibility must be direct" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:573 +#, elixir-format +msgid "The status is over the character limit" +msgstr "" + +#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 +#, elixir-format +msgid "This resource requires authentication." +msgstr "" + +#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 +#, elixir-format +msgid "Throttled" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:356 +#, elixir-format +msgid "Too many choices" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 +#, elixir-format +msgid "Unhandled activity type" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 +#, elixir-format +msgid "You can't revoke your own admin status." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:221 +#: lib/pleroma/web/oauth/oauth_controller.ex:308 +#, elixir-format +msgid "Your account is currently disabled" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:183 +#: lib/pleroma/web/oauth/oauth_controller.ex:331 +#, elixir-format +msgid "Your login is missing a confirmed e-mail address" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 +#, elixir-format +msgid "can't read inbox of %{nickname} as %{as_nickname}" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 +#, elixir-format +msgid "can't update outbox of %{nickname} as %{as_nickname}" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:471 +#, elixir-format +msgid "conversation is already muted" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 +#, elixir-format +msgid "error" +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 +#, elixir-format +msgid "mascots can only be images" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 +#, elixir-format +msgid "not found" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:394 +#, elixir-format +msgid "Bad OAuth request." +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:115 +#, elixir-format +msgid "CAPTCHA already used" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:112 +#, elixir-format +msgid "CAPTCHA expired" +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:57 +#, elixir-format +msgid "Failed" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:410 +#, elixir-format +msgid "Failed to authenticate: %{message}." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:441 +#, elixir-format +msgid "Failed to set up user account." +msgstr "" + +#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 +#, elixir-format +msgid "Insufficient permissions: %{permissions}." +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:104 +#, elixir-format +msgid "Internal Error" +msgstr "" + +#: lib/pleroma/web/oauth/fallback_controller.ex:22 +#: lib/pleroma/web/oauth/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid Username/Password" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:118 +#, elixir-format +msgid "Invalid answer data" +msgstr "" + +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 +#, elixir-format +msgid "Nodeinfo schema version not handled" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:172 +#, elixir-format +msgid "This action is outside the authorized scopes" +msgstr "" + +#: lib/pleroma/web/oauth/fallback_controller.ex:14 +#, elixir-format +msgid "Unknown error, please check the details and try again." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:119 +#: lib/pleroma/web/oauth/oauth_controller.ex:158 +#, elixir-format +msgid "Unlisted redirect_uri." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:390 +#, elixir-format +msgid "Unsupported OAuth provider: %{provider}." +msgstr "" + +#: lib/pleroma/uploaders/uploader.ex:72 +#, elixir-format +msgid "Uploader callback timeout" +msgstr "" + +#: lib/pleroma/web/uploader_controller.ex:23 +#, elixir-format +msgid "bad request" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:103 +#, elixir-format +msgid "CAPTCHA Error" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:290 +#, elixir-format +msgid "Could not add reaction emoji" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:301 +#, elixir-format +msgid "Could not remove reaction emoji" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:129 +#, elixir-format +msgid "Invalid CAPTCHA (Missing parameter: %{name})" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 +#, elixir-format +msgid "List not found" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 +#, elixir-format +msgid "Missing parameter: %{name}" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:210 +#: lib/pleroma/web/oauth/oauth_controller.ex:321 +#, elixir-format +msgid "Password reset is required" +msgstr "" + +#: lib/pleroma/tests/auth_test_controller.ex:9 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6 +#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6 +#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6 +#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2 +#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 +#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 +#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 +#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 +#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6 +#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6 +#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6 +#, elixir-format +msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." +msgstr "" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 +#, elixir-format +msgid "Two-factor authentication enabled, you must use a access token." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 +#, elixir-format +msgid "Unexpected error occurred while adding file to pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 +#, elixir-format +msgid "Unexpected error occurred while creating pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 +#, elixir-format +msgid "Unexpected error occurred while removing file from pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 +#, elixir-format +msgid "Unexpected error occurred while updating file in pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 +#, elixir-format +msgid "Unexpected error occurred while updating pack metadata." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 +#, elixir-format +msgid "Web push subscription is disabled on this Pleroma instance" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 +#, elixir-format +msgid "You can't revoke your own admin/moderator status." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 +#, elixir-format +msgid "authorization required for timeline view" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 +#, elixir-format +msgid "Access denied" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 +#, elixir-format +msgid "This API requires an authenticated user" +msgstr "" + +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 +#, elixir-format +msgid "User is not an admin." +msgstr "" diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po new file mode 100644 index 000000000..4f029d558 --- /dev/null +++ b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po @@ -0,0 +1,580 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-20 13:18+0000\n" +"PO-Revision-Date: 2020-09-20 14:48+0000\n" +"Last-Translator: Kana \n" +"Language-Team: Chinese (Simplified) \n" +"Language: zh_Hans\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.0.4\n" + +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "不能为空" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "已被占用" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "不合法" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "的格式不合法" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "中存在不合法的项目" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "是被保留的" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "仍与该项目相关联" + +msgid "are still associated with this entry" +msgstr "仍与该项目相关联" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "应为 %{count} 个字符" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "应有 %{item} 项" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "应至少有 %{count} 个字符" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "应至少有 %{count} 项" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "应至多有 %{count} 个字符" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "应至多有 %{count} 项" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "必须小于 %{number}" + +msgid "must be greater than %{number}" +msgstr "必须大于 %{number}" + +msgid "must be less than or equal to %{number}" +msgstr "必须小于等于 %{number}" + +msgid "must be greater than or equal to %{number}" +msgstr "必须大于等于 %{number}" + +msgid "must be equal to %{number}" +msgstr "必须等于 %{number}" + +#: lib/pleroma/web/common_api/common_api.ex:505 +#, elixir-format +msgid "Account not found" +msgstr "未找到账号" + +#: lib/pleroma/web/common_api/common_api.ex:339 +#, elixir-format +msgid "Already voted" +msgstr "已经进行了投票" + +#: lib/pleroma/web/oauth/oauth_controller.ex:359 +#, elixir-format +msgid "Bad request" +msgstr "不正确的请求" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426 +#, elixir-format +msgid "Can't delete object" +msgstr "不能删除对象" + +#: lib/pleroma/web/controller_helper.ex:105 +#: lib/pleroma/web/controller_helper.ex:111 +#, elixir-format +msgid "Can't display this activity" +msgstr "不能显示该活动" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285 +#, elixir-format +msgid "Can't find user" +msgstr "找不到用户" + +#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61 +#, elixir-format +msgid "Can't get favorites" +msgstr "不能获取收藏" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438 +#, elixir-format +msgid "Can't like object" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:563 +#, elixir-format +msgid "Cannot post an empty status without attachments" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:511 +#, elixir-format +msgid "Comment must be up to %{max_size} characters" +msgstr "" + +#: lib/pleroma/config/config_db.ex:191 +#, elixir-format +msgid "Config with params %{params} not found" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:181 +#: lib/pleroma/web/common_api/common_api.ex:185 +#, elixir-format +msgid "Could not delete" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:231 +#, elixir-format +msgid "Could not favorite" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:453 +#, elixir-format +msgid "Could not pin" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:278 +#, elixir-format +msgid "Could not unfavorite" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:463 +#, elixir-format +msgid "Could not unpin" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:216 +#, elixir-format +msgid "Could not unrepeat" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:512 +#: lib/pleroma/web/common_api/common_api.ex:521 +#, elixir-format +msgid "Could not update state" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207 +#, elixir-format +msgid "Error." +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:106 +#, elixir-format +msgid "Invalid CAPTCHA" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116 +#: lib/pleroma/web/oauth/oauth_controller.ex:568 +#, elixir-format +msgid "Invalid credentials" +msgstr "" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 +#, elixir-format +msgid "Invalid credentials." +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:355 +#, elixir-format +msgid "Invalid indices" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid parameters" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:414 +#, elixir-format +msgid "Invalid password." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 +#, elixir-format +msgid "Invalid request" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:109 +#, elixir-format +msgid "Kocaptcha service unavailable" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 +#, elixir-format +msgid "Missing parameters" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:547 +#, elixir-format +msgid "No such conversation" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 +#, elixir-format +msgid "No such permission_group" +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:84 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 +#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 +#, elixir-format +msgid "Not found" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:331 +#, elixir-format +msgid "Poll's author can't vote" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306 +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 +#, elixir-format +msgid "Record not found" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 +#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 +#, elixir-format +msgid "Something went wrong" +msgstr "" + +#: lib/pleroma/web/common_api/activity_draft.ex:107 +#, elixir-format +msgid "The message visibility must be direct" +msgstr "" + +#: lib/pleroma/web/common_api/utils.ex:573 +#, elixir-format +msgid "The status is over the character limit" +msgstr "" + +#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 +#, elixir-format +msgid "This resource requires authentication." +msgstr "" + +#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 +#, elixir-format +msgid "Throttled" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:356 +#, elixir-format +msgid "Too many choices" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 +#, elixir-format +msgid "Unhandled activity type" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 +#, elixir-format +msgid "You can't revoke your own admin status." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:221 +#: lib/pleroma/web/oauth/oauth_controller.ex:308 +#, elixir-format +msgid "Your account is currently disabled" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:183 +#: lib/pleroma/web/oauth/oauth_controller.ex:331 +#, elixir-format +msgid "Your login is missing a confirmed e-mail address" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 +#, elixir-format +msgid "can't read inbox of %{nickname} as %{as_nickname}" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 +#, elixir-format +msgid "can't update outbox of %{nickname} as %{as_nickname}" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:471 +#, elixir-format +msgid "conversation is already muted" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 +#, elixir-format +msgid "error" +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 +#, elixir-format +msgid "mascots can only be images" +msgstr "" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 +#, elixir-format +msgid "not found" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:394 +#, elixir-format +msgid "Bad OAuth request." +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:115 +#, elixir-format +msgid "CAPTCHA already used" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:112 +#, elixir-format +msgid "CAPTCHA expired" +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:57 +#, elixir-format +msgid "Failed" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:410 +#, elixir-format +msgid "Failed to authenticate: %{message}." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:441 +#, elixir-format +msgid "Failed to set up user account." +msgstr "" + +#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 +#, elixir-format +msgid "Insufficient permissions: %{permissions}." +msgstr "" + +#: lib/pleroma/plugs/uploaded_media.ex:104 +#, elixir-format +msgid "Internal Error" +msgstr "" + +#: lib/pleroma/web/oauth/fallback_controller.ex:22 +#: lib/pleroma/web/oauth/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid Username/Password" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:118 +#, elixir-format +msgid "Invalid answer data" +msgstr "" + +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 +#, elixir-format +msgid "Nodeinfo schema version not handled" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:172 +#, elixir-format +msgid "This action is outside the authorized scopes" +msgstr "" + +#: lib/pleroma/web/oauth/fallback_controller.ex:14 +#, elixir-format +msgid "Unknown error, please check the details and try again." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:119 +#: lib/pleroma/web/oauth/oauth_controller.ex:158 +#, elixir-format +msgid "Unlisted redirect_uri." +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:390 +#, elixir-format +msgid "Unsupported OAuth provider: %{provider}." +msgstr "" + +#: lib/pleroma/uploaders/uploader.ex:72 +#, elixir-format +msgid "Uploader callback timeout" +msgstr "" + +#: lib/pleroma/web/uploader_controller.ex:23 +#, elixir-format +msgid "bad request" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:103 +#, elixir-format +msgid "CAPTCHA Error" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:290 +#, elixir-format +msgid "Could not add reaction emoji" +msgstr "" + +#: lib/pleroma/web/common_api/common_api.ex:301 +#, elixir-format +msgid "Could not remove reaction emoji" +msgstr "" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:129 +#, elixir-format +msgid "Invalid CAPTCHA (Missing parameter: %{name})" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 +#, elixir-format +msgid "List not found" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 +#, elixir-format +msgid "Missing parameter: %{name}" +msgstr "" + +#: lib/pleroma/web/oauth/oauth_controller.ex:210 +#: lib/pleroma/web/oauth/oauth_controller.ex:321 +#, elixir-format +msgid "Password reset is required" +msgstr "" + +#: lib/pleroma/tests/auth_test_controller.ex:9 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6 +#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6 +#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6 +#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2 +#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 +#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 +#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 +#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 +#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6 +#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6 +#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6 +#, elixir-format +msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." +msgstr "" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 +#, elixir-format +msgid "Two-factor authentication enabled, you must use a access token." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 +#, elixir-format +msgid "Unexpected error occurred while adding file to pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 +#, elixir-format +msgid "Unexpected error occurred while creating pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 +#, elixir-format +msgid "Unexpected error occurred while removing file from pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 +#, elixir-format +msgid "Unexpected error occurred while updating file in pack." +msgstr "" + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 +#, elixir-format +msgid "Unexpected error occurred while updating pack metadata." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 +#, elixir-format +msgid "Web push subscription is disabled on this Pleroma instance" +msgstr "" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 +#, elixir-format +msgid "You can't revoke your own admin/moderator status." +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 +#, elixir-format +msgid "authorization required for timeline view" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 +#, elixir-format +msgid "Access denied" +msgstr "" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 +#, elixir-format +msgid "This API requires an authenticated user" +msgstr "" + +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 +#, elixir-format +msgid "User is not an admin." +msgstr "" diff --git a/priv/repo/migrations/20200824115541_rename_activity_expiration_setting.exs b/priv/repo/migrations/20200824115541_rename_activity_expiration_setting.exs new file mode 100644 index 000000000..241882ef6 --- /dev/null +++ b/priv/repo/migrations/20200824115541_rename_activity_expiration_setting.exs @@ -0,0 +1,13 @@ +defmodule Pleroma.Repo.Migrations.RenameActivityExpirationSetting do + use Ecto.Migration + + def change do + config = Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.ActivityExpiration}) + + if config do + config + |> Ecto.Changeset.change(key: Pleroma.Workers.PurgeExpiredActivity) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs new file mode 100644 index 000000000..a703af83f --- /dev/null +++ b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs @@ -0,0 +1,28 @@ +defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do + use Ecto.Migration + + import Ecto.Query, only: [from: 2] + + def change do + Pleroma.Config.Oban.warn() + + Supervisor.start_link([{Oban, Pleroma.Config.get(Oban)}], + strategy: :one_for_one, + name: Pleroma.Supervisor + ) + + from(e in "activity_expirations", + select: %{id: e.id, activity_id: e.activity_id, scheduled_at: e.scheduled_at} + ) + |> Pleroma.Repo.stream() + |> Stream.each(fn expiration -> + with {:ok, expires_at} <- DateTime.from_naive(expiration.scheduled_at, "Etc/UTC") do + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: FlakeId.to_string(expiration.activity_id), + expires_at: expires_at + }) + end + end) + |> Stream.run() + end +end diff --git a/priv/repo/migrations/20200825093037_drop_activity_expirations_table.exs b/priv/repo/migrations/20200825093037_drop_activity_expirations_table.exs new file mode 100644 index 000000000..11c461427 --- /dev/null +++ b/priv/repo/migrations/20200825093037_drop_activity_expirations_table.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.DropActivityExpirationsTable do + use Ecto.Migration + + def change do + drop(table("activity_expirations")) + end +end diff --git a/priv/repo/migrations/20200831142509_chat_constraints.exs b/priv/repo/migrations/20200831142509_chat_constraints.exs new file mode 100644 index 000000000..868a40a45 --- /dev/null +++ b/priv/repo/migrations/20200831142509_chat_constraints.exs @@ -0,0 +1,22 @@ +defmodule Pleroma.Repo.Migrations.ChatConstraints do + use Ecto.Migration + + def change do + remove_orphans = """ + delete from chats where not exists(select id from users where ap_id = chats.recipient); + """ + + execute(remove_orphans) + + drop(constraint(:chats, "chats_user_id_fkey")) + + alter table(:chats) do + modify(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) + + modify( + :recipient, + references(:users, column: :ap_id, type: :string, on_delete: :delete_all) + ) + end + end +end diff --git a/priv/repo/migrations/20200901061256_ensure_bio_is_string.exs b/priv/repo/migrations/20200901061256_ensure_bio_is_string.exs new file mode 100644 index 000000000..0e3bb3c81 --- /dev/null +++ b/priv/repo/migrations/20200901061256_ensure_bio_is_string.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.EnsureBioIsString do + use Ecto.Migration + + def change do + execute("update users set bio = '' where bio is null", "") + end +end diff --git a/priv/repo/migrations/20200901061637_bio_set_not_null.exs b/priv/repo/migrations/20200901061637_bio_set_not_null.exs new file mode 100644 index 000000000..e3a67d4e7 --- /dev/null +++ b/priv/repo/migrations/20200901061637_bio_set_not_null.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Repo.Migrations.BioSetNotNull do + use Ecto.Migration + + def change do + execute( + "alter table users alter column bio set not null", + "alter table users alter column bio drop not null" + ) + end +end diff --git a/priv/repo/migrations/20200905082737_rename_await_up_timeout_in_connections_pool.exs b/priv/repo/migrations/20200905082737_rename_await_up_timeout_in_connections_pool.exs new file mode 100644 index 000000000..22c40663c --- /dev/null +++ b/priv/repo/migrations/20200905082737_rename_await_up_timeout_in_connections_pool.exs @@ -0,0 +1,13 @@ +defmodule Pleroma.Repo.Migrations.RenameAwaitUpTimeoutInConnectionsPool do + use Ecto.Migration + + def change do + with %Pleroma.ConfigDB{} = config <- + Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: :connections_pool}), + {timeout, value} when is_integer(timeout) <- Keyword.pop(config.value, :await_up_timeout) do + config + |> Ecto.Changeset.change(value: Keyword.put(value, :connect_timeout, timeout)) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200905091427_rename_timeout_in_pools.exs b/priv/repo/migrations/20200905091427_rename_timeout_in_pools.exs new file mode 100644 index 000000000..bb2f50ecc --- /dev/null +++ b/priv/repo/migrations/20200905091427_rename_timeout_in_pools.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.RenameTimeoutInPools do + use Ecto.Migration + + def change do + with %Pleroma.ConfigDB{} = config <- + Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: :pools}) do + updated_value = + Enum.map(config.value, fn {pool, pool_value} -> + with {timeout, value} when is_integer(timeout) <- Keyword.pop(pool_value, :timeout) do + {pool, Keyword.put(value, :recv_timeout, timeout)} + end + end) + + config + |> Ecto.Changeset.change(value: updated_value) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200906072147_remove_cron_stats_worker_from_oban_config.exs b/priv/repo/migrations/20200906072147_remove_cron_stats_worker_from_oban_config.exs new file mode 100644 index 000000000..022f21dc7 --- /dev/null +++ b/priv/repo/migrations/20200906072147_remove_cron_stats_worker_from_oban_config.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.RemoveCronStatsWorkerFromObanConfig do + use Ecto.Migration + + def change do + with %Pleroma.ConfigDB{} = config <- + Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: Oban}), + crontab when is_list(crontab) <- config.value[:crontab], + index when is_integer(index) <- + Enum.find_index(crontab, fn {_, worker} -> + worker == Pleroma.Workers.Cron.StatsWorker + end) do + updated_value = Keyword.put(config.value, :crontab, List.delete_at(crontab, index)) + + config + |> Ecto.Changeset.change(value: updated_value) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200907084956_remove_cron_clear_oauth_token_worker_from_oban_config.exs b/priv/repo/migrations/20200907084956_remove_cron_clear_oauth_token_worker_from_oban_config.exs new file mode 100644 index 000000000..b5a0a0ff6 --- /dev/null +++ b/priv/repo/migrations/20200907084956_remove_cron_clear_oauth_token_worker_from_oban_config.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.RemoveCronClearOauthTokenWorkerFromObanConfig do + use Ecto.Migration + + def change do + with %Pleroma.ConfigDB{} = config <- + Pleroma.ConfigDB.get_by_params(%{group: :pleroma, key: Oban}), + crontab when is_list(crontab) <- config.value[:crontab], + index when is_integer(index) <- + Enum.find_index(crontab, fn {_, worker} -> + worker == Pleroma.Workers.Cron.ClearOauthTokenWorker + end) do + updated_value = Keyword.put(config.value, :crontab, List.delete_at(crontab, index)) + + config + |> Ecto.Changeset.change(value: updated_value) + |> Pleroma.Repo.update() + end + end +end diff --git a/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs new file mode 100644 index 000000000..9e49ddacb --- /dev/null +++ b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs @@ -0,0 +1,38 @@ +defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do + use Ecto.Migration + + import Ecto.Query, only: [from: 2] + + def change do + Pleroma.Config.Oban.warn() + + Supervisor.start_link([{Oban, Pleroma.Config.get(Oban)}], + strategy: :one_for_one, + name: Pleroma.Supervisor + ) + + if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do + from(t in Pleroma.Web.OAuth.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) + |> Pleroma.Repo.stream() + |> Stream.each(fn token -> + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: Pleroma.Web.OAuth.Token + }) + end) + |> Stream.run() + end + + from(t in Pleroma.MFA.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) + |> Pleroma.Repo.stream() + |> Stream.each(fn token -> + Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + token_id: token.id, + valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), + mod: Pleroma.MFA.Token + }) + end) + |> Stream.run() + end +end diff --git a/priv/repo/migrations/20200910113106_remove_managed_config_from_db.exs b/priv/repo/migrations/20200910113106_remove_managed_config_from_db.exs new file mode 100644 index 000000000..e27a9ae48 --- /dev/null +++ b/priv/repo/migrations/20200910113106_remove_managed_config_from_db.exs @@ -0,0 +1,27 @@ +defmodule Pleroma.Repo.Migrations.RemoveManagedConfigFromDb do + use Ecto.Migration + import Ecto.Query + alias Pleroma.ConfigDB + alias Pleroma.Repo + + def up do + config_entry = + from(c in ConfigDB, + select: [:id, :value], + where: c.group == ^:pleroma and c.key == ^:instance + ) + |> Repo.one() + + if config_entry do + {_, value} = Keyword.pop(config_entry.value, :managed_config) + + config_entry + |> Ecto.Changeset.change(value: value) + |> Repo.update() + end + end + + def down do + :ok + end +end diff --git a/priv/repo/migrations/20200911055909_remove_cron_jobs.exs b/priv/repo/migrations/20200911055909_remove_cron_jobs.exs new file mode 100644 index 000000000..33897d128 --- /dev/null +++ b/priv/repo/migrations/20200911055909_remove_cron_jobs.exs @@ -0,0 +1,20 @@ +defmodule Pleroma.Repo.Migrations.RemoveCronJobs do + use Ecto.Migration + + import Ecto.Query, only: [from: 2] + + def up do + from(j in "oban_jobs", + where: + j.worker in ^[ + "Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker", + "Pleroma.Workers.Cron.StatsWorker", + "Pleroma.Workers.Cron.ClearOauthTokenWorker" + ], + select: [:id] + ) + |> Pleroma.Repo.delete_all() + end + + def down, do: :ok +end diff --git a/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs b/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs new file mode 100644 index 000000000..9333fc5a1 --- /dev/null +++ b/priv/repo/migrations/20200914105638_delete_notification_without_activity.exs @@ -0,0 +1,30 @@ +defmodule Pleroma.Repo.Migrations.DeleteNotificationWithoutActivity do + use Ecto.Migration + + import Ecto.Query + alias Pleroma.Repo + + def up do + from( + q in Pleroma.Notification, + left_join: c in assoc(q, :activity), + select: %{id: type(q.id, :integer)}, + where: is_nil(c.id) + ) + |> Repo.chunk_stream(1_000, :batches) + |> Stream.each(fn records -> + notification_ids = Enum.map(records, fn %{id: id} -> id end) + + Repo.delete_all( + from(n in "notifications", + where: n.id in ^notification_ids + ) + ) + end) + |> Stream.run() + end + + def down do + :ok + end +end diff --git a/priv/repo/migrations/20200914105800_add_notification_constraints.exs b/priv/repo/migrations/20200914105800_add_notification_constraints.exs new file mode 100644 index 000000000..a65c35fd0 --- /dev/null +++ b/priv/repo/migrations/20200914105800_add_notification_constraints.exs @@ -0,0 +1,23 @@ +defmodule Pleroma.Repo.Migrations.AddNotificationConstraints do + use Ecto.Migration + + def up do + drop(constraint(:notifications, "notifications_activity_id_fkey")) + + alter table(:notifications) do + modify(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all), + null: false + ) + end + end + + def down do + drop(constraint(:notifications, "notifications_activity_id_fkey")) + + alter table(:notifications) do + modify(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all), + null: true + ) + end + end +end diff --git a/priv/repo/migrations/20200919182636_remoteip_plug_rename.exs b/priv/repo/migrations/20200919182636_remoteip_plug_rename.exs new file mode 100644 index 000000000..77c3b6db1 --- /dev/null +++ b/priv/repo/migrations/20200919182636_remoteip_plug_rename.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.RemoteipPlugRename do + use Ecto.Migration + + import Ecto.Query + + def up do + config = + from(c in Pleroma.ConfigDB, where: c.group == ^:pleroma and c.key == ^Pleroma.Plugs.RemoteIp) + |> Pleroma.Repo.one() + + if config do + config + |> Ecto.Changeset.change(key: Pleroma.Web.Plugs.RemoteIp) + |> Pleroma.Repo.update() + end + end + + def down, do: :ok +end diff --git a/priv/repo/migrations/20200925065249_make_user_ids_ci.exs b/priv/repo/migrations/20200925065249_make_user_ids_ci.exs new file mode 100644 index 000000000..8ea0f2cf1 --- /dev/null +++ b/priv/repo/migrations/20200925065249_make_user_ids_ci.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.MakeUserIdsCI do + use Ecto.Migration + + def change do + # Migration retired, see + # https://git.pleroma.social/pleroma/pleroma/-/issues/2188 + :noop + end +end diff --git a/priv/repo/migrations/20200928145912_revert_citext_change.exs b/priv/repo/migrations/20200928145912_revert_citext_change.exs new file mode 100644 index 000000000..685a98533 --- /dev/null +++ b/priv/repo/migrations/20200928145912_revert_citext_change.exs @@ -0,0 +1,11 @@ +defmodule Pleroma.Repo.Migrations.RevertCitextChange do + use Ecto.Migration + + def change do + alter table(:users) do + modify(:uri, :text) + end + + # create_if_not_exists(unique_index(:users, :uri)) + end +end diff --git a/priv/repo/migrations/20200930082320_user_ur_is_index_part_three.exs b/priv/repo/migrations/20200930082320_user_ur_is_index_part_three.exs new file mode 100644 index 000000000..816c6526e --- /dev/null +++ b/priv/repo/migrations/20200930082320_user_ur_is_index_part_three.exs @@ -0,0 +1,8 @@ +defmodule Pleroma.Repo.Migrations.UserURIsIndexPartThree do + use Ecto.Migration + + def change do + drop_if_exists(unique_index(:users, :uri)) + create_if_not_exists(index(:users, :uri)) + end +end diff --git a/priv/repo/migrations/20201013141127_refactor_locked_user_field.exs b/priv/repo/migrations/20201013141127_refactor_locked_user_field.exs new file mode 100644 index 000000000..6cd23dbac --- /dev/null +++ b/priv/repo/migrations/20201013141127_refactor_locked_user_field.exs @@ -0,0 +1,15 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.RefactorLockedUserField do + use Ecto.Migration + + def up do + execute("ALTER TABLE users RENAME COLUMN locked TO is_locked;") + end + + def down do + execute("ALTER TABLE users RENAME COLUMN is_locked TO locked;") + end +end diff --git a/priv/static/adminfe/app.07a1f8db.css b/priv/static/adminfe/app.6fb984d1.css similarity index 55% rename from priv/static/adminfe/app.07a1f8db.css rename to priv/static/adminfe/app.6fb984d1.css index 9d74d13dc..f1c191c2e 100644 --- a/priv/static/adminfe/app.07a1f8db.css +++ b/priv/static/adminfe/app.6fb984d1.css @@ -1 +1 @@ -.fade-enter-active,.fade-leave-active{-webkit-transition:opacity .28s;transition:opacity .28s}.fade-enter,.fade-leave-active{opacity:0}.fade-transform-enter-active,.fade-transform-leave-active{-webkit-transition:all .5s;transition:all .5s}.fade-transform-enter{opacity:0;-webkit-transform:translateX(-30px);transform:translateX(-30px)}.fade-transform-leave-to{opacity:0;-webkit-transform:translateX(30px);transform:translateX(30px)}.breadcrumb-enter-active,.breadcrumb-leave-active{-webkit-transition:all .5s;transition:all .5s}.breadcrumb-enter,.breadcrumb-leave-active{opacity:0;-webkit-transform:translateX(20px);transform:translateX(20px)}.breadcrumb-move{-webkit-transition:all .5s;transition:all .5s}.breadcrumb-leave-active{position:absolute}.el-breadcrumb__inner,.el-breadcrumb__inner a{font-weight:400!important}.el-upload input[type=file]{display:none!important}.el-upload__input{display:none}.cell .el-tag{margin-right:0}.small-padding .cell{padding-left:5px;padding-right:5px}.fixed-width .el-button--mini{padding:7px 10px;width:60px}.status-col .cell{padding:0 10px;text-align:center}.status-col .cell .el-tag{margin-right:0}.el-dialog{-webkit-transform:none;transform:none;left:0;position:relative;margin:0 auto}.article-textarea textarea{padding-right:40px;resize:none;border-radius:0;border:none;border-bottom:1px solid #bfcbd9}.upload-container .el-upload{width:100%}.upload-container .el-upload .el-upload-dragger{width:100%;height:200px}.el-dropdown-menu a{display:block}#app .main-container{min-height:100%;-webkit-transition:margin-left .28s;transition:margin-left .28s;margin-left:180px;position:relative}#app .sidebar-container{-webkit-transition:width .28s;transition:width .28s;width:180px!important;height:100%;position:fixed;font-size:0;top:0;bottom:0;left:0;z-index:5000;overflow:hidden}#app .sidebar-container .horizontal-collapse-transition{-webkit-transition:width 0s ease-in-out,padding-left 0s ease-in-out,padding-right 0s ease-in-out;transition:width 0s ease-in-out,padding-left 0s ease-in-out,padding-right 0s ease-in-out}#app .sidebar-container .scrollbar-wrapper{overflow-x:hidden!important}#app .sidebar-container .scrollbar-wrapper .el-scrollbar__view{height:100%}#app .sidebar-container .el-scrollbar__bar.is-vertical{right:0}#app .sidebar-container .is-horizontal{display:none}#app .sidebar-container a{display:inline-block;width:100%;overflow:hidden}#app .sidebar-container .svg-icon{margin-right:16px}#app .sidebar-container .el-menu{border:none;height:100%;width:100%!important}#app .sidebar-container .el-submenu__title:hover,#app .sidebar-container .submenu-title-noDropdown:hover{background-color:#263445!important}#app .sidebar-container .is-active>.el-submenu__title{color:#f4f4f5!important}#app .sidebar-container .el-submenu .el-menu-item,#app .sidebar-container .nest-menu .el-submenu>.el-submenu__title{min-width:180px!important;background-color:#1f2d3d!important}#app .sidebar-container .el-submenu .el-menu-item:hover,#app .sidebar-container .nest-menu .el-submenu>.el-submenu__title:hover{background-color:#001528!important}#app .hideSidebar .sidebar-container{width:36px!important}#app .hideSidebar .main-container{margin-left:36px}#app .hideSidebar .submenu-title-noDropdown{padding-left:10px!important;position:relative}#app .hideSidebar .submenu-title-noDropdown .el-tooltip{padding:0 10px!important}#app .hideSidebar .el-submenu{overflow:hidden}#app .hideSidebar .el-submenu>.el-submenu__title{padding-left:10px!important}#app .hideSidebar .el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}#app .hideSidebar .el-menu--collapse .el-submenu>.el-submenu__title>span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}#app .el-menu--collapse .el-menu .el-submenu{min-width:180px!important}#app .mobile .main-container{margin-left:0}#app .mobile .sidebar-container{-webkit-transition:-webkit-transform .28s;transition:-webkit-transform .28s;transition:transform .28s;transition:transform .28s,-webkit-transform .28s;width:180px!important}#app .mobile.hideSidebar .sidebar-container{pointer-events:none;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:translate3d(-180px,0,0);transform:translate3d(-180px,0,0)}#app .withoutAnimation .main-container,#app .withoutAnimation .sidebar-container{-webkit-transition:none;transition:none}.el-menu--vertical>.el-menu .svg-icon{margin-right:16px}.el-menu--vertical .el-menu-item:hover,.el-menu--vertical .nest-menu .el-submenu>.el-submenu__title:hover{background-color:#263445!important}.blue-btn{background:#324157}.blue-btn:hover{color:#324157}.blue-btn:hover:after,.blue-btn:hover:before{background:#324157}.light-blue-btn{background:#3a71a8}.light-blue-btn:hover{color:#3a71a8}.light-blue-btn:hover:after,.light-blue-btn:hover:before{background:#3a71a8}.red-btn{background:#c03639}.red-btn:hover{color:#c03639}.red-btn:hover:after,.red-btn:hover:before{background:#c03639}.pink-btn{background:#e65d6e}.pink-btn:hover{color:#e65d6e}.pink-btn:hover:after,.pink-btn:hover:before{background:#e65d6e}.green-btn{background:#30b08f}.green-btn:hover{color:#30b08f}.green-btn:hover:after,.green-btn:hover:before{background:#30b08f}.tiffany-btn{background:#4ab7bd}.tiffany-btn:hover{color:#4ab7bd}.tiffany-btn:hover:after,.tiffany-btn:hover:before{background:#4ab7bd}.yellow-btn{background:#fec171}.yellow-btn:hover{color:#fec171}.yellow-btn:hover:after,.yellow-btn:hover:before{background:#fec171}.pan-btn{font-size:14px;color:#fff;padding:14px 36px;border-radius:8px;border:none;outline:none;-webkit-transition:all .6s ease;transition:all .6s ease;position:relative;display:inline-block}.pan-btn:hover{background:#fff}.pan-btn:hover:after,.pan-btn:hover:before{width:100%;-webkit-transition:all .6s ease;transition:all .6s ease}.pan-btn:after,.pan-btn:before{content:"";position:absolute;top:0;right:0;height:2px;width:0;-webkit-transition:all .4s ease;transition:all .4s ease}.pan-btn:after{right:inherit;top:inherit;left:0;bottom:0}.custom-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;color:#fff;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;padding:10px 15px;font-size:14px;border-radius:4px}body{height:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif;background:#fff;color:#000}label{font-weight:700}html{-webkit-box-sizing:border-box;box-sizing:border-box}#app,html{height:100%}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}.no-padding{padding:0!important}.padding-content{padding:4px 0}a:active,a:focus{outline:none}a,a:focus,a:hover{cursor:pointer;color:inherit;text-decoration:none}div:focus{outline:none}.fr{float:right}.fl{float:left}.pr-5{padding-right:5px}.pl-5{padding-left:5px}.block{display:block}.pointer{cursor:pointer}.inlineBlock{display:block}.clearfix:after{visibility:hidden;display:block;font-size:0;content:" ";clear:both;height:0}code{background:#eef1f6;padding:15px 16px;margin-bottom:20px;display:block;line-height:36px;font-size:15px;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif}code a{color:#337ab7;cursor:pointer}code a:hover{color:#20a0ff}.warn-content{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem;line-height:1.6rem;word-spacing:.05rem}.warn-content a{color:#42b983;font-weight:600}.app-container{padding:20px}.components-container{margin:30px 50px;position:relative}.pagination-container{margin-top:30px}.text-center{text-align:center}.sub-navbar{height:50px;line-height:50px;position:relative;width:100%;text-align:right;padding-right:20px;-webkit-transition:position .6s ease;transition:position .6s ease;background:-webkit-gradient(linear,left top,right top,from(#20b6f9),color-stop(0,#20b6f9),color-stop(100%,#2178f1),to(#2178f1));background:linear-gradient(90deg,#20b6f9,#20b6f9 0,#2178f1 100%,#2178f1 0)}.sub-navbar .subtitle{font-size:20px;color:#fff}.sub-navbar.deleted,.sub-navbar.draft{background:#d0d0d0}.link-type,.link-type:focus{color:#337ab7;cursor:pointer}.link-type:focus:hover,.link-type:hover{color:#20a0ff}.filter-container{padding-bottom:10px}.filter-container .filter-item{display:inline-block;vertical-align:middle;margin-bottom:10px}.multiselect{line-height:16px}.multiselect--active{z-index:1000!important}.hamburger[data-v-69c6c5c4]{display:inline-block;vertical-align:middle;width:20px;height:20px}.hamburger.is-active[data-v-69c6c5c4]{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.navbar[data-v-28de7ff2]{height:50px;overflow:hidden}.navbar .hamburger-container[data-v-28de7ff2]{line-height:46px;height:100%;float:left;cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.navbar .hamburger-container[data-v-28de7ff2]:hover{background:rgba(0,0,0,.025)}.navbar .breadcrumb-container[data-v-28de7ff2]{float:left}.navbar .errLog-container[data-v-28de7ff2]{display:inline-block;vertical-align:top}.navbar .right-menu[data-v-28de7ff2]{float:right;height:100%;line-height:50px}.navbar .right-menu[data-v-28de7ff2]:focus{outline:none}.navbar .right-menu .right-menu-item[data-v-28de7ff2]{display:inline-block;padding:0 15px;height:100%;font-size:18px;color:#5a5e66;vertical-align:text-bottom}.navbar .right-menu .right-menu-item.hover-effect[data-v-28de7ff2]{cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.navbar .right-menu .right-menu-item.hover-effect[data-v-28de7ff2]:hover{background:rgba(0,0,0,.025)}.navbar .right-menu .avatar-container .avatar-wrapper[data-v-28de7ff2]{margin-top:5px;position:relative}.navbar .right-menu .avatar-container .avatar-wrapper .user-avatar[data-v-28de7ff2]{cursor:pointer;width:40px;height:40px;border-radius:10px}.navbar .right-menu .avatar-container .avatar-wrapper .el-icon-caret-bottom[data-v-28de7ff2]{cursor:pointer;position:absolute;right:-20px;top:25px;font-size:12px}.count-badge[data-v-52140d98]{margin-left:5px;height:48px}.scroll-container[data-v-591d6778]{white-space:nowrap;position:relative;overflow:hidden;width:100%}.scroll-container[data-v-591d6778] .el-scrollbar__bar{bottom:0}.scroll-container[data-v-591d6778] .el-scrollbar__wrap{height:49px}.tags-view-container[data-v-e1cdb714]{height:34px;width:100%;background:#fff;border-bottom:1px solid #d8dce5;-webkit-box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04);box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04)}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-e1cdb714]{display:inline-block;position:relative;cursor:pointer;height:26px;line-height:26px;border:1px solid #d8dce5;color:#495060;background:#fff;padding:0 8px;font-size:12px;margin-left:5px;margin-top:4px}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-e1cdb714]:first-of-type{margin-left:15px}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-e1cdb714]:last-of-type{margin-right:15px}.tags-view-container .tags-view-wrapper .tags-view-item.active[data-v-e1cdb714]{background-color:#42b983;color:#fff;border-color:#42b983}.tags-view-container .tags-view-wrapper .tags-view-item.active[data-v-e1cdb714]:before{content:"";background:#fff;display:inline-block;width:8px;height:8px;border-radius:50%;position:relative;margin-right:2px}.tags-view-container .contextmenu[data-v-e1cdb714]{margin:0;background:#fff;z-index:100;position:absolute;list-style-type:none;padding:5px 0;border-radius:4px;font-size:12px;font-weight:400;color:#333;-webkit-box-shadow:2px 2px 3px 0 rgba(0,0,0,.3);box-shadow:2px 2px 3px 0 rgba(0,0,0,.3)}.tags-view-container .contextmenu li[data-v-e1cdb714]{margin:0;padding:7px 16px;cursor:pointer}.tags-view-container .contextmenu li[data-v-e1cdb714]:hover{background:#eee}.tags-view-wrapper .tags-view-item .el-icon-close{width:16px;height:16px;vertical-align:2px;border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.tags-view-wrapper .tags-view-item .el-icon-close:before{-webkit-transform:scale(.6);transform:scale(.6);display:inline-block;vertical-align:-3px}.tags-view-wrapper .tags-view-item .el-icon-close:hover{background-color:#b4bccc;color:#fff}.app-main[data-v-f852c4f2]{min-height:calc(100vh - 84px);width:100%;position:relative;overflow:hidden}.app-wrapper[data-v-767d264f]{position:relative;height:100%;width:100%}.app-wrapper[data-v-767d264f]:after{content:"";display:table;clear:both}.app-wrapper.mobile.openSidebar[data-v-767d264f]{position:fixed;top:0}.drawer-bg[data-v-767d264f]{background:#000;opacity:.3;width:100%;top:0;height:100%;position:absolute;z-index:999}.svg-icon[data-v-17178ffc]{width:1em;height:1em;vertical-align:-.15em;fill:currentColor;overflow:hidden} \ No newline at end of file +.fade-enter-active,.fade-leave-active{-webkit-transition:opacity .28s;transition:opacity .28s}.fade-enter,.fade-leave-active{opacity:0}.fade-transform-enter-active,.fade-transform-leave-active{-webkit-transition:all .5s;transition:all .5s}.fade-transform-enter{opacity:0;-webkit-transform:translateX(-30px);transform:translateX(-30px)}.fade-transform-leave-to{opacity:0;-webkit-transform:translateX(30px);transform:translateX(30px)}.breadcrumb-enter-active,.breadcrumb-leave-active{-webkit-transition:all .5s;transition:all .5s}.breadcrumb-enter,.breadcrumb-leave-active{opacity:0;-webkit-transform:translateX(20px);transform:translateX(20px)}.breadcrumb-move{-webkit-transition:all .5s;transition:all .5s}.breadcrumb-leave-active{position:absolute}.el-breadcrumb__inner,.el-breadcrumb__inner a{font-weight:400!important}.el-upload input[type=file]{display:none!important}.el-upload__input{display:none}.cell .el-tag{margin-right:0}.small-padding .cell{padding-left:5px;padding-right:5px}.fixed-width .el-button--mini{padding:7px 10px;width:60px}.status-col .cell{padding:0 10px;text-align:center}.status-col .cell .el-tag{margin-right:0}.el-dialog{-webkit-transform:none;transform:none;left:0;position:relative;margin:0 auto}.article-textarea textarea{padding-right:40px;resize:none;border-radius:0;border:none;border-bottom:1px solid #bfcbd9}.upload-container .el-upload{width:100%}.upload-container .el-upload .el-upload-dragger{width:100%;height:200px}.el-dropdown-menu a{display:block}#app .main-container{min-height:100%;-webkit-transition:margin-left .28s;transition:margin-left .28s;margin-left:180px;position:relative}#app .sidebar-container{-webkit-transition:width .28s;transition:width .28s;width:180px!important;height:100%;position:fixed;font-size:0;top:0;bottom:0;left:0;z-index:5000;overflow:hidden}#app .sidebar-container .horizontal-collapse-transition{-webkit-transition:width 0s ease-in-out,padding-left 0s ease-in-out,padding-right 0s ease-in-out;transition:width 0s ease-in-out,padding-left 0s ease-in-out,padding-right 0s ease-in-out}#app .sidebar-container .scrollbar-wrapper{overflow-x:hidden!important}#app .sidebar-container .scrollbar-wrapper .el-scrollbar__view{height:100%}#app .sidebar-container .el-scrollbar__bar.is-vertical{right:0}#app .sidebar-container .is-horizontal{display:none}#app .sidebar-container a{display:inline-block;width:100%;overflow:hidden}#app .sidebar-container .svg-icon{margin-right:16px}#app .sidebar-container .el-menu{border:none;height:100%;width:100%!important}#app .sidebar-container .el-submenu__title:hover,#app .sidebar-container .submenu-title-noDropdown:hover{background-color:#263445!important}#app .sidebar-container .is-active>.el-submenu__title{color:#f4f4f5!important}#app .sidebar-container .el-submenu .el-menu-item,#app .sidebar-container .nest-menu .el-submenu>.el-submenu__title{min-width:180px!important;background-color:#1f2d3d!important}#app .sidebar-container .el-submenu .el-menu-item:hover,#app .sidebar-container .nest-menu .el-submenu>.el-submenu__title:hover{background-color:#001528!important}#app .hideSidebar .sidebar-container{width:36px!important}#app .hideSidebar .main-container{margin-left:36px}#app .hideSidebar .submenu-title-noDropdown{padding-left:10px!important;position:relative}#app .hideSidebar .submenu-title-noDropdown .el-tooltip{padding:0 10px!important}#app .hideSidebar .el-submenu{overflow:hidden}#app .hideSidebar .el-submenu>.el-submenu__title{padding-left:10px!important}#app .hideSidebar .el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}#app .hideSidebar .el-menu--collapse .el-submenu>.el-submenu__title>span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}#app .el-menu--collapse .el-menu .el-submenu{min-width:180px!important}#app .mobile .main-container{margin-left:0}#app .mobile .sidebar-container{-webkit-transition:-webkit-transform .28s;transition:-webkit-transform .28s;transition:transform .28s;transition:transform .28s,-webkit-transform .28s;width:180px!important}#app .mobile.hideSidebar .sidebar-container{pointer-events:none;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:translate3d(-180px,0,0);transform:translate3d(-180px,0,0)}#app .withoutAnimation .main-container,#app .withoutAnimation .sidebar-container{-webkit-transition:none;transition:none}.el-menu--vertical>.el-menu .svg-icon{margin-right:16px}.el-menu--vertical .el-menu-item:hover,.el-menu--vertical .nest-menu .el-submenu>.el-submenu__title:hover{background-color:#263445!important}.blue-btn{background:#324157}.blue-btn:hover{color:#324157}.blue-btn:hover:after,.blue-btn:hover:before{background:#324157}.light-blue-btn{background:#3a71a8}.light-blue-btn:hover{color:#3a71a8}.light-blue-btn:hover:after,.light-blue-btn:hover:before{background:#3a71a8}.red-btn{background:#c03639}.red-btn:hover{color:#c03639}.red-btn:hover:after,.red-btn:hover:before{background:#c03639}.pink-btn{background:#e65d6e}.pink-btn:hover{color:#e65d6e}.pink-btn:hover:after,.pink-btn:hover:before{background:#e65d6e}.green-btn{background:#30b08f}.green-btn:hover{color:#30b08f}.green-btn:hover:after,.green-btn:hover:before{background:#30b08f}.tiffany-btn{background:#4ab7bd}.tiffany-btn:hover{color:#4ab7bd}.tiffany-btn:hover:after,.tiffany-btn:hover:before{background:#4ab7bd}.yellow-btn{background:#fec171}.yellow-btn:hover{color:#fec171}.yellow-btn:hover:after,.yellow-btn:hover:before{background:#fec171}.pan-btn{font-size:14px;color:#fff;padding:14px 36px;border-radius:8px;border:none;outline:none;-webkit-transition:all .6s ease;transition:all .6s ease;position:relative;display:inline-block}.pan-btn:hover{background:#fff}.pan-btn:hover:after,.pan-btn:hover:before{width:100%;-webkit-transition:all .6s ease;transition:all .6s ease}.pan-btn:after,.pan-btn:before{content:"";position:absolute;top:0;right:0;height:2px;width:0;-webkit-transition:all .4s ease;transition:all .4s ease}.pan-btn:after{right:inherit;top:inherit;left:0;bottom:0}.custom-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;color:#fff;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;padding:10px 15px;font-size:14px;border-radius:4px}body{height:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif;background:#fff;color:#000}label{font-weight:700}html{-webkit-box-sizing:border-box;box-sizing:border-box}#app,html{height:100%}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}.no-padding{padding:0!important}.padding-content{padding:4px 0}a:active,a:focus{outline:none}a,a:focus,a:hover{cursor:pointer;color:inherit;text-decoration:none}div:focus{outline:none}.fr{float:right}.fl{float:left}.pr-5{padding-right:5px}.pl-5{padding-left:5px}.block{display:block}.pointer{cursor:pointer}.inlineBlock{display:block}.clearfix:after{visibility:hidden;display:block;font-size:0;content:" ";clear:both;height:0}code{background:#eef1f6;padding:15px 16px;display:block;line-height:36px;font-size:15px;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif}code a{color:#337ab7;cursor:pointer}code a:hover{color:#20a0ff}.warn-content{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem;line-height:1.6rem;word-spacing:.05rem}.warn-content a{color:#42b983;font-weight:600}.app-container{padding:20px}.components-container{margin:30px 50px;position:relative}.pagination-container{margin-top:30px}.text-center{text-align:center}.sub-navbar{height:50px;line-height:50px;position:relative;width:100%;text-align:right;padding-right:20px;-webkit-transition:position .6s ease;transition:position .6s ease;background:-webkit-gradient(linear,left top,right top,from(#20b6f9),color-stop(0,#20b6f9),color-stop(100%,#2178f1),to(#2178f1));background:linear-gradient(90deg,#20b6f9,#20b6f9 0,#2178f1 100%,#2178f1 0)}.sub-navbar .subtitle{font-size:20px;color:#fff}.sub-navbar.deleted,.sub-navbar.draft{background:#d0d0d0}.link-type,.link-type:focus{color:#337ab7;cursor:pointer}.link-type:focus:hover,.link-type:hover{color:#20a0ff}.filter-container{padding-bottom:10px}.filter-container .filter-item{display:inline-block;vertical-align:middle;margin-bottom:10px}.multiselect{line-height:16px}.multiselect--active{z-index:1000!important}.hamburger[data-v-69c6c5c4]{display:inline-block;vertical-align:middle;width:20px;height:20px}.hamburger.is-active[data-v-69c6c5c4]{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.navbar[data-v-28de7ff2]{height:50px;overflow:hidden}.navbar .hamburger-container[data-v-28de7ff2]{line-height:46px;height:100%;float:left;cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.navbar .hamburger-container[data-v-28de7ff2]:hover{background:rgba(0,0,0,.025)}.navbar .breadcrumb-container[data-v-28de7ff2]{float:left}.navbar .errLog-container[data-v-28de7ff2]{display:inline-block;vertical-align:top}.navbar .right-menu[data-v-28de7ff2]{float:right;height:100%;line-height:50px}.navbar .right-menu[data-v-28de7ff2]:focus{outline:none}.navbar .right-menu .right-menu-item[data-v-28de7ff2]{display:inline-block;padding:0 15px;height:100%;font-size:18px;color:#5a5e66;vertical-align:text-bottom}.navbar .right-menu .right-menu-item.hover-effect[data-v-28de7ff2]{cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.navbar .right-menu .right-menu-item.hover-effect[data-v-28de7ff2]:hover{background:rgba(0,0,0,.025)}.navbar .right-menu .avatar-container .avatar-wrapper[data-v-28de7ff2]{margin-top:5px;position:relative}.navbar .right-menu .avatar-container .avatar-wrapper .user-avatar[data-v-28de7ff2]{cursor:pointer;width:40px;height:40px;border-radius:10px}.navbar .right-menu .avatar-container .avatar-wrapper .el-icon-caret-bottom[data-v-28de7ff2]{cursor:pointer;position:absolute;right:-20px;top:25px;font-size:12px}.count-badge[data-v-52140d98]{margin-left:5px;height:48px}.scroll-container[data-v-591d6778]{white-space:nowrap;position:relative;overflow:hidden;width:100%}.scroll-container[data-v-591d6778] .el-scrollbar__bar{bottom:0}.scroll-container[data-v-591d6778] .el-scrollbar__wrap{height:49px}.tags-view-container[data-v-e1cdb714]{height:34px;width:100%;background:#fff;border-bottom:1px solid #d8dce5;-webkit-box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04);box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04)}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-e1cdb714]{display:inline-block;position:relative;cursor:pointer;height:26px;line-height:26px;border:1px solid #d8dce5;color:#495060;background:#fff;padding:0 8px;font-size:12px;margin-left:5px;margin-top:4px}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-e1cdb714]:first-of-type{margin-left:15px}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-e1cdb714]:last-of-type{margin-right:15px}.tags-view-container .tags-view-wrapper .tags-view-item.active[data-v-e1cdb714]{background-color:#42b983;color:#fff;border-color:#42b983}.tags-view-container .tags-view-wrapper .tags-view-item.active[data-v-e1cdb714]:before{content:"";background:#fff;display:inline-block;width:8px;height:8px;border-radius:50%;position:relative;margin-right:2px}.tags-view-container .contextmenu[data-v-e1cdb714]{margin:0;background:#fff;z-index:100;position:absolute;list-style-type:none;padding:5px 0;border-radius:4px;font-size:12px;font-weight:400;color:#333;-webkit-box-shadow:2px 2px 3px 0 rgba(0,0,0,.3);box-shadow:2px 2px 3px 0 rgba(0,0,0,.3)}.tags-view-container .contextmenu li[data-v-e1cdb714]{margin:0;padding:7px 16px;cursor:pointer}.tags-view-container .contextmenu li[data-v-e1cdb714]:hover{background:#eee}.tags-view-wrapper .tags-view-item .el-icon-close{width:16px;height:16px;vertical-align:2px;border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.tags-view-wrapper .tags-view-item .el-icon-close:before{-webkit-transform:scale(.6);transform:scale(.6);display:inline-block;vertical-align:-3px}.tags-view-wrapper .tags-view-item .el-icon-close:hover{background-color:#b4bccc;color:#fff}.app-main[data-v-f852c4f2]{min-height:calc(100vh - 84px);width:100%;position:relative;overflow:hidden}.app-wrapper[data-v-767d264f]{position:relative;height:100%;width:100%}.app-wrapper[data-v-767d264f]:after{content:"";display:table;clear:both}.app-wrapper.mobile.openSidebar[data-v-767d264f]{position:fixed;top:0}.drawer-bg[data-v-767d264f]{background:#000;opacity:.3;width:100%;top:0;height:100%;position:absolute;z-index:999}.svg-icon[data-v-17178ffc]{width:1em;height:1em;vertical-align:-.15em;fill:currentColor;overflow:hidden} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-0171.aa11eafe.css b/priv/static/adminfe/chunk-0171.aa11eafe.css deleted file mode 100644 index 45340d06b..000000000 --- a/priv/static/adminfe/chunk-0171.aa11eafe.css +++ /dev/null @@ -1 +0,0 @@ -.select-field[data-v-377d5068]{width:350px}@media only screen and (max-width:480px){.select-field[data-v-377d5068]{width:100%;margin-bottom:5px}}.el-dialog__body{padding:20px}.create-account-form-item{margin-bottom:20px}.create-account-form-item-without-margin{margin-bottom:0}@media only screen and (max-width:480px){.create-user-dialog{width:85%}.create-account-form-item{margin-bottom:20px}.el-dialog__body{padding:20px}}.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided.actor-type-dropdown:before{margin:0;height:0}.el-dropdown-menu--small .actor-type-dropdown{padding:0}.actor-type-select{width:100%}.actor-type-select input{border-color:transparent;color:#606266}.actor-type-select .el-input__inner:hover{border-color:transparent;background-color:#ecf5ff}.actor-type-select .el-input.is-focus{border-color:transparent}.actor-type-select .el-input__suffix-inner{pointer-events:none}.actor-type-select .el-input.is-active .el-input__inner,.actor-type-select .el-input.is-focus .el-input__inner,.actor-type-select .el-input__inner:focus,.actor-type-select .el-select .el-input__inner:focus{border-color:transparent}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.moderate-user-button{width:100%}}.actions-button{text-align:left;width:350px;padding:10px}.actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0 15px 10px}.actions-container .el-dropdown{margin-left:10px}.active-tag{color:#409eff;font-weight:700}.active-tag .el-icon-check{color:#409eff;float:right;margin:7px 0 0 15px}.el-dropdown-link:hover{cursor:pointer;color:#409eff}.create-account>.el-icon-plus{margin-right:5px}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.reason-tooltip{max-width:450px}.reset-password-link{text-decoration:underline}.users-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.users-container h1{margin:10px 0 0 15px;height:40px}.users-container .cell{word-break:break-word}.users-container .el-table__row:hover{cursor:pointer}.users-container .pagination{margin:25px 0;text-align:center}.users-container .reboot-button{margin:0 15px 0 0;padding:10px;width:145px}.users-container .search{width:350px;float:right;margin-left:10px}.users-container .filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.users-container .user-count{color:grey;font-size:28px}@media only screen and (max-width:480px){.password-reset-token-dialog{width:85%}.users-container h1{margin:0}.users-container .actions-button{width:100%}.users-container .actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px 7px}.users-container .el-icon-arrow-down{font-size:12px}.users-container .search{width:100%;margin-left:0}.users-container .filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px}.users-container .el-table__row .el-tag{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:30px;margin-bottom:4px;font-weight:700}.users-container .reboot-button{margin:0}.users-container .users-header-container{margin:7px 10px 12px}.users-container .user-count{color:grey;font-size:22px}}@media only screen and (max-width:801px) and (min-width:481px){.actions-button,.search{width:49%}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-03c5.f59788cf.css b/priv/static/adminfe/chunk-03c5.f59788cf.css new file mode 100644 index 000000000..863f6f4f4 --- /dev/null +++ b/priv/static/adminfe/chunk-03c5.f59788cf.css @@ -0,0 +1 @@ +h1[data-v-4ee576de]{margin:0}.enable-mediaproxy-container[data-v-4ee576de]{margin:10px 15px}.enable-mediaproxy-container button[data-v-4ee576de]{font-size:16px}.expl[data-v-4ee576de]{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.banned-urls-table[data-v-4ee576de]{margin-top:15px;margin-bottom:15px}.evict-button[data-v-4ee576de]{margin-left:15px}.media-proxy-cache-header[data-v-4ee576de]{margin-left:15px;margin-top:22px;font-weight:500}.media-proxy-cache-header-container[data-v-4ee576de]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:10px 15px}.pagination[data-v-4ee576de]{margin:25px 0;text-align:center}.remove-url-button[data-v-4ee576de]{width:150px}.url-input[data-v-4ee576de]{margin-right:15px}.url-input-container[data-v-4ee576de]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;margin:15px 15px 5px}.url-input-expl[data-v-4ee576de]{margin-left:15px}@media only screen and (max-width:480px){.url-input[data-v-4ee576de]{width:100%;margin-bottom:5px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-0598.d8f2b478.css b/priv/static/adminfe/chunk-0598.d8f2b478.css deleted file mode 100644 index 9b84800d0..000000000 --- a/priv/static/adminfe/chunk-0598.d8f2b478.css +++ /dev/null @@ -1 +0,0 @@ -.status-card{margin-bottom:10px;cursor:pointer}.status-card .account{line-height:26px;font-size:13px;color:#606266}.status-card .account:hover{text-decoration:underline}.status-card .deactivated{color:grey;line-height:28px;vertical-align:middle}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .router-link{text-decoration:none}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;font-size:15px;font-weight:500}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-card-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-created-at{font-size:13px;color:#606266}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-footer,.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-tags{display:inline}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-footer{margin-top:10px}.status-card .status-footer,.status-card .status-header{-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}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex}}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.moderate-user-button{width:100%}}.security-settings-container{display:-webkit-box;display:-ms-flexbox;display:flex}.security-settings-container label{width:15%;height:36px}.security-settings-modal .el-dialog__body{padding-top:10px}.security-settings-modal .el-form-item,.security-settings-modal .password-alert{margin-bottom:15px}.security-settings-modal .password-input{margin-bottom:0}.security-settings-submit-button{float:right}@media (max-width:800px){.security-settings-modal .el-dialog{width:90%}}.security-settings-modal .el-alert .el-alert__description{word-break:break-word;font-size:1em}.security-settings-modal .form-text{display:block;margin-top:.25rem;color:#909399}header{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;margin:22px 0;padding-left:15px}header h1{margin:0 0 0 10px}table{margin:10px 0 0 15px}table .name-col{width:150px}.avatar-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.avatar-name-container .el-icon-top-right{font-size:2em;line-height:36px;color:#606266}.invalid{color:grey}.el-table--border:after,.el-table--group:after,.el-table:before{background-color:transparent}.image{width:20%}.image img{width:100%}.invalid-user-tag{font-size:14px;width:inherit;height:auto;text-align:center;word-wrap:break-word;white-space:normal}.left-header-container{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.no-statuses{margin-left:28px;color:#606266}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.poll ul{list-style-type:none;padding:0;width:30%}.reboot-button{padding:10px;margin-left:10px}.recent-statuses-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:67%}.recent-statuses-header{margin-top:10px}.reset-password-link{text-decoration:underline}.security-setting-button{margin-top:20px;width:100%}.statuses{padding:0 20px 0 0}.show-private{width:200px;text-align:left;line-height:67px;margin-right:20px}.show-private-statuses{margin-left:28px;margin-bottom:20px}.recent-statuses{margin-left:28px}.user-page-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:22px 15px 22px 20px;padding:0;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.user-page-header h1{display:inline}.user-profile-card{margin:0 20px;width:30%;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.user-profile-container{display:-webkit-box;display:-ms-flexbox;display:flex}.user-profile-table{margin:0;width:inherit}.user-profile-tag{margin:0 4px 4px 0}@media only screen and (max-width:480px){.avatar-name-container{margin-bottom:10px}.el-timeline-item__wrapper{padding-left:18px}.password-reset-token-dialog{width:85%}.recent-statuses{margin:20px 10px 15px}.recent-statuses-container{width:100%;margin:0}.show-private-statuses{margin:0 10px 20px}.status-container{margin:0 10px}.statuses{padding-right:10px;margin-left:8px}.user-page-header{padding:0;margin:7px 15px 15px 10px}.user-page-header-container .el-dropdown{width:95%;margin:0 15px 15px 10px}.user-profile-card{margin:0 10px;width:95%}.user-profile-card td{width:80px}.user-profile-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}@media only screen and (max-width:801px) and (min-width:481px){.recent-statuses{margin:20px 10px 15px 0}.recent-statuses-container{width:97%;margin:0 20px}.show-private-statuses{margin:0 10px 20px 0}.user-page-header{padding:0;margin:7px 15px 20px 20px}.user-profile-card{margin:0 20px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.user-profile-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-0f09.66ca2a61.css b/priv/static/adminfe/chunk-0f09.66ca2a61.css deleted file mode 100644 index b580e0699..000000000 --- a/priv/static/adminfe/chunk-0f09.66ca2a61.css +++ /dev/null @@ -1 +0,0 @@ -.actions-button[data-v-2d9f3c5e]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-2d9f3c5e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-2d9f3c5e]{float:right}.el-icon-edit[data-v-2d9f3c5e]{margin-right:5px}.tag-container[data-v-2d9f3c5e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-2d9f3c5e]{padding-right:20px}.no-hover[data-v-2d9f3c5e]:hover{color:#606266;background-color:#fff;cursor:auto} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-176e.b7aa5351.css b/priv/static/adminfe/chunk-176e.a3c8376d.css similarity index 100% rename from priv/static/adminfe/chunk-176e.b7aa5351.css rename to priv/static/adminfe/chunk-176e.a3c8376d.css diff --git a/priv/static/adminfe/chunk-19e2.934ad654.css b/priv/static/adminfe/chunk-19e2.934ad654.css deleted file mode 100644 index 4fd86df25..000000000 --- a/priv/static/adminfe/chunk-19e2.934ad654.css +++ /dev/null @@ -1 +0,0 @@ -.select-field[data-v-06df454a]{width:350px}@media only screen and (max-width:480px){.select-field[data-v-06df454a]{width:100%;margin-bottom:5px}}.el-dialog__body{padding:20px}.create-account-form-item{margin-bottom:20px}.create-account-form-item-without-margin{margin-bottom:0}@media only screen and (max-width:480px){.create-user-dialog{width:85%}.create-account-form-item{margin-bottom:20px}.el-dialog__body{padding:20px}}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.moderate-user-button{width:100%}}.actions-button{text-align:left;width:350px;padding:10px}.actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0 15px 10px}.actions-container .el-dropdown{margin-left:10px}.active-tag{color:#409eff;font-weight:700}.active-tag .el-icon-check{color:#409eff;float:right;margin:7px 0 0 15px}.el-dropdown-link:hover{cursor:pointer;color:#409eff}.create-account>.el-icon-plus{margin-right:5px}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.reset-password-link{text-decoration:underline}.users-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.users-container h1{margin:10px 0 0 15px;height:40px}.users-container .el-table__row:hover{cursor:pointer}.users-container .pagination{margin:25px 0;text-align:center}.users-container .reboot-button{margin:0 15px 0 0;padding:10px;width:145px}.users-container .search{width:350px;float:right;margin-left:10px}.users-container .filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.users-container .user-count{color:grey;font-size:28px}@media only screen and (max-width:480px){.password-reset-token-dialog{width:85%}.users-container h1{margin:0}.users-container .actions-button{width:100%}.users-container .actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px 7px}.users-container .el-icon-arrow-down{font-size:12px}.users-container .search{width:100%;margin-left:0}.users-container .filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px}.users-container .el-table__row .el-tag{width:30px;display:inline-block;margin-bottom:4px;font-weight:700}.users-container .el-table__row .el-tag.el-tag--danger,.users-container .el-table__row .el-tag.el-tag--success{padding-left:8px}.users-container .reboot-button{margin:0}.users-container .users-header-container{margin:7px 10px 12px}.users-container .user-count{color:grey;font-size:22px}}@media only screen and (max-width:801px) and (min-width:481px){.actions-button,.search{width:49%}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-28f8.0aae6427.css b/priv/static/adminfe/chunk-28f8.0aae6427.css deleted file mode 100644 index e811b3260..000000000 --- a/priv/static/adminfe/chunk-28f8.0aae6427.css +++ /dev/null @@ -1 +0,0 @@ -.image-upload-area .input-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.image-upload-area .input-file{z-index:100;position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;cursor:pointer}.image-upload-area .image-button-group{margin-top:20px}.image-upload-area .image-button-group .upload-button,.image-upload-area .image-upload-wrapper{position:relative}.image-upload-area .image-upload-wrapper .image-upload-overlay{border-radius:5px}.image-upload-area .image-upload-wrapper .image-upload-overlay,.image-upload-area .image-upload-wrapper .image-upload-overlay .caption{-webkit-transition:-webkit-box-shadow .1s;transition:-webkit-box-shadow .1s;transition:box-shadow .1s;transition:box-shadow .1s,-webkit-box-shadow .1s}.image-upload-area .image-upload-wrapper .image-upload-overlay .caption{visibility:hidden;position:absolute;top:0;bottom:0;right:0;left:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-weight:700;font-size:10px;text-transform:uppercase;color:#fff;z-index:9}.image-upload-area .image-upload-wrapper .image-upload-overlay .uploaded-image{border-radius:5px;-webkit-box-shadow:0 2px 10px 0 rgba(0,0,0,.1);box-shadow:0 2px 10px 0 rgba(0,0,0,.1)}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover{visibility:visible;cursor:pointer;border-radius:5px}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover .el-image__error{visibility:hidden}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover .caption{visibility:visible;-webkit-box-shadow:0 2px 10px 0 rgba(0,0,0,.1),inset 0 0 120px 25px rgba(0,0,0,.8);box-shadow:0 2px 10px 0 rgba(0,0,0,.1),inset 0 0 120px 25px rgba(0,0,0,.8);border-radius:5px}a{text-decoration:underline}.center-label label{text-align:center}.center-label label span{float:left}.code{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.delete-setting-button{margin-left:5px}.description-container{overflow-wrap:break-word}.description-container .el-form-item__content{line-height:20px}.divider{margin:0 0 18px}.divider.thick-line{height:2px}.docs-search-container{float:right;margin-right:30px}.editable-keyword-container{width:100%}.el-form-item .rate-limit{margin-right:0}.el-input-group__prepend{padding-left:10px;padding-right:10px}.el-tabs__header{z-index:2002}.esshd-list{margin:0}.expl,.expl>p{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.expl>p code,.expl code{display:inline;line-height:22px;font-size:13px;padding:2px 3px}.follow-relay{width:350px;margin-right:7px}.form-container{margin-bottom:80px}.grouped-settings-header{margin:0 0 14px}.highlight{background-color:#e6e6e6}.icons-button-container{width:100%;margin-bottom:10px}.icons-button-desc{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;margin-left:5px}.icon-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:95%}.icon-values-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px 10px 0}.icon-key-input{width:30%;margin-right:8px}.icon-minus-button{width:36px;height:36px}.icon-value-input{width:70%;margin-left:8px}.icons-container,.input-container{display:-webkit-box;display:-ms-flexbox;display:flex}.input-container{-webkit-box-align:start;-ms-flex-align:start;align-items:start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input-container .el-form-item{margin-right:30px;width:100%}.input-container .el-select,.keyword-container{width:100%}label{overflow:hidden;text-overflow:ellipsis}.label-font{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700}.limit-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl{margin-left:10px}.limit-input{width:47%;margin:0 0 5px 1%}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:18px}.mascot{margin-bottom:15px}.mascot-container{width:100%}.mascot-input{margin-bottom:7px}.mascot-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input{margin-right:10px}.multiple-select-container{width:100%}.name-input{width:30%;margin-right:8px}.no-top-margin{margin-top:0}.no-top-margin p{margin-right:30px}.pattern-input{width:20%;margin-right:8px}.proxy-url-input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.proxy-url-host-input{width:35%;margin-right:8px}.proxy-url-value-input{width:35%;margin-left:8px;margin-right:10px}.prune-options{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.prune-options .el-radio{margin-top:11px}.rate-limit .el-form-item__content{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.rate-limit-container{width:100%}.rate-limit-content{width:70%}.rate-limit-label{float:right}.rate-limit-label-container{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;width:30%;margin-right:10px}.reboot-button{width:145px;text-align:left;padding:10px;float:right;margin:0 30px 0 0}.reboot-button-container{width:100%;position:fixed;top:60px;right:0;z-index:2000}.relays-container{margin:0 15px}.replacement-input{width:80%;margin-left:8px;margin-right:10px}.scale-input{width:47%;margin:0 1% 5px 0}.setting-input{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.setting-label{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.settings-container{max-width:1824px;margin:auto}.settings-container .el-tabs{margin-top:20px}.settings-delete-button{margin-left:5px}.settings-docs-button{width:163px;text-align:left;padding:10px}.settings-header{margin:10px 15px 15px}.header-sidebar-opened{max-width:1585px}.header-sidebar-closed{max-width:1728px}.settings-header-container{height:87px}.settings-search-input{width:350px;margin-left:5px}.single-input{margin-right:10px}.socks5-checkbox{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;margin-left:10px}.socks5-checkbox-container{width:40%;height:36px;margin-right:5px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ssl-tls-opts{margin:36px 0 0}.submit-button{float:right;margin:0 30px 22px 0}.submit-button-container{width:100%;position:fixed;bottom:0;right:0;z-index:2000}.switch-input{height:36px}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input{width:70%;margin-left:8px;margin-right:10px}@media only screen and (min-width:1824px){.header-sidebar-closed{max-width:1772px}.header-sidebar-opened{max-width:1630px}.reboot-button-container{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}.reboot-sidebar-opened{max-width:1630px}.reboot-sidebar-closed{max-width:1772px}.sidebar-closed{max-width:1586px}.sidebar-opened{max-width:1442px}.submit-button-container{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}}@media only screen and (max-width:480px){.crontab,.crontab label{width:100%}.delete-setting-button{margin:4px 0 0 5px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 5px 7px 15px}.description>p code{display:inline;line-height:18px;padding:2px 3px;font-size:14px}.description-container{margin:0 15px 22px}.divider{margin:0 0 10px}.divider .thick-line{height:2px}.follow-relay{width:70%;margin-right:5px}.follow-relay input{width:100%}.follow-relay-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}h1{font-size:24px}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container{width:100%}.input-container .el-form-item:first-child{margin:0;padding:0 15px 10px}.input-container .el-form-item.crontab-container:first-child{margin:0;padding:0}.input-container .el-form-item:first-child .mascot-form-item,.input-container .el-form-item:first-child .rate-limit{padding:0}.input-container .settings-delete-button{margin-top:4px;float:right}.input-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.label-with-margin{margin-left:15px}.limit-input{width:45%}.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.proxy-url-input{-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;margin-bottom:0}.proxy-url-host-input{width:100%;margin-bottom:5px}.proxy-url-value-input{width:100%;margin-left:0}.prune-options{height:80px}.prune-options,.rate-limit .el-form-item__content{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.rate-limit-content{width:100%}.rate-limit-label{float:left}.rate-limit-label-container{width:100%}.reboot-button{margin:0 15px 0 0}.reboot-button-container{top:57px}.scale-input{width:45%}.settings-header{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:inline-block;margin:10px 15px 15px}.docs-search-container{float:right}.settings-search-input{width:100%;margin-left:0}.settings-search-input-container{margin:0 15px 15px}.settings-menu{width:163px;margin-right:5px}.socks5-checkbox-container{width:100%}.submit-button{margin:0 15px 22px 0}.el-input__inner{padding:0 5px}.el-form-item__label:not(.no-top-margin){padding-bottom:5px;line-height:22px;margin-top:7px;width:100%}.el-form-item__label:not(.no-top-margin) span{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.el-message{min-width:80%}.el-select__tags{overflow:hidden}.expl,.expl>p{line-height:16px}.icon-key-input{width:40%;margin-right:4px}.icon-minus-button{width:28px;height:28px;margin-top:4px}.icon-values-container{margin:0 7px 7px 0}.icon-value-input{width:60%;margin-left:4px}.icons-button-container{line-height:24px}.line{margin-bottom:10px}.mascot-form-item .el-form-item__label:not(.no-top-margin){margin:0;padding:0}.mascot-container{margin-bottom:5px}.name-input{width:40%;margin-right:5px}p.expl{line-height:20px}.pattern-input{width:40%;margin-right:4px}.relays-container{margin:0 10px}.replacement-input{width:60%;margin-left:4px;margin-right:5px}.settings-header-container{height:45px}.value-input{width:60%;margin-left:5px;margin-right:8px}}@media only screen and (max-width:818px) and (min-width:481px){.delete-setting-button{margin:4px 0 0 10px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 15px 10px 0}.icon-minus-button{width:28px;height:28px;margin-top:4px}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container .el-form-item__label span{margin-left:10px}.input-row,.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.nav-container{height:36px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 30px 15px 15px}.rate-limit-content{width:65%}.rate-limit-label-container{width:35%}.settings-delete-button{float:right}.settings-header-container{height:36px}.settings-search-input{width:250px;margin:0 0 15px 15px}}a[data-v-82f78b3e]{text-decoration:underline}.center-label label[data-v-82f78b3e]{text-align:center}.center-label label span[data-v-82f78b3e]{float:left}.code[data-v-82f78b3e]{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.delete-setting-button[data-v-82f78b3e]{margin-left:5px}.description-container[data-v-82f78b3e]{overflow-wrap:break-word}.description-container .el-form-item__content[data-v-82f78b3e]{line-height:20px}.divider[data-v-82f78b3e]{margin:0 0 18px}.divider.thick-line[data-v-82f78b3e]{height:2px}.docs-search-container[data-v-82f78b3e]{float:right;margin-right:30px}.editable-keyword-container[data-v-82f78b3e]{width:100%}.el-form-item .rate-limit[data-v-82f78b3e]{margin-right:0}.el-input-group__prepend[data-v-82f78b3e]{padding-left:10px;padding-right:10px}.el-tabs__header[data-v-82f78b3e]{z-index:2002}.esshd-list[data-v-82f78b3e]{margin:0}.expl>p[data-v-82f78b3e],.expl[data-v-82f78b3e]{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.expl>p code[data-v-82f78b3e],.expl code[data-v-82f78b3e]{display:inline;line-height:22px;font-size:13px;padding:2px 3px}.follow-relay[data-v-82f78b3e]{width:350px;margin-right:7px}.form-container[data-v-82f78b3e]{margin-bottom:80px}.grouped-settings-header[data-v-82f78b3e]{margin:0 0 14px}.highlight[data-v-82f78b3e]{background-color:#e6e6e6}.icons-button-container[data-v-82f78b3e]{width:100%;margin-bottom:10px}.icons-button-desc[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;margin-left:5px}.icon-container[data-v-82f78b3e]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:95%}.icon-values-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px 10px 0}.icon-key-input[data-v-82f78b3e]{width:30%;margin-right:8px}.icon-minus-button[data-v-82f78b3e]{width:36px;height:36px}.icon-value-input[data-v-82f78b3e]{width:70%;margin-left:8px}.icons-container[data-v-82f78b3e],.input-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex}.input-container[data-v-82f78b3e]{-webkit-box-align:start;-ms-flex-align:start;align-items:start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input-container .el-form-item[data-v-82f78b3e]{margin-right:30px;width:100%}.input-container .el-select[data-v-82f78b3e],.keyword-container[data-v-82f78b3e]{width:100%}label[data-v-82f78b3e]{overflow:hidden;text-overflow:ellipsis}.label-font[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700}.limit-button-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl[data-v-82f78b3e]{margin-left:10px}.limit-input[data-v-82f78b3e]{width:47%;margin:0 0 5px 1%}.line[data-v-82f78b3e]{width:100%;height:0;border:1px solid #eee;margin-bottom:18px}.mascot[data-v-82f78b3e]{margin-bottom:15px}.mascot-container[data-v-82f78b3e]{width:100%}.mascot-input[data-v-82f78b3e]{margin-bottom:7px}.mascot-name-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input[data-v-82f78b3e]{margin-right:10px}.multiple-select-container[data-v-82f78b3e]{width:100%}.name-input[data-v-82f78b3e]{width:30%;margin-right:8px}.no-top-margin[data-v-82f78b3e]{margin-top:0}.no-top-margin p[data-v-82f78b3e]{margin-right:30px}.pattern-input[data-v-82f78b3e]{width:20%;margin-right:8px}.proxy-url-input[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.proxy-url-host-input[data-v-82f78b3e]{width:35%;margin-right:8px}.proxy-url-value-input[data-v-82f78b3e]{width:35%;margin-left:8px;margin-right:10px}.prune-options[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.prune-options .el-radio[data-v-82f78b3e]{margin-top:11px}.rate-limit .el-form-item__content[data-v-82f78b3e]{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.rate-limit-container[data-v-82f78b3e]{width:100%}.rate-limit-content[data-v-82f78b3e]{width:70%}.rate-limit-label[data-v-82f78b3e]{float:right}.rate-limit-label-container[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;width:30%;margin-right:10px}.reboot-button[data-v-82f78b3e]{width:145px;text-align:left;padding:10px;float:right;margin:0 30px 0 0}.reboot-button-container[data-v-82f78b3e]{width:100%;position:fixed;top:60px;right:0;z-index:2000}.relays-container[data-v-82f78b3e]{margin:0 15px}.replacement-input[data-v-82f78b3e]{width:80%;margin-left:8px;margin-right:10px}.scale-input[data-v-82f78b3e]{width:47%;margin:0 1% 5px 0}.setting-input[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.setting-label[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.settings-container[data-v-82f78b3e]{max-width:1824px;margin:auto}.settings-container .el-tabs[data-v-82f78b3e]{margin-top:20px}.settings-delete-button[data-v-82f78b3e]{margin-left:5px}.settings-docs-button[data-v-82f78b3e]{width:163px;text-align:left;padding:10px}.settings-header[data-v-82f78b3e]{margin:10px 15px 15px}.header-sidebar-opened[data-v-82f78b3e]{max-width:1585px}.header-sidebar-closed[data-v-82f78b3e]{max-width:1728px}.settings-header-container[data-v-82f78b3e]{height:87px}.settings-search-input[data-v-82f78b3e]{width:350px;margin-left:5px}.single-input[data-v-82f78b3e]{margin-right:10px}.socks5-checkbox[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;margin-left:10px}.socks5-checkbox-container[data-v-82f78b3e]{width:40%;height:36px;margin-right:5px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ssl-tls-opts[data-v-82f78b3e]{margin:36px 0 0}.submit-button[data-v-82f78b3e]{float:right;margin:0 30px 22px 0}.submit-button-container[data-v-82f78b3e]{width:100%;position:fixed;bottom:0;right:0;z-index:2000}.switch-input[data-v-82f78b3e]{height:36px}.text[data-v-82f78b3e]{line-height:20px;margin-right:15px}.upload-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input[data-v-82f78b3e]{width:70%;margin-left:8px;margin-right:10px}@media only screen and (min-width:1824px){.header-sidebar-closed[data-v-82f78b3e]{max-width:1772px}.header-sidebar-opened[data-v-82f78b3e]{max-width:1630px}.reboot-button-container[data-v-82f78b3e]{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}.reboot-sidebar-opened[data-v-82f78b3e]{max-width:1630px}.reboot-sidebar-closed[data-v-82f78b3e]{max-width:1772px}.sidebar-closed[data-v-82f78b3e]{max-width:1586px}.sidebar-opened[data-v-82f78b3e]{max-width:1442px}.submit-button-container[data-v-82f78b3e]{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}}@media only screen and (max-width:480px){.crontab[data-v-82f78b3e],.crontab label[data-v-82f78b3e]{width:100%}.delete-setting-button[data-v-82f78b3e]{margin:4px 0 0 5px;height:28px}.delete-setting-button-container[data-v-82f78b3e]{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p[data-v-82f78b3e]{line-height:18px;margin:0 5px 7px 15px}.description>p code[data-v-82f78b3e]{display:inline;line-height:18px;padding:2px 3px;font-size:14px}.description-container[data-v-82f78b3e]{margin:0 15px 22px}.divider[data-v-82f78b3e]{margin:0 0 10px}.divider .thick-line[data-v-82f78b3e]{height:2px}.follow-relay[data-v-82f78b3e]{width:70%;margin-right:5px}.follow-relay input[data-v-82f78b3e]{width:100%}.follow-relay-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}h1[data-v-82f78b3e]{font-size:24px}.input[data-v-82f78b3e]{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container[data-v-82f78b3e]{width:100%}.input-container .el-form-item[data-v-82f78b3e]:first-child{margin:0;padding:0 15px 10px}.input-container .el-form-item.crontab-container[data-v-82f78b3e]:first-child{margin:0;padding:0}.input-container .el-form-item:first-child .mascot-form-item[data-v-82f78b3e],.input-container .el-form-item:first-child .rate-limit[data-v-82f78b3e]{padding:0}.input-container .settings-delete-button[data-v-82f78b3e]{margin-top:4px;float:right}.input-row[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.label-with-margin[data-v-82f78b3e]{margin-left:15px}.limit-input[data-v-82f78b3e]{width:45%}.nav-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.proxy-url-input[data-v-82f78b3e]{-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;margin-bottom:0}.proxy-url-host-input[data-v-82f78b3e]{width:100%;margin-bottom:5px}.proxy-url-value-input[data-v-82f78b3e]{width:100%;margin-left:0}.prune-options[data-v-82f78b3e]{height:80px}.prune-options[data-v-82f78b3e],.rate-limit .el-form-item__content[data-v-82f78b3e]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.rate-limit-content[data-v-82f78b3e]{width:100%}.rate-limit-label[data-v-82f78b3e]{float:left}.rate-limit-label-container[data-v-82f78b3e]{width:100%}.reboot-button[data-v-82f78b3e]{margin:0 15px 0 0}.reboot-button-container[data-v-82f78b3e]{top:57px}.scale-input[data-v-82f78b3e]{width:45%}.settings-header[data-v-82f78b3e]{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:inline-block;margin:10px 15px 15px}.docs-search-container[data-v-82f78b3e]{float:right}.settings-search-input[data-v-82f78b3e]{width:100%;margin-left:0}.settings-search-input-container[data-v-82f78b3e]{margin:0 15px 15px}.settings-menu[data-v-82f78b3e]{width:163px;margin-right:5px}.socks5-checkbox-container[data-v-82f78b3e]{width:100%}.submit-button[data-v-82f78b3e]{margin:0 15px 22px 0}.el-input__inner[data-v-82f78b3e]{padding:0 5px}.el-form-item__label[data-v-82f78b3e]:not(.no-top-margin){padding-bottom:5px;line-height:22px;margin-top:7px;width:100%}.el-form-item__label:not(.no-top-margin) span[data-v-82f78b3e]{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.el-message[data-v-82f78b3e]{min-width:80%}.el-select__tags[data-v-82f78b3e]{overflow:hidden}.expl>p[data-v-82f78b3e],.expl[data-v-82f78b3e]{line-height:16px}.icon-key-input[data-v-82f78b3e]{width:40%;margin-right:4px}.icon-minus-button[data-v-82f78b3e]{width:28px;height:28px;margin-top:4px}.icon-values-container[data-v-82f78b3e]{margin:0 7px 7px 0}.icon-value-input[data-v-82f78b3e]{width:60%;margin-left:4px}.icons-button-container[data-v-82f78b3e]{line-height:24px}.line[data-v-82f78b3e]{margin-bottom:10px}.mascot-form-item .el-form-item__label[data-v-82f78b3e]:not(.no-top-margin){margin:0;padding:0}.mascot-container[data-v-82f78b3e]{margin-bottom:5px}.name-input[data-v-82f78b3e]{width:40%;margin-right:5px}p.expl[data-v-82f78b3e]{line-height:20px}.pattern-input[data-v-82f78b3e]{width:40%;margin-right:4px}.relays-container[data-v-82f78b3e]{margin:0 10px}.replacement-input[data-v-82f78b3e]{width:60%;margin-left:4px;margin-right:5px}.settings-header-container[data-v-82f78b3e]{height:45px}.value-input[data-v-82f78b3e]{width:60%;margin-left:5px;margin-right:8px}}@media only screen and (max-width:818px) and (min-width:481px){.delete-setting-button[data-v-82f78b3e]{margin:4px 0 0 10px;height:28px}.delete-setting-button-container[data-v-82f78b3e]{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p[data-v-82f78b3e]{line-height:18px;margin:0 15px 10px 0}.icon-minus-button[data-v-82f78b3e]{width:28px;height:28px;margin-top:4px}.input[data-v-82f78b3e]{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container .el-form-item__label span[data-v-82f78b3e]{margin-left:10px}.input-row[data-v-82f78b3e],.nav-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.nav-container[data-v-82f78b3e]{height:36px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 30px 15px 15px}.rate-limit-content[data-v-82f78b3e]{width:65%}.rate-limit-label-container[data-v-82f78b3e]{width:35%}.settings-delete-button[data-v-82f78b3e]{float:right}.settings-header-container[data-v-82f78b3e]{height:36px}.settings-search-input[data-v-82f78b3e]{width:250px;margin:0 0 15px 15px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-2d97.82cbb623.css b/priv/static/adminfe/chunk-2d97.82cbb623.css deleted file mode 100644 index f6e28e1fb..000000000 --- a/priv/static/adminfe/chunk-2d97.82cbb623.css +++ /dev/null @@ -1 +0,0 @@ -.actions-button[data-v-9bd813c8]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-9bd813c8]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-9bd813c8]{float:right}.el-icon-edit[data-v-9bd813c8]{margin-right:5px}.tag-container[data-v-9bd813c8]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-9bd813c8]{padding-right:20px}.no-hover[data-v-9bd813c8]:hover{color:#606266;background-color:#fff;cursor:auto} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-3221.0ef79c67.css b/priv/static/adminfe/chunk-3221.0ef79c67.css deleted file mode 100644 index bd64e939a..000000000 --- a/priv/static/adminfe/chunk-3221.0ef79c67.css +++ /dev/null @@ -1 +0,0 @@ -h1[data-v-8208195e]{margin:0}.expl[data-v-8208195e]{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.banned-urls-table[data-v-8208195e]{margin-top:15px;margin-bottom:15px}.evict-button[data-v-8208195e]{margin-left:15px}.media-proxy-cache-header[data-v-8208195e]{margin-left:15px;margin-top:22px;font-weight:500}.media-proxy-cache-header-container[data-v-8208195e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:10px 15px}.remove-url-button[data-v-8208195e]{width:150px}.url-input[data-v-8208195e]{margin-right:15px}.url-input-container[data-v-8208195e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;margin:15px 15px 5px}.url-input-expl[data-v-8208195e]{margin-left:15px}@media only screen and (max-width:480px){.url-input[data-v-8208195e]{width:100%;margin-bottom:5px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-3365.201aa8e6.css b/priv/static/adminfe/chunk-3365.201aa8e6.css new file mode 100644 index 000000000..e5024d666 --- /dev/null +++ b/priv/static/adminfe/chunk-3365.201aa8e6.css @@ -0,0 +1 @@ +.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided.actor-type-dropdown:before{margin:0;height:0}.el-dropdown-menu--small .actor-type-dropdown{padding:0}.actor-type-select{width:100%}.actor-type-select input{border-color:transparent;color:#606266}.actor-type-select .el-input__inner:hover{border-color:transparent;background-color:#ecf5ff}.actor-type-select .el-input.is-focus{border-color:transparent}.actor-type-select .el-input__suffix-inner{pointer-events:none}.actor-type-select .el-input.is-active .el-input__inner,.actor-type-select .el-input.is-focus .el-input__inner,.actor-type-select .el-input__inner:focus,.actor-type-select .el-select .el-input__inner:focus{border-color:transparent}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.moderation-dropdown-menu{width:350px}@media only screen and (max-width:480px){.moderate-user-button{width:100%}.moderation-dropdown-menu{width:auto}}.avatar-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.avatar-name-container .el-icon-top-right{font-size:2em;line-height:36px;color:#606266}.avatar-name-header{display:-webkit-box;display:-ms-flexbox;display:flex;height:40px;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.invalid{color:grey}.no-statuses{margin-left:28px;color:#606266}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.reboot-button{padding:10px;margin-left:6px}.recent-statuses-container-show{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.recent-statuses-container-show .el-timeline-item,.recent-statuses-container-show .recent-statuses{margin-left:20px}.recent-statuses-container-show .show-private-statuses{margin-left:20px;margin-bottom:20px}.reset-password-link{text-decoration:underline}.router-link{text-decoration:none}.status-container{margin:0 15px 0 20px}.statuses{padding:0 20px 0 0}.user-page-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:22px 15px 22px 20px;padding:0;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.user-page-header h1{display:inline;margin:0 0 0 10px}@media only screen and (min-width:1824px){.status-show-container{max-width:1824px;margin:auto}}@media only screen and (max-width:480px){.avatar-name-container{margin-bottom:10px}.el-timeline-item__wrapper{padding-left:18px}.left-header-container{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.password-reset-token-dialog{width:85%}.recent-statuses{margin:20px 10px 15px}.recent-statuses-container-show{width:100%;margin:0 0 0 10px}.recent-statuses-container-show .el-timeline-item,.recent-statuses-container-show .recent-statuses{margin-left:0}.recent-statuses-container-show .show-private-statuses{margin:0 10px 20px 0}.status-card .el-card__body{padding:15px}.status-container{margin:0 10px}.statuses{padding-right:10px;margin-left:0}.statuses .el-timeline-item__wrapper{margin-right:10px}.user-page-header{padding:0;margin:7px 15px 5px 10px}.status-page-header-container{width:100%}.status-page-header-container .el-dropdown{width:-webkit-fill-available;width:-moz-available;width:stretch;margin:0 10px 15px}}@media only screen and (max-width:801px) and (min-width:481px){.recent-statuses-container-show{width:97%;margin:0 20px}.recent-statuses-container-show .el-timeline-item{margin-left:2px}.recent-statuses-container-show .recent-statuses{margin:20px 10px 15px 0}.recent-statuses-container-show .show-private-statuses,.show-private-statuses{margin:0 10px 20px 0}.user-page-header{padding:0;margin:7px 15px 20px 20px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-342d.e342722b.css b/priv/static/adminfe/chunk-342d.e342722b.css new file mode 100644 index 000000000..b0fd8dcb3 --- /dev/null +++ b/priv/static/adminfe/chunk-342d.e342722b.css @@ -0,0 +1 @@ +.select-field[data-v-377d5068]{width:350px}@media only screen and (max-width:480px){.select-field[data-v-377d5068]{width:100%;margin-bottom:5px}}.el-dialog__body{padding:20px}.create-account-form-item{margin-bottom:20px}.create-account-form-item-without-margin{margin-bottom:0}@media only screen and (max-width:480px){.create-user-dialog{width:85%}.create-account-form-item{margin-bottom:20px}.el-dialog__body{padding:20px}}.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided.actor-type-dropdown:before{margin:0;height:0}.el-dropdown-menu--small .actor-type-dropdown{padding:0}.actor-type-select{width:100%}.actor-type-select input{border-color:transparent;color:#606266}.actor-type-select .el-input__inner:hover{border-color:transparent;background-color:#ecf5ff}.actor-type-select .el-input.is-focus{border-color:transparent}.actor-type-select .el-input__suffix-inner{pointer-events:none}.actor-type-select .el-input.is-active .el-input__inner,.actor-type-select .el-input.is-focus .el-input__inner,.actor-type-select .el-input__inner:focus,.actor-type-select .el-select .el-input__inner:focus{border-color:transparent}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.moderation-dropdown-menu{width:350px}@media only screen and (max-width:480px){.moderate-user-button{width:100%}.moderation-dropdown-menu{width:auto}}.actions-button{text-align:left;width:350px;padding:10px}.actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0 15px 10px}.actions-container .el-dropdown{margin-left:10px}.active-tag{color:#409eff;font-weight:700}.active-tag .el-icon-check{color:#409eff;float:right;margin:7px 0 0 15px}.active-tag.is-disabled .el-icon-check{color:#bbb}.el-dropdown-link:hover{cursor:pointer;color:#409eff}.create-account>.el-icon-plus{margin-right:5px}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.reason-tooltip{max-width:450px}.reset-password-link{text-decoration:underline}.users-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.users-container h1{margin:10px 0 0 15px;height:40px}.users-container .cell{word-break:break-word}.users-container .el-table__row:hover{cursor:pointer}.users-container .pagination{margin:25px 0;text-align:center}.users-container .reboot-button{margin:0 15px 0 0;padding:10px;width:145px}.users-container .search{width:350px;float:right;margin-left:10px}.users-container .filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.users-container .user-count{color:grey;font-size:28px}@media only screen and (max-width:480px){.password-reset-token-dialog{width:85%}.users-container h1{margin:0}.users-container .actions-button{width:100%}.users-container .actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px 7px}.users-container .el-icon-arrow-down{font-size:12px}.users-container .search{width:100%;margin-left:0}.users-container .filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px}.users-container .el-table__row .el-tag{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:30px;margin-bottom:4px;font-weight:700}.users-container .reboot-button{margin:0}.users-container .users-header-container{margin:7px 10px 12px}.users-container .user-count{color:grey;font-size:22px}}@media only screen and (max-width:801px) and (min-width:481px){.actions-button,.search{width:49%}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-39ad.ba67c97f.css b/priv/static/adminfe/chunk-39ad.ba67c97f.css deleted file mode 100644 index 778a932cf..000000000 --- a/priv/static/adminfe/chunk-39ad.ba67c97f.css +++ /dev/null @@ -1 +0,0 @@ -.status-card{margin-bottom:10px;cursor:pointer}.status-card .account{line-height:26px;font-size:13px;color:#606266}.status-card .account:hover{text-decoration:underline}.status-card .deactivated{color:grey;line-height:28px;vertical-align:middle}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .router-link{text-decoration:none}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;font-size:15px;font-weight:500}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-card-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-created-at{font-size:13px;color:#606266}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-footer,.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-tags{display:inline}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-footer{margin-top:10px}.status-card .status-footer,.status-card .status-header{-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}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex}}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.moderate-user-button{width:100%}}.avatar-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.avatar-name-container .el-icon-top-right{font-size:2em;line-height:36px;color:#606266}.avatar-name-header{display:-webkit-box;display:-ms-flexbox;display:flex;height:40px;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.invalid{color:grey}.no-statuses{margin-left:28px;color:#606266}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.reboot-button{padding:10px;margin-left:6px}.recent-statuses-container-show{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.recent-statuses-container-show .el-timeline-item,.recent-statuses-container-show .recent-statuses{margin-left:20px}.recent-statuses-container-show .show-private-statuses{margin-left:20px;margin-bottom:20px}.reset-password-link{text-decoration:underline}.router-link{text-decoration:none}.status-container{margin:0 15px 0 20px}.statuses{padding:0 20px 0 0}.user-page-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:22px 15px 22px 20px;padding:0;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.user-page-header h1{display:inline;margin:0 0 0 10px}@media only screen and (min-width:1824px){.status-show-container{max-width:1824px;margin:auto}}@media only screen and (max-width:480px){.avatar-name-container{margin-bottom:10px}.el-timeline-item__wrapper{padding-left:18px}.left-header-container{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.password-reset-token-dialog{width:85%}.recent-statuses{margin:20px 10px 15px}.recent-statuses-container-show{width:100%;margin:0 0 0 10px}.recent-statuses-container-show .el-timeline-item,.recent-statuses-container-show .recent-statuses{margin-left:0}.recent-statuses-container-show .show-private-statuses{margin:0 10px 20px 0}.status-card .el-card__body{padding:15px}.status-container{margin:0 10px}.statuses{padding-right:10px;margin-left:0}.statuses .el-timeline-item__wrapper{margin-right:10px}.user-page-header{padding:0;margin:7px 15px 5px 10px}.status-page-header-container{width:100%}.status-page-header-container .el-dropdown{width:-webkit-fill-available;width:-moz-available;width:stretch;margin:0 10px 15px}}@media only screen and (max-width:801px) and (min-width:481px){.recent-statuses-container-show{width:97%;margin:0 20px}.recent-statuses-container-show .el-timeline-item{margin-left:2px}.recent-statuses-container-show .recent-statuses{margin:20px 10px 15px 0}.recent-statuses-container-show .show-private-statuses,.show-private-statuses{margin:0 10px 20px 0}.user-page-header{padding:0;margin:7px 15px 20px 20px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-3ba2.63b1228d.css b/priv/static/adminfe/chunk-3ba2.63b1228d.css deleted file mode 100644 index b375f08d5..000000000 --- a/priv/static/adminfe/chunk-3ba2.63b1228d.css +++ /dev/null @@ -1 +0,0 @@ -a{text-decoration:underline}.note-header{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;height:40px}.note-actor{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.note-actor-name{margin:0;height:28px}.note-avatar-img{width:15px;height:15px;margin-right:5px}.note-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.note-card{margin-bottom:15px}.note-content,.note-header{font-size:15px}.note-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:28px;font-weight:500}@media only screen and (max-width:480px){.el-card__header{padding:10px 17px}.note-header{height:65px}.note-actor{margin-bottom:5px}.note-header{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-align:start;-ms-flex-align:start;align-items:flex-start}}.status-card{margin-bottom:10px;cursor:pointer}.status-card .account{line-height:26px;font-size:13px;color:#606266}.status-card .account:hover{text-decoration:underline}.status-card .deactivated{color:grey;line-height:28px;vertical-align:middle}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .router-link{text-decoration:none}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;font-size:15px;font-weight:500}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-card-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-created-at{font-size:13px;color:#606266}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-footer,.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-tags{display:inline}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-footer{margin-top:10px}.status-card .status-footer,.status-card .status-header{-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}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex}}h4{margin:0;height:17px}.account{line-height:26px;font-size:13px;color:#606266}.account:hover{text-decoration:underline}.avatar-img{vertical-align:bottom;width:15px;height:15px}.divider{margin:15px 0}.deactivated{color:grey}.el-card__body{padding:17px}.el-card__header{background-color:#fafafa;padding:10px 20px}.el-collapse{border-bottom:none}.el-collapse-item__header{height:46px;font-size:14px}.el-collapse-item__content{padding-bottom:7px}.el-icon-arrow-right{margin-right:6px}.id{color:grey;margin-top:6px}.line{width:100%;height:0;border:.5px solid #ebeef5;margin:15px 0}.new-note p{font-size:14px;font-weight:500;height:17px;margin:13px 0 7px}.note{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.1);box-shadow:0 2px 5px 0 rgba(0,0,0,.1);margin-bottom:10px}.no-notes{font-style:italic;color:grey}.report .report-header-container{height:40px}.report-account,.report .report-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.report-account{-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2}.report-account-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.report-account-name{font-size:15px;font-weight:500}.report-row-key{font-size:14px;font-weight:500;padding-right:5px}.report-title{margin:0}.report-note-form{margin:15px 0 0}.report-post-note{margin:5px 0 0;text-align:right}.reports-pagination{margin:25px 0;text-align:center}.reports-timeline{margin:30px 45px 45px 19px;padding:0}.router-link{text-decoration:none}.reported-statuses{margin-top:15px}.submit-button{display:block;margin:7px 0 17px auto}.timestamp{margin:0;font-style:italic;color:grey}@media only screen and (max-width:480px){.report .report-header-container{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:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;height:auto}.report .id{margin:6px 0 0}.report .report-actions-button,.report .report-tag{margin:3px 0 6px}.report .title-container{margin-bottom:7px}.reports-timeline{margin:20px 10px}.reports-timeline .el-timeline-item__wrapper{padding-left:20px}}.select-field[data-v-5ab7c15a]{width:350px}@media only screen and (max-width:480px){.select-field[data-v-5ab7c15a]{width:100%;margin-bottom:5px}}@media only screen and (max-width:801px) and (min-width:481px){.select-field[data-v-5ab7c15a]{width:50%}}.reports-container .reboot-button[data-v-fa601560]{padding:10px;margin:0;width:145px}.reports-container .reports-filter-container[data-v-fa601560]{margin:15px 45px 22px 15px;padding-bottom:0}.reports-container .reports-filter-container[data-v-fa601560],.reports-container .reports-header-container[data-v-fa601560]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.reports-container .reports-header-container[data-v-fa601560]{margin:10px 15px}.reports-container h1[data-v-fa601560]{margin:0}.reports-container .no-reports-message[data-v-fa601560]{color:grey;margin-left:19px}.reports-container .report-count[data-v-fa601560]{color:grey;font-size:28px}@media only screen and (max-width:480px){.reports-container h1[data-v-fa601560]{margin:7px 10px 15px}.reports-container .reboot-button[data-v-fa601560]{margin:0 0 5px 10px;width:145px}.reports-container .report-count[data-v-fa601560]{font-size:22px}.reports-container .reports-filter-container[data-v-fa601560]{margin:0 10px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-4eb4.b72d16c3.css b/priv/static/adminfe/chunk-4eb4.b72d16c3.css deleted file mode 100644 index 1ecdec162..000000000 --- a/priv/static/adminfe/chunk-4eb4.b72d16c3.css +++ /dev/null @@ -1 +0,0 @@ -.status-card{margin-bottom:10px;cursor:pointer}.status-card .account{line-height:26px;font-size:13px;color:#606266}.status-card .account:hover{text-decoration:underline}.status-card .deactivated{color:grey;line-height:28px;vertical-align:middle}.status-card .image{width:20%}.status-card .image img{width:100%}.status-card .router-link{text-decoration:none}.status-card .show-more-button{margin-left:5px}.status-card .status-account{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-avatar-img{display:inline-block;width:15px;height:15px;margin-right:5px}.status-card .status-account-name{display:inline-block;margin:0;font-size:15px;font-weight:500}.status-card .status-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.status-card .status-card-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-checkbox{margin-right:7px}.status-card .status-content{font-size:15px;line-height:26px}.status-card .status-created-at{font-size:13px;color:#606266}.status-card .status-deleted{font-style:italic;margin-top:3px}.status-card .status-footer,.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.status-card .status-tags{display:inline}.status-card .status-without-content{font-style:italic}@media only screen and (max-width:480px){.el-message{min-width:80%}.el-message-box{width:80%}.status-card .el-card__header{padding:10px 17px}.status-card .el-tag{margin:3px 0}.status-card .status-account-container{margin-bottom:5px}.status-card .status-actions-button{margin:3px 0}.status-card .status-actions{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.status-card .status-footer{margin-top:10px}.status-card .status-footer,.status-card .status-header{-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}.status-card .status-header{display:-webkit-box;display:-ms-flexbox;display:flex}}.statuses-container{padding:0 15px}.statuses-container h1{margin:10px 0 15px}.statuses-container .status-container{margin:0 0 10px}.statuses-header-container .el-button.is-plain:focus,.statuses-header-container .el-button.is-plain:hover{border-color:#dcdfe6;color:#606266;cursor:default}.checkbox-container{margin-bottom:15px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 0 15px}.reboot-button{padding:10px;margin:0;width:145px}.select-instance{width:396px}.statuses-header,.statuses-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.statuses-pagination{padding:15px 0;text-align:center}@media only screen and (max-width:480px){.checkbox-container{margin-bottom:10px}.filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:10px 0}.select-field{width:100%;margin-bottom:5px}.select-instance{width:100%}.statuses-header-container{-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}.statuses-header-container .el-button-group{width:100%}.statuses-header-container .el-button{padding:10px 6.5px;width:50%}.statuses-header-container .el-button-group>.el-button:first-child{border-bottom-left-radius:0}.statuses-header-container .el-button-group>.el-button:not(:first-child):not(:last-child).private-button{border-top-right-radius:4px}.statuses-header-container .el-button-group>.el-button:not(:first-child):not(:last-child).public-button{border-bottom-left-radius:4px;border-top:#fff}.statuses-header-container .el-button-group>.el-button:last-child{border-top-right-radius:0;border-top:#fff}.statuses-header-container .reboot-button{margin:10px 0 0}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-521f.b745ee5d.css b/priv/static/adminfe/chunk-521f.b745ee5d.css new file mode 100644 index 000000000..7e8ffb651 --- /dev/null +++ b/priv/static/adminfe/chunk-521f.b745ee5d.css @@ -0,0 +1 @@ +.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided.actor-type-dropdown:before{margin:0;height:0}.el-dropdown-menu--small .actor-type-dropdown{padding:0}.actor-type-select{width:100%}.actor-type-select input{border-color:transparent;color:#606266}.actor-type-select .el-input__inner:hover{border-color:transparent;background-color:#ecf5ff}.actor-type-select .el-input.is-focus{border-color:transparent}.actor-type-select .el-input__suffix-inner{pointer-events:none}.actor-type-select .el-input.is-active .el-input__inner,.actor-type-select .el-input.is-focus .el-input__inner,.actor-type-select .el-input__inner:focus,.actor-type-select .el-select .el-input__inner:focus{border-color:transparent}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.moderation-dropdown-menu{width:350px}@media only screen and (max-width:480px){.moderate-user-button{width:100%}.moderation-dropdown-menu{width:auto}}.security-settings-container{display:-webkit-box;display:-ms-flexbox;display:flex}.security-settings-container label{width:15%;height:36px}.security-settings-modal .el-dialog__body{padding-top:10px}.security-settings-modal .el-form-item,.security-settings-modal .password-alert{margin-bottom:15px}.security-settings-modal .password-input{margin-bottom:0}.security-settings-submit-button{float:right}@media (max-width:800px){.security-settings-modal .el-dialog{width:90%}}.security-settings-modal .el-alert .el-alert__description{word-break:break-word;font-size:1em}.security-settings-modal .form-text{display:block;margin-top:.25rem;color:#909399}header{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;margin:22px 0;padding-left:15px}header h1{margin:0 0 0 10px}table{margin:10px 0 0 15px}table .name-col{width:150px}.avatar-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.avatar-name-container .el-icon-top-right{font-size:2em;line-height:36px;color:#606266}.invalid{color:grey}.el-table--border:after,.el-table--group:after,.el-table:before{background-color:transparent}.image{width:20%}.image img{width:100%}.invalid-user-tag{font-size:14px;width:inherit;height:auto;text-align:center;word-wrap:break-word;white-space:normal}.left-header-container{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.no-statuses{margin-left:28px;color:#606266}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.poll ul{list-style-type:none;padding:0;width:30%}.reboot-button{padding:10px;margin-left:10px}.recent-statuses-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:67%}.recent-statuses-header{margin-top:10px}.reset-password-link{text-decoration:underline}.security-setting-button{margin-top:20px;width:100%}.statuses{padding:0 20px 0 0}.show-private{width:200px;text-align:left;line-height:67px;margin-right:20px}.show-private-statuses{margin-left:28px;margin-bottom:20px}.recent-statuses{margin-left:28px}.user-page-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:22px 15px 22px 20px;padding:0;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.user-page-header h1{display:inline}.user-profile-card{margin:0 20px;width:30%;min-width:300px;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.user-profile-container{display:-webkit-box;display:-ms-flexbox;display:flex}.user-profile-table{margin:0;width:inherit}.user-profile-tag{margin:0 4px 4px 0}.reason-label{color:#878d99;font-weight:700;margin:5px 0}@media only screen and (max-width:480px){.avatar-name-container{margin-bottom:10px}.el-timeline-item__wrapper{padding-left:18px}.password-reset-token-dialog{width:85%}.recent-statuses{margin:20px 10px 15px}.recent-statuses-container{width:100%;margin:0}.show-private-statuses{margin:0 10px 20px}.status-container{margin:0 10px}.statuses{padding-right:10px;margin-left:8px}.user-page-header{padding:0;margin:7px 15px 15px 10px}.user-page-header-container .el-dropdown{width:95%;margin:0 15px 15px 10px}.user-profile-card{margin:0 10px;width:95%}.user-profile-card td{width:80px}.user-profile-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}@media only screen and (max-width:801px) and (min-width:481px){.recent-statuses{margin:20px 10px 15px 0}.recent-statuses-container{width:97%;margin:0 20px}.show-private-statuses{margin:0 10px 20px 0}.user-page-header{padding:0;margin:7px 15px 20px 20px}.user-profile-card{margin:0 20px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.user-profile-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-546f.692d1ab2.css b/priv/static/adminfe/chunk-546f.692d1ab2.css new file mode 100644 index 000000000..5fcb223d8 --- /dev/null +++ b/priv/static/adminfe/chunk-546f.692d1ab2.css @@ -0,0 +1 @@ +.copy-popover{width:330px}.copy-to-local-pack-button{margin-top:15px;float:right}.emoji-buttons{place-self:center;min-width:200px}.emoji-container-grid{display:grid;grid-template-columns:75px 1fr 1fr 200px;grid-column-gap:15px;margin-bottom:10px}.emoji-preview-img{max-width:100%;place-self:center}.emoji-info{place-self:center}.copy-pack-container{place-self:center stretch}.copy-pack-select{width:100%}.remote-emoji-container-grid{display:grid;grid-template-columns:75px 1fr 1fr 160px;grid-column-gap:15px;margin-bottom:10px}@media only screen and (max-width:480px){.emoji-container-flex{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;border:1px solid #dcdfe6;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:4px;padding:15px;margin:0 15px 15px 0}.emoji-info,.emoji-preview-img{margin-bottom:10px}.emoji-buttons{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;width:100%}.emoji-buttons button{padding:10px 5px;width:47%}}@media only screen and (max-width:801px) and (min-width:481px){.emoji-container-grid{grid-column-gap:10px}.emoji-buttons .el-button+.el-button{margin-left:5px}.remote-emoji-container-grid{grid-column-gap:10px}}.add-new-emoji{height:36px;font-size:14px;font-weight:700;color:#606266}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.upload-button{margin-left:10px}.upload-file-url{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.new-emoji-uploader-form label.el-form-item__label{padding:0}}.emoji-table-head{color:#909399;font-size:14px;font-weight:700}.download-archive{width:250px}.download-pack-button-container{width:265px}.download-pack-button-container .el-link,.download-pack-button-container .el-link span,.download-pack-button-container .el-link span .download-archive{width:inherit}.download-shared-pack{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.download-shared-pack-button{margin-left:10px}.el-collapse-item__content{padding-bottom:0}.el-collapse-item__header{height:36px;font-size:14px;font-weight:700;color:#606266}.emoji-pack-card{margin-top:5px}.emoji-pack-metadata .el-form-item{margin-bottom:10px}.files-pagination{margin:25px 0;text-align:center}.has-background .el-collapse-item__header{background:#f6f6f6}.no-background .el-collapse-item__header{background:#fff}.pack-button-container{margin:0 0 18px 120px}.save-pack-button-container{margin-bottom:8px;width:265px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.delete-pack-button{width:45%}.download-pack-button-container{width:100%}.download-shared-pack{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.download-shared-pack-button{margin-left:0;margin-top:10px;padding:10px}.pack-button-container{width:100%;margin:0 0 22px}.remote-pack-metadata .el-form-item__content{line-height:24px;margin-top:4px}.save-pack-button{width:54%}.save-pack-button-container{margin-bottom:8px;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.save-pack-button-container button{padding:10px 5px}.save-pack-button-container .el-button+.el-button{margin-left:3px}}.create-pack{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.create-pack-button{margin-left:10px}.emoji-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:0 15px 22px}.emoji-name-warning{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.emoji-packs-header-button-container{display:-webkit-box;display:-ms-flexbox;display:flex}.emoji-packs-form{margin-top:15px}.emoji-packs-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:10px 15px 15px}.emoji-packs-tabs{margin:0 15px 15px}.import-pack-button{margin-left:10px;width:30%;max-width:700px}h1{margin:0}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:22px}.pagination{margin:25px 0;text-align:center}.reboot-button{padding:10px;margin:0;width:145px}@media only screen and (min-width:1824px){.emoji-packs{max-width:1824px;margin:auto}}@media only screen and (max-width:480px){.create-pack{height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.create-pack-button{margin-left:0}.divider{margin:15px 0}.el-message{min-width:80%}.el-message-box{width:80%}.emoji-header-container{-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}.emoji-packs-form{margin:0 7px}.emoji-packs-form label{padding-right:8px}.emoji-packs-form .el-form-item{margin-bottom:15px}.emoji-packs-header{margin:15px}.emoji-packs-header-button-container{height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.emoji-packs-header-button-container .el-button+.el-button{margin:7px 0 0;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.import-pack-button{width:90%}.reload-emoji-button{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-565e.8c036a6e.css b/priv/static/adminfe/chunk-565e.8c036a6e.css deleted file mode 100644 index c126f246e..000000000 --- a/priv/static/adminfe/chunk-565e.8c036a6e.css +++ /dev/null @@ -1 +0,0 @@ -.image-upload-area .input-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.image-upload-area .input-file{z-index:100;position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;cursor:pointer}.image-upload-area .image-button-group{margin-top:20px}.image-upload-area .image-button-group .upload-button,.image-upload-area .image-upload-wrapper{position:relative}.image-upload-area .image-upload-wrapper .image-upload-overlay{border-radius:5px}.image-upload-area .image-upload-wrapper .image-upload-overlay,.image-upload-area .image-upload-wrapper .image-upload-overlay .caption{-webkit-transition:-webkit-box-shadow .1s;transition:-webkit-box-shadow .1s;transition:box-shadow .1s;transition:box-shadow .1s,-webkit-box-shadow .1s}.image-upload-area .image-upload-wrapper .image-upload-overlay .caption{visibility:hidden;position:absolute;top:0;bottom:0;right:0;left:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-weight:700;font-size:10px;text-transform:uppercase;color:#fff;z-index:9}.image-upload-area .image-upload-wrapper .image-upload-overlay .uploaded-image{border-radius:5px;-webkit-box-shadow:0 2px 10px 0 rgba(0,0,0,.1);box-shadow:0 2px 10px 0 rgba(0,0,0,.1)}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover{visibility:visible;cursor:pointer;border-radius:5px}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover .el-image__error{visibility:hidden}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover .caption{visibility:visible;-webkit-box-shadow:0 2px 10px 0 rgba(0,0,0,.1),inset 0 0 120px 25px rgba(0,0,0,.8);box-shadow:0 2px 10px 0 rgba(0,0,0,.1),inset 0 0 120px 25px rgba(0,0,0,.8);border-radius:5px}a{text-decoration:underline}.center-label label{text-align:center}.center-label label span{float:left}.code{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.delete-setting-button{margin-left:5px}.description-container{overflow-wrap:break-word}.description-container .el-form-item__content{line-height:20px}.divider{margin:0 0 18px}.divider.thick-line{height:2px}.docs-search-container{float:right;margin-right:30px}.editable-keyword-container{width:100%}.el-form-item .rate-limit{margin-right:0}.el-input-group__prepend{padding-left:10px;padding-right:10px}.el-tabs__header{z-index:2002}.email-address-input{width:50%;margin-right:10px}.esshd-list{margin:0}.expl,.expl>p{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.expl>p code,.expl code{display:inline;line-height:22px;font-size:13px;padding:2px 3px}.follow-relay{width:350px;margin-right:7px}.form-container{margin-bottom:80px}.grouped-settings-header{margin:0 0 14px}.highlight{background-color:#e6e6e6}.icons-button-container{width:100%;margin-bottom:10px}.icons-button-desc{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;margin-left:5px}.icon-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:95%}.icon-values-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px 10px 0}.icon-key-input{width:30%;margin-right:8px}.icon-minus-button{width:36px;height:36px}.icon-value-input{width:70%;margin-left:8px}.icons-container,.input-container{display:-webkit-box;display:-ms-flexbox;display:flex}.input-container{-webkit-box-align:start;-ms-flex-align:start;align-items:start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input-container .el-form-item{margin-right:30px;width:100%}.input-container .el-select,.keyword-container{width:100%}label{overflow:hidden;text-overflow:ellipsis}.label-font{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700}.limit-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl{margin-left:10px}.limit-input{width:47%;margin:0 0 5px 1%}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:18px}.mascot{margin-bottom:15px}.mascot-container{width:100%}.mascot-input{margin-bottom:7px}.mascot-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input{margin-right:10px}.multiple-select-container{width:100%}.name-input{width:30%;margin-right:8px}.nickname-input{width:50%}.no-top-margin{margin-top:0}.no-top-margin p{margin-right:30px}.pattern-input{width:20%;margin-right:8px}.proxy-url-input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.proxy-url-host-input{width:35%;margin-right:8px}.proxy-url-value-input{width:35%;margin-left:8px;margin-right:10px}.prune-options{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.prune-options .el-radio{margin-top:11px}.rate-limit .el-form-item__content{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.rate-limit-container{width:100%}.rate-limit-content{width:70%}.rate-limit-label{float:right}.rate-limit-label-container{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;width:30%;margin-right:10px}.reboot-button{width:145px;text-align:left;padding:10px;float:right;margin:0 30px 0 0}.reboot-button-container{width:100%;position:fixed;top:60px;right:0;z-index:2000}.relays-container{margin:0 15px}.replacement-input{width:80%;margin-left:8px;margin-right:10px}.sender-input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.scale-input{width:47%;margin:0 1% 5px 0}.setting-input{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.setting-label{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.settings-container{max-width:1824px;margin:auto}.settings-container .el-tabs{margin-top:20px}.settings-delete-button{margin-left:5px}.settings-docs-button{min-width:163px;text-align:left;padding:10px}.settings-header{margin:10px 15px 15px}.header-sidebar-opened{max-width:1585px}.header-sidebar-closed{max-width:1728px}.settings-header-container{height:87px}.settings-search-input{width:350px;margin-left:5px}.single-input{margin-right:10px}.socks5-checkbox{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;margin-left:10px}.socks5-checkbox-container{width:40%;height:36px;margin-right:5px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ssl-tls-opts{margin:36px 0 0}.submit-button{float:right;margin:0 30px 22px 0}.submit-button-container{width:100%;position:fixed;bottom:0;right:0;z-index:2000}.switch-input{height:36px}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input{width:70%;margin-left:8px;margin-right:10px}@media only screen and (min-width:1824px){.header-sidebar-closed{max-width:1772px}.header-sidebar-opened{max-width:1630px}.reboot-button-container{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}.reboot-sidebar-opened{max-width:1630px}.reboot-sidebar-closed{max-width:1772px}.sidebar-closed{max-width:1586px}.sidebar-opened{max-width:1442px}.submit-button-container{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}}@media only screen and (max-width:480px){.crontab,.crontab label{width:100%}.delete-setting-button{margin:4px 0 0 5px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 5px 7px 15px}.description>p code{display:inline;line-height:18px;padding:2px 3px;font-size:14px}.description-container{margin:0 15px 22px}.divider{margin:0 0 10px}.divider .thick-line{height:2px}.follow-relay{width:75%;margin-right:5px}.follow-relay input{width:100%}.follow-relay-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:0 5px}h1{font-size:24px}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container{width:100%}.input-container .el-form-item:first-child{margin:0;padding:0 15px 10px}.input-container .el-form-item.crontab-container:first-child{margin:0;padding:0}.input-container .el-form-item:first-child .mascot-form-item,.input-container .el-form-item:first-child .rate-limit{padding:0}.input-container .settings-delete-button{margin-top:4px;float:right}.input-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.label-with-margin{margin-left:15px}.limit-input{width:45%}.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.proxy-url-input{-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;margin-bottom:0}.proxy-url-host-input{width:100%;margin-bottom:5px}.proxy-url-value-input{width:100%;margin-left:0}.prune-options{height:80px}.prune-options,.rate-limit .el-form-item__content{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.rate-limit-content{width:100%}.rate-limit-label{float:left}.rate-limit-label-container{width:100%}.reboot-button{margin:0 15px 0 0}.reboot-button-container{top:57px}.scale-input{width:45%}.settings-header{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:inline-block;margin:10px 15px 15px}.docs-search-container{float:right}.settings-search-input{width:100%;margin-left:0}.settings-search-input-container{margin:0 15px 15px}.settings-menu{width:163px;margin-right:5px}.socks5-checkbox-container{width:100%}.submit-button{margin:0 15px 22px 0}.el-input__inner{padding:0 5px}.el-form-item__label:not(.no-top-margin){padding-bottom:5px;line-height:22px;margin-top:7px;width:100%;pointer-events:none}.el-form-item__label:not(.no-top-margin) span{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.el-form-item__label:not(.no-top-margin) button{pointer-events:auto}.el-message{min-width:80%}.el-message-box{width:80%}.el-select__tags{overflow:hidden}.expl,.expl>p{line-height:16px}.icon-key-input{width:40%;margin-right:4px}.icon-minus-button{width:28px;height:28px;margin-top:4px}.icon-values-container{margin:0 7px 7px 0}.icon-value-input{width:60%;margin-left:4px}.icons-button-container{line-height:24px}.line{margin-bottom:10px}.mascot-form-item .el-form-item__label:not(.no-top-margin){margin:0;padding:0}.mascot-container{margin-bottom:5px}.name-input{width:40%;margin-right:5px}p.expl{line-height:20px}.pattern-input{width:40%;margin-right:4px}.relays-container{margin:0 10px}.replacement-input{width:60%;margin-left:4px;margin-right:5px}.settings-header-container{height:45px}.value-input{width:60%;margin-left:5px;margin-right:8px}}@media only screen and (max-width:818px) and (min-width:481px){.delete-setting-button{margin:4px 0 0 10px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 15px 10px 0}.icon-minus-button{width:28px;height:28px;margin-top:4px}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container .el-form-item__label span{margin-left:10px}.input-row,.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.nav-container{height:36px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 30px 15px 15px}.rate-limit-content{width:65%}.rate-limit-label-container{width:35%}.settings-delete-button{float:right}.settings-header-container{height:36px}.settings-search-input{width:250px;margin:0 0 15px 15px}}a[data-v-82f78b3e]{text-decoration:underline}.center-label label[data-v-82f78b3e]{text-align:center}.center-label label span[data-v-82f78b3e]{float:left}.code[data-v-82f78b3e]{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.delete-setting-button[data-v-82f78b3e]{margin-left:5px}.description-container[data-v-82f78b3e]{overflow-wrap:break-word}.description-container .el-form-item__content[data-v-82f78b3e]{line-height:20px}.divider[data-v-82f78b3e]{margin:0 0 18px}.divider.thick-line[data-v-82f78b3e]{height:2px}.docs-search-container[data-v-82f78b3e]{float:right;margin-right:30px}.editable-keyword-container[data-v-82f78b3e]{width:100%}.el-form-item .rate-limit[data-v-82f78b3e]{margin-right:0}.el-input-group__prepend[data-v-82f78b3e]{padding-left:10px;padding-right:10px}.el-tabs__header[data-v-82f78b3e]{z-index:2002}.email-address-input[data-v-82f78b3e]{width:50%;margin-right:10px}.esshd-list[data-v-82f78b3e]{margin:0}.expl>p[data-v-82f78b3e],.expl[data-v-82f78b3e]{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.expl>p code[data-v-82f78b3e],.expl code[data-v-82f78b3e]{display:inline;line-height:22px;font-size:13px;padding:2px 3px}.follow-relay[data-v-82f78b3e]{width:350px;margin-right:7px}.form-container[data-v-82f78b3e]{margin-bottom:80px}.grouped-settings-header[data-v-82f78b3e]{margin:0 0 14px}.highlight[data-v-82f78b3e]{background-color:#e6e6e6}.icons-button-container[data-v-82f78b3e]{width:100%;margin-bottom:10px}.icons-button-desc[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;margin-left:5px}.icon-container[data-v-82f78b3e]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:95%}.icon-values-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px 10px 0}.icon-key-input[data-v-82f78b3e]{width:30%;margin-right:8px}.icon-minus-button[data-v-82f78b3e]{width:36px;height:36px}.icon-value-input[data-v-82f78b3e]{width:70%;margin-left:8px}.icons-container[data-v-82f78b3e],.input-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex}.input-container[data-v-82f78b3e]{-webkit-box-align:start;-ms-flex-align:start;align-items:start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input-container .el-form-item[data-v-82f78b3e]{margin-right:30px;width:100%}.input-container .el-select[data-v-82f78b3e],.keyword-container[data-v-82f78b3e]{width:100%}label[data-v-82f78b3e]{overflow:hidden;text-overflow:ellipsis}.label-font[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700}.limit-button-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl[data-v-82f78b3e]{margin-left:10px}.limit-input[data-v-82f78b3e]{width:47%;margin:0 0 5px 1%}.line[data-v-82f78b3e]{width:100%;height:0;border:1px solid #eee;margin-bottom:18px}.mascot[data-v-82f78b3e]{margin-bottom:15px}.mascot-container[data-v-82f78b3e]{width:100%}.mascot-input[data-v-82f78b3e]{margin-bottom:7px}.mascot-name-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input[data-v-82f78b3e]{margin-right:10px}.multiple-select-container[data-v-82f78b3e]{width:100%}.name-input[data-v-82f78b3e]{width:30%;margin-right:8px}.nickname-input[data-v-82f78b3e]{width:50%}.no-top-margin[data-v-82f78b3e]{margin-top:0}.no-top-margin p[data-v-82f78b3e]{margin-right:30px}.pattern-input[data-v-82f78b3e]{width:20%;margin-right:8px}.proxy-url-input[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.proxy-url-host-input[data-v-82f78b3e]{width:35%;margin-right:8px}.proxy-url-value-input[data-v-82f78b3e]{width:35%;margin-left:8px;margin-right:10px}.prune-options[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.prune-options .el-radio[data-v-82f78b3e]{margin-top:11px}.rate-limit .el-form-item__content[data-v-82f78b3e]{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.rate-limit-container[data-v-82f78b3e]{width:100%}.rate-limit-content[data-v-82f78b3e]{width:70%}.rate-limit-label[data-v-82f78b3e]{float:right}.rate-limit-label-container[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;width:30%;margin-right:10px}.reboot-button[data-v-82f78b3e]{width:145px;text-align:left;padding:10px;float:right;margin:0 30px 0 0}.reboot-button-container[data-v-82f78b3e]{width:100%;position:fixed;top:60px;right:0;z-index:2000}.relays-container[data-v-82f78b3e]{margin:0 15px}.replacement-input[data-v-82f78b3e]{width:80%;margin-left:8px;margin-right:10px}.sender-input[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.scale-input[data-v-82f78b3e]{width:47%;margin:0 1% 5px 0}.setting-input[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.setting-label[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.settings-container[data-v-82f78b3e]{max-width:1824px;margin:auto}.settings-container .el-tabs[data-v-82f78b3e]{margin-top:20px}.settings-delete-button[data-v-82f78b3e]{margin-left:5px}.settings-docs-button[data-v-82f78b3e]{min-width:163px;text-align:left;padding:10px}.settings-header[data-v-82f78b3e]{margin:10px 15px 15px}.header-sidebar-opened[data-v-82f78b3e]{max-width:1585px}.header-sidebar-closed[data-v-82f78b3e]{max-width:1728px}.settings-header-container[data-v-82f78b3e]{height:87px}.settings-search-input[data-v-82f78b3e]{width:350px;margin-left:5px}.single-input[data-v-82f78b3e]{margin-right:10px}.socks5-checkbox[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;margin-left:10px}.socks5-checkbox-container[data-v-82f78b3e]{width:40%;height:36px;margin-right:5px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ssl-tls-opts[data-v-82f78b3e]{margin:36px 0 0}.submit-button[data-v-82f78b3e]{float:right;margin:0 30px 22px 0}.submit-button-container[data-v-82f78b3e]{width:100%;position:fixed;bottom:0;right:0;z-index:2000}.switch-input[data-v-82f78b3e]{height:36px}.text[data-v-82f78b3e]{line-height:20px;margin-right:15px}.upload-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input[data-v-82f78b3e]{width:70%;margin-left:8px;margin-right:10px}@media only screen and (min-width:1824px){.header-sidebar-closed[data-v-82f78b3e]{max-width:1772px}.header-sidebar-opened[data-v-82f78b3e]{max-width:1630px}.reboot-button-container[data-v-82f78b3e]{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}.reboot-sidebar-opened[data-v-82f78b3e]{max-width:1630px}.reboot-sidebar-closed[data-v-82f78b3e]{max-width:1772px}.sidebar-closed[data-v-82f78b3e]{max-width:1586px}.sidebar-opened[data-v-82f78b3e]{max-width:1442px}.submit-button-container[data-v-82f78b3e]{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}}@media only screen and (max-width:480px){.crontab[data-v-82f78b3e],.crontab label[data-v-82f78b3e]{width:100%}.delete-setting-button[data-v-82f78b3e]{margin:4px 0 0 5px;height:28px}.delete-setting-button-container[data-v-82f78b3e]{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p[data-v-82f78b3e]{line-height:18px;margin:0 5px 7px 15px}.description>p code[data-v-82f78b3e]{display:inline;line-height:18px;padding:2px 3px;font-size:14px}.description-container[data-v-82f78b3e]{margin:0 15px 22px}.divider[data-v-82f78b3e]{margin:0 0 10px}.divider .thick-line[data-v-82f78b3e]{height:2px}.follow-relay[data-v-82f78b3e]{width:75%;margin-right:5px}.follow-relay input[data-v-82f78b3e]{width:100%}.follow-relay-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:0 5px}h1[data-v-82f78b3e]{font-size:24px}.input[data-v-82f78b3e]{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container[data-v-82f78b3e]{width:100%}.input-container .el-form-item[data-v-82f78b3e]:first-child{margin:0;padding:0 15px 10px}.input-container .el-form-item.crontab-container[data-v-82f78b3e]:first-child{margin:0;padding:0}.input-container .el-form-item:first-child .mascot-form-item[data-v-82f78b3e],.input-container .el-form-item:first-child .rate-limit[data-v-82f78b3e]{padding:0}.input-container .settings-delete-button[data-v-82f78b3e]{margin-top:4px;float:right}.input-row[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.label-with-margin[data-v-82f78b3e]{margin-left:15px}.limit-input[data-v-82f78b3e]{width:45%}.nav-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.proxy-url-input[data-v-82f78b3e]{-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;margin-bottom:0}.proxy-url-host-input[data-v-82f78b3e]{width:100%;margin-bottom:5px}.proxy-url-value-input[data-v-82f78b3e]{width:100%;margin-left:0}.prune-options[data-v-82f78b3e]{height:80px}.prune-options[data-v-82f78b3e],.rate-limit .el-form-item__content[data-v-82f78b3e]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.rate-limit-content[data-v-82f78b3e]{width:100%}.rate-limit-label[data-v-82f78b3e]{float:left}.rate-limit-label-container[data-v-82f78b3e]{width:100%}.reboot-button[data-v-82f78b3e]{margin:0 15px 0 0}.reboot-button-container[data-v-82f78b3e]{top:57px}.scale-input[data-v-82f78b3e]{width:45%}.settings-header[data-v-82f78b3e]{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:inline-block;margin:10px 15px 15px}.docs-search-container[data-v-82f78b3e]{float:right}.settings-search-input[data-v-82f78b3e]{width:100%;margin-left:0}.settings-search-input-container[data-v-82f78b3e]{margin:0 15px 15px}.settings-menu[data-v-82f78b3e]{width:163px;margin-right:5px}.socks5-checkbox-container[data-v-82f78b3e]{width:100%}.submit-button[data-v-82f78b3e]{margin:0 15px 22px 0}.el-input__inner[data-v-82f78b3e]{padding:0 5px}.el-form-item__label[data-v-82f78b3e]:not(.no-top-margin){padding-bottom:5px;line-height:22px;margin-top:7px;width:100%;pointer-events:none}.el-form-item__label:not(.no-top-margin) span[data-v-82f78b3e]{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.el-form-item__label:not(.no-top-margin) button[data-v-82f78b3e]{pointer-events:auto}.el-message[data-v-82f78b3e]{min-width:80%}.el-message-box[data-v-82f78b3e]{width:80%}.el-select__tags[data-v-82f78b3e]{overflow:hidden}.expl>p[data-v-82f78b3e],.expl[data-v-82f78b3e]{line-height:16px}.icon-key-input[data-v-82f78b3e]{width:40%;margin-right:4px}.icon-minus-button[data-v-82f78b3e]{width:28px;height:28px;margin-top:4px}.icon-values-container[data-v-82f78b3e]{margin:0 7px 7px 0}.icon-value-input[data-v-82f78b3e]{width:60%;margin-left:4px}.icons-button-container[data-v-82f78b3e]{line-height:24px}.line[data-v-82f78b3e]{margin-bottom:10px}.mascot-form-item .el-form-item__label[data-v-82f78b3e]:not(.no-top-margin){margin:0;padding:0}.mascot-container[data-v-82f78b3e]{margin-bottom:5px}.name-input[data-v-82f78b3e]{width:40%;margin-right:5px}p.expl[data-v-82f78b3e]{line-height:20px}.pattern-input[data-v-82f78b3e]{width:40%;margin-right:4px}.relays-container[data-v-82f78b3e]{margin:0 10px}.replacement-input[data-v-82f78b3e]{width:60%;margin-left:4px;margin-right:5px}.settings-header-container[data-v-82f78b3e]{height:45px}.value-input[data-v-82f78b3e]{width:60%;margin-left:5px;margin-right:8px}}@media only screen and (max-width:818px) and (min-width:481px){.delete-setting-button[data-v-82f78b3e]{margin:4px 0 0 10px;height:28px}.delete-setting-button-container[data-v-82f78b3e]{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p[data-v-82f78b3e]{line-height:18px;margin:0 15px 10px 0}.icon-minus-button[data-v-82f78b3e]{width:28px;height:28px;margin-top:4px}.input[data-v-82f78b3e]{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container .el-form-item__label span[data-v-82f78b3e]{margin-left:10px}.input-row[data-v-82f78b3e],.nav-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.nav-container[data-v-82f78b3e]{height:36px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 30px 15px 15px}.rate-limit-content[data-v-82f78b3e]{width:65%}.rate-limit-label-container[data-v-82f78b3e]{width:35%}.settings-delete-button[data-v-82f78b3e]{float:right}.settings-header-container[data-v-82f78b3e]{height:36px}.settings-search-input[data-v-82f78b3e]{width:250px;margin:0 0 15px 15px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-60a9.7b5b9559.css b/priv/static/adminfe/chunk-60a9.7b5b9559.css deleted file mode 100644 index d45d79f4c..000000000 --- a/priv/static/adminfe/chunk-60a9.7b5b9559.css +++ /dev/null @@ -1 +0,0 @@ -h1[data-v-039e6dbb]{margin:0}.expl[data-v-039e6dbb]{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.banned-urls-table[data-v-039e6dbb]{margin-top:15px;margin-bottom:15px}.evict-button[data-v-039e6dbb]{margin-left:15px}.media-proxy-cache-header[data-v-039e6dbb]{margin-left:15px;margin-top:22px;font-weight:500}.media-proxy-cache-header-container[data-v-039e6dbb]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:10px 15px}.pagination[data-v-039e6dbb]{margin:25px 0;text-align:center}.remove-url-button[data-v-039e6dbb]{width:150px}.url-input[data-v-039e6dbb]{margin-right:15px}.url-input-container[data-v-039e6dbb]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;margin:15px 15px 5px}.url-input-expl[data-v-039e6dbb]{margin-left:15px}@media only screen and (max-width:480px){.url-input[data-v-039e6dbb]{width:100%;margin-bottom:5px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-654e.b2e16b59.css b/priv/static/adminfe/chunk-654d.94689c39.css similarity index 100% rename from priv/static/adminfe/chunk-654e.b2e16b59.css rename to priv/static/adminfe/chunk-654d.94689c39.css diff --git a/priv/static/adminfe/chunk-68ea.81e11186.css b/priv/static/adminfe/chunk-68ea.81e11186.css deleted file mode 100644 index 30bf7de23..000000000 --- a/priv/static/adminfe/chunk-68ea.81e11186.css +++ /dev/null @@ -1 +0,0 @@ -.wscn-http404-container[data-v-1d6b2d2a]{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-1d6b2d2a]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-1d6b2d2a]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-1d6b2d2a]{width:100%}.wscn-http404 .pic-404__child[data-v-1d6b2d2a]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-1d6b2d2a]{width:80px;top:17px;left:220px;opacity:0;-webkit-animation-name:cloudLeft-data-v-1d6b2d2a;animation-name:cloudLeft-data-v-1d6b2d2a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-1d6b2d2a]{width:46px;top:10px;left:420px;opacity:0;-webkit-animation-name:cloudMid-data-v-1d6b2d2a;animation-name:cloudMid-data-v-1d6b2d2a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1.2s;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-1d6b2d2a]{width:62px;top:100px;left:500px;opacity:0;-webkit-animation-name:cloudRight-data-v-1d6b2d2a;animation-name:cloudRight-data-v-1d6b2d2a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes cloudLeft-data-v-1d6b2d2a{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudLeft-data-v-1d6b2d2a{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@-webkit-keyframes cloudMid-data-v-1d6b2d2a{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudMid-data-v-1d6b2d2a{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@-webkit-keyframes cloudRight-data-v-1d6b2d2a{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}@keyframes cloudRight-data-v-1d6b2d2a{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-1d6b2d2a]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-1d6b2d2a]{font-size:32px;line-height:40px;color:#1482f0;margin-bottom:20px;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-1d6b2d2a],.wscn-http404 .bullshit__oops[data-v-1d6b2d2a]{font-weight:700;opacity:0;-webkit-animation-name:slideUp-data-v-1d6b2d2a;animation-name:slideUp-data-v-1d6b2d2a;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__headline[data-v-1d6b2d2a]{font-size:20px;line-height:24px;color:#222;margin-bottom:10px;-webkit-animation-delay:.1s;animation-delay:.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-1d6b2d2a]{font-size:13px;line-height:21px;color:grey;margin-bottom:30px;-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-1d6b2d2a],.wscn-http404 .bullshit__return-home[data-v-1d6b2d2a]{opacity:0;-webkit-animation-name:slideUp-data-v-1d6b2d2a;animation-name:slideUp-data-v-1d6b2d2a;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__return-home[data-v-1d6b2d2a]{display:block;float:left;width:165px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;font-size:14px;line-height:36px;cursor:pointer;-webkit-animation-delay:.3s;animation-delay:.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes slideUp-data-v-1d6b2d2a{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes slideUp-data-v-1d6b2d2a{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-68ea.7633295f.css b/priv/static/adminfe/chunk-68ea9.dac85813.css similarity index 100% rename from priv/static/adminfe/chunk-68ea.7633295f.css rename to priv/static/adminfe/chunk-68ea9.dac85813.css diff --git a/priv/static/adminfe/chunk-6e81.0e9e6d27.css b/priv/static/adminfe/chunk-6e81.1c0f2da2.css similarity index 100% rename from priv/static/adminfe/chunk-6e81.0e9e6d27.css rename to priv/static/adminfe/chunk-6e81.1c0f2da2.css diff --git a/priv/static/adminfe/chunk-6e81.7e5babfc.css b/priv/static/adminfe/chunk-6e81.7e5babfc.css deleted file mode 100644 index da819ca09..000000000 --- a/priv/static/adminfe/chunk-6e81.7e5babfc.css +++ /dev/null @@ -1 +0,0 @@ -.errPage-container[data-v-ab9be52c]{width:800px;max-width:100%;margin:100px auto}.errPage-container .pan-back-btn[data-v-ab9be52c]{background:#008489;color:#fff;border:none!important}.errPage-container .pan-gif[data-v-ab9be52c]{margin:0 auto;display:block}.errPage-container .pan-img[data-v-ab9be52c]{display:block;margin:0 auto;width:100%}.errPage-container .text-jumbo[data-v-ab9be52c]{font-size:60px;font-weight:700;color:#484848}.errPage-container .list-unstyled[data-v-ab9be52c]{font-size:14px}.errPage-container .list-unstyled li[data-v-ab9be52c]{padding-bottom:5px}.errPage-container .list-unstyled a[data-v-ab9be52c]{color:#008489;text-decoration:none}.errPage-container .list-unstyled a[data-v-ab9be52c]:hover{text-decoration:underline} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-6e8c.ef26acfd.css b/priv/static/adminfe/chunk-6e8c.ef26acfd.css deleted file mode 100644 index 76f698880..000000000 --- a/priv/static/adminfe/chunk-6e8c.ef26acfd.css +++ /dev/null @@ -1 +0,0 @@ -.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided.actor-type-dropdown:before{margin:0;height:0}.el-dropdown-menu--small .actor-type-dropdown{padding:0}.actor-type-select{width:100%}.actor-type-select input{border-color:transparent;color:#606266}.actor-type-select .el-input__inner:hover{border-color:transparent;background-color:#ecf5ff}.actor-type-select .el-input.is-focus{border-color:transparent}.actor-type-select .el-input__suffix-inner{pointer-events:none}.actor-type-select .el-input.is-active .el-input__inner,.actor-type-select .el-input.is-focus .el-input__inner,.actor-type-select .el-input__inner:focus,.actor-type-select .el-select .el-input__inner:focus{border-color:transparent}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.moderate-user-button{width:100%}}.security-settings-container{display:-webkit-box;display:-ms-flexbox;display:flex}.security-settings-container label{width:15%;height:36px}.security-settings-modal .el-dialog__body{padding-top:10px}.security-settings-modal .el-form-item,.security-settings-modal .password-alert{margin-bottom:15px}.security-settings-modal .password-input{margin-bottom:0}.security-settings-submit-button{float:right}@media (max-width:800px){.security-settings-modal .el-dialog{width:90%}}.security-settings-modal .el-alert .el-alert__description{word-break:break-word;font-size:1em}.security-settings-modal .form-text{display:block;margin-top:.25rem;color:#909399}header{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;margin:22px 0;padding-left:15px}header h1{margin:0 0 0 10px}table{margin:10px 0 0 15px}table .name-col{width:150px}.avatar-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.avatar-name-container .el-icon-top-right{font-size:2em;line-height:36px;color:#606266}.invalid{color:grey}.el-table--border:after,.el-table--group:after,.el-table:before{background-color:transparent}.image{width:20%}.image img{width:100%}.invalid-user-tag{font-size:14px;width:inherit;height:auto;text-align:center;word-wrap:break-word;white-space:normal}.left-header-container{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.no-statuses{margin-left:28px;color:#606266}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.poll ul{list-style-type:none;padding:0;width:30%}.reboot-button{padding:10px;margin-left:10px}.recent-statuses-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:67%}.recent-statuses-header{margin-top:10px}.reset-password-link{text-decoration:underline}.security-setting-button{margin-top:20px;width:100%}.statuses{padding:0 20px 0 0}.show-private{width:200px;text-align:left;line-height:67px;margin-right:20px}.show-private-statuses{margin-left:28px;margin-bottom:20px}.recent-statuses{margin-left:28px}.user-page-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:22px 15px 22px 20px;padding:0;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.user-page-header h1{display:inline}.user-profile-card{margin:0 20px;width:30%;min-width:300px;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.user-profile-container{display:-webkit-box;display:-ms-flexbox;display:flex}.user-profile-table{margin:0;width:inherit}.user-profile-tag{margin:0 4px 4px 0}.reason-label{color:#878d99;font-weight:700;margin:5px 0}@media only screen and (max-width:480px){.avatar-name-container{margin-bottom:10px}.el-timeline-item__wrapper{padding-left:18px}.password-reset-token-dialog{width:85%}.recent-statuses{margin:20px 10px 15px}.recent-statuses-container{width:100%;margin:0}.show-private-statuses{margin:0 10px 20px}.status-container{margin:0 10px}.statuses{padding-right:10px;margin-left:8px}.user-page-header{padding:0;margin:7px 15px 15px 10px}.user-page-header-container .el-dropdown{width:95%;margin:0 15px 15px 10px}.user-profile-card{margin:0 10px;width:95%}.user-profile-card td{width:80px}.user-profile-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}@media only screen and (max-width:801px) and (min-width:481px){.recent-statuses{margin:20px 10px 15px 0}.recent-statuses-container{width:97%;margin:0 20px}.show-private-statuses{margin:0 10px 20px 0}.user-page-header{padding:0;margin:7px 15px 20px 20px}.user-profile-card{margin:0 20px;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.user-profile-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-7503.cc089ee4.css b/priv/static/adminfe/chunk-7503.cc089ee4.css deleted file mode 100644 index cc1e824b8..000000000 --- a/priv/static/adminfe/chunk-7503.cc089ee4.css +++ /dev/null @@ -1 +0,0 @@ -.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided.actor-type-dropdown:before{margin:0;height:0}.el-dropdown-menu--small .actor-type-dropdown{padding:0}.actor-type-select{width:100%}.actor-type-select input{border-color:transparent;color:#606266}.actor-type-select .el-input__inner:hover{border-color:transparent;background-color:#ecf5ff}.actor-type-select .el-input.is-focus{border-color:transparent}.actor-type-select .el-input__suffix-inner{pointer-events:none}.actor-type-select .el-input.is-active .el-input__inner,.actor-type-select .el-input.is-focus .el-input__inner,.actor-type-select .el-input__inner:focus,.actor-type-select .el-select .el-input__inner:focus{border-color:transparent}.moderate-user-button{text-align:left;width:350px;padding:10px}.moderate-user-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.moderate-user-button{width:100%}}.avatar-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.avatar-name-container .el-icon-top-right{font-size:2em;line-height:36px;color:#606266}.avatar-name-header{display:-webkit-box;display:-ms-flexbox;display:flex;height:40px;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.invalid{color:grey}.no-statuses{margin-left:28px;color:#606266}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.reboot-button{padding:10px;margin-left:6px}.recent-statuses-container-show{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.recent-statuses-container-show .el-timeline-item,.recent-statuses-container-show .recent-statuses{margin-left:20px}.recent-statuses-container-show .show-private-statuses{margin-left:20px;margin-bottom:20px}.reset-password-link{text-decoration:underline}.router-link{text-decoration:none}.status-container{margin:0 15px 0 20px}.statuses{padding:0 20px 0 0}.user-page-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:22px 15px 22px 20px;padding:0;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.user-page-header h1{display:inline;margin:0 0 0 10px}@media only screen and (min-width:1824px){.status-show-container{max-width:1824px;margin:auto}}@media only screen and (max-width:480px){.avatar-name-container{margin-bottom:10px}.el-timeline-item__wrapper{padding-left:18px}.left-header-container{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.password-reset-token-dialog{width:85%}.recent-statuses{margin:20px 10px 15px}.recent-statuses-container-show{width:100%;margin:0 0 0 10px}.recent-statuses-container-show .el-timeline-item,.recent-statuses-container-show .recent-statuses{margin-left:0}.recent-statuses-container-show .show-private-statuses{margin:0 10px 20px 0}.status-card .el-card__body{padding:15px}.status-container{margin:0 10px}.statuses{padding-right:10px;margin-left:0}.statuses .el-timeline-item__wrapper{margin-right:10px}.user-page-header{padding:0;margin:7px 15px 5px 10px}.status-page-header-container{width:100%}.status-page-header-container .el-dropdown{width:-webkit-fill-available;width:-moz-available;width:stretch;margin:0 10px 15px}}@media only screen and (max-width:801px) and (min-width:481px){.recent-statuses-container-show{width:97%;margin:0 20px}.recent-statuses-container-show .el-timeline-item{margin-left:2px}.recent-statuses-container-show .recent-statuses{margin:20px 10px 15px 0}.recent-statuses-container-show .show-private-statuses,.show-private-statuses{margin:0 10px 20px 0}.user-page-header{padding:0;margin:7px 15px 20px 20px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-0778.29be65e2.css b/priv/static/adminfe/chunk-7c6b.365cbeda.css similarity index 100% rename from priv/static/adminfe/chunk-0778.29be65e2.css rename to priv/static/adminfe/chunk-7c6b.365cbeda.css diff --git a/priv/static/adminfe/chunk-7c6b.b529c720.css b/priv/static/adminfe/chunk-7c6b.b529c720.css deleted file mode 100644 index 9d730019a..000000000 --- a/priv/static/adminfe/chunk-7c6b.b529c720.css +++ /dev/null @@ -1 +0,0 @@ -.invites-container .actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.invites-container .create-invite-token{text-align:left;width:350px;padding:10px}.invites-container .create-new-token-dialog{width:50%}.invites-container .create-new-token-dialog a{margin-bottom:3px}.invites-container .create-new-token-dialog .el-card__body{padding:10px 20px}.invites-container .el-dialog__body{padding:5px 20px 0}.invites-container h1{margin:0}.invites-container .icon{margin-right:5px}.invites-container .invite-token-table{width:100%;margin:0 15px}.invites-container .invite-via-email{text-align:left;width:350px;padding:10px}.invites-container .invite-via-email-dialog{width:50%}.invites-container .invites-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:10px 15px}.invites-container .info{color:#666;font-size:13px;line-height:22px;margin:0 0 10px}.invites-container .new-token-card .el-form-item{margin:0}.invites-container .reboot-button{padding:10px;margin:0;width:145px}@media only screen and (max-width:480px){.invites-container .actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 10px 7px}.invites-container .cell{padding:0}.invites-container .create-invite-token{width:100%}.invites-container .create-new-token-dialog{width:85%}.invites-container .el-date-editor{width:150px}.invites-container .el-dialog__body{padding:5px 15px 0}.invites-container h1{margin:0}.invites-container .invite-token-table{width:100%;margin:0 5px;font-size:12px;font-weight:500}.invites-container .invite-via-email{width:100%;margin:10px 0 0}.invites-container .invite-via-email-dialog{width:85%}.invites-container .invites-header-container{margin:0 10px}.invites-container .info{margin:0 0 10px 5px}.invites-container th .cell{padding:0}.create-invite-token,.invite-via-email{width:100%}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-40a4.665332db.css b/priv/static/adminfe/chunk-850d.cc4f0ac6.css similarity index 84% rename from priv/static/adminfe/chunk-40a4.665332db.css rename to priv/static/adminfe/chunk-850d.cc4f0ac6.css index 83fefcb55..1cb2ead63 100644 --- a/priv/static/adminfe/chunk-40a4.665332db.css +++ b/priv/static/adminfe/chunk-850d.cc4f0ac6.css @@ -1 +1 @@ -a{text-decoration:underline}.note-header{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;height:40px}.note-actor{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.note-actor-name{margin:0;height:28px}.note-avatar-img{width:15px;height:15px;margin-right:5px}.note-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.note-card{margin-bottom:15px}.note-content,.note-header{font-size:15px}.note-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:28px;font-weight:500}@media only screen and (max-width:480px){.el-card__header{padding:10px 17px}.note-header{height:65px}.note-actor{margin-bottom:5px}.note-header{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-align:start;-ms-flex-align:start;align-items:flex-start}}h4{margin:0;height:17px}.account{line-height:26px;font-size:13px;color:#606266}.account:hover{text-decoration:underline}.avatar-img{vertical-align:bottom;width:15px;height:15px}.divider{margin:15px 0}.deactivated{color:grey}.el-card__body{padding:17px}.el-card__header{background-color:#fafafa;padding:10px 20px}.el-collapse{border-bottom:none}.el-collapse-item__header{height:46px;font-size:14px}.el-collapse-item__content{padding-bottom:7px}.el-icon-arrow-right{margin-right:6px}.id{color:grey;margin-top:6px}.line{width:100%;height:0;border:.5px solid #ebeef5;margin:15px 0}.new-note p{font-size:14px;font-weight:500;height:17px;margin:13px 0 7px}.note{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.1);box-shadow:0 2px 5px 0 rgba(0,0,0,.1);margin-bottom:10px}.no-notes{font-style:italic;color:grey}.report .report-header-container{height:40px}.report-account,.report .report-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.report-account{-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2}.report-account-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.report-account-name{font-size:15px;font-weight:500}.report-row-key{font-size:14px;font-weight:500;padding-right:5px}.report-title{margin:0}.report-note-form{margin:15px 0 0}.report-post-note{margin:5px 0 0;text-align:right}.reports-pagination{margin:25px 0;text-align:center}.reports-timeline{margin:30px 45px 45px 19px;padding:0}.router-link{text-decoration:none}.reported-statuses{margin-top:15px}.submit-button{display:block;margin:7px 0 17px auto}.timestamp{margin:0;font-style:italic;color:grey}@media only screen and (max-width:480px){.report .report-header-container{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:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;height:auto}.report .id{margin:6px 0 0}.report .report-actions-button,.report .report-tag{margin:3px 0 6px}.report .title-container{margin-bottom:7px}.reports-timeline{margin:20px 10px}.reports-timeline .el-timeline-item__wrapper{padding-left:20px}}.select-field[data-v-5ab7c15a]{width:350px}@media only screen and (max-width:480px){.select-field[data-v-5ab7c15a]{width:100%;margin-bottom:5px}}@media only screen and (max-width:801px) and (min-width:481px){.select-field[data-v-5ab7c15a]{width:50%}}.reports-container .reboot-button[data-v-fa601560]{padding:10px;margin:0;width:145px}.reports-container .reports-filter-container[data-v-fa601560]{margin:15px 45px 22px 15px;padding-bottom:0}.reports-container .reports-filter-container[data-v-fa601560],.reports-container .reports-header-container[data-v-fa601560]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.reports-container .reports-header-container[data-v-fa601560]{margin:10px 15px}.reports-container h1[data-v-fa601560]{margin:0}.reports-container .no-reports-message[data-v-fa601560]{color:grey;margin-left:19px}.reports-container .report-count[data-v-fa601560]{color:grey;font-size:28px}@media only screen and (max-width:480px){.reports-container h1[data-v-fa601560]{margin:7px 10px 15px}.reports-container .reboot-button[data-v-fa601560]{margin:0 0 5px 10px;width:145px}.reports-container .report-count[data-v-fa601560]{font-size:22px}.reports-container .reports-filter-container[data-v-fa601560]{margin:0 10px}} \ No newline at end of file +a{text-decoration:underline}.note-header{-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline;height:40px}.note-actor{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.note-actor-name{margin:0;height:28px}.note-avatar-img{width:15px;height:15px;margin-right:5px}.note-body{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.note-card{margin-bottom:15px}.note-content,.note-header{font-size:15px}.note-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:28px;font-weight:500}@media only screen and (max-width:480px){.el-card__header{padding:10px 17px}.note-header{height:65px}.note-actor{margin-bottom:5px}.note-header{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-align:start;-ms-flex-align:start;align-items:flex-start}}h4{margin:0;height:17px}.account{line-height:26px;font-size:13px;color:#606266}.account:hover{text-decoration:underline}.avatar-img{vertical-align:bottom;width:15px;height:15px}.divider{margin:15px 0}.deactivated{color:grey}.el-card__body{padding:17px}.el-card__header{background-color:#fafafa;padding:10px 20px}.el-collapse{border-bottom:none}.el-collapse-item__header{height:46px;font-size:14px}.el-collapse-item__content{padding-bottom:7px}.el-icon-arrow-right{margin-right:6px}.id{color:grey;margin-top:6px}.line{width:100%;height:0;border:.5px solid #ebeef5;margin:15px 0}.new-note p{font-size:14px;font-weight:500;height:17px;margin:13px 0 7px}.note{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,.1);box-shadow:0 2px 5px 0 rgba(0,0,0,.1);margin-bottom:10px}.no-notes{font-style:italic;color:grey}.report .report-header-container{height:40px}.report-account,.report .report-header-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.report-account{-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2}.report-account-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.report-account-name{font-size:15px;font-weight:500}.report-row-key{font-size:14px;font-weight:500;padding-right:5px}.report-title{margin:0}.report-note-form{margin:15px 0 0}.report-post-note{margin:5px 0 0;text-align:right}.reports-pagination{margin:25px 0;text-align:center}.reports-timeline{margin:30px 45px 45px 19px;padding:0}.router-link{text-decoration:none}.reported-statuses{margin-top:15px}.submit-button{display:block;margin:7px 0 17px auto}.timestamp{margin:0;font-style:italic;color:grey}@media only screen and (max-width:480px){.report .report-header-container{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:start;-ms-flex-pack:start;justify-content:flex-start;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;height:auto}.report .id{margin:6px 0 0}.report .report-actions-button,.report .report-tag{margin:3px 0 6px}.report .title-container{margin-bottom:7px}.reports-timeline{margin:20px 10px}.reports-timeline .el-timeline-item__wrapper{padding-left:20px}}.select-field[data-v-5ab7c15a]{width:350px}@media only screen and (max-width:480px){.select-field[data-v-5ab7c15a]{width:100%;margin-bottom:5px}}@media only screen and (max-width:801px) and (min-width:481px){.select-field[data-v-5ab7c15a]{width:50%}}.reports-container .reboot-button[data-v-6ac87f34]{padding:10px;margin:0;width:145px}.reports-container .reports-filter-container[data-v-6ac87f34]{margin:15px 45px 22px 15px;padding-bottom:0}.reports-container .reports-filter-container[data-v-6ac87f34],.reports-container .reports-header-container[data-v-6ac87f34]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.reports-container .reports-header-container[data-v-6ac87f34]{margin:10px 15px}.reports-container h1[data-v-6ac87f34]{margin:0}.reports-container .no-reports-message[data-v-6ac87f34]{color:grey;margin-left:19px}.reports-container .report-count[data-v-6ac87f34]{color:grey;font-size:28px}@media only screen and (max-width:480px){.reports-container h1[data-v-6ac87f34]{margin:7px 10px 15px}.reports-container .reboot-button[data-v-6ac87f34]{margin:0 0 5px 10px;width:145px}.reports-container .report-count[data-v-6ac87f34]{font-size:22px}.reports-container .reports-filter-container[data-v-6ac87f34]{margin:0 10px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-9043.3f527a93.css b/priv/static/adminfe/chunk-9043.3f527a93.css deleted file mode 100644 index d3b7604aa..000000000 --- a/priv/static/adminfe/chunk-9043.3f527a93.css +++ /dev/null @@ -1 +0,0 @@ -.copy-popover{width:330px}.copy-to-local-pack-button{margin-top:15px;float:right}.emoji-buttons{place-self:center;min-width:200px}.emoji-container-grid{display:grid;grid-template-columns:75px auto auto 200px;grid-column-gap:15px;margin-bottom:10px}.emoji-preview-img{max-width:100%;place-self:center}.emoji-info{place-self:center}.copy-pack-container{place-self:center stretch}.copy-pack-select{width:100%}.remote-emoji-container-grid{display:grid;grid-template-columns:75px auto auto 160px;grid-column-gap:15px;margin-bottom:10px}@media only screen and (max-width:480px){.emoji-container-flex{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;border:1px solid #dcdfe6;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:4px;padding:15px;margin:0 15px 15px 0}.emoji-info,.emoji-preview-img{margin-bottom:10px}.emoji-buttons{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;width:100%}.emoji-buttons button{padding:10px 5px;width:47%}}@media only screen and (max-width:801px) and (min-width:481px){.emoji-container-grid{grid-column-gap:10px}.emoji-buttons .el-button+.el-button{margin-left:5px}.remote-emoji-container-grid{grid-column-gap:10px}}.add-new-emoji{height:36px;font-size:14px;font-weight:700;color:#606266}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.upload-button{margin-left:10px}.upload-file-url{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.new-emoji-uploader-form label.el-form-item__label{padding:0}}.files-pagination{margin:25px 0;text-align:center}.download-archive{width:250px}.download-pack-button-container{width:265px}.download-pack-button-container .el-link,.download-pack-button-container .el-link span,.download-pack-button-container .el-link span .download-archive{width:inherit}.download-shared-pack{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.download-shared-pack-button{margin-left:10px}.el-collapse-item__content{padding-bottom:0}.el-collapse-item__header{height:36px;font-size:14px;font-weight:700;color:#606266}.emoji-pack-card{margin-top:5px}.emoji-pack-metadata .el-form-item{margin-bottom:10px}.has-background .el-collapse-item__header{background:#f6f6f6}.no-background .el-collapse-item__header{background:#fff}.pack-button-container{margin:0 0 18px 120px}.save-pack-button-container{margin-bottom:8px;width:265px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.delete-pack-button{width:45%}.download-pack-button-container{width:100%}.download-shared-pack{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.download-shared-pack-button{margin-left:0;margin-top:10px;padding:10px}.pack-button-container{width:100%;margin:0 0 22px}.remote-pack-metadata .el-form-item__content{line-height:24px;margin-top:4px}.save-pack-button{width:54%}.save-pack-button-container{margin-bottom:8px;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.save-pack-button-container button{padding:10px 5px}.save-pack-button-container .el-button+.el-button{margin-left:3px}}.create-pack{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.create-pack-button{margin-left:10px}.emoji-header-container{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:0 15px 22px}.emoji-header-container,.emoji-packs-header-button-container{display:-webkit-box;display:-ms-flexbox;display:flex}.emoji-packs-form{margin-top:15px}.emoji-packs-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:10px 15px 15px}.emoji-packs-tabs{margin:0 15px 15px}.import-pack-button{margin-left:10px;width:30%;max-width:700px}h1{margin:0}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:22px}.pagination{margin:25px 0;text-align:center}.reboot-button{padding:10px;margin:0;width:145px}@media only screen and (min-width:1824px){.emoji-packs{max-width:1824px;margin:auto}}@media only screen and (max-width:480px){.create-pack{height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.create-pack-button{margin-left:0}.divider{margin:15px 0}.el-message{min-width:80%}.el-message-box{width:80%}.emoji-header-container{-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}.emoji-packs-form{margin:0 7px}.emoji-packs-form label{padding-right:8px}.emoji-packs-form .el-form-item{margin-bottom:15px}.emoji-packs-header{margin:15px}.emoji-packs-header-button-container{height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.emoji-packs-header-button-container .el-button+.el-button{margin:7px 0 0;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.import-pack-button{width:90%}.reload-emoji-button{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-97e2.9f9fab0f.css b/priv/static/adminfe/chunk-97e2.9f9fab0f.css deleted file mode 100644 index d3b7604aa..000000000 --- a/priv/static/adminfe/chunk-97e2.9f9fab0f.css +++ /dev/null @@ -1 +0,0 @@ -.copy-popover{width:330px}.copy-to-local-pack-button{margin-top:15px;float:right}.emoji-buttons{place-self:center;min-width:200px}.emoji-container-grid{display:grid;grid-template-columns:75px auto auto 200px;grid-column-gap:15px;margin-bottom:10px}.emoji-preview-img{max-width:100%;place-self:center}.emoji-info{place-self:center}.copy-pack-container{place-self:center stretch}.copy-pack-select{width:100%}.remote-emoji-container-grid{display:grid;grid-template-columns:75px auto auto 160px;grid-column-gap:15px;margin-bottom:10px}@media only screen and (max-width:480px){.emoji-container-flex{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;border:1px solid #dcdfe6;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:4px;padding:15px;margin:0 15px 15px 0}.emoji-info,.emoji-preview-img{margin-bottom:10px}.emoji-buttons{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;width:100%}.emoji-buttons button{padding:10px 5px;width:47%}}@media only screen and (max-width:801px) and (min-width:481px){.emoji-container-grid{grid-column-gap:10px}.emoji-buttons .el-button+.el-button{margin-left:5px}.remote-emoji-container-grid{grid-column-gap:10px}}.add-new-emoji{height:36px;font-size:14px;font-weight:700;color:#606266}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.upload-button{margin-left:10px}.upload-file-url{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.new-emoji-uploader-form label.el-form-item__label{padding:0}}.files-pagination{margin:25px 0;text-align:center}.download-archive{width:250px}.download-pack-button-container{width:265px}.download-pack-button-container .el-link,.download-pack-button-container .el-link span,.download-pack-button-container .el-link span .download-archive{width:inherit}.download-shared-pack{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.download-shared-pack-button{margin-left:10px}.el-collapse-item__content{padding-bottom:0}.el-collapse-item__header{height:36px;font-size:14px;font-weight:700;color:#606266}.emoji-pack-card{margin-top:5px}.emoji-pack-metadata .el-form-item{margin-bottom:10px}.has-background .el-collapse-item__header{background:#f6f6f6}.no-background .el-collapse-item__header{background:#fff}.pack-button-container{margin:0 0 18px 120px}.save-pack-button-container{margin-bottom:8px;width:265px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}@media only screen and (max-width:480px){.delete-pack-button{width:45%}.download-pack-button-container{width:100%}.download-shared-pack{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.download-shared-pack-button{margin-left:0;margin-top:10px;padding:10px}.pack-button-container{width:100%;margin:0 0 22px}.remote-pack-metadata .el-form-item__content{line-height:24px;margin-top:4px}.save-pack-button{width:54%}.save-pack-button-container{margin-bottom:8px;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.save-pack-button-container button{padding:10px 5px}.save-pack-button-container .el-button+.el-button{margin-left:3px}}.create-pack{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.create-pack-button{margin-left:10px}.emoji-header-container{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:0 15px 22px}.emoji-header-container,.emoji-packs-header-button-container{display:-webkit-box;display:-ms-flexbox;display:flex}.emoji-packs-form{margin-top:15px}.emoji-packs-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:10px 15px 15px}.emoji-packs-tabs{margin:0 15px 15px}.import-pack-button{margin-left:10px;width:30%;max-width:700px}h1{margin:0}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:22px}.pagination{margin:25px 0;text-align:center}.reboot-button{padding:10px;margin:0;width:145px}@media only screen and (min-width:1824px){.emoji-packs{max-width:1824px;margin:auto}}@media only screen and (max-width:480px){.create-pack{height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.create-pack-button{margin-left:0}.divider{margin:15px 0}.el-message{min-width:80%}.el-message-box{width:80%}.emoji-header-container{-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}.emoji-packs-form{margin:0 7px}.emoji-packs-form label{padding-right:8px}.emoji-packs-form .el-form-item{margin-bottom:15px}.emoji-packs-header{margin:15px}.emoji-packs-header-button-container{height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.emoji-packs-header-button-container .el-button+.el-button{margin:7px 0 0;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.import-pack-button{width:90%}.reload-emoji-button{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-9a72.786caeb3.css b/priv/static/adminfe/chunk-9a72.786caeb3.css deleted file mode 100644 index c0074e6f7..000000000 --- a/priv/static/adminfe/chunk-9a72.786caeb3.css +++ /dev/null @@ -1 +0,0 @@ -@supports (-webkit-mask:none) and (not (cater-color:#fff)){.login-container .el-input input{color:#fff}.login-container .el-input input:first-line{color:#eee}}.login-container .el-input{display:inline-block;height:47px;width:85%}.login-container .el-input input{background:transparent;border:0;-webkit-appearance:none;border-radius:0;padding:12px 5px 12px 15px;color:#eee;height:47px;caret-color:#fff}.login-container .el-input input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #283443 inset!important;-webkit-text-fill-color:#fff!important}.login-container .el-form-item{border:1px solid hsla(0,0%,100%,.1);background:rgba(0,0,0,.1);border-radius:5px;color:#454545}.login-container .login-button{width:100%;margin:0 0 10px}.login-container .omit-host-note{color:#596f8c;font-size:.8em;font-style:italic;margin:-20px 0 15px;padding:3px 0 0 15px}.login-container[data-v-5bb13616]{min-height:100%;width:100%;background-color:#2d3a4b;overflow:hidden}.login-container .login-form[data-v-5bb13616]{position:relative;width:520px;max-width:100%;padding:160px 35px 0;margin:0 auto;overflow:hidden}.login-container .tips[data-v-5bb13616]{font-size:14px;color:#fff;margin-bottom:10px}.login-container .tips span[data-v-5bb13616]:first-of-type{margin-right:16px}.login-container .svg-container[data-v-5bb13616]{padding:6px 5px 6px 15px;color:#889aa4;vertical-align:middle;width:30px;display:inline-block}.login-container .title-container[data-v-5bb13616]{position:relative}.login-container .title-container .title[data-v-5bb13616]{font-size:26px;color:#eee;margin:0 auto 40px;text-align:center;font-weight:700}.login-container .title-container .set-language[data-v-5bb13616]{color:#fff;position:absolute;top:3px;font-size:18px;right:0;cursor:pointer}.login-container .show-pwd[data-v-5bb13616]{position:absolute;right:10px;top:7px;font-size:16px;color:#889aa4;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.login-container .thirdparty-button[data-v-5bb13616]{position:absolute;right:0;bottom:6px} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-9d55.e2cb1409.css b/priv/static/adminfe/chunk-9d55.e2cb1409.css new file mode 100644 index 000000000..13537842a --- /dev/null +++ b/priv/static/adminfe/chunk-9d55.e2cb1409.css @@ -0,0 +1 @@ +.editor{position:relative;border-radius:4px;border:1px solid #dcdfe6;padding:10px}.editor__content{overflow-wrap:break-word;word-wrap:break-word;word-break:break-word;padding-left:10px}.editor__content *{caret-color:currentColor}.editor__content pre{border-radius:5px;font-size:.8rem;overflow-x:auto}.editor__content pre code{display:block}.editor__content p code{border-radius:5px;font-size:.8rem;font-weight:700}.editor__content ol,.editor__content ul{padding-left:1rem}.editor__content li>ol,.editor__content li>p,.editor__content li>ul{margin:0}.editor__content a{color:inherit}.editor__content blockquote{border-left:3px solid rgba(0,0,0,.1);color:rgba(0,0,0,.8);padding-left:.8rem;font-style:italic}.editor__content blockquote p{margin:0}.editor__content img{max-width:100%;border-radius:3px}.editor__content table{border-collapse:collapse;table-layout:fixed;width:100%;margin:0;overflow:hidden}.editor__content table td,.editor__content table th{min-width:1em;border:2px solid #ddd;padding:3px 5px;vertical-align:top;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative}.editor__content table td>*,.editor__content table th>*{margin-bottom:0}.editor__content table th{font-weight:700;text-align:left}.editor__content table .selectedCell:after{z-index:2;position:absolute;content:"";left:0;right:0;top:0;bottom:0;background:rgba(200,200,255,.4);pointer-events:none}.editor__content table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;z-index:20;background-color:#adf;pointer-events:none}.editor__content .tableWrapper{margin:1em 0;overflow-x:auto}.editor__content .resize-cursor{cursor:ew-resize;cursor:col-resize}.editor-form-item{margin-right:30px}.menubar{margin-bottom:1rem;-webkit-transition:visibility .2s .4s,opacity .2s .4s;transition:visibility .2s .4s,opacity .2s .4s}.menubar.is-hidden{visibility:hidden;opacity:0}.menubar.is-focused{visibility:visible;opacity:1;-webkit-transition:visibility .2s,opacity .2s;transition:visibility .2s,opacity .2s}.menubar__button{font-weight:700;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;background:transparent;border:0;color:#000;padding:.2rem .5rem;margin-right:.2rem;border-radius:3px;cursor:pointer}.menubar__button:hover{background-color:rgba(0,0,0,.05)}.menubar__button.is-active{background-color:rgba(0,0,0,.1)}.menubar span.menubar__button{font-size:13.3333px}.image-upload-area .input-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.image-upload-area .input-file{z-index:100;position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;cursor:pointer}.image-upload-area .image-button-group{margin-top:20px}.image-upload-area .image-button-group .upload-button,.image-upload-area .image-upload-wrapper{position:relative}.image-upload-area .image-upload-wrapper .image-upload-overlay{border-radius:5px}.image-upload-area .image-upload-wrapper .image-upload-overlay,.image-upload-area .image-upload-wrapper .image-upload-overlay .caption{-webkit-transition:-webkit-box-shadow .1s;transition:-webkit-box-shadow .1s;transition:box-shadow .1s;transition:box-shadow .1s,-webkit-box-shadow .1s}.image-upload-area .image-upload-wrapper .image-upload-overlay .caption{visibility:hidden;position:absolute;top:0;bottom:0;right:0;left:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-weight:700;font-size:10px;text-transform:uppercase;color:#fff;z-index:9}.image-upload-area .image-upload-wrapper .image-upload-overlay .uploaded-image{border-radius:5px;-webkit-box-shadow:0 2px 10px 0 rgba(0,0,0,.1);box-shadow:0 2px 10px 0 rgba(0,0,0,.1)}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover{visibility:visible;cursor:pointer;border-radius:5px}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover .el-image__error{visibility:hidden}.image-upload-area .image-upload-wrapper .image-upload-overlay:hover .caption{visibility:visible;-webkit-box-shadow:0 2px 10px 0 rgba(0,0,0,.1),inset 0 0 120px 25px rgba(0,0,0,.8);box-shadow:0 2px 10px 0 rgba(0,0,0,.1),inset 0 0 120px 25px rgba(0,0,0,.8);border-radius:5px}a{text-decoration:underline}.center-label label{text-align:center}.center-label label span{float:left}.code{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.delete-setting-button{margin-left:5px}.description-container{overflow-wrap:break-word}.description-container .el-form-item__content{line-height:20px}.divider{margin:0 0 18px}.divider.thick-line{height:2px}.docs-search-container{float:right;margin-right:30px}.editable-keyword-container{width:100%}.el-form-item .rate-limit{margin-right:0}.el-input-group__prepend{padding-left:10px;padding-right:10px}.el-tabs__header{z-index:2002}.email-address-input{width:50%;margin-right:10px}.esshd-list{margin:0}.expl,.expl>p{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.expl>p code,.expl code{display:inline;line-height:22px;font-size:13px;padding:2px 3px}.follow-relay{width:350px;margin-right:7px}.form-container{margin-bottom:80px}.grouped-settings-header{margin:0 0 14px}.highlight{background-color:#e6e6e6}.icons-button-container{width:100%;margin-bottom:10px}.icons-button-desc{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;margin-left:5px}.icon-container{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:95%}.icon-values-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px 10px 0}.icon-key-input{width:30%;margin-right:8px}.icon-minus-button{width:36px;height:36px}.icon-value-input{width:70%;margin-left:8px}.icons-container,.input-container{display:-webkit-box;display:-ms-flexbox;display:flex}.input-container{-webkit-box-align:start;-ms-flex-align:start;align-items:start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input-container .el-form-item{margin-right:30px;width:100%}.input-container .el-select,.keyword-container{width:100%}label{overflow:hidden;text-overflow:ellipsis}.label-font{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700}.limit-button-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl{margin-left:10px}.limit-input{width:47%;margin:0 0 5px 1%}.line{width:100%;height:0;border:1px solid #eee;margin-bottom:18px}.mascot{margin-bottom:15px}.mascot-container{width:100%}.mascot-input{margin-bottom:7px}.mascot-name-container{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input{margin-right:10px}.multiple-select-container{width:100%}.name-input{width:30%;margin-right:8px}.nickname-input{width:50%}.no-top-margin{margin-top:0}.no-top-margin p{margin-right:30px}.pattern-input{width:20%;margin-right:8px}.proxy-url-input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.proxy-url-host-input{width:35%;margin-right:8px}.proxy-url-value-input{width:35%;margin-left:8px;margin-right:10px}.prune-options{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.prune-options .el-radio{margin-top:11px}.rate-limit .el-form-item__content{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.rate-limit-container{width:100%}.rate-limit-content{width:70%}.rate-limit-label{float:right}.rate-limit-label-container{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;width:30%;margin-right:10px}.reboot-button{width:145px;text-align:left;padding:10px;float:right;margin:0 30px 0 0}.reboot-button-container{width:100%;position:fixed;top:60px;right:0;z-index:2000}.relays-container{margin:0 15px}.replacement-input{width:80%;margin-left:8px;margin-right:10px}.sender-input{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.scale-input{width:47%;margin:0 1% 5px 0}.setting-input{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.setting-label{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.settings-container{max-width:1824px;margin:auto}.settings-container .el-tabs{margin-top:20px}.settings-delete-button{margin-left:5px}.settings-docs-button{min-width:163px;text-align:left;padding:10px}.settings-header{margin:10px 15px 15px}.header-sidebar-opened{max-width:1585px}.header-sidebar-closed{max-width:1728px}.settings-header-container{height:87px}.settings-search-input{width:350px;margin-left:5px}.single-input{margin-right:10px}.socks5-checkbox{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;margin-left:10px}.socks5-checkbox-container{width:40%;height:36px;margin-right:5px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ssl-tls-opts{margin:36px 0 0}.submit-button{float:right;margin:0 30px 22px 0}.submit-button-container{width:100%;position:fixed;bottom:0;right:0;z-index:2000}.switch-input{height:36px}.text{line-height:20px;margin-right:15px}.upload-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input{width:70%;margin-left:8px;margin-right:10px}@media only screen and (min-width:1824px){.header-sidebar-closed{max-width:1772px}.header-sidebar-opened{max-width:1630px}.reboot-button-container{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}.reboot-sidebar-opened{max-width:1630px}.reboot-sidebar-closed{max-width:1772px}.sidebar-closed{max-width:1586px}.sidebar-opened{max-width:1442px}.submit-button-container{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}}@media only screen and (max-width:480px){.crontab,.crontab label{width:100%}.delete-setting-button{margin:4px 0 0 5px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 5px 7px 15px}.description>p code{display:inline;line-height:18px;padding:2px 3px;font-size:14px}.description-container{margin:0 15px 22px}.divider{margin:0 0 10px}.divider .thick-line{height:2px}.follow-relay{width:75%;margin-right:5px}.follow-relay input{width:100%}.follow-relay-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:0 5px}h1{font-size:24px}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container{width:100%}.input-container .el-form-item:first-child{margin:0;padding:0 15px 10px}.input-container .el-form-item.crontab-container:first-child{margin:0;padding:0}.input-container .el-form-item:first-child .mascot-form-item,.input-container .el-form-item:first-child .rate-limit{padding:0}.input-container .settings-delete-button{margin-top:4px;float:right}.input-row{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.label-with-margin{margin-left:15px}.limit-input{width:45%}.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.proxy-url-input{-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;margin-bottom:0}.proxy-url-host-input{width:100%;margin-bottom:5px}.proxy-url-value-input{width:100%;margin-left:0}.prune-options{height:80px}.prune-options,.rate-limit .el-form-item__content{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.rate-limit-content{width:100%}.rate-limit-label{float:left}.rate-limit-label-container{width:100%}.reboot-button{margin:0 15px 0 0}.reboot-button-container{top:57px}.scale-input{width:45%}.settings-header{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:inline-block;margin:10px 15px 15px}.docs-search-container{float:right}.settings-search-input{width:100%;margin-left:0}.settings-search-input-container{margin:0 15px 15px}.settings-menu{width:163px;margin-right:5px}.socks5-checkbox-container{width:100%}.submit-button{margin:0 15px 22px 0}.el-input__inner{padding:0 5px}.el-form-item__label:not(.no-top-margin){padding-bottom:5px;line-height:22px;margin-top:7px;width:100%;pointer-events:none}.el-form-item__label:not(.no-top-margin) span{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.el-form-item__label:not(.no-top-margin) button{pointer-events:auto}.el-message{min-width:80%}.el-message-box{width:80%}.el-select__tags{overflow:hidden}.expl,.expl>p{line-height:16px}.icon-key-input{width:40%;margin-right:4px}.icon-minus-button{width:28px;height:28px;margin-top:4px}.icon-values-container{margin:0 7px 7px 0}.icon-value-input{width:60%;margin-left:4px}.icons-button-container{line-height:24px}.line{margin-bottom:10px}.mascot-form-item .el-form-item__label:not(.no-top-margin){margin:0;padding:0}.mascot-container{margin-bottom:5px}.name-input{width:40%;margin-right:5px}p.expl{line-height:20px}.pattern-input{width:40%;margin-right:4px}.relays-container{margin:0 10px}.replacement-input{width:60%;margin-left:4px;margin-right:5px}.settings-header-container{height:45px}.value-input{width:60%;margin-left:5px;margin-right:8px}}@media only screen and (max-width:818px) and (min-width:481px){.delete-setting-button{margin:4px 0 0 10px;height:28px}.delete-setting-button-container{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p{line-height:18px;margin:0 15px 10px 0}.icon-minus-button{width:28px;height:28px;margin-top:4px}.input{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container .el-form-item__label span{margin-left:10px}.input-row,.nav-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.nav-container{height:36px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 30px 15px 15px}.rate-limit-content{width:65%}.rate-limit-label-container{width:35%}.settings-delete-button{float:right}.settings-header-container{height:36px}.settings-search-input{width:250px;margin:0 0 15px 15px}}a[data-v-82f78b3e]{text-decoration:underline}.center-label label[data-v-82f78b3e]{text-align:center}.center-label label span[data-v-82f78b3e]{float:left}.code[data-v-82f78b3e]{background-color:rgba(173,190,214,.48);border-radius:3px;font-family:monospace;padding:0 3px}.delete-setting-button[data-v-82f78b3e]{margin-left:5px}.description-container[data-v-82f78b3e]{overflow-wrap:break-word}.description-container .el-form-item__content[data-v-82f78b3e]{line-height:20px}.divider[data-v-82f78b3e]{margin:0 0 18px}.divider.thick-line[data-v-82f78b3e]{height:2px}.docs-search-container[data-v-82f78b3e]{float:right;margin-right:30px}.editable-keyword-container[data-v-82f78b3e]{width:100%}.el-form-item .rate-limit[data-v-82f78b3e]{margin-right:0}.el-input-group__prepend[data-v-82f78b3e]{padding-left:10px;padding-right:10px}.el-tabs__header[data-v-82f78b3e]{z-index:2002}.email-address-input[data-v-82f78b3e]{width:50%;margin-right:10px}.esshd-list[data-v-82f78b3e]{margin:0}.expl>p[data-v-82f78b3e],.expl[data-v-82f78b3e]{color:#666;font-size:13px;line-height:22px;margin:5px 0 0;overflow-wrap:break-word;overflow:hidden;text-overflow:ellipsis}.expl>p code[data-v-82f78b3e],.expl code[data-v-82f78b3e]{display:inline;line-height:22px;font-size:13px;padding:2px 3px}.follow-relay[data-v-82f78b3e]{width:350px;margin-right:7px}.form-container[data-v-82f78b3e]{margin-bottom:80px}.grouped-settings-header[data-v-82f78b3e]{margin:0 0 14px}.highlight[data-v-82f78b3e]{background-color:#e6e6e6}.icons-button-container[data-v-82f78b3e]{width:100%;margin-bottom:10px}.icons-button-desc[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;margin-left:5px}.icon-container[data-v-82f78b3e]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:95%}.icon-values-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 10px 10px 0}.icon-key-input[data-v-82f78b3e]{width:30%;margin-right:8px}.icon-minus-button[data-v-82f78b3e]{width:36px;height:36px}.icon-value-input[data-v-82f78b3e]{width:70%;margin-left:8px}.icons-container[data-v-82f78b3e],.input-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex}.input-container[data-v-82f78b3e]{-webkit-box-align:start;-ms-flex-align:start;align-items:start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.input-container .el-form-item[data-v-82f78b3e]{margin-right:30px;width:100%}.input-container .el-select[data-v-82f78b3e],.keyword-container[data-v-82f78b3e]{width:100%}label[data-v-82f78b3e]{overflow:hidden;text-overflow:ellipsis}.label-font[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700}.limit-button-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.limit-expl[data-v-82f78b3e]{margin-left:10px}.limit-input[data-v-82f78b3e]{width:47%;margin:0 0 5px 1%}.line[data-v-82f78b3e]{width:100%;height:0;border:1px solid #eee;margin-bottom:18px}.mascot[data-v-82f78b3e]{margin-bottom:15px}.mascot-container[data-v-82f78b3e]{width:100%}.mascot-input[data-v-82f78b3e]{margin-bottom:7px}.mascot-name-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:7px}.mascot-name-input[data-v-82f78b3e]{margin-right:10px}.multiple-select-container[data-v-82f78b3e]{width:100%}.name-input[data-v-82f78b3e]{width:30%;margin-right:8px}.nickname-input[data-v-82f78b3e]{width:50%}.no-top-margin[data-v-82f78b3e]{margin-top:0}.no-top-margin p[data-v-82f78b3e]{margin-right:30px}.pattern-input[data-v-82f78b3e]{width:20%;margin-right:8px}.proxy-url-input[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.proxy-url-host-input[data-v-82f78b3e]{width:35%;margin-right:8px}.proxy-url-value-input[data-v-82f78b3e]{width:35%;margin-left:8px;margin-right:10px}.prune-options[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.prune-options .el-radio[data-v-82f78b3e]{margin-top:11px}.rate-limit .el-form-item__content[data-v-82f78b3e]{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.rate-limit-container[data-v-82f78b3e]{width:100%}.rate-limit-content[data-v-82f78b3e]{width:70%}.rate-limit-label[data-v-82f78b3e]{float:right}.rate-limit-label-container[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;width:30%;margin-right:10px}.reboot-button[data-v-82f78b3e]{width:145px;text-align:left;padding:10px;float:right;margin:0 30px 0 0}.reboot-button-container[data-v-82f78b3e]{width:100%;position:fixed;top:60px;right:0;z-index:2000}.relays-container[data-v-82f78b3e]{margin:0 15px}.replacement-input[data-v-82f78b3e]{width:80%;margin-left:8px;margin-right:10px}.sender-input[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:10px;width:100%}.scale-input[data-v-82f78b3e]{width:47%;margin:0 1% 5px 0}.setting-input[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:10px}.setting-label[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;line-height:20px;margin:0 0 14px}.settings-container[data-v-82f78b3e]{max-width:1824px;margin:auto}.settings-container .el-tabs[data-v-82f78b3e]{margin-top:20px}.settings-delete-button[data-v-82f78b3e]{margin-left:5px}.settings-docs-button[data-v-82f78b3e]{min-width:163px;text-align:left;padding:10px}.settings-header[data-v-82f78b3e]{margin:10px 15px 15px}.header-sidebar-opened[data-v-82f78b3e]{max-width:1585px}.header-sidebar-closed[data-v-82f78b3e]{max-width:1728px}.settings-header-container[data-v-82f78b3e]{height:87px}.settings-search-input[data-v-82f78b3e]{width:350px;margin-left:5px}.single-input[data-v-82f78b3e]{margin-right:10px}.socks5-checkbox[data-v-82f78b3e]{font-size:14px;color:#606266;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei;font-weight:700;margin-left:10px}.socks5-checkbox-container[data-v-82f78b3e]{width:40%;height:36px;margin-right:5px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.ssl-tls-opts[data-v-82f78b3e]{margin:36px 0 0}.submit-button[data-v-82f78b3e]{float:right;margin:0 30px 22px 0}.submit-button-container[data-v-82f78b3e]{width:100%;position:fixed;bottom:0;right:0;z-index:2000}.switch-input[data-v-82f78b3e]{height:36px}.text[data-v-82f78b3e]{line-height:20px;margin-right:15px}.upload-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.value-input[data-v-82f78b3e]{width:70%;margin-left:8px;margin-right:10px}@media only screen and (min-width:1824px){.header-sidebar-closed[data-v-82f78b3e]{max-width:1772px}.header-sidebar-opened[data-v-82f78b3e]{max-width:1630px}.reboot-button-container[data-v-82f78b3e]{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}.reboot-sidebar-opened[data-v-82f78b3e]{max-width:1630px}.reboot-sidebar-closed[data-v-82f78b3e]{max-width:1772px}.sidebar-closed[data-v-82f78b3e]{max-width:1586px}.sidebar-opened[data-v-82f78b3e]{max-width:1442px}.submit-button-container[data-v-82f78b3e]{width:100%;max-width:inherit;margin-left:auto;margin-right:auto;right:auto}}@media only screen and (max-width:480px){.crontab[data-v-82f78b3e],.crontab label[data-v-82f78b3e]{width:100%}.delete-setting-button[data-v-82f78b3e]{margin:4px 0 0 5px;height:28px}.delete-setting-button-container[data-v-82f78b3e]{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p[data-v-82f78b3e]{line-height:18px;margin:0 5px 7px 15px}.description>p code[data-v-82f78b3e]{display:inline;line-height:18px;padding:2px 3px;font-size:14px}.description-container[data-v-82f78b3e]{margin:0 15px 22px}.divider[data-v-82f78b3e]{margin:0 0 10px}.divider .thick-line[data-v-82f78b3e]{height:2px}.follow-relay[data-v-82f78b3e]{width:75%;margin-right:5px}.follow-relay input[data-v-82f78b3e]{width:100%}.follow-relay-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:0 5px}h1[data-v-82f78b3e]{font-size:24px}.input[data-v-82f78b3e]{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container[data-v-82f78b3e]{width:100%}.input-container .el-form-item[data-v-82f78b3e]:first-child{margin:0;padding:0 15px 10px}.input-container .el-form-item.crontab-container[data-v-82f78b3e]:first-child{margin:0;padding:0}.input-container .el-form-item:first-child .mascot-form-item[data-v-82f78b3e],.input-container .el-form-item:first-child .rate-limit[data-v-82f78b3e]{padding:0}.input-container .settings-delete-button[data-v-82f78b3e]{margin-top:4px;float:right}.input-row[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.label-with-margin[data-v-82f78b3e]{margin-left:15px}.limit-input[data-v-82f78b3e]{width:45%}.nav-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px}.proxy-url-input[data-v-82f78b3e]{-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;margin-bottom:0}.proxy-url-host-input[data-v-82f78b3e]{width:100%;margin-bottom:5px}.proxy-url-value-input[data-v-82f78b3e]{width:100%;margin-left:0}.prune-options[data-v-82f78b3e]{height:80px}.prune-options[data-v-82f78b3e],.rate-limit .el-form-item__content[data-v-82f78b3e]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.rate-limit-content[data-v-82f78b3e]{width:100%}.rate-limit-label[data-v-82f78b3e]{float:left}.rate-limit-label-container[data-v-82f78b3e]{width:100%}.reboot-button[data-v-82f78b3e]{margin:0 15px 0 0}.reboot-button-container[data-v-82f78b3e]{top:57px}.scale-input[data-v-82f78b3e]{width:45%}.settings-header[data-v-82f78b3e]{width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;display:inline-block;margin:10px 15px 15px}.docs-search-container[data-v-82f78b3e]{float:right}.settings-search-input[data-v-82f78b3e]{width:100%;margin-left:0}.settings-search-input-container[data-v-82f78b3e]{margin:0 15px 15px}.settings-menu[data-v-82f78b3e]{width:163px;margin-right:5px}.socks5-checkbox-container[data-v-82f78b3e]{width:100%}.submit-button[data-v-82f78b3e]{margin:0 15px 22px 0}.el-input__inner[data-v-82f78b3e]{padding:0 5px}.el-form-item__label[data-v-82f78b3e]:not(.no-top-margin){padding-bottom:5px;line-height:22px;margin-top:7px;width:100%;pointer-events:none}.el-form-item__label:not(.no-top-margin) span[data-v-82f78b3e]{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:baseline;-ms-flex-align:baseline;align-items:baseline}.el-form-item__label:not(.no-top-margin) button[data-v-82f78b3e]{pointer-events:auto}.el-message[data-v-82f78b3e]{min-width:80%}.el-message-box[data-v-82f78b3e]{width:80%}.el-select__tags[data-v-82f78b3e]{overflow:hidden}.expl>p[data-v-82f78b3e],.expl[data-v-82f78b3e]{line-height:16px}.icon-key-input[data-v-82f78b3e]{width:40%;margin-right:4px}.icon-minus-button[data-v-82f78b3e]{width:28px;height:28px;margin-top:4px}.icon-values-container[data-v-82f78b3e]{margin:0 7px 7px 0}.icon-value-input[data-v-82f78b3e]{width:60%;margin-left:4px}.icons-button-container[data-v-82f78b3e]{line-height:24px}.line[data-v-82f78b3e]{margin-bottom:10px}.mascot-form-item .el-form-item__label[data-v-82f78b3e]:not(.no-top-margin){margin:0;padding:0}.mascot-container[data-v-82f78b3e]{margin-bottom:5px}.name-input[data-v-82f78b3e]{width:40%;margin-right:5px}p.expl[data-v-82f78b3e]{line-height:20px}.pattern-input[data-v-82f78b3e]{width:40%;margin-right:4px}.relays-container[data-v-82f78b3e]{margin:0 10px}.replacement-input[data-v-82f78b3e]{width:60%;margin-left:4px;margin-right:5px}.settings-header-container[data-v-82f78b3e]{height:45px}.value-input[data-v-82f78b3e]{width:60%;margin-left:5px;margin-right:8px}}@media only screen and (max-width:818px) and (min-width:481px){.delete-setting-button[data-v-82f78b3e]{margin:4px 0 0 10px;height:28px}.delete-setting-button-container[data-v-82f78b3e]{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.description>p[data-v-82f78b3e]{line-height:18px;margin:0 15px 10px 0}.icon-minus-button[data-v-82f78b3e]{width:28px;height:28px;margin-top:4px}.input[data-v-82f78b3e]{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.input-container .el-form-item__label span[data-v-82f78b3e]{margin-left:10px}.input-row[data-v-82f78b3e],.nav-container[data-v-82f78b3e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.nav-container[data-v-82f78b3e]{height:36px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 30px 15px 15px}.rate-limit-content[data-v-82f78b3e]{width:65%}.rate-limit-label-container[data-v-82f78b3e]{width:35%}.settings-delete-button[data-v-82f78b3e]{float:right}.settings-header-container[data-v-82f78b3e]{height:36px}.settings-search-input[data-v-82f78b3e]{width:250px;margin:0 0 15px 15px}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-commons.a343b725.css b/priv/static/adminfe/chunk-commons.c0eb3eb7.css similarity index 100% rename from priv/static/adminfe/chunk-commons.a343b725.css rename to priv/static/adminfe/chunk-commons.c0eb3eb7.css diff --git a/priv/static/adminfe/chunk-6198.3c37d6af.css b/priv/static/adminfe/chunk-d34d.b0dd6fb4.css similarity index 100% rename from priv/static/adminfe/chunk-6198.3c37d6af.css rename to priv/static/adminfe/chunk-d34d.b0dd6fb4.css diff --git a/priv/static/adminfe/chunk-d892.56863b19.css b/priv/static/adminfe/chunk-d892.56863b19.css deleted file mode 100644 index 483d88545..000000000 --- a/priv/static/adminfe/chunk-d892.56863b19.css +++ /dev/null @@ -1 +0,0 @@ -.router-link{text-decoration:none}.moderation-log-container[data-v-60b585cf]{margin:0 15px}h1[data-v-60b585cf]{margin:0}.el-timeline[data-v-60b585cf]{margin:25px 45px 0 0;padding:0}.moderation-log-date-panel[data-v-60b585cf]{width:350px}.moderation-log-header-container[data-v-60b585cf]{-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:10px 0 15px}.moderation-log-header-container[data-v-60b585cf],.moderation-log-nav-container[data-v-60b585cf]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.moderation-log-search[data-v-60b585cf]{width:350px}.moderation-log-user-select[data-v-60b585cf]{margin:0 0 20px;width:350px}.reboot-button[data-v-60b585cf]{padding:10px;margin:0;width:145px}.search-container[data-v-60b585cf]{text-align:right}.pagination[data-v-60b585cf]{text-align:center}@media only screen and (max-width:480px){h1[data-v-60b585cf]{font-size:24px}.moderation-log-date-panel[data-v-60b585cf]{width:100%}.moderation-log-user-select[data-v-60b585cf]{margin:0 0 10px;width:55%}.moderation-log-search[data-v-60b585cf]{width:40%}}@media only screen and (max-width:801px) and (min-width:481px){.moderation-log-date-panel[data-v-60b585cf]{width:55%}.moderation-log-user-select[data-v-60b585cf]{margin:0 0 10px;width:55%}.moderation-log-search[data-v-60b585cf]{width:40%}} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-elementUI.40545a1f.css b/priv/static/adminfe/chunk-elementUI.40545a1f.css deleted file mode 100644 index c802d3a40..000000000 --- a/priv/static/adminfe/chunk-elementUI.40545a1f.css +++ /dev/null @@ -1 +0,0 @@ -.el-pagination--small .arrow.disabled,.el-table--hidden,.el-table .hidden-columns,.el-table td.is-hidden>*,.el-table th.is-hidden>*{visibility:hidden}.el-input__suffix,.el-tree.is-dragging .el-tree-node__content *{pointer-events:none}.el-dropdown .el-dropdown-selfdefine:focus:active,.el-dropdown .el-dropdown-selfdefine:focus:not(.focusing),.el-message__closeBtn:focus,.el-message__content:focus,.el-popover:focus,.el-popover:focus:active,.el-popover__reference:focus:hover,.el-popover__reference:focus:not(.focusing),.el-rate:active,.el-rate:focus,.el-tooltip:focus:hover,.el-tooltip:focus:not(.focusing),.el-upload-list__item.is-success:active,.el-upload-list__item.is-success:not(.focusing):focus{outline-width:0}@font-face{font-family:element-icons;src:url(static/fonts/element-icons.535877f.woff) format("woff"),url(static/fonts/element-icons.732389d.ttf) format("truetype");font-weight:400;font-display:"auto";font-style:normal}[class*=" el-icon-"],[class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-icon-ice-cream-round:before{content:"\E6A0"}.el-icon-ice-cream-square:before{content:"\E6A3"}.el-icon-lollipop:before{content:"\E6A4"}.el-icon-potato-strips:before{content:"\E6A5"}.el-icon-milk-tea:before{content:"\E6A6"}.el-icon-ice-drink:before{content:"\E6A7"}.el-icon-ice-tea:before{content:"\E6A9"}.el-icon-coffee:before{content:"\E6AA"}.el-icon-orange:before{content:"\E6AB"}.el-icon-pear:before{content:"\E6AC"}.el-icon-apple:before{content:"\E6AD"}.el-icon-cherry:before{content:"\E6AE"}.el-icon-watermelon:before{content:"\E6AF"}.el-icon-grape:before{content:"\E6B0"}.el-icon-refrigerator:before{content:"\E6B1"}.el-icon-goblet-square-full:before{content:"\E6B2"}.el-icon-goblet-square:before{content:"\E6B3"}.el-icon-goblet-full:before{content:"\E6B4"}.el-icon-goblet:before{content:"\E6B5"}.el-icon-cold-drink:before{content:"\E6B6"}.el-icon-coffee-cup:before{content:"\E6B8"}.el-icon-water-cup:before{content:"\E6B9"}.el-icon-hot-water:before{content:"\E6BA"}.el-icon-ice-cream:before{content:"\E6BB"}.el-icon-dessert:before{content:"\E6BC"}.el-icon-sugar:before{content:"\E6BD"}.el-icon-tableware:before{content:"\E6BE"}.el-icon-burger:before{content:"\E6BF"}.el-icon-knife-fork:before{content:"\E6C1"}.el-icon-fork-spoon:before{content:"\E6C2"}.el-icon-chicken:before{content:"\E6C3"}.el-icon-food:before{content:"\E6C4"}.el-icon-dish-1:before{content:"\E6C5"}.el-icon-dish:before{content:"\E6C6"}.el-icon-moon-night:before{content:"\E6EE"}.el-icon-moon:before{content:"\E6F0"}.el-icon-cloudy-and-sunny:before{content:"\E6F1"}.el-icon-partly-cloudy:before{content:"\E6F2"}.el-icon-cloudy:before{content:"\E6F3"}.el-icon-sunny:before{content:"\E6F6"}.el-icon-sunset:before{content:"\E6F7"}.el-icon-sunrise-1:before{content:"\E6F8"}.el-icon-sunrise:before{content:"\E6F9"}.el-icon-heavy-rain:before{content:"\E6FA"}.el-icon-lightning:before{content:"\E6FB"}.el-icon-light-rain:before{content:"\E6FC"}.el-icon-wind-power:before{content:"\E6FD"}.el-icon-baseball:before{content:"\E712"}.el-icon-soccer:before{content:"\E713"}.el-icon-football:before{content:"\E715"}.el-icon-basketball:before{content:"\E716"}.el-icon-ship:before{content:"\E73F"}.el-icon-truck:before{content:"\E740"}.el-icon-bicycle:before{content:"\E741"}.el-icon-mobile-phone:before{content:"\E6D3"}.el-icon-service:before{content:"\E6D4"}.el-icon-key:before{content:"\E6E2"}.el-icon-unlock:before{content:"\E6E4"}.el-icon-lock:before{content:"\E6E5"}.el-icon-watch:before{content:"\E6FE"}.el-icon-watch-1:before{content:"\E6FF"}.el-icon-timer:before{content:"\E702"}.el-icon-alarm-clock:before{content:"\E703"}.el-icon-map-location:before{content:"\E704"}.el-icon-delete-location:before{content:"\E705"}.el-icon-add-location:before{content:"\E706"}.el-icon-location-information:before{content:"\E707"}.el-icon-location-outline:before{content:"\E708"}.el-icon-location:before{content:"\E79E"}.el-icon-place:before{content:"\E709"}.el-icon-discover:before{content:"\E70A"}.el-icon-first-aid-kit:before{content:"\E70B"}.el-icon-trophy-1:before{content:"\E70C"}.el-icon-trophy:before{content:"\E70D"}.el-icon-medal:before{content:"\E70E"}.el-icon-medal-1:before{content:"\E70F"}.el-icon-stopwatch:before{content:"\E710"}.el-icon-mic:before{content:"\E711"}.el-icon-copy-document:before{content:"\E718"}.el-icon-full-screen:before{content:"\E719"}.el-icon-switch-button:before{content:"\E71B"}.el-icon-aim:before{content:"\E71C"}.el-icon-crop:before{content:"\E71D"}.el-icon-odometer:before{content:"\E71E"}.el-icon-time:before{content:"\E71F"}.el-icon-bangzhu:before{content:"\E724"}.el-icon-close-notification:before{content:"\E726"}.el-icon-microphone:before{content:"\E727"}.el-icon-turn-off-microphone:before{content:"\E728"}.el-icon-position:before{content:"\E729"}.el-icon-postcard:before{content:"\E72A"}.el-icon-message:before{content:"\E72B"}.el-icon-chat-line-square:before{content:"\E72D"}.el-icon-chat-dot-square:before{content:"\E72E"}.el-icon-chat-dot-round:before{content:"\E72F"}.el-icon-chat-square:before{content:"\E730"}.el-icon-chat-line-round:before{content:"\E731"}.el-icon-chat-round:before{content:"\E732"}.el-icon-set-up:before{content:"\E733"}.el-icon-turn-off:before{content:"\E734"}.el-icon-open:before{content:"\E735"}.el-icon-connection:before{content:"\E736"}.el-icon-link:before{content:"\E737"}.el-icon-cpu:before{content:"\E738"}.el-icon-thumb:before{content:"\E739"}.el-icon-female:before{content:"\E73A"}.el-icon-male:before{content:"\E73B"}.el-icon-guide:before{content:"\E73C"}.el-icon-news:before{content:"\E73E"}.el-icon-price-tag:before{content:"\E744"}.el-icon-discount:before{content:"\E745"}.el-icon-wallet:before{content:"\E747"}.el-icon-coin:before{content:"\E748"}.el-icon-money:before{content:"\E749"}.el-icon-bank-card:before{content:"\E74A"}.el-icon-box:before{content:"\E74B"}.el-icon-present:before{content:"\E74C"}.el-icon-sell:before{content:"\E6D5"}.el-icon-sold-out:before{content:"\E6D6"}.el-icon-shopping-bag-2:before{content:"\E74D"}.el-icon-shopping-bag-1:before{content:"\E74E"}.el-icon-shopping-cart-2:before{content:"\E74F"}.el-icon-shopping-cart-1:before{content:"\E750"}.el-icon-shopping-cart-full:before{content:"\E751"}.el-icon-smoking:before{content:"\E752"}.el-icon-no-smoking:before{content:"\E753"}.el-icon-house:before{content:"\E754"}.el-icon-table-lamp:before{content:"\E755"}.el-icon-school:before{content:"\E756"}.el-icon-office-building:before{content:"\E757"}.el-icon-toilet-paper:before{content:"\E758"}.el-icon-notebook-2:before{content:"\E759"}.el-icon-notebook-1:before{content:"\E75A"}.el-icon-files:before{content:"\E75B"}.el-icon-collection:before{content:"\E75C"}.el-icon-receiving:before{content:"\E75D"}.el-icon-suitcase-1:before{content:"\E760"}.el-icon-suitcase:before{content:"\E761"}.el-icon-film:before{content:"\E763"}.el-icon-collection-tag:before{content:"\E765"}.el-icon-data-analysis:before{content:"\E766"}.el-icon-pie-chart:before{content:"\E767"}.el-icon-data-board:before{content:"\E768"}.el-icon-data-line:before{content:"\E76D"}.el-icon-reading:before{content:"\E769"}.el-icon-magic-stick:before{content:"\E76A"}.el-icon-coordinate:before{content:"\E76B"}.el-icon-mouse:before{content:"\E76C"}.el-icon-brush:before{content:"\E76E"}.el-icon-headset:before{content:"\E76F"}.el-icon-umbrella:before{content:"\E770"}.el-icon-scissors:before{content:"\E771"}.el-icon-mobile:before{content:"\E773"}.el-icon-attract:before{content:"\E774"}.el-icon-monitor:before{content:"\E775"}.el-icon-search:before{content:"\E778"}.el-icon-takeaway-box:before{content:"\E77A"}.el-icon-paperclip:before{content:"\E77D"}.el-icon-printer:before{content:"\E77E"}.el-icon-document-add:before{content:"\E782"}.el-icon-document:before{content:"\E785"}.el-icon-document-checked:before{content:"\E786"}.el-icon-document-copy:before{content:"\E787"}.el-icon-document-delete:before{content:"\E788"}.el-icon-document-remove:before{content:"\E789"}.el-icon-tickets:before{content:"\E78B"}.el-icon-folder-checked:before{content:"\E77F"}.el-icon-folder-delete:before{content:"\E780"}.el-icon-folder-remove:before{content:"\E781"}.el-icon-folder-add:before{content:"\E783"}.el-icon-folder-opened:before{content:"\E784"}.el-icon-folder:before{content:"\E78A"}.el-icon-edit-outline:before{content:"\E764"}.el-icon-edit:before{content:"\E78C"}.el-icon-date:before{content:"\E78E"}.el-icon-c-scale-to-original:before{content:"\E7C6"}.el-icon-view:before{content:"\E6CE"}.el-icon-loading:before{content:"\E6CF"}.el-icon-rank:before{content:"\E6D1"}.el-icon-sort-down:before{content:"\E7C4"}.el-icon-sort-up:before{content:"\E7C5"}.el-icon-sort:before{content:"\E6D2"}.el-icon-finished:before{content:"\E6CD"}.el-icon-refresh-left:before{content:"\E6C7"}.el-icon-refresh-right:before{content:"\E6C8"}.el-icon-refresh:before{content:"\E6D0"}.el-icon-video-play:before{content:"\E7C0"}.el-icon-video-pause:before{content:"\E7C1"}.el-icon-d-arrow-right:before{content:"\E6DC"}.el-icon-d-arrow-left:before{content:"\E6DD"}.el-icon-arrow-up:before{content:"\E6E1"}.el-icon-arrow-down:before{content:"\E6DF"}.el-icon-arrow-right:before{content:"\E6E0"}.el-icon-arrow-left:before{content:"\E6DE"}.el-icon-top-right:before{content:"\E6E7"}.el-icon-top-left:before{content:"\E6E8"}.el-icon-top:before{content:"\E6E6"}.el-icon-bottom:before{content:"\E6EB"}.el-icon-right:before{content:"\E6E9"}.el-icon-back:before{content:"\E6EA"}.el-icon-bottom-right:before{content:"\E6EC"}.el-icon-bottom-left:before{content:"\E6ED"}.el-icon-caret-top:before{content:"\E78F"}.el-icon-caret-bottom:before{content:"\E790"}.el-icon-caret-right:before{content:"\E791"}.el-icon-caret-left:before{content:"\E792"}.el-icon-d-caret:before{content:"\E79A"}.el-icon-share:before{content:"\E793"}.el-icon-menu:before{content:"\E798"}.el-icon-s-grid:before{content:"\E7A6"}.el-icon-s-check:before{content:"\E7A7"}.el-icon-s-data:before{content:"\E7A8"}.el-icon-s-opportunity:before{content:"\E7AA"}.el-icon-s-custom:before{content:"\E7AB"}.el-icon-s-claim:before{content:"\E7AD"}.el-icon-s-finance:before{content:"\E7AE"}.el-icon-s-comment:before{content:"\E7AF"}.el-icon-s-flag:before{content:"\E7B0"}.el-icon-s-marketing:before{content:"\E7B1"}.el-icon-s-shop:before{content:"\E7B4"}.el-icon-s-open:before{content:"\E7B5"}.el-icon-s-management:before{content:"\E7B6"}.el-icon-s-ticket:before{content:"\E7B7"}.el-icon-s-release:before{content:"\E7B8"}.el-icon-s-home:before{content:"\E7B9"}.el-icon-s-promotion:before{content:"\E7BA"}.el-icon-s-operation:before{content:"\E7BB"}.el-icon-s-unfold:before{content:"\E7BC"}.el-icon-s-fold:before{content:"\E7A9"}.el-icon-s-platform:before{content:"\E7BD"}.el-icon-s-order:before{content:"\E7BE"}.el-icon-s-cooperation:before{content:"\E7BF"}.el-icon-bell:before{content:"\E725"}.el-icon-message-solid:before{content:"\E799"}.el-icon-video-camera:before{content:"\E772"}.el-icon-video-camera-solid:before{content:"\E796"}.el-icon-camera:before{content:"\E779"}.el-icon-camera-solid:before{content:"\E79B"}.el-icon-download:before{content:"\E77C"}.el-icon-upload2:before{content:"\E77B"}.el-icon-upload:before{content:"\E7C3"}.el-icon-picture-outline-round:before{content:"\E75F"}.el-icon-picture-outline:before{content:"\E75E"}.el-icon-picture:before{content:"\E79F"}.el-icon-close:before{content:"\E6DB"}.el-icon-check:before{content:"\E6DA"}.el-icon-plus:before{content:"\E6D9"}.el-icon-minus:before{content:"\E6D8"}.el-icon-help:before{content:"\E73D"}.el-icon-s-help:before{content:"\E7B3"}.el-icon-circle-close:before{content:"\E78D"}.el-icon-circle-check:before{content:"\E720"}.el-icon-circle-plus-outline:before{content:"\E723"}.el-icon-remove-outline:before{content:"\E722"}.el-icon-zoom-out:before{content:"\E776"}.el-icon-zoom-in:before{content:"\E777"}.el-icon-error:before{content:"\E79D"}.el-icon-success:before{content:"\E79C"}.el-icon-circle-plus:before{content:"\E7A0"}.el-icon-remove:before{content:"\E7A2"}.el-icon-info:before{content:"\E7A1"}.el-icon-question:before{content:"\E7A4"}.el-icon-warning-outline:before{content:"\E6C9"}.el-icon-warning:before{content:"\E7A3"}.el-icon-goods:before{content:"\E7C2"}.el-icon-s-goods:before{content:"\E7B2"}.el-icon-star-off:before{content:"\E717"}.el-icon-star-on:before{content:"\E797"}.el-icon-more-outline:before{content:"\E6CC"}.el-icon-more:before{content:"\E794"}.el-icon-phone-outline:before{content:"\E6CB"}.el-icon-phone:before{content:"\E795"}.el-icon-user:before{content:"\E6E3"}.el-icon-user-solid:before{content:"\E7A5"}.el-icon-setting:before{content:"\E6CA"}.el-icon-s-tools:before{content:"\E7AC"}.el-icon-delete:before{content:"\E6D7"}.el-icon-delete-solid:before{content:"\E7C9"}.el-icon-eleme:before{content:"\E7C7"}.el-icon-platform-eleme:before{content:"\E7CA"}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotating{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.el-pagination{white-space:nowrap;padding:2px 5px;color:#303133;font-weight:700}.el-pagination:after,.el-pagination:before{display:table;content:""}.el-pagination:after{clear:both}.el-pagination button,.el-pagination span:not([class*=suffix]){display:inline-block;font-size:13px;min-width:35.5px;height:28px;line-height:28px;vertical-align:top;-webkit-box-sizing:border-box;box-sizing:border-box}.el-pagination .el-input__inner{text-align:center;-moz-appearance:textfield;line-height:normal}.el-pagination .el-input__suffix{right:0;-webkit-transform:scale(.8);transform:scale(.8)}.el-pagination .el-select .el-input{width:100px;margin:0 5px}.el-pagination .el-select .el-input .el-input__inner{padding-right:25px;border-radius:3px}.el-pagination button{border:none;padding:0 6px;background:0 0}.el-pagination button:focus{outline:0}.el-pagination button:hover{color:#409eff}.el-pagination button:disabled{color:#c0c4cc;background-color:#fff;cursor:not-allowed}.el-pagination .btn-next,.el-pagination .btn-prev{background:50% no-repeat #fff;background-size:16px;cursor:pointer;margin:0;color:#303133}.el-pagination .btn-next .el-icon,.el-pagination .btn-prev .el-icon{display:block;font-size:12px;font-weight:700}.el-pagination .btn-prev{padding-right:12px}.el-pagination .btn-next{padding-left:12px}.el-pagination .el-pager li.disabled{color:#c0c4cc;cursor:not-allowed}.el-pager li,.el-pager li.btn-quicknext:hover,.el-pager li.btn-quickprev:hover{cursor:pointer}.el-pagination--small .btn-next,.el-pagination--small .btn-prev,.el-pagination--small .el-pager li,.el-pagination--small .el-pager li.btn-quicknext,.el-pagination--small .el-pager li.btn-quickprev,.el-pagination--small .el-pager li:last-child{border-color:transparent;font-size:12px;line-height:22px;height:22px;min-width:22px}.el-pagination--small .more:before,.el-pagination--small li.more:before{line-height:24px}.el-pagination--small button,.el-pagination--small span:not([class*=suffix]){height:22px;line-height:22px}.el-pagination--small .el-pagination__editor,.el-pagination--small .el-pagination__editor.el-input .el-input__inner{height:22px}.el-pagination__sizes{margin:0 10px 0 0;font-weight:400;color:#606266}.el-pagination__sizes .el-input .el-input__inner{font-size:13px;padding-left:8px}.el-pagination__sizes .el-input .el-input__inner:hover{border-color:#409eff}.el-pagination__total{margin-right:10px;font-weight:400;color:#606266}.el-pagination__jump{margin-left:24px;font-weight:400;color:#606266}.el-pagination__jump .el-input__inner{padding:0 3px}.el-pagination__rightwrapper{float:right}.el-pagination__editor{line-height:18px;padding:0 2px;height:28px;text-align:center;margin:0 2px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:3px}.el-pager,.el-pagination.is-background .btn-next,.el-pagination.is-background .btn-prev{padding:0}.el-pagination__editor.el-input{width:50px}.el-pagination__editor.el-input .el-input__inner{height:28px}.el-pagination__editor .el-input__inner::-webkit-inner-spin-button,.el-pagination__editor .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.el-pagination.is-background .btn-next,.el-pagination.is-background .btn-prev,.el-pagination.is-background .el-pager li{margin:0 5px;background-color:#f4f4f5;color:#606266;min-width:30px;border-radius:2px}.el-pagination.is-background .btn-next.disabled,.el-pagination.is-background .btn-next:disabled,.el-pagination.is-background .btn-prev.disabled,.el-pagination.is-background .btn-prev:disabled,.el-pagination.is-background .el-pager li.disabled{color:#c0c4cc}.el-pagination.is-background .el-pager li:not(.disabled):hover{color:#409eff}.el-pagination.is-background .el-pager li:not(.disabled).active{background-color:#409eff;color:#fff}.el-dialog,.el-pager li{background:#fff;-webkit-box-sizing:border-box}.el-pagination.is-background.el-pagination--small .btn-next,.el-pagination.is-background.el-pagination--small .btn-prev,.el-pagination.is-background.el-pagination--small .el-pager li{margin:0 3px;min-width:22px}.el-pager,.el-pager li{vertical-align:top;margin:0;display:inline-block}.el-pager{-ms-user-select:none;user-select:none;list-style:none;font-size:0}.el-date-table,.el-pager,.el-table th{-webkit-user-select:none;-moz-user-select:none}.el-pager .more:before{line-height:30px}.el-pager li{padding:0 4px;font-size:13px;min-width:35.5px;height:28px;line-height:28px;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center}.el-menu--collapse .el-menu .el-submenu,.el-menu--popup{min-width:200px}.el-pager li.btn-quicknext,.el-pager li.btn-quickprev{line-height:28px;color:#303133}.el-pager li.btn-quicknext.disabled,.el-pager li.btn-quickprev.disabled{color:#c0c4cc}.el-pager li.active+li{border-left:0}.el-pager li:hover{color:#409eff}.el-pager li.active{color:#409eff;cursor:default}@-webkit-keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{to{opacity:0}}.el-dialog{position:relative;margin:0 auto 50px;border-radius:2px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.3);box-shadow:0 1px 3px rgba(0,0,0,.3);-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.el-dialog.is-fullscreen{width:100%;margin-top:0;margin-bottom:0;height:100%;overflow:auto}.el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.el-dialog__header{padding:20px 20px 10px}.el-dialog__headerbtn{position:absolute;top:20px;right:20px;padding:0;background:0 0;border:none;outline:0;cursor:pointer;font-size:16px}.el-dialog__headerbtn .el-dialog__close{color:#909399}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:#409eff}.el-dialog__title{line-height:24px;font-size:18px;color:#303133}.el-dialog__body{padding:30px 20px;color:#606266;font-size:14px;word-break:break-all}.el-dialog__footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial;padding:25px 25px 30px}.el-dialog--center .el-dialog__footer{text-align:inherit}.dialog-fade-enter-active{-webkit-animation:dialog-fade-in .3s;animation:dialog-fade-in .3s}.dialog-fade-leave-active{-webkit-animation:dialog-fade-out .3s;animation:dialog-fade-out .3s}@-webkit-keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-autocomplete{position:relative;display:inline-block}.el-autocomplete-suggestion{margin:5px 0;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:4px;border:1px solid #e4e7ed;-webkit-box-sizing:border-box;box-sizing:border-box;background-color:#fff}.el-dropdown-menu,.el-menu--collapse .el-submenu .el-menu{z-index:10;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-autocomplete-suggestion__wrap{max-height:280px;padding:10px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-autocomplete-suggestion__list{margin:0;padding:0}.el-autocomplete-suggestion li{padding:0 20px;margin:0;line-height:34px;cursor:pointer;color:#606266;font-size:14px;list-style:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-autocomplete-suggestion li.highlighted,.el-autocomplete-suggestion li:hover{background-color:#f5f7fa}.el-autocomplete-suggestion li.divider{margin-top:6px;border-top:1px solid #000}.el-autocomplete-suggestion li.divider:last-child{margin-bottom:-6px}.el-autocomplete-suggestion.is-loading li{text-align:center;height:100px;line-height:100px;font-size:20px;color:#999}.el-autocomplete-suggestion.is-loading li:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-autocomplete-suggestion.is-loading li:hover{background-color:#fff}.el-autocomplete-suggestion.is-loading .el-icon-loading{vertical-align:middle}.el-dropdown{display:inline-block;position:relative;color:#606266;font-size:14px}.el-dropdown .el-button-group{display:block}.el-dropdown .el-button-group .el-button{float:none}.el-dropdown .el-dropdown__caret-button{padding-left:5px;padding-right:5px;position:relative;border-left:none}.el-dropdown .el-dropdown__caret-button:before{content:"";position:absolute;display:block;width:1px;top:5px;bottom:5px;left:0;background:hsla(0,0%,100%,.5)}.el-dropdown .el-dropdown__caret-button.el-button--default:before{background:rgba(220,223,230,.5)}.el-dropdown .el-dropdown__caret-button:hover:before{top:0;bottom:0}.el-dropdown .el-dropdown__caret-button .el-dropdown__icon{padding-left:0}.el-dropdown__icon{font-size:12px;margin:0 3px}.el-dropdown-menu{position:absolute;top:0;left:0;padding:10px 0;margin:5px 0;background-color:#fff;border:1px solid #ebeef5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-dropdown-menu__item{list-style:none;line-height:36px;padding:0 20px;margin:0;font-size:14px;color:#606266;cursor:pointer;outline:0}.el-dropdown-menu__item:focus,.el-dropdown-menu__item:not(.is-disabled):hover{background-color:#ecf5ff;color:#66b1ff}.el-dropdown-menu__item i{margin-right:5px}.el-dropdown-menu__item--divided{position:relative;margin-top:6px;border-top:1px solid #ebeef5}.el-dropdown-menu__item--divided:before{content:"";height:6px;display:block;margin:0 -20px;background-color:#fff}.el-dropdown-menu__item.is-disabled{cursor:default;color:#bbb;pointer-events:none}.el-dropdown-menu--medium{padding:6px 0}.el-dropdown-menu--medium .el-dropdown-menu__item{line-height:30px;padding:0 17px;font-size:14px}.el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:6px}.el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:6px;margin:0 -17px}.el-dropdown-menu--small{padding:6px 0}.el-dropdown-menu--small .el-dropdown-menu__item{line-height:27px;padding:0 15px;font-size:13px}.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:4px}.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:4px;margin:0 -15px}.el-dropdown-menu--mini{padding:3px 0}.el-dropdown-menu--mini .el-dropdown-menu__item{line-height:24px;padding:0 10px;font-size:12px}.el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:3px}.el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:3px;margin:0 -10px}.el-menu{border-right:1px solid #e6e6e6;list-style:none;position:relative;margin:0;padding-left:0}.el-menu,.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus,.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover,.el-menu--horizontal>.el-submenu .el-submenu__title:hover{background-color:#fff}.el-menu:after,.el-menu:before{display:table;content:""}.el-menu:after{clear:both}.el-menu.el-menu--horizontal{border-bottom:1px solid #e6e6e6}.el-menu--horizontal{border-right:none}.el-menu--horizontal>.el-menu-item{float:left;height:60px;line-height:60px;margin:0;border-bottom:2px solid transparent;color:#909399}.el-menu--horizontal>.el-menu-item a,.el-menu--horizontal>.el-menu-item a:hover{color:inherit}.el-menu--horizontal>.el-submenu{float:left}.el-menu--horizontal>.el-submenu:focus,.el-menu--horizontal>.el-submenu:hover{outline:0}.el-menu--horizontal>.el-submenu:focus .el-submenu__title,.el-menu--horizontal>.el-submenu:hover .el-submenu__title{color:#303133}.el-menu--horizontal>.el-submenu.is-active .el-submenu__title{border-bottom:2px solid #409eff;color:#303133}.el-menu--horizontal>.el-submenu .el-submenu__title{height:60px;line-height:60px;border-bottom:2px solid transparent;color:#909399}.el-menu--horizontal>.el-submenu .el-submenu__icon-arrow{position:static;vertical-align:middle;margin-left:8px;margin-top:-3px}.el-menu--horizontal .el-menu .el-menu-item,.el-menu--horizontal .el-menu .el-submenu__title{background-color:#fff;float:none;height:36px;line-height:36px;padding:0 10px;color:#909399}.el-menu--horizontal .el-menu .el-menu-item.is-active,.el-menu--horizontal .el-menu .el-submenu.is-active>.el-submenu__title{color:#303133}.el-menu--horizontal .el-menu-item:not(.is-disabled):focus,.el-menu--horizontal .el-menu-item:not(.is-disabled):hover{outline:0;color:#303133}.el-menu--horizontal>.el-menu-item.is-active{border-bottom:2px solid #409eff;color:#303133}.el-menu--collapse{width:64px}.el-menu--collapse>.el-menu-item [class^=el-icon-],.el-menu--collapse>.el-submenu>.el-submenu__title [class^=el-icon-]{margin:0;vertical-align:middle;width:24px;text-align:center}.el-menu--collapse>.el-menu-item .el-submenu__icon-arrow,.el-menu--collapse>.el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}.el-menu--collapse>.el-menu-item span,.el-menu--collapse>.el-submenu>.el-submenu__title span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}.el-menu--collapse>.el-menu-item.is-active i{color:inherit}.el-menu--collapse .el-submenu{position:relative}.el-menu--collapse .el-submenu .el-menu{position:absolute;margin-left:5px;top:0;left:100%;border:1px solid #e4e7ed;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-menu-item,.el-submenu__title{height:56px;line-height:56px;position:relative;-webkit-box-sizing:border-box;white-space:nowrap;list-style:none}.el-menu--collapse .el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:none;transform:none}.el-menu--popup{z-index:100;border:none;padding:5px 0;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-menu--popup-bottom-start{margin-top:5px}.el-menu--popup-right-start{margin-left:5px;margin-right:5px}.el-menu-item{font-size:14px;color:#303133;padding:0 20px;cursor:pointer;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box}.el-menu-item *{vertical-align:middle}.el-menu-item i{color:#909399}.el-menu-item:focus,.el-menu-item:hover{outline:0;background-color:#ecf5ff}.el-menu-item.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-menu-item [class^=el-icon-]{margin-right:5px;width:24px;text-align:center;font-size:18px;vertical-align:middle}.el-menu-item.is-active{color:#409eff}.el-menu-item.is-active i{color:inherit}.el-submenu{list-style:none;margin:0;padding-left:0}.el-submenu__title{font-size:14px;color:#303133;padding:0 20px;cursor:pointer;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box}.el-submenu__title *{vertical-align:middle}.el-submenu__title i{color:#909399}.el-submenu__title:focus,.el-submenu__title:hover{outline:0;background-color:#ecf5ff}.el-submenu__title.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-submenu__title:hover{background-color:#ecf5ff}.el-submenu .el-menu{border:none}.el-submenu .el-menu-item{height:50px;line-height:50px;padding:0 45px;min-width:200px}.el-submenu__icon-arrow{position:absolute;top:50%;right:20px;margin-top:-7px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:12px}.el-submenu.is-active .el-submenu__title{border-bottom-color:#409eff}.el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.el-submenu.is-disabled .el-menu-item,.el-submenu.is-disabled .el-submenu__title{opacity:.25;cursor:not-allowed;background:0 0!important}.el-submenu [class^=el-icon-]{vertical-align:middle;margin-right:5px;width:24px;text-align:center;font-size:18px}.el-menu-item-group>ul{padding:0}.el-menu-item-group__title{padding:7px 0 7px 20px;line-height:normal;font-size:12px;color:#909399}.el-radio-button__inner,.el-radio-group{display:inline-block;line-height:1;vertical-align:middle}.horizontal-collapse-transition .el-submenu__title .el-submenu__icon-arrow{-webkit-transition:.2s;transition:.2s;opacity:0}.el-radio-group{font-size:0}.el-radio-button{position:relative;display:inline-block;outline:0}.el-radio-button__inner{white-space:nowrap;background:#fff;border:1px solid #dcdfe6;font-weight:500;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-radio-button__inner.is-round{padding:12px 20px}.el-radio-button__inner:hover{color:#409eff}.el-radio-button__inner [class*=el-icon-]{line-height:.9}.el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.el-radio-button:first-child .el-radio-button__inner{border-left:1px solid #dcdfe6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-radio-button__orig-radio{opacity:0;outline:0;position:absolute;z-index:-1}.el-radio-button__orig-radio:checked+.el-radio-button__inner{color:#fff;background-color:#409eff;border-color:#409eff;-webkit-box-shadow:-1px 0 0 0 #409eff;box-shadow:-1px 0 0 0 #409eff}.el-radio-button__orig-radio:disabled+.el-radio-button__inner{color:#c0c4cc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#ebeef5;-webkit-box-shadow:none;box-shadow:none}.el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner{background-color:#f2f6fc}.el-radio-button:last-child .el-radio-button__inner{border-radius:0 4px 4px 0}.el-popover,.el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:4px}.el-radio-button--medium .el-radio-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-radio-button--medium .el-radio-button__inner.is-round{padding:10px 20px}.el-radio-button--small .el-radio-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-radio-button--small .el-radio-button__inner.is-round{padding:9px 15px}.el-radio-button--mini .el-radio-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-radio-button--mini .el-radio-button__inner.is-round{padding:7px 15px}.el-radio-button:focus:not(.is-focus):not(:active):not(.is-disabled){-webkit-box-shadow:0 0 2px 2px #409eff;box-shadow:0 0 2px 2px #409eff}.el-switch{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;font-size:14px;line-height:20px;height:20px;vertical-align:middle}.el-switch__core,.el-switch__label{display:inline-block;cursor:pointer}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__label{-webkit-transition:.2s;transition:.2s;height:20px;font-size:14px;font-weight:500;vertical-align:middle;color:#303133}.el-switch__label.is-active{color:#409eff}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{line-height:1;font-size:14px;display:inline-block}.el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.el-switch__core{margin:0;position:relative;width:40px;height:20px;border:1px solid #dcdfe6;outline:0;border-radius:10px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#dcdfe6;-webkit-transition:border-color .3s,background-color .3s;transition:border-color .3s,background-color .3s;vertical-align:middle}.el-switch__core:after{content:"";position:absolute;top:1px;left:1px;border-radius:100%;-webkit-transition:all .3s;transition:all .3s;width:16px;height:16px;background-color:#fff}.el-switch.is-checked .el-switch__core{border-color:#409eff;background-color:#409eff}.el-switch.is-checked .el-switch__core:after{left:100%;margin-left:-17px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter,.el-switch .label-fade-leave-active{opacity:0}.el-select-dropdown{position:absolute;z-index:1001;border:1px solid #e4e7ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#409eff;background-color:#fff}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected:after{position:absolute;right:20px;font-family:element-icons;content:"\E6DA";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#606266;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.el-select-dropdown__item.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-select-dropdown__item.is-disabled:hover{background-color:#fff}.el-select-dropdown__item.hover,.el-select-dropdown__item:hover{background-color:#f5f7fa}.el-select-dropdown__item.selected{color:#409eff;font-weight:700}.el-select-group{margin:0;padding:0}.el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.el-select-group__wrap:not(:last-of-type):after{content:"";position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#e4e7ed}.el-select-group__title{padding-left:20px;font-size:12px;color:#909399;line-height:30px}.el-select-group .el-select-dropdown__item{padding-left:20px}.el-select{display:inline-block;position:relative}.el-select .el-select__tags>span{display:contents}.el-select:hover .el-input__inner{border-color:#c0c4cc}.el-select .el-input__inner{cursor:pointer;padding-right:35px}.el-select .el-input__inner:focus{border-color:#409eff}.el-select .el-input .el-select__caret{color:#c0c4cc;font-size:14px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:rotate(180deg);transform:rotate(180deg);cursor:pointer}.el-select .el-input .el-select__caret.is-reverse{-webkit-transform:rotate(0);transform:rotate(0)}.el-select .el-input .el-select__caret.is-show-close{font-size:14px;text-align:center;-webkit-transform:rotate(180deg);transform:rotate(180deg);border-radius:100%;color:#c0c4cc;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.el-select .el-input .el-select__caret.is-show-close:hover{color:#909399}.el-select .el-input.is-disabled .el-input__inner{cursor:not-allowed}.el-select .el-input.is-disabled .el-input__inner:hover{border-color:#e4e7ed}.el-select .el-input.is-focus .el-input__inner{border-color:#409eff}.el-select>.el-input{display:block}.el-select__input{border:none;outline:0;padding:0;margin-left:15px;color:#666;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px;background-color:transparent}.el-select__input.is-mini{height:14px}.el-select__close{cursor:pointer;position:absolute;top:8px;z-index:1000;right:25px;color:#c0c4cc;line-height:18px;font-size:14px}.el-select__close:hover{color:#909399}.el-select__tags{position:absolute;line-height:normal;white-space:normal;z-index:1;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-select .el-tag__close{margin-top:-2px}.el-select .el-tag{-webkit-box-sizing:border-box;box-sizing:border-box;border-color:transparent;margin:2px 0 2px 6px;background-color:#f0f2f5}.el-select .el-tag__close.el-icon-close{background-color:#c0c4cc;right:-7px;top:0;color:#fff}.el-select .el-tag__close.el-icon-close:hover{background-color:#909399}.el-table,.el-table__expanded-cell{background-color:#fff}.el-select .el-tag__close.el-icon-close:before{display:block;-webkit-transform:translateY(.5px);transform:translateY(.5px)}.el-table{position:relative;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-flex:1;-ms-flex:1;flex:1;width:100%;max-width:100%;font-size:14px;color:#606266}.el-table--mini,.el-table--small,.el-table__expand-icon{font-size:12px}.el-table__empty-block{min-height:60px;text-align:center;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-table__empty-text{line-height:60px;width:50%;color:#909399}.el-table__expand-column .cell{padding:0;text-align:center}.el-table__expand-icon{position:relative;cursor:pointer;color:#666;-webkit-transition:-webkit-transform .2s ease-in-out;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out,-webkit-transform .2s ease-in-out;height:20px}.el-table__expand-icon--expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-table__expand-icon>.el-icon{position:absolute;left:50%;top:50%;margin-left:-5px;margin-top:-5px}.el-table__expanded-cell[class*=cell]{padding:20px 50px}.el-table__expanded-cell:hover{background-color:transparent!important}.el-table__placeholder{display:inline-block;width:20px}.el-table__append-wrapper{overflow:hidden}.el-table--fit{border-right:0;border-bottom:0}.el-table--fit td.gutter,.el-table--fit th.gutter{border-right-width:1px}.el-table--scrollable-x .el-table__body-wrapper{overflow-x:auto}.el-table--scrollable-y .el-table__body-wrapper{overflow-y:auto}.el-table thead{color:#909399;font-weight:500}.el-table thead.is-group th{background:#f5f7fa}.el-table th,.el-table tr{background-color:#fff}.el-table td,.el-table th{padding:12px 0;min-width:0;-webkit-box-sizing:border-box;box-sizing:border-box;text-overflow:ellipsis;vertical-align:middle;position:relative;text-align:left}.el-table td.is-center,.el-table th.is-center{text-align:center}.el-table td.is-right,.el-table th.is-right{text-align:right}.el-table td.gutter,.el-table th.gutter{width:15px;border-right-width:0;border-bottom-width:0;padding:0}.el-table--medium td,.el-table--medium th{padding:10px 0}.el-table--small td,.el-table--small th{padding:8px 0}.el-table--mini td,.el-table--mini th{padding:6px 0}.el-table--border td:first-child .cell,.el-table--border th:first-child .cell,.el-table .cell{padding-left:10px}.el-table tr input[type=checkbox]{margin:0}.el-table td,.el-table th.is-leaf{border-bottom:1px solid #ebeef5}.el-table th.is-sortable{cursor:pointer}.el-table th{overflow:hidden;-ms-user-select:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-table th>.cell{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;vertical-align:middle;padding-left:10px;padding-right:10px;width:100%}.el-table th>.cell.highlight{color:#409eff}.el-table th.required>div:before{display:inline-block;content:"";width:8px;height:8px;border-radius:50%;background:#ff4d51;margin-right:5px;vertical-align:middle}.el-table td div{-webkit-box-sizing:border-box;box-sizing:border-box}.el-table td.gutter{width:0}.el-table .cell{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;white-space:normal;word-break:break-all;line-height:23px;padding-right:10px}.el-table .cell.el-tooltip{white-space:nowrap;min-width:50px}.el-table--border,.el-table--group{border:1px solid #ebeef5}.el-table--border:after,.el-table--group:after,.el-table:before{content:"";position:absolute;background-color:#ebeef5;z-index:1}.el-table--border:after,.el-table--group:after{top:0;right:0;width:1px;height:100%}.el-table:before{left:0;bottom:0;width:100%;height:1px}.el-table--border{border-right:none;border-bottom:none}.el-table--border.el-loading-parent--relative{border-color:transparent}.el-table--border td,.el-table--border th,.el-table__body-wrapper .el-table--border.is-scrolling-left~.el-table__fixed{border-right:1px solid #ebeef5}.el-table--border th,.el-table--border th.gutter:last-of-type,.el-table__fixed-right-patch{border-bottom:1px solid #ebeef5}.el-table__fixed,.el-table__fixed-right{position:absolute;top:0;left:0;overflow-x:hidden;overflow-y:hidden;-webkit-box-shadow:0 0 10px rgba(0,0,0,.12);box-shadow:0 0 10px rgba(0,0,0,.12)}.el-table__fixed-right:before,.el-table__fixed:before{content:"";position:absolute;left:0;bottom:0;width:100%;height:1px;background-color:#ebeef5;z-index:4}.el-table__fixed-right-patch{position:absolute;top:-1px;right:0;background-color:#fff}.el-table__fixed-right{top:0;left:auto;right:0}.el-table__fixed-right .el-table__fixed-body-wrapper,.el-table__fixed-right .el-table__fixed-footer-wrapper,.el-table__fixed-right .el-table__fixed-header-wrapper{left:auto;right:0}.el-table__fixed-header-wrapper{position:absolute;left:0;top:0;z-index:3}.el-table__fixed-footer-wrapper{position:absolute;left:0;bottom:0;z-index:3}.el-table__fixed-footer-wrapper tbody td{border-top:1px solid #ebeef5;background-color:#f5f7fa;color:#606266}.el-table__fixed-body-wrapper{position:absolute;left:0;top:37px;overflow:hidden;z-index:3}.el-table__body-wrapper,.el-table__footer-wrapper,.el-table__header-wrapper{width:100%}.el-table__footer-wrapper{margin-top:-1px}.el-table__footer-wrapper td{border-top:1px solid #ebeef5}.el-table__body,.el-table__footer,.el-table__header{table-layout:fixed;border-collapse:separate}.el-table__footer-wrapper,.el-table__header-wrapper{overflow:hidden}.el-table__footer-wrapper tbody td,.el-table__header-wrapper tbody td{background-color:#f5f7fa;color:#606266}.el-table__body-wrapper{overflow:hidden;position:relative}.el-table__body-wrapper.is-scrolling-left~.el-table__fixed,.el-table__body-wrapper.is-scrolling-none~.el-table__fixed,.el-table__body-wrapper.is-scrolling-none~.el-table__fixed-right,.el-table__body-wrapper.is-scrolling-right~.el-table__fixed-right{-webkit-box-shadow:none;box-shadow:none}.el-picker-panel,.el-table-filter{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-table__body-wrapper .el-table--border.is-scrolling-right~.el-table__fixed-right{border-left:1px solid #ebeef5}.el-table .caret-wrapper{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:34px;width:24px;vertical-align:middle;cursor:pointer;overflow:initial;position:relative}.el-table .sort-caret{width:0;height:0;border:5px solid transparent;position:absolute;left:7px}.el-table .sort-caret.ascending{border-bottom-color:#c0c4cc;top:5px}.el-table .sort-caret.descending{border-top-color:#c0c4cc;bottom:7px}.el-table .ascending .sort-caret.ascending{border-bottom-color:#409eff}.el-table .descending .sort-caret.descending{border-top-color:#409eff}.el-table .hidden-columns{position:absolute;z-index:-1}.el-table--striped .el-table__body tr.el-table__row--striped td{background:#fafafa}.el-table--striped .el-table__body tr.el-table__row--striped.current-row td{background-color:#ecf5ff}.el-table__body tr.hover-row.current-row>td,.el-table__body tr.hover-row.el-table__row--striped.current-row>td,.el-table__body tr.hover-row.el-table__row--striped>td,.el-table__body tr.hover-row>td{background-color:#f5f7fa}.el-table__body tr.current-row>td{background-color:#ecf5ff}.el-table__column-resize-proxy{position:absolute;left:200px;top:0;bottom:0;width:0;border-left:1px solid #ebeef5;z-index:10}.el-table__column-filter-trigger{display:inline-block;line-height:34px;cursor:pointer}.el-table__column-filter-trigger i{color:#909399;font-size:12px;-webkit-transform:scale(.75);transform:scale(.75)}.el-table--enable-row-transition .el-table__body td{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}.el-table--enable-row-hover .el-table__body tr:hover>td{background-color:#f5f7fa}.el-table--fluid-height .el-table__fixed,.el-table--fluid-height .el-table__fixed-right{bottom:0;overflow:hidden}.el-table [class*=el-table__row--level] .el-table__expand-icon{display:inline-block;width:20px;line-height:20px;height:20px;text-align:center;margin-right:3px}.el-table-column--selection .cell{padding-left:14px;padding-right:14px}.el-table-filter{border:1px solid #ebeef5;border-radius:2px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:2px 0}.el-date-table td,.el-date-table td div{height:30px;-webkit-box-sizing:border-box}.el-table-filter__list{padding:5px 0;margin:0;list-style:none;min-width:100px}.el-table-filter__list-item{line-height:36px;padding:0 10px;cursor:pointer;font-size:14px}.el-table-filter__list-item:hover{background-color:#ecf5ff;color:#66b1ff}.el-table-filter__list-item.is-active{background-color:#409eff;color:#fff}.el-table-filter__content{min-width:100px}.el-table-filter__bottom{border-top:1px solid #ebeef5;padding:8px}.el-table-filter__bottom button{background:0 0;border:none;color:#606266;cursor:pointer;font-size:13px;padding:0 3px}.el-date-table.is-week-mode .el-date-table__row.current div,.el-date-table.is-week-mode .el-date-table__row:hover div,.el-date-table td.in-range div,.el-date-table td.in-range div:hover{background-color:#f2f6fc}.el-table-filter__bottom button:hover{color:#409eff}.el-table-filter__bottom button:focus{outline:0}.el-table-filter__bottom button.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-table-filter__wrap{max-height:280px}.el-table-filter__checkbox-group{padding:10px}.el-table-filter__checkbox-group label.el-checkbox{display:block;margin-right:5px;margin-bottom:8px;margin-left:5px}.el-table-filter__checkbox-group .el-checkbox:last-child{margin-bottom:0}.el-date-table{font-size:12px;-ms-user-select:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-date-table.is-week-mode .el-date-table__row:hover td.available:hover{color:#606266}.el-date-table.is-week-mode .el-date-table__row:hover td:first-child div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.el-date-table.is-week-mode .el-date-table__row:hover td:last-child div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.el-date-table td{width:32px;padding:4px 0;text-align:center;cursor:pointer;position:relative}.el-date-table td,.el-date-table td div{-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-table td div{padding:3px 0}.el-date-table td span{width:24px;height:24px;display:block;margin:0 auto;line-height:24px;position:absolute;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);border-radius:50%}.el-date-table td.next-month,.el-date-table td.prev-month{color:#c0c4cc}.el-date-table td.today{position:relative}.el-date-table td.today span{color:#409eff;font-weight:700}.el-date-table td.today.end-date span,.el-date-table td.today.start-date span{color:#fff}.el-date-table td.available:hover{color:#409eff}.el-date-table td.current:not(.disabled) span{color:#fff;background-color:#409eff}.el-date-table td.end-date div,.el-date-table td.start-date div{color:#fff}.el-date-table td.end-date span,.el-date-table td.start-date span{background-color:#409eff}.el-date-table td.start-date div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.el-date-table td.end-date div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.el-date-table td.disabled div{background-color:#f5f7fa;opacity:1;cursor:not-allowed;color:#c0c4cc}.el-date-table td.selected div{margin-left:5px;margin-right:5px;background-color:#f2f6fc;border-radius:15px}.el-date-table td.selected div:hover{background-color:#f2f6fc}.el-date-table td.selected span{background-color:#409eff;color:#fff;border-radius:15px}.el-date-table td.week{font-size:80%;color:#606266}.el-month-table,.el-year-table{font-size:12px;border-collapse:collapse}.el-date-table th{padding:5px;color:#606266;font-weight:400;border-bottom:1px solid #ebeef5}.el-month-table{margin:-1px}.el-month-table td{text-align:center;padding:8px 0;cursor:pointer}.el-month-table td div{height:48px;padding:6px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-month-table td.today .cell{color:#409eff;font-weight:700}.el-month-table td.today.end-date .cell,.el-month-table td.today.start-date .cell{color:#fff}.el-month-table td.disabled .cell{background-color:#f5f7fa;cursor:not-allowed;color:#c0c4cc}.el-month-table td.disabled .cell:hover{color:#c0c4cc}.el-month-table td .cell{width:60px;height:36px;display:block;line-height:36px;color:#606266;margin:0 auto;border-radius:18px}.el-month-table td .cell:hover{color:#409eff}.el-month-table td.in-range div,.el-month-table td.in-range div:hover{background-color:#f2f6fc}.el-month-table td.end-date div,.el-month-table td.start-date div{color:#fff}.el-month-table td.end-date .cell,.el-month-table td.start-date .cell{color:#fff;background-color:#409eff}.el-month-table td.start-date div{border-top-left-radius:24px;border-bottom-left-radius:24px}.el-month-table td.end-date div{border-top-right-radius:24px;border-bottom-right-radius:24px}.el-month-table td.current:not(.disabled) .cell{color:#409eff}.el-year-table{margin:-1px}.el-year-table .el-icon{color:#303133}.el-year-table td{text-align:center;padding:20px 3px;cursor:pointer}.el-year-table td.today .cell{color:#409eff;font-weight:700}.el-year-table td.disabled .cell{background-color:#f5f7fa;cursor:not-allowed;color:#c0c4cc}.el-year-table td.disabled .cell:hover{color:#c0c4cc}.el-year-table td .cell{width:48px;height:32px;display:block;line-height:32px;color:#606266;margin:0 auto}.el-year-table td .cell:hover,.el-year-table td.current:not(.disabled) .cell{color:#409eff}.el-date-range-picker{width:646px}.el-date-range-picker.has-sidebar{width:756px}.el-date-range-picker table{table-layout:fixed;width:100%}.el-date-range-picker .el-picker-panel__body{min-width:513px}.el-date-range-picker .el-picker-panel__content{margin:0}.el-date-range-picker__header{position:relative;text-align:center;height:28px}.el-date-range-picker__header [class*=arrow-left]{float:left}.el-date-range-picker__header [class*=arrow-right]{float:right}.el-date-range-picker__header div{font-size:16px;font-weight:500;margin-right:50px}.el-date-range-picker__content{float:left;width:50%;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:16px}.el-date-range-picker__content.is-left{border-right:1px solid #e4e4e4}.el-date-range-picker__content .el-date-range-picker__header div{margin-left:50px;margin-right:50px}.el-date-range-picker__editors-wrap{-webkit-box-sizing:border-box;box-sizing:border-box;display:table-cell}.el-date-range-picker__editors-wrap.is-right{text-align:right}.el-date-range-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-range-picker__time-header>.el-icon-arrow-right{font-size:20px;vertical-align:middle;display:table-cell;color:#303133}.el-date-range-picker__time-picker-wrap{position:relative;display:table-cell;padding:0 5px}.el-date-range-picker__time-picker-wrap .el-picker-panel{position:absolute;top:13px;right:0;z-index:1;background:#fff}.el-date-picker{width:322px}.el-date-picker.has-sidebar.has-time{width:434px}.el-date-picker.has-sidebar{width:438px}.el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.el-date-picker .el-picker-panel__content{width:292px}.el-date-picker table{table-layout:fixed;width:100%}.el-date-picker__editor-wrap{position:relative;display:table-cell;padding:0 5px}.el-date-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-picker__header{margin:12px;text-align:center}.el-date-picker__header--bordered{margin-bottom:0;padding-bottom:12px;border-bottom:1px solid #ebeef5}.el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.el-date-picker__header-label{font-size:16px;font-weight:500;padding:0 5px;line-height:22px;text-align:center;cursor:pointer;color:#606266}.el-date-picker__header-label.active,.el-date-picker__header-label:hover{color:#409eff}.el-date-picker__prev-btn{float:left}.el-date-picker__next-btn{float:right}.el-date-picker__time-wrap{padding:10px;text-align:center}.el-date-picker__time-label{float:left;cursor:pointer;line-height:30px;margin-left:10px}.time-select{margin:5px 0;min-width:0}.time-select .el-picker-panel__content{max-height:200px;margin:0}.time-select-item{padding:8px 10px;font-size:14px;line-height:20px}.time-select-item.selected:not(.disabled){color:#409eff;font-weight:700}.time-select-item.disabled{color:#e4e7ed;cursor:not-allowed}.time-select-item:hover{background-color:#f5f7fa;font-weight:700;cursor:pointer}.el-date-editor{position:relative;display:inline-block;text-align:left}.el-date-editor.el-input,.el-date-editor.el-input__inner{width:220px}.el-date-editor--monthrange.el-input,.el-date-editor--monthrange.el-input__inner{width:300px}.el-date-editor--daterange.el-input,.el-date-editor--daterange.el-input__inner,.el-date-editor--timerange.el-input,.el-date-editor--timerange.el-input__inner{width:350px}.el-date-editor--datetimerange.el-input,.el-date-editor--datetimerange.el-input__inner{width:400px}.el-date-editor--dates .el-input__inner{text-overflow:ellipsis;white-space:nowrap}.el-date-editor .el-icon-circle-close{cursor:pointer}.el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#c0c4cc;float:left;line-height:32px}.el-date-editor .el-range-input,.el-date-editor .el-range-separator{height:100%;margin:0;text-align:center;display:inline-block;font-size:14px}.el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;padding:0;width:39%;color:#606266}.el-date-editor .el-range-input::-webkit-input-placeholder{color:#c0c4cc}.el-date-editor .el-range-input:-ms-input-placeholder{color:#c0c4cc}.el-date-editor .el-range-input::-ms-input-placeholder{color:#c0c4cc}.el-date-editor .el-range-input::placeholder{color:#c0c4cc}.el-date-editor .el-range-separator{padding:0 5px;line-height:32px;width:5%;color:#303133}.el-date-editor .el-range__close-icon{font-size:14px;color:#c0c4cc;width:25px;display:inline-block;float:right;line-height:32px}.el-range-editor.el-input__inner{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:3px 10px}.el-range-editor .el-range-input{line-height:1}.el-range-editor.is-active,.el-range-editor.is-active:hover{border-color:#409eff}.el-range-editor--medium.el-input__inner{height:36px}.el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.el-range-editor--medium .el-range-input{font-size:14px}.el-range-editor--medium .el-range__close-icon,.el-range-editor--medium .el-range__icon{line-height:28px}.el-range-editor--small.el-input__inner{height:32px}.el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.el-range-editor--small .el-range-input{font-size:13px}.el-range-editor--small .el-range__close-icon,.el-range-editor--small .el-range__icon{line-height:24px}.el-range-editor--mini.el-input__inner{height:28px}.el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.el-range-editor--mini .el-range-input{font-size:12px}.el-range-editor--mini .el-range__close-icon,.el-range-editor--mini .el-range__icon{line-height:20px}.el-range-editor.is-disabled{background-color:#f5f7fa;border-color:#e4e7ed;color:#c0c4cc;cursor:not-allowed}.el-range-editor.is-disabled:focus,.el-range-editor.is-disabled:hover{border-color:#e4e7ed}.el-range-editor.is-disabled input{background-color:#f5f7fa;color:#c0c4cc;cursor:not-allowed}.el-range-editor.is-disabled input::-webkit-input-placeholder{color:#c0c4cc}.el-range-editor.is-disabled input:-ms-input-placeholder{color:#c0c4cc}.el-range-editor.is-disabled input::-ms-input-placeholder{color:#c0c4cc}.el-range-editor.is-disabled input::placeholder{color:#c0c4cc}.el-range-editor.is-disabled .el-range-separator{color:#c0c4cc}.el-picker-panel{color:#606266;border:1px solid #e4e7ed;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);background:#fff;border-radius:4px;line-height:30px;margin:5px 0}.el-picker-panel,.el-popover,.el-time-panel{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-picker-panel__body-wrapper:after,.el-picker-panel__body:after{content:"";display:table;clear:both}.el-picker-panel__content{position:relative;margin:15px}.el-picker-panel__footer{border-top:1px solid #e4e4e4;padding:4px;text-align:right;background-color:#fff;position:relative;font-size:0}.el-picker-panel__shortcut{display:block;width:100%;border:0;background-color:transparent;line-height:28px;font-size:14px;color:#606266;padding-left:12px;text-align:left;outline:0;cursor:pointer}.el-picker-panel__shortcut:hover{color:#409eff}.el-picker-panel__shortcut.active{background-color:#e6f1fe;color:#409eff}.el-picker-panel__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.el-picker-panel__btn[disabled]{color:#ccc;cursor:not-allowed}.el-picker-panel__icon-btn{font-size:12px;color:#303133;border:0;background:0 0;cursor:pointer;outline:0;margin-top:8px}.el-picker-panel__icon-btn:hover{color:#409eff}.el-picker-panel__icon-btn.is-disabled{color:#bbb}.el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.el-picker-panel__link-btn{vertical-align:middle}.el-picker-panel [slot=sidebar],.el-picker-panel__sidebar{position:absolute;top:0;bottom:0;width:110px;border-right:1px solid #e4e4e4;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;background-color:#fff;overflow:auto}.el-picker-panel [slot=sidebar]+.el-picker-panel__body,.el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.el-time-spinner__wrapper{max-height:190px;overflow:auto;display:inline-block;width:50%;vertical-align:top;position:relative}.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.el-time-spinner__input.el-input .el-input__inner,.el-time-spinner__list{padding:0;text-align:center}.el-time-spinner__wrapper.is-arrow{-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden}.el-time-spinner__wrapper.is-arrow .el-time-spinner__list{-webkit-transform:translateY(-32px);transform:translateY(-32px)}.el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active){background:#fff;cursor:default}.el-time-spinner__arrow{font-size:12px;color:#909399;position:absolute;left:0;width:100%;z-index:1;text-align:center;height:30px;line-height:30px;cursor:pointer}.el-time-spinner__arrow:hover{color:#409eff}.el-time-spinner__arrow.el-icon-arrow-up{top:10px}.el-time-spinner__arrow.el-icon-arrow-down{bottom:10px}.el-time-spinner__input.el-input{width:70%}.el-time-spinner__list{margin:0;list-style:none}.el-time-spinner__list:after,.el-time-spinner__list:before{content:"";display:block;width:100%;height:80px}.el-time-spinner__item{height:32px;line-height:32px;font-size:12px;color:#606266}.el-time-spinner__item:hover:not(.disabled):not(.active){background:#f5f7fa;cursor:pointer}.el-time-spinner__item.active:not(.disabled){color:#303133;font-weight:700}.el-time-spinner__item.disabled{color:#c0c4cc;cursor:not-allowed}.el-time-panel{margin:5px 0;border:1px solid #e4e7ed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px;position:absolute;width:180px;left:0;z-index:1000;user-select:none;-webkit-box-sizing:content-box;box-sizing:content-box}.el-slider__button,.el-slider__button-wrapper,.el-time-panel{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-time-panel__content{font-size:0;position:relative;overflow:hidden}.el-time-panel__content:after,.el-time-panel__content:before{content:"";top:50%;position:absolute;margin-top:-15px;height:32px;z-index:-1;left:0;right:0;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;text-align:left;border-top:1px solid #e4e7ed;border-bottom:1px solid #e4e7ed}.el-time-panel__content:after{left:50%;margin-left:12%;margin-right:12%}.el-time-panel__content:before{padding-left:50%;margin-right:12%;margin-left:12%}.el-time-panel__content.has-seconds:after{left:66.66667%}.el-time-panel__content.has-seconds:before{padding-left:33.33333%}.el-time-panel__footer{border-top:1px solid #e4e4e4;padding:4px;height:36px;line-height:25px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-time-panel__btn{border:none;line-height:28px;padding:0 5px;margin:0 5px;cursor:pointer;background-color:transparent;outline:0;font-size:12px;color:#303133}.el-time-panel__btn.confirm{font-weight:800;color:#409eff}.el-time-range-picker{width:354px;overflow:visible}.el-time-range-picker__content{position:relative;text-align:center;padding:10px}.el-time-range-picker__cell{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:4px 7px 7px;width:50%;display:inline-block}.el-time-range-picker__header{margin-bottom:5px;text-align:center;font-size:14px}.el-time-range-picker__body{border-radius:2px;border:1px solid #e4e7ed}.el-popover{position:absolute;background:#fff;min-width:150px;border:1px solid #ebeef5;padding:12px;z-index:2000;color:#606266;line-height:1.4;text-align:justify;font-size:14px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);word-break:break-all}.el-popover--plain{padding:18px 20px}.el-popover__title{color:#303133;font-size:16px;line-height:1;margin-bottom:12px}.v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-out{to{opacity:0}}.v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.el-popup-parent--hidden{overflow:hidden}.el-message-box{display:inline-block;width:420px;padding-bottom:10px;vertical-align:middle;background-color:#fff;border-radius:4px;border:1px solid #ebeef5;font-size:18px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);text-align:left;overflow:hidden;-webkit-backface-visibility:hidden;backface-visibility:hidden}.el-message-box__wrapper{position:fixed;top:0;bottom:0;left:0;right:0;text-align:center}.el-message-box__wrapper:after{content:"";display:inline-block;height:100%;width:0;vertical-align:middle}.el-message-box__header{position:relative;padding:15px 15px 10px}.el-message-box__title{padding-left:0;margin-bottom:0;font-size:18px;line-height:1;color:#303133}.el-message-box__headerbtn{position:absolute;top:15px;right:15px;padding:0;border:none;outline:0;background:0 0;font-size:16px;cursor:pointer}.el-form-item.is-error .el-input__inner,.el-form-item.is-error .el-input__inner:focus,.el-form-item.is-error .el-textarea__inner,.el-form-item.is-error .el-textarea__inner:focus,.el-message-box__input input.invalid,.el-message-box__input input.invalid:focus{border-color:#f56c6c}.el-message-box__headerbtn .el-message-box__close{color:#909399}.el-message-box__headerbtn:focus .el-message-box__close,.el-message-box__headerbtn:hover .el-message-box__close{color:#409eff}.el-message-box__content{padding:10px 15px;color:#606266;font-size:14px}.el-message-box__container{position:relative}.el-message-box__input{padding-top:15px}.el-message-box__status{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);font-size:24px!important}.el-message-box__status:before{padding-left:1px}.el-message-box__status+.el-message-box__message{padding-left:36px;padding-right:12px}.el-message-box__status.el-icon-success{color:#67c23a}.el-message-box__status.el-icon-info{color:#909399}.el-message-box__status.el-icon-warning{color:#e6a23c}.el-message-box__status.el-icon-error{color:#f56c6c}.el-message-box__message{margin:0}.el-message-box__message p{margin:0;line-height:24px}.el-message-box__errormsg{color:#f56c6c;font-size:12px;min-height:18px;margin-top:2px}.el-message-box__btns{padding:5px 15px 0;text-align:right}.el-message-box__btns button:nth-child(2){margin-left:10px}.el-message-box__btns-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.el-message-box--center{padding-bottom:30px}.el-message-box--center .el-message-box__header{padding-top:30px}.el-message-box--center .el-message-box__title{position:relative;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}.el-message-box--center .el-message-box__status{position:relative;top:auto;padding-right:5px;text-align:center;-webkit-transform:translateY(-1px);transform:translateY(-1px)}.el-message-box--center .el-message-box__message{margin-left:0}.el-message-box--center .el-message-box__btns,.el-message-box--center .el-message-box__content{text-align:center}.el-message-box--center .el-message-box__content{padding-left:27px;padding-right:27px}.msgbox-fade-enter-active{-webkit-animation:msgbox-fade-in .3s;animation:msgbox-fade-in .3s}.msgbox-fade-leave-active{-webkit-animation:msgbox-fade-out .3s;animation:msgbox-fade-out .3s}@-webkit-keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@-webkit-keyframes msgbox-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes msgbox-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-breadcrumb{font-size:14px;line-height:1}.el-breadcrumb:after,.el-breadcrumb:before{display:table;content:""}.el-breadcrumb:after{clear:both}.el-breadcrumb__separator{margin:0 9px;font-weight:700;color:#c0c4cc}.el-breadcrumb__separator[class*=icon]{margin:0 6px;font-weight:400}.el-breadcrumb__item{float:left}.el-breadcrumb__inner{color:#606266}.el-breadcrumb__inner.is-link,.el-breadcrumb__inner a{font-weight:700;text-decoration:none;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1);color:#303133}.el-breadcrumb__inner.is-link:hover,.el-breadcrumb__inner a:hover{color:#409eff;cursor:pointer}.el-breadcrumb__item:last-child .el-breadcrumb__inner,.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover,.el-breadcrumb__item:last-child .el-breadcrumb__inner a,.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover{font-weight:400;color:#606266;cursor:text}.el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}.el-form--label-left .el-form-item__label{text-align:left}.el-form--label-top .el-form-item__label{float:none;display:inline-block;text-align:left;padding:0 0 10px}.el-form--inline .el-form-item{display:inline-block;margin-right:10px;vertical-align:top}.el-form--inline .el-form-item__label{float:none;display:inline-block}.el-form--inline .el-form-item__content{display:inline-block;vertical-align:top}.el-form--inline.el-form--label-top .el-form-item__content{display:block}.el-form-item{margin-bottom:22px}.el-form-item:after,.el-form-item:before{display:table;content:""}.el-form-item:after{clear:both}.el-form-item .el-form-item{margin-bottom:0}.el-form-item--mini.el-form-item,.el-form-item--small.el-form-item{margin-bottom:18px}.el-form-item .el-input__validateIcon{display:none}.el-form-item--medium .el-form-item__content,.el-form-item--medium .el-form-item__label{line-height:36px}.el-form-item--small .el-form-item__content,.el-form-item--small .el-form-item__label{line-height:32px}.el-form-item--small .el-form-item__error{padding-top:2px}.el-form-item--mini .el-form-item__content,.el-form-item--mini .el-form-item__label{line-height:28px}.el-form-item--mini .el-form-item__error{padding-top:1px}.el-form-item__label-wrap{float:left}.el-form-item__label-wrap .el-form-item__label{display:inline-block;float:none}.el-form-item__label{text-align:right;vertical-align:middle;float:left;font-size:14px;color:#606266;line-height:40px;padding:0 12px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-form-item__content{line-height:40px;position:relative;font-size:14px}.el-form-item__content:after,.el-form-item__content:before{display:table;content:""}.el-form-item__content:after{clear:both}.el-form-item__content .el-input-group{vertical-align:top}.el-form-item__error{color:#f56c6c;font-size:12px;line-height:1;padding-top:4px;position:absolute;top:100%;left:0}.el-form-item__error--inline{position:relative;top:auto;left:auto;display:inline-block;margin-left:10px}.el-form-item.is-required:not(.is-no-asterisk) .el-form-item__label-wrap>.el-form-item__label:before,.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label:before{content:"*";color:#f56c6c;margin-right:4px}.el-form-item.is-error .el-input-group__append .el-input__inner,.el-form-item.is-error .el-input-group__prepend .el-input__inner{border-color:transparent}.el-form-item.is-error .el-input__validateIcon{color:#f56c6c}.el-form-item--feedback .el-input__validateIcon{display:inline-block}.el-tabs__header{padding:0;position:relative;margin:0 0 15px}.el-tabs__active-bar{position:absolute;bottom:0;left:0;height:2px;background-color:#409eff;z-index:1;-webkit-transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);list-style:none}.el-tabs__new-tab{float:right;border:1px solid #d3dce6;height:18px;width:18px;line-height:18px;margin:12px 0 9px 10px;border-radius:3px;text-align:center;font-size:12px;color:#d3dce6;cursor:pointer;-webkit-transition:all .15s;transition:all .15s}.el-collapse-item__arrow,.el-tabs__nav{-webkit-transition:-webkit-transform .3s}.el-tabs__new-tab .el-icon-plus{-webkit-transform:scale(.8);transform:scale(.8)}.el-tabs__new-tab:hover{color:#409eff}.el-tabs__nav-wrap{overflow:hidden;margin-bottom:-1px;position:relative}.el-tabs__nav-wrap:after{content:"";position:absolute;left:0;bottom:0;width:100%;height:2px;background-color:#e4e7ed;z-index:1}.el-tabs--border-card>.el-tabs__header .el-tabs__nav-wrap:after,.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap:after{content:none}.el-tabs__nav-wrap.is-scrollable{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-tabs__nav-scroll{overflow:hidden}.el-tabs__nav-next,.el-tabs__nav-prev{position:absolute;cursor:pointer;line-height:44px;font-size:12px;color:#909399}.el-tabs__nav-next{right:0}.el-tabs__nav-prev{left:0}.el-tabs__nav{white-space:nowrap;position:relative;transition:-webkit-transform .3s;-webkit-transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;float:left;z-index:2}.el-tabs__nav.is-stretch{min-width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.el-tabs__nav.is-stretch>*{-webkit-box-flex:1;-ms-flex:1;flex:1;text-align:center}.el-tabs__item{padding:0 20px;height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;line-height:40px;display:inline-block;list-style:none;font-size:14px;font-weight:500;color:#303133;position:relative}.el-tabs__item:focus,.el-tabs__item:focus:active{outline:0}.el-tabs__item:focus.is-active.is-focus:not(:active){-webkit-box-shadow:0 0 2px 2px #409eff inset;box-shadow:inset 0 0 2px 2px #409eff;border-radius:3px}.el-tabs__item .el-icon-close{border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);margin-left:5px}.el-tabs__item .el-icon-close:before{-webkit-transform:scale(.9);transform:scale(.9);display:inline-block}.el-tabs__item .el-icon-close:hover{background-color:#c0c4cc;color:#fff}.el-tabs__item.is-active{color:#409eff}.el-tabs__item:hover{color:#409eff;cursor:pointer}.el-tabs__item.is-disabled{color:#c0c4cc;cursor:default}.el-tabs__content{overflow:hidden;position:relative}.el-tabs--card>.el-tabs__header{border-bottom:1px solid #e4e7ed}.el-tabs--card>.el-tabs__header .el-tabs__nav{border:1px solid #e4e7ed;border-bottom:none;border-radius:4px 4px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-tabs--card>.el-tabs__header .el-tabs__active-bar{display:none}.el-tabs--card>.el-tabs__header .el-tabs__item .el-icon-close{position:relative;font-size:12px;width:0;height:14px;vertical-align:middle;line-height:15px;overflow:hidden;top:-1px;right:-2px;-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable .el-icon-close,.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover .el-icon-close{width:14px}.el-tabs--card>.el-tabs__header .el-tabs__item{border-bottom:1px solid transparent;border-left:1px solid #e4e7ed;-webkit-transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1);transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-left:none}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover{padding-left:13px;padding-right:13px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:#fff}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable{padding-left:20px;padding-right:20px}.el-tabs--border-card{background:#fff;border:1px solid #dcdfe6;-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04);box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04)}.el-tabs--border-card>.el-tabs__content{padding:15px}.el-tabs--border-card>.el-tabs__header{background-color:#f5f7fa;border-bottom:1px solid #e4e7ed;margin:0}.el-tabs--border-card>.el-tabs__header .el-tabs__item{-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);border:1px solid transparent;margin-top:-1px;color:#909399}.el-tabs--border-card>.el-tabs__header .el-tabs__item+.el-tabs__item,.el-tabs--border-card>.el-tabs__header .el-tabs__item:first-child{margin-left:-1px}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{color:#409eff;background-color:#fff;border-right-color:#dcdfe6;border-left-color:#dcdfe6}.el-tabs--border-card>.el-tabs__header .el-tabs__item:not(.is-disabled):hover{color:#409eff}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-disabled{color:#c0c4cc}.el-tabs--border-card>.el-tabs__header .is-scrollable .el-tabs__item:first-child{margin-left:0}.el-tabs--bottom .el-tabs__item.is-bottom:nth-child(2),.el-tabs--bottom .el-tabs__item.is-top:nth-child(2),.el-tabs--top .el-tabs__item.is-bottom:nth-child(2),.el-tabs--top .el-tabs__item.is-top:nth-child(2){padding-left:0}.el-tabs--bottom .el-tabs__item.is-bottom:last-child,.el-tabs--bottom .el-tabs__item.is-top:last-child,.el-tabs--top .el-tabs__item.is-bottom:last-child,.el-tabs--top .el-tabs__item.is-top:last-child{padding-right:0}.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2){padding-left:20px}.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:last-child{padding-right:20px}.el-tabs--bottom .el-tabs__header.is-bottom{margin-bottom:0;margin-top:10px}.el-tabs--bottom.el-tabs--border-card .el-tabs__header.is-bottom{border-bottom:0;border-top:1px solid #dcdfe6}.el-tabs--bottom.el-tabs--border-card .el-tabs__nav-wrap.is-bottom{margin-top:-1px;margin-bottom:0}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom:not(.is-active){border:1px solid transparent}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom{margin:0 -1px -1px}.el-tabs--left,.el-tabs--right{overflow:hidden}.el-tabs--left .el-tabs__header.is-left,.el-tabs--left .el-tabs__header.is-right,.el-tabs--left .el-tabs__nav-scroll,.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__header.is-left,.el-tabs--right .el-tabs__header.is-right,.el-tabs--right .el-tabs__nav-scroll,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{height:100%}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__active-bar.is-right,.el-tabs--right .el-tabs__active-bar.is-left,.el-tabs--right .el-tabs__active-bar.is-right{top:0;bottom:auto;width:2px;height:auto}.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{margin-bottom:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{height:30px;line-height:30px;width:100%;text-align:center;cursor:pointer}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{left:auto;top:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next{right:auto;bottom:0}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__nav-wrap.is-left:after{right:0;left:auto}.el-tabs--left .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--left .el-tabs__nav-wrap.is-right.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-right.is-scrollable{padding:30px 0}.el-tabs--left .el-tabs__nav-wrap.is-left:after,.el-tabs--left .el-tabs__nav-wrap.is-right:after,.el-tabs--right .el-tabs__nav-wrap.is-left:after,.el-tabs--right .el-tabs__nav-wrap.is-right:after{height:100%;width:2px;bottom:auto;top:0}.el-tabs--left .el-tabs__nav.is-left,.el-tabs--left .el-tabs__nav.is-right,.el-tabs--right .el-tabs__nav.is-left,.el-tabs--right .el-tabs__nav.is-right{float:none}.el-tabs--left .el-tabs__item.is-left,.el-tabs--left .el-tabs__item.is-right,.el-tabs--right .el-tabs__item.is-left,.el-tabs--right .el-tabs__item.is-right{display:block}.el-tabs--left.el-tabs--card .el-tabs__active-bar.is-left,.el-tabs--right.el-tabs--card .el-tabs__active-bar.is-right{display:none}.el-tabs--left .el-tabs__header.is-left{float:left;margin-bottom:0;margin-right:10px}.el-tabs--left .el-tabs__nav-wrap.is-left{margin-right:-1px}.el-tabs--left .el-tabs__item.is-left{text-align:right}.el-tabs--left.el-tabs--card .el-tabs__item.is-left{border:1px solid #e4e7ed;border-bottom:none;border-left:none;text-align:left}.el-tabs--left.el-tabs--card .el-tabs__item.is-left:first-child{border-right:1px solid #e4e7ed;border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active{border:none;border-top:1px solid #e4e7ed;border-right:1px solid #fff}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:first-child{border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:last-child{border-bottom:none}.el-tabs--left.el-tabs--card .el-tabs__nav{border-radius:4px 0 0 4px;border-bottom:1px solid #e4e7ed;border-right:none}.el-tabs--left.el-tabs--card .el-tabs__new-tab{float:none}.el-tabs--left.el-tabs--border-card .el-tabs__header.is-left{border-right:1px solid #dfe4ed}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left{border:1px solid transparent;margin:-1px 0 -1px -1px}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left.is-active{border-color:#d1dbe5 transparent}.el-tabs--right .el-tabs__header.is-right{float:right;margin-bottom:0;margin-left:10px}.el-tabs--right .el-tabs__nav-wrap.is-right{margin-left:-1px}.el-tabs--right .el-tabs__nav-wrap.is-right:after{left:0;right:auto}.el-tabs--right .el-tabs__active-bar.is-right{left:0}.el-tabs--right.el-tabs--card .el-tabs__item.is-right{border-bottom:none;border-top:1px solid #e4e7ed}.el-tabs--right.el-tabs--card .el-tabs__item.is-right:first-child{border-left:1px solid #e4e7ed;border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active{border:none;border-top:1px solid #e4e7ed;border-left:1px solid #fff}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:first-child{border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:last-child{border-bottom:none}.el-tabs--right.el-tabs--card .el-tabs__nav{border-radius:0 4px 4px 0;border-bottom:1px solid #e4e7ed;border-left:none}.el-tabs--right.el-tabs--border-card .el-tabs__header.is-right{border-left:1px solid #dfe4ed}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right{border:1px solid transparent;margin:-1px -1px -1px 0}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right.is-active{border-color:#d1dbe5 transparent}.slideInLeft-transition,.slideInRight-transition{display:inline-block}.slideInRight-enter{-webkit-animation:slideInRight-enter .3s;animation:slideInRight-enter .3s}.slideInRight-leave{position:absolute;left:0;right:0;-webkit-animation:slideInRight-leave .3s;animation:slideInRight-leave .3s}.slideInLeft-enter{-webkit-animation:slideInLeft-enter .3s;animation:slideInLeft-enter .3s}.slideInLeft-leave{position:absolute;left:0;right:0;-webkit-animation:slideInLeft-leave .3s;animation:slideInLeft-leave .3s}@-webkit-keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}to{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}to{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@-webkit-keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}to{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}@keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}to{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}.el-tree{position:relative;cursor:default;background:#fff;color:#606266}.el-tree__empty-block{position:relative;min-height:60px;text-align:center;width:100%;height:100%}.el-tree__empty-text{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);color:#909399;font-size:14px}.el-tree__drop-indicator{position:absolute;left:0;right:0;height:1px;background-color:#409eff}.el-tree-node{white-space:nowrap;outline:0}.el-tree-node:focus>.el-tree-node__content{background-color:#f5f7fa}.el-tree-node.is-drop-inner>.el-tree-node__content .el-tree-node__label{background-color:#409eff;color:#fff}.el-tree-node__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:26px;cursor:pointer}.el-tree-node__content>.el-tree-node__expand-icon{padding:6px}.el-tree-node__content>label.el-checkbox{margin-right:8px}.el-tree-node__content:hover{background-color:#f5f7fa}.el-tree.is-dragging .el-tree-node__content{cursor:move}.el-tree.is-dragging.is-drop-not-allow .el-tree-node__content{cursor:not-allowed}.el-tree-node__expand-icon{cursor:pointer;color:#c0c4cc;font-size:12px;-webkit-transform:rotate(0);transform:rotate(0);-webkit-transition:-webkit-transform .3s ease-in-out;transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;transition:transform .3s ease-in-out,-webkit-transform .3s ease-in-out}.el-tree-node__expand-icon.expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-tree-node__expand-icon.is-leaf{color:transparent;cursor:default}.el-tree-node__label{font-size:14px}.el-tree-node__loading-icon{margin-right:8px;font-size:14px;color:#c0c4cc}.el-tree-node>.el-tree-node__children{overflow:hidden;background-color:transparent}.el-tree-node.is-expanded>.el-tree-node__children{display:block}.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:#f0f7ff}.el-alert{width:100%;padding:8px 16px;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;position:relative;background-color:#fff;overflow:hidden;opacity:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .2s;transition:opacity .2s}.el-alert.is-light .el-alert__closebtn{color:#c0c4cc}.el-alert.is-dark .el-alert__closebtn,.el-alert.is-dark .el-alert__description{color:#fff}.el-alert.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-alert--success.is-light{background-color:#f0f9eb;color:#67c23a}.el-alert--success.is-light .el-alert__description{color:#67c23a}.el-alert--success.is-dark{background-color:#67c23a;color:#fff}.el-alert--info.is-light{background-color:#f4f4f5;color:#909399}.el-alert--info.is-dark{background-color:#909399;color:#fff}.el-alert--info .el-alert__description{color:#909399}.el-alert--warning.is-light{background-color:#fdf6ec;color:#e6a23c}.el-alert--warning.is-light .el-alert__description{color:#e6a23c}.el-alert--warning.is-dark{background-color:#e6a23c;color:#fff}.el-alert--error.is-light{background-color:#fef0f0;color:#f56c6c}.el-alert--error.is-light .el-alert__description{color:#f56c6c}.el-alert--error.is-dark{background-color:#f56c6c;color:#fff}.el-alert__content{display:table-cell;padding:0 8px}.el-alert__icon{font-size:16px;width:16px}.el-alert__icon.is-big{font-size:28px;width:28px}.el-alert__title{font-size:13px;line-height:18px}.el-alert__title.is-bold{font-weight:700}.el-alert .el-alert__description{font-size:12px;margin:5px 0 0}.el-alert__closebtn{font-size:12px;opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.el-alert-fade-enter,.el-alert-fade-leave-active,.el-loading-fade-enter,.el-loading-fade-leave-active,.el-notification-fade-leave-active{opacity:0}.el-alert__closebtn.is-customed{font-style:normal;font-size:13px;top:9px}.el-notification{display:-webkit-box;display:-ms-flexbox;display:flex;width:330px;padding:14px 26px 14px 13px;border-radius:8px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #ebeef5;position:fixed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;overflow:hidden}.el-notification.right{right:16px}.el-notification.left{left:16px}.el-notification__group{margin-left:13px;margin-right:8px}.el-notification__title{font-weight:700;font-size:16px;color:#303133;margin:0}.el-notification__content{font-size:14px;line-height:21px;margin:6px 0 0;color:#606266;text-align:justify}.el-notification__content p{margin:0}.el-notification__icon{height:24px;width:24px;font-size:24px}.el-notification__closeBtn{position:absolute;top:18px;right:15px;cursor:pointer;color:#909399;font-size:16px}.el-notification__closeBtn:hover{color:#606266}.el-notification .el-icon-success{color:#67c23a}.el-notification .el-icon-error{color:#f56c6c}.el-notification .el-icon-info{color:#909399}.el-notification .el-icon-warning{color:#e6a23c}.el-notification-fade-enter.right{right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.el-notification-fade-enter.left{left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.el-input-number{position:relative;display:inline-block;width:180px;line-height:38px}.el-input-number .el-input{display:block}.el-input-number .el-input__inner{-webkit-appearance:none;padding-left:50px;padding-right:50px;text-align:center}.el-input-number__decrease,.el-input-number__increase{position:absolute;z-index:1;top:1px;width:40px;height:auto;text-align:center;background:#f5f7fa;color:#606266;cursor:pointer;font-size:13px}.el-input-number__decrease:hover,.el-input-number__increase:hover{color:#409eff}.el-input-number__decrease:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled),.el-input-number__increase:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled){border-color:#409eff}.el-input-number__decrease.is-disabled,.el-input-number__increase.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-input-number__increase{right:1px;border-radius:0 4px 4px 0;border-left:1px solid #dcdfe6}.el-input-number__decrease{left:1px;border-radius:4px 0 0 4px;border-right:1px solid #dcdfe6}.el-input-number.is-disabled .el-input-number__decrease,.el-input-number.is-disabled .el-input-number__increase{border-color:#e4e7ed;color:#e4e7ed}.el-input-number.is-disabled .el-input-number__decrease:hover,.el-input-number.is-disabled .el-input-number__increase:hover{color:#e4e7ed;cursor:not-allowed}.el-input-number--medium{width:200px;line-height:34px}.el-input-number--medium .el-input-number__decrease,.el-input-number--medium .el-input-number__increase{width:36px;font-size:14px}.el-input-number--medium .el-input__inner{padding-left:43px;padding-right:43px}.el-input-number--small{width:130px;line-height:30px}.el-input-number--small .el-input-number__decrease,.el-input-number--small .el-input-number__increase{width:32px;font-size:13px}.el-input-number--small .el-input-number__decrease [class*=el-icon],.el-input-number--small .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.9);transform:scale(.9)}.el-input-number--small .el-input__inner{padding-left:39px;padding-right:39px}.el-input-number--mini{width:130px;line-height:26px}.el-input-number--mini .el-input-number__decrease,.el-input-number--mini .el-input-number__increase{width:28px;font-size:12px}.el-input-number--mini .el-input-number__decrease [class*=el-icon],.el-input-number--mini .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.el-input-number--mini .el-input__inner{padding-left:35px;padding-right:35px}.el-input-number.is-without-controls .el-input__inner{padding-left:15px;padding-right:15px}.el-input-number.is-controls-right .el-input__inner{padding-left:15px;padding-right:50px}.el-input-number.is-controls-right .el-input-number__decrease,.el-input-number.is-controls-right .el-input-number__increase{height:auto;line-height:19px}.el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.el-input-number.is-controls-right .el-input-number__increase{border-radius:0 4px 0 0;border-bottom:1px solid #dcdfe6}.el-input-number.is-controls-right .el-input-number__decrease{right:1px;bottom:1px;top:auto;left:auto;border-right:none;border-left:1px solid #dcdfe6;border-radius:0 0 4px}.el-input-number.is-controls-right[class*=medium] [class*=decrease],.el-input-number.is-controls-right[class*=medium] [class*=increase]{line-height:17px}.el-input-number.is-controls-right[class*=small] [class*=decrease],.el-input-number.is-controls-right[class*=small] [class*=increase]{line-height:15px}.el-input-number.is-controls-right[class*=mini] [class*=decrease],.el-input-number.is-controls-right[class*=mini] [class*=increase]{line-height:13px}.el-tooltip__popper{position:absolute;border-radius:4px;padding:10px;z-index:2000;font-size:12px;line-height:1.2;min-width:10px;word-wrap:break-word}.el-tooltip__popper .popper__arrow,.el-tooltip__popper .popper__arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-tooltip__popper .popper__arrow{border-width:6px}.el-tooltip__popper .popper__arrow:after{content:" ";border-width:5px}.el-progress-bar__inner:after,.el-row:after,.el-row:before,.el-slider:after,.el-slider:before,.el-slider__button-wrapper:after,.el-upload-cover:after{content:""}.el-tooltip__popper[x-placement^=top]{margin-bottom:12px}.el-tooltip__popper[x-placement^=top] .popper__arrow{bottom:-6px;border-top-color:#303133;border-bottom-width:0}.el-tooltip__popper[x-placement^=top] .popper__arrow:after{bottom:1px;margin-left:-5px;border-top-color:#303133;border-bottom-width:0}.el-tooltip__popper[x-placement^=bottom]{margin-top:12px}.el-tooltip__popper[x-placement^=bottom] .popper__arrow{top:-6px;border-top-width:0;border-bottom-color:#303133}.el-tooltip__popper[x-placement^=bottom] .popper__arrow:after{top:1px;margin-left:-5px;border-top-width:0;border-bottom-color:#303133}.el-tooltip__popper[x-placement^=right]{margin-left:12px}.el-tooltip__popper[x-placement^=right] .popper__arrow{left:-6px;border-right-color:#303133;border-left-width:0}.el-tooltip__popper[x-placement^=right] .popper__arrow:after{bottom:-5px;left:1px;border-right-color:#303133;border-left-width:0}.el-tooltip__popper[x-placement^=left]{margin-right:12px}.el-tooltip__popper[x-placement^=left] .popper__arrow{right:-6px;border-right-width:0;border-left-color:#303133}.el-tooltip__popper[x-placement^=left] .popper__arrow:after{right:1px;bottom:-5px;margin-left:-5px;border-right-width:0;border-left-color:#303133}.el-tooltip__popper.is-dark{background:#303133;color:#fff}.el-tooltip__popper.is-light{background:#fff;border:1px solid #303133}.el-tooltip__popper.is-light[x-placement^=top] .popper__arrow{border-top-color:#303133}.el-tooltip__popper.is-light[x-placement^=top] .popper__arrow:after{border-top-color:#fff}.el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow{border-bottom-color:#303133}.el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow:after{border-bottom-color:#fff}.el-tooltip__popper.is-light[x-placement^=left] .popper__arrow{border-left-color:#303133}.el-tooltip__popper.is-light[x-placement^=left] .popper__arrow:after{border-left-color:#fff}.el-tooltip__popper.is-light[x-placement^=right] .popper__arrow{border-right-color:#303133}.el-tooltip__popper.is-light[x-placement^=right] .popper__arrow:after{border-right-color:#fff}.el-slider:after,.el-slider:before{display:table}.el-slider__button-wrapper .el-tooltip,.el-slider__button-wrapper:after{vertical-align:middle;display:inline-block}.el-slider:after{clear:both}.el-slider__runway{width:100%;height:6px;margin:16px 0;background-color:#e4e7ed;border-radius:3px;position:relative;cursor:pointer;vertical-align:middle}.el-slider__runway.show-input{margin-right:160px;width:auto}.el-slider__runway.disabled{cursor:default}.el-slider__runway.disabled .el-slider__bar{background-color:#c0c4cc}.el-slider__runway.disabled .el-slider__button{border-color:#c0c4cc}.el-slider__runway.disabled .el-slider__button-wrapper.dragging,.el-slider__runway.disabled .el-slider__button-wrapper.hover,.el-slider__runway.disabled .el-slider__button-wrapper:hover{cursor:not-allowed}.el-slider__runway.disabled .el-slider__button.dragging,.el-slider__runway.disabled .el-slider__button.hover,.el-slider__runway.disabled .el-slider__button:hover{-webkit-transform:scale(1);transform:scale(1);cursor:not-allowed}.el-slider__button-wrapper,.el-slider__stop{-webkit-transform:translateX(-50%);position:absolute}.el-slider__input{float:right;margin-top:3px;width:130px}.el-slider__input.el-input-number--mini{margin-top:5px}.el-slider__input.el-input-number--medium{margin-top:0}.el-slider__input.el-input-number--large{margin-top:-2px}.el-slider__bar{height:6px;background-color:#409eff;border-top-left-radius:3px;border-bottom-left-radius:3px;position:absolute}.el-slider__button-wrapper{height:36px;width:36px;z-index:1001;top:-15px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:transparent;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;line-height:normal}.el-slider__button-wrapper:after{height:100%}.el-slider__button-wrapper.hover,.el-slider__button-wrapper:hover{cursor:-webkit-grab;cursor:grab}.el-slider__button-wrapper.dragging{cursor:-webkit-grabbing;cursor:grabbing}.el-slider__button{width:16px;height:16px;border:2px solid #409eff;background-color:#fff;border-radius:50%;-webkit-transition:.2s;transition:.2s;user-select:none}.el-image-viewer__btn,.el-slider__button,.el-step__icon-inner{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-slider__button.dragging,.el-slider__button.hover,.el-slider__button:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.el-slider__button.hover,.el-slider__button:hover{cursor:-webkit-grab;cursor:grab}.el-slider__button.dragging{cursor:-webkit-grabbing;cursor:grabbing}.el-slider__stop{height:6px;width:6px;border-radius:100%;background-color:#fff;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.el-slider__marks{top:0;left:12px;width:18px;height:100%}.el-slider__marks-text{position:absolute;-webkit-transform:translateX(-50%);transform:translateX(-50%);font-size:14px;color:#909399;margin-top:15px}.el-slider.is-vertical{position:relative}.el-slider.is-vertical .el-slider__runway{width:6px;height:100%;margin:0 16px}.el-slider.is-vertical .el-slider__bar{width:6px;height:auto;border-radius:0 0 3px 3px}.el-slider.is-vertical .el-slider__button-wrapper{top:auto;left:-15px}.el-slider.is-vertical .el-slider__button-wrapper,.el-slider.is-vertical .el-slider__stop{-webkit-transform:translateY(50%);transform:translateY(50%)}.el-slider.is-vertical.el-slider--with-input{padding-bottom:58px}.el-slider.is-vertical.el-slider--with-input .el-slider__input{overflow:visible;float:none;position:absolute;bottom:22px;width:36px;margin-top:15px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input__inner{text-align:center;padding-left:5px;padding-right:5px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{top:32px;margin-top:-1px;border:1px solid #dcdfe6;line-height:20px;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease{width:18px;right:18px;border-bottom-left-radius:4px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{width:19px;border-bottom-right-radius:4px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase~.el-input .el-input__inner{border-bottom-left-radius:0;border-bottom-right-radius:0}.el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__increase{border-color:#c0c4cc}.el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__increase{border-color:#409eff}.el-slider.is-vertical .el-slider__marks-text{margin-top:0;left:15px;-webkit-transform:translateY(50%);transform:translateY(50%)}.el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{position:absolute;z-index:2000;background-color:hsla(0,0%,100%,.9);margin:0;top:0;right:0;bottom:0;left:0;-webkit-transition:opacity .3s;transition:opacity .3s}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:-25px}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:50px;width:50px}.el-loading-spinner{top:50%;margin-top:-21px;width:100%;text-align:center;position:absolute}.el-col-pull-0,.el-col-pull-1,.el-col-pull-2,.el-col-pull-3,.el-col-pull-4,.el-col-pull-5,.el-col-pull-6,.el-col-pull-7,.el-col-pull-8,.el-col-pull-9,.el-col-pull-10,.el-col-pull-11,.el-col-pull-13,.el-col-pull-14,.el-col-pull-15,.el-col-pull-16,.el-col-pull-17,.el-col-pull-18,.el-col-pull-19,.el-col-pull-20,.el-col-pull-21,.el-col-pull-22,.el-col-pull-23,.el-col-pull-24,.el-col-push-0,.el-col-push-1,.el-col-push-2,.el-col-push-3,.el-col-push-4,.el-col-push-5,.el-col-push-6,.el-col-push-7,.el-col-push-8,.el-col-push-9,.el-col-push-10,.el-col-push-11,.el-col-push-12,.el-col-push-13,.el-col-push-14,.el-col-push-15,.el-col-push-16,.el-col-push-17,.el-col-push-18,.el-col-push-19,.el-col-push-20,.el-col-push-21,.el-col-push-22,.el-col-push-23,.el-col-push-24,.el-row{position:relative}.el-loading-spinner .el-loading-text{color:#409eff;margin:3px 0;font-size:14px}.el-loading-spinner .circular{height:42px;width:42px;-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:#409eff;stroke-linecap:round}.el-loading-spinner i{color:#409eff}@-webkit-keyframes loading-rotate{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes loading-rotate{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}to{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}to{stroke-dasharray:90,150;stroke-dashoffset:-120px}}.el-row{-webkit-box-sizing:border-box;box-sizing:border-box}.el-row:after,.el-row:before{display:table}.el-row:after{clear:both}.el-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.el-col-0,.el-row--flex:after,.el-row--flex:before{display:none}.el-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.el-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.el-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}[class*=el-col-]{float:left;-webkit-box-sizing:border-box;box-sizing:border-box}.el-upload--picture-card,.el-upload-dragger{-webkit-box-sizing:border-box;cursor:pointer}.el-col-0{width:0}.el-col-offset-0{margin-left:0}.el-col-pull-0{right:0}.el-col-push-0{left:0}.el-col-1{width:4.16667%}.el-col-offset-1{margin-left:4.16667%}.el-col-pull-1{right:4.16667%}.el-col-push-1{left:4.16667%}.el-col-2{width:8.33333%}.el-col-offset-2{margin-left:8.33333%}.el-col-pull-2{right:8.33333%}.el-col-push-2{left:8.33333%}.el-col-3{width:12.5%}.el-col-offset-3{margin-left:12.5%}.el-col-pull-3{right:12.5%}.el-col-push-3{left:12.5%}.el-col-4{width:16.66667%}.el-col-offset-4{margin-left:16.66667%}.el-col-pull-4{right:16.66667%}.el-col-push-4{left:16.66667%}.el-col-5{width:20.83333%}.el-col-offset-5{margin-left:20.83333%}.el-col-pull-5{right:20.83333%}.el-col-push-5{left:20.83333%}.el-col-6{width:25%}.el-col-offset-6{margin-left:25%}.el-col-pull-6{right:25%}.el-col-push-6{left:25%}.el-col-7{width:29.16667%}.el-col-offset-7{margin-left:29.16667%}.el-col-pull-7{right:29.16667%}.el-col-push-7{left:29.16667%}.el-col-8{width:33.33333%}.el-col-offset-8{margin-left:33.33333%}.el-col-pull-8{right:33.33333%}.el-col-push-8{left:33.33333%}.el-col-9{width:37.5%}.el-col-offset-9{margin-left:37.5%}.el-col-pull-9{right:37.5%}.el-col-push-9{left:37.5%}.el-col-10{width:41.66667%}.el-col-offset-10{margin-left:41.66667%}.el-col-pull-10{right:41.66667%}.el-col-push-10{left:41.66667%}.el-col-11{width:45.83333%}.el-col-offset-11{margin-left:45.83333%}.el-col-pull-11{right:45.83333%}.el-col-push-11{left:45.83333%}.el-col-12{width:50%}.el-col-offset-12{margin-left:50%}.el-col-pull-12{position:relative;right:50%}.el-col-push-12{left:50%}.el-col-13{width:54.16667%}.el-col-offset-13{margin-left:54.16667%}.el-col-pull-13{right:54.16667%}.el-col-push-13{left:54.16667%}.el-col-14{width:58.33333%}.el-col-offset-14{margin-left:58.33333%}.el-col-pull-14{right:58.33333%}.el-col-push-14{left:58.33333%}.el-col-15{width:62.5%}.el-col-offset-15{margin-left:62.5%}.el-col-pull-15{right:62.5%}.el-col-push-15{left:62.5%}.el-col-16{width:66.66667%}.el-col-offset-16{margin-left:66.66667%}.el-col-pull-16{right:66.66667%}.el-col-push-16{left:66.66667%}.el-col-17{width:70.83333%}.el-col-offset-17{margin-left:70.83333%}.el-col-pull-17{right:70.83333%}.el-col-push-17{left:70.83333%}.el-col-18{width:75%}.el-col-offset-18{margin-left:75%}.el-col-pull-18{right:75%}.el-col-push-18{left:75%}.el-col-19{width:79.16667%}.el-col-offset-19{margin-left:79.16667%}.el-col-pull-19{right:79.16667%}.el-col-push-19{left:79.16667%}.el-col-20{width:83.33333%}.el-col-offset-20{margin-left:83.33333%}.el-col-pull-20{right:83.33333%}.el-col-push-20{left:83.33333%}.el-col-21{width:87.5%}.el-col-offset-21{margin-left:87.5%}.el-col-pull-21{right:87.5%}.el-col-push-21{left:87.5%}.el-col-22{width:91.66667%}.el-col-offset-22{margin-left:91.66667%}.el-col-pull-22{right:91.66667%}.el-col-push-22{left:91.66667%}.el-col-23{width:95.83333%}.el-col-offset-23{margin-left:95.83333%}.el-col-pull-23{right:95.83333%}.el-col-push-23{left:95.83333%}.el-col-24{width:100%}.el-col-offset-24{margin-left:100%}.el-col-pull-24{right:100%}.el-col-push-24{left:100%}@media only screen and (max-width:767px){.el-col-xs-0{display:none;width:0}.el-col-xs-offset-0{margin-left:0}.el-col-xs-pull-0{position:relative;right:0}.el-col-xs-push-0{position:relative;left:0}.el-col-xs-1{width:4.16667%}.el-col-xs-offset-1{margin-left:4.16667%}.el-col-xs-pull-1{position:relative;right:4.16667%}.el-col-xs-push-1{position:relative;left:4.16667%}.el-col-xs-2{width:8.33333%}.el-col-xs-offset-2{margin-left:8.33333%}.el-col-xs-pull-2{position:relative;right:8.33333%}.el-col-xs-push-2{position:relative;left:8.33333%}.el-col-xs-3{width:12.5%}.el-col-xs-offset-3{margin-left:12.5%}.el-col-xs-pull-3{position:relative;right:12.5%}.el-col-xs-push-3{position:relative;left:12.5%}.el-col-xs-4{width:16.66667%}.el-col-xs-offset-4{margin-left:16.66667%}.el-col-xs-pull-4{position:relative;right:16.66667%}.el-col-xs-push-4{position:relative;left:16.66667%}.el-col-xs-5{width:20.83333%}.el-col-xs-offset-5{margin-left:20.83333%}.el-col-xs-pull-5{position:relative;right:20.83333%}.el-col-xs-push-5{position:relative;left:20.83333%}.el-col-xs-6{width:25%}.el-col-xs-offset-6{margin-left:25%}.el-col-xs-pull-6{position:relative;right:25%}.el-col-xs-push-6{position:relative;left:25%}.el-col-xs-7{width:29.16667%}.el-col-xs-offset-7{margin-left:29.16667%}.el-col-xs-pull-7{position:relative;right:29.16667%}.el-col-xs-push-7{position:relative;left:29.16667%}.el-col-xs-8{width:33.33333%}.el-col-xs-offset-8{margin-left:33.33333%}.el-col-xs-pull-8{position:relative;right:33.33333%}.el-col-xs-push-8{position:relative;left:33.33333%}.el-col-xs-9{width:37.5%}.el-col-xs-offset-9{margin-left:37.5%}.el-col-xs-pull-9{position:relative;right:37.5%}.el-col-xs-push-9{position:relative;left:37.5%}.el-col-xs-10{width:41.66667%}.el-col-xs-offset-10{margin-left:41.66667%}.el-col-xs-pull-10{position:relative;right:41.66667%}.el-col-xs-push-10{position:relative;left:41.66667%}.el-col-xs-11{width:45.83333%}.el-col-xs-offset-11{margin-left:45.83333%}.el-col-xs-pull-11{position:relative;right:45.83333%}.el-col-xs-push-11{position:relative;left:45.83333%}.el-col-xs-12{width:50%}.el-col-xs-offset-12{margin-left:50%}.el-col-xs-pull-12{position:relative;right:50%}.el-col-xs-push-12{position:relative;left:50%}.el-col-xs-13{width:54.16667%}.el-col-xs-offset-13{margin-left:54.16667%}.el-col-xs-pull-13{position:relative;right:54.16667%}.el-col-xs-push-13{position:relative;left:54.16667%}.el-col-xs-14{width:58.33333%}.el-col-xs-offset-14{margin-left:58.33333%}.el-col-xs-pull-14{position:relative;right:58.33333%}.el-col-xs-push-14{position:relative;left:58.33333%}.el-col-xs-15{width:62.5%}.el-col-xs-offset-15{margin-left:62.5%}.el-col-xs-pull-15{position:relative;right:62.5%}.el-col-xs-push-15{position:relative;left:62.5%}.el-col-xs-16{width:66.66667%}.el-col-xs-offset-16{margin-left:66.66667%}.el-col-xs-pull-16{position:relative;right:66.66667%}.el-col-xs-push-16{position:relative;left:66.66667%}.el-col-xs-17{width:70.83333%}.el-col-xs-offset-17{margin-left:70.83333%}.el-col-xs-pull-17{position:relative;right:70.83333%}.el-col-xs-push-17{position:relative;left:70.83333%}.el-col-xs-18{width:75%}.el-col-xs-offset-18{margin-left:75%}.el-col-xs-pull-18{position:relative;right:75%}.el-col-xs-push-18{position:relative;left:75%}.el-col-xs-19{width:79.16667%}.el-col-xs-offset-19{margin-left:79.16667%}.el-col-xs-pull-19{position:relative;right:79.16667%}.el-col-xs-push-19{position:relative;left:79.16667%}.el-col-xs-20{width:83.33333%}.el-col-xs-offset-20{margin-left:83.33333%}.el-col-xs-pull-20{position:relative;right:83.33333%}.el-col-xs-push-20{position:relative;left:83.33333%}.el-col-xs-21{width:87.5%}.el-col-xs-offset-21{margin-left:87.5%}.el-col-xs-pull-21{position:relative;right:87.5%}.el-col-xs-push-21{position:relative;left:87.5%}.el-col-xs-22{width:91.66667%}.el-col-xs-offset-22{margin-left:91.66667%}.el-col-xs-pull-22{position:relative;right:91.66667%}.el-col-xs-push-22{position:relative;left:91.66667%}.el-col-xs-23{width:95.83333%}.el-col-xs-offset-23{margin-left:95.83333%}.el-col-xs-pull-23{position:relative;right:95.83333%}.el-col-xs-push-23{position:relative;left:95.83333%}.el-col-xs-24{width:100%}.el-col-xs-offset-24{margin-left:100%}.el-col-xs-pull-24{position:relative;right:100%}.el-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.el-col-sm-0{display:none;width:0}.el-col-sm-offset-0{margin-left:0}.el-col-sm-pull-0{position:relative;right:0}.el-col-sm-push-0{position:relative;left:0}.el-col-sm-1{width:4.16667%}.el-col-sm-offset-1{margin-left:4.16667%}.el-col-sm-pull-1{position:relative;right:4.16667%}.el-col-sm-push-1{position:relative;left:4.16667%}.el-col-sm-2{width:8.33333%}.el-col-sm-offset-2{margin-left:8.33333%}.el-col-sm-pull-2{position:relative;right:8.33333%}.el-col-sm-push-2{position:relative;left:8.33333%}.el-col-sm-3{width:12.5%}.el-col-sm-offset-3{margin-left:12.5%}.el-col-sm-pull-3{position:relative;right:12.5%}.el-col-sm-push-3{position:relative;left:12.5%}.el-col-sm-4{width:16.66667%}.el-col-sm-offset-4{margin-left:16.66667%}.el-col-sm-pull-4{position:relative;right:16.66667%}.el-col-sm-push-4{position:relative;left:16.66667%}.el-col-sm-5{width:20.83333%}.el-col-sm-offset-5{margin-left:20.83333%}.el-col-sm-pull-5{position:relative;right:20.83333%}.el-col-sm-push-5{position:relative;left:20.83333%}.el-col-sm-6{width:25%}.el-col-sm-offset-6{margin-left:25%}.el-col-sm-pull-6{position:relative;right:25%}.el-col-sm-push-6{position:relative;left:25%}.el-col-sm-7{width:29.16667%}.el-col-sm-offset-7{margin-left:29.16667%}.el-col-sm-pull-7{position:relative;right:29.16667%}.el-col-sm-push-7{position:relative;left:29.16667%}.el-col-sm-8{width:33.33333%}.el-col-sm-offset-8{margin-left:33.33333%}.el-col-sm-pull-8{position:relative;right:33.33333%}.el-col-sm-push-8{position:relative;left:33.33333%}.el-col-sm-9{width:37.5%}.el-col-sm-offset-9{margin-left:37.5%}.el-col-sm-pull-9{position:relative;right:37.5%}.el-col-sm-push-9{position:relative;left:37.5%}.el-col-sm-10{width:41.66667%}.el-col-sm-offset-10{margin-left:41.66667%}.el-col-sm-pull-10{position:relative;right:41.66667%}.el-col-sm-push-10{position:relative;left:41.66667%}.el-col-sm-11{width:45.83333%}.el-col-sm-offset-11{margin-left:45.83333%}.el-col-sm-pull-11{position:relative;right:45.83333%}.el-col-sm-push-11{position:relative;left:45.83333%}.el-col-sm-12{width:50%}.el-col-sm-offset-12{margin-left:50%}.el-col-sm-pull-12{position:relative;right:50%}.el-col-sm-push-12{position:relative;left:50%}.el-col-sm-13{width:54.16667%}.el-col-sm-offset-13{margin-left:54.16667%}.el-col-sm-pull-13{position:relative;right:54.16667%}.el-col-sm-push-13{position:relative;left:54.16667%}.el-col-sm-14{width:58.33333%}.el-col-sm-offset-14{margin-left:58.33333%}.el-col-sm-pull-14{position:relative;right:58.33333%}.el-col-sm-push-14{position:relative;left:58.33333%}.el-col-sm-15{width:62.5%}.el-col-sm-offset-15{margin-left:62.5%}.el-col-sm-pull-15{position:relative;right:62.5%}.el-col-sm-push-15{position:relative;left:62.5%}.el-col-sm-16{width:66.66667%}.el-col-sm-offset-16{margin-left:66.66667%}.el-col-sm-pull-16{position:relative;right:66.66667%}.el-col-sm-push-16{position:relative;left:66.66667%}.el-col-sm-17{width:70.83333%}.el-col-sm-offset-17{margin-left:70.83333%}.el-col-sm-pull-17{position:relative;right:70.83333%}.el-col-sm-push-17{position:relative;left:70.83333%}.el-col-sm-18{width:75%}.el-col-sm-offset-18{margin-left:75%}.el-col-sm-pull-18{position:relative;right:75%}.el-col-sm-push-18{position:relative;left:75%}.el-col-sm-19{width:79.16667%}.el-col-sm-offset-19{margin-left:79.16667%}.el-col-sm-pull-19{position:relative;right:79.16667%}.el-col-sm-push-19{position:relative;left:79.16667%}.el-col-sm-20{width:83.33333%}.el-col-sm-offset-20{margin-left:83.33333%}.el-col-sm-pull-20{position:relative;right:83.33333%}.el-col-sm-push-20{position:relative;left:83.33333%}.el-col-sm-21{width:87.5%}.el-col-sm-offset-21{margin-left:87.5%}.el-col-sm-pull-21{position:relative;right:87.5%}.el-col-sm-push-21{position:relative;left:87.5%}.el-col-sm-22{width:91.66667%}.el-col-sm-offset-22{margin-left:91.66667%}.el-col-sm-pull-22{position:relative;right:91.66667%}.el-col-sm-push-22{position:relative;left:91.66667%}.el-col-sm-23{width:95.83333%}.el-col-sm-offset-23{margin-left:95.83333%}.el-col-sm-pull-23{position:relative;right:95.83333%}.el-col-sm-push-23{position:relative;left:95.83333%}.el-col-sm-24{width:100%}.el-col-sm-offset-24{margin-left:100%}.el-col-sm-pull-24{position:relative;right:100%}.el-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.el-col-md-0{display:none;width:0}.el-col-md-offset-0{margin-left:0}.el-col-md-pull-0{position:relative;right:0}.el-col-md-push-0{position:relative;left:0}.el-col-md-1{width:4.16667%}.el-col-md-offset-1{margin-left:4.16667%}.el-col-md-pull-1{position:relative;right:4.16667%}.el-col-md-push-1{position:relative;left:4.16667%}.el-col-md-2{width:8.33333%}.el-col-md-offset-2{margin-left:8.33333%}.el-col-md-pull-2{position:relative;right:8.33333%}.el-col-md-push-2{position:relative;left:8.33333%}.el-col-md-3{width:12.5%}.el-col-md-offset-3{margin-left:12.5%}.el-col-md-pull-3{position:relative;right:12.5%}.el-col-md-push-3{position:relative;left:12.5%}.el-col-md-4{width:16.66667%}.el-col-md-offset-4{margin-left:16.66667%}.el-col-md-pull-4{position:relative;right:16.66667%}.el-col-md-push-4{position:relative;left:16.66667%}.el-col-md-5{width:20.83333%}.el-col-md-offset-5{margin-left:20.83333%}.el-col-md-pull-5{position:relative;right:20.83333%}.el-col-md-push-5{position:relative;left:20.83333%}.el-col-md-6{width:25%}.el-col-md-offset-6{margin-left:25%}.el-col-md-pull-6{position:relative;right:25%}.el-col-md-push-6{position:relative;left:25%}.el-col-md-7{width:29.16667%}.el-col-md-offset-7{margin-left:29.16667%}.el-col-md-pull-7{position:relative;right:29.16667%}.el-col-md-push-7{position:relative;left:29.16667%}.el-col-md-8{width:33.33333%}.el-col-md-offset-8{margin-left:33.33333%}.el-col-md-pull-8{position:relative;right:33.33333%}.el-col-md-push-8{position:relative;left:33.33333%}.el-col-md-9{width:37.5%}.el-col-md-offset-9{margin-left:37.5%}.el-col-md-pull-9{position:relative;right:37.5%}.el-col-md-push-9{position:relative;left:37.5%}.el-col-md-10{width:41.66667%}.el-col-md-offset-10{margin-left:41.66667%}.el-col-md-pull-10{position:relative;right:41.66667%}.el-col-md-push-10{position:relative;left:41.66667%}.el-col-md-11{width:45.83333%}.el-col-md-offset-11{margin-left:45.83333%}.el-col-md-pull-11{position:relative;right:45.83333%}.el-col-md-push-11{position:relative;left:45.83333%}.el-col-md-12{width:50%}.el-col-md-offset-12{margin-left:50%}.el-col-md-pull-12{position:relative;right:50%}.el-col-md-push-12{position:relative;left:50%}.el-col-md-13{width:54.16667%}.el-col-md-offset-13{margin-left:54.16667%}.el-col-md-pull-13{position:relative;right:54.16667%}.el-col-md-push-13{position:relative;left:54.16667%}.el-col-md-14{width:58.33333%}.el-col-md-offset-14{margin-left:58.33333%}.el-col-md-pull-14{position:relative;right:58.33333%}.el-col-md-push-14{position:relative;left:58.33333%}.el-col-md-15{width:62.5%}.el-col-md-offset-15{margin-left:62.5%}.el-col-md-pull-15{position:relative;right:62.5%}.el-col-md-push-15{position:relative;left:62.5%}.el-col-md-16{width:66.66667%}.el-col-md-offset-16{margin-left:66.66667%}.el-col-md-pull-16{position:relative;right:66.66667%}.el-col-md-push-16{position:relative;left:66.66667%}.el-col-md-17{width:70.83333%}.el-col-md-offset-17{margin-left:70.83333%}.el-col-md-pull-17{position:relative;right:70.83333%}.el-col-md-push-17{position:relative;left:70.83333%}.el-col-md-18{width:75%}.el-col-md-offset-18{margin-left:75%}.el-col-md-pull-18{position:relative;right:75%}.el-col-md-push-18{position:relative;left:75%}.el-col-md-19{width:79.16667%}.el-col-md-offset-19{margin-left:79.16667%}.el-col-md-pull-19{position:relative;right:79.16667%}.el-col-md-push-19{position:relative;left:79.16667%}.el-col-md-20{width:83.33333%}.el-col-md-offset-20{margin-left:83.33333%}.el-col-md-pull-20{position:relative;right:83.33333%}.el-col-md-push-20{position:relative;left:83.33333%}.el-col-md-21{width:87.5%}.el-col-md-offset-21{margin-left:87.5%}.el-col-md-pull-21{position:relative;right:87.5%}.el-col-md-push-21{position:relative;left:87.5%}.el-col-md-22{width:91.66667%}.el-col-md-offset-22{margin-left:91.66667%}.el-col-md-pull-22{position:relative;right:91.66667%}.el-col-md-push-22{position:relative;left:91.66667%}.el-col-md-23{width:95.83333%}.el-col-md-offset-23{margin-left:95.83333%}.el-col-md-pull-23{position:relative;right:95.83333%}.el-col-md-push-23{position:relative;left:95.83333%}.el-col-md-24{width:100%}.el-col-md-offset-24{margin-left:100%}.el-col-md-pull-24{position:relative;right:100%}.el-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.el-col-lg-0{display:none;width:0}.el-col-lg-offset-0{margin-left:0}.el-col-lg-pull-0{position:relative;right:0}.el-col-lg-push-0{position:relative;left:0}.el-col-lg-1{width:4.16667%}.el-col-lg-offset-1{margin-left:4.16667%}.el-col-lg-pull-1{position:relative;right:4.16667%}.el-col-lg-push-1{position:relative;left:4.16667%}.el-col-lg-2{width:8.33333%}.el-col-lg-offset-2{margin-left:8.33333%}.el-col-lg-pull-2{position:relative;right:8.33333%}.el-col-lg-push-2{position:relative;left:8.33333%}.el-col-lg-3{width:12.5%}.el-col-lg-offset-3{margin-left:12.5%}.el-col-lg-pull-3{position:relative;right:12.5%}.el-col-lg-push-3{position:relative;left:12.5%}.el-col-lg-4{width:16.66667%}.el-col-lg-offset-4{margin-left:16.66667%}.el-col-lg-pull-4{position:relative;right:16.66667%}.el-col-lg-push-4{position:relative;left:16.66667%}.el-col-lg-5{width:20.83333%}.el-col-lg-offset-5{margin-left:20.83333%}.el-col-lg-pull-5{position:relative;right:20.83333%}.el-col-lg-push-5{position:relative;left:20.83333%}.el-col-lg-6{width:25%}.el-col-lg-offset-6{margin-left:25%}.el-col-lg-pull-6{position:relative;right:25%}.el-col-lg-push-6{position:relative;left:25%}.el-col-lg-7{width:29.16667%}.el-col-lg-offset-7{margin-left:29.16667%}.el-col-lg-pull-7{position:relative;right:29.16667%}.el-col-lg-push-7{position:relative;left:29.16667%}.el-col-lg-8{width:33.33333%}.el-col-lg-offset-8{margin-left:33.33333%}.el-col-lg-pull-8{position:relative;right:33.33333%}.el-col-lg-push-8{position:relative;left:33.33333%}.el-col-lg-9{width:37.5%}.el-col-lg-offset-9{margin-left:37.5%}.el-col-lg-pull-9{position:relative;right:37.5%}.el-col-lg-push-9{position:relative;left:37.5%}.el-col-lg-10{width:41.66667%}.el-col-lg-offset-10{margin-left:41.66667%}.el-col-lg-pull-10{position:relative;right:41.66667%}.el-col-lg-push-10{position:relative;left:41.66667%}.el-col-lg-11{width:45.83333%}.el-col-lg-offset-11{margin-left:45.83333%}.el-col-lg-pull-11{position:relative;right:45.83333%}.el-col-lg-push-11{position:relative;left:45.83333%}.el-col-lg-12{width:50%}.el-col-lg-offset-12{margin-left:50%}.el-col-lg-pull-12{position:relative;right:50%}.el-col-lg-push-12{position:relative;left:50%}.el-col-lg-13{width:54.16667%}.el-col-lg-offset-13{margin-left:54.16667%}.el-col-lg-pull-13{position:relative;right:54.16667%}.el-col-lg-push-13{position:relative;left:54.16667%}.el-col-lg-14{width:58.33333%}.el-col-lg-offset-14{margin-left:58.33333%}.el-col-lg-pull-14{position:relative;right:58.33333%}.el-col-lg-push-14{position:relative;left:58.33333%}.el-col-lg-15{width:62.5%}.el-col-lg-offset-15{margin-left:62.5%}.el-col-lg-pull-15{position:relative;right:62.5%}.el-col-lg-push-15{position:relative;left:62.5%}.el-col-lg-16{width:66.66667%}.el-col-lg-offset-16{margin-left:66.66667%}.el-col-lg-pull-16{position:relative;right:66.66667%}.el-col-lg-push-16{position:relative;left:66.66667%}.el-col-lg-17{width:70.83333%}.el-col-lg-offset-17{margin-left:70.83333%}.el-col-lg-pull-17{position:relative;right:70.83333%}.el-col-lg-push-17{position:relative;left:70.83333%}.el-col-lg-18{width:75%}.el-col-lg-offset-18{margin-left:75%}.el-col-lg-pull-18{position:relative;right:75%}.el-col-lg-push-18{position:relative;left:75%}.el-col-lg-19{width:79.16667%}.el-col-lg-offset-19{margin-left:79.16667%}.el-col-lg-pull-19{position:relative;right:79.16667%}.el-col-lg-push-19{position:relative;left:79.16667%}.el-col-lg-20{width:83.33333%}.el-col-lg-offset-20{margin-left:83.33333%}.el-col-lg-pull-20{position:relative;right:83.33333%}.el-col-lg-push-20{position:relative;left:83.33333%}.el-col-lg-21{width:87.5%}.el-col-lg-offset-21{margin-left:87.5%}.el-col-lg-pull-21{position:relative;right:87.5%}.el-col-lg-push-21{position:relative;left:87.5%}.el-col-lg-22{width:91.66667%}.el-col-lg-offset-22{margin-left:91.66667%}.el-col-lg-pull-22{position:relative;right:91.66667%}.el-col-lg-push-22{position:relative;left:91.66667%}.el-col-lg-23{width:95.83333%}.el-col-lg-offset-23{margin-left:95.83333%}.el-col-lg-pull-23{position:relative;right:95.83333%}.el-col-lg-push-23{position:relative;left:95.83333%}.el-col-lg-24{width:100%}.el-col-lg-offset-24{margin-left:100%}.el-col-lg-pull-24{position:relative;right:100%}.el-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.el-col-xl-0{display:none;width:0}.el-col-xl-offset-0{margin-left:0}.el-col-xl-pull-0{position:relative;right:0}.el-col-xl-push-0{position:relative;left:0}.el-col-xl-1{width:4.16667%}.el-col-xl-offset-1{margin-left:4.16667%}.el-col-xl-pull-1{position:relative;right:4.16667%}.el-col-xl-push-1{position:relative;left:4.16667%}.el-col-xl-2{width:8.33333%}.el-col-xl-offset-2{margin-left:8.33333%}.el-col-xl-pull-2{position:relative;right:8.33333%}.el-col-xl-push-2{position:relative;left:8.33333%}.el-col-xl-3{width:12.5%}.el-col-xl-offset-3{margin-left:12.5%}.el-col-xl-pull-3{position:relative;right:12.5%}.el-col-xl-push-3{position:relative;left:12.5%}.el-col-xl-4{width:16.66667%}.el-col-xl-offset-4{margin-left:16.66667%}.el-col-xl-pull-4{position:relative;right:16.66667%}.el-col-xl-push-4{position:relative;left:16.66667%}.el-col-xl-5{width:20.83333%}.el-col-xl-offset-5{margin-left:20.83333%}.el-col-xl-pull-5{position:relative;right:20.83333%}.el-col-xl-push-5{position:relative;left:20.83333%}.el-col-xl-6{width:25%}.el-col-xl-offset-6{margin-left:25%}.el-col-xl-pull-6{position:relative;right:25%}.el-col-xl-push-6{position:relative;left:25%}.el-col-xl-7{width:29.16667%}.el-col-xl-offset-7{margin-left:29.16667%}.el-col-xl-pull-7{position:relative;right:29.16667%}.el-col-xl-push-7{position:relative;left:29.16667%}.el-col-xl-8{width:33.33333%}.el-col-xl-offset-8{margin-left:33.33333%}.el-col-xl-pull-8{position:relative;right:33.33333%}.el-col-xl-push-8{position:relative;left:33.33333%}.el-col-xl-9{width:37.5%}.el-col-xl-offset-9{margin-left:37.5%}.el-col-xl-pull-9{position:relative;right:37.5%}.el-col-xl-push-9{position:relative;left:37.5%}.el-col-xl-10{width:41.66667%}.el-col-xl-offset-10{margin-left:41.66667%}.el-col-xl-pull-10{position:relative;right:41.66667%}.el-col-xl-push-10{position:relative;left:41.66667%}.el-col-xl-11{width:45.83333%}.el-col-xl-offset-11{margin-left:45.83333%}.el-col-xl-pull-11{position:relative;right:45.83333%}.el-col-xl-push-11{position:relative;left:45.83333%}.el-col-xl-12{width:50%}.el-col-xl-offset-12{margin-left:50%}.el-col-xl-pull-12{position:relative;right:50%}.el-col-xl-push-12{position:relative;left:50%}.el-col-xl-13{width:54.16667%}.el-col-xl-offset-13{margin-left:54.16667%}.el-col-xl-pull-13{position:relative;right:54.16667%}.el-col-xl-push-13{position:relative;left:54.16667%}.el-col-xl-14{width:58.33333%}.el-col-xl-offset-14{margin-left:58.33333%}.el-col-xl-pull-14{position:relative;right:58.33333%}.el-col-xl-push-14{position:relative;left:58.33333%}.el-col-xl-15{width:62.5%}.el-col-xl-offset-15{margin-left:62.5%}.el-col-xl-pull-15{position:relative;right:62.5%}.el-col-xl-push-15{position:relative;left:62.5%}.el-col-xl-16{width:66.66667%}.el-col-xl-offset-16{margin-left:66.66667%}.el-col-xl-pull-16{position:relative;right:66.66667%}.el-col-xl-push-16{position:relative;left:66.66667%}.el-col-xl-17{width:70.83333%}.el-col-xl-offset-17{margin-left:70.83333%}.el-col-xl-pull-17{position:relative;right:70.83333%}.el-col-xl-push-17{position:relative;left:70.83333%}.el-col-xl-18{width:75%}.el-col-xl-offset-18{margin-left:75%}.el-col-xl-pull-18{position:relative;right:75%}.el-col-xl-push-18{position:relative;left:75%}.el-col-xl-19{width:79.16667%}.el-col-xl-offset-19{margin-left:79.16667%}.el-col-xl-pull-19{position:relative;right:79.16667%}.el-col-xl-push-19{position:relative;left:79.16667%}.el-col-xl-20{width:83.33333%}.el-col-xl-offset-20{margin-left:83.33333%}.el-col-xl-pull-20{position:relative;right:83.33333%}.el-col-xl-push-20{position:relative;left:83.33333%}.el-col-xl-21{width:87.5%}.el-col-xl-offset-21{margin-left:87.5%}.el-col-xl-pull-21{position:relative;right:87.5%}.el-col-xl-push-21{position:relative;left:87.5%}.el-col-xl-22{width:91.66667%}.el-col-xl-offset-22{margin-left:91.66667%}.el-col-xl-pull-22{position:relative;right:91.66667%}.el-col-xl-push-22{position:relative;left:91.66667%}.el-col-xl-23{width:95.83333%}.el-col-xl-offset-23{margin-left:95.83333%}.el-col-xl-pull-23{position:relative;right:95.83333%}.el-col-xl-push-23{position:relative;left:95.83333%}.el-col-xl-24{width:100%}.el-col-xl-offset-24{margin-left:100%}.el-col-xl-pull-24{position:relative;right:100%}.el-col-xl-push-24{position:relative;left:100%}}@-webkit-keyframes progress{0%{background-position:0 0}to{background-position:32px 0}}.el-upload{display:inline-block;text-align:center;cursor:pointer;outline:0}.el-upload__input{display:none}.el-upload__tip{font-size:12px;color:#606266;margin-top:7px}.el-upload iframe{position:absolute;z-index:-1;top:0;left:0;opacity:0;filter:alpha(opacity=0)}.el-upload--picture-card{background-color:#fbfdff;border:1px dashed #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;line-height:146px;vertical-align:top}.el-upload--picture-card i{font-size:28px;color:#8c939d}.el-upload--picture-card:hover,.el-upload:focus{border-color:#409eff;color:#409eff}.el-upload:focus .el-upload-dragger{border-color:#409eff}.el-upload-dragger{background-color:#fff;border:1px dashed #d9d9d9;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:360px;height:180px;text-align:center;position:relative;overflow:hidden}.el-upload-dragger .el-icon-upload{font-size:67px;color:#c0c4cc;margin:40px 0 16px;line-height:50px}.el-upload-dragger+.el-upload__tip{text-align:center}.el-upload-dragger~.el-upload__files{border-top:1px solid #dcdfe6;margin-top:7px;padding-top:5px}.el-upload-dragger .el-upload__text{color:#606266;font-size:14px;text-align:center}.el-upload-dragger .el-upload__text em{color:#409eff;font-style:normal}.el-upload-dragger:hover{border-color:#409eff}.el-upload-dragger.is-dragover{background-color:rgba(32,159,255,.06);border:2px dashed #409eff}.el-upload-list{margin:0;padding:0;list-style:none}.el-upload-list__item{-webkit-transition:all .5s cubic-bezier(.55,0,.1,1);transition:all .5s cubic-bezier(.55,0,.1,1);font-size:14px;color:#606266;line-height:1.8;margin-top:5px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;width:100%}.el-upload-list__item .el-progress{position:absolute;top:20px;width:100%}.el-upload-list__item .el-progress__text{position:absolute;right:0;top:-13px}.el-upload-list__item .el-progress-bar{margin-right:0;padding-right:0}.el-upload-list__item:first-child{margin-top:10px}.el-upload-list__item .el-icon-upload-success{color:#67c23a}.el-upload-list__item .el-icon-close{display:none;position:absolute;top:5px;right:5px;cursor:pointer;opacity:.75;color:#606266}.el-upload-list__item .el-icon-close:hover{opacity:1}.el-upload-list__item .el-icon-close-tip{display:none;position:absolute;top:5px;right:5px;font-size:12px;cursor:pointer;opacity:1;color:#409eff}.el-upload-list__item:hover{background-color:#f5f7fa}.el-upload-list__item:hover .el-icon-close{display:inline-block}.el-upload-list__item:hover .el-progress__text{display:none}.el-upload-list__item.is-success .el-upload-list__item-status-label{display:block}.el-upload-list__item.is-success .el-upload-list__item-name:focus,.el-upload-list__item.is-success .el-upload-list__item-name:hover{color:#409eff;cursor:pointer}.el-upload-list__item.is-success:focus:not(:hover) .el-icon-close-tip{display:inline-block}.el-upload-list__item.is-success:active .el-icon-close-tip,.el-upload-list__item.is-success:focus .el-upload-list__item-status-label,.el-upload-list__item.is-success:hover .el-upload-list__item-status-label,.el-upload-list__item.is-success:not(.focusing):focus .el-icon-close-tip{display:none}.el-upload-list.is-disabled .el-upload-list__item:hover .el-upload-list__item-status-label{display:block}.el-upload-list__item-name{color:#606266;display:block;margin-right:40px;overflow:hidden;padding-left:4px;text-overflow:ellipsis;-webkit-transition:color .3s;transition:color .3s;white-space:nowrap}.el-upload-list__item-name [class^=el-icon]{height:100%;margin-right:7px;color:#909399;line-height:inherit}.el-upload-list__item-status-label{position:absolute;right:5px;top:0;line-height:inherit;display:none}.el-upload-list__item-delete{position:absolute;right:10px;top:0;font-size:12px;color:#606266;display:none}.el-upload-list__item-delete:hover{color:#409eff}.el-upload-list--picture-card{margin:0;display:inline;vertical-align:top}.el-upload-list--picture-card .el-upload-list__item{overflow:hidden;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;margin:0 8px 8px 0;display:inline-block}.el-upload-list--picture-card .el-upload-list__item .el-icon-check,.el-upload-list--picture-card .el-upload-list__item .el-icon-circle-check{color:#fff}.el-upload-list--picture-card .el-upload-list__item .el-icon-close,.el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label{display:none}.el-upload-list--picture-card .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture-card .el-upload-list__item-name{display:none}.el-upload-list--picture-card .el-upload-list__item-thumbnail{width:100%;height:100%}.el-upload-list--picture-card .el-upload-list__item-status-label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.el-upload-list--picture-card .el-upload-list__item-status-label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.el-upload-list--picture-card .el-upload-list__item-actions{position:absolute;width:100%;height:100%;left:0;top:0;cursor:default;text-align:center;color:#fff;opacity:0;font-size:20px;background-color:rgba(0,0,0,.5);-webkit-transition:opacity .3s;transition:opacity .3s}.el-upload-list--picture-card .el-upload-list__item-actions:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-upload-list--picture-card .el-upload-list__item-actions span{display:none;cursor:pointer}.el-upload-list--picture-card .el-upload-list__item-actions span+span{margin-left:15px}.el-upload-list--picture-card .el-upload-list__item-actions .el-upload-list__item-delete{position:static;font-size:inherit;color:inherit}.el-upload-list--picture-card .el-upload-list__item-actions:hover{opacity:1}.el-upload-list--picture-card .el-upload-list__item-actions:hover span{display:inline-block}.el-upload-list--picture-card .el-progress{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);bottom:auto;width:126px}.el-upload-list--picture-card .el-progress .el-progress__text{top:50%}.el-upload-list--picture .el-upload-list__item{overflow:hidden;z-index:0;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;margin-top:10px;padding:10px 10px 10px 90px;height:92px}.el-upload-list--picture .el-upload-list__item .el-icon-check,.el-upload-list--picture .el-upload-list__item .el-icon-circle-check{color:#fff}.el-upload-list--picture .el-upload-list__item:hover .el-upload-list__item-status-label{background:0 0;-webkit-box-shadow:none;box-shadow:none;top:-2px;right:-12px}.el-upload-list--picture .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name{line-height:70px;margin-top:0}.el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name i{display:none}.el-upload-list--picture .el-upload-list__item-thumbnail{vertical-align:middle;display:inline-block;width:70px;height:70px;float:left;position:relative;z-index:1;margin-left:-80px;background-color:#fff}.el-upload-list--picture .el-upload-list__item-name{display:block;margin-top:20px}.el-upload-list--picture .el-upload-list__item-name i{font-size:70px;line-height:1;position:absolute;left:9px;top:10px}.el-upload-list--picture .el-upload-list__item-status-label{position:absolute;right:-17px;top:-7px;width:46px;height:26px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 1px 1px #ccc;box-shadow:0 1px 1px #ccc}.el-upload-list--picture .el-upload-list__item-status-label i{font-size:12px;margin-top:12px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.el-upload-list--picture .el-progress{position:relative;top:-7px}.el-upload-cover{position:absolute;left:0;top:0;width:100%;height:100%;overflow:hidden;z-index:10;cursor:default}.el-upload-cover:after{display:inline-block;height:100%;vertical-align:middle}.el-upload-cover img{display:block;width:100%;height:100%}.el-upload-cover__label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.el-upload-cover__label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);color:#fff}.el-upload-cover__progress{display:inline-block;vertical-align:middle;position:static;width:243px}.el-upload-cover__progress+.el-upload__inner{opacity:0}.el-upload-cover__content{position:absolute;top:0;left:0;width:100%;height:100%}.el-upload-cover__interact{position:absolute;bottom:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.72);text-align:center}.el-upload-cover__interact .btn{display:inline-block;color:#fff;font-size:14px;cursor:pointer;vertical-align:middle;-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);margin-top:60px}.el-upload-cover__interact .btn span{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.el-upload-cover__interact .btn:not(:first-child){margin-left:35px}.el-upload-cover__interact .btn:hover{-webkit-transform:translateY(-13px);transform:translateY(-13px)}.el-upload-cover__interact .btn:hover span{opacity:1}.el-upload-cover__interact .btn i{color:#fff;display:block;font-size:24px;line-height:inherit;margin:0 auto 5px}.el-upload-cover__title{position:absolute;bottom:0;left:0;background-color:#fff;height:36px;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400;text-align:left;padding:0 10px;margin:0;line-height:36px;font-size:14px;color:#303133}.el-upload-cover+.el-upload__inner{opacity:0;position:relative;z-index:1}.el-progress{position:relative;line-height:1}.el-progress__text{font-size:14px;color:#606266;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.el-progress__text i{vertical-align:middle;display:block}.el-progress--circle,.el-progress--dashboard{display:inline-block}.el-progress--circle .el-progress__text,.el-progress--dashboard .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-progress--circle .el-progress__text i,.el-progress--dashboard .el-progress__text i{vertical-align:middle;display:inline-block}.el-progress--without-text .el-progress__text{display:none}.el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.el-progress-bar,.el-progress-bar__inner:after,.el-progress-bar__innerText,.el-spinner{display:inline-block;vertical-align:middle}.el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.el-progress.is-success .el-progress-bar__inner{background-color:#67c23a}.el-progress.is-success .el-progress__text{color:#67c23a}.el-progress.is-warning .el-progress-bar__inner{background-color:#e6a23c}.el-progress.is-warning .el-progress__text{color:#e6a23c}.el-progress.is-exception .el-progress-bar__inner{background-color:#f56c6c}.el-progress.is-exception .el-progress__text{color:#f56c6c}.el-progress-bar{padding-right:50px;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-progress-bar__outer{height:6px;border-radius:100px;background-color:#ebeef5;overflow:hidden;position:relative;vertical-align:middle}.el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#409eff;text-align:right;border-radius:100px;line-height:1;white-space:nowrap;-webkit-transition:width .6s ease;transition:width .6s ease}.el-card,.el-message{border-radius:4px;overflow:hidden}.el-progress-bar__inner:after{height:100%}.el-progress-bar__innerText{color:#fff;font-size:12px;margin:0 5px}@keyframes progress{0%{background-position:0 0}to{background-position:32px 0}}.el-time-spinner{width:100%;white-space:nowrap}.el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.el-spinner-inner .path{stroke:#ececec;stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotate{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}.el-message{min-width:380px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #ebeef5;position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:#edf2fc;-webkit-transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,transform .4s,top .4s;transition:opacity .3s,transform .4s,top .4s,-webkit-transform .4s;padding:15px 15px 15px 20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-message.is-closable .el-message__content{padding-right:16px}.el-message p{margin:0}.el-message--info .el-message__content{color:#909399}.el-message--success{background-color:#f0f9eb;border-color:#e1f3d8}.el-message--success .el-message__content{color:#67c23a}.el-message--warning{background-color:#fdf6ec;border-color:#faecd8}.el-message--warning .el-message__content{color:#e6a23c}.el-message--error{background-color:#fef0f0;border-color:#fde2e2}.el-message--error .el-message__content{color:#f56c6c}.el-message__icon{margin-right:10px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:#c0c4cc;font-size:16px}.el-message__closeBtn:hover{color:#909399}.el-message .el-icon-success{color:#67c23a}.el-message .el-icon-error{color:#f56c6c}.el-message .el-icon-info{color:#909399}.el-message .el-icon-warning{color:#e6a23c}.el-message-fade-enter,.el-message-fade-leave-active{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}.el-badge{position:relative;vertical-align:middle;display:inline-block}.el-badge__content{background-color:#f56c6c;border-radius:10px;color:#fff;display:inline-block;font-size:12px;height:18px;line-height:18px;padding:0 6px;text-align:center;white-space:nowrap;border:1px solid #fff}.el-badge__content.is-fixed{position:absolute;top:0;right:10px;-webkit-transform:translateY(-50%) translateX(100%);transform:translateY(-50%) translateX(100%)}.el-rate__icon,.el-rate__item{position:relative;display:inline-block}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.el-badge__content--primary{background-color:#409eff}.el-badge__content--success{background-color:#67c23a}.el-badge__content--warning{background-color:#e6a23c}.el-badge__content--info{background-color:#909399}.el-badge__content--danger{background-color:#f56c6c}.el-card{border:1px solid #ebeef5;background-color:#fff;color:#303133;-webkit-transition:.3s;transition:.3s}.el-card.is-always-shadow,.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-card__header{padding:18px 20px;border-bottom:1px solid #ebeef5;-webkit-box-sizing:border-box;box-sizing:border-box}.el-card__body{padding:20px}.el-rate{height:20px;line-height:1}.el-rate__item{font-size:0;vertical-align:middle}.el-rate__icon{font-size:18px;margin-right:6px;color:#c0c4cc;-webkit-transition:.3s;transition:.3s}.el-rate__decimal,.el-rate__icon .path2{position:absolute;top:0;left:0}.el-rate__icon.hover{-webkit-transform:scale(1.15);transform:scale(1.15)}.el-rate__decimal{display:inline-block;overflow:hidden}.el-step.is-vertical,.el-steps{display:-webkit-box;display:-ms-flexbox}.el-rate__text{font-size:14px;vertical-align:middle}.el-steps{display:-webkit-box;display:-ms-flexbox;display:flex}.el-steps--simple{padding:13px 8%;border-radius:4px;background:#f5f7fa}.el-steps--horizontal{white-space:nowrap}.el-steps--vertical{height:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}.el-step{position:relative;-ms-flex-negative:1;flex-shrink:1}.el-step:last-of-type .el-step__line{display:none}.el-step:last-of-type.is-flex{-ms-flex-preferred-size:auto!important;flex-basis:auto!important;-ms-flex-negative:0;flex-shrink:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.el-step:last-of-type .el-step__description,.el-step:last-of-type .el-step__main{padding-right:0}.el-step__head{position:relative;width:100%}.el-step__head.is-process{color:#303133;border-color:#303133}.el-step__head.is-wait{color:#c0c4cc;border-color:#c0c4cc}.el-step__head.is-success{color:#67c23a;border-color:#67c23a}.el-step__head.is-error{color:#f56c6c;border-color:#f56c6c}.el-step__head.is-finish{color:#409eff;border-color:#409eff}.el-step__icon{position:relative;z-index:1;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:24px;height:24px;font-size:14px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#fff;-webkit-transition:.15s ease-out;transition:.15s ease-out}.el-step__icon.is-text{border-radius:50%;border:2px solid;border-color:inherit}.el-step__icon.is-icon{width:40px}.el-step__icon-inner{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:center;font-weight:700;line-height:1;color:inherit}.el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:25px;font-weight:400}.el-step__icon-inner.is-status{-webkit-transform:translateY(1px);transform:translateY(1px)}.el-step__line{position:absolute;border-color:inherit;background-color:#c0c4cc}.el-step__line-inner{display:block;border:1px solid;border-color:inherit;-webkit-transition:.15s ease-out;transition:.15s ease-out;-webkit-box-sizing:border-box;box-sizing:border-box;width:0;height:0}.el-step__main{white-space:normal;text-align:left}.el-step__title{font-size:16px;line-height:38px}.el-step__title.is-process{font-weight:700;color:#303133}.el-step__title.is-wait{color:#c0c4cc}.el-step__title.is-success{color:#67c23a}.el-step__title.is-error{color:#f56c6c}.el-step__title.is-finish{color:#409eff}.el-step__description{padding-right:10%;margin-top:-5px;font-size:12px;line-height:20px;font-weight:400}.el-step__description.is-process{color:#303133}.el-step__description.is-wait{color:#c0c4cc}.el-step__description.is-success{color:#67c23a}.el-step__description.is-error{color:#f56c6c}.el-step__description.is-finish{color:#409eff}.el-step.is-horizontal{display:inline-block}.el-step.is-horizontal .el-step__line{height:2px;top:11px;left:0;right:0}.el-step.is-vertical{display:-webkit-box;display:-ms-flexbox;display:flex}.el-step.is-vertical .el-step__head{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:24px}.el-step.is-vertical .el-step__main{padding-left:10px;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.el-step.is-vertical .el-step__title{line-height:24px;padding-bottom:8px}.el-step.is-vertical .el-step__line{width:2px;top:0;bottom:0;left:11px}.el-step.is-vertical .el-step__icon.is-icon{width:24px}.el-step.is-center .el-step__head,.el-step.is-center .el-step__main{text-align:center}.el-step.is-center .el-step__description{padding-left:20%;padding-right:20%}.el-step.is-center .el-step__line{left:50%;right:-50%}.el-step.is-simple{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-step.is-simple .el-step__head{width:auto;font-size:0;padding-right:10px}.el-step.is-simple .el-step__icon{background:0 0;width:16px;height:16px;font-size:12px}.el-step.is-simple .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:18px}.el-step.is-simple .el-step__icon-inner.is-status{-webkit-transform:scale(.8) translateY(1px);transform:scale(.8) translateY(1px)}.el-step.is-simple .el-step__main{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.el-step.is-simple .el-step__title{font-size:16px;line-height:20px}.el-step.is-simple:not(:last-of-type) .el-step__title{max-width:50%;word-break:break-all}.el-step.is-simple .el-step__arrow{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow: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}.el-step.is-simple .el-step__arrow:after,.el-step.is-simple .el-step__arrow:before{content:"";display:inline-block;position:absolute;height:15px;width:1px;background:#c0c4cc}.el-step.is-simple .el-step__arrow:before{-webkit-transform:rotate(-45deg) translateY(-4px);transform:rotate(-45deg) translateY(-4px);-webkit-transform-origin:0 0;transform-origin:0 0}.el-step.is-simple .el-step__arrow:after{-webkit-transform:rotate(45deg) translateY(4px);transform:rotate(45deg) translateY(4px);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.el-step.is-simple:last-of-type .el-step__arrow{display:none}.el-carousel{position:relative}.el-carousel--horizontal{overflow-x:hidden}.el-carousel--vertical{overflow-y:hidden}.el-carousel__container{position:relative;height:300px}.el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:36px;width:36px;cursor:pointer;-webkit-transition:.3s;transition:.3s;border-radius:50%;background-color:rgba(31,45,61,.11);color:#fff;position:absolute;top:50%;z-index:10;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center;font-size:12px}.el-carousel__arrow--left{left:16px}.el-carousel__arrow--right{right:16px}.el-carousel__arrow:hover{background-color:rgba(31,45,61,.23)}.el-carousel__arrow i{cursor:pointer}.el-carousel__indicators{position:absolute;list-style:none;margin:0;padding:0;z-index:2}.el-carousel__indicators--horizontal{bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.el-carousel__indicators--vertical{right:0;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-carousel__indicators--outside{bottom:26px;text-align:center;position:static;-webkit-transform:none;transform:none}.el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.el-carousel__indicators--outside button{background-color:#c0c4cc;opacity:.24}.el-carousel__indicators--labels{left:0;right:0;-webkit-transform:none;transform:none;text-align:center}.el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px}.el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.el-carousel__indicator{background-color:transparent;cursor:pointer}.el-carousel__indicator:hover button{opacity:.72}.el-carousel__indicator--horizontal{display:inline-block;padding:12px 4px}.el-carousel__indicator--vertical{padding:4px 12px}.el-carousel__indicator--vertical .el-carousel__button{width:2px;height:15px}.el-carousel__indicator.is-active button{opacity:1}.el-carousel__button{display:block;opacity:.48;width:30px;height:2px;background-color:#fff;border:none;outline:0;padding:0;margin:0;cursor:pointer;-webkit-transition:.3s;transition:.3s}.el-carousel__item,.el-carousel__mask{height:100%;top:0;left:0;position:absolute}.carousel-arrow-left-enter,.carousel-arrow-left-leave-active{-webkit-transform:translateY(-50%) translateX(-10px);transform:translateY(-50%) translateX(-10px);opacity:0}.carousel-arrow-right-enter,.carousel-arrow-right-leave-active{-webkit-transform:translateY(-50%) translateX(10px);transform:translateY(-50%) translateX(10px);opacity:0}.el-carousel__item{width:100%;display:inline-block;overflow:hidden;z-index:0}.el-carousel__item.is-active{z-index:2}.el-carousel__item--card,.el-carousel__item.is-animating{-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card{width:50%}.el-carousel__item--card.is-in-stage{cursor:pointer;z-index:1}.el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.el-carousel__item--card.is-active{z-index:2}.el-carousel__mask{width:100%;background-color:#fff;opacity:.24;-webkit-transition:.2s;transition:.2s}.el-fade-in-enter,.el-fade-in-leave-active,.el-fade-in-linear-enter,.el-fade-in-linear-leave,.el-fade-in-linear-leave-active,.fade-in-linear-enter,.fade-in-linear-leave,.fade-in-linear-leave-active{opacity:0}.el-fade-in-linear-enter-active,.el-fade-in-linear-leave-active,.fade-in-linear-enter-active,.fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.el-fade-in-enter-active,.el-fade-in-leave-active,.el-zoom-in-center-enter-active,.el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.el-zoom-in-center-enter,.el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.el-zoom-in-top-enter-active,.el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center top;transform-origin:center top}.el-zoom-in-top-enter,.el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.el-zoom-in-bottom-enter-active,.el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center bottom;transform-origin:center bottom}.el-zoom-in-bottom-enter,.el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.el-zoom-in-left-enter-active,.el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1);transform:scale(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:top left;transform-origin:top left}.el-zoom-in-left-enter,.el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45);transform:scale(.45)}.collapse-transition{-webkit-transition:height .3s ease-in-out,padding-top .3s ease-in-out,padding-bottom .3s ease-in-out;transition:height .3s ease-in-out,padding-top .3s ease-in-out,padding-bottom .3s ease-in-out}.horizontal-collapse-transition{-webkit-transition:width .3s ease-in-out,padding-left .3s ease-in-out,padding-right .3s ease-in-out;transition:width .3s ease-in-out,padding-left .3s ease-in-out,padding-right .3s ease-in-out}.el-list-enter-active,.el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.el-list-enter,.el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.el-collapse{border-top:1px solid #ebeef5;border-bottom:1px solid #ebeef5}.el-collapse-item.is-disabled .el-collapse-item__header{color:#bbb;cursor:not-allowed}.el-collapse-item__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:48px;line-height:48px;background-color:#fff;color:#303133;cursor:pointer;border-bottom:1px solid #ebeef5;font-size:13px;font-weight:500;-webkit-transition:border-bottom-color .3s;transition:border-bottom-color .3s;outline:0}.el-collapse-item__arrow{margin:0 8px 0 auto;transition:-webkit-transform .3s;-webkit-transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-weight:300}.el-collapse-item__arrow.is-active{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-collapse-item__header.focusing:focus:not(:hover){color:#409eff}.el-collapse-item__header.is-active{border-bottom-color:transparent}.el-collapse-item__wrap{will-change:height;background-color:#fff;overflow:hidden;box-sizing:border-box;border-bottom:1px solid #ebeef5}.el-cascader__tags,.el-collapse-item__wrap,.el-tag{-webkit-box-sizing:border-box}.el-collapse-item__content{padding-bottom:25px;font-size:13px;color:#303133;line-height:1.769230769230769}.el-collapse-item:last-child{margin-bottom:-1px}.el-popper .popper__arrow,.el-popper .popper__arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0,0,0,.03));filter:drop-shadow(0 2px 12px rgba(0,0,0,.03))}.el-popper .popper__arrow:after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#ebeef5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow:after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#ebeef5}.el-popper[x-placement^=bottom] .popper__arrow:after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#ebeef5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow:after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#ebeef5}.el-popper[x-placement^=left] .popper__arrow:after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.el-tag{background-color:#ecf5ff;display:inline-block;height:32px;padding:0 10px;line-height:30px;font-size:12px;color:#409eff;border:1px solid #d9ecff;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;white-space:nowrap}.el-tag.is-hit{border-color:#409eff}.el-tag .el-tag__close{color:#409eff}.el-tag .el-tag__close:hover{color:#fff;background-color:#409eff}.el-tag.el-tag--info{background-color:#f4f4f5;border-color:#e9e9eb;color:#909399}.el-tag.el-tag--info.is-hit{border-color:#909399}.el-tag.el-tag--info .el-tag__close{color:#909399}.el-tag.el-tag--info .el-tag__close:hover{color:#fff;background-color:#909399}.el-tag.el-tag--success{background-color:#f0f9eb;border-color:#e1f3d8;color:#67c23a}.el-tag.el-tag--success.is-hit{border-color:#67c23a}.el-tag.el-tag--success .el-tag__close{color:#67c23a}.el-tag.el-tag--success .el-tag__close:hover{color:#fff;background-color:#67c23a}.el-tag.el-tag--warning{background-color:#fdf6ec;border-color:#faecd8;color:#e6a23c}.el-tag.el-tag--warning.is-hit{border-color:#e6a23c}.el-tag.el-tag--warning .el-tag__close{color:#e6a23c}.el-tag.el-tag--warning .el-tag__close:hover{color:#fff;background-color:#e6a23c}.el-tag.el-tag--danger{background-color:#fef0f0;border-color:#fde2e2;color:#f56c6c}.el-tag.el-tag--danger.is-hit{border-color:#f56c6c}.el-tag.el-tag--danger .el-tag__close{color:#f56c6c}.el-tag.el-tag--danger .el-tag__close:hover{color:#fff;background-color:#f56c6c}.el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:16px;width:16px;line-height:16px;vertical-align:middle;top:-1px;right:-5px}.el-tag .el-icon-close:before{display:block}.el-tag--dark{background-color:#409eff;color:#fff}.el-tag--dark,.el-tag--dark.is-hit{border-color:#409eff}.el-tag--dark .el-tag__close{color:#fff}.el-tag--dark .el-tag__close:hover{color:#fff;background-color:#66b1ff}.el-tag--dark.el-tag--info{background-color:#909399;border-color:#909399;color:#fff}.el-tag--dark.el-tag--info.is-hit{border-color:#909399}.el-tag--dark.el-tag--info .el-tag__close{color:#fff}.el-tag--dark.el-tag--info .el-tag__close:hover{color:#fff;background-color:#a6a9ad}.el-tag--dark.el-tag--success{background-color:#67c23a;border-color:#67c23a;color:#fff}.el-tag--dark.el-tag--success.is-hit{border-color:#67c23a}.el-tag--dark.el-tag--success .el-tag__close{color:#fff}.el-tag--dark.el-tag--success .el-tag__close:hover{color:#fff;background-color:#85ce61}.el-tag--dark.el-tag--warning{background-color:#e6a23c;border-color:#e6a23c;color:#fff}.el-tag--dark.el-tag--warning.is-hit{border-color:#e6a23c}.el-tag--dark.el-tag--warning .el-tag__close{color:#fff}.el-tag--dark.el-tag--warning .el-tag__close:hover{color:#fff;background-color:#ebb563}.el-tag--dark.el-tag--danger{background-color:#f56c6c;border-color:#f56c6c;color:#fff}.el-tag--dark.el-tag--danger.is-hit{border-color:#f56c6c}.el-tag--dark.el-tag--danger .el-tag__close{color:#fff}.el-tag--dark.el-tag--danger .el-tag__close:hover{color:#fff;background-color:#f78989}.el-tag--plain{background-color:#fff;border-color:#b3d8ff;color:#409eff}.el-tag--plain.is-hit{border-color:#409eff}.el-tag--plain .el-tag__close{color:#409eff}.el-tag--plain .el-tag__close:hover{color:#fff;background-color:#409eff}.el-tag--plain.el-tag--info{background-color:#fff;border-color:#d3d4d6;color:#909399}.el-tag--plain.el-tag--info.is-hit{border-color:#909399}.el-tag--plain.el-tag--info .el-tag__close{color:#909399}.el-tag--plain.el-tag--info .el-tag__close:hover{color:#fff;background-color:#909399}.el-tag--plain.el-tag--success{background-color:#fff;border-color:#c2e7b0;color:#67c23a}.el-tag--plain.el-tag--success.is-hit{border-color:#67c23a}.el-tag--plain.el-tag--success .el-tag__close{color:#67c23a}.el-tag--plain.el-tag--success .el-tag__close:hover{color:#fff;background-color:#67c23a}.el-tag--plain.el-tag--warning{background-color:#fff;border-color:#f5dab1;color:#e6a23c}.el-tag--plain.el-tag--warning.is-hit{border-color:#e6a23c}.el-tag--plain.el-tag--warning .el-tag__close{color:#e6a23c}.el-tag--plain.el-tag--warning .el-tag__close:hover{color:#fff;background-color:#e6a23c}.el-tag--plain.el-tag--danger{background-color:#fff;border-color:#fbc4c4;color:#f56c6c}.el-tag--plain.el-tag--danger.is-hit{border-color:#f56c6c}.el-tag--plain.el-tag--danger .el-tag__close{color:#f56c6c}.el-tag--plain.el-tag--danger .el-tag__close:hover{color:#fff;background-color:#f56c6c}.el-tag--medium{height:28px;line-height:26px}.el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.el-tag--small{height:24px;padding:0 8px;line-height:22px}.el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.el-tag--mini{height:20px;padding:0 5px;line-height:19px}.el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.el-cascader{display:inline-block;position:relative;font-size:14px;line-height:40px}.el-cascader:not(.is-disabled):hover .el-input__inner{cursor:pointer;border-color:#c0c4cc}.el-cascader .el-input .el-input__inner:focus,.el-cascader .el-input.is-focus .el-input__inner{border-color:#409eff}.el-cascader .el-input{cursor:pointer}.el-cascader .el-input .el-input__inner{text-overflow:ellipsis}.el-cascader .el-input .el-icon-arrow-down{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:14px}.el-cascader .el-input .el-icon-arrow-down.is-reverse{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.el-cascader .el-input .el-icon-circle-close:hover{color:#909399}.el-cascader--medium{font-size:14px;line-height:36px}.el-cascader--small{font-size:13px;line-height:32px}.el-cascader--mini{font-size:12px;line-height:28px}.el-cascader.is-disabled .el-cascader__label{z-index:2;color:#c0c4cc}.el-cascader__dropdown{margin:5px 0;font-size:14px;background:#fff;border:1px solid #e4e7ed;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-cascader__tags{position:absolute;left:0;right:30px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;line-height:normal;text-align:left;-webkit-box-sizing:border-box;box-sizing:border-box}.el-cascader__tags .el-tag{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;max-width:100%;margin:2px 0 2px 6px;text-overflow:ellipsis;background:#f0f2f5}.el-cascader__tags .el-tag:not(.is-hit){border-color:transparent}.el-cascader__tags .el-tag>span{-webkit-box-flex:1;-ms-flex:1;flex:1;overflow:hidden;text-overflow:ellipsis}.el-cascader__tags .el-tag .el-icon-close{-webkit-box-flex:0;-ms-flex:none;flex:none;background-color:#c0c4cc;color:#fff}.el-cascader__tags .el-tag .el-icon-close:hover{background-color:#909399}.el-cascader__suggestion-panel{border-radius:4px}.el-cascader__suggestion-list{max-height:204px;margin:0;padding:6px 0;font-size:14px;color:#606266;text-align:center}.el-cascader__suggestion-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:34px;padding:0 15px;text-align:left;outline:0;cursor:pointer}.el-cascader__suggestion-item:focus,.el-cascader__suggestion-item:hover{background:#f5f7fa}.el-cascader__suggestion-item.is-checked{color:#409eff;font-weight:700}.el-cascader__suggestion-item>span{margin-right:10px}.el-cascader__empty-text{margin:10px 0;color:#c0c4cc}.el-cascader__search-input{-webkit-box-flex:1;-ms-flex:1;flex:1;height:24px;min-width:60px;margin:2px 0 2px 15px;padding:0;color:#606266;border:none;outline:0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-cascader__search-input::-webkit-input-placeholder{color:#c0c4cc}.el-cascader__search-input:-ms-input-placeholder{color:#c0c4cc}.el-cascader__search-input::-ms-input-placeholder{color:#c0c4cc}.el-cascader__search-input::placeholder{color:#c0c4cc}.el-color-predefine{font-size:12px;margin-top:8px;width:280px}.el-color-predefine,.el-color-predefine__colors{display:-webkit-box;display:-ms-flexbox;display:flex}.el-color-predefine__colors{-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-color-predefine__color-selector{margin:0 0 8px 8px;width:20px;height:20px;border-radius:4px;cursor:pointer}.el-color-predefine__color-selector:nth-child(10n+1){margin-left:0}.el-color-predefine__color-selector.selected{-webkit-box-shadow:0 0 3px 2px #409eff;box-shadow:0 0 3px 2px #409eff}.el-color-predefine__color-selector>div{display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;border-radius:3px}.el-color-predefine__color-selector.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-hue-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background-color:red;padding:0 2px}.el-color-hue-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,color-stop(0,red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(90deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red);height:100%}.el-color-hue-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.el-color-hue-slider.is-vertical{width:12px;height:180px;padding:2px 0}.el-color-hue-slider.is-vertical .el-color-hue-slider__bar{background:-webkit-gradient(linear,left top,left bottom,color-stop(0,red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(180deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red)}.el-color-hue-slider.is-vertical .el-color-hue-slider__thumb{left:0;top:0;width:100%;height:4px}.el-color-svpanel{position:relative;width:280px;height:180px}.el-color-svpanel__black,.el-color-svpanel__white{position:absolute;top:0;left:0;right:0;bottom:0}.el-color-svpanel__white{background:-webkit-gradient(linear,left top,right top,from(#fff),to(hsla(0,0%,100%,0)));background:linear-gradient(90deg,#fff,hsla(0,0%,100%,0))}.el-color-svpanel__black{background:-webkit-gradient(linear,left bottom,left top,from(#000),to(transparent));background:linear-gradient(0deg,#000,transparent)}.el-color-svpanel__cursor{position:absolute}.el-color-svpanel__cursor>div{cursor:head;width:4px;height:4px;-webkit-box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);border-radius:50%;-webkit-transform:translate(-2px,-2px);transform:translate(-2px,-2px)}.el-color-alpha-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-alpha-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,color-stop(0,hsla(0,0%,100%,0)),to(#fff));background:linear-gradient(90deg,hsla(0,0%,100%,0) 0,#fff);height:100%}.el-color-alpha-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.el-color-alpha-slider.is-vertical{width:20px;height:180px}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__bar{background:-webkit-gradient(linear,left top,left bottom,color-stop(0,hsla(0,0%,100%,0)),to(#fff));background:linear-gradient(180deg,hsla(0,0%,100%,0) 0,#fff)}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__thumb{left:0;top:0;width:100%;height:4px}.el-color-dropdown{width:300px}.el-color-dropdown__main-wrapper{margin-bottom:6px}.el-color-dropdown__main-wrapper:after{content:"";display:table;clear:both}.el-color-dropdown__btns{margin-top:6px;text-align:right}.el-color-dropdown__value{float:left;line-height:26px;font-size:12px;color:#000;width:160px}.el-color-dropdown__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.el-color-dropdown__btn[disabled]{color:#ccc;cursor:not-allowed}.el-color-dropdown__btn:hover{color:#409eff;border-color:#409eff}.el-color-dropdown__link-btn{cursor:pointer;color:#409eff;text-decoration:none;padding:15px;font-size:12px}.el-color-dropdown__link-btn:hover{color:tint(#409eff,20%)}.el-color-picker{display:inline-block;position:relative;line-height:normal;height:40px}.el-color-picker.is-disabled .el-color-picker__trigger{cursor:not-allowed}.el-color-picker--medium{height:36px}.el-color-picker--medium .el-color-picker__trigger{height:36px;width:36px}.el-color-picker--medium .el-color-picker__mask{height:34px;width:34px}.el-color-picker--small{height:32px}.el-color-picker--small .el-color-picker__trigger{height:32px;width:32px}.el-color-picker--small .el-color-picker__mask{height:30px;width:30px}.el-color-picker--small .el-color-picker__empty,.el-color-picker--small .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.el-color-picker--mini{height:28px}.el-color-picker--mini .el-color-picker__trigger{height:28px;width:28px}.el-color-picker--mini .el-color-picker__mask{height:26px;width:26px}.el-color-picker--mini .el-color-picker__empty,.el-color-picker--mini .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.el-color-picker__mask{height:38px;width:38px;border-radius:4px;position:absolute;top:1px;left:1px;z-index:1;cursor:not-allowed;background-color:hsla(0,0%,100%,.7)}.el-color-picker__trigger{display:inline-block;height:40px;width:40px;padding:4px;border:1px solid #e6e6e6;border-radius:4px;font-size:0;cursor:pointer}.el-color-picker__color,.el-color-picker__trigger{-webkit-box-sizing:border-box;box-sizing:border-box;position:relative}.el-color-picker__color{display:block;border:1px solid #999;border-radius:2px;width:100%;height:100%;text-align:center}.el-color-picker__color.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-picker__color-inner{position:absolute;left:0;top:0;right:0;bottom:0}.el-color-picker__empty,.el-color-picker__icon{top:50%;left:50%;font-size:12px;position:absolute}.el-color-picker__empty{color:#999}.el-color-picker__empty,.el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}.el-color-picker__icon{display:inline-block;width:100%;color:#fff;text-align:center}.el-color-picker__panel{position:absolute;z-index:10;padding:6px;-webkit-box-sizing:content-box;box-sizing:content-box;background-color:#fff;border:1px solid #ebeef5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-textarea{position:relative;display:inline-block;width:100%;vertical-align:bottom;font-size:14px}.el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:inherit;color:#606266;background-color:#fff;background-image:none;border:1px solid #dcdfe6;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.el-textarea__inner::-webkit-input-placeholder{color:#c0c4cc}.el-textarea__inner:-ms-input-placeholder{color:#c0c4cc}.el-textarea__inner::-ms-input-placeholder{color:#c0c4cc}.el-textarea__inner::placeholder{color:#c0c4cc}.el-textarea__inner:hover{border-color:#c0c4cc}.el-textarea__inner:focus{outline:0;border-color:#409eff}.el-textarea .el-input__count{color:#909399;background:#fff;position:absolute;font-size:12px;bottom:5px;right:10px}.el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#e4e7ed;color:#c0c4cc;cursor:not-allowed}.el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#c0c4cc}.el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#c0c4cc}.el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#c0c4cc}.el-textarea.is-disabled .el-textarea__inner::placeholder{color:#c0c4cc}.el-textarea.is-exceed .el-textarea__inner{border-color:#f56c6c}.el-textarea.is-exceed .el-input__count{color:#f56c6c}.el-input{position:relative;font-size:14px;display:inline-block;width:100%}.el-input::-webkit-scrollbar{z-index:11;width:6px}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.el-input::-webkit-scrollbar-corner,.el-input::-webkit-scrollbar-track{background:#fff}.el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.el-input .el-input__clear{color:#c0c4cc;font-size:14px;cursor:pointer;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.el-input .el-input__clear:hover{color:#909399}.el-input .el-input__count{height:100%;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#909399;font-size:12px}.el-input .el-input__count .el-input__count-inner{background:#fff;line-height:normal;display:inline-block;padding:0 5px}.el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #dcdfe6;-webkit-box-sizing:border-box;box-sizing:border-box;color:#606266;display:inline-block;font-size:inherit;height:40px;line-height:40px;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.el-input__prefix,.el-input__suffix{position:absolute;top:0;-webkit-transition:all .3s;height:100%;color:#c0c4cc;text-align:center}.el-input__inner::-webkit-input-placeholder{color:#c0c4cc}.el-input__inner:-ms-input-placeholder{color:#c0c4cc}.el-input__inner::-ms-input-placeholder{color:#c0c4cc}.el-input__inner::placeholder{color:#c0c4cc}.el-input__inner:hover{border-color:#c0c4cc}.el-input.is-active .el-input__inner,.el-input__inner:focus{border-color:#409eff;outline:0}.el-input__suffix{right:5px;-webkit-transition:all .3s;transition:all .3s}.el-input__suffix-inner{pointer-events:all}.el-input__prefix{left:5px}.el-input__icon,.el-input__prefix{-webkit-transition:all .3s;transition:all .3s}.el-input__icon{height:100%;width:25px;text-align:center;line-height:40px}.el-input__icon:after{content:"";height:100%;width:0;display:inline-block;vertical-align:middle}.el-input__validateIcon{pointer-events:none}.el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#e4e7ed;color:#c0c4cc;cursor:not-allowed}.el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__inner::placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__icon{cursor:not-allowed}.el-link,.el-transfer-panel__filter .el-icon-circle-close{cursor:pointer}.el-input.is-exceed .el-input__inner{border-color:#f56c6c}.el-input.is-exceed .el-input__suffix .el-input__count{color:#f56c6c}.el-input--suffix .el-input__inner{padding-right:30px}.el-input--prefix .el-input__inner{padding-left:30px}.el-input--medium{font-size:14px}.el-input--medium .el-input__inner{height:36px;line-height:36px}.el-input--medium .el-input__icon{line-height:36px}.el-input--small{font-size:13px}.el-input--small .el-input__inner{height:32px;line-height:32px}.el-input--small .el-input__icon{line-height:32px}.el-input--mini{font-size:12px}.el-input--mini .el-input__inner{height:28px;line-height:28px}.el-input--mini .el-input__icon{line-height:28px}.el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate;border-spacing:0}.el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.el-input-group__append,.el-input-group__prepend{background-color:#f5f7fa;color:#909399;vertical-align:middle;display:table-cell;position:relative;border:1px solid #dcdfe6;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.el-input-group--prepend .el-input__inner,.el-input-group__append{border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--append .el-input__inner,.el-input-group__prepend{border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group__append:focus,.el-input-group__prepend:focus{outline:0}.el-input-group__append .el-button,.el-input-group__append .el-select,.el-input-group__prepend .el-button,.el-input-group__prepend .el-select{display:inline-block;margin:-10px -20px}.el-input-group__append button.el-button,.el-input-group__append div.el-select .el-input__inner,.el-input-group__append div.el-select:hover .el-input__inner,.el-input-group__prepend button.el-button,.el-input-group__prepend div.el-select .el-input__inner,.el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.el-input-group__append .el-button,.el-input-group__append .el-input,.el-input-group__prepend .el-button,.el-input-group__prepend .el-input{font-size:inherit}.el-input-group__prepend{border-right:0}.el-input-group__append{border-left:0}.el-input-group--append .el-select .el-input.is-focus .el-input__inner,.el-input-group--prepend .el-select .el-input.is-focus .el-input__inner{border-color:transparent}.el-input__inner::-ms-clear{display:none;width:0;height:0}.el-transfer{font-size:14px}.el-transfer__buttons{display:inline-block;vertical-align:middle;padding:0 30px}.el-transfer__button{display:block;margin:0 auto;padding:10px;border-radius:50%;color:#fff;background-color:#409eff;font-size:0}.el-transfer__button.is-with-texts{border-radius:4px}.el-transfer__button.is-disabled,.el-transfer__button.is-disabled:hover{border:1px solid #dcdfe6;background-color:#f5f7fa;color:#c0c4cc}.el-transfer__button:first-child{margin-bottom:10px}.el-transfer__button:nth-child(2){margin:0}.el-transfer__button i,.el-transfer__button span{font-size:14px}.el-transfer__button [class*=el-icon-]+span{margin-left:0}.el-transfer-panel{border:1px solid #ebeef5;border-radius:4px;overflow:hidden;background:#fff;display:inline-block;vertical-align:middle;width:200px;max-height:100%;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative}.el-transfer-panel__body{height:246px}.el-transfer-panel__body.is-with-footer{padding-bottom:40px}.el-transfer-panel__list{margin:0;padding:6px 0;list-style:none;height:246px;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box}.el-transfer-panel__list.is-filterable{height:194px;padding-top:0}.el-transfer-panel__item{height:30px;line-height:30px;padding-left:15px;display:block}.el-transfer-panel__item+.el-transfer-panel__item{margin-left:0;display:block!important}.el-transfer-panel__item.el-checkbox{color:#606266}.el-transfer-panel__item:hover{color:#409eff}.el-transfer-panel__item.el-checkbox .el-checkbox__label{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:24px;line-height:30px}.el-transfer-panel__item .el-checkbox__input{position:absolute;top:8px}.el-transfer-panel__filter{text-align:center;margin:15px;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;width:auto}.el-transfer-panel__filter .el-input__inner{height:32px;width:100%;font-size:12px;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:16px;padding-right:10px;padding-left:30px}.el-transfer-panel__filter .el-input__icon{margin-left:5px}.el-transfer-panel .el-transfer-panel__header{height:40px;line-height:40px;background:#f5f7fa;margin:0;padding-left:15px;border-bottom:1px solid #ebeef5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#000}.el-transfer-panel .el-transfer-panel__header .el-checkbox{display:block;line-height:40px}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label{font-size:16px;color:#303133;font-weight:400}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label span{position:absolute;right:15px;color:#909399;font-size:12px;font-weight:400}.el-divider__text,.el-link{font-weight:500;font-size:14px}.el-transfer-panel .el-transfer-panel__footer{height:40px;background:#fff;margin:0;padding:0;border-top:1px solid #ebeef5;position:absolute;bottom:0;left:0;width:100%;z-index:1}.el-transfer-panel .el-transfer-panel__footer:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-container,.el-timeline-item__node{display:-webkit-box;display:-ms-flexbox}.el-transfer-panel .el-transfer-panel__footer .el-checkbox{padding-left:20px;color:#606266}.el-transfer-panel .el-transfer-panel__empty{margin:0;height:30px;line-height:30px;padding:6px 15px 0;color:#909399;text-align:center}.el-transfer-panel .el-checkbox__label{padding-left:8px}.el-transfer-panel .el-checkbox__inner{height:14px;width:14px;border-radius:3px}.el-transfer-panel .el-checkbox__inner:after{height:6px;width:3px;left:4px}.el-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-box-sizing:border-box;box-sizing:border-box;min-width:0}.el-container.is-vertical,.el-drawer{-webkit-box-orient:vertical;-webkit-box-direction:normal}.el-aside,.el-header{-webkit-box-sizing:border-box}.el-container.is-vertical{-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column}.el-header{padding:0 20px}.el-aside,.el-header{-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}.el-aside{overflow:auto}.el-footer,.el-main{-webkit-box-sizing:border-box}.el-main{display:block;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;overflow:auto;padding:20px}.el-footer,.el-main{-webkit-box-sizing:border-box;box-sizing:border-box}.el-footer{padding:0 20px;-ms-flex-negative:0;flex-shrink:0}.el-timeline{margin:0;font-size:14px;list-style:none}.el-timeline .el-timeline-item:last-child .el-timeline-item__tail{display:none}.el-timeline-item{position:relative;padding-bottom:20px}.el-timeline-item__wrapper{position:relative;padding-left:28px;top:-3px}.el-timeline-item__tail{position:absolute;left:4px;height:100%;border-left:2px solid #e4e7ed}.el-timeline-item__icon{color:#fff;font-size:13px}.el-timeline-item__node{position:absolute;background-color:#e4e7ed;border-radius:50%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-image__error,.el-timeline-item__dot{display:-webkit-box;display:-ms-flexbox}.el-timeline-item__node--normal{left:-1px;width:12px;height:12px}.el-timeline-item__node--large{left:-2px;width:14px;height:14px}.el-timeline-item__node--primary{background-color:#409eff}.el-timeline-item__node--success{background-color:#67c23a}.el-timeline-item__node--warning{background-color:#e6a23c}.el-timeline-item__node--danger{background-color:#f56c6c}.el-timeline-item__node--info{background-color:#909399}.el-timeline-item__dot{position:absolute;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-timeline-item__content{color:#303133}.el-timeline-item__timestamp{color:#909399;line-height:1;font-size:13px}.el-timeline-item__timestamp.is-top{margin-bottom:8px;padding-top:4px}.el-timeline-item__timestamp.is-bottom{margin-top:8px}.el-link{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;vertical-align:middle;position:relative;text-decoration:none;outline:0;padding:0}.el-link.is-underline:hover:after{content:"";position:absolute;left:0;right:0;height:0;bottom:0;border-bottom:1px solid #409eff}.el-link.el-link--default:after,.el-link.el-link--primary.is-underline:hover:after,.el-link.el-link--primary:after{border-color:#409eff}.el-link.is-disabled{cursor:not-allowed}.el-link [class*=el-icon-]+span{margin-left:5px}.el-link.el-link--default{color:#606266}.el-link.el-link--default:hover{color:#409eff}.el-link.el-link--default.is-disabled{color:#c0c4cc}.el-link.el-link--primary{color:#409eff}.el-link.el-link--primary:hover{color:#66b1ff}.el-link.el-link--primary.is-disabled{color:#a0cfff}.el-link.el-link--danger.is-underline:hover:after,.el-link.el-link--danger:after{border-color:#f56c6c}.el-link.el-link--danger{color:#f56c6c}.el-link.el-link--danger:hover{color:#f78989}.el-link.el-link--danger.is-disabled{color:#fab6b6}.el-link.el-link--success.is-underline:hover:after,.el-link.el-link--success:after{border-color:#67c23a}.el-link.el-link--success{color:#67c23a}.el-link.el-link--success:hover{color:#85ce61}.el-link.el-link--success.is-disabled{color:#b3e19d}.el-link.el-link--warning.is-underline:hover:after,.el-link.el-link--warning:after{border-color:#e6a23c}.el-link.el-link--warning{color:#e6a23c}.el-link.el-link--warning:hover{color:#ebb563}.el-link.el-link--warning.is-disabled{color:#f3d19e}.el-link.el-link--info.is-underline:hover:after,.el-link.el-link--info:after{border-color:#909399}.el-link.el-link--info{color:#909399}.el-link.el-link--info:hover{color:#a6a9ad}.el-link.el-link--info.is-disabled{color:#c8c9cc}.el-divider{background-color:#dcdfe6;position:relative}.el-divider--horizontal{display:block;height:1px;width:100%;margin:24px 0}.el-divider--vertical{display:inline-block;width:1px;height:1em;margin:0 8px;vertical-align:middle;position:relative}.el-divider__text{position:absolute;background-color:#fff;padding:0 20px;color:#303133}.el-image__error,.el-image__placeholder{background:#f5f7fa}.el-divider__text.is-left{left:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-divider__text.is-center{left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.el-divider__text.is-right{right:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-image__error,.el-image__inner,.el-image__placeholder{width:100%;height:100%}.el-image{position:relative;display:inline-block;overflow:hidden}.el-image__inner{vertical-align:top}.el-image__inner--center{position:relative;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);display:block}.el-image__error{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:14px;color:#c0c4cc;vertical-align:middle}.el-image__preview{cursor:pointer}.el-image-viewer__wrapper{position:fixed;top:0;right:0;bottom:0;left:0}.el-image-viewer__btn{position:absolute;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;border-radius:50%;opacity:.8;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;user-select:none}.el-button,.el-checkbox,.el-image-viewer__btn{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-image-viewer__close{top:40px;right:40px;width:40px;height:40px;font-size:40px}.el-image-viewer__canvas{width:100%;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-image-viewer__actions{left:50%;bottom:30px;-webkit-transform:translateX(-50%);transform:translateX(-50%);width:282px;height:44px;padding:0 23px;background-color:#606266;border-color:#fff;border-radius:22px}.el-image-viewer__actions__inner{width:100%;height:100%;text-align:justify;cursor:default;font-size:23px;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-pack:distribute;justify-content:space-around}.el-image-viewer__next,.el-image-viewer__prev{top:50%;width:44px;height:44px;font-size:24px;color:#fff;background-color:#606266;border-color:#fff}.el-image-viewer__prev{left:40px}.el-image-viewer__next,.el-image-viewer__prev{-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-image-viewer__next{right:40px;text-indent:2px}.el-image-viewer__mask{position:absolute;width:100%;height:100%;top:0;left:0;opacity:.5;background:#000}.viewer-fade-enter-active{-webkit-animation:viewer-fade-in .3s;animation:viewer-fade-in .3s}.viewer-fade-leave-active{-webkit-animation:viewer-fade-out .3s;animation:viewer-fade-out .3s}@-webkit-keyframes viewer-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@keyframes viewer-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@-webkit-keyframes viewer-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes viewer-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #dcdfe6;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;padding:12px 20px;font-size:14px;border-radius:4px}.el-button+.el-button{margin-left:10px}.el-button:focus,.el-button:hover{color:#409eff;border-color:#c6e2ff;background-color:#ecf5ff}.el-button:active{color:#3a8ee6;border-color:#3a8ee6;outline:0}.el-button::-moz-focus-inner{border:0}.el-button [class*=el-icon-]+span{margin-left:5px}.el-button.is-plain:focus,.el-button.is-plain:hover{background:#fff;border-color:#409eff;color:#409eff}.el-button.is-active,.el-button.is-plain:active{color:#3a8ee6;border-color:#3a8ee6}.el-button.is-plain:active{background:#fff;outline:0}.el-button.is-disabled,.el-button.is-disabled:focus,.el-button.is-disabled:hover{color:#c0c4cc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#ebeef5}.el-button.is-disabled.el-button--text{background-color:transparent}.el-button.is-disabled.is-plain,.el-button.is-disabled.is-plain:focus,.el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#ebeef5;color:#c0c4cc}.el-button.is-loading{position:relative;pointer-events:none}.el-button.is-loading:before{pointer-events:none;content:"";position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:hsla(0,0%,100%,.35)}.el-button.is-round{border-radius:20px;padding:12px 23px}.el-button.is-circle{border-radius:50%;padding:12px}.el-button--primary{color:#fff;background-color:#409eff;border-color:#409eff}.el-button--primary:focus,.el-button--primary:hover{background:#66b1ff;border-color:#66b1ff;color:#fff}.el-button--primary.is-active,.el-button--primary:active{background:#3a8ee6;border-color:#3a8ee6;color:#fff}.el-button--primary:active{outline:0}.el-button--primary.is-disabled,.el-button--primary.is-disabled:active,.el-button--primary.is-disabled:focus,.el-button--primary.is-disabled:hover{color:#fff;background-color:#a0cfff;border-color:#a0cfff}.el-button--primary.is-plain{color:#409eff;background:#ecf5ff;border-color:#b3d8ff}.el-button--primary.is-plain:focus,.el-button--primary.is-plain:hover{background:#409eff;border-color:#409eff;color:#fff}.el-button--primary.is-plain:active{background:#3a8ee6;border-color:#3a8ee6;color:#fff;outline:0}.el-button--primary.is-plain.is-disabled,.el-button--primary.is-plain.is-disabled:active,.el-button--primary.is-plain.is-disabled:focus,.el-button--primary.is-plain.is-disabled:hover{color:#8cc5ff;background-color:#ecf5ff;border-color:#d9ecff}.el-button--success{color:#fff;background-color:#67c23a;border-color:#67c23a}.el-button--success:focus,.el-button--success:hover{background:#85ce61;border-color:#85ce61;color:#fff}.el-button--success.is-active,.el-button--success:active{background:#5daf34;border-color:#5daf34;color:#fff}.el-button--success:active{outline:0}.el-button--success.is-disabled,.el-button--success.is-disabled:active,.el-button--success.is-disabled:focus,.el-button--success.is-disabled:hover{color:#fff;background-color:#b3e19d;border-color:#b3e19d}.el-button--success.is-plain{color:#67c23a;background:#f0f9eb;border-color:#c2e7b0}.el-button--success.is-plain:focus,.el-button--success.is-plain:hover{background:#67c23a;border-color:#67c23a;color:#fff}.el-button--success.is-plain:active{background:#5daf34;border-color:#5daf34;color:#fff;outline:0}.el-button--success.is-plain.is-disabled,.el-button--success.is-plain.is-disabled:active,.el-button--success.is-plain.is-disabled:focus,.el-button--success.is-plain.is-disabled:hover{color:#a4da89;background-color:#f0f9eb;border-color:#e1f3d8}.el-button--warning{color:#fff;background-color:#e6a23c;border-color:#e6a23c}.el-button--warning:focus,.el-button--warning:hover{background:#ebb563;border-color:#ebb563;color:#fff}.el-button--warning.is-active,.el-button--warning:active{background:#cf9236;border-color:#cf9236;color:#fff}.el-button--warning:active{outline:0}.el-button--warning.is-disabled,.el-button--warning.is-disabled:active,.el-button--warning.is-disabled:focus,.el-button--warning.is-disabled:hover{color:#fff;background-color:#f3d19e;border-color:#f3d19e}.el-button--warning.is-plain{color:#e6a23c;background:#fdf6ec;border-color:#f5dab1}.el-button--warning.is-plain:focus,.el-button--warning.is-plain:hover{background:#e6a23c;border-color:#e6a23c;color:#fff}.el-button--warning.is-plain:active{background:#cf9236;border-color:#cf9236;color:#fff;outline:0}.el-button--warning.is-plain.is-disabled,.el-button--warning.is-plain.is-disabled:active,.el-button--warning.is-plain.is-disabled:focus,.el-button--warning.is-plain.is-disabled:hover{color:#f0c78a;background-color:#fdf6ec;border-color:#faecd8}.el-button--danger{color:#fff;background-color:#f56c6c;border-color:#f56c6c}.el-button--danger:focus,.el-button--danger:hover{background:#f78989;border-color:#f78989;color:#fff}.el-button--danger.is-active,.el-button--danger:active{background:#dd6161;border-color:#dd6161;color:#fff}.el-button--danger:active{outline:0}.el-button--danger.is-disabled,.el-button--danger.is-disabled:active,.el-button--danger.is-disabled:focus,.el-button--danger.is-disabled:hover{color:#fff;background-color:#fab6b6;border-color:#fab6b6}.el-button--danger.is-plain{color:#f56c6c;background:#fef0f0;border-color:#fbc4c4}.el-button--danger.is-plain:focus,.el-button--danger.is-plain:hover{background:#f56c6c;border-color:#f56c6c;color:#fff}.el-button--danger.is-plain:active{background:#dd6161;border-color:#dd6161;color:#fff;outline:0}.el-button--danger.is-plain.is-disabled,.el-button--danger.is-plain.is-disabled:active,.el-button--danger.is-plain.is-disabled:focus,.el-button--danger.is-plain.is-disabled:hover{color:#f9a7a7;background-color:#fef0f0;border-color:#fde2e2}.el-button--info{color:#fff;background-color:#909399;border-color:#909399}.el-button--info:focus,.el-button--info:hover{background:#a6a9ad;border-color:#a6a9ad;color:#fff}.el-button--info.is-active,.el-button--info:active{background:#82848a;border-color:#82848a;color:#fff}.el-button--info:active{outline:0}.el-button--info.is-disabled,.el-button--info.is-disabled:active,.el-button--info.is-disabled:focus,.el-button--info.is-disabled:hover{color:#fff;background-color:#c8c9cc;border-color:#c8c9cc}.el-button--info.is-plain{color:#909399;background:#f4f4f5;border-color:#d3d4d6}.el-button--info.is-plain:focus,.el-button--info.is-plain:hover{background:#909399;border-color:#909399;color:#fff}.el-button--info.is-plain:active{background:#82848a;border-color:#82848a;color:#fff;outline:0}.el-button--info.is-plain.is-disabled,.el-button--info.is-plain.is-disabled:active,.el-button--info.is-plain.is-disabled:focus,.el-button--info.is-plain.is-disabled:hover{color:#bcbec2;background-color:#f4f4f5;border-color:#e9e9eb}.el-button--text,.el-button--text.is-disabled,.el-button--text.is-disabled:focus,.el-button--text.is-disabled:hover,.el-button--text:active{border-color:transparent}.el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.el-button--mini,.el-button--small{font-size:12px;border-radius:3px}.el-button--medium.is-round{padding:10px 20px}.el-button--medium.is-circle{padding:10px}.el-button--small,.el-button--small.is-round{padding:9px 15px}.el-button--small.is-circle{padding:9px}.el-button--mini,.el-button--mini.is-round{padding:7px 15px}.el-button--mini.is-circle{padding:7px}.el-button--text{color:#409eff;background:0 0;padding-left:0;padding-right:0}.el-button--text:focus,.el-button--text:hover{color:#66b1ff;border-color:transparent;background-color:transparent}.el-button--text:active{color:#3a8ee6;background-color:transparent}.el-button-group{display:inline-block;vertical-align:middle}.el-button-group:after,.el-button-group:before{display:table;content:""}.el-button-group:after{clear:both}.el-button-group>.el-button{float:left;position:relative}.el-button-group>.el-button+.el-button{margin-left:0}.el-button-group>.el-button.is-disabled{z-index:1}.el-button-group>.el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.el-button-group>.el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.el-button-group>.el-button:first-child:last-child{border-radius:4px}.el-button-group>.el-button:first-child:last-child.is-round{border-radius:20px}.el-button-group>.el-button:first-child:last-child.is-circle{border-radius:50%}.el-button-group>.el-button:not(:first-child):not(:last-child){border-radius:0}.el-button-group>.el-button:not(:last-child){margin-right:-1px}.el-button-group>.el-button.is-active,.el-button-group>.el-button:active,.el-button-group>.el-button:focus,.el-button-group>.el-button:hover{z-index:1}.el-button-group>.el-dropdown>.el-button{border-top-left-radius:0;border-bottom-left-radius:0;border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--primary:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--primary:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--success:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--success:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--warning:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--warning:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--danger:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--danger:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--info:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--info:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-calendar{background-color:#fff}.el-calendar__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:12px 20px;border-bottom:1px solid #ebeef5}.el-backtop,.el-page-header{display:-webkit-box;display:-ms-flexbox}.el-calendar__title{color:#000;-ms-flex-item-align:center;align-self:center}.el-calendar__body{padding:12px 20px 35px}.el-calendar-table{table-layout:fixed;width:100%}.el-calendar-table thead th{padding:12px 0;color:#606266;font-weight:400}.el-calendar-table:not(.is-range) td.next,.el-calendar-table:not(.is-range) td.prev{color:#c0c4cc}.el-backtop,.el-calendar-table td.is-today{color:#409eff}.el-calendar-table td{border-bottom:1px solid #ebeef5;border-right:1px solid #ebeef5;vertical-align:top;-webkit-transition:background-color .2s ease;transition:background-color .2s ease}.el-calendar-table td.is-selected{background-color:#f2f8fe}.el-calendar-table tr:first-child td{border-top:1px solid #ebeef5}.el-calendar-table tr td:first-child{border-left:1px solid #ebeef5}.el-calendar-table tr.el-calendar-table__row--hide-border td{border-top:none}.el-calendar-table .el-calendar-day{-webkit-box-sizing:border-box;box-sizing:border-box;padding:8px;height:85px}.el-calendar-table .el-calendar-day:hover{cursor:pointer;background-color:#f2f8fe}.el-backtop{position:fixed;background-color:#fff;width:40px;height:40px;border-radius:50%;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;font-size:20px;-webkit-box-shadow:0 0 6px rgba(0,0,0,.12);box-shadow:0 0 6px rgba(0,0,0,.12);cursor:pointer;z-index:5}.el-backtop:hover{background-color:#f2f6fc}.el-page-header{line-height:24px}.el-page-header,.el-page-header__left{display:-webkit-box;display:-ms-flexbox;display:flex}.el-page-header__left{cursor:pointer;margin-right:40px;position:relative}.el-page-header__left:after{content:"";position:absolute;width:1px;height:16px;right:-20px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);background-color:#dcdfe6}.el-checkbox,.el-checkbox__input{display:inline-block;position:relative;white-space:nowrap}.el-page-header__left .el-icon-back{font-size:18px;margin-right:6px;-ms-flex-item-align:center;align-self:center}.el-page-header__title{font-size:14px;font-weight:500}.el-page-header__content{font-size:18px;color:#303133}.el-checkbox{color:#606266;font-size:14px;cursor:pointer;user-select:none;margin-right:30px}.el-checkbox,.el-checkbox-button__inner,.el-radio{font-weight:500;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #dcdfe6;-webkit-box-sizing:border-box;box-sizing:border-box;line-height:normal;height:40px}.el-checkbox.is-bordered.is-checked{border-color:#409eff}.el-checkbox.is-bordered.is-disabled{border-color:#ebeef5;cursor:not-allowed}.el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px;height:36px}.el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.el-checkbox.is-bordered.el-checkbox--small{padding:5px 15px 5px 10px;border-radius:3px;height:32px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner:after{height:6px;width:2px}.el-checkbox.is-bordered.el-checkbox--mini{padding:3px 15px 3px 10px;border-radius:3px;height:28px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner:after{height:6px;width:2px}.el-checkbox__input{cursor:pointer;outline:0;line-height:1;vertical-align:middle}.el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#dcdfe6;cursor:not-allowed}.el-checkbox__input.is-disabled .el-checkbox__inner:after{cursor:not-allowed;border-color:#c0c4cc}.el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#f2f6fc;border-color:#dcdfe6}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner:after{border-color:#c0c4cc}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#f2f6fc;border-color:#dcdfe6}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner:before{background-color:#c0c4cc;border-color:#c0c4cc}.el-checkbox__input.is-checked .el-checkbox__inner,.el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#409eff;border-color:#409eff}.el-checkbox__input.is-disabled+span.el-checkbox__label{color:#c0c4cc;cursor:not-allowed}.el-checkbox__input.is-checked .el-checkbox__inner:after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.el-checkbox__input.is-checked+.el-checkbox__label{color:#409eff}.el-checkbox__input.is-focus .el-checkbox__inner{border-color:#409eff}.el-checkbox__input.is-indeterminate .el-checkbox__inner:before{content:"";position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.el-checkbox__input.is-indeterminate .el-checkbox__inner:after{display:none}.el-checkbox__inner{display:inline-block;position:relative;border:1px solid #dcdfe6;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.el-checkbox__inner:hover{border-color:#409eff}.el-checkbox__inner:after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s ease-in .05s;transition:-webkit-transform .15s ease-in .05s;transition:transform .15s ease-in .05s;transition:transform .15s ease-in .05s,-webkit-transform .15s ease-in .05s;-webkit-transform-origin:center;transform-origin:center}.el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;z-index:-1}.el-checkbox-button,.el-checkbox-button__inner{display:inline-block;position:relative}.el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.el-checkbox:last-of-type{margin-right:0}.el-checkbox-button__inner{line-height:1;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #dcdfe6;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-checkbox-button__inner.is-round{padding:12px 20px}.el-checkbox-button__inner:hover{color:#409eff}.el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.el-radio,.el-radio__input{line-height:1;outline:0;white-space:nowrap}.el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;z-index:-1}.el-radio,.el-radio__inner,.el-radio__input{position:relative;display:inline-block}.el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#409eff;border-color:#409eff;-webkit-box-shadow:-1px 0 0 0 #8cc5ff;box-shadow:-1px 0 0 0 #8cc5ff}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner{border-left-color:#409eff}.el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#c0c4cc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#ebeef5;-webkit-box-shadow:none;box-shadow:none}.el-checkbox-button.is-disabled:first-child .el-checkbox-button__inner{border-left-color:#ebeef5}.el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #dcdfe6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#409eff}.el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.el-checkbox-group{font-size:0}.el-radio,.el-radio--medium.is-bordered .el-radio__label{font-size:14px}.el-radio{color:#606266;cursor:pointer;margin-right:30px}.el-cascader-node>.el-radio,.el-radio:last-child{margin-right:0}.el-radio.is-bordered{padding:12px 20px 0 10px;border-radius:4px;border:1px solid #dcdfe6;-webkit-box-sizing:border-box;box-sizing:border-box;height:40px}.el-radio.is-bordered.is-checked{border-color:#409eff}.el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:#ebeef5}.el-radio__input.is-disabled .el-radio__inner,.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:#f5f7fa;border-color:#e4e7ed}.el-radio.is-bordered+.el-radio.is-bordered{margin-left:10px}.el-radio--medium.is-bordered{padding:10px 20px 0 10px;border-radius:4px;height:36px}.el-radio--mini.is-bordered .el-radio__label,.el-radio--small.is-bordered .el-radio__label{font-size:12px}.el-radio--medium.is-bordered .el-radio__inner{height:14px;width:14px}.el-radio--small.is-bordered{padding:8px 15px 0 10px;border-radius:3px;height:32px}.el-radio--small.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio--mini.is-bordered{padding:6px 15px 0 10px;border-radius:3px;height:28px}.el-radio--mini.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio__input{cursor:pointer;vertical-align:middle}.el-radio__input.is-disabled .el-radio__inner{cursor:not-allowed}.el-radio__input.is-disabled .el-radio__inner:after{cursor:not-allowed;background-color:#f5f7fa}.el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.el-radio__input.is-disabled.is-checked .el-radio__inner:after{background-color:#c0c4cc}.el-radio__input.is-disabled+span.el-radio__label{color:#c0c4cc;cursor:not-allowed}.el-radio__input.is-checked .el-radio__inner{border-color:#409eff;background:#409eff}.el-radio__input.is-checked .el-radio__inner:after{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.el-radio__input.is-checked+.el-radio__label{color:#409eff}.el-radio__input.is-focus .el-radio__inner{border-color:#409eff}.el-radio__inner{border:1px solid #dcdfe6;border-radius:100%;width:14px;height:14px;background-color:#fff;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box}.el-radio__inner:hover{border-color:#409eff}.el-radio__inner:after{width:4px;height:4px;border-radius:100%;background-color:#fff;content:"";position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in}.el-radio__original{opacity:0;outline:0;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner{-webkit-box-shadow:0 0 2px 2px #409eff;box-shadow:0 0 2px 2px #409eff}.el-radio__label{font-size:14px;padding-left:10px}.el-scrollbar{overflow:hidden;position:relative}.el-scrollbar:active>.el-scrollbar__bar,.el-scrollbar:focus>.el-scrollbar__bar,.el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity .34s ease-out;transition:opacity .34s ease-out}.el-scrollbar__wrap{overflow:scroll;height:100%}.el-scrollbar__wrap--hidden-default{scrollbar-width:none}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(144,147,153,.3);-webkit-transition:background-color .3s;transition:background-color .3s}.el-scrollbar__thumb:hover{background-color:rgba(144,147,153,.5)}.el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity .12s ease-out;transition:opacity .12s ease-out}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}.el-cascader-panel{display:-webkit-box;display:-ms-flexbox;display:flex;border-radius:4px;font-size:14px}.el-cascader-panel.is-bordered{border:1px solid #e4e7ed;border-radius:4px}.el-cascader-menu{min-width:180px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#606266;border-right:1px solid #e4e7ed}.el-cascader-menu:last-child{border-right:none}.el-cascader-menu:last-child .el-cascader-node{padding-right:20px}.el-cascader-menu__wrap{height:204px}.el-cascader-menu__list{position:relative;min-height:100%;margin:0;padding:6px 0;list-style:none;-webkit-box-sizing:border-box;box-sizing:border-box}.el-avatar,.el-drawer{-webkit-box-sizing:border-box;overflow:hidden}.el-cascader-menu__hover-zone{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.el-cascader-menu__empty-text{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-align:center;color:#c0c4cc}.el-cascader-node{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 30px 0 20px;height:34px;line-height:34px;outline:0}.el-cascader-node.is-selectable.in-active-path{color:#606266}.el-cascader-node.in-active-path,.el-cascader-node.is-active,.el-cascader-node.is-selectable.in-checked-path{color:#409eff;font-weight:700}.el-cascader-node:not(.is-disabled){cursor:pointer}.el-cascader-node:not(.is-disabled):focus,.el-cascader-node:not(.is-disabled):hover{background:#f5f7fa}.el-cascader-node.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-cascader-node__prefix{position:absolute;left:10px}.el-cascader-node__postfix{position:absolute;right:10px}.el-cascader-node__label{-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-cascader-node>.el-radio .el-radio__label{padding-left:0}.el-avatar{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;color:#fff;background:#c0c4cc;width:40px;height:40px;line-height:40px;font-size:14px}.el-avatar>img{display:block;height:100%;vertical-align:middle}.el-drawer,.el-drawer__header{display:-webkit-box;display:-ms-flexbox}.el-avatar--circle{border-radius:50%}.el-avatar--square{border-radius:4px}.el-avatar--icon{font-size:18px}.el-avatar--large{width:40px;height:40px;line-height:40px}.el-avatar--medium{width:36px;height:36px;line-height:36px}.el-avatar--small{width:28px;height:28px;line-height:28px}.el-drawer.btt,.el-drawer.ttb,.el-drawer__container{left:0;right:0;width:100%}.el-drawer.ltr,.el-drawer.rtl,.el-drawer__container{top:0;bottom:0;height:100%}@-webkit-keyframes el-drawer-fade-in{0%{opacity:0}to{opacity:1}}@keyframes el-drawer-fade-in{0%{opacity:0}to{opacity:1}}@-webkit-keyframes rtl-drawer-in{0%{-webkit-transform:translate(100%);transform:translate(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes rtl-drawer-in{0%{-webkit-transform:translate(100%);transform:translate(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes rtl-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translate(100%);transform:translate(100%)}}@keyframes rtl-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translate(100%);transform:translate(100%)}}@-webkit-keyframes ltr-drawer-in{0%{-webkit-transform:translate(-100%);transform:translate(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ltr-drawer-in{0%{-webkit-transform:translate(-100%);transform:translate(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes ltr-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translate(-100%);transform:translate(-100%)}}@keyframes ltr-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translate(-100%);transform:translate(-100%)}}@-webkit-keyframes ttb-drawer-in{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ttb-drawer-in{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes ttb-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(-100%);transform:translateY(-100%)}}@keyframes ttb-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(-100%);transform:translateY(-100%)}}@-webkit-keyframes btt-drawer-in{0%{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes btt-drawer-in{0%{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes btt-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(100%);transform:translateY(100%)}}@keyframes btt-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(100%);transform:translateY(100%)}}.el-drawer{position:absolute;-webkit-box-sizing:border-box;box-sizing:border-box;background-color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;-webkit-box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12);box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12)}.el-drawer.rtl{-webkit-animation:rtl-drawer-out .3s;animation:rtl-drawer-out .3s;right:0}.el-drawer__open .el-drawer.rtl{-webkit-animation:rtl-drawer-in .3s 1ms;animation:rtl-drawer-in .3s 1ms}.el-drawer.ltr{-webkit-animation:ltr-drawer-out .3s;animation:ltr-drawer-out .3s;left:0}.el-drawer__open .el-drawer.ltr{-webkit-animation:ltr-drawer-in .3s 1ms;animation:ltr-drawer-in .3s 1ms}.el-drawer.ttb{-webkit-animation:ttb-drawer-out .3s;animation:ttb-drawer-out .3s;top:0}.el-drawer__open .el-drawer.ttb{-webkit-animation:ttb-drawer-in .3s 1ms;animation:ttb-drawer-in .3s 1ms}.el-drawer.btt{-webkit-animation:btt-drawer-out .3s;animation:btt-drawer-out .3s;bottom:0}.el-drawer__open .el-drawer.btt{-webkit-animation:btt-drawer-in .3s 1ms;animation:btt-drawer-in .3s 1ms}.el-drawer__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:hidden;margin:0}.el-drawer__header{-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#72767b;display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:32px;padding:20px 20px 0}.el-drawer__header>:first-child,.el-drawer__title{-webkit-box-flex:1;-ms-flex:1;flex:1}.el-drawer__title{margin:0;line-height:inherit;font-size:1rem}.el-drawer__close-btn{border:none;cursor:pointer;font-size:20px;color:inherit;background-color:transparent}.el-drawer__body{-webkit-box-flex:1;-ms-flex:1;flex:1}.el-drawer__body>*{-webkit-box-sizing:border-box;box-sizing:border-box}.el-drawer__container{position:relative}.el-drawer-fade-enter-active{-webkit-animation:el-drawer-fade-in .3s;animation:el-drawer-fade-in .3s}.el-drawer-fade-leave-active{animation:el-drawer-fade-in .3s reverse}.el-popconfirm__main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-popconfirm__icon{margin-right:5px}.el-popconfirm__action{text-align:right;margin:0} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-elementUI.f77689d7.css b/priv/static/adminfe/chunk-elementUI.f77689d7.css new file mode 100644 index 000000000..01bdb7fd5 --- /dev/null +++ b/priv/static/adminfe/chunk-elementUI.f77689d7.css @@ -0,0 +1 @@ +.el-pagination--small .arrow.disabled,.el-table--hidden,.el-table .hidden-columns,.el-table td.is-hidden>*,.el-table th.is-hidden>*{visibility:hidden}.el-input__suffix,.el-tree.is-dragging .el-tree-node__content *{pointer-events:none}.el-dropdown .el-dropdown-selfdefine:focus:active,.el-dropdown .el-dropdown-selfdefine:focus:not(.focusing),.el-message__closeBtn:focus,.el-message__content:focus,.el-popover:focus,.el-popover:focus:active,.el-popover__reference:focus:hover,.el-popover__reference:focus:not(.focusing),.el-rate:active,.el-rate:focus,.el-tooltip:focus:hover,.el-tooltip:focus:not(.focusing),.el-upload-list__item.is-success:active,.el-upload-list__item.is-success:not(.focusing):focus{outline-width:0}@font-face{font-family:element-icons;src:url(static/fonts/element-icons.535877f.woff) format("woff"),url(static/fonts/element-icons.732389d.ttf) format("truetype");font-weight:400;font-display:"auto";font-style:normal}[class*=" el-icon-"],[class^=el-icon-]{font-family:element-icons!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;vertical-align:baseline;display:inline-block;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-icon-ice-cream-round:before{content:"\E6A0"}.el-icon-ice-cream-square:before{content:"\E6A3"}.el-icon-lollipop:before{content:"\E6A4"}.el-icon-potato-strips:before{content:"\E6A5"}.el-icon-milk-tea:before{content:"\E6A6"}.el-icon-ice-drink:before{content:"\E6A7"}.el-icon-ice-tea:before{content:"\E6A9"}.el-icon-coffee:before{content:"\E6AA"}.el-icon-orange:before{content:"\E6AB"}.el-icon-pear:before{content:"\E6AC"}.el-icon-apple:before{content:"\E6AD"}.el-icon-cherry:before{content:"\E6AE"}.el-icon-watermelon:before{content:"\E6AF"}.el-icon-grape:before{content:"\E6B0"}.el-icon-refrigerator:before{content:"\E6B1"}.el-icon-goblet-square-full:before{content:"\E6B2"}.el-icon-goblet-square:before{content:"\E6B3"}.el-icon-goblet-full:before{content:"\E6B4"}.el-icon-goblet:before{content:"\E6B5"}.el-icon-cold-drink:before{content:"\E6B6"}.el-icon-coffee-cup:before{content:"\E6B8"}.el-icon-water-cup:before{content:"\E6B9"}.el-icon-hot-water:before{content:"\E6BA"}.el-icon-ice-cream:before{content:"\E6BB"}.el-icon-dessert:before{content:"\E6BC"}.el-icon-sugar:before{content:"\E6BD"}.el-icon-tableware:before{content:"\E6BE"}.el-icon-burger:before{content:"\E6BF"}.el-icon-knife-fork:before{content:"\E6C1"}.el-icon-fork-spoon:before{content:"\E6C2"}.el-icon-chicken:before{content:"\E6C3"}.el-icon-food:before{content:"\E6C4"}.el-icon-dish-1:before{content:"\E6C5"}.el-icon-dish:before{content:"\E6C6"}.el-icon-moon-night:before{content:"\E6EE"}.el-icon-moon:before{content:"\E6F0"}.el-icon-cloudy-and-sunny:before{content:"\E6F1"}.el-icon-partly-cloudy:before{content:"\E6F2"}.el-icon-cloudy:before{content:"\E6F3"}.el-icon-sunny:before{content:"\E6F6"}.el-icon-sunset:before{content:"\E6F7"}.el-icon-sunrise-1:before{content:"\E6F8"}.el-icon-sunrise:before{content:"\E6F9"}.el-icon-heavy-rain:before{content:"\E6FA"}.el-icon-lightning:before{content:"\E6FB"}.el-icon-light-rain:before{content:"\E6FC"}.el-icon-wind-power:before{content:"\E6FD"}.el-icon-baseball:before{content:"\E712"}.el-icon-soccer:before{content:"\E713"}.el-icon-football:before{content:"\E715"}.el-icon-basketball:before{content:"\E716"}.el-icon-ship:before{content:"\E73F"}.el-icon-truck:before{content:"\E740"}.el-icon-bicycle:before{content:"\E741"}.el-icon-mobile-phone:before{content:"\E6D3"}.el-icon-service:before{content:"\E6D4"}.el-icon-key:before{content:"\E6E2"}.el-icon-unlock:before{content:"\E6E4"}.el-icon-lock:before{content:"\E6E5"}.el-icon-watch:before{content:"\E6FE"}.el-icon-watch-1:before{content:"\E6FF"}.el-icon-timer:before{content:"\E702"}.el-icon-alarm-clock:before{content:"\E703"}.el-icon-map-location:before{content:"\E704"}.el-icon-delete-location:before{content:"\E705"}.el-icon-add-location:before{content:"\E706"}.el-icon-location-information:before{content:"\E707"}.el-icon-location-outline:before{content:"\E708"}.el-icon-location:before{content:"\E79E"}.el-icon-place:before{content:"\E709"}.el-icon-discover:before{content:"\E70A"}.el-icon-first-aid-kit:before{content:"\E70B"}.el-icon-trophy-1:before{content:"\E70C"}.el-icon-trophy:before{content:"\E70D"}.el-icon-medal:before{content:"\E70E"}.el-icon-medal-1:before{content:"\E70F"}.el-icon-stopwatch:before{content:"\E710"}.el-icon-mic:before{content:"\E711"}.el-icon-copy-document:before{content:"\E718"}.el-icon-full-screen:before{content:"\E719"}.el-icon-switch-button:before{content:"\E71B"}.el-icon-aim:before{content:"\E71C"}.el-icon-crop:before{content:"\E71D"}.el-icon-odometer:before{content:"\E71E"}.el-icon-time:before{content:"\E71F"}.el-icon-bangzhu:before{content:"\E724"}.el-icon-close-notification:before{content:"\E726"}.el-icon-microphone:before{content:"\E727"}.el-icon-turn-off-microphone:before{content:"\E728"}.el-icon-position:before{content:"\E729"}.el-icon-postcard:before{content:"\E72A"}.el-icon-message:before{content:"\E72B"}.el-icon-chat-line-square:before{content:"\E72D"}.el-icon-chat-dot-square:before{content:"\E72E"}.el-icon-chat-dot-round:before{content:"\E72F"}.el-icon-chat-square:before{content:"\E730"}.el-icon-chat-line-round:before{content:"\E731"}.el-icon-chat-round:before{content:"\E732"}.el-icon-set-up:before{content:"\E733"}.el-icon-turn-off:before{content:"\E734"}.el-icon-open:before{content:"\E735"}.el-icon-connection:before{content:"\E736"}.el-icon-link:before{content:"\E737"}.el-icon-cpu:before{content:"\E738"}.el-icon-thumb:before{content:"\E739"}.el-icon-female:before{content:"\E73A"}.el-icon-male:before{content:"\E73B"}.el-icon-guide:before{content:"\E73C"}.el-icon-news:before{content:"\E73E"}.el-icon-price-tag:before{content:"\E744"}.el-icon-discount:before{content:"\E745"}.el-icon-wallet:before{content:"\E747"}.el-icon-coin:before{content:"\E748"}.el-icon-money:before{content:"\E749"}.el-icon-bank-card:before{content:"\E74A"}.el-icon-box:before{content:"\E74B"}.el-icon-present:before{content:"\E74C"}.el-icon-sell:before{content:"\E6D5"}.el-icon-sold-out:before{content:"\E6D6"}.el-icon-shopping-bag-2:before{content:"\E74D"}.el-icon-shopping-bag-1:before{content:"\E74E"}.el-icon-shopping-cart-2:before{content:"\E74F"}.el-icon-shopping-cart-1:before{content:"\E750"}.el-icon-shopping-cart-full:before{content:"\E751"}.el-icon-smoking:before{content:"\E752"}.el-icon-no-smoking:before{content:"\E753"}.el-icon-house:before{content:"\E754"}.el-icon-table-lamp:before{content:"\E755"}.el-icon-school:before{content:"\E756"}.el-icon-office-building:before{content:"\E757"}.el-icon-toilet-paper:before{content:"\E758"}.el-icon-notebook-2:before{content:"\E759"}.el-icon-notebook-1:before{content:"\E75A"}.el-icon-files:before{content:"\E75B"}.el-icon-collection:before{content:"\E75C"}.el-icon-receiving:before{content:"\E75D"}.el-icon-suitcase-1:before{content:"\E760"}.el-icon-suitcase:before{content:"\E761"}.el-icon-film:before{content:"\E763"}.el-icon-collection-tag:before{content:"\E765"}.el-icon-data-analysis:before{content:"\E766"}.el-icon-pie-chart:before{content:"\E767"}.el-icon-data-board:before{content:"\E768"}.el-icon-data-line:before{content:"\E76D"}.el-icon-reading:before{content:"\E769"}.el-icon-magic-stick:before{content:"\E76A"}.el-icon-coordinate:before{content:"\E76B"}.el-icon-mouse:before{content:"\E76C"}.el-icon-brush:before{content:"\E76E"}.el-icon-headset:before{content:"\E76F"}.el-icon-umbrella:before{content:"\E770"}.el-icon-scissors:before{content:"\E771"}.el-icon-mobile:before{content:"\E773"}.el-icon-attract:before{content:"\E774"}.el-icon-monitor:before{content:"\E775"}.el-icon-search:before{content:"\E778"}.el-icon-takeaway-box:before{content:"\E77A"}.el-icon-paperclip:before{content:"\E77D"}.el-icon-printer:before{content:"\E77E"}.el-icon-document-add:before{content:"\E782"}.el-icon-document:before{content:"\E785"}.el-icon-document-checked:before{content:"\E786"}.el-icon-document-copy:before{content:"\E787"}.el-icon-document-delete:before{content:"\E788"}.el-icon-document-remove:before{content:"\E789"}.el-icon-tickets:before{content:"\E78B"}.el-icon-folder-checked:before{content:"\E77F"}.el-icon-folder-delete:before{content:"\E780"}.el-icon-folder-remove:before{content:"\E781"}.el-icon-folder-add:before{content:"\E783"}.el-icon-folder-opened:before{content:"\E784"}.el-icon-folder:before{content:"\E78A"}.el-icon-edit-outline:before{content:"\E764"}.el-icon-edit:before{content:"\E78C"}.el-icon-date:before{content:"\E78E"}.el-icon-c-scale-to-original:before{content:"\E7C6"}.el-icon-view:before{content:"\E6CE"}.el-icon-loading:before{content:"\E6CF"}.el-icon-rank:before{content:"\E6D1"}.el-icon-sort-down:before{content:"\E7C4"}.el-icon-sort-up:before{content:"\E7C5"}.el-icon-sort:before{content:"\E6D2"}.el-icon-finished:before{content:"\E6CD"}.el-icon-refresh-left:before{content:"\E6C7"}.el-icon-refresh-right:before{content:"\E6C8"}.el-icon-refresh:before{content:"\E6D0"}.el-icon-video-play:before{content:"\E7C0"}.el-icon-video-pause:before{content:"\E7C1"}.el-icon-d-arrow-right:before{content:"\E6DC"}.el-icon-d-arrow-left:before{content:"\E6DD"}.el-icon-arrow-up:before{content:"\E6E1"}.el-icon-arrow-down:before{content:"\E6DF"}.el-icon-arrow-right:before{content:"\E6E0"}.el-icon-arrow-left:before{content:"\E6DE"}.el-icon-top-right:before{content:"\E6E7"}.el-icon-top-left:before{content:"\E6E8"}.el-icon-top:before{content:"\E6E6"}.el-icon-bottom:before{content:"\E6EB"}.el-icon-right:before{content:"\E6E9"}.el-icon-back:before{content:"\E6EA"}.el-icon-bottom-right:before{content:"\E6EC"}.el-icon-bottom-left:before{content:"\E6ED"}.el-icon-caret-top:before{content:"\E78F"}.el-icon-caret-bottom:before{content:"\E790"}.el-icon-caret-right:before{content:"\E791"}.el-icon-caret-left:before{content:"\E792"}.el-icon-d-caret:before{content:"\E79A"}.el-icon-share:before{content:"\E793"}.el-icon-menu:before{content:"\E798"}.el-icon-s-grid:before{content:"\E7A6"}.el-icon-s-check:before{content:"\E7A7"}.el-icon-s-data:before{content:"\E7A8"}.el-icon-s-opportunity:before{content:"\E7AA"}.el-icon-s-custom:before{content:"\E7AB"}.el-icon-s-claim:before{content:"\E7AD"}.el-icon-s-finance:before{content:"\E7AE"}.el-icon-s-comment:before{content:"\E7AF"}.el-icon-s-flag:before{content:"\E7B0"}.el-icon-s-marketing:before{content:"\E7B1"}.el-icon-s-shop:before{content:"\E7B4"}.el-icon-s-open:before{content:"\E7B5"}.el-icon-s-management:before{content:"\E7B6"}.el-icon-s-ticket:before{content:"\E7B7"}.el-icon-s-release:before{content:"\E7B8"}.el-icon-s-home:before{content:"\E7B9"}.el-icon-s-promotion:before{content:"\E7BA"}.el-icon-s-operation:before{content:"\E7BB"}.el-icon-s-unfold:before{content:"\E7BC"}.el-icon-s-fold:before{content:"\E7A9"}.el-icon-s-platform:before{content:"\E7BD"}.el-icon-s-order:before{content:"\E7BE"}.el-icon-s-cooperation:before{content:"\E7BF"}.el-icon-bell:before{content:"\E725"}.el-icon-message-solid:before{content:"\E799"}.el-icon-video-camera:before{content:"\E772"}.el-icon-video-camera-solid:before{content:"\E796"}.el-icon-camera:before{content:"\E779"}.el-icon-camera-solid:before{content:"\E79B"}.el-icon-download:before{content:"\E77C"}.el-icon-upload2:before{content:"\E77B"}.el-icon-upload:before{content:"\E7C3"}.el-icon-picture-outline-round:before{content:"\E75F"}.el-icon-picture-outline:before{content:"\E75E"}.el-icon-picture:before{content:"\E79F"}.el-icon-close:before{content:"\E6DB"}.el-icon-check:before{content:"\E6DA"}.el-icon-plus:before{content:"\E6D9"}.el-icon-minus:before{content:"\E6D8"}.el-icon-help:before{content:"\E73D"}.el-icon-s-help:before{content:"\E7B3"}.el-icon-circle-close:before{content:"\E78D"}.el-icon-circle-check:before{content:"\E720"}.el-icon-circle-plus-outline:before{content:"\E723"}.el-icon-remove-outline:before{content:"\E722"}.el-icon-zoom-out:before{content:"\E776"}.el-icon-zoom-in:before{content:"\E777"}.el-icon-error:before{content:"\E79D"}.el-icon-success:before{content:"\E79C"}.el-icon-circle-plus:before{content:"\E7A0"}.el-icon-remove:before{content:"\E7A2"}.el-icon-info:before{content:"\E7A1"}.el-icon-question:before{content:"\E7A4"}.el-icon-warning-outline:before{content:"\E6C9"}.el-icon-warning:before{content:"\E7A3"}.el-icon-goods:before{content:"\E7C2"}.el-icon-s-goods:before{content:"\E7B2"}.el-icon-star-off:before{content:"\E717"}.el-icon-star-on:before{content:"\E797"}.el-icon-more-outline:before{content:"\E6CC"}.el-icon-more:before{content:"\E794"}.el-icon-phone-outline:before{content:"\E6CB"}.el-icon-phone:before{content:"\E795"}.el-icon-user:before{content:"\E6E3"}.el-icon-user-solid:before{content:"\E7A5"}.el-icon-setting:before{content:"\E6CA"}.el-icon-s-tools:before{content:"\E7AC"}.el-icon-delete:before{content:"\E6D7"}.el-icon-delete-solid:before{content:"\E7C9"}.el-icon-eleme:before{content:"\E7C7"}.el-icon-platform-eleme:before{content:"\E7CA"}.el-icon-loading{-webkit-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotating{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.el-pagination{white-space:nowrap;padding:2px 5px;color:#303133;font-weight:700}.el-pagination:after,.el-pagination:before{display:table;content:""}.el-pagination:after{clear:both}.el-pagination button,.el-pagination span:not([class*=suffix]){display:inline-block;font-size:13px;min-width:35.5px;height:28px;line-height:28px;vertical-align:top;-webkit-box-sizing:border-box;box-sizing:border-box}.el-pagination .el-input__inner{text-align:center;-moz-appearance:textfield;line-height:normal}.el-pagination .el-input__suffix{right:0;-webkit-transform:scale(.8);transform:scale(.8)}.el-pagination .el-select .el-input{width:100px;margin:0 5px}.el-pagination .el-select .el-input .el-input__inner{padding-right:25px;border-radius:3px}.el-pagination button{border:none;padding:0 6px;background:0 0}.el-pagination button:focus{outline:0}.el-pagination button:hover{color:#409eff}.el-pagination button:disabled{color:#c0c4cc;background-color:#fff;cursor:not-allowed}.el-pagination .btn-next,.el-pagination .btn-prev{background:50% no-repeat #fff;background-size:16px;cursor:pointer;margin:0;color:#303133}.el-pagination .btn-next .el-icon,.el-pagination .btn-prev .el-icon{display:block;font-size:12px;font-weight:700}.el-pagination .btn-prev{padding-right:12px}.el-pagination .btn-next{padding-left:12px}.el-pagination .el-pager li.disabled{color:#c0c4cc;cursor:not-allowed}.el-pager li,.el-pager li.btn-quicknext:hover,.el-pager li.btn-quickprev:hover{cursor:pointer}.el-pagination--small .btn-next,.el-pagination--small .btn-prev,.el-pagination--small .el-pager li,.el-pagination--small .el-pager li.btn-quicknext,.el-pagination--small .el-pager li.btn-quickprev,.el-pagination--small .el-pager li:last-child{border-color:transparent;font-size:12px;line-height:22px;height:22px;min-width:22px}.el-pagination--small .more:before,.el-pagination--small li.more:before{line-height:24px}.el-pagination--small button,.el-pagination--small span:not([class*=suffix]){height:22px;line-height:22px}.el-pagination--small .el-pagination__editor,.el-pagination--small .el-pagination__editor.el-input .el-input__inner{height:22px}.el-pagination__sizes{margin:0 10px 0 0;font-weight:400;color:#606266}.el-pagination__sizes .el-input .el-input__inner{font-size:13px;padding-left:8px}.el-pagination__sizes .el-input .el-input__inner:hover{border-color:#409eff}.el-pagination__total{margin-right:10px;font-weight:400;color:#606266}.el-pagination__jump{margin-left:24px;font-weight:400;color:#606266}.el-pagination__jump .el-input__inner{padding:0 3px}.el-pagination__rightwrapper{float:right}.el-pagination__editor{line-height:18px;padding:0 2px;height:28px;text-align:center;margin:0 2px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:3px}.el-pager,.el-pagination.is-background .btn-next,.el-pagination.is-background .btn-prev{padding:0}.el-pagination__editor.el-input{width:50px}.el-pagination__editor.el-input .el-input__inner{height:28px}.el-pagination__editor .el-input__inner::-webkit-inner-spin-button,.el-pagination__editor .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.el-pagination.is-background .btn-next,.el-pagination.is-background .btn-prev,.el-pagination.is-background .el-pager li{margin:0 5px;background-color:#f4f4f5;color:#606266;min-width:30px;border-radius:2px}.el-pagination.is-background .btn-next.disabled,.el-pagination.is-background .btn-next:disabled,.el-pagination.is-background .btn-prev.disabled,.el-pagination.is-background .btn-prev:disabled,.el-pagination.is-background .el-pager li.disabled{color:#c0c4cc}.el-pagination.is-background .el-pager li:not(.disabled):hover{color:#409eff}.el-pagination.is-background .el-pager li:not(.disabled).active{background-color:#409eff;color:#fff}.el-dialog,.el-pager li{background:#fff;-webkit-box-sizing:border-box}.el-pagination.is-background.el-pagination--small .btn-next,.el-pagination.is-background.el-pagination--small .btn-prev,.el-pagination.is-background.el-pagination--small .el-pager li{margin:0 3px;min-width:22px}.el-pager,.el-pager li{vertical-align:top;margin:0;display:inline-block}.el-pager{-ms-user-select:none;user-select:none;list-style:none;font-size:0}.el-date-table,.el-pager,.el-table th{-webkit-user-select:none;-moz-user-select:none}.el-pager .more:before{line-height:30px}.el-pager li{padding:0 4px;font-size:13px;min-width:35.5px;height:28px;line-height:28px;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center}.el-menu--collapse .el-menu .el-submenu,.el-menu--popup{min-width:200px}.el-pager li.btn-quicknext,.el-pager li.btn-quickprev{line-height:28px;color:#303133}.el-pager li.btn-quicknext.disabled,.el-pager li.btn-quickprev.disabled{color:#c0c4cc}.el-pager li.active+li{border-left:0}.el-pager li:hover{color:#409eff}.el-pager li.active{color:#409eff;cursor:default}@-webkit-keyframes v-modal-in{0%{opacity:0}}@-webkit-keyframes v-modal-out{to{opacity:0}}.el-dialog{position:relative;margin:0 auto 50px;border-radius:2px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.3);box-shadow:0 1px 3px rgba(0,0,0,.3);-webkit-box-sizing:border-box;box-sizing:border-box;width:50%}.el-dialog.is-fullscreen{width:100%;margin-top:0;margin-bottom:0;height:100%;overflow:auto}.el-dialog__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:auto;margin:0}.el-dialog__header{padding:20px 20px 10px}.el-dialog__headerbtn{position:absolute;top:20px;right:20px;padding:0;background:0 0;border:none;outline:0;cursor:pointer;font-size:16px}.el-dialog__headerbtn .el-dialog__close{color:#909399}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:#409eff}.el-dialog__title{line-height:24px;font-size:18px;color:#303133}.el-dialog__body{padding:30px 20px;color:#606266;font-size:14px;word-break:break-all}.el-dialog__footer{padding:10px 20px 20px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial;padding:25px 25px 30px}.el-dialog--center .el-dialog__footer{text-align:inherit}.dialog-fade-enter-active{-webkit-animation:dialog-fade-in .3s;animation:dialog-fade-in .3s}.dialog-fade-leave-active{-webkit-animation:dialog-fade-out .3s;animation:dialog-fade-out .3s}@-webkit-keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@keyframes dialog-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@-webkit-keyframes dialog-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes dialog-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-autocomplete{position:relative;display:inline-block}.el-autocomplete-suggestion{margin:5px 0;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:4px;border:1px solid #e4e7ed;-webkit-box-sizing:border-box;box-sizing:border-box;background-color:#fff}.el-dropdown-menu,.el-menu--collapse .el-submenu .el-menu{z-index:10;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-autocomplete-suggestion__wrap{max-height:280px;padding:10px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-autocomplete-suggestion__list{margin:0;padding:0}.el-autocomplete-suggestion li{padding:0 20px;margin:0;line-height:34px;cursor:pointer;color:#606266;font-size:14px;list-style:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-autocomplete-suggestion li.highlighted,.el-autocomplete-suggestion li:hover{background-color:#f5f7fa}.el-autocomplete-suggestion li.divider{margin-top:6px;border-top:1px solid #000}.el-autocomplete-suggestion li.divider:last-child{margin-bottom:-6px}.el-autocomplete-suggestion.is-loading li{text-align:center;height:100px;line-height:100px;font-size:20px;color:#999}.el-autocomplete-suggestion.is-loading li:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-autocomplete-suggestion.is-loading li:hover{background-color:#fff}.el-autocomplete-suggestion.is-loading .el-icon-loading{vertical-align:middle}.el-dropdown{display:inline-block;position:relative;color:#606266;font-size:14px}.el-dropdown .el-button-group{display:block}.el-dropdown .el-button-group .el-button{float:none}.el-dropdown .el-dropdown__caret-button{padding-left:5px;padding-right:5px;position:relative;border-left:none}.el-dropdown .el-dropdown__caret-button:before{content:"";position:absolute;display:block;width:1px;top:5px;bottom:5px;left:0;background:hsla(0,0%,100%,.5)}.el-dropdown .el-dropdown__caret-button.el-button--default:before{background:rgba(220,223,230,.5)}.el-dropdown .el-dropdown__caret-button:hover:before{top:0;bottom:0}.el-dropdown .el-dropdown__caret-button .el-dropdown__icon{padding-left:0}.el-dropdown__icon{font-size:12px;margin:0 3px}.el-dropdown-menu{position:absolute;top:0;left:0;padding:10px 0;margin:5px 0;background-color:#fff;border:1px solid #ebeef5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-dropdown-menu__item{list-style:none;line-height:36px;padding:0 20px;margin:0;font-size:14px;color:#606266;cursor:pointer;outline:0}.el-dropdown-menu__item:focus,.el-dropdown-menu__item:not(.is-disabled):hover{background-color:#ecf5ff;color:#66b1ff}.el-dropdown-menu__item i{margin-right:5px}.el-dropdown-menu__item--divided{position:relative;margin-top:6px;border-top:1px solid #ebeef5}.el-dropdown-menu__item--divided:before{content:"";height:6px;display:block;margin:0 -20px;background-color:#fff}.el-dropdown-menu__item.is-disabled{cursor:default;color:#bbb;pointer-events:none}.el-dropdown-menu--medium{padding:6px 0}.el-dropdown-menu--medium .el-dropdown-menu__item{line-height:30px;padding:0 17px;font-size:14px}.el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:6px}.el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:6px;margin:0 -17px}.el-dropdown-menu--small{padding:6px 0}.el-dropdown-menu--small .el-dropdown-menu__item{line-height:27px;padding:0 15px;font-size:13px}.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:4px}.el-dropdown-menu--small .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:4px;margin:0 -15px}.el-dropdown-menu--mini{padding:3px 0}.el-dropdown-menu--mini .el-dropdown-menu__item{line-height:24px;padding:0 10px;font-size:12px}.el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided{margin-top:3px}.el-dropdown-menu--mini .el-dropdown-menu__item.el-dropdown-menu__item--divided:before{height:3px;margin:0 -10px}.el-menu{border-right:1px solid #e6e6e6;list-style:none;position:relative;margin:0;padding-left:0}.el-menu,.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus,.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover,.el-menu--horizontal>.el-submenu .el-submenu__title:hover{background-color:#fff}.el-menu:after,.el-menu:before{display:table;content:""}.el-menu:after{clear:both}.el-menu.el-menu--horizontal{border-bottom:1px solid #e6e6e6}.el-menu--horizontal{border-right:none}.el-menu--horizontal>.el-menu-item{float:left;height:60px;line-height:60px;margin:0;border-bottom:2px solid transparent;color:#909399}.el-menu--horizontal>.el-menu-item a,.el-menu--horizontal>.el-menu-item a:hover{color:inherit}.el-menu--horizontal>.el-submenu{float:left}.el-menu--horizontal>.el-submenu:focus,.el-menu--horizontal>.el-submenu:hover{outline:0}.el-menu--horizontal>.el-submenu:focus .el-submenu__title,.el-menu--horizontal>.el-submenu:hover .el-submenu__title{color:#303133}.el-menu--horizontal>.el-submenu.is-active .el-submenu__title{border-bottom:2px solid #409eff;color:#303133}.el-menu--horizontal>.el-submenu .el-submenu__title{height:60px;line-height:60px;border-bottom:2px solid transparent;color:#909399}.el-menu--horizontal>.el-submenu .el-submenu__icon-arrow{position:static;vertical-align:middle;margin-left:8px;margin-top:-3px}.el-menu--horizontal .el-menu .el-menu-item,.el-menu--horizontal .el-menu .el-submenu__title{background-color:#fff;float:none;height:36px;line-height:36px;padding:0 10px;color:#909399}.el-menu--horizontal .el-menu .el-menu-item.is-active,.el-menu--horizontal .el-menu .el-submenu.is-active>.el-submenu__title{color:#303133}.el-menu--horizontal .el-menu-item:not(.is-disabled):focus,.el-menu--horizontal .el-menu-item:not(.is-disabled):hover{outline:0;color:#303133}.el-menu--horizontal>.el-menu-item.is-active{border-bottom:2px solid #409eff;color:#303133}.el-menu--collapse{width:64px}.el-menu--collapse>.el-menu-item [class^=el-icon-],.el-menu--collapse>.el-submenu>.el-submenu__title [class^=el-icon-]{margin:0;vertical-align:middle;width:24px;text-align:center}.el-menu--collapse>.el-menu-item .el-submenu__icon-arrow,.el-menu--collapse>.el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}.el-menu--collapse>.el-menu-item span,.el-menu--collapse>.el-submenu>.el-submenu__title span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}.el-menu--collapse>.el-menu-item.is-active i{color:inherit}.el-menu--collapse .el-submenu{position:relative}.el-menu--collapse .el-submenu .el-menu{position:absolute;margin-left:5px;top:0;left:100%;border:1px solid #e4e7ed;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-menu-item,.el-submenu__title{height:56px;line-height:56px;position:relative;-webkit-box-sizing:border-box;white-space:nowrap;list-style:none}.el-menu--collapse .el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:none;transform:none}.el-menu--popup{z-index:100;border:none;padding:5px 0;border-radius:2px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-menu--popup-bottom-start{margin-top:5px}.el-menu--popup-right-start{margin-left:5px;margin-right:5px}.el-menu-item{font-size:14px;color:#303133;padding:0 20px;cursor:pointer;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box}.el-menu-item *{vertical-align:middle}.el-menu-item i{color:#909399}.el-menu-item:focus,.el-menu-item:hover{outline:0;background-color:#ecf5ff}.el-menu-item.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-menu-item [class^=el-icon-]{margin-right:5px;width:24px;text-align:center;font-size:18px;vertical-align:middle}.el-menu-item.is-active{color:#409eff}.el-menu-item.is-active i{color:inherit}.el-submenu{list-style:none;margin:0;padding-left:0}.el-submenu__title{font-size:14px;color:#303133;padding:0 20px;cursor:pointer;-webkit-transition:border-color .3s,background-color .3s,color .3s;transition:border-color .3s,background-color .3s,color .3s;-webkit-box-sizing:border-box;box-sizing:border-box}.el-submenu__title *{vertical-align:middle}.el-submenu__title i{color:#909399}.el-submenu__title:focus,.el-submenu__title:hover{outline:0;background-color:#ecf5ff}.el-submenu__title.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-submenu__title:hover{background-color:#ecf5ff}.el-submenu .el-menu{border:none}.el-submenu .el-menu-item{height:50px;line-height:50px;padding:0 45px;min-width:200px}.el-submenu__icon-arrow{position:absolute;top:50%;right:20px;margin-top:-7px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:12px}.el-submenu.is-active .el-submenu__title{border-bottom-color:#409eff}.el-submenu.is-opened>.el-submenu__title .el-submenu__icon-arrow{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.el-submenu.is-disabled .el-menu-item,.el-submenu.is-disabled .el-submenu__title{opacity:.25;cursor:not-allowed;background:0 0!important}.el-submenu [class^=el-icon-]{vertical-align:middle;margin-right:5px;width:24px;text-align:center;font-size:18px}.el-menu-item-group>ul{padding:0}.el-menu-item-group__title{padding:7px 0 7px 20px;line-height:normal;font-size:12px;color:#909399}.el-radio-button__inner,.el-radio-group{display:inline-block;line-height:1;vertical-align:middle}.horizontal-collapse-transition .el-submenu__title .el-submenu__icon-arrow{-webkit-transition:.2s;transition:.2s;opacity:0}.el-radio-group{font-size:0}.el-radio-button{position:relative;display:inline-block;outline:0}.el-radio-button__inner{white-space:nowrap;background:#fff;border:1px solid #dcdfe6;font-weight:500;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;position:relative;cursor:pointer;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-radio-button__inner.is-round{padding:12px 20px}.el-radio-button__inner:hover{color:#409eff}.el-radio-button__inner [class*=el-icon-]{line-height:.9}.el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.el-radio-button:first-child .el-radio-button__inner{border-left:1px solid #dcdfe6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-radio-button__orig-radio{opacity:0;outline:0;position:absolute;z-index:-1}.el-radio-button__orig-radio:checked+.el-radio-button__inner{color:#fff;background-color:#409eff;border-color:#409eff;-webkit-box-shadow:-1px 0 0 0 #409eff;box-shadow:-1px 0 0 0 #409eff}.el-radio-button__orig-radio:disabled+.el-radio-button__inner{color:#c0c4cc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#ebeef5;-webkit-box-shadow:none;box-shadow:none}.el-radio-button__orig-radio:disabled:checked+.el-radio-button__inner{background-color:#f2f6fc}.el-radio-button:last-child .el-radio-button__inner{border-radius:0 4px 4px 0}.el-popover,.el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:4px}.el-radio-button--medium .el-radio-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-radio-button--medium .el-radio-button__inner.is-round{padding:10px 20px}.el-radio-button--small .el-radio-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-radio-button--small .el-radio-button__inner.is-round{padding:9px 15px}.el-radio-button--mini .el-radio-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-radio-button--mini .el-radio-button__inner.is-round{padding:7px 15px}.el-radio-button:focus:not(.is-focus):not(:active):not(.is-disabled){-webkit-box-shadow:0 0 2px 2px #409eff;box-shadow:0 0 2px 2px #409eff}.el-switch{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;font-size:14px;line-height:20px;height:20px;vertical-align:middle}.el-switch__core,.el-switch__label{display:inline-block;cursor:pointer}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__label{-webkit-transition:.2s;transition:.2s;height:20px;font-size:14px;font-weight:500;vertical-align:middle;color:#303133}.el-switch__label.is-active{color:#409eff}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{line-height:1;font-size:14px;display:inline-block}.el-switch__input{position:absolute;width:0;height:0;opacity:0;margin:0}.el-switch__core{margin:0;position:relative;width:40px;height:20px;border:1px solid #dcdfe6;outline:0;border-radius:10px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#dcdfe6;-webkit-transition:border-color .3s,background-color .3s;transition:border-color .3s,background-color .3s;vertical-align:middle}.el-switch__core:after{content:"";position:absolute;top:1px;left:1px;border-radius:100%;-webkit-transition:all .3s;transition:all .3s;width:16px;height:16px;background-color:#fff}.el-switch.is-checked .el-switch__core{border-color:#409eff;background-color:#409eff}.el-switch.is-checked .el-switch__core:after{left:100%;margin-left:-17px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter,.el-switch .label-fade-leave-active{opacity:0}.el-select-dropdown{position:absolute;z-index:1001;border:1px solid #e4e7ed;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:5px 0}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected{color:#409eff;background-color:#fff}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover{background-color:#f5f7fa}.el-select-dropdown.is-multiple .el-select-dropdown__item.selected:after{position:absolute;right:20px;font-family:element-icons;content:"\E6DA";font-size:12px;font-weight:700;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown__empty{padding:10px 0;margin:0;text-align:center;color:#999;font-size:14px}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{list-style:none;padding:6px 0;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-select-dropdown__item{font-size:14px;padding:0 20px;position:relative;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#606266;height:34px;line-height:34px;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.el-select-dropdown__item.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-select-dropdown__item.is-disabled:hover{background-color:#fff}.el-select-dropdown__item.hover,.el-select-dropdown__item:hover{background-color:#f5f7fa}.el-select-dropdown__item.selected{color:#409eff;font-weight:700}.el-select-group{margin:0;padding:0}.el-select-group__wrap{position:relative;list-style:none;margin:0;padding:0}.el-select-group__wrap:not(:last-of-type){padding-bottom:24px}.el-select-group__wrap:not(:last-of-type):after{content:"";position:absolute;display:block;left:20px;right:20px;bottom:12px;height:1px;background:#e4e7ed}.el-select-group__title{padding-left:20px;font-size:12px;color:#909399;line-height:30px}.el-select-group .el-select-dropdown__item{padding-left:20px}.el-select{display:inline-block;position:relative}.el-select .el-select__tags>span{display:contents}.el-select:hover .el-input__inner{border-color:#c0c4cc}.el-select .el-input__inner{cursor:pointer;padding-right:35px}.el-select .el-input__inner:focus{border-color:#409eff}.el-select .el-input .el-select__caret{color:#c0c4cc;font-size:14px;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:rotate(180deg);transform:rotate(180deg);cursor:pointer}.el-select .el-input .el-select__caret.is-reverse{-webkit-transform:rotate(0);transform:rotate(0)}.el-select .el-input .el-select__caret.is-show-close{font-size:14px;text-align:center;-webkit-transform:rotate(180deg);transform:rotate(180deg);border-radius:100%;color:#c0c4cc;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.el-select .el-input .el-select__caret.is-show-close:hover{color:#909399}.el-select .el-input.is-disabled .el-input__inner{cursor:not-allowed}.el-select .el-input.is-disabled .el-input__inner:hover{border-color:#e4e7ed}.el-select .el-input.is-focus .el-input__inner{border-color:#409eff}.el-select>.el-input{display:block}.el-select__input{border:none;outline:0;padding:0;margin-left:15px;color:#666;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:28px;background-color:transparent}.el-select__input.is-mini{height:14px}.el-select__close{cursor:pointer;position:absolute;top:8px;z-index:1000;right:25px;color:#c0c4cc;line-height:18px;font-size:14px}.el-select__close:hover{color:#909399}.el-select__tags{position:absolute;line-height:normal;white-space:normal;z-index:1;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-select .el-tag__close{margin-top:-2px}.el-select .el-tag{-webkit-box-sizing:border-box;box-sizing:border-box;border-color:transparent;margin:2px 0 2px 6px;background-color:#f0f2f5}.el-select .el-tag__close.el-icon-close{background-color:#c0c4cc;right:-7px;top:0;color:#fff}.el-select .el-tag__close.el-icon-close:hover{background-color:#909399}.el-table,.el-table__expanded-cell{background-color:#fff}.el-select .el-tag__close.el-icon-close:before{display:block;-webkit-transform:translateY(.5px);transform:translateY(.5px)}.el-table{position:relative;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-box-flex:1;-ms-flex:1;flex:1;width:100%;max-width:100%;font-size:14px;color:#606266}.el-table--mini,.el-table--small,.el-table__expand-icon{font-size:12px}.el-table__empty-block{min-height:60px;text-align:center;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-table__empty-text{line-height:60px;width:50%;color:#909399}.el-table__expand-column .cell{padding:0;text-align:center}.el-table__expand-icon{position:relative;cursor:pointer;color:#666;-webkit-transition:-webkit-transform .2s ease-in-out;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out,-webkit-transform .2s ease-in-out;height:20px}.el-table__expand-icon--expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-table__expand-icon>.el-icon{position:absolute;left:50%;top:50%;margin-left:-5px;margin-top:-5px}.el-table__expanded-cell[class*=cell]{padding:20px 50px}.el-table__expanded-cell:hover{background-color:transparent!important}.el-table__placeholder{display:inline-block;width:20px}.el-table__append-wrapper{overflow:hidden}.el-table--fit{border-right:0;border-bottom:0}.el-table--fit td.gutter,.el-table--fit th.gutter{border-right-width:1px}.el-table--scrollable-x .el-table__body-wrapper{overflow-x:auto}.el-table--scrollable-y .el-table__body-wrapper{overflow-y:auto}.el-table thead{color:#909399;font-weight:500}.el-table thead.is-group th{background:#f5f7fa}.el-table th,.el-table tr{background-color:#fff}.el-table td,.el-table th{padding:12px 0;min-width:0;-webkit-box-sizing:border-box;box-sizing:border-box;text-overflow:ellipsis;vertical-align:middle;position:relative;text-align:left}.el-table td.is-center,.el-table th.is-center{text-align:center}.el-table td.is-right,.el-table th.is-right{text-align:right}.el-table td.gutter,.el-table th.gutter{width:15px;border-right-width:0;border-bottom-width:0;padding:0}.el-table--medium td,.el-table--medium th{padding:10px 0}.el-table--small td,.el-table--small th{padding:8px 0}.el-table--mini td,.el-table--mini th{padding:6px 0}.el-table--border td:first-child .cell,.el-table--border th:first-child .cell,.el-table .cell{padding-left:10px}.el-table tr input[type=checkbox]{margin:0}.el-table td,.el-table th.is-leaf{border-bottom:1px solid #ebeef5}.el-table th.is-sortable{cursor:pointer}.el-table th{overflow:hidden;-ms-user-select:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-table th>.cell{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;vertical-align:middle;padding-left:10px;padding-right:10px;width:100%}.el-table th>.cell.highlight{color:#409eff}.el-table th.required>div:before{display:inline-block;content:"";width:8px;height:8px;border-radius:50%;background:#ff4d51;margin-right:5px;vertical-align:middle}.el-table td div{-webkit-box-sizing:border-box;box-sizing:border-box}.el-table td.gutter{width:0}.el-table .cell{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;white-space:normal;word-break:break-all;line-height:23px;padding-right:10px}.el-table .cell.el-tooltip{white-space:nowrap;min-width:50px}.el-table--border,.el-table--group{border:1px solid #ebeef5}.el-table--border:after,.el-table--group:after,.el-table:before{content:"";position:absolute;background-color:#ebeef5;z-index:1}.el-table--border:after,.el-table--group:after{top:0;right:0;width:1px;height:100%}.el-table:before{left:0;bottom:0;width:100%;height:1px}.el-table--border{border-right:none;border-bottom:none}.el-table--border.el-loading-parent--relative{border-color:transparent}.el-table--border td,.el-table--border th,.el-table__body-wrapper .el-table--border.is-scrolling-left~.el-table__fixed{border-right:1px solid #ebeef5}.el-table--border th,.el-table--border th.gutter:last-of-type,.el-table__fixed-right-patch{border-bottom:1px solid #ebeef5}.el-table__fixed,.el-table__fixed-right{position:absolute;top:0;left:0;overflow-x:hidden;overflow-y:hidden;-webkit-box-shadow:0 0 10px rgba(0,0,0,.12);box-shadow:0 0 10px rgba(0,0,0,.12)}.el-table__fixed-right:before,.el-table__fixed:before{content:"";position:absolute;left:0;bottom:0;width:100%;height:1px;background-color:#ebeef5;z-index:4}.el-table__fixed-right-patch{position:absolute;top:-1px;right:0;background-color:#fff}.el-table__fixed-right{top:0;left:auto;right:0}.el-table__fixed-right .el-table__fixed-body-wrapper,.el-table__fixed-right .el-table__fixed-footer-wrapper,.el-table__fixed-right .el-table__fixed-header-wrapper{left:auto;right:0}.el-table__fixed-header-wrapper{position:absolute;left:0;top:0;z-index:3}.el-table__fixed-footer-wrapper{position:absolute;left:0;bottom:0;z-index:3}.el-table__fixed-footer-wrapper tbody td{border-top:1px solid #ebeef5;background-color:#f5f7fa;color:#606266}.el-table__fixed-body-wrapper{position:absolute;left:0;top:37px;overflow:hidden;z-index:3}.el-table__body-wrapper,.el-table__footer-wrapper,.el-table__header-wrapper{width:100%}.el-table__footer-wrapper{margin-top:-1px}.el-table__footer-wrapper td{border-top:1px solid #ebeef5}.el-table__body,.el-table__footer,.el-table__header{table-layout:fixed;border-collapse:separate}.el-table__footer-wrapper,.el-table__header-wrapper{overflow:hidden}.el-table__footer-wrapper tbody td,.el-table__header-wrapper tbody td{background-color:#f5f7fa;color:#606266}.el-table__body-wrapper{overflow:hidden;position:relative}.el-table__body-wrapper.is-scrolling-left~.el-table__fixed,.el-table__body-wrapper.is-scrolling-none~.el-table__fixed,.el-table__body-wrapper.is-scrolling-none~.el-table__fixed-right,.el-table__body-wrapper.is-scrolling-right~.el-table__fixed-right{-webkit-box-shadow:none;box-shadow:none}.el-picker-panel,.el-table-filter{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-table__body-wrapper .el-table--border.is-scrolling-right~.el-table__fixed-right{border-left:1px solid #ebeef5}.el-table .caret-wrapper{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:34px;width:24px;vertical-align:middle;cursor:pointer;overflow:initial;position:relative}.el-table .sort-caret{width:0;height:0;border:5px solid transparent;position:absolute;left:7px}.el-table .sort-caret.ascending{border-bottom-color:#c0c4cc;top:5px}.el-table .sort-caret.descending{border-top-color:#c0c4cc;bottom:7px}.el-table .ascending .sort-caret.ascending{border-bottom-color:#409eff}.el-table .descending .sort-caret.descending{border-top-color:#409eff}.el-table .hidden-columns{position:absolute;z-index:-1}.el-table--striped .el-table__body tr.el-table__row--striped td{background:#fafafa}.el-table--striped .el-table__body tr.el-table__row--striped.current-row td{background-color:#ecf5ff}.el-table__body tr.hover-row.current-row>td,.el-table__body tr.hover-row.el-table__row--striped.current-row>td,.el-table__body tr.hover-row.el-table__row--striped>td,.el-table__body tr.hover-row>td{background-color:#f5f7fa}.el-table__body tr.current-row>td{background-color:#ecf5ff}.el-table__column-resize-proxy{position:absolute;left:200px;top:0;bottom:0;width:0;border-left:1px solid #ebeef5;z-index:10}.el-table__column-filter-trigger{display:inline-block;line-height:34px;cursor:pointer}.el-table__column-filter-trigger i{color:#909399;font-size:12px;-webkit-transform:scale(.75);transform:scale(.75)}.el-table--enable-row-transition .el-table__body td{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}.el-table--enable-row-hover .el-table__body tr:hover>td{background-color:#f5f7fa}.el-table--fluid-height .el-table__fixed,.el-table--fluid-height .el-table__fixed-right{bottom:0;overflow:hidden}.el-table [class*=el-table__row--level] .el-table__expand-icon{display:inline-block;width:20px;line-height:20px;height:20px;text-align:center;margin-right:3px}.el-table-column--selection .cell{padding-left:14px;padding-right:14px}.el-table-filter{border:1px solid #ebeef5;border-radius:2px;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-box-sizing:border-box;box-sizing:border-box;margin:2px 0}.el-date-table td,.el-date-table td div{height:30px;-webkit-box-sizing:border-box}.el-table-filter__list{padding:5px 0;margin:0;list-style:none;min-width:100px}.el-table-filter__list-item{line-height:36px;padding:0 10px;cursor:pointer;font-size:14px}.el-table-filter__list-item:hover{background-color:#ecf5ff;color:#66b1ff}.el-table-filter__list-item.is-active{background-color:#409eff;color:#fff}.el-table-filter__content{min-width:100px}.el-table-filter__bottom{border-top:1px solid #ebeef5;padding:8px}.el-table-filter__bottom button{background:0 0;border:none;color:#606266;cursor:pointer;font-size:13px;padding:0 3px}.el-date-table.is-week-mode .el-date-table__row.current div,.el-date-table.is-week-mode .el-date-table__row:hover div,.el-date-table td.in-range div,.el-date-table td.in-range div:hover{background-color:#f2f6fc}.el-table-filter__bottom button:hover{color:#409eff}.el-table-filter__bottom button:focus{outline:0}.el-table-filter__bottom button.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-table-filter__wrap{max-height:280px}.el-table-filter__checkbox-group{padding:10px}.el-table-filter__checkbox-group label.el-checkbox{display:block;margin-right:5px;margin-bottom:8px;margin-left:5px}.el-table-filter__checkbox-group .el-checkbox:last-child{margin-bottom:0}.el-date-table{font-size:12px;-ms-user-select:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-date-table.is-week-mode .el-date-table__row:hover td.available:hover{color:#606266}.el-date-table.is-week-mode .el-date-table__row:hover td:first-child div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.el-date-table.is-week-mode .el-date-table__row:hover td:last-child div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.el-date-table td{width:32px;padding:4px 0;text-align:center;cursor:pointer;position:relative}.el-date-table td,.el-date-table td div{-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-table td div{padding:3px 0}.el-date-table td span{width:24px;height:24px;display:block;margin:0 auto;line-height:24px;position:absolute;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);border-radius:50%}.el-date-table td.next-month,.el-date-table td.prev-month{color:#c0c4cc}.el-date-table td.today{position:relative}.el-date-table td.today span{color:#409eff;font-weight:700}.el-date-table td.today.end-date span,.el-date-table td.today.start-date span{color:#fff}.el-date-table td.available:hover{color:#409eff}.el-date-table td.current:not(.disabled) span{color:#fff;background-color:#409eff}.el-date-table td.end-date div,.el-date-table td.start-date div{color:#fff}.el-date-table td.end-date span,.el-date-table td.start-date span{background-color:#409eff}.el-date-table td.start-date div{margin-left:5px;border-top-left-radius:15px;border-bottom-left-radius:15px}.el-date-table td.end-date div{margin-right:5px;border-top-right-radius:15px;border-bottom-right-radius:15px}.el-date-table td.disabled div{background-color:#f5f7fa;opacity:1;cursor:not-allowed;color:#c0c4cc}.el-date-table td.selected div{margin-left:5px;margin-right:5px;background-color:#f2f6fc;border-radius:15px}.el-date-table td.selected div:hover{background-color:#f2f6fc}.el-date-table td.selected span{background-color:#409eff;color:#fff;border-radius:15px}.el-date-table td.week{font-size:80%;color:#606266}.el-month-table,.el-year-table{font-size:12px;border-collapse:collapse}.el-date-table th{padding:5px;color:#606266;font-weight:400;border-bottom:1px solid #ebeef5}.el-month-table{margin:-1px}.el-month-table td{text-align:center;padding:8px 0;cursor:pointer}.el-month-table td div{height:48px;padding:6px 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-month-table td.today .cell{color:#409eff;font-weight:700}.el-month-table td.today.end-date .cell,.el-month-table td.today.start-date .cell{color:#fff}.el-month-table td.disabled .cell{background-color:#f5f7fa;cursor:not-allowed;color:#c0c4cc}.el-month-table td.disabled .cell:hover{color:#c0c4cc}.el-month-table td .cell{width:60px;height:36px;display:block;line-height:36px;color:#606266;margin:0 auto;border-radius:18px}.el-month-table td .cell:hover{color:#409eff}.el-month-table td.in-range div,.el-month-table td.in-range div:hover{background-color:#f2f6fc}.el-month-table td.end-date div,.el-month-table td.start-date div{color:#fff}.el-month-table td.end-date .cell,.el-month-table td.start-date .cell{color:#fff;background-color:#409eff}.el-month-table td.start-date div{border-top-left-radius:24px;border-bottom-left-radius:24px}.el-month-table td.end-date div{border-top-right-radius:24px;border-bottom-right-radius:24px}.el-month-table td.current:not(.disabled) .cell{color:#409eff}.el-year-table{margin:-1px}.el-year-table .el-icon{color:#303133}.el-year-table td{text-align:center;padding:20px 3px;cursor:pointer}.el-year-table td.today .cell{color:#409eff;font-weight:700}.el-year-table td.disabled .cell{background-color:#f5f7fa;cursor:not-allowed;color:#c0c4cc}.el-year-table td.disabled .cell:hover{color:#c0c4cc}.el-year-table td .cell{width:48px;height:32px;display:block;line-height:32px;color:#606266;margin:0 auto}.el-year-table td .cell:hover,.el-year-table td.current:not(.disabled) .cell{color:#409eff}.el-date-range-picker{width:646px}.el-date-range-picker.has-sidebar{width:756px}.el-date-range-picker table{table-layout:fixed;width:100%}.el-date-range-picker .el-picker-panel__body{min-width:513px}.el-date-range-picker .el-picker-panel__content{margin:0}.el-date-range-picker__header{position:relative;text-align:center;height:28px}.el-date-range-picker__header [class*=arrow-left]{float:left}.el-date-range-picker__header [class*=arrow-right]{float:right}.el-date-range-picker__header div{font-size:16px;font-weight:500;margin-right:50px}.el-date-range-picker__content{float:left;width:50%;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:16px}.el-date-range-picker__content.is-left{border-right:1px solid #e4e4e4}.el-date-range-picker__content .el-date-range-picker__header div{margin-left:50px;margin-right:50px}.el-date-range-picker__editors-wrap{-webkit-box-sizing:border-box;box-sizing:border-box;display:table-cell}.el-date-range-picker__editors-wrap.is-right{text-align:right}.el-date-range-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-range-picker__time-header>.el-icon-arrow-right{font-size:20px;vertical-align:middle;display:table-cell;color:#303133}.el-date-range-picker__time-picker-wrap{position:relative;display:table-cell;padding:0 5px}.el-date-range-picker__time-picker-wrap .el-picker-panel{position:absolute;top:13px;right:0;z-index:1;background:#fff}.el-date-picker{width:322px}.el-date-picker.has-sidebar.has-time{width:434px}.el-date-picker.has-sidebar{width:438px}.el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.el-date-picker .el-picker-panel__content{width:292px}.el-date-picker table{table-layout:fixed;width:100%}.el-date-picker__editor-wrap{position:relative;display:table-cell;padding:0 5px}.el-date-picker__time-header{position:relative;border-bottom:1px solid #e4e4e4;font-size:12px;padding:8px 5px 5px;display:table;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}.el-date-picker__header{margin:12px;text-align:center}.el-date-picker__header--bordered{margin-bottom:0;padding-bottom:12px;border-bottom:1px solid #ebeef5}.el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.el-date-picker__header-label{font-size:16px;font-weight:500;padding:0 5px;line-height:22px;text-align:center;cursor:pointer;color:#606266}.el-date-picker__header-label.active,.el-date-picker__header-label:hover{color:#409eff}.el-date-picker__prev-btn{float:left}.el-date-picker__next-btn{float:right}.el-date-picker__time-wrap{padding:10px;text-align:center}.el-date-picker__time-label{float:left;cursor:pointer;line-height:30px;margin-left:10px}.time-select{margin:5px 0;min-width:0}.time-select .el-picker-panel__content{max-height:200px;margin:0}.time-select-item{padding:8px 10px;font-size:14px;line-height:20px}.time-select-item.selected:not(.disabled){color:#409eff;font-weight:700}.time-select-item.disabled{color:#e4e7ed;cursor:not-allowed}.time-select-item:hover{background-color:#f5f7fa;font-weight:700;cursor:pointer}.el-date-editor{position:relative;display:inline-block;text-align:left}.el-date-editor.el-input,.el-date-editor.el-input__inner{width:220px}.el-date-editor--monthrange.el-input,.el-date-editor--monthrange.el-input__inner{width:300px}.el-date-editor--daterange.el-input,.el-date-editor--daterange.el-input__inner,.el-date-editor--timerange.el-input,.el-date-editor--timerange.el-input__inner{width:350px}.el-date-editor--datetimerange.el-input,.el-date-editor--datetimerange.el-input__inner{width:400px}.el-date-editor--dates .el-input__inner{text-overflow:ellipsis;white-space:nowrap}.el-date-editor .el-icon-circle-close{cursor:pointer}.el-date-editor .el-range__icon{font-size:14px;margin-left:-5px;color:#c0c4cc;float:left;line-height:32px}.el-date-editor .el-range-input,.el-date-editor .el-range-separator{height:100%;margin:0;text-align:center;display:inline-block;font-size:14px}.el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;outline:0;padding:0;width:39%;color:#606266}.el-date-editor .el-range-input::-webkit-input-placeholder{color:#c0c4cc}.el-date-editor .el-range-input:-ms-input-placeholder{color:#c0c4cc}.el-date-editor .el-range-input::-ms-input-placeholder{color:#c0c4cc}.el-date-editor .el-range-input::-moz-placeholder{color:#c0c4cc}.el-date-editor .el-range-input::placeholder{color:#c0c4cc}.el-date-editor .el-range-separator{padding:0 5px;line-height:32px;width:5%;color:#303133}.el-date-editor .el-range__close-icon{font-size:14px;color:#c0c4cc;width:25px;display:inline-block;float:right;line-height:32px}.el-range-editor.el-input__inner{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:3px 10px}.el-range-editor .el-range-input{line-height:1}.el-range-editor.is-active,.el-range-editor.is-active:hover{border-color:#409eff}.el-range-editor--medium.el-input__inner{height:36px}.el-range-editor--medium .el-range-separator{line-height:28px;font-size:14px}.el-range-editor--medium .el-range-input{font-size:14px}.el-range-editor--medium .el-range__close-icon,.el-range-editor--medium .el-range__icon{line-height:28px}.el-range-editor--small.el-input__inner{height:32px}.el-range-editor--small .el-range-separator{line-height:24px;font-size:13px}.el-range-editor--small .el-range-input{font-size:13px}.el-range-editor--small .el-range__close-icon,.el-range-editor--small .el-range__icon{line-height:24px}.el-range-editor--mini.el-input__inner{height:28px}.el-range-editor--mini .el-range-separator{line-height:20px;font-size:12px}.el-range-editor--mini .el-range-input{font-size:12px}.el-range-editor--mini .el-range__close-icon,.el-range-editor--mini .el-range__icon{line-height:20px}.el-range-editor.is-disabled{background-color:#f5f7fa;border-color:#e4e7ed;color:#c0c4cc;cursor:not-allowed}.el-range-editor.is-disabled:focus,.el-range-editor.is-disabled:hover{border-color:#e4e7ed}.el-range-editor.is-disabled input{background-color:#f5f7fa;color:#c0c4cc;cursor:not-allowed}.el-range-editor.is-disabled input::-webkit-input-placeholder{color:#c0c4cc}.el-range-editor.is-disabled input:-ms-input-placeholder{color:#c0c4cc}.el-range-editor.is-disabled input::-ms-input-placeholder{color:#c0c4cc}.el-range-editor.is-disabled input::-moz-placeholder{color:#c0c4cc}.el-range-editor.is-disabled input::placeholder{color:#c0c4cc}.el-range-editor.is-disabled .el-range-separator{color:#c0c4cc}.el-picker-panel{color:#606266;border:1px solid #e4e7ed;box-shadow:0 2px 12px 0 rgba(0,0,0,.1);background:#fff;border-radius:4px;line-height:30px;margin:5px 0}.el-picker-panel,.el-popover,.el-time-panel{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-picker-panel__body-wrapper:after,.el-picker-panel__body:after{content:"";display:table;clear:both}.el-picker-panel__content{position:relative;margin:15px}.el-picker-panel__footer{border-top:1px solid #e4e4e4;padding:4px;text-align:right;background-color:#fff;position:relative;font-size:0}.el-picker-panel__shortcut{display:block;width:100%;border:0;background-color:transparent;line-height:28px;font-size:14px;color:#606266;padding-left:12px;text-align:left;outline:0;cursor:pointer}.el-picker-panel__shortcut:hover{color:#409eff}.el-picker-panel__shortcut.active{background-color:#e6f1fe;color:#409eff}.el-picker-panel__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.el-picker-panel__btn[disabled]{color:#ccc;cursor:not-allowed}.el-picker-panel__icon-btn{font-size:12px;color:#303133;border:0;background:0 0;cursor:pointer;outline:0;margin-top:8px}.el-picker-panel__icon-btn:hover{color:#409eff}.el-picker-panel__icon-btn.is-disabled{color:#bbb}.el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.el-picker-panel__link-btn{vertical-align:middle}.el-picker-panel [slot=sidebar],.el-picker-panel__sidebar{position:absolute;top:0;bottom:0;width:110px;border-right:1px solid #e4e4e4;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;background-color:#fff;overflow:auto}.el-picker-panel [slot=sidebar]+.el-picker-panel__body,.el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.el-time-spinner__wrapper{max-height:190px;overflow:auto;display:inline-block;width:50%;vertical-align:top;position:relative}.el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.el-time-spinner__input.el-input .el-input__inner,.el-time-spinner__list{padding:0;text-align:center}.el-time-spinner__wrapper.is-arrow{-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;overflow:hidden}.el-time-spinner__wrapper.is-arrow .el-time-spinner__list{-webkit-transform:translateY(-32px);transform:translateY(-32px)}.el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active){background:#fff;cursor:default}.el-time-spinner__arrow{font-size:12px;color:#909399;position:absolute;left:0;width:100%;z-index:1;text-align:center;height:30px;line-height:30px;cursor:pointer}.el-time-spinner__arrow:hover{color:#409eff}.el-time-spinner__arrow.el-icon-arrow-up{top:10px}.el-time-spinner__arrow.el-icon-arrow-down{bottom:10px}.el-time-spinner__input.el-input{width:70%}.el-time-spinner__list{margin:0;list-style:none}.el-time-spinner__list:after,.el-time-spinner__list:before{content:"";display:block;width:100%;height:80px}.el-time-spinner__item{height:32px;line-height:32px;font-size:12px;color:#606266}.el-time-spinner__item:hover:not(.disabled):not(.active){background:#f5f7fa;cursor:pointer}.el-time-spinner__item.active:not(.disabled){color:#303133;font-weight:700}.el-time-spinner__item.disabled{color:#c0c4cc;cursor:not-allowed}.el-time-panel{margin:5px 0;border:1px solid #e4e7ed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);border-radius:2px;position:absolute;width:180px;left:0;z-index:1000;user-select:none;-webkit-box-sizing:content-box;box-sizing:content-box}.el-slider__button,.el-slider__button-wrapper,.el-time-panel{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-time-panel__content{font-size:0;position:relative;overflow:hidden}.el-time-panel__content:after,.el-time-panel__content:before{content:"";top:50%;position:absolute;margin-top:-15px;height:32px;z-index:-1;left:0;right:0;-webkit-box-sizing:border-box;box-sizing:border-box;padding-top:6px;text-align:left;border-top:1px solid #e4e7ed;border-bottom:1px solid #e4e7ed}.el-time-panel__content:after{left:50%;margin-left:12%;margin-right:12%}.el-time-panel__content:before{padding-left:50%;margin-right:12%;margin-left:12%}.el-time-panel__content.has-seconds:after{left:66.66667%}.el-time-panel__content.has-seconds:before{padding-left:33.33333%}.el-time-panel__footer{border-top:1px solid #e4e4e4;padding:4px;height:36px;line-height:25px;text-align:right;-webkit-box-sizing:border-box;box-sizing:border-box}.el-time-panel__btn{border:none;line-height:28px;padding:0 5px;margin:0 5px;cursor:pointer;background-color:transparent;outline:0;font-size:12px;color:#303133}.el-time-panel__btn.confirm{font-weight:800;color:#409eff}.el-time-range-picker{width:354px;overflow:visible}.el-time-range-picker__content{position:relative;text-align:center;padding:10px}.el-time-range-picker__cell{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:4px 7px 7px;width:50%;display:inline-block}.el-time-range-picker__header{margin-bottom:5px;text-align:center;font-size:14px}.el-time-range-picker__body{border-radius:2px;border:1px solid #e4e7ed}.el-popover{position:absolute;background:#fff;min-width:150px;border:1px solid #ebeef5;padding:12px;z-index:2000;color:#606266;line-height:1.4;text-align:justify;font-size:14px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);word-break:break-all}.el-popover--plain{padding:18px 20px}.el-popover__title{color:#303133;font-size:16px;line-height:1;margin-bottom:12px}.v-modal-enter{-webkit-animation:v-modal-in .2s ease;animation:v-modal-in .2s ease}.v-modal-leave{-webkit-animation:v-modal-out .2s ease forwards;animation:v-modal-out .2s ease forwards}@keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-out{to{opacity:0}}.v-modal{position:fixed;left:0;top:0;width:100%;height:100%;opacity:.5;background:#000}.el-popup-parent--hidden{overflow:hidden}.el-message-box{display:inline-block;width:420px;padding-bottom:10px;vertical-align:middle;background-color:#fff;border-radius:4px;border:1px solid #ebeef5;font-size:18px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);text-align:left;overflow:hidden;-webkit-backface-visibility:hidden;backface-visibility:hidden}.el-message-box__wrapper{position:fixed;top:0;bottom:0;left:0;right:0;text-align:center}.el-message-box__wrapper:after{content:"";display:inline-block;height:100%;width:0;vertical-align:middle}.el-message-box__header{position:relative;padding:15px 15px 10px}.el-message-box__title{padding-left:0;margin-bottom:0;font-size:18px;line-height:1;color:#303133}.el-message-box__headerbtn{position:absolute;top:15px;right:15px;padding:0;border:none;outline:0;background:0 0;font-size:16px;cursor:pointer}.el-form-item.is-error .el-input__inner,.el-form-item.is-error .el-input__inner:focus,.el-form-item.is-error .el-textarea__inner,.el-form-item.is-error .el-textarea__inner:focus,.el-message-box__input input.invalid,.el-message-box__input input.invalid:focus{border-color:#f56c6c}.el-message-box__headerbtn .el-message-box__close{color:#909399}.el-message-box__headerbtn:focus .el-message-box__close,.el-message-box__headerbtn:hover .el-message-box__close{color:#409eff}.el-message-box__content{padding:10px 15px;color:#606266;font-size:14px}.el-message-box__container{position:relative}.el-message-box__input{padding-top:15px}.el-message-box__status{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);font-size:24px!important}.el-message-box__status:before{padding-left:1px}.el-message-box__status+.el-message-box__message{padding-left:36px;padding-right:12px}.el-message-box__status.el-icon-success{color:#67c23a}.el-message-box__status.el-icon-info{color:#909399}.el-message-box__status.el-icon-warning{color:#e6a23c}.el-message-box__status.el-icon-error{color:#f56c6c}.el-message-box__message{margin:0}.el-message-box__message p{margin:0;line-height:24px}.el-message-box__errormsg{color:#f56c6c;font-size:12px;min-height:18px;margin-top:2px}.el-message-box__btns{padding:5px 15px 0;text-align:right}.el-message-box__btns button:nth-child(2){margin-left:10px}.el-message-box__btns-reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.el-message-box--center{padding-bottom:30px}.el-message-box--center .el-message-box__header{padding-top:30px}.el-message-box--center .el-message-box__title{position:relative;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}.el-message-box--center .el-message-box__status{position:relative;top:auto;padding-right:5px;text-align:center;-webkit-transform:translateY(-1px);transform:translateY(-1px)}.el-message-box--center .el-message-box__message{margin-left:0}.el-message-box--center .el-message-box__btns,.el-message-box--center .el-message-box__content{text-align:center}.el-message-box--center .el-message-box__content{padding-left:27px;padding-right:27px}.msgbox-fade-enter-active{-webkit-animation:msgbox-fade-in .3s;animation:msgbox-fade-in .3s}.msgbox-fade-leave-active{-webkit-animation:msgbox-fade-out .3s;animation:msgbox-fade-out .3s}@-webkit-keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@keyframes msgbox-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@-webkit-keyframes msgbox-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes msgbox-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-breadcrumb{font-size:14px;line-height:1}.el-breadcrumb:after,.el-breadcrumb:before{display:table;content:""}.el-breadcrumb:after{clear:both}.el-breadcrumb__separator{margin:0 9px;font-weight:700;color:#c0c4cc}.el-breadcrumb__separator[class*=icon]{margin:0 6px;font-weight:400}.el-breadcrumb__item{float:left}.el-breadcrumb__inner{color:#606266}.el-breadcrumb__inner.is-link,.el-breadcrumb__inner a{font-weight:700;text-decoration:none;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1);color:#303133}.el-breadcrumb__inner.is-link:hover,.el-breadcrumb__inner a:hover{color:#409eff;cursor:pointer}.el-breadcrumb__item:last-child .el-breadcrumb__inner,.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover,.el-breadcrumb__item:last-child .el-breadcrumb__inner a,.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover{font-weight:400;color:#606266;cursor:text}.el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}.el-form--label-left .el-form-item__label{text-align:left}.el-form--label-top .el-form-item__label{float:none;display:inline-block;text-align:left;padding:0 0 10px}.el-form--inline .el-form-item{display:inline-block;margin-right:10px;vertical-align:top}.el-form--inline .el-form-item__label{float:none;display:inline-block}.el-form--inline .el-form-item__content{display:inline-block;vertical-align:top}.el-form--inline.el-form--label-top .el-form-item__content{display:block}.el-form-item{margin-bottom:22px}.el-form-item:after,.el-form-item:before{display:table;content:""}.el-form-item:after{clear:both}.el-form-item .el-form-item{margin-bottom:0}.el-form-item--mini.el-form-item,.el-form-item--small.el-form-item{margin-bottom:18px}.el-form-item .el-input__validateIcon{display:none}.el-form-item--medium .el-form-item__content,.el-form-item--medium .el-form-item__label{line-height:36px}.el-form-item--small .el-form-item__content,.el-form-item--small .el-form-item__label{line-height:32px}.el-form-item--small .el-form-item__error{padding-top:2px}.el-form-item--mini .el-form-item__content,.el-form-item--mini .el-form-item__label{line-height:28px}.el-form-item--mini .el-form-item__error{padding-top:1px}.el-form-item__label-wrap{float:left}.el-form-item__label-wrap .el-form-item__label{display:inline-block;float:none}.el-form-item__label{text-align:right;vertical-align:middle;float:left;font-size:14px;color:#606266;line-height:40px;padding:0 12px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-form-item__content{line-height:40px;position:relative;font-size:14px}.el-form-item__content:after,.el-form-item__content:before{display:table;content:""}.el-form-item__content:after{clear:both}.el-form-item__content .el-input-group{vertical-align:top}.el-form-item__error{color:#f56c6c;font-size:12px;line-height:1;padding-top:4px;position:absolute;top:100%;left:0}.el-form-item__error--inline{position:relative;top:auto;left:auto;display:inline-block;margin-left:10px}.el-form-item.is-required:not(.is-no-asterisk) .el-form-item__label-wrap>.el-form-item__label:before,.el-form-item.is-required:not(.is-no-asterisk)>.el-form-item__label:before{content:"*";color:#f56c6c;margin-right:4px}.el-form-item.is-error .el-input-group__append .el-input__inner,.el-form-item.is-error .el-input-group__prepend .el-input__inner{border-color:transparent}.el-form-item.is-error .el-input__validateIcon{color:#f56c6c}.el-form-item--feedback .el-input__validateIcon{display:inline-block}.el-tabs__header{padding:0;position:relative;margin:0 0 15px}.el-tabs__active-bar{position:absolute;bottom:0;left:0;height:2px;background-color:#409eff;z-index:1;-webkit-transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:-webkit-transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1);transition:transform .3s cubic-bezier(.645,.045,.355,1),-webkit-transform .3s cubic-bezier(.645,.045,.355,1);list-style:none}.el-tabs__new-tab{float:right;border:1px solid #d3dce6;height:18px;width:18px;line-height:18px;margin:12px 0 9px 10px;border-radius:3px;text-align:center;font-size:12px;color:#d3dce6;cursor:pointer;-webkit-transition:all .15s;transition:all .15s}.el-collapse-item__arrow,.el-tabs__nav{-webkit-transition:-webkit-transform .3s}.el-tabs__new-tab .el-icon-plus{-webkit-transform:scale(.8);transform:scale(.8)}.el-tabs__new-tab:hover{color:#409eff}.el-tabs__nav-wrap{overflow:hidden;margin-bottom:-1px;position:relative}.el-tabs__nav-wrap:after{content:"";position:absolute;left:0;bottom:0;width:100%;height:2px;background-color:#e4e7ed;z-index:1}.el-tabs--border-card>.el-tabs__header .el-tabs__nav-wrap:after,.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap:after{content:none}.el-tabs__nav-wrap.is-scrollable{padding:0 20px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-tabs__nav-scroll{overflow:hidden}.el-tabs__nav-next,.el-tabs__nav-prev{position:absolute;cursor:pointer;line-height:44px;font-size:12px;color:#909399}.el-tabs__nav-next{right:0}.el-tabs__nav-prev{left:0}.el-tabs__nav{white-space:nowrap;position:relative;transition:-webkit-transform .3s;-webkit-transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;float:left;z-index:2}.el-tabs__nav.is-stretch{min-width:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.el-tabs__nav.is-stretch>*{-webkit-box-flex:1;-ms-flex:1;flex:1;text-align:center}.el-tabs__item{padding:0 20px;height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;line-height:40px;display:inline-block;list-style:none;font-size:14px;font-weight:500;color:#303133;position:relative}.el-tabs__item:focus,.el-tabs__item:focus:active{outline:0}.el-tabs__item:focus.is-active.is-focus:not(:active){-webkit-box-shadow:0 0 2px 2px #409eff inset;box-shadow:inset 0 0 2px 2px #409eff;border-radius:3px}.el-tabs__item .el-icon-close{border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);margin-left:5px}.el-tabs__item .el-icon-close:before{-webkit-transform:scale(.9);transform:scale(.9);display:inline-block}.el-tabs__item .el-icon-close:hover{background-color:#c0c4cc;color:#fff}.el-tabs__item.is-active{color:#409eff}.el-tabs__item:hover{color:#409eff;cursor:pointer}.el-tabs__item.is-disabled{color:#c0c4cc;cursor:default}.el-tabs__content{overflow:hidden;position:relative}.el-tabs--card>.el-tabs__header{border-bottom:1px solid #e4e7ed}.el-tabs--card>.el-tabs__header .el-tabs__nav{border:1px solid #e4e7ed;border-bottom:none;border-radius:4px 4px 0 0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-tabs--card>.el-tabs__header .el-tabs__active-bar{display:none}.el-tabs--card>.el-tabs__header .el-tabs__item .el-icon-close{position:relative;font-size:12px;width:0;height:14px;vertical-align:middle;line-height:15px;overflow:hidden;top:-1px;right:-2px;-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable .el-icon-close,.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover .el-icon-close{width:14px}.el-tabs--card>.el-tabs__header .el-tabs__item{border-bottom:1px solid transparent;border-left:1px solid #e4e7ed;-webkit-transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1);transition:color .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-left:none}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover{padding-left:13px;padding-right:13px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:#fff}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable{padding-left:20px;padding-right:20px}.el-tabs--border-card{background:#fff;border:1px solid #dcdfe6;-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04);box-shadow:0 2px 4px 0 rgba(0,0,0,.12),0 0 6px 0 rgba(0,0,0,.04)}.el-tabs--border-card>.el-tabs__content{padding:15px}.el-tabs--border-card>.el-tabs__header{background-color:#f5f7fa;border-bottom:1px solid #e4e7ed;margin:0}.el-tabs--border-card>.el-tabs__header .el-tabs__item{-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);border:1px solid transparent;margin-top:-1px;color:#909399}.el-tabs--border-card>.el-tabs__header .el-tabs__item+.el-tabs__item,.el-tabs--border-card>.el-tabs__header .el-tabs__item:first-child{margin-left:-1px}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{color:#409eff;background-color:#fff;border-right-color:#dcdfe6;border-left-color:#dcdfe6}.el-tabs--border-card>.el-tabs__header .el-tabs__item:not(.is-disabled):hover{color:#409eff}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-disabled{color:#c0c4cc}.el-tabs--border-card>.el-tabs__header .is-scrollable .el-tabs__item:first-child{margin-left:0}.el-tabs--bottom .el-tabs__item.is-bottom:nth-child(2),.el-tabs--bottom .el-tabs__item.is-top:nth-child(2),.el-tabs--top .el-tabs__item.is-bottom:nth-child(2),.el-tabs--top .el-tabs__item.is-top:nth-child(2){padding-left:0}.el-tabs--bottom .el-tabs__item.is-bottom:last-child,.el-tabs--bottom .el-tabs__item.is-top:last-child,.el-tabs--top .el-tabs__item.is-bottom:last-child,.el-tabs--top .el-tabs__item.is-top:last-child{padding-right:0}.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:nth-child(2){padding-left:20px}.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom .el-tabs--left>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom .el-tabs--right>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top .el-tabs--left>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top .el-tabs--right>.el-tabs__header .el-tabs__item:last-child{padding-right:20px}.el-tabs--bottom .el-tabs__header.is-bottom{margin-bottom:0;margin-top:10px}.el-tabs--bottom.el-tabs--border-card .el-tabs__header.is-bottom{border-bottom:0;border-top:1px solid #dcdfe6}.el-tabs--bottom.el-tabs--border-card .el-tabs__nav-wrap.is-bottom{margin-top:-1px;margin-bottom:0}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom:not(.is-active){border:1px solid transparent}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom{margin:0 -1px -1px}.el-tabs--left,.el-tabs--right{overflow:hidden}.el-tabs--left .el-tabs__header.is-left,.el-tabs--left .el-tabs__header.is-right,.el-tabs--left .el-tabs__nav-scroll,.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__header.is-left,.el-tabs--right .el-tabs__header.is-right,.el-tabs--right .el-tabs__nav-scroll,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{height:100%}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__active-bar.is-right,.el-tabs--right .el-tabs__active-bar.is-left,.el-tabs--right .el-tabs__active-bar.is-right{top:0;bottom:auto;width:2px;height:auto}.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{margin-bottom:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{height:30px;line-height:30px;width:100%;text-align:center;cursor:pointer}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{left:auto;top:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next{right:auto;bottom:0}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__nav-wrap.is-left:after{right:0;left:auto}.el-tabs--left .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--left .el-tabs__nav-wrap.is-right.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-right.is-scrollable{padding:30px 0}.el-tabs--left .el-tabs__nav-wrap.is-left:after,.el-tabs--left .el-tabs__nav-wrap.is-right:after,.el-tabs--right .el-tabs__nav-wrap.is-left:after,.el-tabs--right .el-tabs__nav-wrap.is-right:after{height:100%;width:2px;bottom:auto;top:0}.el-tabs--left .el-tabs__nav.is-left,.el-tabs--left .el-tabs__nav.is-right,.el-tabs--right .el-tabs__nav.is-left,.el-tabs--right .el-tabs__nav.is-right{float:none}.el-tabs--left .el-tabs__item.is-left,.el-tabs--left .el-tabs__item.is-right,.el-tabs--right .el-tabs__item.is-left,.el-tabs--right .el-tabs__item.is-right{display:block}.el-tabs--left.el-tabs--card .el-tabs__active-bar.is-left,.el-tabs--right.el-tabs--card .el-tabs__active-bar.is-right{display:none}.el-tabs--left .el-tabs__header.is-left{float:left;margin-bottom:0;margin-right:10px}.el-tabs--left .el-tabs__nav-wrap.is-left{margin-right:-1px}.el-tabs--left .el-tabs__item.is-left{text-align:right}.el-tabs--left.el-tabs--card .el-tabs__item.is-left{border:1px solid #e4e7ed;border-bottom:none;border-left:none;text-align:left}.el-tabs--left.el-tabs--card .el-tabs__item.is-left:first-child{border-right:1px solid #e4e7ed;border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active{border:none;border-top:1px solid #e4e7ed;border-right:1px solid #fff}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:first-child{border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:last-child{border-bottom:none}.el-tabs--left.el-tabs--card .el-tabs__nav{border-radius:4px 0 0 4px;border-bottom:1px solid #e4e7ed;border-right:none}.el-tabs--left.el-tabs--card .el-tabs__new-tab{float:none}.el-tabs--left.el-tabs--border-card .el-tabs__header.is-left{border-right:1px solid #dfe4ed}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left{border:1px solid transparent;margin:-1px 0 -1px -1px}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left.is-active{border-color:#d1dbe5 transparent}.el-tabs--right .el-tabs__header.is-right{float:right;margin-bottom:0;margin-left:10px}.el-tabs--right .el-tabs__nav-wrap.is-right{margin-left:-1px}.el-tabs--right .el-tabs__nav-wrap.is-right:after{left:0;right:auto}.el-tabs--right .el-tabs__active-bar.is-right{left:0}.el-tabs--right.el-tabs--card .el-tabs__item.is-right{border-bottom:none;border-top:1px solid #e4e7ed}.el-tabs--right.el-tabs--card .el-tabs__item.is-right:first-child{border-left:1px solid #e4e7ed;border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active{border:none;border-top:1px solid #e4e7ed;border-left:1px solid #fff}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:first-child{border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:last-child{border-bottom:none}.el-tabs--right.el-tabs--card .el-tabs__nav{border-radius:0 4px 4px 0;border-bottom:1px solid #e4e7ed;border-left:none}.el-tabs--right.el-tabs--border-card .el-tabs__header.is-right{border-left:1px solid #dfe4ed}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right{border:1px solid transparent;margin:-1px -1px -1px 0}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right.is-active{border-color:#d1dbe5 transparent}.slideInLeft-transition,.slideInRight-transition{display:inline-block}.slideInRight-enter{-webkit-animation:slideInRight-enter .3s;animation:slideInRight-enter .3s}.slideInRight-leave{position:absolute;left:0;right:0;-webkit-animation:slideInRight-leave .3s;animation:slideInRight-leave .3s}.slideInLeft-enter{-webkit-animation:slideInLeft-enter .3s;animation:slideInLeft-enter .3s}.slideInLeft-leave{position:absolute;left:0;right:0;-webkit-animation:slideInLeft-leave .3s;animation:slideInLeft-leave .3s}@-webkit-keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInRight-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}to{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@keyframes slideInRight-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}to{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(100%);transform:translateX(100%);opacity:0}}@-webkit-keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideInLeft-enter{0%{opacity:0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}to{opacity:1;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}to{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}@keyframes slideInLeft-leave{0%{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(0);transform:translateX(0);opacity:1}to{-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform:translateX(-100%);transform:translateX(-100%);opacity:0}}.el-tree{position:relative;cursor:default;background:#fff;color:#606266}.el-tree__empty-block{position:relative;min-height:60px;text-align:center;width:100%;height:100%}.el-tree__empty-text{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);color:#909399;font-size:14px}.el-tree__drop-indicator{position:absolute;left:0;right:0;height:1px;background-color:#409eff}.el-tree-node{white-space:nowrap;outline:0}.el-tree-node:focus>.el-tree-node__content{background-color:#f5f7fa}.el-tree-node.is-drop-inner>.el-tree-node__content .el-tree-node__label{background-color:#409eff;color:#fff}.el-tree-node__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:26px;cursor:pointer}.el-tree-node__content>.el-tree-node__expand-icon{padding:6px}.el-tree-node__content>label.el-checkbox{margin-right:8px}.el-tree-node__content:hover{background-color:#f5f7fa}.el-tree.is-dragging .el-tree-node__content{cursor:move}.el-tree.is-dragging.is-drop-not-allow .el-tree-node__content{cursor:not-allowed}.el-tree-node__expand-icon{cursor:pointer;color:#c0c4cc;font-size:12px;-webkit-transform:rotate(0);transform:rotate(0);-webkit-transition:-webkit-transform .3s ease-in-out;transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;transition:transform .3s ease-in-out,-webkit-transform .3s ease-in-out}.el-tree-node__expand-icon.expanded{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-tree-node__expand-icon.is-leaf{color:transparent;cursor:default}.el-tree-node__label{font-size:14px}.el-tree-node__loading-icon{margin-right:8px;font-size:14px;color:#c0c4cc}.el-tree-node>.el-tree-node__children{overflow:hidden;background-color:transparent}.el-tree-node.is-expanded>.el-tree-node__children{display:block}.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:#f0f7ff}.el-alert{width:100%;padding:8px 16px;margin:0;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;position:relative;background-color:#fff;overflow:hidden;opacity:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .2s;transition:opacity .2s}.el-alert.is-light .el-alert__closebtn{color:#c0c4cc}.el-alert.is-dark .el-alert__closebtn,.el-alert.is-dark .el-alert__description{color:#fff}.el-alert.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-alert--success.is-light{background-color:#f0f9eb;color:#67c23a}.el-alert--success.is-light .el-alert__description{color:#67c23a}.el-alert--success.is-dark{background-color:#67c23a;color:#fff}.el-alert--info.is-light{background-color:#f4f4f5;color:#909399}.el-alert--info.is-dark{background-color:#909399;color:#fff}.el-alert--info .el-alert__description{color:#909399}.el-alert--warning.is-light{background-color:#fdf6ec;color:#e6a23c}.el-alert--warning.is-light .el-alert__description{color:#e6a23c}.el-alert--warning.is-dark{background-color:#e6a23c;color:#fff}.el-alert--error.is-light{background-color:#fef0f0;color:#f56c6c}.el-alert--error.is-light .el-alert__description{color:#f56c6c}.el-alert--error.is-dark{background-color:#f56c6c;color:#fff}.el-alert__content{display:table-cell;padding:0 8px}.el-alert__icon{font-size:16px;width:16px}.el-alert__icon.is-big{font-size:28px;width:28px}.el-alert__title{font-size:13px;line-height:18px}.el-alert__title.is-bold{font-weight:700}.el-alert .el-alert__description{font-size:12px;margin:5px 0 0}.el-alert__closebtn{font-size:12px;opacity:1;position:absolute;top:12px;right:15px;cursor:pointer}.el-alert-fade-enter,.el-alert-fade-leave-active,.el-loading-fade-enter,.el-loading-fade-leave-active,.el-notification-fade-leave-active{opacity:0}.el-alert__closebtn.is-customed{font-style:normal;font-size:13px;top:9px}.el-notification{display:-webkit-box;display:-ms-flexbox;display:flex;width:330px;padding:14px 26px 14px 13px;border-radius:8px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #ebeef5;position:fixed;background-color:#fff;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1);-webkit-transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s;transition:opacity .3s,transform .3s,left .3s,right .3s,top .4s,bottom .3s,-webkit-transform .3s;overflow:hidden}.el-notification.right{right:16px}.el-notification.left{left:16px}.el-notification__group{margin-left:13px;margin-right:8px}.el-notification__title{font-weight:700;font-size:16px;color:#303133;margin:0}.el-notification__content{font-size:14px;line-height:21px;margin:6px 0 0;color:#606266;text-align:justify}.el-notification__content p{margin:0}.el-notification__icon{height:24px;width:24px;font-size:24px}.el-notification__closeBtn{position:absolute;top:18px;right:15px;cursor:pointer;color:#909399;font-size:16px}.el-notification__closeBtn:hover{color:#606266}.el-notification .el-icon-success{color:#67c23a}.el-notification .el-icon-error{color:#f56c6c}.el-notification .el-icon-info{color:#909399}.el-notification .el-icon-warning{color:#e6a23c}.el-notification-fade-enter.right{right:0;-webkit-transform:translateX(100%);transform:translateX(100%)}.el-notification-fade-enter.left{left:0;-webkit-transform:translateX(-100%);transform:translateX(-100%)}.el-input-number{position:relative;display:inline-block;width:180px;line-height:38px}.el-input-number .el-input{display:block}.el-input-number .el-input__inner{-webkit-appearance:none;padding-left:50px;padding-right:50px;text-align:center}.el-input-number__decrease,.el-input-number__increase{position:absolute;z-index:1;top:1px;width:40px;height:auto;text-align:center;background:#f5f7fa;color:#606266;cursor:pointer;font-size:13px}.el-input-number__decrease:hover,.el-input-number__increase:hover{color:#409eff}.el-input-number__decrease:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled),.el-input-number__increase:hover:not(.is-disabled)~.el-input .el-input__inner:not(.is-disabled){border-color:#409eff}.el-input-number__decrease.is-disabled,.el-input-number__increase.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-input-number__increase{right:1px;border-radius:0 4px 4px 0;border-left:1px solid #dcdfe6}.el-input-number__decrease{left:1px;border-radius:4px 0 0 4px;border-right:1px solid #dcdfe6}.el-input-number.is-disabled .el-input-number__decrease,.el-input-number.is-disabled .el-input-number__increase{border-color:#e4e7ed;color:#e4e7ed}.el-input-number.is-disabled .el-input-number__decrease:hover,.el-input-number.is-disabled .el-input-number__increase:hover{color:#e4e7ed;cursor:not-allowed}.el-input-number--medium{width:200px;line-height:34px}.el-input-number--medium .el-input-number__decrease,.el-input-number--medium .el-input-number__increase{width:36px;font-size:14px}.el-input-number--medium .el-input__inner{padding-left:43px;padding-right:43px}.el-input-number--small{width:130px;line-height:30px}.el-input-number--small .el-input-number__decrease,.el-input-number--small .el-input-number__increase{width:32px;font-size:13px}.el-input-number--small .el-input-number__decrease [class*=el-icon],.el-input-number--small .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.9);transform:scale(.9)}.el-input-number--small .el-input__inner{padding-left:39px;padding-right:39px}.el-input-number--mini{width:130px;line-height:26px}.el-input-number--mini .el-input-number__decrease,.el-input-number--mini .el-input-number__increase{width:28px;font-size:12px}.el-input-number--mini .el-input-number__decrease [class*=el-icon],.el-input-number--mini .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.el-input-number--mini .el-input__inner{padding-left:35px;padding-right:35px}.el-input-number.is-without-controls .el-input__inner{padding-left:15px;padding-right:15px}.el-input-number.is-controls-right .el-input__inner{padding-left:15px;padding-right:50px}.el-input-number.is-controls-right .el-input-number__decrease,.el-input-number.is-controls-right .el-input-number__increase{height:auto;line-height:19px}.el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{-webkit-transform:scale(.8);transform:scale(.8)}.el-input-number.is-controls-right .el-input-number__increase{border-radius:0 4px 0 0;border-bottom:1px solid #dcdfe6}.el-input-number.is-controls-right .el-input-number__decrease{right:1px;bottom:1px;top:auto;left:auto;border-right:none;border-left:1px solid #dcdfe6;border-radius:0 0 4px}.el-input-number.is-controls-right[class*=medium] [class*=decrease],.el-input-number.is-controls-right[class*=medium] [class*=increase]{line-height:17px}.el-input-number.is-controls-right[class*=small] [class*=decrease],.el-input-number.is-controls-right[class*=small] [class*=increase]{line-height:15px}.el-input-number.is-controls-right[class*=mini] [class*=decrease],.el-input-number.is-controls-right[class*=mini] [class*=increase]{line-height:13px}.el-tooltip__popper{position:absolute;border-radius:4px;padding:10px;z-index:2000;font-size:12px;line-height:1.2;min-width:10px;word-wrap:break-word}.el-tooltip__popper .popper__arrow,.el-tooltip__popper .popper__arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-tooltip__popper .popper__arrow{border-width:6px}.el-tooltip__popper .popper__arrow:after{content:" ";border-width:5px}.el-progress-bar__inner:after,.el-row:after,.el-row:before,.el-slider:after,.el-slider:before,.el-slider__button-wrapper:after,.el-upload-cover:after{content:""}.el-tooltip__popper[x-placement^=top]{margin-bottom:12px}.el-tooltip__popper[x-placement^=top] .popper__arrow{bottom:-6px;border-top-color:#303133;border-bottom-width:0}.el-tooltip__popper[x-placement^=top] .popper__arrow:after{bottom:1px;margin-left:-5px;border-top-color:#303133;border-bottom-width:0}.el-tooltip__popper[x-placement^=bottom]{margin-top:12px}.el-tooltip__popper[x-placement^=bottom] .popper__arrow{top:-6px;border-top-width:0;border-bottom-color:#303133}.el-tooltip__popper[x-placement^=bottom] .popper__arrow:after{top:1px;margin-left:-5px;border-top-width:0;border-bottom-color:#303133}.el-tooltip__popper[x-placement^=right]{margin-left:12px}.el-tooltip__popper[x-placement^=right] .popper__arrow{left:-6px;border-right-color:#303133;border-left-width:0}.el-tooltip__popper[x-placement^=right] .popper__arrow:after{bottom:-5px;left:1px;border-right-color:#303133;border-left-width:0}.el-tooltip__popper[x-placement^=left]{margin-right:12px}.el-tooltip__popper[x-placement^=left] .popper__arrow{right:-6px;border-right-width:0;border-left-color:#303133}.el-tooltip__popper[x-placement^=left] .popper__arrow:after{right:1px;bottom:-5px;margin-left:-5px;border-right-width:0;border-left-color:#303133}.el-tooltip__popper.is-dark{background:#303133;color:#fff}.el-tooltip__popper.is-light{background:#fff;border:1px solid #303133}.el-tooltip__popper.is-light[x-placement^=top] .popper__arrow{border-top-color:#303133}.el-tooltip__popper.is-light[x-placement^=top] .popper__arrow:after{border-top-color:#fff}.el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow{border-bottom-color:#303133}.el-tooltip__popper.is-light[x-placement^=bottom] .popper__arrow:after{border-bottom-color:#fff}.el-tooltip__popper.is-light[x-placement^=left] .popper__arrow{border-left-color:#303133}.el-tooltip__popper.is-light[x-placement^=left] .popper__arrow:after{border-left-color:#fff}.el-tooltip__popper.is-light[x-placement^=right] .popper__arrow{border-right-color:#303133}.el-tooltip__popper.is-light[x-placement^=right] .popper__arrow:after{border-right-color:#fff}.el-slider:after,.el-slider:before{display:table}.el-slider__button-wrapper .el-tooltip,.el-slider__button-wrapper:after{vertical-align:middle;display:inline-block}.el-slider:after{clear:both}.el-slider__runway{width:100%;height:6px;margin:16px 0;background-color:#e4e7ed;border-radius:3px;position:relative;cursor:pointer;vertical-align:middle}.el-slider__runway.show-input{margin-right:160px;width:auto}.el-slider__runway.disabled{cursor:default}.el-slider__runway.disabled .el-slider__bar{background-color:#c0c4cc}.el-slider__runway.disabled .el-slider__button{border-color:#c0c4cc}.el-slider__runway.disabled .el-slider__button-wrapper.dragging,.el-slider__runway.disabled .el-slider__button-wrapper.hover,.el-slider__runway.disabled .el-slider__button-wrapper:hover{cursor:not-allowed}.el-slider__runway.disabled .el-slider__button.dragging,.el-slider__runway.disabled .el-slider__button.hover,.el-slider__runway.disabled .el-slider__button:hover{-webkit-transform:scale(1);transform:scale(1);cursor:not-allowed}.el-slider__button-wrapper,.el-slider__stop{-webkit-transform:translateX(-50%);position:absolute}.el-slider__input{float:right;margin-top:3px;width:130px}.el-slider__input.el-input-number--mini{margin-top:5px}.el-slider__input.el-input-number--medium{margin-top:0}.el-slider__input.el-input-number--large{margin-top:-2px}.el-slider__bar{height:6px;background-color:#409eff;border-top-left-radius:3px;border-bottom-left-radius:3px;position:absolute}.el-slider__button-wrapper{height:36px;width:36px;z-index:1001;top:-15px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:transparent;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;line-height:normal}.el-slider__button-wrapper:after{height:100%}.el-slider__button-wrapper.hover,.el-slider__button-wrapper:hover{cursor:-webkit-grab;cursor:grab}.el-slider__button-wrapper.dragging{cursor:-webkit-grabbing;cursor:grabbing}.el-slider__button{width:16px;height:16px;border:2px solid #409eff;background-color:#fff;border-radius:50%;-webkit-transition:.2s;transition:.2s;user-select:none}.el-image-viewer__btn,.el-slider__button,.el-step__icon-inner{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-slider__button.dragging,.el-slider__button.hover,.el-slider__button:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.el-slider__button.hover,.el-slider__button:hover{cursor:-webkit-grab;cursor:grab}.el-slider__button.dragging{cursor:-webkit-grabbing;cursor:grabbing}.el-slider__stop{height:6px;width:6px;border-radius:100%;background-color:#fff;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.el-slider__marks{top:0;left:12px;width:18px;height:100%}.el-slider__marks-text{position:absolute;-webkit-transform:translateX(-50%);transform:translateX(-50%);font-size:14px;color:#909399;margin-top:15px}.el-slider.is-vertical{position:relative}.el-slider.is-vertical .el-slider__runway{width:6px;height:100%;margin:0 16px}.el-slider.is-vertical .el-slider__bar{width:6px;height:auto;border-radius:0 0 3px 3px}.el-slider.is-vertical .el-slider__button-wrapper{top:auto;left:-15px}.el-slider.is-vertical .el-slider__button-wrapper,.el-slider.is-vertical .el-slider__stop{-webkit-transform:translateY(50%);transform:translateY(50%)}.el-slider.is-vertical.el-slider--with-input{padding-bottom:58px}.el-slider.is-vertical.el-slider--with-input .el-slider__input{overflow:visible;float:none;position:absolute;bottom:22px;width:36px;margin-top:15px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input__inner{text-align:center;padding-left:5px;padding-right:5px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{top:32px;margin-top:-1px;border:1px solid #dcdfe6;line-height:20px;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__decrease{width:18px;right:18px;border-bottom-left-radius:4px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase{width:19px;border-bottom-right-radius:4px}.el-slider.is-vertical.el-slider--with-input .el-slider__input .el-input-number__increase~.el-input .el-input__inner{border-bottom-left-radius:0;border-bottom-right-radius:0}.el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input:hover .el-input-number__increase{border-color:#c0c4cc}.el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__decrease,.el-slider.is-vertical.el-slider--with-input .el-slider__input:active .el-input-number__increase{border-color:#409eff}.el-slider.is-vertical .el-slider__marks-text{margin-top:0;left:15px;-webkit-transform:translateY(50%);transform:translateY(50%)}.el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{position:absolute;z-index:2000;background-color:hsla(0,0%,100%,.9);margin:0;top:0;right:0;bottom:0;left:0;-webkit-transition:opacity .3s;transition:opacity .3s}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:-25px}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:50px;width:50px}.el-loading-spinner{top:50%;margin-top:-21px;width:100%;text-align:center;position:absolute}.el-col-pull-0,.el-col-pull-1,.el-col-pull-2,.el-col-pull-3,.el-col-pull-4,.el-col-pull-5,.el-col-pull-6,.el-col-pull-7,.el-col-pull-8,.el-col-pull-9,.el-col-pull-10,.el-col-pull-11,.el-col-pull-13,.el-col-pull-14,.el-col-pull-15,.el-col-pull-16,.el-col-pull-17,.el-col-pull-18,.el-col-pull-19,.el-col-pull-20,.el-col-pull-21,.el-col-pull-22,.el-col-pull-23,.el-col-pull-24,.el-col-push-0,.el-col-push-1,.el-col-push-2,.el-col-push-3,.el-col-push-4,.el-col-push-5,.el-col-push-6,.el-col-push-7,.el-col-push-8,.el-col-push-9,.el-col-push-10,.el-col-push-11,.el-col-push-12,.el-col-push-13,.el-col-push-14,.el-col-push-15,.el-col-push-16,.el-col-push-17,.el-col-push-18,.el-col-push-19,.el-col-push-20,.el-col-push-21,.el-col-push-22,.el-col-push-23,.el-col-push-24,.el-row{position:relative}.el-loading-spinner .el-loading-text{color:#409eff;margin:3px 0;font-size:14px}.el-loading-spinner .circular{height:42px;width:42px;-webkit-animation:loading-rotate 2s linear infinite;animation:loading-rotate 2s linear infinite}.el-loading-spinner .path{-webkit-animation:loading-dash 1.5s ease-in-out infinite;animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:#409eff;stroke-linecap:round}.el-loading-spinner i{color:#409eff}@-webkit-keyframes loading-rotate{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes loading-rotate{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}to{stroke-dasharray:90,150;stroke-dashoffset:-120px}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}to{stroke-dasharray:90,150;stroke-dashoffset:-120px}}.el-row{-webkit-box-sizing:border-box;box-sizing:border-box}.el-row:after,.el-row:before{display:table}.el-row:after{clear:both}.el-row--flex{display:-webkit-box;display:-ms-flexbox;display:flex}.el-col-0,.el-row--flex:after,.el-row--flex:before{display:none}.el-row--flex.is-justify-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-row--flex.is-justify-end{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.el-row--flex.is-justify-space-between{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-row--flex.is-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.el-row--flex.is-align-middle{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-row--flex.is-align-bottom{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}[class*=el-col-]{float:left;-webkit-box-sizing:border-box;box-sizing:border-box}.el-upload--picture-card,.el-upload-dragger{-webkit-box-sizing:border-box;cursor:pointer}.el-col-0{width:0}.el-col-offset-0{margin-left:0}.el-col-pull-0{right:0}.el-col-push-0{left:0}.el-col-1{width:4.16667%}.el-col-offset-1{margin-left:4.16667%}.el-col-pull-1{right:4.16667%}.el-col-push-1{left:4.16667%}.el-col-2{width:8.33333%}.el-col-offset-2{margin-left:8.33333%}.el-col-pull-2{right:8.33333%}.el-col-push-2{left:8.33333%}.el-col-3{width:12.5%}.el-col-offset-3{margin-left:12.5%}.el-col-pull-3{right:12.5%}.el-col-push-3{left:12.5%}.el-col-4{width:16.66667%}.el-col-offset-4{margin-left:16.66667%}.el-col-pull-4{right:16.66667%}.el-col-push-4{left:16.66667%}.el-col-5{width:20.83333%}.el-col-offset-5{margin-left:20.83333%}.el-col-pull-5{right:20.83333%}.el-col-push-5{left:20.83333%}.el-col-6{width:25%}.el-col-offset-6{margin-left:25%}.el-col-pull-6{right:25%}.el-col-push-6{left:25%}.el-col-7{width:29.16667%}.el-col-offset-7{margin-left:29.16667%}.el-col-pull-7{right:29.16667%}.el-col-push-7{left:29.16667%}.el-col-8{width:33.33333%}.el-col-offset-8{margin-left:33.33333%}.el-col-pull-8{right:33.33333%}.el-col-push-8{left:33.33333%}.el-col-9{width:37.5%}.el-col-offset-9{margin-left:37.5%}.el-col-pull-9{right:37.5%}.el-col-push-9{left:37.5%}.el-col-10{width:41.66667%}.el-col-offset-10{margin-left:41.66667%}.el-col-pull-10{right:41.66667%}.el-col-push-10{left:41.66667%}.el-col-11{width:45.83333%}.el-col-offset-11{margin-left:45.83333%}.el-col-pull-11{right:45.83333%}.el-col-push-11{left:45.83333%}.el-col-12{width:50%}.el-col-offset-12{margin-left:50%}.el-col-pull-12{position:relative;right:50%}.el-col-push-12{left:50%}.el-col-13{width:54.16667%}.el-col-offset-13{margin-left:54.16667%}.el-col-pull-13{right:54.16667%}.el-col-push-13{left:54.16667%}.el-col-14{width:58.33333%}.el-col-offset-14{margin-left:58.33333%}.el-col-pull-14{right:58.33333%}.el-col-push-14{left:58.33333%}.el-col-15{width:62.5%}.el-col-offset-15{margin-left:62.5%}.el-col-pull-15{right:62.5%}.el-col-push-15{left:62.5%}.el-col-16{width:66.66667%}.el-col-offset-16{margin-left:66.66667%}.el-col-pull-16{right:66.66667%}.el-col-push-16{left:66.66667%}.el-col-17{width:70.83333%}.el-col-offset-17{margin-left:70.83333%}.el-col-pull-17{right:70.83333%}.el-col-push-17{left:70.83333%}.el-col-18{width:75%}.el-col-offset-18{margin-left:75%}.el-col-pull-18{right:75%}.el-col-push-18{left:75%}.el-col-19{width:79.16667%}.el-col-offset-19{margin-left:79.16667%}.el-col-pull-19{right:79.16667%}.el-col-push-19{left:79.16667%}.el-col-20{width:83.33333%}.el-col-offset-20{margin-left:83.33333%}.el-col-pull-20{right:83.33333%}.el-col-push-20{left:83.33333%}.el-col-21{width:87.5%}.el-col-offset-21{margin-left:87.5%}.el-col-pull-21{right:87.5%}.el-col-push-21{left:87.5%}.el-col-22{width:91.66667%}.el-col-offset-22{margin-left:91.66667%}.el-col-pull-22{right:91.66667%}.el-col-push-22{left:91.66667%}.el-col-23{width:95.83333%}.el-col-offset-23{margin-left:95.83333%}.el-col-pull-23{right:95.83333%}.el-col-push-23{left:95.83333%}.el-col-24{width:100%}.el-col-offset-24{margin-left:100%}.el-col-pull-24{right:100%}.el-col-push-24{left:100%}@media only screen and (max-width:767px){.el-col-xs-0{display:none;width:0}.el-col-xs-offset-0{margin-left:0}.el-col-xs-pull-0{position:relative;right:0}.el-col-xs-push-0{position:relative;left:0}.el-col-xs-1{width:4.16667%}.el-col-xs-offset-1{margin-left:4.16667%}.el-col-xs-pull-1{position:relative;right:4.16667%}.el-col-xs-push-1{position:relative;left:4.16667%}.el-col-xs-2{width:8.33333%}.el-col-xs-offset-2{margin-left:8.33333%}.el-col-xs-pull-2{position:relative;right:8.33333%}.el-col-xs-push-2{position:relative;left:8.33333%}.el-col-xs-3{width:12.5%}.el-col-xs-offset-3{margin-left:12.5%}.el-col-xs-pull-3{position:relative;right:12.5%}.el-col-xs-push-3{position:relative;left:12.5%}.el-col-xs-4{width:16.66667%}.el-col-xs-offset-4{margin-left:16.66667%}.el-col-xs-pull-4{position:relative;right:16.66667%}.el-col-xs-push-4{position:relative;left:16.66667%}.el-col-xs-5{width:20.83333%}.el-col-xs-offset-5{margin-left:20.83333%}.el-col-xs-pull-5{position:relative;right:20.83333%}.el-col-xs-push-5{position:relative;left:20.83333%}.el-col-xs-6{width:25%}.el-col-xs-offset-6{margin-left:25%}.el-col-xs-pull-6{position:relative;right:25%}.el-col-xs-push-6{position:relative;left:25%}.el-col-xs-7{width:29.16667%}.el-col-xs-offset-7{margin-left:29.16667%}.el-col-xs-pull-7{position:relative;right:29.16667%}.el-col-xs-push-7{position:relative;left:29.16667%}.el-col-xs-8{width:33.33333%}.el-col-xs-offset-8{margin-left:33.33333%}.el-col-xs-pull-8{position:relative;right:33.33333%}.el-col-xs-push-8{position:relative;left:33.33333%}.el-col-xs-9{width:37.5%}.el-col-xs-offset-9{margin-left:37.5%}.el-col-xs-pull-9{position:relative;right:37.5%}.el-col-xs-push-9{position:relative;left:37.5%}.el-col-xs-10{width:41.66667%}.el-col-xs-offset-10{margin-left:41.66667%}.el-col-xs-pull-10{position:relative;right:41.66667%}.el-col-xs-push-10{position:relative;left:41.66667%}.el-col-xs-11{width:45.83333%}.el-col-xs-offset-11{margin-left:45.83333%}.el-col-xs-pull-11{position:relative;right:45.83333%}.el-col-xs-push-11{position:relative;left:45.83333%}.el-col-xs-12{width:50%}.el-col-xs-offset-12{margin-left:50%}.el-col-xs-pull-12{position:relative;right:50%}.el-col-xs-push-12{position:relative;left:50%}.el-col-xs-13{width:54.16667%}.el-col-xs-offset-13{margin-left:54.16667%}.el-col-xs-pull-13{position:relative;right:54.16667%}.el-col-xs-push-13{position:relative;left:54.16667%}.el-col-xs-14{width:58.33333%}.el-col-xs-offset-14{margin-left:58.33333%}.el-col-xs-pull-14{position:relative;right:58.33333%}.el-col-xs-push-14{position:relative;left:58.33333%}.el-col-xs-15{width:62.5%}.el-col-xs-offset-15{margin-left:62.5%}.el-col-xs-pull-15{position:relative;right:62.5%}.el-col-xs-push-15{position:relative;left:62.5%}.el-col-xs-16{width:66.66667%}.el-col-xs-offset-16{margin-left:66.66667%}.el-col-xs-pull-16{position:relative;right:66.66667%}.el-col-xs-push-16{position:relative;left:66.66667%}.el-col-xs-17{width:70.83333%}.el-col-xs-offset-17{margin-left:70.83333%}.el-col-xs-pull-17{position:relative;right:70.83333%}.el-col-xs-push-17{position:relative;left:70.83333%}.el-col-xs-18{width:75%}.el-col-xs-offset-18{margin-left:75%}.el-col-xs-pull-18{position:relative;right:75%}.el-col-xs-push-18{position:relative;left:75%}.el-col-xs-19{width:79.16667%}.el-col-xs-offset-19{margin-left:79.16667%}.el-col-xs-pull-19{position:relative;right:79.16667%}.el-col-xs-push-19{position:relative;left:79.16667%}.el-col-xs-20{width:83.33333%}.el-col-xs-offset-20{margin-left:83.33333%}.el-col-xs-pull-20{position:relative;right:83.33333%}.el-col-xs-push-20{position:relative;left:83.33333%}.el-col-xs-21{width:87.5%}.el-col-xs-offset-21{margin-left:87.5%}.el-col-xs-pull-21{position:relative;right:87.5%}.el-col-xs-push-21{position:relative;left:87.5%}.el-col-xs-22{width:91.66667%}.el-col-xs-offset-22{margin-left:91.66667%}.el-col-xs-pull-22{position:relative;right:91.66667%}.el-col-xs-push-22{position:relative;left:91.66667%}.el-col-xs-23{width:95.83333%}.el-col-xs-offset-23{margin-left:95.83333%}.el-col-xs-pull-23{position:relative;right:95.83333%}.el-col-xs-push-23{position:relative;left:95.83333%}.el-col-xs-24{width:100%}.el-col-xs-offset-24{margin-left:100%}.el-col-xs-pull-24{position:relative;right:100%}.el-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.el-col-sm-0{display:none;width:0}.el-col-sm-offset-0{margin-left:0}.el-col-sm-pull-0{position:relative;right:0}.el-col-sm-push-0{position:relative;left:0}.el-col-sm-1{width:4.16667%}.el-col-sm-offset-1{margin-left:4.16667%}.el-col-sm-pull-1{position:relative;right:4.16667%}.el-col-sm-push-1{position:relative;left:4.16667%}.el-col-sm-2{width:8.33333%}.el-col-sm-offset-2{margin-left:8.33333%}.el-col-sm-pull-2{position:relative;right:8.33333%}.el-col-sm-push-2{position:relative;left:8.33333%}.el-col-sm-3{width:12.5%}.el-col-sm-offset-3{margin-left:12.5%}.el-col-sm-pull-3{position:relative;right:12.5%}.el-col-sm-push-3{position:relative;left:12.5%}.el-col-sm-4{width:16.66667%}.el-col-sm-offset-4{margin-left:16.66667%}.el-col-sm-pull-4{position:relative;right:16.66667%}.el-col-sm-push-4{position:relative;left:16.66667%}.el-col-sm-5{width:20.83333%}.el-col-sm-offset-5{margin-left:20.83333%}.el-col-sm-pull-5{position:relative;right:20.83333%}.el-col-sm-push-5{position:relative;left:20.83333%}.el-col-sm-6{width:25%}.el-col-sm-offset-6{margin-left:25%}.el-col-sm-pull-6{position:relative;right:25%}.el-col-sm-push-6{position:relative;left:25%}.el-col-sm-7{width:29.16667%}.el-col-sm-offset-7{margin-left:29.16667%}.el-col-sm-pull-7{position:relative;right:29.16667%}.el-col-sm-push-7{position:relative;left:29.16667%}.el-col-sm-8{width:33.33333%}.el-col-sm-offset-8{margin-left:33.33333%}.el-col-sm-pull-8{position:relative;right:33.33333%}.el-col-sm-push-8{position:relative;left:33.33333%}.el-col-sm-9{width:37.5%}.el-col-sm-offset-9{margin-left:37.5%}.el-col-sm-pull-9{position:relative;right:37.5%}.el-col-sm-push-9{position:relative;left:37.5%}.el-col-sm-10{width:41.66667%}.el-col-sm-offset-10{margin-left:41.66667%}.el-col-sm-pull-10{position:relative;right:41.66667%}.el-col-sm-push-10{position:relative;left:41.66667%}.el-col-sm-11{width:45.83333%}.el-col-sm-offset-11{margin-left:45.83333%}.el-col-sm-pull-11{position:relative;right:45.83333%}.el-col-sm-push-11{position:relative;left:45.83333%}.el-col-sm-12{width:50%}.el-col-sm-offset-12{margin-left:50%}.el-col-sm-pull-12{position:relative;right:50%}.el-col-sm-push-12{position:relative;left:50%}.el-col-sm-13{width:54.16667%}.el-col-sm-offset-13{margin-left:54.16667%}.el-col-sm-pull-13{position:relative;right:54.16667%}.el-col-sm-push-13{position:relative;left:54.16667%}.el-col-sm-14{width:58.33333%}.el-col-sm-offset-14{margin-left:58.33333%}.el-col-sm-pull-14{position:relative;right:58.33333%}.el-col-sm-push-14{position:relative;left:58.33333%}.el-col-sm-15{width:62.5%}.el-col-sm-offset-15{margin-left:62.5%}.el-col-sm-pull-15{position:relative;right:62.5%}.el-col-sm-push-15{position:relative;left:62.5%}.el-col-sm-16{width:66.66667%}.el-col-sm-offset-16{margin-left:66.66667%}.el-col-sm-pull-16{position:relative;right:66.66667%}.el-col-sm-push-16{position:relative;left:66.66667%}.el-col-sm-17{width:70.83333%}.el-col-sm-offset-17{margin-left:70.83333%}.el-col-sm-pull-17{position:relative;right:70.83333%}.el-col-sm-push-17{position:relative;left:70.83333%}.el-col-sm-18{width:75%}.el-col-sm-offset-18{margin-left:75%}.el-col-sm-pull-18{position:relative;right:75%}.el-col-sm-push-18{position:relative;left:75%}.el-col-sm-19{width:79.16667%}.el-col-sm-offset-19{margin-left:79.16667%}.el-col-sm-pull-19{position:relative;right:79.16667%}.el-col-sm-push-19{position:relative;left:79.16667%}.el-col-sm-20{width:83.33333%}.el-col-sm-offset-20{margin-left:83.33333%}.el-col-sm-pull-20{position:relative;right:83.33333%}.el-col-sm-push-20{position:relative;left:83.33333%}.el-col-sm-21{width:87.5%}.el-col-sm-offset-21{margin-left:87.5%}.el-col-sm-pull-21{position:relative;right:87.5%}.el-col-sm-push-21{position:relative;left:87.5%}.el-col-sm-22{width:91.66667%}.el-col-sm-offset-22{margin-left:91.66667%}.el-col-sm-pull-22{position:relative;right:91.66667%}.el-col-sm-push-22{position:relative;left:91.66667%}.el-col-sm-23{width:95.83333%}.el-col-sm-offset-23{margin-left:95.83333%}.el-col-sm-pull-23{position:relative;right:95.83333%}.el-col-sm-push-23{position:relative;left:95.83333%}.el-col-sm-24{width:100%}.el-col-sm-offset-24{margin-left:100%}.el-col-sm-pull-24{position:relative;right:100%}.el-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.el-col-md-0{display:none;width:0}.el-col-md-offset-0{margin-left:0}.el-col-md-pull-0{position:relative;right:0}.el-col-md-push-0{position:relative;left:0}.el-col-md-1{width:4.16667%}.el-col-md-offset-1{margin-left:4.16667%}.el-col-md-pull-1{position:relative;right:4.16667%}.el-col-md-push-1{position:relative;left:4.16667%}.el-col-md-2{width:8.33333%}.el-col-md-offset-2{margin-left:8.33333%}.el-col-md-pull-2{position:relative;right:8.33333%}.el-col-md-push-2{position:relative;left:8.33333%}.el-col-md-3{width:12.5%}.el-col-md-offset-3{margin-left:12.5%}.el-col-md-pull-3{position:relative;right:12.5%}.el-col-md-push-3{position:relative;left:12.5%}.el-col-md-4{width:16.66667%}.el-col-md-offset-4{margin-left:16.66667%}.el-col-md-pull-4{position:relative;right:16.66667%}.el-col-md-push-4{position:relative;left:16.66667%}.el-col-md-5{width:20.83333%}.el-col-md-offset-5{margin-left:20.83333%}.el-col-md-pull-5{position:relative;right:20.83333%}.el-col-md-push-5{position:relative;left:20.83333%}.el-col-md-6{width:25%}.el-col-md-offset-6{margin-left:25%}.el-col-md-pull-6{position:relative;right:25%}.el-col-md-push-6{position:relative;left:25%}.el-col-md-7{width:29.16667%}.el-col-md-offset-7{margin-left:29.16667%}.el-col-md-pull-7{position:relative;right:29.16667%}.el-col-md-push-7{position:relative;left:29.16667%}.el-col-md-8{width:33.33333%}.el-col-md-offset-8{margin-left:33.33333%}.el-col-md-pull-8{position:relative;right:33.33333%}.el-col-md-push-8{position:relative;left:33.33333%}.el-col-md-9{width:37.5%}.el-col-md-offset-9{margin-left:37.5%}.el-col-md-pull-9{position:relative;right:37.5%}.el-col-md-push-9{position:relative;left:37.5%}.el-col-md-10{width:41.66667%}.el-col-md-offset-10{margin-left:41.66667%}.el-col-md-pull-10{position:relative;right:41.66667%}.el-col-md-push-10{position:relative;left:41.66667%}.el-col-md-11{width:45.83333%}.el-col-md-offset-11{margin-left:45.83333%}.el-col-md-pull-11{position:relative;right:45.83333%}.el-col-md-push-11{position:relative;left:45.83333%}.el-col-md-12{width:50%}.el-col-md-offset-12{margin-left:50%}.el-col-md-pull-12{position:relative;right:50%}.el-col-md-push-12{position:relative;left:50%}.el-col-md-13{width:54.16667%}.el-col-md-offset-13{margin-left:54.16667%}.el-col-md-pull-13{position:relative;right:54.16667%}.el-col-md-push-13{position:relative;left:54.16667%}.el-col-md-14{width:58.33333%}.el-col-md-offset-14{margin-left:58.33333%}.el-col-md-pull-14{position:relative;right:58.33333%}.el-col-md-push-14{position:relative;left:58.33333%}.el-col-md-15{width:62.5%}.el-col-md-offset-15{margin-left:62.5%}.el-col-md-pull-15{position:relative;right:62.5%}.el-col-md-push-15{position:relative;left:62.5%}.el-col-md-16{width:66.66667%}.el-col-md-offset-16{margin-left:66.66667%}.el-col-md-pull-16{position:relative;right:66.66667%}.el-col-md-push-16{position:relative;left:66.66667%}.el-col-md-17{width:70.83333%}.el-col-md-offset-17{margin-left:70.83333%}.el-col-md-pull-17{position:relative;right:70.83333%}.el-col-md-push-17{position:relative;left:70.83333%}.el-col-md-18{width:75%}.el-col-md-offset-18{margin-left:75%}.el-col-md-pull-18{position:relative;right:75%}.el-col-md-push-18{position:relative;left:75%}.el-col-md-19{width:79.16667%}.el-col-md-offset-19{margin-left:79.16667%}.el-col-md-pull-19{position:relative;right:79.16667%}.el-col-md-push-19{position:relative;left:79.16667%}.el-col-md-20{width:83.33333%}.el-col-md-offset-20{margin-left:83.33333%}.el-col-md-pull-20{position:relative;right:83.33333%}.el-col-md-push-20{position:relative;left:83.33333%}.el-col-md-21{width:87.5%}.el-col-md-offset-21{margin-left:87.5%}.el-col-md-pull-21{position:relative;right:87.5%}.el-col-md-push-21{position:relative;left:87.5%}.el-col-md-22{width:91.66667%}.el-col-md-offset-22{margin-left:91.66667%}.el-col-md-pull-22{position:relative;right:91.66667%}.el-col-md-push-22{position:relative;left:91.66667%}.el-col-md-23{width:95.83333%}.el-col-md-offset-23{margin-left:95.83333%}.el-col-md-pull-23{position:relative;right:95.83333%}.el-col-md-push-23{position:relative;left:95.83333%}.el-col-md-24{width:100%}.el-col-md-offset-24{margin-left:100%}.el-col-md-pull-24{position:relative;right:100%}.el-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.el-col-lg-0{display:none;width:0}.el-col-lg-offset-0{margin-left:0}.el-col-lg-pull-0{position:relative;right:0}.el-col-lg-push-0{position:relative;left:0}.el-col-lg-1{width:4.16667%}.el-col-lg-offset-1{margin-left:4.16667%}.el-col-lg-pull-1{position:relative;right:4.16667%}.el-col-lg-push-1{position:relative;left:4.16667%}.el-col-lg-2{width:8.33333%}.el-col-lg-offset-2{margin-left:8.33333%}.el-col-lg-pull-2{position:relative;right:8.33333%}.el-col-lg-push-2{position:relative;left:8.33333%}.el-col-lg-3{width:12.5%}.el-col-lg-offset-3{margin-left:12.5%}.el-col-lg-pull-3{position:relative;right:12.5%}.el-col-lg-push-3{position:relative;left:12.5%}.el-col-lg-4{width:16.66667%}.el-col-lg-offset-4{margin-left:16.66667%}.el-col-lg-pull-4{position:relative;right:16.66667%}.el-col-lg-push-4{position:relative;left:16.66667%}.el-col-lg-5{width:20.83333%}.el-col-lg-offset-5{margin-left:20.83333%}.el-col-lg-pull-5{position:relative;right:20.83333%}.el-col-lg-push-5{position:relative;left:20.83333%}.el-col-lg-6{width:25%}.el-col-lg-offset-6{margin-left:25%}.el-col-lg-pull-6{position:relative;right:25%}.el-col-lg-push-6{position:relative;left:25%}.el-col-lg-7{width:29.16667%}.el-col-lg-offset-7{margin-left:29.16667%}.el-col-lg-pull-7{position:relative;right:29.16667%}.el-col-lg-push-7{position:relative;left:29.16667%}.el-col-lg-8{width:33.33333%}.el-col-lg-offset-8{margin-left:33.33333%}.el-col-lg-pull-8{position:relative;right:33.33333%}.el-col-lg-push-8{position:relative;left:33.33333%}.el-col-lg-9{width:37.5%}.el-col-lg-offset-9{margin-left:37.5%}.el-col-lg-pull-9{position:relative;right:37.5%}.el-col-lg-push-9{position:relative;left:37.5%}.el-col-lg-10{width:41.66667%}.el-col-lg-offset-10{margin-left:41.66667%}.el-col-lg-pull-10{position:relative;right:41.66667%}.el-col-lg-push-10{position:relative;left:41.66667%}.el-col-lg-11{width:45.83333%}.el-col-lg-offset-11{margin-left:45.83333%}.el-col-lg-pull-11{position:relative;right:45.83333%}.el-col-lg-push-11{position:relative;left:45.83333%}.el-col-lg-12{width:50%}.el-col-lg-offset-12{margin-left:50%}.el-col-lg-pull-12{position:relative;right:50%}.el-col-lg-push-12{position:relative;left:50%}.el-col-lg-13{width:54.16667%}.el-col-lg-offset-13{margin-left:54.16667%}.el-col-lg-pull-13{position:relative;right:54.16667%}.el-col-lg-push-13{position:relative;left:54.16667%}.el-col-lg-14{width:58.33333%}.el-col-lg-offset-14{margin-left:58.33333%}.el-col-lg-pull-14{position:relative;right:58.33333%}.el-col-lg-push-14{position:relative;left:58.33333%}.el-col-lg-15{width:62.5%}.el-col-lg-offset-15{margin-left:62.5%}.el-col-lg-pull-15{position:relative;right:62.5%}.el-col-lg-push-15{position:relative;left:62.5%}.el-col-lg-16{width:66.66667%}.el-col-lg-offset-16{margin-left:66.66667%}.el-col-lg-pull-16{position:relative;right:66.66667%}.el-col-lg-push-16{position:relative;left:66.66667%}.el-col-lg-17{width:70.83333%}.el-col-lg-offset-17{margin-left:70.83333%}.el-col-lg-pull-17{position:relative;right:70.83333%}.el-col-lg-push-17{position:relative;left:70.83333%}.el-col-lg-18{width:75%}.el-col-lg-offset-18{margin-left:75%}.el-col-lg-pull-18{position:relative;right:75%}.el-col-lg-push-18{position:relative;left:75%}.el-col-lg-19{width:79.16667%}.el-col-lg-offset-19{margin-left:79.16667%}.el-col-lg-pull-19{position:relative;right:79.16667%}.el-col-lg-push-19{position:relative;left:79.16667%}.el-col-lg-20{width:83.33333%}.el-col-lg-offset-20{margin-left:83.33333%}.el-col-lg-pull-20{position:relative;right:83.33333%}.el-col-lg-push-20{position:relative;left:83.33333%}.el-col-lg-21{width:87.5%}.el-col-lg-offset-21{margin-left:87.5%}.el-col-lg-pull-21{position:relative;right:87.5%}.el-col-lg-push-21{position:relative;left:87.5%}.el-col-lg-22{width:91.66667%}.el-col-lg-offset-22{margin-left:91.66667%}.el-col-lg-pull-22{position:relative;right:91.66667%}.el-col-lg-push-22{position:relative;left:91.66667%}.el-col-lg-23{width:95.83333%}.el-col-lg-offset-23{margin-left:95.83333%}.el-col-lg-pull-23{position:relative;right:95.83333%}.el-col-lg-push-23{position:relative;left:95.83333%}.el-col-lg-24{width:100%}.el-col-lg-offset-24{margin-left:100%}.el-col-lg-pull-24{position:relative;right:100%}.el-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.el-col-xl-0{display:none;width:0}.el-col-xl-offset-0{margin-left:0}.el-col-xl-pull-0{position:relative;right:0}.el-col-xl-push-0{position:relative;left:0}.el-col-xl-1{width:4.16667%}.el-col-xl-offset-1{margin-left:4.16667%}.el-col-xl-pull-1{position:relative;right:4.16667%}.el-col-xl-push-1{position:relative;left:4.16667%}.el-col-xl-2{width:8.33333%}.el-col-xl-offset-2{margin-left:8.33333%}.el-col-xl-pull-2{position:relative;right:8.33333%}.el-col-xl-push-2{position:relative;left:8.33333%}.el-col-xl-3{width:12.5%}.el-col-xl-offset-3{margin-left:12.5%}.el-col-xl-pull-3{position:relative;right:12.5%}.el-col-xl-push-3{position:relative;left:12.5%}.el-col-xl-4{width:16.66667%}.el-col-xl-offset-4{margin-left:16.66667%}.el-col-xl-pull-4{position:relative;right:16.66667%}.el-col-xl-push-4{position:relative;left:16.66667%}.el-col-xl-5{width:20.83333%}.el-col-xl-offset-5{margin-left:20.83333%}.el-col-xl-pull-5{position:relative;right:20.83333%}.el-col-xl-push-5{position:relative;left:20.83333%}.el-col-xl-6{width:25%}.el-col-xl-offset-6{margin-left:25%}.el-col-xl-pull-6{position:relative;right:25%}.el-col-xl-push-6{position:relative;left:25%}.el-col-xl-7{width:29.16667%}.el-col-xl-offset-7{margin-left:29.16667%}.el-col-xl-pull-7{position:relative;right:29.16667%}.el-col-xl-push-7{position:relative;left:29.16667%}.el-col-xl-8{width:33.33333%}.el-col-xl-offset-8{margin-left:33.33333%}.el-col-xl-pull-8{position:relative;right:33.33333%}.el-col-xl-push-8{position:relative;left:33.33333%}.el-col-xl-9{width:37.5%}.el-col-xl-offset-9{margin-left:37.5%}.el-col-xl-pull-9{position:relative;right:37.5%}.el-col-xl-push-9{position:relative;left:37.5%}.el-col-xl-10{width:41.66667%}.el-col-xl-offset-10{margin-left:41.66667%}.el-col-xl-pull-10{position:relative;right:41.66667%}.el-col-xl-push-10{position:relative;left:41.66667%}.el-col-xl-11{width:45.83333%}.el-col-xl-offset-11{margin-left:45.83333%}.el-col-xl-pull-11{position:relative;right:45.83333%}.el-col-xl-push-11{position:relative;left:45.83333%}.el-col-xl-12{width:50%}.el-col-xl-offset-12{margin-left:50%}.el-col-xl-pull-12{position:relative;right:50%}.el-col-xl-push-12{position:relative;left:50%}.el-col-xl-13{width:54.16667%}.el-col-xl-offset-13{margin-left:54.16667%}.el-col-xl-pull-13{position:relative;right:54.16667%}.el-col-xl-push-13{position:relative;left:54.16667%}.el-col-xl-14{width:58.33333%}.el-col-xl-offset-14{margin-left:58.33333%}.el-col-xl-pull-14{position:relative;right:58.33333%}.el-col-xl-push-14{position:relative;left:58.33333%}.el-col-xl-15{width:62.5%}.el-col-xl-offset-15{margin-left:62.5%}.el-col-xl-pull-15{position:relative;right:62.5%}.el-col-xl-push-15{position:relative;left:62.5%}.el-col-xl-16{width:66.66667%}.el-col-xl-offset-16{margin-left:66.66667%}.el-col-xl-pull-16{position:relative;right:66.66667%}.el-col-xl-push-16{position:relative;left:66.66667%}.el-col-xl-17{width:70.83333%}.el-col-xl-offset-17{margin-left:70.83333%}.el-col-xl-pull-17{position:relative;right:70.83333%}.el-col-xl-push-17{position:relative;left:70.83333%}.el-col-xl-18{width:75%}.el-col-xl-offset-18{margin-left:75%}.el-col-xl-pull-18{position:relative;right:75%}.el-col-xl-push-18{position:relative;left:75%}.el-col-xl-19{width:79.16667%}.el-col-xl-offset-19{margin-left:79.16667%}.el-col-xl-pull-19{position:relative;right:79.16667%}.el-col-xl-push-19{position:relative;left:79.16667%}.el-col-xl-20{width:83.33333%}.el-col-xl-offset-20{margin-left:83.33333%}.el-col-xl-pull-20{position:relative;right:83.33333%}.el-col-xl-push-20{position:relative;left:83.33333%}.el-col-xl-21{width:87.5%}.el-col-xl-offset-21{margin-left:87.5%}.el-col-xl-pull-21{position:relative;right:87.5%}.el-col-xl-push-21{position:relative;left:87.5%}.el-col-xl-22{width:91.66667%}.el-col-xl-offset-22{margin-left:91.66667%}.el-col-xl-pull-22{position:relative;right:91.66667%}.el-col-xl-push-22{position:relative;left:91.66667%}.el-col-xl-23{width:95.83333%}.el-col-xl-offset-23{margin-left:95.83333%}.el-col-xl-pull-23{position:relative;right:95.83333%}.el-col-xl-push-23{position:relative;left:95.83333%}.el-col-xl-24{width:100%}.el-col-xl-offset-24{margin-left:100%}.el-col-xl-pull-24{position:relative;right:100%}.el-col-xl-push-24{position:relative;left:100%}}@-webkit-keyframes progress{0%{background-position:0 0}to{background-position:32px 0}}.el-upload{display:inline-block;text-align:center;cursor:pointer;outline:0}.el-upload__input{display:none}.el-upload__tip{font-size:12px;color:#606266;margin-top:7px}.el-upload iframe{position:absolute;z-index:-1;top:0;left:0;opacity:0;filter:alpha(opacity=0)}.el-upload--picture-card{background-color:#fbfdff;border:1px dashed #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;line-height:146px;vertical-align:top}.el-upload--picture-card i{font-size:28px;color:#8c939d}.el-upload--picture-card:hover,.el-upload:focus{border-color:#409eff;color:#409eff}.el-upload:focus .el-upload-dragger{border-color:#409eff}.el-upload-dragger{background-color:#fff;border:1px dashed #d9d9d9;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:360px;height:180px;text-align:center;position:relative;overflow:hidden}.el-upload-dragger .el-icon-upload{font-size:67px;color:#c0c4cc;margin:40px 0 16px;line-height:50px}.el-upload-dragger+.el-upload__tip{text-align:center}.el-upload-dragger~.el-upload__files{border-top:1px solid #dcdfe6;margin-top:7px;padding-top:5px}.el-upload-dragger .el-upload__text{color:#606266;font-size:14px;text-align:center}.el-upload-dragger .el-upload__text em{color:#409eff;font-style:normal}.el-upload-dragger:hover{border-color:#409eff}.el-upload-dragger.is-dragover{background-color:rgba(32,159,255,.06);border:2px dashed #409eff}.el-upload-list{margin:0;padding:0;list-style:none}.el-upload-list__item{-webkit-transition:all .5s cubic-bezier(.55,0,.1,1);transition:all .5s cubic-bezier(.55,0,.1,1);font-size:14px;color:#606266;line-height:1.8;margin-top:5px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:4px;width:100%}.el-upload-list__item .el-progress{position:absolute;top:20px;width:100%}.el-upload-list__item .el-progress__text{position:absolute;right:0;top:-13px}.el-upload-list__item .el-progress-bar{margin-right:0;padding-right:0}.el-upload-list__item:first-child{margin-top:10px}.el-upload-list__item .el-icon-upload-success{color:#67c23a}.el-upload-list__item .el-icon-close{display:none;position:absolute;top:5px;right:5px;cursor:pointer;opacity:.75;color:#606266}.el-upload-list__item .el-icon-close:hover{opacity:1}.el-upload-list__item .el-icon-close-tip{display:none;position:absolute;top:5px;right:5px;font-size:12px;cursor:pointer;opacity:1;color:#409eff}.el-upload-list__item:hover{background-color:#f5f7fa}.el-upload-list__item:hover .el-icon-close{display:inline-block}.el-upload-list__item:hover .el-progress__text{display:none}.el-upload-list__item.is-success .el-upload-list__item-status-label{display:block}.el-upload-list__item.is-success .el-upload-list__item-name:focus,.el-upload-list__item.is-success .el-upload-list__item-name:hover{color:#409eff;cursor:pointer}.el-upload-list__item.is-success:focus:not(:hover) .el-icon-close-tip{display:inline-block}.el-upload-list__item.is-success:active .el-icon-close-tip,.el-upload-list__item.is-success:focus .el-upload-list__item-status-label,.el-upload-list__item.is-success:hover .el-upload-list__item-status-label,.el-upload-list__item.is-success:not(.focusing):focus .el-icon-close-tip{display:none}.el-upload-list.is-disabled .el-upload-list__item:hover .el-upload-list__item-status-label{display:block}.el-upload-list__item-name{color:#606266;display:block;margin-right:40px;overflow:hidden;padding-left:4px;text-overflow:ellipsis;-webkit-transition:color .3s;transition:color .3s;white-space:nowrap}.el-upload-list__item-name [class^=el-icon]{height:100%;margin-right:7px;color:#909399;line-height:inherit}.el-upload-list__item-status-label{position:absolute;right:5px;top:0;line-height:inherit;display:none}.el-upload-list__item-delete{position:absolute;right:10px;top:0;font-size:12px;color:#606266;display:none}.el-upload-list__item-delete:hover{color:#409eff}.el-upload-list--picture-card{margin:0;display:inline;vertical-align:top}.el-upload-list--picture-card .el-upload-list__item{overflow:hidden;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;width:148px;height:148px;margin:0 8px 8px 0;display:inline-block}.el-upload-list--picture-card .el-upload-list__item .el-icon-check,.el-upload-list--picture-card .el-upload-list__item .el-icon-circle-check{color:#fff}.el-upload-list--picture-card .el-upload-list__item .el-icon-close,.el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label{display:none}.el-upload-list--picture-card .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture-card .el-upload-list__item-name{display:none}.el-upload-list--picture-card .el-upload-list__item-thumbnail{width:100%;height:100%}.el-upload-list--picture-card .el-upload-list__item-status-label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.el-upload-list--picture-card .el-upload-list__item-status-label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.el-upload-list--picture-card .el-upload-list__item-actions{position:absolute;width:100%;height:100%;left:0;top:0;cursor:default;text-align:center;color:#fff;opacity:0;font-size:20px;background-color:rgba(0,0,0,.5);-webkit-transition:opacity .3s;transition:opacity .3s}.el-upload-list--picture-card .el-upload-list__item-actions:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-upload-list--picture-card .el-upload-list__item-actions span{display:none;cursor:pointer}.el-upload-list--picture-card .el-upload-list__item-actions span+span{margin-left:15px}.el-upload-list--picture-card .el-upload-list__item-actions .el-upload-list__item-delete{position:static;font-size:inherit;color:inherit}.el-upload-list--picture-card .el-upload-list__item-actions:hover{opacity:1}.el-upload-list--picture-card .el-upload-list__item-actions:hover span{display:inline-block}.el-upload-list--picture-card .el-progress{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);bottom:auto;width:126px}.el-upload-list--picture-card .el-progress .el-progress__text{top:50%}.el-upload-list--picture .el-upload-list__item{overflow:hidden;z-index:0;background-color:#fff;border:1px solid #c0ccda;border-radius:6px;-webkit-box-sizing:border-box;box-sizing:border-box;margin-top:10px;padding:10px 10px 10px 90px;height:92px}.el-upload-list--picture .el-upload-list__item .el-icon-check,.el-upload-list--picture .el-upload-list__item .el-icon-circle-check{color:#fff}.el-upload-list--picture .el-upload-list__item:hover .el-upload-list__item-status-label{background:0 0;-webkit-box-shadow:none;box-shadow:none;top:-2px;right:-12px}.el-upload-list--picture .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name{line-height:70px;margin-top:0}.el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name i{display:none}.el-upload-list--picture .el-upload-list__item-thumbnail{vertical-align:middle;display:inline-block;width:70px;height:70px;float:left;position:relative;z-index:1;margin-left:-80px;background-color:#fff}.el-upload-list--picture .el-upload-list__item-name{display:block;margin-top:20px}.el-upload-list--picture .el-upload-list__item-name i{font-size:70px;line-height:1;position:absolute;left:9px;top:10px}.el-upload-list--picture .el-upload-list__item-status-label{position:absolute;right:-17px;top:-7px;width:46px;height:26px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 1px 1px #ccc;box-shadow:0 1px 1px #ccc}.el-upload-list--picture .el-upload-list__item-status-label i{font-size:12px;margin-top:12px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.el-upload-list--picture .el-progress{position:relative;top:-7px}.el-upload-cover{position:absolute;left:0;top:0;width:100%;height:100%;overflow:hidden;z-index:10;cursor:default}.el-upload-cover:after{display:inline-block;height:100%;vertical-align:middle}.el-upload-cover img{display:block;width:100%;height:100%}.el-upload-cover__label{position:absolute;right:-15px;top:-6px;width:40px;height:24px;background:#13ce66;text-align:center;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-box-shadow:0 0 1pc 1px rgba(0,0,0,.2);box-shadow:0 0 1pc 1px rgba(0,0,0,.2)}.el-upload-cover__label i{font-size:12px;margin-top:11px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);color:#fff}.el-upload-cover__progress{display:inline-block;vertical-align:middle;position:static;width:243px}.el-upload-cover__progress+.el-upload__inner{opacity:0}.el-upload-cover__content{position:absolute;top:0;left:0;width:100%;height:100%}.el-upload-cover__interact{position:absolute;bottom:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.72);text-align:center}.el-upload-cover__interact .btn{display:inline-block;color:#fff;font-size:14px;cursor:pointer;vertical-align:middle;-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);margin-top:60px}.el-upload-cover__interact .btn span{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.el-upload-cover__interact .btn:not(:first-child){margin-left:35px}.el-upload-cover__interact .btn:hover{-webkit-transform:translateY(-13px);transform:translateY(-13px)}.el-upload-cover__interact .btn:hover span{opacity:1}.el-upload-cover__interact .btn i{color:#fff;display:block;font-size:24px;line-height:inherit;margin:0 auto 5px}.el-upload-cover__title{position:absolute;bottom:0;left:0;background-color:#fff;height:36px;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400;text-align:left;padding:0 10px;margin:0;line-height:36px;font-size:14px;color:#303133}.el-upload-cover+.el-upload__inner{opacity:0;position:relative;z-index:1}.el-progress{position:relative;line-height:1}.el-progress__text{font-size:14px;color:#606266;display:inline-block;vertical-align:middle;margin-left:10px;line-height:1}.el-progress__text i{vertical-align:middle;display:block}.el-progress--circle,.el-progress--dashboard{display:inline-block}.el-progress--circle .el-progress__text,.el-progress--dashboard .el-progress__text{position:absolute;top:50%;left:0;width:100%;text-align:center;margin:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-progress--circle .el-progress__text i,.el-progress--dashboard .el-progress__text i{vertical-align:middle;display:inline-block}.el-progress--without-text .el-progress__text{display:none}.el-progress--without-text .el-progress-bar{padding-right:0;margin-right:0;display:block}.el-progress-bar,.el-progress-bar__inner:after,.el-progress-bar__innerText,.el-spinner{display:inline-block;vertical-align:middle}.el-progress--text-inside .el-progress-bar{padding-right:0;margin-right:0}.el-progress.is-success .el-progress-bar__inner{background-color:#67c23a}.el-progress.is-success .el-progress__text{color:#67c23a}.el-progress.is-warning .el-progress-bar__inner{background-color:#e6a23c}.el-progress.is-warning .el-progress__text{color:#e6a23c}.el-progress.is-exception .el-progress-bar__inner{background-color:#f56c6c}.el-progress.is-exception .el-progress__text{color:#f56c6c}.el-progress-bar{padding-right:50px;width:100%;margin-right:-55px;-webkit-box-sizing:border-box;box-sizing:border-box}.el-progress-bar__outer{height:6px;border-radius:100px;background-color:#ebeef5;overflow:hidden;position:relative;vertical-align:middle}.el-progress-bar__inner{position:absolute;left:0;top:0;height:100%;background-color:#409eff;text-align:right;border-radius:100px;line-height:1;white-space:nowrap;-webkit-transition:width .6s ease;transition:width .6s ease}.el-card,.el-message{border-radius:4px;overflow:hidden}.el-progress-bar__inner:after{height:100%}.el-progress-bar__innerText{color:#fff;font-size:12px;margin:0 5px}@keyframes progress{0%{background-position:0 0}to{background-position:32px 0}}.el-time-spinner{width:100%;white-space:nowrap}.el-spinner-inner{-webkit-animation:rotate 2s linear infinite;animation:rotate 2s linear infinite;width:50px;height:50px}.el-spinner-inner .path{stroke:#ececec;stroke-linecap:round;-webkit-animation:dash 1.5s ease-in-out infinite;animation:dash 1.5s ease-in-out infinite}@-webkit-keyframes rotate{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rotate{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}@keyframes dash{0%{stroke-dasharray:1,150;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-35}to{stroke-dasharray:90,150;stroke-dashoffset:-124}}.el-message{min-width:380px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #ebeef5;position:fixed;left:50%;top:20px;-webkit-transform:translateX(-50%);transform:translateX(-50%);background-color:#edf2fc;-webkit-transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,top .4s,-webkit-transform .4s;transition:opacity .3s,transform .4s,top .4s;transition:opacity .3s,transform .4s,top .4s,-webkit-transform .4s;padding:15px 15px 15px 20px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-message.is-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.el-message.is-closable .el-message__content{padding-right:16px}.el-message p{margin:0}.el-message--info .el-message__content{color:#909399}.el-message--success{background-color:#f0f9eb;border-color:#e1f3d8}.el-message--success .el-message__content{color:#67c23a}.el-message--warning{background-color:#fdf6ec;border-color:#faecd8}.el-message--warning .el-message__content{color:#e6a23c}.el-message--error{background-color:#fef0f0;border-color:#fde2e2}.el-message--error .el-message__content{color:#f56c6c}.el-message__icon{margin-right:10px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__closeBtn{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);transform:translateY(-50%);cursor:pointer;color:#c0c4cc;font-size:16px}.el-message__closeBtn:hover{color:#909399}.el-message .el-icon-success{color:#67c23a}.el-message .el-icon-error{color:#f56c6c}.el-message .el-icon-info{color:#909399}.el-message .el-icon-warning{color:#e6a23c}.el-message-fade-enter,.el-message-fade-leave-active{opacity:0;-webkit-transform:translate(-50%,-100%);transform:translate(-50%,-100%)}.el-badge{position:relative;vertical-align:middle;display:inline-block}.el-badge__content{background-color:#f56c6c;border-radius:10px;color:#fff;display:inline-block;font-size:12px;height:18px;line-height:18px;padding:0 6px;text-align:center;white-space:nowrap;border:1px solid #fff}.el-badge__content.is-fixed{position:absolute;top:0;right:10px;-webkit-transform:translateY(-50%) translateX(100%);transform:translateY(-50%) translateX(100%)}.el-rate__icon,.el-rate__item{position:relative;display:inline-block}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{height:8px;width:8px;padding:0;right:0;border-radius:50%}.el-badge__content--primary{background-color:#409eff}.el-badge__content--success{background-color:#67c23a}.el-badge__content--warning{background-color:#e6a23c}.el-badge__content--info{background-color:#909399}.el-badge__content--danger{background-color:#f56c6c}.el-card{border:1px solid #ebeef5;background-color:#fff;color:#303133;-webkit-transition:.3s;transition:.3s}.el-card.is-always-shadow,.el-card.is-hover-shadow:focus,.el-card.is-hover-shadow:hover{-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-card__header{padding:18px 20px;border-bottom:1px solid #ebeef5;-webkit-box-sizing:border-box;box-sizing:border-box}.el-card__body{padding:20px}.el-rate{height:20px;line-height:1}.el-rate__item{font-size:0;vertical-align:middle}.el-rate__icon{font-size:18px;margin-right:6px;color:#c0c4cc;-webkit-transition:.3s;transition:.3s}.el-rate__decimal,.el-rate__icon .path2{position:absolute;top:0;left:0}.el-rate__icon.hover{-webkit-transform:scale(1.15);transform:scale(1.15)}.el-rate__decimal{display:inline-block;overflow:hidden}.el-step.is-vertical,.el-steps{display:-webkit-box;display:-ms-flexbox}.el-rate__text{font-size:14px;vertical-align:middle}.el-steps{display:-webkit-box;display:-ms-flexbox;display:flex}.el-steps--simple{padding:13px 8%;border-radius:4px;background:#f5f7fa}.el-steps--horizontal{white-space:nowrap}.el-steps--vertical{height:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}.el-step{position:relative;-ms-flex-negative:1;flex-shrink:1}.el-step:last-of-type .el-step__line{display:none}.el-step:last-of-type.is-flex{-ms-flex-preferred-size:auto!important;flex-basis:auto!important;-ms-flex-negative:0;flex-shrink:0;-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0}.el-step:last-of-type .el-step__description,.el-step:last-of-type .el-step__main{padding-right:0}.el-step__head{position:relative;width:100%}.el-step__head.is-process{color:#303133;border-color:#303133}.el-step__head.is-wait{color:#c0c4cc;border-color:#c0c4cc}.el-step__head.is-success{color:#67c23a;border-color:#67c23a}.el-step__head.is-error{color:#f56c6c;border-color:#f56c6c}.el-step__head.is-finish{color:#409eff;border-color:#409eff}.el-step__icon{position:relative;z-index:1;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:24px;height:24px;font-size:14px;-webkit-box-sizing:border-box;box-sizing:border-box;background:#fff;-webkit-transition:.15s ease-out;transition:.15s ease-out}.el-step__icon.is-text{border-radius:50%;border:2px solid;border-color:inherit}.el-step__icon.is-icon{width:40px}.el-step__icon-inner{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:center;font-weight:700;line-height:1;color:inherit}.el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:25px;font-weight:400}.el-step__icon-inner.is-status{-webkit-transform:translateY(1px);transform:translateY(1px)}.el-step__line{position:absolute;border-color:inherit;background-color:#c0c4cc}.el-step__line-inner{display:block;border:1px solid;border-color:inherit;-webkit-transition:.15s ease-out;transition:.15s ease-out;-webkit-box-sizing:border-box;box-sizing:border-box;width:0;height:0}.el-step__main{white-space:normal;text-align:left}.el-step__title{font-size:16px;line-height:38px}.el-step__title.is-process{font-weight:700;color:#303133}.el-step__title.is-wait{color:#c0c4cc}.el-step__title.is-success{color:#67c23a}.el-step__title.is-error{color:#f56c6c}.el-step__title.is-finish{color:#409eff}.el-step__description{padding-right:10%;margin-top:-5px;font-size:12px;line-height:20px;font-weight:400}.el-step__description.is-process{color:#303133}.el-step__description.is-wait{color:#c0c4cc}.el-step__description.is-success{color:#67c23a}.el-step__description.is-error{color:#f56c6c}.el-step__description.is-finish{color:#409eff}.el-step.is-horizontal{display:inline-block}.el-step.is-horizontal .el-step__line{height:2px;top:11px;left:0;right:0}.el-step.is-vertical{display:-webkit-box;display:-ms-flexbox;display:flex}.el-step.is-vertical .el-step__head{-webkit-box-flex:0;-ms-flex-positive:0;flex-grow:0;width:24px}.el-step.is-vertical .el-step__main{padding-left:10px;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.el-step.is-vertical .el-step__title{line-height:24px;padding-bottom:8px}.el-step.is-vertical .el-step__line{width:2px;top:0;bottom:0;left:11px}.el-step.is-vertical .el-step__icon.is-icon{width:24px}.el-step.is-center .el-step__head,.el-step.is-center .el-step__main{text-align:center}.el-step.is-center .el-step__description{padding-left:20%;padding-right:20%}.el-step.is-center .el-step__line{left:50%;right:-50%}.el-step.is-simple{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-step.is-simple .el-step__head{width:auto;font-size:0;padding-right:10px}.el-step.is-simple .el-step__icon{background:0 0;width:16px;height:16px;font-size:12px}.el-step.is-simple .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:18px}.el-step.is-simple .el-step__icon-inner.is-status{-webkit-transform:scale(.8) translateY(1px);transform:scale(.8) translateY(1px)}.el-step.is-simple .el-step__main{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.el-step.is-simple .el-step__title{font-size:16px;line-height:20px}.el-step.is-simple:not(:last-of-type) .el-step__title{max-width:50%;word-break:break-all}.el-step.is-simple .el-step__arrow{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow: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}.el-step.is-simple .el-step__arrow:after,.el-step.is-simple .el-step__arrow:before{content:"";display:inline-block;position:absolute;height:15px;width:1px;background:#c0c4cc}.el-step.is-simple .el-step__arrow:before{-webkit-transform:rotate(-45deg) translateY(-4px);transform:rotate(-45deg) translateY(-4px);-webkit-transform-origin:0 0;transform-origin:0 0}.el-step.is-simple .el-step__arrow:after{-webkit-transform:rotate(45deg) translateY(4px);transform:rotate(45deg) translateY(4px);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}.el-step.is-simple:last-of-type .el-step__arrow{display:none}.el-carousel{position:relative}.el-carousel--horizontal{overflow-x:hidden}.el-carousel--vertical{overflow-y:hidden}.el-carousel__container{position:relative;height:300px}.el-carousel__arrow{border:none;outline:0;padding:0;margin:0;height:36px;width:36px;cursor:pointer;-webkit-transition:.3s;transition:.3s;border-radius:50%;background-color:rgba(31,45,61,.11);color:#fff;position:absolute;top:50%;z-index:10;-webkit-transform:translateY(-50%);transform:translateY(-50%);text-align:center;font-size:12px}.el-carousel__arrow--left{left:16px}.el-carousel__arrow--right{right:16px}.el-carousel__arrow:hover{background-color:rgba(31,45,61,.23)}.el-carousel__arrow i{cursor:pointer}.el-carousel__indicators{position:absolute;list-style:none;margin:0;padding:0;z-index:2}.el-carousel__indicators--horizontal{bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.el-carousel__indicators--vertical{right:0;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-carousel__indicators--outside{bottom:26px;text-align:center;position:static;-webkit-transform:none;transform:none}.el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.el-carousel__indicators--outside button{background-color:#c0c4cc;opacity:.24}.el-carousel__indicators--labels{left:0;right:0;-webkit-transform:none;transform:none;text-align:center}.el-carousel__indicators--labels .el-carousel__button{height:auto;width:auto;padding:2px 18px;font-size:12px}.el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.el-carousel__indicator{background-color:transparent;cursor:pointer}.el-carousel__indicator:hover button{opacity:.72}.el-carousel__indicator--horizontal{display:inline-block;padding:12px 4px}.el-carousel__indicator--vertical{padding:4px 12px}.el-carousel__indicator--vertical .el-carousel__button{width:2px;height:15px}.el-carousel__indicator.is-active button{opacity:1}.el-carousel__button{display:block;opacity:.48;width:30px;height:2px;background-color:#fff;border:none;outline:0;padding:0;margin:0;cursor:pointer;-webkit-transition:.3s;transition:.3s}.el-carousel__item,.el-carousel__mask{height:100%;top:0;left:0;position:absolute}.carousel-arrow-left-enter,.carousel-arrow-left-leave-active{-webkit-transform:translateY(-50%) translateX(-10px);transform:translateY(-50%) translateX(-10px);opacity:0}.carousel-arrow-right-enter,.carousel-arrow-right-leave-active{-webkit-transform:translateY(-50%) translateX(10px);transform:translateY(-50%) translateX(10px);opacity:0}.el-carousel__item{width:100%;display:inline-block;overflow:hidden;z-index:0}.el-carousel__item.is-active{z-index:2}.el-carousel__item--card,.el-carousel__item.is-animating{-webkit-transition:-webkit-transform .4s ease-in-out;transition:-webkit-transform .4s ease-in-out;transition:transform .4s ease-in-out;transition:transform .4s ease-in-out,-webkit-transform .4s ease-in-out}.el-carousel__item--card{width:50%}.el-carousel__item--card.is-in-stage{cursor:pointer;z-index:1}.el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask,.el-carousel__item--card.is-in-stage:hover .el-carousel__mask{opacity:.12}.el-carousel__item--card.is-active{z-index:2}.el-carousel__mask{width:100%;background-color:#fff;opacity:.24;-webkit-transition:.2s;transition:.2s}.el-fade-in-enter,.el-fade-in-leave-active,.el-fade-in-linear-enter,.el-fade-in-linear-leave,.el-fade-in-linear-leave-active,.fade-in-linear-enter,.fade-in-linear-leave,.fade-in-linear-leave-active{opacity:0}.el-fade-in-linear-enter-active,.el-fade-in-linear-leave-active,.fade-in-linear-enter-active,.fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.el-fade-in-enter-active,.el-fade-in-leave-active,.el-zoom-in-center-enter-active,.el-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.el-zoom-in-center-enter,.el-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.el-zoom-in-top-enter-active,.el-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center top;transform-origin:center top}.el-zoom-in-top-enter,.el-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.el-zoom-in-bottom-enter-active,.el-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center bottom;transform-origin:center bottom}.el-zoom-in-bottom-enter,.el-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.el-zoom-in-left-enter-active,.el-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1);transform:scale(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:top left;transform-origin:top left}.el-zoom-in-left-enter,.el-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45);transform:scale(.45)}.collapse-transition{-webkit-transition:height .3s ease-in-out,padding-top .3s ease-in-out,padding-bottom .3s ease-in-out;transition:height .3s ease-in-out,padding-top .3s ease-in-out,padding-bottom .3s ease-in-out}.horizontal-collapse-transition{-webkit-transition:width .3s ease-in-out,padding-left .3s ease-in-out,padding-right .3s ease-in-out;transition:width .3s ease-in-out,padding-left .3s ease-in-out,padding-right .3s ease-in-out}.el-list-enter-active,.el-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.el-list-enter,.el-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.el-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}.el-collapse{border-top:1px solid #ebeef5;border-bottom:1px solid #ebeef5}.el-collapse-item.is-disabled .el-collapse-item__header{color:#bbb;cursor:not-allowed}.el-collapse-item__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:48px;line-height:48px;background-color:#fff;color:#303133;cursor:pointer;border-bottom:1px solid #ebeef5;font-size:13px;font-weight:500;-webkit-transition:border-bottom-color .3s;transition:border-bottom-color .3s;outline:0}.el-collapse-item__arrow{margin:0 8px 0 auto;transition:-webkit-transform .3s;-webkit-transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-weight:300}.el-collapse-item__arrow.is-active{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.el-collapse-item__header.focusing:focus:not(:hover){color:#409eff}.el-collapse-item__header.is-active{border-bottom-color:transparent}.el-collapse-item__wrap{will-change:height;background-color:#fff;overflow:hidden;box-sizing:border-box;border-bottom:1px solid #ebeef5}.el-cascader__tags,.el-collapse-item__wrap,.el-tag{-webkit-box-sizing:border-box}.el-collapse-item__content{padding-bottom:25px;font-size:13px;color:#303133;line-height:1.769230769230769}.el-collapse-item:last-child{margin-bottom:-1px}.el-popper .popper__arrow,.el-popper .popper__arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.el-popper .popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0,0,0,.03));filter:drop-shadow(0 2px 12px rgba(0,0,0,.03))}.el-popper .popper__arrow:after{content:" ";border-width:6px}.el-popper[x-placement^=top]{margin-bottom:12px}.el-popper[x-placement^=top] .popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-top-color:#ebeef5;border-bottom-width:0}.el-popper[x-placement^=top] .popper__arrow:after{bottom:1px;margin-left:-6px;border-top-color:#fff;border-bottom-width:0}.el-popper[x-placement^=bottom]{margin-top:12px}.el-popper[x-placement^=bottom] .popper__arrow{top:-6px;left:50%;margin-right:3px;border-top-width:0;border-bottom-color:#ebeef5}.el-popper[x-placement^=bottom] .popper__arrow:after{top:1px;margin-left:-6px;border-top-width:0;border-bottom-color:#fff}.el-popper[x-placement^=right]{margin-left:12px}.el-popper[x-placement^=right] .popper__arrow{top:50%;left:-6px;margin-bottom:3px;border-right-color:#ebeef5;border-left-width:0}.el-popper[x-placement^=right] .popper__arrow:after{bottom:-6px;left:1px;border-right-color:#fff;border-left-width:0}.el-popper[x-placement^=left]{margin-right:12px}.el-popper[x-placement^=left] .popper__arrow{top:50%;right:-6px;margin-bottom:3px;border-right-width:0;border-left-color:#ebeef5}.el-popper[x-placement^=left] .popper__arrow:after{right:1px;bottom:-6px;margin-left:-6px;border-right-width:0;border-left-color:#fff}.el-tag{background-color:#ecf5ff;display:inline-block;height:32px;padding:0 10px;line-height:30px;font-size:12px;color:#409eff;border:1px solid #d9ecff;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box;white-space:nowrap}.el-tag.is-hit{border-color:#409eff}.el-tag .el-tag__close{color:#409eff}.el-tag .el-tag__close:hover{color:#fff;background-color:#409eff}.el-tag.el-tag--info{background-color:#f4f4f5;border-color:#e9e9eb;color:#909399}.el-tag.el-tag--info.is-hit{border-color:#909399}.el-tag.el-tag--info .el-tag__close{color:#909399}.el-tag.el-tag--info .el-tag__close:hover{color:#fff;background-color:#909399}.el-tag.el-tag--success{background-color:#f0f9eb;border-color:#e1f3d8;color:#67c23a}.el-tag.el-tag--success.is-hit{border-color:#67c23a}.el-tag.el-tag--success .el-tag__close{color:#67c23a}.el-tag.el-tag--success .el-tag__close:hover{color:#fff;background-color:#67c23a}.el-tag.el-tag--warning{background-color:#fdf6ec;border-color:#faecd8;color:#e6a23c}.el-tag.el-tag--warning.is-hit{border-color:#e6a23c}.el-tag.el-tag--warning .el-tag__close{color:#e6a23c}.el-tag.el-tag--warning .el-tag__close:hover{color:#fff;background-color:#e6a23c}.el-tag.el-tag--danger{background-color:#fef0f0;border-color:#fde2e2;color:#f56c6c}.el-tag.el-tag--danger.is-hit{border-color:#f56c6c}.el-tag.el-tag--danger .el-tag__close{color:#f56c6c}.el-tag.el-tag--danger .el-tag__close:hover{color:#fff;background-color:#f56c6c}.el-tag .el-icon-close{border-radius:50%;text-align:center;position:relative;cursor:pointer;font-size:12px;height:16px;width:16px;line-height:16px;vertical-align:middle;top:-1px;right:-5px}.el-tag .el-icon-close:before{display:block}.el-tag--dark{background-color:#409eff;color:#fff}.el-tag--dark,.el-tag--dark.is-hit{border-color:#409eff}.el-tag--dark .el-tag__close{color:#fff}.el-tag--dark .el-tag__close:hover{color:#fff;background-color:#66b1ff}.el-tag--dark.el-tag--info{background-color:#909399;border-color:#909399;color:#fff}.el-tag--dark.el-tag--info.is-hit{border-color:#909399}.el-tag--dark.el-tag--info .el-tag__close{color:#fff}.el-tag--dark.el-tag--info .el-tag__close:hover{color:#fff;background-color:#a6a9ad}.el-tag--dark.el-tag--success{background-color:#67c23a;border-color:#67c23a;color:#fff}.el-tag--dark.el-tag--success.is-hit{border-color:#67c23a}.el-tag--dark.el-tag--success .el-tag__close{color:#fff}.el-tag--dark.el-tag--success .el-tag__close:hover{color:#fff;background-color:#85ce61}.el-tag--dark.el-tag--warning{background-color:#e6a23c;border-color:#e6a23c;color:#fff}.el-tag--dark.el-tag--warning.is-hit{border-color:#e6a23c}.el-tag--dark.el-tag--warning .el-tag__close{color:#fff}.el-tag--dark.el-tag--warning .el-tag__close:hover{color:#fff;background-color:#ebb563}.el-tag--dark.el-tag--danger{background-color:#f56c6c;border-color:#f56c6c;color:#fff}.el-tag--dark.el-tag--danger.is-hit{border-color:#f56c6c}.el-tag--dark.el-tag--danger .el-tag__close{color:#fff}.el-tag--dark.el-tag--danger .el-tag__close:hover{color:#fff;background-color:#f78989}.el-tag--plain{background-color:#fff;border-color:#b3d8ff;color:#409eff}.el-tag--plain.is-hit{border-color:#409eff}.el-tag--plain .el-tag__close{color:#409eff}.el-tag--plain .el-tag__close:hover{color:#fff;background-color:#409eff}.el-tag--plain.el-tag--info{background-color:#fff;border-color:#d3d4d6;color:#909399}.el-tag--plain.el-tag--info.is-hit{border-color:#909399}.el-tag--plain.el-tag--info .el-tag__close{color:#909399}.el-tag--plain.el-tag--info .el-tag__close:hover{color:#fff;background-color:#909399}.el-tag--plain.el-tag--success{background-color:#fff;border-color:#c2e7b0;color:#67c23a}.el-tag--plain.el-tag--success.is-hit{border-color:#67c23a}.el-tag--plain.el-tag--success .el-tag__close{color:#67c23a}.el-tag--plain.el-tag--success .el-tag__close:hover{color:#fff;background-color:#67c23a}.el-tag--plain.el-tag--warning{background-color:#fff;border-color:#f5dab1;color:#e6a23c}.el-tag--plain.el-tag--warning.is-hit{border-color:#e6a23c}.el-tag--plain.el-tag--warning .el-tag__close{color:#e6a23c}.el-tag--plain.el-tag--warning .el-tag__close:hover{color:#fff;background-color:#e6a23c}.el-tag--plain.el-tag--danger{background-color:#fff;border-color:#fbc4c4;color:#f56c6c}.el-tag--plain.el-tag--danger.is-hit{border-color:#f56c6c}.el-tag--plain.el-tag--danger .el-tag__close{color:#f56c6c}.el-tag--plain.el-tag--danger .el-tag__close:hover{color:#fff;background-color:#f56c6c}.el-tag--medium{height:28px;line-height:26px}.el-tag--medium .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.el-tag--small{height:24px;padding:0 8px;line-height:22px}.el-tag--small .el-icon-close{-webkit-transform:scale(.8);transform:scale(.8)}.el-tag--mini{height:20px;padding:0 5px;line-height:19px}.el-tag--mini .el-icon-close{margin-left:-3px;-webkit-transform:scale(.7);transform:scale(.7)}.el-cascader{display:inline-block;position:relative;font-size:14px;line-height:40px}.el-cascader:not(.is-disabled):hover .el-input__inner{cursor:pointer;border-color:#c0c4cc}.el-cascader .el-input .el-input__inner:focus,.el-cascader .el-input.is-focus .el-input__inner{border-color:#409eff}.el-cascader .el-input{cursor:pointer}.el-cascader .el-input .el-input__inner{text-overflow:ellipsis}.el-cascader .el-input .el-icon-arrow-down{-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;font-size:14px}.el-cascader .el-input .el-icon-arrow-down.is-reverse{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.el-cascader .el-input .el-icon-circle-close:hover{color:#909399}.el-cascader--medium{font-size:14px;line-height:36px}.el-cascader--small{font-size:13px;line-height:32px}.el-cascader--mini{font-size:12px;line-height:28px}.el-cascader.is-disabled .el-cascader__label{z-index:2;color:#c0c4cc}.el-cascader__dropdown{margin:5px 0;font-size:14px;background:#fff;border:1px solid #e4e7ed;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-cascader__tags{position:absolute;left:0;right:30px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;line-height:normal;text-align:left;-webkit-box-sizing:border-box;box-sizing:border-box}.el-cascader__tags .el-tag{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;max-width:100%;margin:2px 0 2px 6px;text-overflow:ellipsis;background:#f0f2f5}.el-cascader__tags .el-tag:not(.is-hit){border-color:transparent}.el-cascader__tags .el-tag>span{-webkit-box-flex:1;-ms-flex:1;flex:1;overflow:hidden;text-overflow:ellipsis}.el-cascader__tags .el-tag .el-icon-close{-webkit-box-flex:0;-ms-flex:none;flex:none;background-color:#c0c4cc;color:#fff}.el-cascader__tags .el-tag .el-icon-close:hover{background-color:#909399}.el-cascader__suggestion-panel{border-radius:4px}.el-cascader__suggestion-list{max-height:204px;margin:0;padding:6px 0;font-size:14px;color:#606266;text-align:center}.el-cascader__suggestion-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;height:34px;padding:0 15px;text-align:left;outline:0;cursor:pointer}.el-cascader__suggestion-item:focus,.el-cascader__suggestion-item:hover{background:#f5f7fa}.el-cascader__suggestion-item.is-checked{color:#409eff;font-weight:700}.el-cascader__suggestion-item>span{margin-right:10px}.el-cascader__empty-text{margin:10px 0;color:#c0c4cc}.el-cascader__search-input{-webkit-box-flex:1;-ms-flex:1;flex:1;height:24px;min-width:60px;margin:2px 0 2px 15px;padding:0;color:#606266;border:none;outline:0;-webkit-box-sizing:border-box;box-sizing:border-box}.el-cascader__search-input::-webkit-input-placeholder{color:#c0c4cc}.el-cascader__search-input:-ms-input-placeholder{color:#c0c4cc}.el-cascader__search-input::-ms-input-placeholder{color:#c0c4cc}.el-cascader__search-input::-moz-placeholder{color:#c0c4cc}.el-cascader__search-input::placeholder{color:#c0c4cc}.el-color-predefine{font-size:12px;margin-top:8px;width:280px}.el-color-predefine,.el-color-predefine__colors{display:-webkit-box;display:-ms-flexbox;display:flex}.el-color-predefine__colors{-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-wrap:wrap;flex-wrap:wrap}.el-color-predefine__color-selector{margin:0 0 8px 8px;width:20px;height:20px;border-radius:4px;cursor:pointer}.el-color-predefine__color-selector:nth-child(10n+1){margin-left:0}.el-color-predefine__color-selector.selected{-webkit-box-shadow:0 0 3px 2px #409eff;box-shadow:0 0 3px 2px #409eff}.el-color-predefine__color-selector>div{display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;border-radius:3px}.el-color-predefine__color-selector.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-hue-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background-color:red;padding:0 2px}.el-color-hue-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,color-stop(0,red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(90deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red);height:100%}.el-color-hue-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.el-color-hue-slider.is-vertical{width:12px;height:180px;padding:2px 0}.el-color-hue-slider.is-vertical .el-color-hue-slider__bar{background:-webkit-gradient(linear,left top,left bottom,color-stop(0,red),color-stop(17%,#ff0),color-stop(33%,#0f0),color-stop(50%,#0ff),color-stop(67%,#00f),color-stop(83%,#f0f),to(red));background:linear-gradient(180deg,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red)}.el-color-hue-slider.is-vertical .el-color-hue-slider__thumb{left:0;top:0;width:100%;height:4px}.el-color-svpanel{position:relative;width:280px;height:180px}.el-color-svpanel__black,.el-color-svpanel__white{position:absolute;top:0;left:0;right:0;bottom:0}.el-color-svpanel__white{background:-webkit-gradient(linear,left top,right top,from(#fff),to(hsla(0,0%,100%,0)));background:linear-gradient(90deg,#fff,hsla(0,0%,100%,0))}.el-color-svpanel__black{background:-webkit-gradient(linear,left bottom,left top,from(#000),to(transparent));background:linear-gradient(0deg,#000,transparent)}.el-color-svpanel__cursor{position:absolute}.el-color-svpanel__cursor>div{cursor:head;width:4px;height:4px;-webkit-box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px rgba(0,0,0,.3),0 0 1px 2px rgba(0,0,0,.4);border-radius:50%;-webkit-transform:translate(-2px,-2px);transform:translate(-2px,-2px)}.el-color-alpha-slider{position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;width:280px;height:12px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-alpha-slider__bar{position:relative;background:-webkit-gradient(linear,left top,right top,color-stop(0,hsla(0,0%,100%,0)),to(#fff));background:linear-gradient(90deg,hsla(0,0%,100%,0) 0,#fff);height:100%}.el-color-alpha-slider__thumb{position:absolute;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;left:0;top:0;width:4px;height:100%;border-radius:1px;background:#fff;border:1px solid #f0f0f0;-webkit-box-shadow:0 0 2px rgba(0,0,0,.6);box-shadow:0 0 2px rgba(0,0,0,.6);z-index:1}.el-color-alpha-slider.is-vertical{width:20px;height:180px}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__bar{background:-webkit-gradient(linear,left top,left bottom,color-stop(0,hsla(0,0%,100%,0)),to(#fff));background:linear-gradient(180deg,hsla(0,0%,100%,0) 0,#fff)}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__thumb{left:0;top:0;width:100%;height:4px}.el-color-dropdown{width:300px}.el-color-dropdown__main-wrapper{margin-bottom:6px}.el-color-dropdown__main-wrapper:after{content:"";display:table;clear:both}.el-color-dropdown__btns{margin-top:6px;text-align:right}.el-color-dropdown__value{float:left;line-height:26px;font-size:12px;color:#000;width:160px}.el-color-dropdown__btn{border:1px solid #dcdcdc;color:#333;line-height:24px;border-radius:2px;padding:0 20px;cursor:pointer;background-color:transparent;outline:0;font-size:12px}.el-color-dropdown__btn[disabled]{color:#ccc;cursor:not-allowed}.el-color-dropdown__btn:hover{color:#409eff;border-color:#409eff}.el-color-dropdown__link-btn{cursor:pointer;color:#409eff;text-decoration:none;padding:15px;font-size:12px}.el-color-dropdown__link-btn:hover{color:tint(#409eff,20%)}.el-color-picker{display:inline-block;position:relative;line-height:normal;height:40px}.el-color-picker.is-disabled .el-color-picker__trigger{cursor:not-allowed}.el-color-picker--medium{height:36px}.el-color-picker--medium .el-color-picker__trigger{height:36px;width:36px}.el-color-picker--medium .el-color-picker__mask{height:34px;width:34px}.el-color-picker--small{height:32px}.el-color-picker--small .el-color-picker__trigger{height:32px;width:32px}.el-color-picker--small .el-color-picker__mask{height:30px;width:30px}.el-color-picker--small .el-color-picker__empty,.el-color-picker--small .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.el-color-picker--mini{height:28px}.el-color-picker--mini .el-color-picker__trigger{height:28px;width:28px}.el-color-picker--mini .el-color-picker__mask{height:26px;width:26px}.el-color-picker--mini .el-color-picker__empty,.el-color-picker--mini .el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0) scale(.8);transform:translate3d(-50%,-50%,0) scale(.8)}.el-color-picker__mask{height:38px;width:38px;border-radius:4px;position:absolute;top:1px;left:1px;z-index:1;cursor:not-allowed;background-color:hsla(0,0%,100%,.7)}.el-color-picker__trigger{display:inline-block;height:40px;width:40px;padding:4px;border:1px solid #e6e6e6;border-radius:4px;font-size:0;cursor:pointer}.el-color-picker__color,.el-color-picker__trigger{-webkit-box-sizing:border-box;box-sizing:border-box;position:relative}.el-color-picker__color{display:block;border:1px solid #999;border-radius:2px;width:100%;height:100%;text-align:center}.el-color-picker__color.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-picker__color-inner{position:absolute;left:0;top:0;right:0;bottom:0}.el-color-picker__empty,.el-color-picker__icon{top:50%;left:50%;font-size:12px;position:absolute}.el-color-picker__empty{color:#999}.el-color-picker__empty,.el-color-picker__icon{-webkit-transform:translate3d(-50%,-50%,0);transform:translate3d(-50%,-50%,0)}.el-color-picker__icon{display:inline-block;width:100%;color:#fff;text-align:center}.el-color-picker__panel{position:absolute;z-index:10;padding:6px;-webkit-box-sizing:content-box;box-sizing:content-box;background-color:#fff;border:1px solid #ebeef5;border-radius:4px;-webkit-box-shadow:0 2px 12px 0 rgba(0,0,0,.1);box-shadow:0 2px 12px 0 rgba(0,0,0,.1)}.el-textarea{position:relative;display:inline-block;width:100%;vertical-align:bottom;font-size:14px}.el-textarea__inner{display:block;resize:vertical;padding:5px 15px;line-height:1.5;-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;font-size:inherit;color:#606266;background-color:#fff;background-image:none;border:1px solid #dcdfe6;border-radius:4px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1)}.el-textarea__inner::-webkit-input-placeholder{color:#c0c4cc}.el-textarea__inner:-ms-input-placeholder{color:#c0c4cc}.el-textarea__inner::-ms-input-placeholder{color:#c0c4cc}.el-textarea__inner::-moz-placeholder{color:#c0c4cc}.el-textarea__inner::placeholder{color:#c0c4cc}.el-textarea__inner:hover{border-color:#c0c4cc}.el-textarea__inner:focus{outline:0;border-color:#409eff}.el-textarea .el-input__count{color:#909399;background:#fff;position:absolute;font-size:12px;bottom:5px;right:10px}.el-textarea.is-disabled .el-textarea__inner{background-color:#f5f7fa;border-color:#e4e7ed;color:#c0c4cc;cursor:not-allowed}.el-textarea.is-disabled .el-textarea__inner::-webkit-input-placeholder{color:#c0c4cc}.el-textarea.is-disabled .el-textarea__inner:-ms-input-placeholder{color:#c0c4cc}.el-textarea.is-disabled .el-textarea__inner::-ms-input-placeholder{color:#c0c4cc}.el-textarea.is-disabled .el-textarea__inner::-moz-placeholder{color:#c0c4cc}.el-textarea.is-disabled .el-textarea__inner::placeholder{color:#c0c4cc}.el-textarea.is-exceed .el-textarea__inner{border-color:#f56c6c}.el-textarea.is-exceed .el-input__count{color:#f56c6c}.el-input{position:relative;font-size:14px;display:inline-block;width:100%}.el-input::-webkit-scrollbar{z-index:11;width:6px}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{border-radius:5px;width:6px;background:#b4bccc}.el-input::-webkit-scrollbar-corner,.el-input::-webkit-scrollbar-track{background:#fff}.el-input::-webkit-scrollbar-track-piece{background:#fff;width:6px}.el-input .el-input__clear{color:#c0c4cc;font-size:14px;cursor:pointer;-webkit-transition:color .2s cubic-bezier(.645,.045,.355,1);transition:color .2s cubic-bezier(.645,.045,.355,1)}.el-input .el-input__clear:hover{color:#909399}.el-input .el-input__count{height:100%;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#909399;font-size:12px}.el-input .el-input__count .el-input__count-inner{background:#fff;line-height:normal;display:inline-block;padding:0 5px}.el-input__inner{-webkit-appearance:none;background-color:#fff;background-image:none;border-radius:4px;border:1px solid #dcdfe6;-webkit-box-sizing:border-box;box-sizing:border-box;color:#606266;display:inline-block;font-size:inherit;height:40px;line-height:40px;outline:0;padding:0 15px;-webkit-transition:border-color .2s cubic-bezier(.645,.045,.355,1);transition:border-color .2s cubic-bezier(.645,.045,.355,1);width:100%}.el-input__prefix,.el-input__suffix{position:absolute;top:0;-webkit-transition:all .3s;height:100%;color:#c0c4cc;text-align:center}.el-input__inner::-webkit-input-placeholder{color:#c0c4cc}.el-input__inner:-ms-input-placeholder{color:#c0c4cc}.el-input__inner::-ms-input-placeholder{color:#c0c4cc}.el-input__inner::-moz-placeholder{color:#c0c4cc}.el-input__inner::placeholder{color:#c0c4cc}.el-input__inner:hover{border-color:#c0c4cc}.el-input.is-active .el-input__inner,.el-input__inner:focus{border-color:#409eff;outline:0}.el-input__suffix{right:5px;-webkit-transition:all .3s;transition:all .3s}.el-input__suffix-inner{pointer-events:all}.el-input__prefix{left:5px}.el-input__icon,.el-input__prefix{-webkit-transition:all .3s;transition:all .3s}.el-input__icon{height:100%;width:25px;text-align:center;line-height:40px}.el-input__icon:after{content:"";height:100%;width:0;display:inline-block;vertical-align:middle}.el-input__validateIcon{pointer-events:none}.el-input.is-disabled .el-input__inner{background-color:#f5f7fa;border-color:#e4e7ed;color:#c0c4cc;cursor:not-allowed}.el-input.is-disabled .el-input__inner::-webkit-input-placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__inner:-ms-input-placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__inner::-ms-input-placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__inner::-moz-placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__inner::placeholder{color:#c0c4cc}.el-input.is-disabled .el-input__icon{cursor:not-allowed}.el-link,.el-transfer-panel__filter .el-icon-circle-close{cursor:pointer}.el-input.is-exceed .el-input__inner{border-color:#f56c6c}.el-input.is-exceed .el-input__suffix .el-input__count{color:#f56c6c}.el-input--suffix .el-input__inner{padding-right:30px}.el-input--prefix .el-input__inner{padding-left:30px}.el-input--medium{font-size:14px}.el-input--medium .el-input__inner{height:36px;line-height:36px}.el-input--medium .el-input__icon{line-height:36px}.el-input--small{font-size:13px}.el-input--small .el-input__inner{height:32px;line-height:32px}.el-input--small .el-input__icon{line-height:32px}.el-input--mini{font-size:12px}.el-input--mini .el-input__inner{height:28px;line-height:28px}.el-input--mini .el-input__icon{line-height:28px}.el-input-group{line-height:normal;display:inline-table;width:100%;border-collapse:separate;border-spacing:0}.el-input-group>.el-input__inner{vertical-align:middle;display:table-cell}.el-input-group__append,.el-input-group__prepend{background-color:#f5f7fa;color:#909399;vertical-align:middle;display:table-cell;position:relative;border:1px solid #dcdfe6;border-radius:4px;padding:0 20px;width:1px;white-space:nowrap}.el-input-group--prepend .el-input__inner,.el-input-group__append{border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--append .el-input__inner,.el-input-group__prepend{border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group__append:focus,.el-input-group__prepend:focus{outline:0}.el-input-group__append .el-button,.el-input-group__append .el-select,.el-input-group__prepend .el-button,.el-input-group__prepend .el-select{display:inline-block;margin:-10px -20px}.el-input-group__append button.el-button,.el-input-group__append div.el-select .el-input__inner,.el-input-group__append div.el-select:hover .el-input__inner,.el-input-group__prepend button.el-button,.el-input-group__prepend div.el-select .el-input__inner,.el-input-group__prepend div.el-select:hover .el-input__inner{border-color:transparent;background-color:transparent;color:inherit;border-top:0;border-bottom:0}.el-input-group__append .el-button,.el-input-group__append .el-input,.el-input-group__prepend .el-button,.el-input-group__prepend .el-input{font-size:inherit}.el-input-group__prepend{border-right:0}.el-input-group__append{border-left:0}.el-input-group--append .el-select .el-input.is-focus .el-input__inner,.el-input-group--prepend .el-select .el-input.is-focus .el-input__inner{border-color:transparent}.el-input__inner::-ms-clear{display:none;width:0;height:0}.el-transfer{font-size:14px}.el-transfer__buttons{display:inline-block;vertical-align:middle;padding:0 30px}.el-transfer__button{display:block;margin:0 auto;padding:10px;border-radius:50%;color:#fff;background-color:#409eff;font-size:0}.el-transfer-panel__item+.el-transfer-panel__item,.el-transfer__button [class*=el-icon-]+span{margin-left:0}.el-transfer__button.is-with-texts{border-radius:4px}.el-transfer__button.is-disabled,.el-transfer__button.is-disabled:hover{border:1px solid #dcdfe6;background-color:#f5f7fa;color:#c0c4cc}.el-transfer__button:first-child{margin-bottom:10px}.el-transfer__button:nth-child(2){margin:0}.el-transfer__button i,.el-transfer__button span{font-size:14px}.el-transfer-panel{border:1px solid #ebeef5;border-radius:4px;overflow:hidden;background:#fff;display:inline-block;vertical-align:middle;width:200px;max-height:100%;-webkit-box-sizing:border-box;box-sizing:border-box;position:relative}.el-transfer-panel__body{height:246px}.el-transfer-panel__body.is-with-footer{padding-bottom:40px}.el-transfer-panel__list{margin:0;padding:6px 0;list-style:none;height:246px;overflow:auto;-webkit-box-sizing:border-box;box-sizing:border-box}.el-transfer-panel__list.is-filterable{height:194px;padding-top:0}.el-transfer-panel__item{height:30px;line-height:30px;padding-left:15px;display:block!important}.el-transfer-panel__item.el-checkbox{color:#606266}.el-transfer-panel__item:hover{color:#409eff}.el-transfer-panel__item.el-checkbox .el-checkbox__label{width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:24px;line-height:30px}.el-transfer-panel__item .el-checkbox__input{position:absolute;top:8px}.el-transfer-panel__filter{text-align:center;margin:15px;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;width:auto}.el-transfer-panel__filter .el-input__inner{height:32px;width:100%;font-size:12px;display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:16px;padding-right:10px;padding-left:30px}.el-transfer-panel__filter .el-input__icon{margin-left:5px}.el-transfer-panel .el-transfer-panel__header{height:40px;line-height:40px;background:#f5f7fa;margin:0;padding-left:15px;border-bottom:1px solid #ebeef5;-webkit-box-sizing:border-box;box-sizing:border-box;color:#000}.el-transfer-panel .el-transfer-panel__header .el-checkbox{display:block;line-height:40px}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label{font-size:16px;color:#303133;font-weight:400}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label span{position:absolute;right:15px;color:#909399;font-size:12px;font-weight:400}.el-divider__text,.el-link{font-weight:500;font-size:14px}.el-transfer-panel .el-transfer-panel__footer{height:40px;background:#fff;margin:0;padding:0;border-top:1px solid #ebeef5;position:absolute;bottom:0;left:0;width:100%;z-index:1}.el-transfer-panel .el-transfer-panel__footer:after{display:inline-block;content:"";height:100%;vertical-align:middle}.el-container,.el-timeline-item__node{display:-webkit-box;display:-ms-flexbox}.el-transfer-panel .el-transfer-panel__footer .el-checkbox{padding-left:20px;color:#606266}.el-transfer-panel .el-transfer-panel__empty{margin:0;height:30px;line-height:30px;padding:6px 15px 0;color:#909399;text-align:center}.el-transfer-panel .el-checkbox__label{padding-left:8px}.el-transfer-panel .el-checkbox__inner{height:14px;width:14px;border-radius:3px}.el-transfer-panel .el-checkbox__inner:after{height:6px;width:3px;left:4px}.el-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;-webkit-box-sizing:border-box;box-sizing:border-box;min-width:0}.el-container.is-vertical,.el-drawer{-webkit-box-orient:vertical;-webkit-box-direction:normal}.el-aside,.el-header{-webkit-box-sizing:border-box}.el-container.is-vertical{-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column}.el-header{padding:0 20px}.el-aside,.el-header{-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0}.el-aside{overflow:auto}.el-footer,.el-main{-webkit-box-sizing:border-box}.el-main{display:block;-webkit-box-flex:1;-ms-flex:1;flex:1;-ms-flex-preferred-size:auto;flex-basis:auto;overflow:auto;padding:20px}.el-footer,.el-main{-webkit-box-sizing:border-box;box-sizing:border-box}.el-footer{padding:0 20px;-ms-flex-negative:0;flex-shrink:0}.el-timeline{margin:0;font-size:14px;list-style:none}.el-timeline .el-timeline-item:last-child .el-timeline-item__tail{display:none}.el-timeline-item{position:relative;padding-bottom:20px}.el-timeline-item__wrapper{position:relative;padding-left:28px;top:-3px}.el-timeline-item__tail{position:absolute;left:4px;height:100%;border-left:2px solid #e4e7ed}.el-timeline-item__icon{color:#fff;font-size:13px}.el-timeline-item__node{position:absolute;background-color:#e4e7ed;border-radius:50%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-image__error,.el-timeline-item__dot{display:-webkit-box;display:-ms-flexbox}.el-timeline-item__node--normal{left:-1px;width:12px;height:12px}.el-timeline-item__node--large{left:-2px;width:14px;height:14px}.el-timeline-item__node--primary{background-color:#409eff}.el-timeline-item__node--success{background-color:#67c23a}.el-timeline-item__node--warning{background-color:#e6a23c}.el-timeline-item__node--danger{background-color:#f56c6c}.el-timeline-item__node--info{background-color:#909399}.el-timeline-item__dot{position:absolute;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-timeline-item__content{color:#303133}.el-timeline-item__timestamp{color:#909399;line-height:1;font-size:13px}.el-timeline-item__timestamp.is-top{margin-bottom:8px;padding-top:4px}.el-timeline-item__timestamp.is-bottom{margin-top:8px}.el-link{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;vertical-align:middle;position:relative;text-decoration:none;outline:0;padding:0}.el-link.is-underline:hover:after{content:"";position:absolute;left:0;right:0;height:0;bottom:0;border-bottom:1px solid #409eff}.el-link.el-link--default:after,.el-link.el-link--primary.is-underline:hover:after,.el-link.el-link--primary:after{border-color:#409eff}.el-link.is-disabled{cursor:not-allowed}.el-link [class*=el-icon-]+span{margin-left:5px}.el-link.el-link--default{color:#606266}.el-link.el-link--default:hover{color:#409eff}.el-link.el-link--default.is-disabled{color:#c0c4cc}.el-link.el-link--primary{color:#409eff}.el-link.el-link--primary:hover{color:#66b1ff}.el-link.el-link--primary.is-disabled{color:#a0cfff}.el-link.el-link--danger.is-underline:hover:after,.el-link.el-link--danger:after{border-color:#f56c6c}.el-link.el-link--danger{color:#f56c6c}.el-link.el-link--danger:hover{color:#f78989}.el-link.el-link--danger.is-disabled{color:#fab6b6}.el-link.el-link--success.is-underline:hover:after,.el-link.el-link--success:after{border-color:#67c23a}.el-link.el-link--success{color:#67c23a}.el-link.el-link--success:hover{color:#85ce61}.el-link.el-link--success.is-disabled{color:#b3e19d}.el-link.el-link--warning.is-underline:hover:after,.el-link.el-link--warning:after{border-color:#e6a23c}.el-link.el-link--warning{color:#e6a23c}.el-link.el-link--warning:hover{color:#ebb563}.el-link.el-link--warning.is-disabled{color:#f3d19e}.el-link.el-link--info.is-underline:hover:after,.el-link.el-link--info:after{border-color:#909399}.el-link.el-link--info{color:#909399}.el-link.el-link--info:hover{color:#a6a9ad}.el-link.el-link--info.is-disabled{color:#c8c9cc}.el-divider{background-color:#dcdfe6;position:relative}.el-divider--horizontal{display:block;height:1px;width:100%;margin:24px 0}.el-divider--vertical{display:inline-block;width:1px;height:1em;margin:0 8px;vertical-align:middle;position:relative}.el-divider__text{position:absolute;background-color:#fff;padding:0 20px;color:#303133}.el-image__error,.el-image__placeholder{background:#f5f7fa}.el-divider__text.is-left{left:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-divider__text.is-center{left:50%;-webkit-transform:translateX(-50%) translateY(-50%);transform:translateX(-50%) translateY(-50%)}.el-divider__text.is-right{right:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-image__error,.el-image__inner,.el-image__placeholder{width:100%;height:100%}.el-image{position:relative;display:inline-block;overflow:hidden}.el-image__inner{vertical-align:top}.el-image__inner--center{position:relative;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);display:block}.el-image__error{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:14px;color:#c0c4cc;vertical-align:middle}.el-image__preview{cursor:pointer}.el-image-viewer__wrapper{position:fixed;top:0;right:0;bottom:0;left:0}.el-image-viewer__btn{position:absolute;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;border-radius:50%;opacity:.8;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box;user-select:none}.el-button,.el-checkbox,.el-image-viewer__btn{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-image-viewer__close{top:40px;right:40px;width:40px;height:40px;font-size:40px}.el-image-viewer__canvas{width:100%;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-image-viewer__actions{left:50%;bottom:30px;-webkit-transform:translateX(-50%);transform:translateX(-50%);width:282px;height:44px;padding:0 23px;background-color:#606266;border-color:#fff;border-radius:22px}.el-image-viewer__actions__inner{width:100%;height:100%;text-align:justify;cursor:default;font-size:23px;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-pack:distribute;justify-content:space-around}.el-image-viewer__next,.el-image-viewer__prev{top:50%;width:44px;height:44px;font-size:24px;color:#fff;background-color:#606266;border-color:#fff}.el-image-viewer__prev{left:40px}.el-image-viewer__next,.el-image-viewer__prev{-webkit-transform:translateY(-50%);transform:translateY(-50%)}.el-image-viewer__next{right:40px;text-indent:2px}.el-image-viewer__mask{position:absolute;width:100%;height:100%;top:0;left:0;opacity:.5;background:#000}.viewer-fade-enter-active{-webkit-animation:viewer-fade-in .3s;animation:viewer-fade-in .3s}.viewer-fade-leave-active{-webkit-animation:viewer-fade-out .3s;animation:viewer-fade-out .3s}@-webkit-keyframes viewer-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@keyframes viewer-fade-in{0%{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}to{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}}@-webkit-keyframes viewer-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}@keyframes viewer-fade-out{0%{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}to{-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0);opacity:0}}.el-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #dcdfe6;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;padding:12px 20px;font-size:14px;border-radius:4px}.el-button+.el-button{margin-left:10px}.el-button:focus,.el-button:hover{color:#409eff;border-color:#c6e2ff;background-color:#ecf5ff}.el-button:active{color:#3a8ee6;border-color:#3a8ee6;outline:0}.el-button::-moz-focus-inner{border:0}.el-button [class*=el-icon-]+span{margin-left:5px}.el-button.is-plain:focus,.el-button.is-plain:hover{background:#fff;border-color:#409eff;color:#409eff}.el-button.is-active,.el-button.is-plain:active{color:#3a8ee6;border-color:#3a8ee6}.el-button.is-plain:active{background:#fff;outline:0}.el-button.is-disabled,.el-button.is-disabled:focus,.el-button.is-disabled:hover{color:#c0c4cc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#ebeef5}.el-button.is-disabled.el-button--text{background-color:transparent}.el-button.is-disabled.is-plain,.el-button.is-disabled.is-plain:focus,.el-button.is-disabled.is-plain:hover{background-color:#fff;border-color:#ebeef5;color:#c0c4cc}.el-button.is-loading{position:relative;pointer-events:none}.el-button.is-loading:before{pointer-events:none;content:"";position:absolute;left:-1px;top:-1px;right:-1px;bottom:-1px;border-radius:inherit;background-color:hsla(0,0%,100%,.35)}.el-button.is-round{border-radius:20px;padding:12px 23px}.el-button.is-circle{border-radius:50%;padding:12px}.el-button--primary{color:#fff;background-color:#409eff;border-color:#409eff}.el-button--primary:focus,.el-button--primary:hover{background:#66b1ff;border-color:#66b1ff;color:#fff}.el-button--primary.is-active,.el-button--primary:active{background:#3a8ee6;border-color:#3a8ee6;color:#fff}.el-button--primary:active{outline:0}.el-button--primary.is-disabled,.el-button--primary.is-disabled:active,.el-button--primary.is-disabled:focus,.el-button--primary.is-disabled:hover{color:#fff;background-color:#a0cfff;border-color:#a0cfff}.el-button--primary.is-plain{color:#409eff;background:#ecf5ff;border-color:#b3d8ff}.el-button--primary.is-plain:focus,.el-button--primary.is-plain:hover{background:#409eff;border-color:#409eff;color:#fff}.el-button--primary.is-plain:active{background:#3a8ee6;border-color:#3a8ee6;color:#fff;outline:0}.el-button--primary.is-plain.is-disabled,.el-button--primary.is-plain.is-disabled:active,.el-button--primary.is-plain.is-disabled:focus,.el-button--primary.is-plain.is-disabled:hover{color:#8cc5ff;background-color:#ecf5ff;border-color:#d9ecff}.el-button--success{color:#fff;background-color:#67c23a;border-color:#67c23a}.el-button--success:focus,.el-button--success:hover{background:#85ce61;border-color:#85ce61;color:#fff}.el-button--success.is-active,.el-button--success:active{background:#5daf34;border-color:#5daf34;color:#fff}.el-button--success:active{outline:0}.el-button--success.is-disabled,.el-button--success.is-disabled:active,.el-button--success.is-disabled:focus,.el-button--success.is-disabled:hover{color:#fff;background-color:#b3e19d;border-color:#b3e19d}.el-button--success.is-plain{color:#67c23a;background:#f0f9eb;border-color:#c2e7b0}.el-button--success.is-plain:focus,.el-button--success.is-plain:hover{background:#67c23a;border-color:#67c23a;color:#fff}.el-button--success.is-plain:active{background:#5daf34;border-color:#5daf34;color:#fff;outline:0}.el-button--success.is-plain.is-disabled,.el-button--success.is-plain.is-disabled:active,.el-button--success.is-plain.is-disabled:focus,.el-button--success.is-plain.is-disabled:hover{color:#a4da89;background-color:#f0f9eb;border-color:#e1f3d8}.el-button--warning{color:#fff;background-color:#e6a23c;border-color:#e6a23c}.el-button--warning:focus,.el-button--warning:hover{background:#ebb563;border-color:#ebb563;color:#fff}.el-button--warning.is-active,.el-button--warning:active{background:#cf9236;border-color:#cf9236;color:#fff}.el-button--warning:active{outline:0}.el-button--warning.is-disabled,.el-button--warning.is-disabled:active,.el-button--warning.is-disabled:focus,.el-button--warning.is-disabled:hover{color:#fff;background-color:#f3d19e;border-color:#f3d19e}.el-button--warning.is-plain{color:#e6a23c;background:#fdf6ec;border-color:#f5dab1}.el-button--warning.is-plain:focus,.el-button--warning.is-plain:hover{background:#e6a23c;border-color:#e6a23c;color:#fff}.el-button--warning.is-plain:active{background:#cf9236;border-color:#cf9236;color:#fff;outline:0}.el-button--warning.is-plain.is-disabled,.el-button--warning.is-plain.is-disabled:active,.el-button--warning.is-plain.is-disabled:focus,.el-button--warning.is-plain.is-disabled:hover{color:#f0c78a;background-color:#fdf6ec;border-color:#faecd8}.el-button--danger{color:#fff;background-color:#f56c6c;border-color:#f56c6c}.el-button--danger:focus,.el-button--danger:hover{background:#f78989;border-color:#f78989;color:#fff}.el-button--danger.is-active,.el-button--danger:active{background:#dd6161;border-color:#dd6161;color:#fff}.el-button--danger:active{outline:0}.el-button--danger.is-disabled,.el-button--danger.is-disabled:active,.el-button--danger.is-disabled:focus,.el-button--danger.is-disabled:hover{color:#fff;background-color:#fab6b6;border-color:#fab6b6}.el-button--danger.is-plain{color:#f56c6c;background:#fef0f0;border-color:#fbc4c4}.el-button--danger.is-plain:focus,.el-button--danger.is-plain:hover{background:#f56c6c;border-color:#f56c6c;color:#fff}.el-button--danger.is-plain:active{background:#dd6161;border-color:#dd6161;color:#fff;outline:0}.el-button--danger.is-plain.is-disabled,.el-button--danger.is-plain.is-disabled:active,.el-button--danger.is-plain.is-disabled:focus,.el-button--danger.is-plain.is-disabled:hover{color:#f9a7a7;background-color:#fef0f0;border-color:#fde2e2}.el-button--info{color:#fff;background-color:#909399;border-color:#909399}.el-button--info:focus,.el-button--info:hover{background:#a6a9ad;border-color:#a6a9ad;color:#fff}.el-button--info.is-active,.el-button--info:active{background:#82848a;border-color:#82848a;color:#fff}.el-button--info:active{outline:0}.el-button--info.is-disabled,.el-button--info.is-disabled:active,.el-button--info.is-disabled:focus,.el-button--info.is-disabled:hover{color:#fff;background-color:#c8c9cc;border-color:#c8c9cc}.el-button--info.is-plain{color:#909399;background:#f4f4f5;border-color:#d3d4d6}.el-button--info.is-plain:focus,.el-button--info.is-plain:hover{background:#909399;border-color:#909399;color:#fff}.el-button--info.is-plain:active{background:#82848a;border-color:#82848a;color:#fff;outline:0}.el-button--info.is-plain.is-disabled,.el-button--info.is-plain.is-disabled:active,.el-button--info.is-plain.is-disabled:focus,.el-button--info.is-plain.is-disabled:hover{color:#bcbec2;background-color:#f4f4f5;border-color:#e9e9eb}.el-button--text,.el-button--text.is-disabled,.el-button--text.is-disabled:focus,.el-button--text.is-disabled:hover,.el-button--text:active{border-color:transparent}.el-button--medium{padding:10px 20px;font-size:14px;border-radius:4px}.el-button--mini,.el-button--small{font-size:12px;border-radius:3px}.el-button--medium.is-round{padding:10px 20px}.el-button--medium.is-circle{padding:10px}.el-button--small,.el-button--small.is-round{padding:9px 15px}.el-button--small.is-circle{padding:9px}.el-button--mini,.el-button--mini.is-round{padding:7px 15px}.el-button--mini.is-circle{padding:7px}.el-button--text{color:#409eff;background:0 0;padding-left:0;padding-right:0}.el-button--text:focus,.el-button--text:hover{color:#66b1ff;border-color:transparent;background-color:transparent}.el-button--text:active{color:#3a8ee6;background-color:transparent}.el-button-group{display:inline-block;vertical-align:middle}.el-button-group:after,.el-button-group:before{display:table;content:""}.el-button-group:after{clear:both}.el-button-group>.el-button{float:left;position:relative}.el-button-group>.el-button+.el-button{margin-left:0}.el-button-group>.el-button.is-disabled{z-index:1}.el-button-group>.el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.el-button-group>.el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.el-button-group>.el-button:first-child:last-child{border-radius:4px}.el-button-group>.el-button:first-child:last-child.is-round{border-radius:20px}.el-button-group>.el-button:first-child:last-child.is-circle{border-radius:50%}.el-button-group>.el-button:not(:first-child):not(:last-child){border-radius:0}.el-button-group>.el-button:not(:last-child){margin-right:-1px}.el-button-group>.el-button.is-active,.el-button-group>.el-button:active,.el-button-group>.el-button:focus,.el-button-group>.el-button:hover{z-index:1}.el-button-group>.el-dropdown>.el-button{border-top-left-radius:0;border-bottom-left-radius:0;border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--primary:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--primary:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--primary:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--success:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--success:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--success:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--warning:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--warning:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--warning:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--danger:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--danger:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--danger:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--info:first-child{border-right-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--info:last-child{border-left-color:hsla(0,0%,100%,.5)}.el-button-group .el-button--info:not(:first-child):not(:last-child){border-left-color:hsla(0,0%,100%,.5);border-right-color:hsla(0,0%,100%,.5)}.el-calendar{background-color:#fff}.el-calendar__header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:12px 20px;border-bottom:1px solid #ebeef5}.el-backtop,.el-page-header{display:-webkit-box;display:-ms-flexbox}.el-calendar__title{color:#000;-ms-flex-item-align:center;align-self:center}.el-calendar__body{padding:12px 20px 35px}.el-calendar-table{table-layout:fixed;width:100%}.el-calendar-table thead th{padding:12px 0;color:#606266;font-weight:400}.el-calendar-table:not(.is-range) td.next,.el-calendar-table:not(.is-range) td.prev{color:#c0c4cc}.el-backtop,.el-calendar-table td.is-today{color:#409eff}.el-calendar-table td{border-bottom:1px solid #ebeef5;border-right:1px solid #ebeef5;vertical-align:top;-webkit-transition:background-color .2s ease;transition:background-color .2s ease}.el-calendar-table td.is-selected{background-color:#f2f8fe}.el-calendar-table tr:first-child td{border-top:1px solid #ebeef5}.el-calendar-table tr td:first-child{border-left:1px solid #ebeef5}.el-calendar-table tr.el-calendar-table__row--hide-border td{border-top:none}.el-calendar-table .el-calendar-day{-webkit-box-sizing:border-box;box-sizing:border-box;padding:8px;height:85px}.el-calendar-table .el-calendar-day:hover{cursor:pointer;background-color:#f2f8fe}.el-backtop{position:fixed;background-color:#fff;width:40px;height:40px;border-radius:50%;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;font-size:20px;-webkit-box-shadow:0 0 6px rgba(0,0,0,.12);box-shadow:0 0 6px rgba(0,0,0,.12);cursor:pointer;z-index:5}.el-backtop:hover{background-color:#f2f6fc}.el-page-header{line-height:24px}.el-page-header,.el-page-header__left{display:-webkit-box;display:-ms-flexbox;display:flex}.el-page-header__left{cursor:pointer;margin-right:40px;position:relative}.el-page-header__left:after{content:"";position:absolute;width:1px;height:16px;right:-20px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);background-color:#dcdfe6}.el-checkbox,.el-checkbox__input{display:inline-block;position:relative;white-space:nowrap}.el-page-header__left .el-icon-back{font-size:18px;margin-right:6px;-ms-flex-item-align:center;align-self:center}.el-page-header__title{font-size:14px;font-weight:500}.el-page-header__content{font-size:18px;color:#303133}.el-checkbox{color:#606266;font-size:14px;cursor:pointer;user-select:none;margin-right:30px}.el-checkbox,.el-checkbox-button__inner,.el-radio{font-weight:500;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.el-checkbox.is-bordered{padding:9px 20px 9px 10px;border-radius:4px;border:1px solid #dcdfe6;-webkit-box-sizing:border-box;box-sizing:border-box;line-height:normal;height:40px}.el-checkbox.is-bordered.is-checked{border-color:#409eff}.el-checkbox.is-bordered.is-disabled{border-color:#ebeef5;cursor:not-allowed}.el-checkbox.is-bordered+.el-checkbox.is-bordered{margin-left:10px}.el-checkbox.is-bordered.el-checkbox--medium{padding:7px 20px 7px 10px;border-radius:4px;height:36px}.el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label{line-height:17px;font-size:14px}.el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__inner{height:14px;width:14px}.el-checkbox.is-bordered.el-checkbox--small{padding:5px 15px 5px 10px;border-radius:3px;height:32px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{line-height:15px;font-size:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner:after{height:6px;width:2px}.el-checkbox.is-bordered.el-checkbox--mini{padding:3px 15px 3px 10px;border-radius:3px;height:28px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__label{line-height:12px;font-size:12px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner{height:12px;width:12px}.el-checkbox.is-bordered.el-checkbox--mini .el-checkbox__inner:after{height:6px;width:2px}.el-checkbox__input{cursor:pointer;outline:0;line-height:1;vertical-align:middle}.el-checkbox__input.is-disabled .el-checkbox__inner{background-color:#edf2fc;border-color:#dcdfe6;cursor:not-allowed}.el-checkbox__input.is-disabled .el-checkbox__inner:after{cursor:not-allowed;border-color:#c0c4cc}.el-checkbox__input.is-disabled .el-checkbox__inner+.el-checkbox__label{cursor:not-allowed}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:#f2f6fc;border-color:#dcdfe6}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner:after{border-color:#c0c4cc}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:#f2f6fc;border-color:#dcdfe6}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner:before{background-color:#c0c4cc;border-color:#c0c4cc}.el-checkbox__input.is-checked .el-checkbox__inner,.el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:#409eff;border-color:#409eff}.el-checkbox__input.is-disabled+span.el-checkbox__label{color:#c0c4cc;cursor:not-allowed}.el-checkbox__input.is-checked .el-checkbox__inner:after{-webkit-transform:rotate(45deg) scaleY(1);transform:rotate(45deg) scaleY(1)}.el-checkbox__input.is-checked+.el-checkbox__label{color:#409eff}.el-checkbox__input.is-focus .el-checkbox__inner{border-color:#409eff}.el-checkbox__input.is-indeterminate .el-checkbox__inner:before{content:"";position:absolute;display:block;background-color:#fff;height:2px;-webkit-transform:scale(.5);transform:scale(.5);left:0;right:0;top:5px}.el-checkbox__input.is-indeterminate .el-checkbox__inner:after{display:none}.el-checkbox__inner{display:inline-block;position:relative;border:1px solid #dcdfe6;border-radius:2px;-webkit-box-sizing:border-box;box-sizing:border-box;width:14px;height:14px;background-color:#fff;z-index:1;-webkit-transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46)}.el-checkbox__inner:hover{border-color:#409eff}.el-checkbox__inner:after{-webkit-box-sizing:content-box;box-sizing:content-box;content:"";border:1px solid #fff;border-left:0;border-top:0;height:7px;left:4px;position:absolute;top:1px;-webkit-transform:rotate(45deg) scaleY(0);transform:rotate(45deg) scaleY(0);width:3px;-webkit-transition:-webkit-transform .15s ease-in .05s;transition:-webkit-transform .15s ease-in .05s;transition:transform .15s ease-in .05s;transition:transform .15s ease-in .05s,-webkit-transform .15s ease-in .05s;-webkit-transform-origin:center;transform-origin:center}.el-checkbox__original{opacity:0;outline:0;position:absolute;margin:0;width:0;height:0;z-index:-1}.el-checkbox-button,.el-checkbox-button__inner{display:inline-block;position:relative}.el-checkbox__label{display:inline-block;padding-left:10px;line-height:19px;font-size:14px}.el-checkbox:last-of-type{margin-right:0}.el-checkbox-button__inner{line-height:1;white-space:nowrap;vertical-align:middle;cursor:pointer;background:#fff;border:1px solid #dcdfe6;border-left:0;color:#606266;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);padding:12px 20px;font-size:14px;border-radius:0}.el-checkbox-button__inner.is-round{padding:12px 20px}.el-checkbox-button__inner:hover{color:#409eff}.el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.el-radio,.el-radio__input{line-height:1;outline:0;white-space:nowrap}.el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.el-checkbox-button__original{opacity:0;outline:0;position:absolute;margin:0;z-index:-1}.el-radio,.el-radio__inner,.el-radio__input{position:relative;display:inline-block}.el-checkbox-button.is-checked .el-checkbox-button__inner{color:#fff;background-color:#409eff;border-color:#409eff;-webkit-box-shadow:-1px 0 0 0 #8cc5ff;box-shadow:-1px 0 0 0 #8cc5ff}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner{border-left-color:#409eff}.el-checkbox-button.is-disabled .el-checkbox-button__inner{color:#c0c4cc;cursor:not-allowed;background-image:none;background-color:#fff;border-color:#ebeef5;-webkit-box-shadow:none;box-shadow:none}.el-checkbox-button.is-disabled:first-child .el-checkbox-button__inner{border-left-color:#ebeef5}.el-checkbox-button:first-child .el-checkbox-button__inner{border-left:1px solid #dcdfe6;border-radius:4px 0 0 4px;-webkit-box-shadow:none!important;box-shadow:none!important}.el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:#409eff}.el-checkbox-button:last-child .el-checkbox-button__inner{border-radius:0 4px 4px 0}.el-checkbox-button--medium .el-checkbox-button__inner{padding:10px 20px;font-size:14px;border-radius:0}.el-checkbox-button--medium .el-checkbox-button__inner.is-round{padding:10px 20px}.el-checkbox-button--small .el-checkbox-button__inner{padding:9px 15px;font-size:12px;border-radius:0}.el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:9px 15px}.el-checkbox-button--mini .el-checkbox-button__inner{padding:7px 15px;font-size:12px;border-radius:0}.el-checkbox-button--mini .el-checkbox-button__inner.is-round{padding:7px 15px}.el-checkbox-group{font-size:0}.el-radio,.el-radio--medium.is-bordered .el-radio__label{font-size:14px}.el-radio{color:#606266;cursor:pointer;margin-right:30px}.el-cascader-node>.el-radio,.el-radio:last-child{margin-right:0}.el-radio.is-bordered{padding:12px 20px 0 10px;border-radius:4px;border:1px solid #dcdfe6;-webkit-box-sizing:border-box;box-sizing:border-box;height:40px}.el-radio.is-bordered.is-checked{border-color:#409eff}.el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:#ebeef5}.el-radio__input.is-disabled .el-radio__inner,.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:#f5f7fa;border-color:#e4e7ed}.el-radio.is-bordered+.el-radio.is-bordered{margin-left:10px}.el-radio--medium.is-bordered{padding:10px 20px 0 10px;border-radius:4px;height:36px}.el-radio--mini.is-bordered .el-radio__label,.el-radio--small.is-bordered .el-radio__label{font-size:12px}.el-radio--medium.is-bordered .el-radio__inner{height:14px;width:14px}.el-radio--small.is-bordered{padding:8px 15px 0 10px;border-radius:3px;height:32px}.el-radio--small.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio--mini.is-bordered{padding:6px 15px 0 10px;border-radius:3px;height:28px}.el-radio--mini.is-bordered .el-radio__inner{height:12px;width:12px}.el-radio__input{cursor:pointer;vertical-align:middle}.el-radio__input.is-disabled .el-radio__inner{cursor:not-allowed}.el-radio__input.is-disabled .el-radio__inner:after{cursor:not-allowed;background-color:#f5f7fa}.el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.el-radio__input.is-disabled.is-checked .el-radio__inner:after{background-color:#c0c4cc}.el-radio__input.is-disabled+span.el-radio__label{color:#c0c4cc;cursor:not-allowed}.el-radio__input.is-checked .el-radio__inner{border-color:#409eff;background:#409eff}.el-radio__input.is-checked .el-radio__inner:after{-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}.el-radio__input.is-checked+.el-radio__label{color:#409eff}.el-radio__input.is-focus .el-radio__inner{border-color:#409eff}.el-radio__inner{border:1px solid #dcdfe6;border-radius:100%;width:14px;height:14px;background-color:#fff;cursor:pointer;-webkit-box-sizing:border-box;box-sizing:border-box}.el-radio__inner:hover{border-color:#409eff}.el-radio__inner:after{width:4px;height:4px;border-radius:100%;background-color:#fff;content:"";position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%) scale(0);transform:translate(-50%,-50%) scale(0);-webkit-transition:-webkit-transform .15s ease-in;transition:-webkit-transform .15s ease-in;transition:transform .15s ease-in;transition:transform .15s ease-in,-webkit-transform .15s ease-in}.el-radio__original{opacity:0;outline:0;position:absolute;z-index:-1;top:0;left:0;right:0;bottom:0;margin:0}.el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner{-webkit-box-shadow:0 0 2px 2px #409eff;box-shadow:0 0 2px 2px #409eff}.el-radio__label{font-size:14px;padding-left:10px}.el-scrollbar{overflow:hidden;position:relative}.el-scrollbar:active>.el-scrollbar__bar,.el-scrollbar:focus>.el-scrollbar__bar,.el-scrollbar:hover>.el-scrollbar__bar{opacity:1;-webkit-transition:opacity .34s ease-out;transition:opacity .34s ease-out}.el-scrollbar__wrap{overflow:scroll;height:100%}.el-scrollbar__wrap--hidden-default{scrollbar-width:none}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{width:0;height:0}.el-scrollbar__thumb{position:relative;display:block;width:0;height:0;cursor:pointer;border-radius:inherit;background-color:rgba(144,147,153,.3);-webkit-transition:background-color .3s;transition:background-color .3s}.el-scrollbar__thumb:hover{background-color:rgba(144,147,153,.5)}.el-scrollbar__bar{position:absolute;right:2px;bottom:2px;z-index:1;border-radius:4px;opacity:0;-webkit-transition:opacity .12s ease-out;transition:opacity .12s ease-out}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}.el-cascader-panel{display:-webkit-box;display:-ms-flexbox;display:flex;border-radius:4px;font-size:14px}.el-cascader-panel.is-bordered{border:1px solid #e4e7ed;border-radius:4px}.el-cascader-menu{min-width:180px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#606266;border-right:1px solid #e4e7ed}.el-cascader-menu:last-child{border-right:none}.el-cascader-menu:last-child .el-cascader-node{padding-right:20px}.el-cascader-menu__wrap{height:204px}.el-cascader-menu__list{position:relative;min-height:100%;margin:0;padding:6px 0;list-style:none;-webkit-box-sizing:border-box;box-sizing:border-box}.el-avatar,.el-drawer{-webkit-box-sizing:border-box;overflow:hidden}.el-cascader-menu__hover-zone{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}.el-cascader-menu__empty-text{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);text-align:center;color:#c0c4cc}.el-cascader-node{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 30px 0 20px;height:34px;line-height:34px;outline:0}.el-cascader-node.is-selectable.in-active-path{color:#606266}.el-cascader-node.in-active-path,.el-cascader-node.is-active,.el-cascader-node.is-selectable.in-checked-path{color:#409eff;font-weight:700}.el-cascader-node:not(.is-disabled){cursor:pointer}.el-cascader-node:not(.is-disabled):focus,.el-cascader-node:not(.is-disabled):hover{background:#f5f7fa}.el-cascader-node.is-disabled{color:#c0c4cc;cursor:not-allowed}.el-cascader-node__prefix{position:absolute;left:10px}.el-cascader-node__postfix{position:absolute;right:10px}.el-cascader-node__label{-webkit-box-flex:1;-ms-flex:1;flex:1;padding:0 10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.el-cascader-node>.el-radio .el-radio__label{padding-left:0}.el-avatar{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;color:#fff;background:#c0c4cc;width:40px;height:40px;line-height:40px;font-size:14px}.el-avatar>img{display:block;height:100%;vertical-align:middle}.el-drawer,.el-drawer__header{display:-webkit-box;display:-ms-flexbox}.el-avatar--circle{border-radius:50%}.el-avatar--square{border-radius:4px}.el-avatar--icon{font-size:18px}.el-avatar--large{width:40px;height:40px;line-height:40px}.el-avatar--medium{width:36px;height:36px;line-height:36px}.el-avatar--small{width:28px;height:28px;line-height:28px}.el-drawer.btt,.el-drawer.ttb,.el-drawer__container{left:0;right:0;width:100%}.el-drawer.ltr,.el-drawer.rtl,.el-drawer__container{top:0;bottom:0;height:100%}@-webkit-keyframes el-drawer-fade-in{0%{opacity:0}to{opacity:1}}@keyframes el-drawer-fade-in{0%{opacity:0}to{opacity:1}}@-webkit-keyframes rtl-drawer-in{0%{-webkit-transform:translate(100%);transform:translate(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes rtl-drawer-in{0%{-webkit-transform:translate(100%);transform:translate(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes rtl-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translate(100%);transform:translate(100%)}}@keyframes rtl-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translate(100%);transform:translate(100%)}}@-webkit-keyframes ltr-drawer-in{0%{-webkit-transform:translate(-100%);transform:translate(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ltr-drawer-in{0%{-webkit-transform:translate(-100%);transform:translate(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes ltr-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translate(-100%);transform:translate(-100%)}}@keyframes ltr-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translate(-100%);transform:translate(-100%)}}@-webkit-keyframes ttb-drawer-in{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes ttb-drawer-in{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes ttb-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(-100%);transform:translateY(-100%)}}@keyframes ttb-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(-100%);transform:translateY(-100%)}}@-webkit-keyframes btt-drawer-in{0%{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@keyframes btt-drawer-in{0%{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translate(0);transform:translate(0)}}@-webkit-keyframes btt-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(100%);transform:translateY(100%)}}@keyframes btt-drawer-out{0%{-webkit-transform:translate(0);transform:translate(0)}to{-webkit-transform:translateY(100%);transform:translateY(100%)}}.el-drawer{position:absolute;-webkit-box-sizing:border-box;box-sizing:border-box;background-color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;-webkit-box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12);box-shadow:0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12)}.el-drawer.rtl{-webkit-animation:rtl-drawer-out .3s;animation:rtl-drawer-out .3s;right:0}.el-drawer__open .el-drawer.rtl{-webkit-animation:rtl-drawer-in .3s 1ms;animation:rtl-drawer-in .3s 1ms}.el-drawer.ltr{-webkit-animation:ltr-drawer-out .3s;animation:ltr-drawer-out .3s;left:0}.el-drawer__open .el-drawer.ltr{-webkit-animation:ltr-drawer-in .3s 1ms;animation:ltr-drawer-in .3s 1ms}.el-drawer.ttb{-webkit-animation:ttb-drawer-out .3s;animation:ttb-drawer-out .3s;top:0}.el-drawer__open .el-drawer.ttb{-webkit-animation:ttb-drawer-in .3s 1ms;animation:ttb-drawer-in .3s 1ms}.el-drawer.btt{-webkit-animation:btt-drawer-out .3s;animation:btt-drawer-out .3s;bottom:0}.el-drawer__open .el-drawer.btt{-webkit-animation:btt-drawer-in .3s 1ms;animation:btt-drawer-in .3s 1ms}.el-drawer__wrapper{position:fixed;top:0;right:0;bottom:0;left:0;overflow:hidden;margin:0}.el-drawer__header{-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:#72767b;display:-webkit-box;display:-ms-flexbox;display:flex;margin-bottom:32px;padding:20px 20px 0}.el-drawer__header>:first-child,.el-drawer__title{-webkit-box-flex:1;-ms-flex:1;flex:1}.el-drawer__title{margin:0;line-height:inherit;font-size:1rem}.el-drawer__close-btn{border:none;cursor:pointer;font-size:20px;color:inherit;background-color:transparent}.el-drawer__body{-webkit-box-flex:1;-ms-flex:1;flex:1}.el-drawer__body>*{-webkit-box-sizing:border-box;box-sizing:border-box}.el-drawer__container{position:relative}.el-drawer-fade-enter-active{-webkit-animation:el-drawer-fade-in .3s;animation:el-drawer-fade-in .3s}.el-drawer-fade-leave-active{animation:el-drawer-fade-in .3s reverse}.el-popconfirm__main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.el-popconfirm__icon{margin-right:5px}.el-popconfirm__action{text-align:right;margin:0} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-f625.25a6a4ae.css b/priv/static/adminfe/chunk-f625.25a6a4ae.css new file mode 100644 index 000000000..ac26ef0f5 --- /dev/null +++ b/priv/static/adminfe/chunk-f625.25a6a4ae.css @@ -0,0 +1 @@ +.actions-button[data-v-794b0bb8]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-794b0bb8]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-794b0bb8]{float:right}.el-icon-edit[data-v-794b0bb8]{margin-right:5px}.tag-container[data-v-794b0bb8]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-794b0bb8]{padding-right:20px}.no-hover[data-v-794b0bb8]:hover{color:#606266;background-color:#fff;cursor:auto} \ No newline at end of file diff --git a/priv/static/adminfe/chunk-libs.0380664d.css b/priv/static/adminfe/chunk-libs.5cf7f50a.css similarity index 100% rename from priv/static/adminfe/chunk-libs.0380664d.css rename to priv/static/adminfe/chunk-libs.5cf7f50a.css diff --git a/priv/static/adminfe/index.html b/priv/static/adminfe/index.html index 0500424b6..d6b9b22b8 100644 --- a/priv/static/adminfe/index.html +++ b/priv/static/adminfe/index.html @@ -1 +1 @@ -Admin FE
\ No newline at end of file +Admin FE
\ No newline at end of file diff --git a/priv/static/adminfe/static/js/app.1df22cde.js b/priv/static/adminfe/static/js/app.1df22cde.js deleted file mode 100644 index 00a5fbcd3..000000000 Binary files a/priv/static/adminfe/static/js/app.1df22cde.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/app.1df22cde.js.map b/priv/static/adminfe/static/js/app.1df22cde.js.map deleted file mode 100644 index 4f6ad8e95..000000000 Binary files a/priv/static/adminfe/static/js/app.1df22cde.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/app.69891fda.js b/priv/static/adminfe/static/js/app.69891fda.js new file mode 100644 index 000000000..3d04d9273 Binary files /dev/null and b/priv/static/adminfe/static/js/app.69891fda.js differ diff --git a/priv/static/adminfe/static/js/app.69891fda.js.map b/priv/static/adminfe/static/js/app.69891fda.js.map new file mode 100644 index 000000000..0131793e9 Binary files /dev/null and b/priv/static/adminfe/static/js/app.69891fda.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-0171.9e927b8a.js b/priv/static/adminfe/static/js/chunk-0171.9e927b8a.js deleted file mode 100644 index f20f619ad..000000000 Binary files a/priv/static/adminfe/static/js/chunk-0171.9e927b8a.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-0171.9e927b8a.js.map b/priv/static/adminfe/static/js/chunk-0171.9e927b8a.js.map deleted file mode 100644 index 4f2d63f3e..000000000 Binary files a/priv/static/adminfe/static/js/chunk-0171.9e927b8a.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js b/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js new file mode 100644 index 000000000..b4601abae Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js differ diff --git a/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js.map b/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js.map new file mode 100644 index 000000000..193c65bb1 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-03c5.1c694c49.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-16d0.6ce78978.js b/priv/static/adminfe/static/js/chunk-16d0.6ce78978.js deleted file mode 100644 index 497bbcb88..000000000 Binary files a/priv/static/adminfe/static/js/chunk-16d0.6ce78978.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-16d0.6ce78978.js.map b/priv/static/adminfe/static/js/chunk-16d0.6ce78978.js.map deleted file mode 100644 index 17c3378e3..000000000 Binary files a/priv/static/adminfe/static/js/chunk-16d0.6ce78978.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-16d0.fef0ce65.js b/priv/static/adminfe/static/js/chunk-16d0.fef0ce65.js new file mode 100644 index 000000000..8bddbe967 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-16d0.fef0ce65.js differ diff --git a/priv/static/adminfe/static/js/chunk-16d0.fef0ce65.js.map b/priv/static/adminfe/static/js/chunk-16d0.fef0ce65.js.map new file mode 100644 index 000000000..9617ae0d0 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-16d0.fef0ce65.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-176e.5c19378d.js.map b/priv/static/adminfe/static/js/chunk-176e.5c19378d.js.map deleted file mode 100644 index fa116fb3b..000000000 Binary files a/priv/static/adminfe/static/js/chunk-176e.5c19378d.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-176e.5c19378d.js b/priv/static/adminfe/static/js/chunk-176e.be050aba.js similarity index 59% rename from priv/static/adminfe/static/js/chunk-176e.5c19378d.js rename to priv/static/adminfe/static/js/chunk-176e.be050aba.js index 65269ccf1..cab83489b 100644 Binary files a/priv/static/adminfe/static/js/chunk-176e.5c19378d.js and b/priv/static/adminfe/static/js/chunk-176e.be050aba.js differ diff --git a/priv/static/adminfe/static/js/chunk-176e.be050aba.js.map b/priv/static/adminfe/static/js/chunk-176e.be050aba.js.map new file mode 100644 index 000000000..bff959cd6 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-176e.be050aba.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-2d97.079e9e64.js b/priv/static/adminfe/static/js/chunk-2d97.079e9e64.js deleted file mode 100644 index 90399920a..000000000 Binary files a/priv/static/adminfe/static/js/chunk-2d97.079e9e64.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-2d97.079e9e64.js.map b/priv/static/adminfe/static/js/chunk-2d97.079e9e64.js.map deleted file mode 100644 index 5e3e417cd..000000000 Binary files a/priv/static/adminfe/static/js/chunk-2d97.079e9e64.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js b/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js new file mode 100644 index 000000000..421bf2a99 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js differ diff --git a/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js.map b/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js.map new file mode 100644 index 000000000..d2ad4d9aa Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-3365.b73c30a8.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-342d.479e01dd.js b/priv/static/adminfe/static/js/chunk-342d.479e01dd.js new file mode 100644 index 000000000..5ee311c4a Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-342d.479e01dd.js differ diff --git a/priv/static/adminfe/static/js/chunk-342d.479e01dd.js.map b/priv/static/adminfe/static/js/chunk-342d.479e01dd.js.map new file mode 100644 index 000000000..b73bbb0aa Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-342d.479e01dd.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-40a4.5dc0e299.js b/priv/static/adminfe/static/js/chunk-40a4.5dc0e299.js deleted file mode 100644 index ee0e267db..000000000 Binary files a/priv/static/adminfe/static/js/chunk-40a4.5dc0e299.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-40a4.5dc0e299.js.map b/priv/static/adminfe/static/js/chunk-40a4.5dc0e299.js.map deleted file mode 100644 index 61c30c39b..000000000 Binary files a/priv/static/adminfe/static/js/chunk-40a4.5dc0e299.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-5118.7c48ad58.js b/priv/static/adminfe/static/js/chunk-5118.7c48ad58.js deleted file mode 100644 index 2357e225d..000000000 Binary files a/priv/static/adminfe/static/js/chunk-5118.7c48ad58.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-5118.7c48ad58.js.map b/priv/static/adminfe/static/js/chunk-5118.7c48ad58.js.map deleted file mode 100644 index c29b4b170..000000000 Binary files a/priv/static/adminfe/static/js/chunk-5118.7c48ad58.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-521f.748b331d.js b/priv/static/adminfe/static/js/chunk-521f.748b331d.js new file mode 100644 index 000000000..570dab224 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-521f.748b331d.js differ diff --git a/priv/static/adminfe/static/js/chunk-521f.748b331d.js.map b/priv/static/adminfe/static/js/chunk-521f.748b331d.js.map new file mode 100644 index 000000000..3380bbbd5 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-521f.748b331d.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-546f.81668ba7.js b/priv/static/adminfe/static/js/chunk-546f.81668ba7.js new file mode 100644 index 000000000..252991021 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-546f.81668ba7.js differ diff --git a/priv/static/adminfe/static/js/chunk-546f.81668ba7.js.map b/priv/static/adminfe/static/js/chunk-546f.81668ba7.js.map new file mode 100644 index 000000000..6a9d2a8dc Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-546f.81668ba7.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-565e.e1555105.js b/priv/static/adminfe/static/js/chunk-565e.e1555105.js deleted file mode 100644 index 638c78b38..000000000 Binary files a/priv/static/adminfe/static/js/chunk-565e.e1555105.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-565e.e1555105.js.map b/priv/static/adminfe/static/js/chunk-565e.e1555105.js.map deleted file mode 100644 index 1cfc4cdfa..000000000 Binary files a/priv/static/adminfe/static/js/chunk-565e.e1555105.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-60a9.22fe45f3.js b/priv/static/adminfe/static/js/chunk-60a9.22fe45f3.js deleted file mode 100644 index a23d46b72..000000000 Binary files a/priv/static/adminfe/static/js/chunk-60a9.22fe45f3.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-60a9.22fe45f3.js.map b/priv/static/adminfe/static/js/chunk-60a9.22fe45f3.js.map deleted file mode 100644 index 690979713..000000000 Binary files a/priv/static/adminfe/static/js/chunk-60a9.22fe45f3.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-654e.38dd4b85.js b/priv/static/adminfe/static/js/chunk-654d.653b067f.js similarity index 58% rename from priv/static/adminfe/static/js/chunk-654e.38dd4b85.js rename to priv/static/adminfe/static/js/chunk-654d.653b067f.js index eecdac498..209873ec1 100644 Binary files a/priv/static/adminfe/static/js/chunk-654e.38dd4b85.js and b/priv/static/adminfe/static/js/chunk-654d.653b067f.js differ diff --git a/priv/static/adminfe/static/js/chunk-654d.653b067f.js.map b/priv/static/adminfe/static/js/chunk-654d.653b067f.js.map new file mode 100644 index 000000000..72aca0e98 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-654d.653b067f.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-654e.38dd4b85.js.map b/priv/static/adminfe/static/js/chunk-654e.38dd4b85.js.map deleted file mode 100644 index 4fc105fb7..000000000 Binary files a/priv/static/adminfe/static/js/chunk-654e.38dd4b85.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-68ea.0dae7e55.js b/priv/static/adminfe/static/js/chunk-68ea.0dae7e55.js deleted file mode 100644 index dc31a8bb0..000000000 Binary files a/priv/static/adminfe/static/js/chunk-68ea.0dae7e55.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-68ea.0dae7e55.js.map b/priv/static/adminfe/static/js/chunk-68ea.0dae7e55.js.map deleted file mode 100644 index 6c87803a8..000000000 Binary files a/priv/static/adminfe/static/js/chunk-68ea.0dae7e55.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-68ea.6d56674a.js b/priv/static/adminfe/static/js/chunk-68ea.6d56674a.js new file mode 100644 index 000000000..1f43a39db Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-68ea.6d56674a.js differ diff --git a/priv/static/adminfe/static/js/chunk-68ea.6d56674a.js.map b/priv/static/adminfe/static/js/chunk-68ea.6d56674a.js.map new file mode 100644 index 000000000..45000a1b0 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-68ea.6d56674a.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js b/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js new file mode 100644 index 000000000..02091ed84 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js differ diff --git a/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js.map b/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js.map new file mode 100644 index 000000000..019cede66 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-68ea9.9821cd6a.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-6e81.6043af74.js b/priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js similarity index 97% rename from priv/static/adminfe/static/js/chunk-6e81.6043af74.js rename to priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js index 82b08ad24..cd79db1d3 100644 Binary files a/priv/static/adminfe/static/js/chunk-6e81.6043af74.js and b/priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js differ diff --git a/priv/static/adminfe/static/js/chunk-6e81.6043af74.js.map b/priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js.map similarity index 98% rename from priv/static/adminfe/static/js/chunk-6e81.6043af74.js.map rename to priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js.map index 2c1c86e2c..10b437760 100644 Binary files a/priv/static/adminfe/static/js/chunk-6e81.6043af74.js.map and b/priv/static/adminfe/static/js/chunk-6e81.ebe9039f.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-6e8c.2aa335e0.js b/priv/static/adminfe/static/js/chunk-6e8c.2aa335e0.js deleted file mode 100644 index 020158f81..000000000 Binary files a/priv/static/adminfe/static/js/chunk-6e8c.2aa335e0.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-6e8c.2aa335e0.js.map b/priv/static/adminfe/static/js/chunk-6e8c.2aa335e0.js.map deleted file mode 100644 index 1e742c3f9..000000000 Binary files a/priv/static/adminfe/static/js/chunk-6e8c.2aa335e0.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-7503.278e0031.js b/priv/static/adminfe/static/js/chunk-7503.278e0031.js deleted file mode 100644 index 3875f9ad9..000000000 Binary files a/priv/static/adminfe/static/js/chunk-7503.278e0031.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-7503.278e0031.js.map b/priv/static/adminfe/static/js/chunk-7503.278e0031.js.map deleted file mode 100644 index 494d1a763..000000000 Binary files a/priv/static/adminfe/static/js/chunk-7503.278e0031.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-7c6b.1ebeb0e4.js b/priv/static/adminfe/static/js/chunk-7c6b.1ebeb0e4.js deleted file mode 100644 index 63be4d84f..000000000 Binary files a/priv/static/adminfe/static/js/chunk-7c6b.1ebeb0e4.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-7c6b.1ebeb0e4.js.map b/priv/static/adminfe/static/js/chunk-7c6b.1ebeb0e4.js.map deleted file mode 100644 index 85d8dcb1c..000000000 Binary files a/priv/static/adminfe/static/js/chunk-7c6b.1ebeb0e4.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js b/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js new file mode 100644 index 000000000..df9b7d6ac Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js differ diff --git a/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js.map b/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js.map new file mode 100644 index 000000000..6584ba082 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-7c6b.56a14571.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js b/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js new file mode 100644 index 000000000..a2c2df2e7 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js differ diff --git a/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js.map b/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js.map new file mode 100644 index 000000000..7f7718547 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-850d.3e6102c2.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-97e2.c51fe6b0.js b/priv/static/adminfe/static/js/chunk-97e2.c51fe6b0.js deleted file mode 100644 index a9cef7b6b..000000000 Binary files a/priv/static/adminfe/static/js/chunk-97e2.c51fe6b0.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-97e2.c51fe6b0.js.map b/priv/static/adminfe/static/js/chunk-97e2.c51fe6b0.js.map deleted file mode 100644 index 1d489f4c2..000000000 Binary files a/priv/static/adminfe/static/js/chunk-97e2.c51fe6b0.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-9a72.41e843cd.js b/priv/static/adminfe/static/js/chunk-9a72.41e843cd.js deleted file mode 100644 index 575a01d30..000000000 Binary files a/priv/static/adminfe/static/js/chunk-9a72.41e843cd.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-9a72.41e843cd.js.map b/priv/static/adminfe/static/js/chunk-9a72.41e843cd.js.map deleted file mode 100644 index aede70d0a..000000000 Binary files a/priv/static/adminfe/static/js/chunk-9a72.41e843cd.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js b/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js new file mode 100644 index 000000000..89d35af79 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js differ diff --git a/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js.map b/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js.map new file mode 100644 index 000000000..fa8694b8e Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-9d55.7af22f45.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-commons.51fe2926.js.map b/priv/static/adminfe/static/js/chunk-commons.51fe2926.js.map deleted file mode 100644 index 7d55c69d6..000000000 Binary files a/priv/static/adminfe/static/js/chunk-commons.51fe2926.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-commons.51fe2926.js b/priv/static/adminfe/static/js/chunk-commons.a6002038.js similarity index 89% rename from priv/static/adminfe/static/js/chunk-commons.51fe2926.js rename to priv/static/adminfe/static/js/chunk-commons.a6002038.js index 3fe10f0d3..2b16da9c7 100644 Binary files a/priv/static/adminfe/static/js/chunk-commons.51fe2926.js and b/priv/static/adminfe/static/js/chunk-commons.a6002038.js differ diff --git a/priv/static/adminfe/static/js/chunk-commons.a6002038.js.map b/priv/static/adminfe/static/js/chunk-commons.a6002038.js.map new file mode 100644 index 000000000..3c7d78861 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-commons.a6002038.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js b/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js new file mode 100644 index 000000000..edd221e6e Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js differ diff --git a/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js.map b/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js.map new file mode 100644 index 000000000..6bcd4ed8b Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-d34d.0f06fe76.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-d55e.f9bab96d.js b/priv/static/adminfe/static/js/chunk-d55e.f9bab96d.js new file mode 100644 index 000000000..4e7ab35bb Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-d55e.f9bab96d.js differ diff --git a/priv/static/adminfe/static/js/chunk-d55e.f9bab96d.js.map b/priv/static/adminfe/static/js/chunk-d55e.f9bab96d.js.map new file mode 100644 index 000000000..323d9ba2a Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-d55e.f9bab96d.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-df62.6c5105a6.js b/priv/static/adminfe/static/js/chunk-df62.6c5105a6.js deleted file mode 100644 index c6c4b82ee..000000000 Binary files a/priv/static/adminfe/static/js/chunk-df62.6c5105a6.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-df62.6c5105a6.js.map b/priv/static/adminfe/static/js/chunk-df62.6c5105a6.js.map deleted file mode 100644 index a2380c4cd..000000000 Binary files a/priv/static/adminfe/static/js/chunk-df62.6c5105a6.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-elementUI.21957ec8.js b/priv/static/adminfe/static/js/chunk-elementUI.21957ec8.js new file mode 100644 index 000000000..65462dc89 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-elementUI.21957ec8.js differ diff --git a/priv/static/adminfe/static/js/chunk-elementUI.21957ec8.js.map b/priv/static/adminfe/static/js/chunk-elementUI.21957ec8.js.map new file mode 100644 index 000000000..3381fd56a Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-elementUI.21957ec8.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-elementUI.8e5c404c.js b/priv/static/adminfe/static/js/chunk-elementUI.8e5c404c.js deleted file mode 100644 index e8424c9ed..000000000 Binary files a/priv/static/adminfe/static/js/chunk-elementUI.8e5c404c.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-elementUI.8e5c404c.js.map b/priv/static/adminfe/static/js/chunk-elementUI.8e5c404c.js.map deleted file mode 100644 index a3c9be946..000000000 Binary files a/priv/static/adminfe/static/js/chunk-elementUI.8e5c404c.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-f625.29237434.js b/priv/static/adminfe/static/js/chunk-f625.29237434.js new file mode 100644 index 000000000..522755a98 Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-f625.29237434.js differ diff --git a/priv/static/adminfe/static/js/chunk-f625.29237434.js.map b/priv/static/adminfe/static/js/chunk-f625.29237434.js.map new file mode 100644 index 000000000..4f8774c3a Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-f625.29237434.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-libs.32ea9181.js b/priv/static/adminfe/static/js/chunk-libs.32ea9181.js new file mode 100644 index 000000000..29cfb2b1d Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-libs.32ea9181.js differ diff --git a/priv/static/adminfe/static/js/chunk-libs.32ea9181.js.map b/priv/static/adminfe/static/js/chunk-libs.32ea9181.js.map new file mode 100644 index 000000000..c80cf9acc Binary files /dev/null and b/priv/static/adminfe/static/js/chunk-libs.32ea9181.js.map differ diff --git a/priv/static/adminfe/static/js/chunk-libs.f842b12e.js b/priv/static/adminfe/static/js/chunk-libs.f842b12e.js deleted file mode 100644 index 2e8e0aba5..000000000 Binary files a/priv/static/adminfe/static/js/chunk-libs.f842b12e.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/chunk-libs.f842b12e.js.map b/priv/static/adminfe/static/js/chunk-libs.f842b12e.js.map deleted file mode 100644 index de17844c9..000000000 Binary files a/priv/static/adminfe/static/js/chunk-libs.f842b12e.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/oAJy.2d5429b2.js b/priv/static/adminfe/static/js/oAJy.2d5429b2.js new file mode 100644 index 000000000..72ef1a89b Binary files /dev/null and b/priv/static/adminfe/static/js/oAJy.2d5429b2.js differ diff --git a/priv/static/adminfe/static/js/oAJy.2d5429b2.js.map b/priv/static/adminfe/static/js/oAJy.2d5429b2.js.map new file mode 100644 index 000000000..e93954508 Binary files /dev/null and b/priv/static/adminfe/static/js/oAJy.2d5429b2.js.map differ diff --git a/priv/static/adminfe/static/js/oAJy.840fb1c2.js b/priv/static/adminfe/static/js/oAJy.840fb1c2.js deleted file mode 100644 index 9973db60a..000000000 Binary files a/priv/static/adminfe/static/js/oAJy.840fb1c2.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/oAJy.840fb1c2.js.map b/priv/static/adminfe/static/js/oAJy.840fb1c2.js.map deleted file mode 100644 index 48420eecd..000000000 Binary files a/priv/static/adminfe/static/js/oAJy.840fb1c2.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/runtime.04c4fa2f.js b/priv/static/adminfe/static/js/runtime.04c4fa2f.js deleted file mode 100644 index c08585729..000000000 Binary files a/priv/static/adminfe/static/js/runtime.04c4fa2f.js and /dev/null differ diff --git a/priv/static/adminfe/static/js/runtime.04c4fa2f.js.map b/priv/static/adminfe/static/js/runtime.04c4fa2f.js.map deleted file mode 100644 index bf0af11e1..000000000 Binary files a/priv/static/adminfe/static/js/runtime.04c4fa2f.js.map and /dev/null differ diff --git a/priv/static/adminfe/static/js/runtime.8f631d12.js b/priv/static/adminfe/static/js/runtime.8f631d12.js new file mode 100644 index 000000000..6fa7d9ee1 Binary files /dev/null and b/priv/static/adminfe/static/js/runtime.8f631d12.js differ diff --git a/priv/static/adminfe/static/js/runtime.8f631d12.js.map b/priv/static/adminfe/static/js/runtime.8f631d12.js.map new file mode 100644 index 000000000..d5c20400e Binary files /dev/null and b/priv/static/adminfe/static/js/runtime.8f631d12.js.map differ diff --git a/priv/static/index.html b/priv/static/index.html index f5690a8d6..9b774959a 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/config.json b/priv/static/static/config.json index 0030f78f1..f59e645ac 100644 --- a/priv/static/static/config.json +++ b/priv/static/static/config.json @@ -10,9 +10,10 @@ "hideSitename": false, "hideUserStats": false, "loginMethod": "password", - "logo": "/static/logo.png", + "logo": "/static/logo.svg", "logoMargin": ".1em", "logoMask": true, + "logoLeft": false, "minimalScopesMode": false, "nsfwCensorImage": "", "postContentType": "text/plain", diff --git a/priv/static/static/css/app.77b1644622e3bae24b6b.css.map b/priv/static/static/css/app.77b1644622e3bae24b6b.css.map deleted file mode 100644 index 4b042ef35..000000000 --- a/priv/static/static/css/app.77b1644622e3bae24b6b.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_load_more/with_load_more.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACtOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.77b1644622e3bae24b6b.css","sourcesContent":[".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n}\n.tab-switcher .tab-icon {\n font-size: 2em;\n display: block;\n}\n.tab-switcher.top-tabs {\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.top-tabs > .tabs {\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n -ms-flex-direction: row;\n flex-direction: row;\n}\n.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper {\n height: 28px;\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {\n left: 0;\n right: 0;\n bottom: 0;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab {\n width: 100%;\n min-width: 1px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding-bottom: 99px;\n margin-bottom: -93px;\n}\n.tab-switcher.top-tabs .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n}\n.tab-switcher.side-tabs {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs {\n overflow-x: auto;\n }\n}\n.tab-switcher.side-tabs > .contents {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher.side-tabs > .tabs {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n overflow-y: auto;\n overflow-x: hidden;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -ms-flex-preferred-size: 0.5em;\n flex-basis: 0.5em;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs::after {\n -ms-flex-positive: 1;\n flex-grow: 1;\n}\n.tab-switcher.side-tabs > .tabs::before {\n -ms-flex-positive: 0;\n flex-grow: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 10em;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 1em;\n }\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {\n top: 0;\n right: 0;\n bottom: 0;\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper::before {\n -ms-flex: 0 0 6px;\n flex: 0 0 6px;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {\n margin-bottom: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab {\n -ms-flex: 1;\n flex: 1;\n box-sizing: content-box;\n min-width: 10em;\n min-width: 1px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding-left: 1em;\n padding-right: calc(1em + 200px);\n margin-right: -200px;\n margin-left: 1em;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab {\n padding-left: 0.25em;\n padding-right: calc(.25em + 200px);\n margin-right: calc(.25em - 200px);\n margin-left: 0.25em;\n }\n .tab-switcher.side-tabs > .tabs .tab .text {\n display: none;\n }\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents .full-height:not(.hidden) {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {\n -ms-flex: 1;\n flex: 1;\n}\n.tab-switcher .contents.scrollable-tabs {\n overflow-y: auto;\n}\n.tab-switcher .tab {\n position: relative;\n white-space: nowrap;\n padding: 6px 1em;\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n}\n.tab-switcher .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher .tab-wrapper {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n z-index: 7;\n}\n.tab-switcher .mobile-label {\n padding-left: 0.3em;\n padding-bottom: 0.25em;\n margin-top: 0.5em;\n margin-left: 0.2em;\n margin-bottom: 0.25em;\n border-bottom: 1px solid var(--border, #222);\n}\n@media all and (min-width: 800px) {\n .tab-switcher .mobile-label {\n display: none;\n }\n}",".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}\n.with-load-more-footer a {\n cursor: pointer;\n}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/app.77b1644622e3bae24b6b.css b/priv/static/static/css/app.9a4c5ede37b2f0230836.css similarity index 98% rename from priv/static/static/css/app.77b1644622e3bae24b6b.css rename to priv/static/static/css/app.9a4c5ede37b2f0230836.css index 8038882c0..22b9fdbe7 100644 --- a/priv/static/static/css/app.77b1644622e3bae24b6b.css +++ b/priv/static/static/css/app.9a4c5ede37b2f0230836.css @@ -3,7 +3,7 @@ .tab-switcher { display: flex; } .tab-switcher .tab-icon { - font-size: 2em; + margin: 0.2em auto; display: block; } .tab-switcher.top-tabs { @@ -97,7 +97,7 @@ .tab-switcher.side-tabs > .tabs .tab-wrapper { } @media all and (max-width: 800px) { .tab-switcher.side-tabs > .tabs .tab-wrapper { - min-width: 1em; + min-width: 4em; } } .tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after { @@ -243,4 +243,4 @@ .with-load-more-footer a { cursor: pointer; } -/*# sourceMappingURL=app.77b1644622e3bae24b6b.css.map*/ \ No newline at end of file +/*# sourceMappingURL=app.9a4c5ede37b2f0230836.css.map*/ \ No newline at end of file diff --git a/priv/static/static/css/app.9a4c5ede37b2f0230836.css.map b/priv/static/static/css/app.9a4c5ede37b2f0230836.css.map new file mode 100644 index 000000000..f54bd9ee6 --- /dev/null +++ b/priv/static/static/css/app.9a4c5ede37b2f0230836.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_load_more/with_load_more.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACtOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.9a4c5ede37b2f0230836.css","sourcesContent":[".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n}\n.tab-switcher .tab-icon {\n margin: 0.2em auto;\n display: block;\n}\n.tab-switcher.top-tabs {\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.top-tabs > .tabs {\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n -ms-flex-direction: row;\n flex-direction: row;\n}\n.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper {\n height: 28px;\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {\n left: 0;\n right: 0;\n bottom: 0;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab {\n width: 100%;\n min-width: 1px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding-bottom: 99px;\n margin-bottom: -93px;\n}\n.tab-switcher.top-tabs .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n}\n.tab-switcher.side-tabs {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs {\n overflow-x: auto;\n }\n}\n.tab-switcher.side-tabs > .contents {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher.side-tabs > .tabs {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n overflow-y: auto;\n overflow-x: hidden;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -ms-flex-preferred-size: 0.5em;\n flex-basis: 0.5em;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs::after {\n -ms-flex-positive: 1;\n flex-grow: 1;\n}\n.tab-switcher.side-tabs > .tabs::before {\n -ms-flex-positive: 0;\n flex-grow: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 10em;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 4em;\n }\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {\n top: 0;\n right: 0;\n bottom: 0;\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper::before {\n -ms-flex: 0 0 6px;\n flex: 0 0 6px;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {\n margin-bottom: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab {\n -ms-flex: 1;\n flex: 1;\n box-sizing: content-box;\n min-width: 10em;\n min-width: 1px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding-left: 1em;\n padding-right: calc(1em + 200px);\n margin-right: -200px;\n margin-left: 1em;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab {\n padding-left: 0.25em;\n padding-right: calc(.25em + 200px);\n margin-right: calc(.25em - 200px);\n margin-left: 0.25em;\n }\n .tab-switcher.side-tabs > .tabs .tab .text {\n display: none;\n }\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents .full-height:not(.hidden) {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {\n -ms-flex: 1;\n flex: 1;\n}\n.tab-switcher .contents.scrollable-tabs {\n overflow-y: auto;\n}\n.tab-switcher .tab {\n position: relative;\n white-space: nowrap;\n padding: 6px 1em;\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n}\n.tab-switcher .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher .tab-wrapper {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n z-index: 7;\n}\n.tab-switcher .mobile-label {\n padding-left: 0.3em;\n padding-bottom: 0.25em;\n margin-top: 0.5em;\n margin-left: 0.2em;\n margin-bottom: 0.25em;\n border-bottom: 1px solid var(--border, #222);\n}\n@media all and (min-width: 800px) {\n .tab-switcher .mobile-label {\n display: none;\n }\n}",".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}\n.with-load-more-footer a {\n cursor: pointer;\n}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/font/fontello.1600365488745.eot b/priv/static/static/font/fontello.1600365488745.eot deleted file mode 100644 index 255f50372..000000000 Binary files a/priv/static/static/font/fontello.1600365488745.eot and /dev/null differ diff --git a/priv/static/static/font/fontello.1600365488745.svg b/priv/static/static/font/fontello.1600365488745.svg deleted file mode 100644 index 9eddf62ea..000000000 --- a/priv/static/static/font/fontello.1600365488745.svg +++ /dev/null @@ -1,140 +0,0 @@ - - - -Copyright (C) 2020 by original authors @ fontello.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/priv/static/static/font/fontello.1600365488745.ttf b/priv/static/static/font/fontello.1600365488745.ttf deleted file mode 100644 index 6bda99d50..000000000 Binary files a/priv/static/static/font/fontello.1600365488745.ttf and /dev/null differ diff --git a/priv/static/static/font/fontello.1600365488745.woff b/priv/static/static/font/fontello.1600365488745.woff deleted file mode 100644 index 11c866ae0..000000000 Binary files a/priv/static/static/font/fontello.1600365488745.woff and /dev/null differ diff --git a/priv/static/static/font/fontello.1600365488745.woff2 b/priv/static/static/font/fontello.1600365488745.woff2 deleted file mode 100644 index 06151d28c..000000000 Binary files a/priv/static/static/font/fontello.1600365488745.woff2 and /dev/null differ diff --git a/priv/static/static/fontello.1600365488745.css b/priv/static/static/fontello.1600365488745.css deleted file mode 100644 index 781ff7620..000000000 --- a/priv/static/static/fontello.1600365488745.css +++ /dev/null @@ -1,160 +0,0 @@ -@font-face { - font-family: "Icons"; - src: url("./font/fontello.1600365488745.eot"); - src: url("./font/fontello.1600365488745.eot") format("embedded-opentype"), - url("./font/fontello.1600365488745.woff2") format("woff2"), - url("./font/fontello.1600365488745.woff") format("woff"), - url("./font/fontello.1600365488745.ttf") format("truetype"), - url("./font/fontello.1600365488745.svg") format("svg"); - font-weight: normal; - font-style: normal; -} - -[class^="icon-"]::before, -[class*=" icon-"]::before { - font-family: "Icons"; - font-style: normal; - font-weight: normal; - speak: none; - display: inline-block; - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - font-variant: normal; - text-transform: none; - line-height: 1em; - margin-left: .2em; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.icon-spin4::before { content: "\e834"; } - -.icon-cancel::before { content: "\e800"; } - -.icon-upload::before { content: "\e801"; } - -.icon-spin3::before { content: "\e832"; } - -.icon-reply::before { content: "\f112"; } - -.icon-star::before { content: "\e802"; } - -.icon-star-empty::before { content: "\e803"; } - -.icon-retweet::before { content: "\e804"; } - -.icon-eye-off::before { content: "\e805"; } - -.icon-binoculars::before { content: "\f1e5"; } - -.icon-cog::before { content: "\e807"; } - -.icon-user-plus::before { content: "\f234"; } - -.icon-menu::before { content: "\f0c9"; } - -.icon-logout::before { content: "\e808"; } - -.icon-down-open::before { content: "\e809"; } - -.icon-attach::before { content: "\e80a"; } - -.icon-link-ext::before { content: "\f08e"; } - -.icon-link-ext-alt::before { content: "\f08f"; } - -.icon-picture::before { content: "\e80b"; } - -.icon-video::before { content: "\e80c"; } - -.icon-right-open::before { content: "\e80d"; } - -.icon-left-open::before { content: "\e80e"; } - -.icon-up-open::before { content: "\e80f"; } - -.icon-comment-empty::before { content: "\f0e5"; } - -.icon-mail-alt::before { content: "\f0e0"; } - -.icon-lock::before { content: "\e811"; } - -.icon-lock-open-alt::before { content: "\f13e"; } - -.icon-globe::before { content: "\e812"; } - -.icon-brush::before { content: "\e813"; } - -.icon-search::before { content: "\e806"; } - -.icon-adjust::before { content: "\e816"; } - -.icon-thumbs-up-alt::before { content: "\f164"; } - -.icon-attention::before { content: "\e814"; } - -.icon-plus-squared::before { content: "\f0fe"; } - -.icon-plus::before { content: "\e815"; } - -.icon-edit::before { content: "\e817"; } - -.icon-play-circled::before { content: "\f144"; } - -.icon-pencil::before { content: "\e818"; } - -.icon-chart-bar::before { content: "\e81b"; } - -.icon-smile::before { content: "\f118"; } - -.icon-bell-alt::before { content: "\f0f3"; } - -.icon-wrench::before { content: "\e81a"; } - -.icon-pin::before { content: "\e819"; } - -.icon-ellipsis::before { content: "\f141"; } - -.icon-bell-ringing-o::before { content: "\e810"; } - -.icon-zoom-in::before { content: "\e81c"; } - -.icon-gauge::before { content: "\f0e4"; } - -.icon-users::before { content: "\e81d"; } - -.icon-info-circled::before { content: "\e81f"; } - -.icon-home-2::before { content: "\e821"; } - -.icon-chat::before { content: "\e81e"; } - -.icon-login::before { content: "\e820"; } - -.icon-arrow-curved::before { content: "\e822"; } - -.icon-link::before { content: "\e823"; } - -.icon-share::before { content: "\f1e0"; } - -.icon-user::before { content: "\e824"; } - -.icon-ok::before { content: "\e827"; } - -.icon-filter::before { content: "\f0b0"; } - -.icon-download::before { content: "\e825"; } - -.icon-bookmark::before { content: "\e826"; } - -.icon-bookmark-empty::before { content: "\f097"; } - -.icon-music::before { content: "\e828"; } - -.icon-doc::before { content: "\e829"; } - -.icon-block::before { content: "\e82a"; } - -.icon-megaphone::before { content: "\e82b"; } diff --git a/priv/static/static/fontello.json b/priv/static/static/fontello.json deleted file mode 100644 index b0136fd90..000000000 --- a/priv/static/static/fontello.json +++ /dev/null @@ -1,416 +0,0 @@ -{ - "name": "", - "css_prefix_text": "icon-", - "css_use_suffix": false, - "hinting": true, - "units_per_em": 1000, - "ascent": 857, - "glyphs": [ - { - "uid": "9bd60140934a1eb9236fd7a8ab1ff6ba", - "css": "spin4", - "code": 59444, - "src": "fontelico" - }, - { - "uid": "5211af474d3a9848f67f945e2ccaf143", - "css": "cancel", - "code": 59392, - "src": "fontawesome" - }, - { - "uid": "eeec3208c90b7b48e804919d0d2d4a41", - "css": "upload", - "code": 59393, - "src": "fontawesome" - }, - { - "uid": "2a6740fc2f9d0edea54205963f662594", - "css": "spin3", - "code": 59442, - "src": "fontelico" - }, - { - "uid": "c6be5a58ee4e63a5ec399c2b0d15cf2c", - "css": "reply", - "code": 61714, - "src": "fontawesome" - }, - { - "uid": "474656633f79ea2f1dad59ff63f6bf07", - "css": "star", - "code": 59394, - "src": "fontawesome" - }, - { - "uid": "d17030afaecc1e1c22349b99f3c4992a", - "css": "star-empty", - "code": 59395, - "src": "fontawesome" - }, - { - "uid": "09feb4465d9bd1364f4e301c9ddbaa92", - "css": "retweet", - "code": 59396, - "src": "fontawesome" - }, - { - "uid": "7fd683b2c518ceb9e5fa6757f2276faa", - "css": "eye-off", - "code": 59397, - "src": "fontawesome" - }, - { - "uid": "73ffeb70554099177620847206c12457", - "css": "binoculars", - "code": 61925, - "src": "fontawesome" - }, - { - "uid": "e99461abfef3923546da8d745372c995", - "css": "cog", - "code": 59399, - "src": "fontawesome" - }, - { - "uid": "1bafeeb1808a5fe24484c7890096901a", - "css": "user-plus", - "code": 62004, - "src": "fontawesome" - }, - { - "uid": "559647a6f430b3aeadbecd67194451dd", - "css": "menu", - "code": 61641, - "src": "fontawesome" - }, - { - "uid": "0d20938846444af8deb1920dc85a29fb", - "css": "logout", - "code": 59400, - "src": "fontawesome" - }, - { - "uid": "ccddff8e8670dcd130e3cb55fdfc2fd0", - "css": "down-open", - "code": 59401, - "src": "fontawesome" - }, - { - "uid": "44b9e75612c5fad5505edd70d071651f", - "css": "attach", - "code": 59402, - "src": "entypo" - }, - { - "uid": "e15f0d620a7897e2035c18c80142f6d9", - "css": "link-ext", - "code": 61582, - "src": "fontawesome" - }, - { - "uid": "e35de5ea31cd56970498e33efbcb8e36", - "css": "link-ext-alt", - "code": 61583, - "src": "fontawesome" - }, - { - "uid": "381da2c2f7fd51f8de877c044d7f439d", - "css": "picture", - "code": 59403, - "src": "fontawesome" - }, - { - "uid": "872d9516df93eb6b776cc4d94bd97dac", - "css": "video", - "code": 59404, - "src": "fontawesome" - }, - { - "uid": "399ef63b1e23ab1b761dfbb5591fa4da", - "css": "right-open", - "code": 59405, - "src": "fontawesome" - }, - { - "uid": "d870630ff8f81e6de3958ecaeac532f2", - "css": "left-open", - "code": 59406, - "src": "fontawesome" - }, - { - "uid": "fe6697b391355dec12f3d86d6d490397", - "css": "up-open", - "code": 59407, - "src": "fontawesome" - }, - { - "uid": "9c1376672bb4f1ed616fdd78a23667e9", - "css": "comment-empty", - "code": 61669, - "src": "fontawesome" - }, - { - "uid": "ccc2329632396dc096bb638d4b46fb98", - "css": "mail-alt", - "code": 61664, - "src": "fontawesome" - }, - { - "uid": "c1f1975c885aa9f3dad7810c53b82074", - "css": "lock", - "code": 59409, - "src": "fontawesome" - }, - { - "uid": "05376be04a27d5a46e855a233d6e8508", - "css": "lock-open-alt", - "code": 61758, - "src": "fontawesome" - }, - { - "uid": "197375a3cea8cb90b02d06e4ddf1433d", - "css": "globe", - "code": 59410, - "src": "fontawesome" - }, - { - "uid": "b3a9e2dab4d19ea3b2f628242c926bfe", - "css": "brush", - "code": 59411, - "src": "iconic" - }, - { - "uid": "9dd9e835aebe1060ba7190ad2b2ed951", - "css": "search", - "code": 59398, - "src": "fontawesome" - }, - { - "uid": "ca90da02d2c6a3183f2458e4dc416285", - "css": "adjust", - "code": 59414, - "src": "fontawesome" - }, - { - "uid": "5e2ab018e3044337bcef5f7e94098ea1", - "css": "thumbs-up-alt", - "code": 61796, - "src": "fontawesome" - }, - { - "uid": "c76b7947c957c9b78b11741173c8349b", - "css": "attention", - "code": 59412, - "src": "fontawesome" - }, - { - "uid": "1a5cfa186647e8c929c2b17b9fc4dac1", - "css": "plus-squared", - "code": 61694, - "src": "fontawesome" - }, - { - "uid": "44e04715aecbca7f266a17d5a7863c68", - "css": "plus", - "code": 59413, - "src": "fontawesome" - }, - { - "uid": "41087bc74d4b20b55059c60a33bf4008", - "css": "edit", - "code": 59415, - "src": "fontawesome" - }, - { - "uid": "5717236f6134afe2d2a278a5c9b3927a", - "css": "play-circled", - "code": 61764, - "src": "fontawesome" - }, - { - "uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6", - "css": "pencil", - "code": 59416, - "src": "fontawesome" - }, - { - "uid": "266d5d9adf15a61800477a5acf9a4462", - "css": "chart-bar", - "code": 59419, - "src": "fontawesome" - }, - { - "uid": "d862a10e1448589215be19702f98f2c1", - "css": "smile", - "code": 61720, - "src": "fontawesome" - }, - { - "uid": "671f29fa10dda08074a4c6a341bb4f39", - "css": "bell-alt", - "code": 61683, - "src": "fontawesome" - }, - { - "uid": "5bb103cd29de77e0e06a52638527b575", - "css": "wrench", - "code": 59418, - "src": "fontawesome" - }, - { - "uid": "5b0772e9484a1a11646793a82edd622a", - "css": "pin", - "code": 59417, - "src": "fontawesome" - }, - { - "uid": "22411a88489225a018f68db737de3c77", - "css": "ellipsis", - "code": 61761, - "src": "custom_icons", - "selected": true, - "svg": { - "path": "M214 411V518Q214 540 199 556T161 571H54Q31 571 16 556T0 518V411Q0 388 16 373T54 357H161Q183 357 199 373T214 411ZM500 411V518Q500 540 484 556T446 571H339Q317 571 301 556T286 518V411Q286 388 301 373T339 357H446Q469 357 484 373T500 411ZM786 411V518Q786 540 770 556T732 571H625Q603 571 587 556T571 518V411Q571 388 587 373T625 357H732Q755 357 770 373T786 411Z", - "width": 785.7 - }, - "search": [ - "ellipsis" - ] - }, - { - "uid": "0bef873af785ead27781fdf98b3ae740", - "css": "bell-ringing-o", - "code": 59408, - "src": "custom_icons", - "selected": true, - "svg": { - "path": "M497.8 0C468.3 0 444.4 23.9 444.4 53.3 444.4 61.1 446.1 68.3 448.9 75 301.7 96.7 213.3 213.3 213.3 320 213.3 588.3 117.8 712.8 35.6 782.2 35.6 821.1 67.8 853.3 106.7 853.3H355.6C355.6 931.7 419.4 995.6 497.8 995.6S640 931.7 640 853.3H888.9C927.8 853.3 960 821.1 960 782.2 877.8 712.8 782.2 588.3 782.2 320 782.2 213.3 693.9 96.7 546.7 75 549.4 68.3 551.1 61.1 551.1 53.3 551.1 23.9 527.2 0 497.8 0ZM189.4 44.8C108.4 118.6 70.5 215.1 71.1 320.2L142.2 319.8C141.7 231.2 170.4 158.3 237.3 97.4L189.4 44.8ZM806.2 44.8L758.3 97.4C825.2 158.3 853.9 231.2 853.3 319.8L924.4 320.2C925.1 215.1 887.2 118.6 806.2 44.8ZM408.9 844.4C413.9 844.4 417.8 848.3 417.8 853.3 417.8 897.2 453.9 933.3 497.8 933.3 502.8 933.3 506.7 937.2 506.7 942.2S502.8 951.1 497.8 951.1C443.9 951.1 400 907.2 400 853.3 400 848.3 403.9 844.4 408.9 844.4Z", - "width": 1000 - }, - "search": [ - "bell-ringing-o" - ] - }, - { - "uid": "0b2b66e526028a6972d51a6f10281b4b", - "css": "zoom-in", - "code": 59420, - "src": "fontawesome" - }, - { - "uid": "0bda4bc779d4c32623dec2e43bd67ee8", - "css": "gauge", - "code": 61668, - "src": "fontawesome" - }, - { - "uid": "31972e4e9d080eaa796290349ae6c1fd", - "css": "users", - "code": 59421, - "src": "fontawesome" - }, - { - "uid": "e82cedfa1d5f15b00c5a81c9bd731ea2", - "css": "info-circled", - "code": 59423, - "src": "fontawesome" - }, - { - "uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6", - "css": "home-2", - "code": 59425, - "src": "typicons" - }, - { - "uid": "dcedf50ab1ede3283d7a6c70e2fe32f3", - "css": "chat", - "code": 59422, - "src": "fontawesome" - }, - { - "uid": "3a00327e61b997b58518bd43ed83c3df", - "css": "login", - "code": 59424, - "src": "fontawesome" - }, - { - "uid": "f3ebd6751c15a280af5cc5f4a764187d", - "css": "arrow-curved", - "code": 59426, - "src": "iconic" - }, - { - "uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1", - "css": "link", - "code": 59427, - "src": "fontawesome" - }, - { - "uid": "4aad6bb50b02c18508aae9cbe14e784e", - "css": "share", - "code": 61920, - "src": "fontawesome" - }, - { - "uid": "8b80d36d4ef43889db10bc1f0dc9a862", - "css": "user", - "code": 59428, - "src": "fontawesome" - }, - { - "uid": "12f4ece88e46abd864e40b35e05b11cd", - "css": "ok", - "code": 59431, - "src": "fontawesome" - }, - { - "uid": "4109c474ff99cad28fd5a2c38af2ec6f", - "css": "filter", - "code": 61616, - "src": "fontawesome" - }, - { - "uid": "9a76bc135eac17d2c8b8ad4a5774fc87", - "css": "download", - "code": 59429, - "src": "fontawesome" - }, - { - "uid": "f04a5d24e9e659145b966739c4fde82a", - "css": "bookmark", - "code": 59430, - "src": "fontawesome" - }, - { - "uid": "2f5ef6f6b7aaebc56458ab4e865beff5", - "css": "bookmark-empty", - "code": 61591, - "src": "fontawesome" - }, - { - "uid": "9ea0a737ccc45d6c510dcbae56058849", - "css": "music", - "code": 59432, - "src": "fontawesome" - }, - { - "uid": "1b5a5d7b7e3c71437f5a26befdd045ed", - "css": "doc", - "code": 59433, - "src": "fontawesome" - }, - { - "uid": "98d9c83c1ee7c2c25af784b518c522c5", - "css": "block", - "code": 59434, - "src": "fontawesome" - }, - { - "uid": "3e674995cacc2b09692c096ea7eb6165", - "css": "megaphone", - "code": 59435, - "src": "fontawesome" - } - ] -} \ No newline at end of file diff --git a/priv/static/static/js/10.46fbbdfaf0d4800f349b.js b/priv/static/static/js/10.46f441b948010eda4403.js similarity index 71% rename from priv/static/static/js/10.46fbbdfaf0d4800f349b.js rename to priv/static/static/js/10.46f441b948010eda4403.js index 0fd8463df..308d124c0 100644 Binary files a/priv/static/static/js/10.46fbbdfaf0d4800f349b.js and b/priv/static/static/js/10.46f441b948010eda4403.js differ diff --git a/priv/static/static/js/10.46fbbdfaf0d4800f349b.js.map b/priv/static/static/js/10.46f441b948010eda4403.js.map similarity index 56% rename from priv/static/static/js/10.46fbbdfaf0d4800f349b.js.map rename to priv/static/static/js/10.46f441b948010eda4403.js.map index bee2feb10..e0623e6bf 100644 Binary files a/priv/static/static/js/10.46fbbdfaf0d4800f349b.js.map and b/priv/static/static/js/10.46f441b948010eda4403.js.map differ diff --git a/priv/static/static/js/11.708cc2513c53879a92cc.js b/priv/static/static/js/11.8ff1ed54814f2d34cb3e.js similarity index 99% rename from priv/static/static/js/11.708cc2513c53879a92cc.js rename to priv/static/static/js/11.8ff1ed54814f2d34cb3e.js index 4fe316ecf..cb57f2a65 100644 Binary files a/priv/static/static/js/11.708cc2513c53879a92cc.js and b/priv/static/static/js/11.8ff1ed54814f2d34cb3e.js differ diff --git a/priv/static/static/js/11.708cc2513c53879a92cc.js.map b/priv/static/static/js/11.8ff1ed54814f2d34cb3e.js.map similarity index 56% rename from priv/static/static/js/11.708cc2513c53879a92cc.js.map rename to priv/static/static/js/11.8ff1ed54814f2d34cb3e.js.map index 64c9320c4..4ce6d7227 100644 Binary files a/priv/static/static/js/11.708cc2513c53879a92cc.js.map and b/priv/static/static/js/11.8ff1ed54814f2d34cb3e.js.map differ diff --git a/priv/static/static/js/12.13204bdd0ad5703a3ea3.js b/priv/static/static/js/12.13204bdd0ad5703a3ea3.js new file mode 100644 index 000000000..a89bfeb67 Binary files /dev/null and b/priv/static/static/js/12.13204bdd0ad5703a3ea3.js differ diff --git a/priv/static/static/js/12.b3bf0bc313861d6ec36b.js.map b/priv/static/static/js/12.13204bdd0ad5703a3ea3.js.map similarity index 56% rename from priv/static/static/js/12.b3bf0bc313861d6ec36b.js.map rename to priv/static/static/js/12.13204bdd0ad5703a3ea3.js.map index 28545ac96..366ec2927 100644 Binary files a/priv/static/static/js/12.b3bf0bc313861d6ec36b.js.map and b/priv/static/static/js/12.13204bdd0ad5703a3ea3.js.map differ diff --git a/priv/static/static/js/12.b3bf0bc313861d6ec36b.js b/priv/static/static/js/12.b3bf0bc313861d6ec36b.js deleted file mode 100644 index 4890ca10a..000000000 Binary files a/priv/static/static/js/12.b3bf0bc313861d6ec36b.js and /dev/null differ diff --git a/priv/static/static/js/13.adb8a942514d735722c4.js b/priv/static/static/js/13.e27c3eeddcc4b11c1f54.js similarity index 76% rename from priv/static/static/js/13.adb8a942514d735722c4.js rename to priv/static/static/js/13.e27c3eeddcc4b11c1f54.js index 41abcb5a6..8cd482b41 100644 Binary files a/priv/static/static/js/13.adb8a942514d735722c4.js and b/priv/static/static/js/13.e27c3eeddcc4b11c1f54.js differ diff --git a/priv/static/static/js/13.adb8a942514d735722c4.js.map b/priv/static/static/js/13.e27c3eeddcc4b11c1f54.js.map similarity index 56% rename from priv/static/static/js/13.adb8a942514d735722c4.js.map rename to priv/static/static/js/13.e27c3eeddcc4b11c1f54.js.map index 2b8ff6d6c..0c61c3fca 100644 Binary files a/priv/static/static/js/13.adb8a942514d735722c4.js.map and b/priv/static/static/js/13.e27c3eeddcc4b11c1f54.js.map differ diff --git a/priv/static/static/js/14.273855b3e4e27ce80219.js b/priv/static/static/js/14.273855b3e4e27ce80219.js new file mode 100644 index 000000000..78c0bfebc Binary files /dev/null and b/priv/static/static/js/14.273855b3e4e27ce80219.js differ diff --git a/priv/static/static/js/14.273855b3e4e27ce80219.js.map b/priv/static/static/js/14.273855b3e4e27ce80219.js.map new file mode 100644 index 000000000..9ee527eaa Binary files /dev/null and b/priv/static/static/js/14.273855b3e4e27ce80219.js.map differ diff --git a/priv/static/static/js/14.d015d9b2ea16407e389c.js b/priv/static/static/js/14.d015d9b2ea16407e389c.js deleted file mode 100644 index 200a79625..000000000 Binary files a/priv/static/static/js/14.d015d9b2ea16407e389c.js and /dev/null differ diff --git a/priv/static/static/js/14.d015d9b2ea16407e389c.js.map b/priv/static/static/js/14.d015d9b2ea16407e389c.js.map deleted file mode 100644 index 49dab13f7..000000000 Binary files a/priv/static/static/js/14.d015d9b2ea16407e389c.js.map and /dev/null differ diff --git a/priv/static/static/js/15.19866e6a366ccf982284.js.map b/priv/static/static/js/15.19866e6a366ccf982284.js.map deleted file mode 100644 index 561ab7dcf..000000000 Binary files a/priv/static/static/js/15.19866e6a366ccf982284.js.map and /dev/null differ diff --git a/priv/static/static/js/15.19866e6a366ccf982284.js b/priv/static/static/js/15.afbe29b6665fcd015b2d.js similarity index 98% rename from priv/static/static/js/15.19866e6a366ccf982284.js rename to priv/static/static/js/15.afbe29b6665fcd015b2d.js index 0cc2e266a..b83752240 100644 Binary files a/priv/static/static/js/15.19866e6a366ccf982284.js and b/priv/static/static/js/15.afbe29b6665fcd015b2d.js differ diff --git a/priv/static/static/js/15.afbe29b6665fcd015b2d.js.map b/priv/static/static/js/15.afbe29b6665fcd015b2d.js.map new file mode 100644 index 000000000..c7a0be582 Binary files /dev/null and b/priv/static/static/js/15.afbe29b6665fcd015b2d.js.map differ diff --git a/priv/static/static/js/16.38a984effd54736f6a2c.js.map b/priv/static/static/js/16.38a984effd54736f6a2c.js.map deleted file mode 100644 index 68ee95f97..000000000 Binary files a/priv/static/static/js/16.38a984effd54736f6a2c.js.map and /dev/null differ diff --git a/priv/static/static/js/16.38a984effd54736f6a2c.js b/priv/static/static/js/16.5e3f20da470591d0cabf.js similarity index 99% rename from priv/static/static/js/16.38a984effd54736f6a2c.js rename to priv/static/static/js/16.5e3f20da470591d0cabf.js index b3cebb0bd..e90ed4ca1 100644 Binary files a/priv/static/static/js/16.38a984effd54736f6a2c.js and b/priv/static/static/js/16.5e3f20da470591d0cabf.js differ diff --git a/priv/static/static/js/16.5e3f20da470591d0cabf.js.map b/priv/static/static/js/16.5e3f20da470591d0cabf.js.map new file mode 100644 index 000000000..0c4d0e385 Binary files /dev/null and b/priv/static/static/js/16.5e3f20da470591d0cabf.js.map differ diff --git a/priv/static/static/js/17.9c25507194320db2e85b.js b/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js similarity index 94% rename from priv/static/static/js/17.9c25507194320db2e85b.js rename to priv/static/static/js/17.44e90ef82ee2ef12dc3f.js index 451bf8bd3..9b5adfd12 100644 Binary files a/priv/static/static/js/17.9c25507194320db2e85b.js and b/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js differ diff --git a/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js.map b/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js.map new file mode 100644 index 000000000..1d191b94a Binary files /dev/null and b/priv/static/static/js/17.44e90ef82ee2ef12dc3f.js.map differ diff --git a/priv/static/static/js/17.9c25507194320db2e85b.js.map b/priv/static/static/js/17.9c25507194320db2e85b.js.map deleted file mode 100644 index f843d4400..000000000 Binary files a/priv/static/static/js/17.9c25507194320db2e85b.js.map and /dev/null differ diff --git a/priv/static/static/js/18.94946caca48930c224c7.js.map b/priv/static/static/js/18.94946caca48930c224c7.js.map deleted file mode 100644 index ad04b99ab..000000000 Binary files a/priv/static/static/js/18.94946caca48930c224c7.js.map and /dev/null differ diff --git a/priv/static/static/js/18.94946caca48930c224c7.js b/priv/static/static/js/18.9a5b877f94b2b53065e1.js similarity index 57% rename from priv/static/static/js/18.94946caca48930c224c7.js rename to priv/static/static/js/18.9a5b877f94b2b53065e1.js index 5a1f40c6d..c4aea5b25 100644 Binary files a/priv/static/static/js/18.94946caca48930c224c7.js and b/priv/static/static/js/18.9a5b877f94b2b53065e1.js differ diff --git a/priv/static/static/js/18.9a5b877f94b2b53065e1.js.map b/priv/static/static/js/18.9a5b877f94b2b53065e1.js.map new file mode 100644 index 000000000..61d9a7d41 Binary files /dev/null and b/priv/static/static/js/18.9a5b877f94b2b53065e1.js.map differ diff --git a/priv/static/static/js/19.233c81ac2c28d55e9f13.js b/priv/static/static/js/19.1fd4da643df0abf89122.js similarity index 99% rename from priv/static/static/js/19.233c81ac2c28d55e9f13.js rename to priv/static/static/js/19.1fd4da643df0abf89122.js index ace0a1d41..c1ca1643b 100644 Binary files a/priv/static/static/js/19.233c81ac2c28d55e9f13.js and b/priv/static/static/js/19.1fd4da643df0abf89122.js differ diff --git a/priv/static/static/js/19.1fd4da643df0abf89122.js.map b/priv/static/static/js/19.1fd4da643df0abf89122.js.map new file mode 100644 index 000000000..010c8674d Binary files /dev/null and b/priv/static/static/js/19.1fd4da643df0abf89122.js.map differ diff --git a/priv/static/static/js/19.233c81ac2c28d55e9f13.js.map b/priv/static/static/js/19.233c81ac2c28d55e9f13.js.map deleted file mode 100644 index cd3f7354d..000000000 Binary files a/priv/static/static/js/19.233c81ac2c28d55e9f13.js.map and /dev/null differ diff --git a/priv/static/static/js/2.422e6c756ac673a6fd44.js b/priv/static/static/js/2.422e6c756ac673a6fd44.js new file mode 100644 index 000000000..9fb47e2bf Binary files /dev/null and b/priv/static/static/js/2.422e6c756ac673a6fd44.js differ diff --git a/priv/static/static/js/2.422e6c756ac673a6fd44.js.map b/priv/static/static/js/2.422e6c756ac673a6fd44.js.map new file mode 100644 index 000000000..92fdb4d2c Binary files /dev/null and b/priv/static/static/js/2.422e6c756ac673a6fd44.js.map differ diff --git a/priv/static/static/js/2.e852a6b4b3bba752b838.js b/priv/static/static/js/2.e852a6b4b3bba752b838.js deleted file mode 100644 index 42e446575..000000000 Binary files a/priv/static/static/js/2.e852a6b4b3bba752b838.js and /dev/null differ diff --git a/priv/static/static/js/2.e852a6b4b3bba752b838.js.map b/priv/static/static/js/2.e852a6b4b3bba752b838.js.map deleted file mode 100644 index d698f09e1..000000000 Binary files a/priv/static/static/js/2.e852a6b4b3bba752b838.js.map and /dev/null differ diff --git a/priv/static/static/js/20.818c38d27369c3a4d677.js.map b/priv/static/static/js/20.818c38d27369c3a4d677.js.map deleted file mode 100644 index 696eab20f..000000000 Binary files a/priv/static/static/js/20.818c38d27369c3a4d677.js.map and /dev/null differ diff --git a/priv/static/static/js/20.818c38d27369c3a4d677.js b/priv/static/static/js/20.a64fd29da59076399a27.js similarity index 99% rename from priv/static/static/js/20.818c38d27369c3a4d677.js rename to priv/static/static/js/20.a64fd29da59076399a27.js index 133eac52d..eae5b3947 100644 Binary files a/priv/static/static/js/20.818c38d27369c3a4d677.js and b/priv/static/static/js/20.a64fd29da59076399a27.js differ diff --git a/priv/static/static/js/20.a64fd29da59076399a27.js.map b/priv/static/static/js/20.a64fd29da59076399a27.js.map new file mode 100644 index 000000000..b2917fa10 Binary files /dev/null and b/priv/static/static/js/20.a64fd29da59076399a27.js.map differ diff --git a/priv/static/static/js/21.ce4cda179d888ca6bc2a.js b/priv/static/static/js/21.243d9e6ebf469a2dc740.js similarity index 99% rename from priv/static/static/js/21.ce4cda179d888ca6bc2a.js rename to priv/static/static/js/21.243d9e6ebf469a2dc740.js index 49700403c..61633519b 100644 Binary files a/priv/static/static/js/21.ce4cda179d888ca6bc2a.js and b/priv/static/static/js/21.243d9e6ebf469a2dc740.js differ diff --git a/priv/static/static/js/21.243d9e6ebf469a2dc740.js.map b/priv/static/static/js/21.243d9e6ebf469a2dc740.js.map new file mode 100644 index 000000000..3f98250fa Binary files /dev/null and b/priv/static/static/js/21.243d9e6ebf469a2dc740.js.map differ diff --git a/priv/static/static/js/21.ce4cda179d888ca6bc2a.js.map b/priv/static/static/js/21.ce4cda179d888ca6bc2a.js.map deleted file mode 100644 index 124d58abc..000000000 Binary files a/priv/static/static/js/21.ce4cda179d888ca6bc2a.js.map and /dev/null differ diff --git a/priv/static/static/js/22.2ea93c6cc569ef0256ab.js.map b/priv/static/static/js/22.2ea93c6cc569ef0256ab.js.map deleted file mode 100644 index 773159f01..000000000 Binary files a/priv/static/static/js/22.2ea93c6cc569ef0256ab.js.map and /dev/null differ diff --git a/priv/static/static/js/22.2ea93c6cc569ef0256ab.js b/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js similarity index 99% rename from priv/static/static/js/22.2ea93c6cc569ef0256ab.js rename to priv/static/static/js/22.e20ef7e5fefc0964cdd1.js index 1d2077720..e8f309f8a 100644 Binary files a/priv/static/static/js/22.2ea93c6cc569ef0256ab.js and b/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js differ diff --git a/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js.map b/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js.map new file mode 100644 index 000000000..7780cffe6 Binary files /dev/null and b/priv/static/static/js/22.e20ef7e5fefc0964cdd1.js.map differ diff --git a/priv/static/static/js/23.a57a7845cc20fafd06d1.js b/priv/static/static/js/23.614a35f9ded445292f4a.js similarity index 99% rename from priv/static/static/js/23.a57a7845cc20fafd06d1.js rename to priv/static/static/js/23.614a35f9ded445292f4a.js index b15a888df..a35450986 100644 Binary files a/priv/static/static/js/23.a57a7845cc20fafd06d1.js and b/priv/static/static/js/23.614a35f9ded445292f4a.js differ diff --git a/priv/static/static/js/23.614a35f9ded445292f4a.js.map b/priv/static/static/js/23.614a35f9ded445292f4a.js.map new file mode 100644 index 000000000..4158041f4 Binary files /dev/null and b/priv/static/static/js/23.614a35f9ded445292f4a.js.map differ diff --git a/priv/static/static/js/23.a57a7845cc20fafd06d1.js.map b/priv/static/static/js/23.a57a7845cc20fafd06d1.js.map deleted file mode 100644 index 0e5b421e6..000000000 Binary files a/priv/static/static/js/23.a57a7845cc20fafd06d1.js.map and /dev/null differ diff --git a/priv/static/static/js/24.35eb55a657b5485f8491.js.map b/priv/static/static/js/24.35eb55a657b5485f8491.js.map deleted file mode 100644 index 93ffbb2e9..000000000 Binary files a/priv/static/static/js/24.35eb55a657b5485f8491.js.map and /dev/null differ diff --git a/priv/static/static/js/24.35eb55a657b5485f8491.js b/priv/static/static/js/24.6ae9ca51e51e023afbe4.js similarity index 99% rename from priv/static/static/js/24.35eb55a657b5485f8491.js rename to priv/static/static/js/24.6ae9ca51e51e023afbe4.js index d09d5c371..d075f3b1f 100644 Binary files a/priv/static/static/js/24.35eb55a657b5485f8491.js and b/priv/static/static/js/24.6ae9ca51e51e023afbe4.js differ diff --git a/priv/static/static/js/24.6ae9ca51e51e023afbe4.js.map b/priv/static/static/js/24.6ae9ca51e51e023afbe4.js.map new file mode 100644 index 000000000..7e68d5eaa Binary files /dev/null and b/priv/static/static/js/24.6ae9ca51e51e023afbe4.js.map differ diff --git a/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js b/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js deleted file mode 100644 index e96c5e6ec..000000000 Binary files a/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js and /dev/null differ diff --git a/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js.map b/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js.map deleted file mode 100644 index a506e6fa8..000000000 Binary files a/priv/static/static/js/25.5a9efe20e3ae1352e6d2.js.map and /dev/null differ diff --git a/priv/static/static/js/25.eadae0d48ee5be52a16c.js b/priv/static/static/js/25.eadae0d48ee5be52a16c.js new file mode 100644 index 000000000..a0e44e1aa Binary files /dev/null and b/priv/static/static/js/25.eadae0d48ee5be52a16c.js differ diff --git a/priv/static/static/js/25.eadae0d48ee5be52a16c.js.map b/priv/static/static/js/25.eadae0d48ee5be52a16c.js.map new file mode 100644 index 000000000..aaa5e3a57 Binary files /dev/null and b/priv/static/static/js/25.eadae0d48ee5be52a16c.js.map differ diff --git a/priv/static/static/js/26.cf13231d524e5ca3b3e6.js b/priv/static/static/js/26.8fd0027b982c4bcdc88f.js similarity index 99% rename from priv/static/static/js/26.cf13231d524e5ca3b3e6.js rename to priv/static/static/js/26.8fd0027b982c4bcdc88f.js index adc57d6c7..3b149915b 100644 Binary files a/priv/static/static/js/26.cf13231d524e5ca3b3e6.js and b/priv/static/static/js/26.8fd0027b982c4bcdc88f.js differ diff --git a/priv/static/static/js/26.8fd0027b982c4bcdc88f.js.map b/priv/static/static/js/26.8fd0027b982c4bcdc88f.js.map new file mode 100644 index 000000000..d40f1979a Binary files /dev/null and b/priv/static/static/js/26.8fd0027b982c4bcdc88f.js.map differ diff --git a/priv/static/static/js/26.cf13231d524e5ca3b3e6.js.map b/priv/static/static/js/26.cf13231d524e5ca3b3e6.js.map deleted file mode 100644 index 8654bda10..000000000 Binary files a/priv/static/static/js/26.cf13231d524e5ca3b3e6.js.map and /dev/null differ diff --git a/priv/static/static/js/27.fca8d4f6e444bd14f376.js b/priv/static/static/js/27.6d90a54efba08d261d69.js similarity index 94% rename from priv/static/static/js/27.fca8d4f6e444bd14f376.js rename to priv/static/static/js/27.6d90a54efba08d261d69.js index 9f8b5c85d..e8420a54f 100644 Binary files a/priv/static/static/js/27.fca8d4f6e444bd14f376.js and b/priv/static/static/js/27.6d90a54efba08d261d69.js differ diff --git a/priv/static/static/js/27.6d90a54efba08d261d69.js.map b/priv/static/static/js/27.6d90a54efba08d261d69.js.map new file mode 100644 index 000000000..6685474ce Binary files /dev/null and b/priv/static/static/js/27.6d90a54efba08d261d69.js.map differ diff --git a/priv/static/static/js/27.fca8d4f6e444bd14f376.js.map b/priv/static/static/js/27.fca8d4f6e444bd14f376.js.map deleted file mode 100644 index f6ea8afc9..000000000 Binary files a/priv/static/static/js/27.fca8d4f6e444bd14f376.js.map and /dev/null differ diff --git a/priv/static/static/js/28.e0f9f164e0bfd890dc61.js.map b/priv/static/static/js/28.e0f9f164e0bfd890dc61.js.map deleted file mode 100644 index 536ae2d7a..000000000 Binary files a/priv/static/static/js/28.e0f9f164e0bfd890dc61.js.map and /dev/null differ diff --git a/priv/static/static/js/28.e0f9f164e0bfd890dc61.js b/priv/static/static/js/28.f1353aa382a104262d1a.js similarity index 98% rename from priv/static/static/js/28.e0f9f164e0bfd890dc61.js rename to priv/static/static/js/28.f1353aa382a104262d1a.js index 75ba6d69d..a253284f0 100644 Binary files a/priv/static/static/js/28.e0f9f164e0bfd890dc61.js and b/priv/static/static/js/28.f1353aa382a104262d1a.js differ diff --git a/priv/static/static/js/28.f1353aa382a104262d1a.js.map b/priv/static/static/js/28.f1353aa382a104262d1a.js.map new file mode 100644 index 000000000..3421c9511 Binary files /dev/null and b/priv/static/static/js/28.f1353aa382a104262d1a.js.map differ diff --git a/priv/static/static/js/29.0b69359f0fe5c0785746.js.map b/priv/static/static/js/29.0b69359f0fe5c0785746.js.map deleted file mode 100644 index 65cd6bc82..000000000 Binary files a/priv/static/static/js/29.0b69359f0fe5c0785746.js.map and /dev/null differ diff --git a/priv/static/static/js/29.0b69359f0fe5c0785746.js b/priv/static/static/js/29.39c1e87a689c840395b2.js similarity index 99% rename from priv/static/static/js/29.0b69359f0fe5c0785746.js rename to priv/static/static/js/29.39c1e87a689c840395b2.js index 24d73bcd5..ddb512279 100644 Binary files a/priv/static/static/js/29.0b69359f0fe5c0785746.js and b/priv/static/static/js/29.39c1e87a689c840395b2.js differ diff --git a/priv/static/static/js/29.39c1e87a689c840395b2.js.map b/priv/static/static/js/29.39c1e87a689c840395b2.js.map new file mode 100644 index 000000000..5901ce9b7 Binary files /dev/null and b/priv/static/static/js/29.39c1e87a689c840395b2.js.map differ diff --git a/priv/static/static/js/3.7d21accf4e5bd07e3ebf.js b/priv/static/static/js/3.a0df8a5bcd120d1f8581.js similarity index 99% rename from priv/static/static/js/3.7d21accf4e5bd07e3ebf.js rename to priv/static/static/js/3.a0df8a5bcd120d1f8581.js index d98aadec2..423121114 100644 Binary files a/priv/static/static/js/3.7d21accf4e5bd07e3ebf.js and b/priv/static/static/js/3.a0df8a5bcd120d1f8581.js differ diff --git a/priv/static/static/js/3.7d21accf4e5bd07e3ebf.js.map b/priv/static/static/js/3.a0df8a5bcd120d1f8581.js.map similarity index 99% rename from priv/static/static/js/3.7d21accf4e5bd07e3ebf.js.map rename to priv/static/static/js/3.a0df8a5bcd120d1f8581.js.map index 37826baac..653727d10 100644 Binary files a/priv/static/static/js/3.7d21accf4e5bd07e3ebf.js.map and b/priv/static/static/js/3.a0df8a5bcd120d1f8581.js.map differ diff --git a/priv/static/static/js/30.64736585965c63c2b5d4.js b/priv/static/static/js/30.64736585965c63c2b5d4.js new file mode 100644 index 000000000..4fdbe8c3e Binary files /dev/null and b/priv/static/static/js/30.64736585965c63c2b5d4.js differ diff --git a/priv/static/static/js/30.64736585965c63c2b5d4.js.map b/priv/static/static/js/30.64736585965c63c2b5d4.js.map new file mode 100644 index 000000000..376920946 Binary files /dev/null and b/priv/static/static/js/30.64736585965c63c2b5d4.js.map differ diff --git a/priv/static/static/js/30.fce58be0b52ca3e32fa4.js b/priv/static/static/js/30.fce58be0b52ca3e32fa4.js deleted file mode 100644 index 03a5d65f6..000000000 Binary files a/priv/static/static/js/30.fce58be0b52ca3e32fa4.js and /dev/null differ diff --git a/priv/static/static/js/30.fce58be0b52ca3e32fa4.js.map b/priv/static/static/js/30.fce58be0b52ca3e32fa4.js.map deleted file mode 100644 index f7dc83701..000000000 Binary files a/priv/static/static/js/30.fce58be0b52ca3e32fa4.js.map and /dev/null differ diff --git a/priv/static/static/js/4.5719922a4e807145346d.js b/priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js similarity index 77% rename from priv/static/static/js/4.5719922a4e807145346d.js rename to priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js index 91ea2ac5e..4da4c56fa 100644 Binary files a/priv/static/static/js/4.5719922a4e807145346d.js and b/priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js differ diff --git a/priv/static/static/js/4.5719922a4e807145346d.js.map b/priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js.map similarity index 99% rename from priv/static/static/js/4.5719922a4e807145346d.js.map rename to priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js.map index d5e592cfd..bc040ab9b 100644 Binary files a/priv/static/static/js/4.5719922a4e807145346d.js.map and b/priv/static/static/js/4.4cde7fdd1fe6bf2a9499.js.map differ diff --git a/priv/static/static/js/5.cf05c5ddbdbac890ae35.js b/priv/static/static/js/5.2e165bc072548e533dd4.js similarity index 98% rename from priv/static/static/js/5.cf05c5ddbdbac890ae35.js rename to priv/static/static/js/5.2e165bc072548e533dd4.js index f54d67fb3..cfd84226c 100644 Binary files a/priv/static/static/js/5.cf05c5ddbdbac890ae35.js and b/priv/static/static/js/5.2e165bc072548e533dd4.js differ diff --git a/priv/static/static/js/5.cf05c5ddbdbac890ae35.js.map b/priv/static/static/js/5.2e165bc072548e533dd4.js.map similarity index 57% rename from priv/static/static/js/5.cf05c5ddbdbac890ae35.js.map rename to priv/static/static/js/5.2e165bc072548e533dd4.js.map index 77f2d0898..49959c78e 100644 Binary files a/priv/static/static/js/5.cf05c5ddbdbac890ae35.js.map and b/priv/static/static/js/5.2e165bc072548e533dd4.js.map differ diff --git a/priv/static/static/js/6.260ccd84f8cd2af27970.js b/priv/static/static/js/6.260ccd84f8cd2af27970.js new file mode 100644 index 000000000..fb4a690f4 Binary files /dev/null and b/priv/static/static/js/6.260ccd84f8cd2af27970.js differ diff --git a/priv/static/static/js/6.ecfd3302a692de148391.js.map b/priv/static/static/js/6.260ccd84f8cd2af27970.js.map similarity index 57% rename from priv/static/static/js/6.ecfd3302a692de148391.js.map rename to priv/static/static/js/6.260ccd84f8cd2af27970.js.map index a17c7d297..850fe731a 100644 Binary files a/priv/static/static/js/6.ecfd3302a692de148391.js.map and b/priv/static/static/js/6.260ccd84f8cd2af27970.js.map differ diff --git a/priv/static/static/js/6.ecfd3302a692de148391.js b/priv/static/static/js/6.ecfd3302a692de148391.js deleted file mode 100644 index 354243ec2..000000000 Binary files a/priv/static/static/js/6.ecfd3302a692de148391.js and /dev/null differ diff --git a/priv/static/static/js/7.dd44c3d58fb9dced093d.js b/priv/static/static/js/7.1c41eff6cfc75a00bde4.js similarity index 99% rename from priv/static/static/js/7.dd44c3d58fb9dced093d.js rename to priv/static/static/js/7.1c41eff6cfc75a00bde4.js index cb95efc73..317770a53 100644 Binary files a/priv/static/static/js/7.dd44c3d58fb9dced093d.js and b/priv/static/static/js/7.1c41eff6cfc75a00bde4.js differ diff --git a/priv/static/static/js/7.dd44c3d58fb9dced093d.js.map b/priv/static/static/js/7.1c41eff6cfc75a00bde4.js.map similarity index 57% rename from priv/static/static/js/7.dd44c3d58fb9dced093d.js.map rename to priv/static/static/js/7.1c41eff6cfc75a00bde4.js.map index ae7e35d5d..36f327b3f 100644 Binary files a/priv/static/static/js/7.dd44c3d58fb9dced093d.js.map and b/priv/static/static/js/7.1c41eff6cfc75a00bde4.js.map differ diff --git a/priv/static/static/js/8.636322a87bb10a1754f8.js b/priv/static/static/js/8.9b35c2fee24ab7481e00.js similarity index 99% rename from priv/static/static/js/8.636322a87bb10a1754f8.js rename to priv/static/static/js/8.9b35c2fee24ab7481e00.js index 6e635fb6a..cb7844ffc 100644 Binary files a/priv/static/static/js/8.636322a87bb10a1754f8.js and b/priv/static/static/js/8.9b35c2fee24ab7481e00.js differ diff --git a/priv/static/static/js/8.636322a87bb10a1754f8.js.map b/priv/static/static/js/8.9b35c2fee24ab7481e00.js.map similarity index 57% rename from priv/static/static/js/8.636322a87bb10a1754f8.js.map rename to priv/static/static/js/8.9b35c2fee24ab7481e00.js.map index f074928a5..65f4d5ae9 100644 Binary files a/priv/static/static/js/8.636322a87bb10a1754f8.js.map and b/priv/static/static/js/8.9b35c2fee24ab7481e00.js.map differ diff --git a/priv/static/static/js/9.3a29094f1886648a0af3.js b/priv/static/static/js/9.3a29094f1886648a0af3.js new file mode 100644 index 000000000..eb3516dcd Binary files /dev/null and b/priv/static/static/js/9.3a29094f1886648a0af3.js differ diff --git a/priv/static/static/js/9.3a29094f1886648a0af3.js.map b/priv/static/static/js/9.3a29094f1886648a0af3.js.map new file mode 100644 index 000000000..1b6224a6a Binary files /dev/null and b/priv/static/static/js/9.3a29094f1886648a0af3.js.map differ diff --git a/priv/static/static/js/9.6010dbcce7b4d7c05a18.js b/priv/static/static/js/9.6010dbcce7b4d7c05a18.js deleted file mode 100644 index fcad39a7e..000000000 Binary files a/priv/static/static/js/9.6010dbcce7b4d7c05a18.js and /dev/null differ diff --git a/priv/static/static/js/9.6010dbcce7b4d7c05a18.js.map b/priv/static/static/js/9.6010dbcce7b4d7c05a18.js.map deleted file mode 100644 index e5e1cd823..000000000 Binary files a/priv/static/static/js/9.6010dbcce7b4d7c05a18.js.map and /dev/null differ diff --git a/priv/static/static/js/app.45547c05212c403dd77c.js b/priv/static/static/js/app.45547c05212c403dd77c.js new file mode 100644 index 000000000..219a59493 Binary files /dev/null and b/priv/static/static/js/app.45547c05212c403dd77c.js differ diff --git a/priv/static/static/js/app.45547c05212c403dd77c.js.map b/priv/static/static/js/app.45547c05212c403dd77c.js.map new file mode 100644 index 000000000..e1dd6c992 Binary files /dev/null and b/priv/static/static/js/app.45547c05212c403dd77c.js.map differ diff --git a/priv/static/static/js/app.826c44232e0a76bbd9ba.js b/priv/static/static/js/app.826c44232e0a76bbd9ba.js deleted file mode 100644 index 16762165e..000000000 Binary files a/priv/static/static/js/app.826c44232e0a76bbd9ba.js and /dev/null differ diff --git a/priv/static/static/js/app.826c44232e0a76bbd9ba.js.map b/priv/static/static/js/app.826c44232e0a76bbd9ba.js.map deleted file mode 100644 index b188c3379..000000000 Binary files a/priv/static/static/js/app.826c44232e0a76bbd9ba.js.map and /dev/null differ diff --git a/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js b/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js deleted file mode 100644 index 879a3b312..000000000 Binary files a/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js and /dev/null differ diff --git a/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js.map b/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js.map deleted file mode 100644 index 395f83b6b..000000000 Binary files a/priv/static/static/js/vendors~app.90c4af83c1ae68f4cd95.js.map and /dev/null differ diff --git a/priv/static/static/js/vendors~app.952124344a84613dbac0.js b/priv/static/static/js/vendors~app.952124344a84613dbac0.js new file mode 100644 index 000000000..f7943c122 Binary files /dev/null and b/priv/static/static/js/vendors~app.952124344a84613dbac0.js differ diff --git a/priv/static/static/js/vendors~app.952124344a84613dbac0.js.map b/priv/static/static/js/vendors~app.952124344a84613dbac0.js.map new file mode 100644 index 000000000..05fc07c18 Binary files /dev/null and b/priv/static/static/js/vendors~app.952124344a84613dbac0.js.map differ diff --git a/priv/static/static/logo.png b/priv/static/static/logo.png deleted file mode 100644 index 7744b1acc..000000000 Binary files a/priv/static/static/logo.png and /dev/null differ diff --git a/priv/static/static/logo.svg b/priv/static/static/logo.svg new file mode 100644 index 000000000..68e647e6c --- /dev/null +++ b/priv/static/static/logo.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/priv/static/static/terms-of-service.html b/priv/static/static/terms-of-service.html index 3b6bbb36b..2b7bf7697 100644 --- a/priv/static/static/terms-of-service.html +++ b/priv/static/static/terms-of-service.html @@ -6,4 +6,4 @@ Pleroma install containing the real ToS for your instance.

See the Pleroma documentation for more information.


- + diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index fa4969025..385ee2f0c 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/priv/static/sw-pleroma.js.map b/priv/static/sw-pleroma.js.map index cd5ea0ae6..0b6a76c2f 100644 Binary files a/priv/static/sw-pleroma.js.map and b/priv/static/sw-pleroma.js.map differ diff --git a/priv/templates/sample_config.eex b/priv/templates/sample_config.eex index bc7e37375..cdddc47ea 100644 --- a/priv/templates/sample_config.eex +++ b/priv/templates/sample_config.eex @@ -71,3 +71,7 @@ config :pleroma, Pleroma.Uploaders.Local, uploads: "<%= uploads_dir %>" config :joken, default_signer: "<%= jwt_secret %>" config :pleroma, configurable_from_database: <%= db_configurable? %> + +<%= if Kernel.length(upload_filters) > 0 do +"config :pleroma, Pleroma.Upload, filters: #{inspect(upload_filters)}" +end %> diff --git a/test/activity_expiration_test.exs b/test/activity_expiration_test.exs deleted file mode 100644 index f86d79826..000000000 --- a/test/activity_expiration_test.exs +++ /dev/null @@ -1,55 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.ActivityExpirationTest do - use Pleroma.DataCase - alias Pleroma.ActivityExpiration - import Pleroma.Factory - - setup do: clear_config([ActivityExpiration, :enabled]) - - test "finds activities due to be deleted only" do - activity = insert(:note_activity) - - expiration_due = - insert(:expiration_in_the_past, %{activity_id: activity.id}) |> Repo.preload(:activity) - - activity2 = insert(:note_activity) - insert(:expiration_in_the_future, %{activity_id: activity2.id}) - - expirations = ActivityExpiration.due_expirations() - - assert length(expirations) == 1 - assert hd(expirations) == expiration_due - end - - test "denies expirations that don't live long enough" do - activity = insert(:note_activity) - now = NaiveDateTime.utc_now() - assert {:error, _} = ActivityExpiration.create(activity, now) - end - - test "deletes an expiration activity" do - Pleroma.Config.put([ActivityExpiration, :enabled], true) - activity = insert(:note_activity) - - naive_datetime = - NaiveDateTime.add( - NaiveDateTime.utc_now(), - -:timer.minutes(2), - :millisecond - ) - - expiration = - insert( - :expiration_in_the_past, - %{activity_id: activity.id, scheduled_at: naive_datetime} - ) - - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) - - refute Pleroma.Repo.get(Pleroma.Activity, activity.id) - refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id) - end -end diff --git a/test/config/deprecation_warnings_test.exs b/test/config/deprecation_warnings_test.exs deleted file mode 100644 index 555661a71..000000000 --- a/test/config/deprecation_warnings_test.exs +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Pleroma.Config.DeprecationWarningsTest do - use ExUnit.Case, async: true - use Pleroma.Tests.Helpers - - import ExUnit.CaptureLog - - test "check_old_mrf_config/0" do - clear_config([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.NoOpPolicy) - clear_config([:instance, :mrf_transparency], true) - clear_config([:instance, :mrf_transparency_exclusions], []) - - assert capture_log(fn -> Pleroma.Config.DeprecationWarnings.check_old_mrf_config() end) =~ - """ - !!!DEPRECATION WARNING!!! - Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later: - - * `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies` - * `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency` - * `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions` - """ - end - - test "move_namespace_and_warn/2" do - old_group1 = [:group, :key] - old_group2 = [:group, :key2] - old_group3 = [:group, :key3] - - new_group1 = [:another_group, :key4] - new_group2 = [:another_group, :key5] - new_group3 = [:another_group, :key6] - - clear_config(old_group1, 1) - clear_config(old_group2, 2) - clear_config(old_group3, 3) - - clear_config(new_group1) - clear_config(new_group2) - clear_config(new_group3) - - config_map = [ - {old_group1, new_group1, "\n error :key"}, - {old_group2, new_group2, "\n error :key2"}, - {old_group3, new_group3, "\n error :key3"} - ] - - assert capture_log(fn -> - Pleroma.Config.DeprecationWarnings.move_namespace_and_warn( - config_map, - "Warning preface" - ) - end) =~ "Warning preface\n error :key\n error :key2\n error :key3" - - assert Pleroma.Config.get(new_group1) == 1 - assert Pleroma.Config.get(new_group2) == 2 - assert Pleroma.Config.get(new_group3) == 3 - end - - test "check_media_proxy_whitelist_config/0" do - clear_config([:media_proxy, :whitelist], ["https://example.com", "example2.com"]) - - assert capture_log(fn -> - Pleroma.Config.DeprecationWarnings.check_media_proxy_whitelist_config() - end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option" - end -end diff --git a/test/credo/check/consistency/file_location.ex b/test/credo/check/consistency/file_location.ex new file mode 100644 index 000000000..500983608 --- /dev/null +++ b/test/credo/check/consistency/file_location.ex @@ -0,0 +1,166 @@ +# Pleroma: A lightweight social networking server +# Originally taken from +# https://github.com/VeryBigThings/elixir_common/blob/master/lib/vbt/credo/check/consistency/file_location.ex +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Credo.Check.Consistency.FileLocation do + @moduledoc false + + # credo:disable-for-this-file Credo.Check.Readability.Specs + + @checkdoc """ + File location should follow the namespace hierarchy of the module it defines. + + Examples: + + - `lib/my_system.ex` should define the `MySystem` module + - `lib/my_system/accounts.ex` should define the `MySystem.Accounts` module + """ + @explanation [warning: @checkdoc] + + @special_namespaces [ + "controllers", + "views", + "operations", + "channels" + ] + + # `use Credo.Check` required that module attributes are already defined, so we need + # to place these attributes + # before use/alias expressions. + # credo:disable-for-next-line VBT.Credo.Check.Consistency.ModuleLayout + use Credo.Check, category: :warning, base_priority: :high + + alias Credo.Code + + def run(source_file, params \\ []) do + case verify(source_file, params) do + :ok -> + [] + + {:error, module, expected_file} -> + error(IssueMeta.for(source_file, params), module, expected_file) + end + end + + defp verify(source_file, params) do + source_file.filename + |> Path.relative_to_cwd() + |> verify(Code.ast(source_file), params) + end + + @doc false + def verify(relative_path, ast, params) do + if verify_path?(relative_path, params), + do: ast |> main_module() |> verify_module(relative_path, params), + else: :ok + end + + defp verify_path?(relative_path, params) do + case Path.split(relative_path) do + ["lib" | _] -> not exclude?(relative_path, params) + ["test", "support" | _] -> false + ["test", "test_helper.exs"] -> false + ["test" | _] -> not exclude?(relative_path, params) + _ -> false + end + end + + defp exclude?(relative_path, params) do + params + |> Keyword.get(:exclude, []) + |> Enum.any?(&String.starts_with?(relative_path, &1)) + end + + defp main_module(ast) do + {_ast, modules} = Macro.prewalk(ast, [], &traverse/2) + Enum.at(modules, -1) + end + + defp traverse({:defmodule, _meta, args}, modules) do + [{:__aliases__, _, name_parts}, _module_body] = args + {args, [Module.concat(name_parts) | modules]} + end + + defp traverse(ast, state), do: {ast, state} + + # empty file - shouldn't really happen, but we'll let it through + defp verify_module(nil, _relative_path, _params), do: :ok + + defp verify_module(main_module, relative_path, params) do + parsed_path = parsed_path(relative_path, params) + + expected_file = + expected_file_base(parsed_path.root, main_module) <> + Path.extname(parsed_path.allowed) + + cond do + expected_file == parsed_path.allowed -> + :ok + + special_namespaces?(parsed_path.allowed) -> + original_path = parsed_path.allowed + + namespace = + Enum.find(@special_namespaces, original_path, fn namespace -> + String.contains?(original_path, namespace) + end) + + allowed = String.replace(original_path, "/" <> namespace, "") + + if expected_file == allowed, + do: :ok, + else: {:error, main_module, expected_file} + + true -> + {:error, main_module, expected_file} + end + end + + defp special_namespaces?(path), do: String.contains?(path, @special_namespaces) + + defp parsed_path(relative_path, params) do + parts = Path.split(relative_path) + + allowed = + Keyword.get(params, :ignore_folder_namespace, %{}) + |> Stream.flat_map(fn {root, folders} -> Enum.map(folders, &Path.join([root, &1])) end) + |> Stream.map(&Path.split/1) + |> Enum.find(&List.starts_with?(parts, &1)) + |> case do + nil -> + relative_path + + ignore_parts -> + Stream.drop(ignore_parts, -1) + |> Enum.concat(Stream.drop(parts, length(ignore_parts))) + |> Path.join() + end + + %{root: hd(parts), allowed: allowed} + end + + defp expected_file_base(root_folder, module) do + {parent_namespace, module_name} = module |> Module.split() |> Enum.split(-1) + + relative_path = + if parent_namespace == [], + do: "", + else: parent_namespace |> Module.concat() |> Macro.underscore() + + file_name = module_name |> Module.concat() |> Macro.underscore() + + Path.join([root_folder, relative_path, file_name]) + end + + defp error(issue_meta, module, expected_file) do + format_issue(issue_meta, + message: + "Mismatch between file name and main module #{inspect(module)}. " <> + "Expected file path to be #{expected_file}. " <> + "Either move the file or rename the module.", + line_no: 1 + ) + end +end diff --git a/test/fixtures/23211.atom b/test/fixtures/23211.atom deleted file mode 100644 index d5d111baa..000000000 --- a/test/fixtures/23211.atom +++ /dev/null @@ -1,508 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-02T14:59:30+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2015260:2017-05-02T14:45:47+00:00 - Favorite - lambadalambda favorited something by godemperorofdune: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> It's because your instance decided to be trap! lol.</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T14:45:47+00:00 - 2017-05-02T14:45:47+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:pawoo.net,2017-05-02:objectId=7397439:objectType=Status - New comment by godemperorofdune - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> It's because your instance decided to be trap! lol.</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=136e244b26cdf1e9 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-02:noticeId=2015221:objectType=note - New note by lambadalambda - Some script thinks I'm a mastodon server.<br /> <br /> [info] GET /api/v1/timelines/public<br /> [debug] Processing with Fallback.RedirectController.redirector/2<br /> Parameters: %{&quot;limit&quot; =&gt; &quot;40&quot;, &quot;path&quot; =&gt; [&quot;api&quot;, &quot;v1&quot;, &quot;timelines&quot;, &quot;public&quot;]}<br /> Pipelines: [] - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T14:40:50+00:00 - 2017-05-02T14:40:50+00:00 - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=136e244b26cdf1e9 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2014759:objectType=comment - New comment by lambadalambda - @<a href="https://mstdn.io/users/mattskala" class="h-card u-url p-nickname mention" title="Matthew Skala">mattskala</a> You and @<a href="https://mastodon.social/users/kevinmarks" class="h-card u-url p-nickname mention" title="Kevin Marks">kevinmarks</a> are not wrong, but my comment was a suggestion to users and admins: Don't use big instances, don't run big instances. Also, it's a secondary advice to devs: Don't add features that encourage big instances. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T14:11:54+00:00 - 2017-05-02T14:11:54+00:00 - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2014684:objectType=comment - New comment by lambadalambda - @<a href="https://mastodon.social/users/Ronkjeffries" class="h-card u-url p-nickname mention" title="Ron K Jeffries social">ronkjeffries</a> @<a href="https://xoxo.zone/users/KevinMarks" class="h-card u-url p-nickname mention" title="Kevin Marks ">kevinmarks</a> Usually people who run their own private instance just look at the timelines of other servers, follow a seed population and then go from there. This is of course hard on Mastodon, because it doesn't have a publicly visible timeline. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T14:07:00+00:00 - 2017-05-02T14:07:00+00:00 - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2014584:2017-05-02T14:05:32+00:00 - Favorite - lambadalambda favorited something by mattskala: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> It's reasonable to expect that instance sizes will obey a power-law distribution because that's what such things in nature nearly always do. If so, there'll necessarily be a few instances much larger than the others; even if most are small, the network both socially and technically has to be able to deal with the existence of the few large ones.</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T14:05:32+00:00 - 2017-05-02T14:05:32+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:mstdn.io,2017-05-02:objectId=1316931:objectType=Status - New comment by mattskala - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> It's reasonable to expect that instance sizes will obey a power-law distribution because that's what such things in nature nearly always do. If so, there'll necessarily be a few instances much larger than the others; even if most are small, the network both socially and technically has to be able to deal with the existence of the few large ones.</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013568:2017-05-02T14:05:29+00:00 - Favorite - lambadalambda favorited something by kevinmarks: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> except instance populations will be power law distributed, and the problems for the tummlers are worse at scale</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T14:05:29+00:00 - 2017-05-02T14:05:29+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:xoxo.zone,2017-05-02:objectId=89478:objectType=Status - New comment by kevinmarks - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> except instance populations will be power law distributed, and the problems for the tummlers are worse at scale</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2014060:2017-05-02T13:34:32+00:00 - Favorite - lambadalambda favorited something by gcarregues: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> Oh purée ! Ma vie en images !</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T13:34:32+00:00 - 2017-05-02T13:34:32+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:mastodon.etalab.gouv.fr,2017-05-02:objectId=55287:objectType=Status - New comment by gcarregues - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> Oh purée ! Ma vie en images !</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:note:2013573:2017-05-02T13:03:33+00:00 - Favorite - lambadalambda favorited something by phildobangnz: also @<a href="https://sealion.club/user/579" class="h-card mention" title="Sim Bot">sim</a> reminder you are awesome; don't even trip- u kewler than Tutankhamen's cucumber, fam. Okay, good night. - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T13:03:33+00:00 - 2017-05-02T13:03:33+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:sealion.club,2017-05-02:noticeId=3060818:objectType=note - New note by phildobangnz - also @<a href="https://sealion.club/user/579" class="h-card mention" title="Sim Bot">sim</a> reminder you are awesome; don't even trip- u kewler than Tutankhamen's cucumber, fam. Okay, good night. - - - - - - - https://sealion.club/conversation/1633267 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2013586:objectType=comment - New comment by lambadalambda - @<a href="https://xoxo.zone/users/KevinMarks" class="h-card u-url p-nickname mention" title="Kevin Marks ">kevinmarks</a> People can stay in their giant unmoderatable instances with meaningless public and federated timelines and experience constant federation drama if they want. I'll stay here with my 5 friends. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T12:54:59+00:00 - 2017-05-02T12:54:59+00:00 - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=58e32e013ab6487d - - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:note:2013486:2017-05-02T12:46:48+00:00 - Favorite - lambadalambda favorited something by fortune: There once was a dentist named Stone<br /> Who saw all his patients alone.<br /> In a fit of depravity<br /> He filled the wrong cavity,<br /> And my, how his practice has grown! - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:46:48+00:00 - 2017-05-02T12:46:48+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.kawa-kun.com,2017-05-02:noticeId=1655658:objectType=note - New note by fortune - There once was a dentist named Stone<br /> Who saw all his patients alone.<br /> In a fit of depravity<br /> He filled the wrong cavity,<br /> And my, how his practice has grown! - - - - - - - https://gs.kawa-kun.com/conversation/714072 - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:note:2013365:2017-05-02T12:37:55+00:00 - Favorite - lambadalambda favorited something by xj9: <p>&gt; rollerblading to work</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:37:55+00:00 - 2017-05-02T12:37:55+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:sunshinegardens.org,2017-05-02:objectId=61020:objectType=Status - New note by xj9 - <p>&gt; rollerblading to work</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=5a0e98612f634218 - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013259:2017-05-02T12:29:03+00:00 - Favorite - lambadalambda favorited something by cereal: @<a href="https://gs.smuglo.li/user/28250" class="h-card mention" title="Bricky">thatbrickster</a> @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> But why? - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:29:03+00:00 - 2017-05-02T12:29:03+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:sealion.club,2017-05-02:noticeId=3059985:objectType=comment - New comment by cereal - @<a href="https://gs.smuglo.li/user/28250" class="h-card mention" title="Bricky">thatbrickster</a> @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> But why? - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013227:2017-05-02T12:24:27+00:00 - Favorite - lambadalambda favorited something by thatbrickster: @<a href="https://social.heldscal.la/user/23211" class="h-card u-url p-nickname mention" title="Constance Variable">lambadalambda</a> install gentoo - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:24:27+00:00 - 2017-05-02T12:24:27+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:gs.smuglo.li,2017-05-02:noticeId=2144296:objectType=comment - New comment by thatbrickster - @<a href="https://social.heldscal.la/user/23211" class="h-card u-url p-nickname mention" title="Constance Variable">lambadalambda</a> install gentoo - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013213:2017-05-02T12:22:53+00:00 - Favorite - lambadalambda favorited something by dwmatiz: @<a href="https://social.heldscal.la/user/23211" class="h-card mention">lambadalambda</a> *unzips dick* - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:22:53+00:00 - 2017-05-02T12:22:53+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:sealion.club,2017-05-02:noticeId=3059800:objectType=comment - New comment by dwmatiz - @<a href="https://social.heldscal.la/user/23211" class="h-card mention">lambadalambda</a> *unzips dick* - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2013199:2017-05-02T12:22:03+00:00 - Favorite - lambadalambda favorited something by shpuld: @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> get #<span class="tag"><a href="https://shitposter.club/tag/cofe" rel="tag">cofe</a></span> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:22:03+00:00 - 2017-05-02T12:22:03+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-02:noticeId=2783524:objectType=comment - New comment by shpuld - @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> get #<span class="tag"><a href="https://shitposter.club/tag/cofe" rel="tag">cofe</a></span> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-02:noticeId=2013185:objectType=note - New note by lambadalambda - What now? <a href="https://social.heldscal.la/file/e4822d95de677757ff50d49672a4046c83218b76c04a0ad5e5f1f0a9a9eb1a74.gif" title="https://social.heldscal.la/file/e4822d95de677757ff50d49672a4046c83218b76c04a0ad5e5f1f0a9a9eb1a74.gif" rel="nofollow external noreferrer" class="attachment" id="attachment-422572">https://social.heldscal.la/attachment/422572</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T12:21:04+00:00 - 2017-05-02T12:21:04+00:00 - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2c27c27df8ec4dcc - - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:note:2012929:2017-05-02T12:01:25+00:00 - Favorite - lambadalambda favorited something by drkmttr: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> I checked out No Agenda because I saw you mention it several time. Sadly, I wasn't impressed. I'm all about varying perspectives but Adam and John basically just sound like resentful curmudgeons. It seems like their shtick is basically playing devil's advocate to everything to arouse some discontent. Just my two cents. 😉</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T12:01:25+00:00 - 2017-05-02T12:01:25+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:mstdn.io,2017-05-02:objectId=1310093:objectType=Status - New note by drkmttr - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> I checked out No Agenda because I saw you mention it several time. Sadly, I wasn't impressed. I'm all about varying perspectives but Adam and John basically just sound like resentful curmudgeons. It seems like their shtick is basically playing devil's advocate to everything to arouse some discontent. Just my two cents. 😉</p> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=2f329b4eb20e83e2 - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2012336:2017-05-02T11:06:42+00:00 - Favorite - lambadalambda favorited something by clacke: @<a href="https://mastodon.org.uk/users/dick_turpin" class="h-card u-url p-nickname mention" title="dick_turpin">dickturpin</a> @<a href="http://quitter.se/user/113503" class="h-card u-url p-nickname mention" title="Luke">luke</a> Oh no, I miss being irritated by you, it helps me understand myself and others. Also it builds character. :-)<br /> <br /> So if this is not federation because you can't follow all of online mankind, what should we call it? Proto-federated? Pre-federated?<br /> <br /> The term has been used decades ago for just one Microsoft Active Directory domain cross-certifying the root of another, by mutual agreement. I don't see how it's any less relevant to opportunistic federation between open servers on an open internet.<br /> <br /> I'm not saying we should be satisfied, I'm just saying that "federate" is a useful word and to build a big system we need to start with a small one. And focus on the things we *can* change, like helping the OStatus network grow and making the tools more useful.<br /> <br /> Saying that the network's ideals have failed because other networks aren't joining is doing neither of that. - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T11:06:42+00:00 - 2017-05-02T11:06:42+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2012336:objectType=comment - New comment by clacke - @<a href="https://mastodon.org.uk/users/dick_turpin" class="h-card u-url p-nickname mention" title="dick_turpin">dickturpin</a> @<a href="http://quitter.se/user/113503" class="h-card u-url p-nickname mention" title="Luke">luke</a> Oh no, I miss being irritated by you, it helps me understand myself and others. Also it builds character. :-)<br /> <br /> So if this is not federation because you can't follow all of online mankind, what should we call it? Proto-federated? Pre-federated?<br /> <br /> The term has been used decades ago for just one Microsoft Active Directory domain cross-certifying the root of another, by mutual agreement. I don't see how it's any less relevant to opportunistic federation between open servers on an open internet.<br /> <br /> I'm not saying we should be satisfied, I'm just saying that &quot;federate&quot; is a useful word and to build a big system we need to start with a small one. And focus on the things we *can* change, like helping the OStatus network grow and making the tools more useful.<br /> <br /> Saying that the network's ideals have failed because other networks aren't joining is doing neither of that. - - - - - - - https://s.wefamlee.be/conversation/16478 - - - - - - - tag:social.heldscal.la,2017-05-02:fave:23211:comment:2011332:2017-05-02T10:37:40+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> <a href="https://www.youtube.com/watch?v=mKLizztikRk" title="https://www.youtube.com/watch?v=mKLizztikRk" class="attachment" rel="nofollow">https://www.youtube.com/watch?v=mKLizztikRk</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-02T10:37:40+00:00 - 2017-05-02T10:37:40+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-02:noticeId=2781833:objectType=comment - New comment by moonman - @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> <a href="https://www.youtube.com/watch?v=mKLizztikRk" title="https://www.youtube.com/watch?v=mKLizztikRk" class="attachment" rel="nofollow">https://www.youtube.com/watch?v=mKLizztikRk</a> - - - - - - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=11d8b8c27d9513ec - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-02:noticeId=2012145:objectType=comment - New comment by lambadalambda - @<a href="https://sealion.club/user/186" class="h-card u-url p-nickname mention" title="I'M CEREAL U GUISE">cereal</a> ? No, you don't even need the identity servers for federation. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T10:37:33+00:00 - 2017-05-02T10:37:33+00:00 - - - - https://sealion.club/conversation/1629037 - - - - - - - diff --git a/test/fixtures/config/temp.secret.exs b/test/fixtures/config/temp.secret.exs index fa8c7c7e8..621bc8cf6 100644 --- a/test/fixtures/config/temp.secret.exs +++ b/test/fixtures/config/temp.secret.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + use Mix.Config config :pleroma, :first_setting, key: "value", key2: [Pleroma.Repo] diff --git a/test/fixtures/custom_instance_panel.html b/test/fixtures/custom_instance_panel.html new file mode 100644 index 000000000..6371a1e43 --- /dev/null +++ b/test/fixtures/custom_instance_panel.html @@ -0,0 +1 @@ +

Custom instance panel

\ No newline at end of file diff --git a/test/fixtures/cw_retweet.xml b/test/fixtures/cw_retweet.xml deleted file mode 100644 index c99a569d7..000000000 --- a/test/fixtures/cw_retweet.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - - - - - - tag:mastodon.social,2017-05-11:objectId=5647963:objectType=Status - 2017-05-11T10:23:15Z - 2017-05-11T10:23:15Z - lambadalambda shared a status by Skruyb@mamot.fr - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:mamot.fr,2017-05-10:objectId=1294943:objectType=Status - 2017-05-10T17:31:44Z - 2017-05-10T17:31:45Z - New status by Skruyb@mamot.fr - - https://mamot.fr/users/Skruyb - http://activitystrea.ms/schema/1.0/person - https://mamot.fr/users/Skruyb - Skruyb - Skruyb@mamot.fr - <p>Fr and En.<br>Posts will disappear on a regular basis.</p> - - - - Skruyb - The 7th Son - Fr and En.Posts will disappear on a regular basis. - public - - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - Hey. - <p><span class="h-card"><a href="https://mastodon.social/@lambadalambda">@<span>lambadalambda</span></a></span></p><p>Hey!!!</p> - - - public - - - - <p><span class="h-card"><a href="https://mastodon.social/@lambadalambda">@<span>lambadalambda</span></a></span></p><p>Hey!!!</p> - - public - - - - diff --git a/test/fixtures/delete.xml b/test/fixtures/delete.xml deleted file mode 100644 index 731e1c204..000000000 --- a/test/fixtures/delete.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - https://mastodon.sdf.org/users/snowdusk.atom - snowdusk - Amateur live performance DJ/radio DJ on SDF's underground Internet radio http://aNONradio.net (LIVE Sat Sun Mon Tue 23:00-24:00 UTC) - http://snowdusk.sdf.org - 2017-06-17T04:14:34Z - https://mastodon.sdf.org/system/accounts/avatars/000/000/002/original/405a7652d5f60449.jpg?1497672873 - - https://mastodon.sdf.org/users/snowdusk - http://activitystrea.ms/schema/1.0/person - https://mastodon.sdf.org/users/snowdusk - snowdusk - snowdusk@mastodon.sdf.org - <p>Amateur live performance DJ/radio DJ on SDF&apos;s underground Internet radio <a href="http://anonradio.net/" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">anonradio.net/</span><span class="invisible"></span></a> (LIVE Sat Sun Mon Tue 23:00-24:00 UTC) - <a href="http://snowdusk.sdf.org/" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">snowdusk.sdf.org/</span><span class="invisible"></span></a></p> - - - - snowdusk - snowdusk - Amateur live performance DJ/radio DJ on SDF's underground Internet radio http://aNONradio.net (LIVE Sat Sun Mon Tue 23:00-24:00 UTC) - http://snowdusk.sdf.org - public - - - - - - - - tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status - 2017-06-10T22:02:31Z - 2017-06-10T22:02:31Z - snowdusk deleted status - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/delete - Deleted status - - - - diff --git a/test/fixtures/dm.xml b/test/fixtures/dm.xml deleted file mode 100644 index d0b8aa811..000000000 --- a/test/fixtures/dm.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - tag:mastodon.social,2017-06-30:objectId=11260427:objectType=Status - 2017-06-30T13:27:47Z - 2017-06-30T13:27:47Z - New status by lambadalambda - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - lambadalambda - Critical Value - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey.</p> - - direct - - - - diff --git a/test/fixtures/emojis.zip b/test/fixtures/emojis.zip new file mode 100644 index 000000000..d7fc4732b Binary files /dev/null and b/test/fixtures/emojis.zip differ diff --git a/test/fixtures/empty.zip b/test/fixtures/empty.zip new file mode 100644 index 000000000..15cb0ecb3 Binary files /dev/null and b/test/fixtures/empty.zip differ diff --git a/test/fixtures/favorite.xml b/test/fixtures/favorite.xml deleted file mode 100644 index c32b4a403..000000000 --- a/test/fixtures/favorite.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-05T09:12:53+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:12:50+00:00 - 2017-05-05T09:12:50+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - diff --git a/test/fixtures/favorite_with_local_note.xml b/test/fixtures/favorite_with_local_note.xml deleted file mode 100644 index 3c955607d..000000000 --- a/test/fixtures/favorite_with_local_note.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-05T09:12:53+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:12:50+00:00 - 2017-05-05T09:12:50+00:00 - - http://activitystrea.ms/schema/1.0/comment - localid - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - diff --git a/test/fixtures/follow.xml b/test/fixtures/follow.xml deleted file mode 100644 index d4e89954b..000000000 --- a/test/fixtures/follow.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-07T09:54:49+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00 - Constance Variable (lambadalambda@social.heldscal.la)'s status on Sunday, 07-May-2017 09:54:49 UTC - <a href="https://social.heldscal.la/lambadalambda">Constance Variable</a> started following <a href="https://pawoo.net/@pekorino">mono</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-05-07T09:54:49+00:00 - 2017-05-07T09:54:49+00:00 - - http://activitystrea.ms/schema/1.0/person - https://pawoo.net/users/pekorino - mono - http://shitposter.club/mono 孤独のグルメ - - - - - pekorino - mono - http://shitposter.club/mono 孤独のグルメ - - - tag:social.heldscal.la,2017-05-07:objectType=thread:nonce=6e80caf94e03029f - - - - - - diff --git a/test/fixtures/image.gif b/test/fixtures/image.gif new file mode 100755 index 000000000..9df64778b Binary files /dev/null and b/test/fixtures/image.gif differ diff --git a/test/fixtures/image.png b/test/fixtures/image.png new file mode 100755 index 000000000..e999e8800 Binary files /dev/null and b/test/fixtures/image.png differ diff --git a/test/fixtures/incoming_note_activity.xml b/test/fixtures/incoming_note_activity.xml deleted file mode 100644 index 21eda2d30..000000000 --- a/test/fixtures/incoming_note_activity.xml +++ /dev/null @@ -1,42 +0,0 @@ - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-23:noticeId=29:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain3" class="h-card mention">lain3</a> - - - - - http://activitystrea.ms/schema/1.0/post - 2017-04-23T14:51:03+00:00 - 2017-04-23T14:51:03+00:00 - - http://activitystrea.ms/schema/1.0/person - http://gs.example.org:4040/index.php/user/1 - lambda - - - - - lambda - lambda - - - - - tag:gs.example.org:4040,2017-04-23:objectType=thread:nonce=f09e22f58abd5c7b - - - - http://gs.example.org:4040/index.php/api/statuses/user_timeline/1.atom - lambda - - - - http://gs.example.org:4040/theme/neo-gnu/default-avatar-profile.png - 2017-04-23T14:51:03+00:00 - - - - - diff --git a/test/fixtures/incoming_note_activity_answer.xml b/test/fixtures/incoming_note_activity_answer.xml deleted file mode 100644 index b1244faa6..000000000 --- a/test/fixtures/incoming_note_activity_answer.xml +++ /dev/null @@ -1,42 +0,0 @@ - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note - New note by lambda - hey. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:16:13+00:00 - 2017-04-25T18:16:13+00:00 - - http://activitystrea.ms/schema/1.0/person - http://gs.example.org:4040/index.php/user/1 - lambda - - - - - lambda - lambda - - - - - - - http://pleroma.example.org:4000/contexts/8f6f45d4-8e4d-4e1a-a2de-09f27367d2d0 - - - - http://gs.example.org:4040/index.php/api/statuses/user_timeline/1.atom - lambda - - - - http://gs.example.org:4040/theme/neo-gnu/default-avatar-profile.png - 2017-04-25T18:16:13+00:00 - - - - - diff --git a/test/fixtures/incoming_reply_mastodon.xml b/test/fixtures/incoming_reply_mastodon.xml deleted file mode 100644 index 8ee1186cc..000000000 --- a/test/fixtures/incoming_reply_mastodon.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - tag:mastodon.social,2017-05-02:objectId=4901603:objectType=Status - 2017-05-02T18:33:06Z - 2017-05-02T18:33:06Z - New status by lambadalambda - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> hey</p> - - - public - - - - diff --git a/test/fixtures/incoming_websub_gnusocial_attachments.xml b/test/fixtures/incoming_websub_gnusocial_attachments.xml deleted file mode 100644 index 9d331ef32..000000000 --- a/test/fixtures/incoming_websub_gnusocial_attachments.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-02T20:29:35+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-02:noticeId=2020923:objectType=note - New note by lambadalambda - Okay gonna stream some cool games!! <a href="https://social.heldscal.la/file/7ed5ee508e6376a6e3dd581e17e7ed0b7b638147c7e86784bf83abc2641ee3d4.gif" title="https://social.heldscal.la/file/7ed5ee508e6376a6e3dd581e17e7ed0b7b638147c7e86784bf83abc2641ee3d4.gif" rel="nofollow external noreferrer" class="attachment" id="attachment-423842">https://social.heldscal.la/attachment/423842</a> <a href="https://social.heldscal.la/file/4c209099cadfc5afd3e27a334aa0db96b3a7510dde1603305d68a2707e59a11f.png" title="https://social.heldscal.la/file/4c209099cadfc5afd3e27a334aa0db96b3a7510dde1603305d68a2707e59a11f.png" rel="nofollow external noreferrer" class="attachment" id="attachment-423843">https://social.heldscal.la/attachment/423843</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-02T20:29:35+00:00 - 2017-05-02T20:29:35+00:00 - - tag:social.heldscal.la,2017-05-02:objectType=thread:nonce=26c7afdcbcf4ebd4 - - - - - - - - diff --git a/test/fixtures/lambadalambda.atom b/test/fixtures/lambadalambda.atom deleted file mode 100644 index 964a416f7..000000000 --- a/test/fixtures/lambadalambda.atom +++ /dev/null @@ -1,479 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif?1492379244 - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - a cool dude. - - - - lambadalambda - Critical Value - public - - - - - - - - tag:mastodon.social,2017-04-07:objectId=1874242:objectType=Status - 2017-04-07T11:02:56Z - 2017-04-07T11:02:56Z - lambadalambda shared a status by 0xroy@social.wxcafe.net - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.wxcafe.net,2017-04-07:objectId=72554:objectType=Status - 2017-04-07T11:01:59Z - 2017-04-07T11:02:00Z - New status by 0xroy@social.wxcafe.net - - https://social.wxcafe.net/users/0xroy - http://activitystrea.ms/schema/1.0/person - https://social.wxcafe.net/users/0xroy - 0xroy - 0xroy@social.wxcafe.net - ta caution weeb | discussions privées : <a href="https://💌.0xroy.me" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">💌.0xroy.me</span><span class="invisible"></span></a> - - - - 0xroy - 「R O Y 🍵 B O S」 - ta caution weeb | discussions privées : <a href="https://%F0%9F%92%8C.0xroy.me" rel="nofollow noopener"><span class="invisible">https://</span><span class="">💌.0xroy.me</span><span class="invisible"></span></a> - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>someone pls eli5 matrix (protocol) and riot</p> - - public - - - <p>someone pls eli5 matrix (protocol) and riot</p> - - public - - - - - tag:mastodon.social,2017-04-06:objectId=1768247:objectType=Status - 2017-04-06T11:10:19Z - 2017-04-06T11:10:19Z - lambadalambda shared a status by areyoutoo@mastodon.xyz - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:mastodon.xyz,2017-04-05:objectId=133327:objectType=Status - 2017-04-05T17:36:41Z - 2017-04-05T18:12:14Z - New status by areyoutoo@mastodon.xyz - - https://mastodon.xyz/users/areyoutoo - http://activitystrea.ms/schema/1.0/person - https://mastodon.xyz/users/areyoutoo - areyoutoo - areyoutoo@mastodon.xyz - devops | retired gamedev | always boost puppy pics - - - - areyoutoo - Raw Butter - devops | retired gamedev | always boost puppy pics - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Some UX thoughts for <a href="https://mastodon.xyz/tags/mastodev" class="mention hashtag">#<span>mastodev</span></a>:</p><p>- Would be nice if I could work on multiple draft toots? Clicking to reply to someone seems to erase any draft I had been working on.</p><p>- Kinda risky to click on the Federated Timeline if it loads new toots and scrolls 10ms before I click on something.</p><p>I probably don't know enough web frontend to help, but it might be fun to try.</p> - - - public - - - <p>Some UX thoughts for <a href="https://mastodon.xyz/tags/mastodev" class="mention hashtag">#<span>mastodev</span></a>:</p><p>- Would be nice if I could work on multiple draft toots? Clicking to reply to someone seems to erase any draft I had been working on.</p><p>- Kinda risky to click on the Federated Timeline if it loads new toots and scrolls 10ms before I click on something.</p><p>I probably don't know enough web frontend to help, but it might be fun to try.</p> - - public - - - - - tag:mastodon.social,2017-04-06:objectId=1764509:objectType=Status - 2017-04-06T10:15:38Z - 2017-04-06T10:15:38Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - This is a test for cw federation - <p>This is a test for cw federation body text.</p> - - public - - - - - tag:mastodon.social,2017-04-05:objectId=1645208:objectType=Status - 2017-04-05T07:14:53Z - 2017-04-05T07:14:53Z - lambadalambda shared a status by lambadalambda@social.heldscal.la - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.heldscal.la,2017-04-05:noticeId=1502088:objectType=note - 2017-04-05T06:12:09Z - 2017-04-05T07:12:47Z - New status by lambadalambda@social.heldscal.la - - https://social.heldscal.la/user/23211 - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - lambadalambda@social.heldscal.la - Call me Deacon Blues. - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Federation 101: <a href="https://www.youtube.com/watch?v=t1lYU5CA40o" rel="nofollow external noreferrer" class="attachment thumbnail">https://www.youtube.com/watch?v=t1lYU5CA40o</a> - - public - - - Federation 101: <a href="https://www.youtube.com/watch?v=t1lYU5CA40o" rel="nofollow external noreferrer" class="attachment thumbnail">https://www.youtube.com/watch?v=t1lYU5CA40o</a> - - public - - - - - tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status - 2017-04-05T05:44:48Z - 2017-04-05T05:44:48Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> just a test.</p> - - - public - - - - - tag:mastodon.social,2017-04-04:objectId=1540149:objectType=Status - 2017-04-04T06:31:09Z - 2017-04-04T06:31:09Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Looks like you still can&apos;t delete your account here (PRIVACY!), but I won&apos;t be posting here anymore, my main account is <span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span></p> - - - public - - - - - tag:mastodon.social,2017-04-04:objectId=1539608:objectType=Status - 2017-04-04T06:18:16Z - 2017-04-04T06:18:16Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@ghostbar" class="u-url mention">@<span>ghostbar</span></a></span> Remember to rewrite it in Rust once you&apos;re done.</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1504813:objectType=Status - 2017-04-03T18:01:20Z - 2017-04-03T18:01:20Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.xyz/@Azurolu" class="u-url mention">@<span>Azurolu</span></a></span> You mean gs.smuglo.li?</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1504805:objectType=Status - 2017-04-03T18:01:05Z - 2017-04-03T18:01:05Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>There&apos;s nothing wrong with having several alt accounts all across the fediverse. Try out another mastodon instance (<a href="https://icosahedron.website" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">icosahedron.website</span><span class="invisible"></span></a>) or a GNU Social instance (like <a href="https://shitposter.club" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">shitposter.club</span><span class="invisible"></span></a> or <a href="https://freezepeach.xyz" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">freezepeach.xyz</span><span class="invisible"></span></a>), or friendica. They are all on the same network, so you can still follow all your friends!</p> - - public - - - - - tag:mastodon.social,2017-04-03:objectId=1503965:objectType=Status - 2017-04-03T17:31:30Z - 2017-04-03T17:31:30Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@20Hz" class="u-url mention">@<span>20Hz</span></a></span> you could also try out a GS instance, which are on the same network :)</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1503955:objectType=Status - 2017-04-03T17:31:08Z - 2017-04-03T17:31:08Z - lambadalambda shared a status by shpuld@shitposter.club - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:shitposter.club,2017-04-03:noticeId=2251717:objectType=note - 2017-04-03T17:06:43Z - 2017-04-03T17:12:06Z - New status by shpuld@shitposter.club - - https://shitposter.club/user/5381 - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/5381 - shpuld - shpuld@shitposter.club - - - - - shpuld - shp - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - reposting the classic <a href="https://shitposter.club/file/89c5fe483526caf3a46cfc5cdd4ae68061054350e767397731af658d54786e31.jpg" class="attachment" rel="nofollow external">https://shitposter.club/attachment/219846</a> - - - public - - - reposting the classic <a href="https://shitposter.club/file/89c5fe483526caf3a46cfc5cdd4ae68061054350e767397731af658d54786e31.jpg" class="attachment" rel="nofollow external">https://shitposter.club/attachment/219846</a> - - public - - - - - tag:mastodon.social,2017-04-03:objectId=1503929:objectType=Status - 2017-04-03T17:30:43Z - 2017-04-03T17:30:43Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@ghostbar" class="u-url mention">@<span>ghostbar</span></a></span> Normally you shouldn&apos;t be running tens of thousands of users on one instance... That&apos;s one of the reasons for federation.</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1477255:objectType=Status - 2017-04-03T08:24:39Z - 2017-04-03T08:24:39Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@dot_tiff" class="u-url mention">@<span>dot_tiff</span></a></span> it&apos;s the vaporwave mode.</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1476210:objectType=Status - 2017-04-03T07:45:42Z - 2017-04-03T07:45:42Z - lambadalambda shared a status by lambadalambda@social.heldscal.la - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.heldscal.la,2017-04-03:noticeId=1475727:objectType=note - 2017-04-03T07:44:43Z - 2017-04-03T07:44:48Z - New status by lambadalambda@social.heldscal.la - - https://social.heldscal.la/user/23211 - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - lambadalambda@social.heldscal.la - Call me Deacon Blues. - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Here's a song by the original anti-idol, Togawa Jun: <a href="https://www.youtube.com/watch?v=kNI_NK2YY-s" rel="nofollow external noreferrer" class="attachment">https://www.youtube.com/watch?v=kNI_NK2YY-s</a> - - public - - - Here's a song by the original anti-idol, Togawa Jun: <a href="https://www.youtube.com/watch?v=kNI_NK2YY-s" rel="nofollow external noreferrer" class="attachment">https://www.youtube.com/watch?v=kNI_NK2YY-s</a> - - public - - - - - tag:mastodon.social,2017-04-03:objectId=1476047:objectType=Status - 2017-04-03T07:39:14Z - 2017-04-03T07:39:14Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@amrrr" class="u-url mention">@<span>amrrr</span></a></span> tumblr/10, but pretty good!</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1475949:objectType=Status - 2017-04-03T07:35:45Z - 2017-04-03T07:35:45Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@Shookaite" class="u-url mention">@<span>Shookaite</span></a></span> Oh, you mean like userstyles?</p> - - - public - - - - - - tag:mastodon.social,2017-04-03:objectId=1475581:objectType=Status - 2017-04-03T07:20:03Z - 2017-04-03T07:20:03Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@Shookaite" class="u-url mention">@<span>Shookaite</span></a></span> Would be nice if someone helped port Pleroma to Mastodon, that has a theme switcher (click on the cog in the upper right): <a href="https://pleroma.heldscal.la/main/all" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">pleroma.heldscal.la/main/all</span><span class="invisible"></span></a></p> - - - public - - - - - - tag:mastodon.social,2017-04-02:objectId=1457325:objectType=Status - 2017-04-02T21:57:43Z - 2017-04-02T21:57:43Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@rhosyn" class="u-url mention">@<span>rhosyn</span></a></span> <span class="h-card"><a href="https://mastodon.social/@Meaningness" class="u-url mention">@<span>Meaningness</span></a></span> you could take a look at those listed at social.guhnoo.org</p> - - - - public - - - - - - tag:mastodon.social,2017-04-02:objectId=1447926:objectType=Status - 2017-04-02T18:31:52Z - 2017-04-02T18:31:52Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>My main account is <span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> , btw.</p> - - - public - - - - - tag:mastodon.social,2017-04-02:objectId=1447878:objectType=Status - 2017-04-02T18:30:37Z - 2017-04-02T18:30:37Z - lambadalambda shared a status by Firstaide@awoo.space - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:awoo.space,2017-04-02:objectId=135324:objectType=Status - 2017-04-02T18:29:32Z - 2017-04-02T18:29:32Z - New status by Firstaide@awoo.space - - https://awoo.space/users/Firstaide - http://activitystrea.ms/schema/1.0/person - https://awoo.space/users/Firstaide - Firstaide - Firstaide@awoo.space - A smol awoo account, for a smol autistic 💙 -They/them please! -NB/white/ace - - - - Firstaide - Miff🚑✨ - A smol awoo account, for a smol autistic 💙 -They/them please! -NB/white/ace - public - - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><a href="https://mastodon.social/users/lambadalambda" class="h-card u-url p-nickname mention">@<span>lambadalambda</span></a> yeah, I think that's p much the big issue here? <br>When I first heard of Masto, I thought it was just like twitter at first, I had no idea federation was even a thing?, and I actually joined p early on? :-o </p><p>idk I think more stuff needs to be done about federation promotion, but honestly its gotta come from the get go when people get here to make an account I feel :-o</p> - - - public - - - - <p><a href="https://mastodon.social/users/lambadalambda" class="h-card u-url p-nickname mention">@<span>lambadalambda</span></a> yeah, I think that's p much the big issue here? <br>When I first heard of Masto, I thought it was just like twitter at first, I had no idea federation was even a thing?, and I actually joined p early on? :-o </p><p>idk I think more stuff needs to be done about federation promotion, but honestly its gotta come from the get go when people get here to make an account I feel :-o</p> - - public - - - - diff --git a/test/fixtures/mastodon-note-cw.xml b/test/fixtures/mastodon-note-cw.xml deleted file mode 100644 index 02f49dd61..000000000 --- a/test/fixtures/mastodon-note-cw.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - - - - - - tag:mastodon.social,2017-05-10:objectId=5551985:objectType=Status - 2017-05-10T12:21:36Z - 2017-05-10T12:21:36Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - technologic - <p>test</p> - - public - - - - diff --git a/test/fixtures/mastodon-note-unlisted.xml b/test/fixtures/mastodon-note-unlisted.xml deleted file mode 100644 index d21017b80..000000000 --- a/test/fixtures/mastodon-note-unlisted.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - - - - - - tag:mastodon.social,2017-05-10:objectId=5551985:objectType=Status - 2017-05-10T12:21:36Z - 2017-05-10T12:21:36Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - technologic - <p>test</p> - unlisted - - - - diff --git a/test/fixtures/mastodon-post-activity-nsfw.json b/test/fixtures/mastodon-post-activity-nsfw.json new file mode 100644 index 000000000..70729a1bd --- /dev/null +++ b/test/fixtures/mastodon-post-activity-nsfw.json @@ -0,0 +1,68 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "Emoji": "toot:Emoji", + "Hashtag": "as:Hashtag", + "atomUri": "ostatus:atomUri", + "conversation": "ostatus:conversation", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "movedTo": "as:movedTo", + "ostatus": "http://ostatus.org#", + "toot": "http://joinmastodon.org/ns#" + } + ], + "actor": "http://mastodon.example.org/users/admin", + "cc": [ + "http://mastodon.example.org/users/admin/followers", + "http://localtesting.pleroma.lol/users/lain" + ], + "id": "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity", + "nickname": "lain", + "object": { + "atomUri": "http://mastodon.example.org/users/admin/statuses/99512778738411822", + "attachment": [], + "attributedTo": "http://mastodon.example.org/users/admin", + "cc": [ + "http://mastodon.example.org/users/admin/followers", + "http://localtesting.pleroma.lol/users/lain" + ], + "content": "

@lain #moo

", + "conversation": "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation", + "id": "http://mastodon.example.org/users/admin/statuses/99512778738411822", + "inReplyTo": null, + "inReplyToAtomUri": null, + "published": "2018-02-12T14:08:20Z", + "summary": "cw", + "tag": [ + { + "href": "http://localtesting.pleroma.lol/users/lain", + "name": "@lain@localtesting.pleroma.lol", + "type": "Mention" + }, + { + "href": "http://mastodon.example.org/tags/nsfw", + "name": "#NSFW", + "type": "Hashtag" + } + ], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Note", + "url": "http://mastodon.example.org/@admin/99512778738411822" + }, + "published": "2018-02-12T14:08:20Z", + "signature": { + "created": "2018-02-12T14:08:20Z", + "creator": "http://mastodon.example.org/users/admin#main-key", + "signatureValue": "rnNfcopkc6+Ju73P806popcfwrK9wGYHaJVG1/ZvrlEbWVDzaHjkXqj9Q3/xju5l8CSn9tvSgCCtPFqZsFQwn/pFIFUcw7ZWB2xi4bDm3NZ3S4XQ8JRaaX7og5hFxAhWkGhJhAkfxVnOg2hG+w2d/7d7vRVSC1vo5ip4erUaA/PkWusZvPIpxnRWoXaxJsFmVx0gJgjpJkYDyjaXUlp+jmaoseeZ4EPQUWqHLKJ59PRG0mg8j2xAjYH9nQaN14qMRmTGPxY8gfv/CUFcatA+8VJU9KEsJkDAwLVvglydNTLGrxpAJU78a2eaht0foV43XUIZGe3DKiJPgE+UOKGCJw==", + "type": "RsaSignature2017" + }, + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Create" +} diff --git a/test/fixtures/mastodon-problematic.xml b/test/fixtures/mastodon-problematic.xml deleted file mode 100644 index a39e72759..000000000 --- a/test/fixtures/mastodon-problematic.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - https://icosahedron.website/users/shel.atom - shel🍖‼️ - Gay jackal dog, poet, future librarian. - -http://datapup.info -avatar: @puppytube@twitter.com - 2017-05-02T23:26:01Z - https://icosahedron.website/system/accounts/avatars/000/001/207/original/b1e07b09ae1cc787.png?1493767561 - - https://icosahedron.website/users/shel - http://activitystrea.ms/schema/1.0/person - https://icosahedron.website/users/shel - shel - shel@icosahedron.website - <p>Gay jackal dog, poet, future librarian. </p><p><a href="http://datapup.info/" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">datapup.info/</span><span class="invisible"></span></a><br />avatar: @puppytube@twitter.com</p> - - - - shel - shel🍖‼️ - Gay jackal dog, poet, future librarian. - -http://datapup.info -avatar: @puppytube@twitter.com - public - - - - - - - tag:icosahedron.website,2017-05-10:objectId=1414013:objectType=Status - 2017-05-10T17:16:24Z - 2017-05-10T17:16:24Z - shel shared a status by instance_names@cybre.space - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:cybre.space,2017-05-10:objectId=946671:objectType=Status - 2017-05-10T17:15:51Z - 2017-05-10T17:15:52Z - New status by instance_names@cybre.space - - https://cybre.space/users/instance_names - http://activitystrea.ms/schema/1.0/person - https://cybre.space/users/instance_names - instance_names - instance_names@cybre.space - <p>name ideas for your new mastodon instance. made by <span class="h-card"><a href="https://witches.town/@lycaon">@<span>lycaon</span></a></span> source available at <a href="https://github.com/LycaonIsAWolf/instance_names"><span class="invisible">https://</span><span class="ellipsis">github.com/LycaonIsAWolf/insta</span><span class="invisible">nce_names</span></a></p> - - - - instance_names - instance names - name ideas for your new mastodon instance. made by @lycaon source available at https://github.com/LycaonIsAWolf/instance_names - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>dildo.codes</p> - unlisted - - - <p>dildo.codes</p> - - public - - - - diff --git a/test/fixtures/mastodon_conversation.xml b/test/fixtures/mastodon_conversation.xml deleted file mode 100644 index 8faab2304..000000000 --- a/test/fixtures/mastodon_conversation.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - tag:mastodon.social,2017-08-28:objectId=16402826:objectType=Status - 2017-08-28T17:58:55Z - 2017-08-28T17:58:55Z - New status by lambadalambda - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>test. <a href="https://mastodon.social/media/XCp0OHGPON9kWZwhjaI" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">mastodon.social/media/XCp0OHGP</span><span class="invisible">ON9kWZwhjaI</span></a></p> - - - public - - - - diff --git a/test/fixtures/mewmew_no_name.json b/test/fixtures/mewmew_no_name.json new file mode 100644 index 000000000..532d4cf70 --- /dev/null +++ b/test/fixtures/mewmew_no_name.json @@ -0,0 +1,46 @@ +{ + "@context" : [ + "https://www.w3.org/ns/activitystreams", + "https://princess.cat/schemas/litepub-0.1.jsonld", + { + "@language" : "und" + } + ], + "attachment" : [], + "capabilities" : { + "acceptsChatMessages" : true + }, + "discoverable" : false, + "endpoints" : { + "oauthAuthorizationEndpoint" : "https://princess.cat/oauth/authorize", + "oauthRegistrationEndpoint" : "https://princess.cat/api/v1/apps", + "oauthTokenEndpoint" : "https://princess.cat/oauth/token", + "sharedInbox" : "https://princess.cat/inbox", + "uploadMedia" : "https://princess.cat/api/ap/upload_media" + }, + "followers" : "https://princess.cat/users/mewmew/followers", + "following" : "https://princess.cat/users/mewmew/following", + "icon" : { + "type" : "Image", + "url" : "https://princess.cat/media/12794fb50e86911e65be97f69196814049dcb398a2f8b58b99bb6591576e648c.png?name=blobcatpresentpink.png" + }, + "id" : "https://princess.cat/users/mewmew", + "image" : { + "type" : "Image", + "url" : "https://princess.cat/media/05d8bf3953ab6028fc920494ffc643fbee9dcef40d7bdd06f107e19acbfbd7f9.png" + }, + "inbox" : "https://princess.cat/users/mewmew/inbox", + "manuallyApprovesFollowers" : true, + "name" : " ", + "outbox" : "https://princess.cat/users/mewmew/outbox", + "preferredUsername" : "mewmew", + "publicKey" : { + "id" : "https://princess.cat/users/mewmew#main-key", + "owner" : "https://princess.cat/users/mewmew", + "publicKeyPem" : "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAru7VpygVef4zrFwnj0Mh\nrbO/2z2EdKN3rERtNrT8zWsLXNLQ50lfpRPnGDrd+xq7Rva4EIu0d5KJJ9n4vtY0\nuxK3On9vA2oyjLlR9O0lI3XTrHJborG3P7IPXrmNUMFpHiFHNqHp5tugUrs1gUFq\n7tmOmM92IP4Wjk8qNHFcsfnUbaPTX7sNIhteQKdi5HrTb/6lrEIe4G/FlMKRqxo3\nRNHuv6SNFQuiUKvFzjzazvjkjvBSm+aFROgdHa2tKl88StpLr7xmuY8qNFCRT6W0\nLacRp6c8ah5f03Kd+xCBVhCKvKaF1K0ERnQTBiitUh85md+Mtx/CoDoLnmpnngR3\nvQIDAQAB\n-----END PUBLIC KEY-----\n\n" + }, + "summary" : "please reply to my posts as direct messages if you have many followers", + "tag" : [], + "type" : "Person", + "url" : "https://princess.cat/users/mewmew" +} diff --git a/test/fixtures/modules/runtime_module.ex b/test/fixtures/modules/runtime_module.ex index f11032b57..e348c499e 100644 --- a/test/fixtures/modules/runtime_module.ex +++ b/test/fixtures/modules/runtime_module.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule RuntimeModule do +defmodule Fixtures.Modules.RuntimeModule do @moduledoc """ This is a dummy module to test custom runtime modules. """ diff --git a/test/fixtures/nil_mention_entry.xml b/test/fixtures/nil_mention_entry.xml deleted file mode 100644 index e13024cb3..000000000 --- a/test/fixtures/nil_mention_entry.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - GNU social - https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom - atarifrosch timeline - Updates from atarifrosch on social.stopwatchingus-heidelberg.de! - https://social.stopwatchingus-heidelberg.de/avatar/18330-96-20150628163706.png - 2017-08-24T11:36:49+02:00 - - http://activitystrea.ms/schema/1.0/person - https://social.stopwatchingus-heidelberg.de/user/18330 - atarifrosch - Nerd, Pirat, Debian user, CAcert assurer, Geocacher, Freifunker. Autismus/Depression, agender. GnuPG Key-ID: 0xBCF81ADE - - - - - - atarifrosch - Atari-Frosch - Nerd, Pirat, Debian user, CAcert assurer, Geocacher, Freifunker. Autismus/Depression, agender. GnuPG Key-ID: 0xBCF81ADE - - Düsseldorf, NRW, Germany - - - homepage - https://www.atari-frosch.de/ - true - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978072:objectType=note - New note by atarifrosch - 2017-08-22 Bundesverfassungsgericht: Erfolgreiche Verfassungsbeschwerde gegen die Versagung vorläufiger Leistungen für Kosten der Unterkunft und Heizung – <a href="https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html" title="https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html" class="attachment" id="attachment-450768" rel="nofollow external">https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html</a> !<a href="http://quitter.se/group/2184/id" class="h-card group" title="HartzIV (hartziv)">hartziv</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-08-22T12:00:21+00:00 - 2017-08-22T12:00:21+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978072:objectType=thread:crc32=28a35f44 - - - - - - - - diff --git a/test/fixtures/ostatus_incoming_post.xml b/test/fixtures/ostatus_incoming_post.xml deleted file mode 100644 index 7967e1b32..000000000 --- a/test/fixtures/ostatus_incoming_post.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-04-29T18:25:38+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-29:noticeId=1967725:objectType=note - New note by lambadalambda - Will it blend? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T18:25:38+00:00 - 2017-04-29T18:25:38+00:00 - - tag:social.heldscal.la,2017-04-29:objectType=thread:nonce=3f3a9dd83acc4e35 - - - - - - diff --git a/test/fixtures/ostatus_incoming_post_tag.xml b/test/fixtures/ostatus_incoming_post_tag.xml deleted file mode 100644 index 0f99c4126..000000000 --- a/test/fixtures/ostatus_incoming_post_tag.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-04-29T18:25:38+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-29:noticeId=1967725:objectType=note - New note by lambadalambda - Will it blend? - - - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T18:25:38+00:00 - 2017-04-29T18:25:38+00:00 - - tag:social.heldscal.la,2017-04-29:objectType=thread:nonce=3f3a9dd83acc4e35 - - - - - - diff --git a/test/fixtures/ostatus_incoming_reply.xml b/test/fixtures/ostatus_incoming_reply.xml deleted file mode 100644 index 83a427a68..000000000 --- a/test/fixtures/ostatus_incoming_reply.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-04-30T09:30:32+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-04-30:noticeId=1978790:objectType=comment - New comment by lambadalambda - @<a href="https://gs.archae.me/user/4687" class="h-card u-url p-nickname mention" title="shpbot">shpbot</a> why not indeed. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-30T09:30:32+00:00 - 2017-04-30T09:30:32+00:00 - - - - https://gs.archae.me/conversation/327120 - - - - - - - diff --git a/test/fixtures/share-gs-local.xml b/test/fixtures/share-gs-local.xml deleted file mode 100644 index 9d52eab7b..000000000 --- a/test/fixtures/share-gs-local.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-03T08:05:41+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-03:noticeId=2028428:objectType=note - lambadalambda repeated a notice by lain - RT @<a href="https://pleroma.soykaf.com/users/lain" class="h-card u-url p-nickname mention" title="Lain Iwakura">lain</a> Added returning the entries as xml... let's see if the mastodon hammering stops now. - - http://activitystrea.ms/schema/1.0/share - 2017-05-03T08:05:41+00:00 - 2017-05-03T08:05:41+00:00 - - http://activitystrea.ms/schema/1.0/activity - LOCAL_ID - - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - http://activitystrea.ms/schema/1.0/post - 2017-05-03T08:04:44+00:00 - 2017-05-03T08:04:44+00:00 - - http://activitystrea.ms/schema/1.0/person - LOCAL_USER - lain - Test account - - - - - - lain - Lain Iwakura - Test account - - - - http://activitystrea.ms/schema/1.0/note - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - New note by lain - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - - - - https://pleroma.soykaf.com/contexts/ede39a2b-7cf3-4fa4-8ccd-cb97431bcc22 - - - https://pleroma.soykaf.com/users/lain/feed.atom - Lain Iwakura - - - https://social.heldscal.la/avatar/43188-96-20170429172422.jpeg - 2017-05-03T08:04:44+00:00 - - - - https://pleroma.soykaf.com/contexts/ede39a2b-7cf3-4fa4-8ccd-cb97431bcc22 - - - - - - diff --git a/test/fixtures/share-gs.xml b/test/fixtures/share-gs.xml deleted file mode 100644 index ab5e488bd..000000000 --- a/test/fixtures/share-gs.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-03T08:05:41+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-03:noticeId=2028428:objectType=note - lambadalambda repeated a notice by lain - RT @<a href="https://pleroma.soykaf.com/users/lain" class="h-card u-url p-nickname mention" title="Lain Iwakura">lain</a> Added returning the entries as xml... let's see if the mastodon hammering stops now. - - http://activitystrea.ms/schema/1.0/share - 2017-05-03T08:05:41+00:00 - 2017-05-03T08:05:41+00:00 - - http://activitystrea.ms/schema/1.0/activity - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - http://activitystrea.ms/schema/1.0/post - 2017-05-03T08:04:44+00:00 - 2017-05-03T08:04:44+00:00 - - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - Test account - - - - - - lain - Lain Iwakura - Test account - - - - http://activitystrea.ms/schema/1.0/note - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - New note by lain - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - - - - https://pleroma.soykaf.com/contexts/ede39a2b-7cf3-4fa4-8ccd-cb97431bcc22 - - - https://pleroma.soykaf.com/users/lain/feed.atom - Lain Iwakura - - - https://social.heldscal.la/avatar/43188-96-20170429172422.jpeg - 2017-05-03T08:04:44+00:00 - - - - https://pleroma.soykaf.com/contexts/ede39a2b-7cf3-4fa4-8ccd-cb97431bcc22 - - - - - - diff --git a/test/fixtures/share.xml b/test/fixtures/share.xml deleted file mode 100644 index e07b88680..000000000 --- a/test/fixtures/share.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status - 2017-05-03T08:21:09Z - 2017-05-03T08:21:09Z - lambadalambda shared a status by lain@pleroma.soykaf.com - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - 2017-05-03T08:04:44Z - 2017-05-03T08:05:52Z - New status by lain@pleroma.soykaf.com - - https://pleroma.soykaf.com/users/lain - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - lain@pleroma.soykaf.com - Test account - - - - lain - Lain Iwakura - Test account - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - public - - - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - public - - - diff --git a/test/fixtures/spoofed-object.json b/test/fixtures/spoofed-object.json new file mode 100644 index 000000000..91e34307d --- /dev/null +++ b/test/fixtures/spoofed-object.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://patch.cx/schemas/litepub-0.1.jsonld", + { + "@language": "und" + } + ], + "actor": "https://patch.cx/users/rin", + "attachment": [], + "attributedTo": "https://patch.cx/users/rin", + "cc": [ + "https://patch.cx/users/rin/followers" + ], + "content": "Oracle Corporation (NYSE: ORCL) today announced that it has signed a definitive merger agreement to acquire Pleroma AG (FRA: PLA), for $26.50 per share (approximately $10.3 billion). The transaction has been approved by the boards of directors of both companies and should close by early January.", + "context": "https://patch.cx/contexts/spoof", + "id": "https://patch.cx/objects/spoof", + "published": "2020-10-23T18:02:06.038856Z", + "sensitive": false, + "summary": "Oracle buys Pleroma", + "tag": [], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Note" +} diff --git a/test/fixtures/tesla_mock/7369654.atom b/test/fixtures/tesla_mock/7369654.atom deleted file mode 100644 index 74fd9ce6b..000000000 --- a/test/fixtures/tesla_mock/7369654.atom +++ /dev/null @@ -1,44 +0,0 @@ - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-22:noticeId=7369654:objectType=comment - New comment by shpuld - @<a href="https://testing.pleroma.lol/users/lain" class="h-card mention" title="Rael Electric Razor">lain</a> me far right - - - http://activitystrea.ms/schema/1.0/post - 2018-02-22T09:20:12+00:00 - 2018-02-22T09:20:12+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/5381 - shpuld - - - - - - shpuld - shp - - - - - - - tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4 - - - - https://shitposter.club/api/statuses/user_timeline/5381.atom - shp - - - - https://shitposter.club/avatar/5381-96-20171230093854.png - 2018-02-23T13:30:15+00:00 - - - - - diff --git a/test/fixtures/tesla_mock/admin@mastdon.example.org.json b/test/fixtures/tesla_mock/admin@mastdon.example.org.json index a911b979a..f961ccb36 100644 --- a/test/fixtures/tesla_mock/admin@mastdon.example.org.json +++ b/test/fixtures/tesla_mock/admin@mastdon.example.org.json @@ -1,20 +1,24 @@ { - "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", { - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "sensitive": "as:sensitive", - "movedTo": "as:movedTo", - "Hashtag": "as:Hashtag", - "ostatus": "http://ostatus.org#", - "atomUri": "ostatus:atomUri", - "inReplyToAtomUri": "ostatus:inReplyToAtomUri", - "conversation": "ostatus:conversation", - "toot": "http://joinmastodon.org/ns#", - "Emoji": "toot:Emoji", - "alsoKnownAs": { - "@id": "as:alsoKnownAs", - "@type": "@id" + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "sensitive": "as:sensitive", + "movedTo": "as:movedTo", + "Hashtag": "as:Hashtag", + "ostatus": "http://ostatus.org#", + "atomUri": "ostatus:atomUri", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "toot": "http://joinmastodon.org/ns#", + "Emoji": "toot:Emoji", + "alsoKnownAs": { + "@id": "as:alsoKnownAs", + "@type": "@id" + } } - }], + ], "id": "http://mastodon.example.org/users/admin", "type": "Person", "following": "http://mastodon.example.org/users/admin/following", @@ -23,6 +27,7 @@ "outbox": "http://mastodon.example.org/users/admin/outbox", "preferredUsername": "admin", "name": null, + "discoverable": "true", "summary": "\u003cp\u003e\u003c/p\u003e", "url": "http://mastodon.example.org/@admin", "manuallyApprovesFollowers": false, @@ -34,7 +39,8 @@ "owner": "http://mastodon.example.org/users/admin", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n" }, - "attachment": [{ + "attachment": [ + { "type": "PropertyValue", "name": "foo", "value": "bar" @@ -58,5 +64,7 @@ "mediaType": "image/png", "url": "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png" }, - "alsoKnownAs": ["http://example.org/users/foo"] -} + "alsoKnownAs": [ + "http://example.org/users/foo" + ] +} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/atarifrosch_feed.xml b/test/fixtures/tesla_mock/atarifrosch_feed.xml deleted file mode 100644 index e00df782e..000000000 --- a/test/fixtures/tesla_mock/atarifrosch_feed.xml +++ /dev/null @@ -1,473 +0,0 @@ - - - GNU social - https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom - atarifrosch-Zeitleiste - Aktualisierungen von atarifrosch auf social.stopwatchingus-heidelberg.de! - https://social.stopwatchingus-heidelberg.de/avatar/18330-96-20150628163706.png - 2017-08-24T12:06:55+02:00 - - http://activitystrea.ms/schema/1.0/person - https://social.stopwatchingus-heidelberg.de/user/18330 - atarifrosch - Nerd, Pirat, Debian user, CAcert assurer, Geocacher, Freifunker. Autismus/Depression, agender. GnuPG Key-ID: 0xBCF81ADE - - - - - - atarifrosch - Atari-Frosch - Nerd, Pirat, Debian user, CAcert assurer, Geocacher, Freifunker. Autismus/Depression, agender. GnuPG Key-ID: 0xBCF81ADE - - Düsseldorf, NRW, Germany - - - homepage - https://www.atari-frosch.de/ - true - - - - - - - - - - - - - - tag:social.stopwatchingus-heidelberg.de,2017-08-24:noticeId=978735:objectType=note - atarifrosch repeated a notice by hoergen - RT @<a href="https://social.hoergen.org/hoergen" class="h-card mention" title="hoergen">hoergen</a> Das falsche Bild der Tagesschau &quot;Auffallend &quot;erfolgreich&quot; - Andrea Nahles und Manuela Schwesig&quot; #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/geringverdiener" rel="tag">Geringverdiener</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/mindestlohn" rel="tag">Mindestlohn</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/mannxismus" rel="tag">mannxismus</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/erwerbsminderungsrente" rel="tag">Erwerbsminderungsrente</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/arbeitnehmerflexibilisierung" rel="tag">ArbeitnehmerFlexibilisierung</a></span> #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/altersarmut" rel="tag">AltersArmut</a></span> ..... <a href="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" title="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" class="attachment" id="attachment-450858" rel="nofollow external">http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html</a> - https://social.stopwatchingus-heidelberg.de/notice/978735 - http://activitystrea.ms/schema/1.0/share - 2017-08-24T09:18:25+00:00 - 2017-08-24T09:18:25+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:social.hoergen.org,2017-08-24:noticeId=222320:objectType=note - - Das falsche Bild der Tagesschau <br /> &quot;Auffallend &quot;erfolgreich&quot; - Andrea Nahles und Manuela Schwesig&quot; #<span class="tag"><a href="https://social.hoergen.org/tag/geringverdiener" rel="tag">Geringverdiener</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/mindestlohn" rel="tag">Mindestlohn</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/mannxismus" rel="tag">mannxismus</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/erwerbsminderungsrente" rel="tag">Erwerbsminderungsrente</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/arbeitnehmerflexibilisierung" rel="tag">ArbeitnehmerFlexibilisierung</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/altersarmut" rel="tag">AltersArmut</a></span> ..... <br /> <br /> <a href="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" title="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" rel="nofollow external noreferrer" class="attachment">http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html</a> - https://social.hoergen.org/notice/222320 - http://activitystrea.ms/schema/1.0/post - 2017-08-24T07:36:31+00:00 - 2017-08-24T07:36:31+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.hoergen.org/user/2 - hoergen - aka Andi Memyself #humanist #nerd Menschen liebhabender Misanthrop und auch sonst sehr vielseitig interessiert. - - - - - - hoergen - hoergen - aka Andi Memyself #humanist #nerd Menschen liebhabender Misanthrop und auch sonst sehr vielseitig interessiert. - - Berlin - - - homepage - https://hyperblog.de/hoergen/ - true - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.hoergen.org,2017-08-24:noticeId=222320:objectType=note - New note by hoergen - Das falsche Bild der Tagesschau <br /> &quot;Auffallend &quot;erfolgreich&quot; - Andrea Nahles und Manuela Schwesig&quot; #<span class="tag"><a href="https://social.hoergen.org/tag/geringverdiener" rel="tag">Geringverdiener</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/mindestlohn" rel="tag">Mindestlohn</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/mannxismus" rel="tag">mannxismus</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/erwerbsminderungsrente" rel="tag">Erwerbsminderungsrente</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/arbeitnehmerflexibilisierung" rel="tag">ArbeitnehmerFlexibilisierung</a></span> #<span class="tag"><a href="https://social.hoergen.org/tag/altersarmut" rel="tag">AltersArmut</a></span> ..... <br /> <br /> <a href="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" title="http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html" rel="nofollow external noreferrer" class="attachment">http://www.tagesschau.de/inland/btw17/bilanz-schwesig-nahles-101.html</a> - - - - - https://social.hoergen.org/conversation/98616 - - - - - - - - - https://social.hoergen.org/api/statuses/user_timeline/2.atom - hoergen - - - https://social.stopwatchingus-heidelberg.de/avatar/54316-original-20170824072526.jpeg - 2017-08-24T09:48:30+00:00 - - - - https://social.hoergen.org/conversation/98616 - - - - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.stopwatchingus-heidelberg.de,2017-08-24:noticeId=978734:objectType=comment - New comment by atarifrosch - Jo, die Anzahl der Hartz-IV-Sanktionen nennt sie genausowenig wie die Anzahl der Menschen, die von den Repressionsbehörden in Obdachlosigkeit und Suizid getrieben wurden. Das würde die Erfolgszahlen dann doch ein wenig trüben, nech? - - - http://activitystrea.ms/schema/1.0/post - 2017-08-24T09:18:13+00:00 - 2017-08-24T09:18:13+00:00 - - - - https://social.hoergen.org/conversation/98616 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-24:noticeId=978732:objectType=note - New note by atarifrosch - Moin-quak. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-24T09:09:39+00:00 - 2017-08-24T09:09:39+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-24:noticeId=978732:objectType=thread:crc32=2f92b7b6 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978594:objectType=note - New note by atarifrosch - n8-quak! - - - http://activitystrea.ms/schema/1.0/post - 2017-08-23T21:39:54+00:00 - 2017-08-23T21:39:54+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978594:objectType=thread:crc32=9bdb0ac9 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978503:objectType=note - New note by atarifrosch - 2017-08-16 Michal Špaček: Post a boarding pass on Facebook, get your account stolen – Post a boarding pass on Facebook, get your account stolen (gilt übrinx nicht nur für Facebook) - - - http://activitystrea.ms/schema/1.0/post - 2017-08-23T15:14:29+00:00 - 2017-08-23T15:14:29+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978503:objectType=thread:crc32=3de05c3a - - - - - - - tag:social.stopwatchingus-heidelberg.de,2017-08-23:fave:18330:activity:978458:2017-08-23T15:18:19+02:00 - Favorite - atarifrosch favorited something by einebiene: Haha, große Überraschung. <a href="http://www.sueddeutsche.de/wirtschaft/abgasaffaere-software-updates-fuer-dieselautos-helfen-kaum-1.3637636" title="http://www.sueddeutsche.de/wirtschaft/abgasaffaere-software-updates-fuer-dieselautos-helfen-kaum-1.3637636" rel="nofollow noreferrer" class="attachment">https://quitter.is/url/1122672</a><br /> Was ich an all diesen Artikeln schade finde, ist, daß immer nur auf den Umstieg von Auto zu anderem Auto gesprochen wird. Öffis werden nicht erwähnt, Carsharing nicht, radeln nicht, und in der Stadt wäre ne Vespa auch deutlich besser als ein SUV. - http://activitystrea.ms/schema/1.0/favorite - 2017-08-23T13:18:19+00:00 - 2017-08-23T13:18:19+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:quitter.is,2017-08-23:noticeId=4032910:objectType=note - New note by einebiene - Haha, große Überraschung. <a href="http://www.sueddeutsche.de/wirtschaft/abgasaffaere-software-updates-fuer-dieselautos-helfen-kaum-1.3637636" title="http://www.sueddeutsche.de/wirtschaft/abgasaffaere-software-updates-fuer-dieselautos-helfen-kaum-1.3637636" rel="nofollow noreferrer" class="attachment">https://quitter.is/url/1122672</a><br /> Was ich an all diesen Artikeln schade finde, ist, daß immer nur auf den Umstieg von Auto zu anderem Auto gesprochen wird. Öffis werden nicht erwähnt, Carsharing nicht, radeln nicht, und in der Stadt wäre ne Vespa auch deutlich besser als ein SUV. - - - - - - - https://quitter.is/conversation/2535246 - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978402:objectType=note - New note by atarifrosch - moin-quak - - - http://activitystrea.ms/schema/1.0/post - 2017-08-23T10:57:26+00:00 - 2017-08-23T10:57:26+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-23:noticeId=978402:objectType=thread:crc32=7050c397 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978164:objectType=note - New note by atarifrosch - n8-quak - - - http://activitystrea.ms/schema/1.0/post - 2017-08-22T19:54:30+00:00 - 2017-08-22T19:54:30+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978164:objectType=thread:crc32=b0a209c7 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978072:objectType=note - New note by atarifrosch - 2017-08-22 Bundesverfassungsgericht: Erfolgreiche Verfassungsbeschwerde gegen die Versagung vorläufiger Leistungen für Kosten der Unterkunft und Heizung – <a href="https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html" title="https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html" class="attachment" id="attachment-450768" rel="nofollow external">https://www.bundesverfassungsgericht.de/SharedDocs/Pressemitteilungen/DE/2017/bvg17-072.html</a> !<a href="http://quitter.se/group/2184/id" class="h-card group" title="HartzIV (hartziv)">hartziv</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-08-22T12:00:21+00:00 - 2017-08-22T12:00:21+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978072:objectType=thread:crc32=28a35f44 - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978042:objectType=note - New note by atarifrosch - moin-quak! - - - http://activitystrea.ms/schema/1.0/post - 2017-08-22T07:55:27+00:00 - 2017-08-22T07:55:27+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-22:noticeId=978042:objectType=thread:crc32=f070a9f7 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-21:noticeId=977914:objectType=note - New note by atarifrosch - So, morgen geht's weiter. n8-quak! - - - http://activitystrea.ms/schema/1.0/post - 2017-08-21T22:09:53+00:00 - 2017-08-21T22:09:53+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-21:noticeId=977914:objectType=thread:crc32=c0a9f7fa - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-21:noticeId=977710:objectType=note - New note by atarifrosch - moin-quak. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-21T08:58:26+00:00 - 2017-08-21T08:58:26+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-21:noticeId=977710:objectType=thread:crc32=60cfb466 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-20:noticeId=977526:objectType=note - New note by atarifrosch - Meine Augen meinen, für heute sei es genug. Nun denn. n8-quak. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-20T19:58:16+00:00 - 2017-08-20T19:58:16+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-20:noticeId=977526:objectType=thread:crc32=ce79634 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-20:noticeId=977369:objectType=note - New note by atarifrosch - [Blog] Im Netz aufgefischt #<span class="tag"><a href="https://social.stopwatchingus-heidelberg.de/tag/330" rel="tag">330</a></span> – <a href="https://blog.atari-frosch.de/2017/08/20/im-netz-aufgefischt-330/" title="https://blog.atari-frosch.de/2017/08/20/im-netz-aufgefischt-330/" class="attachment" id="attachment-450668" rel="nofollow external">https://blog.atari-frosch.de/2017/08/20/im-netz-aufgefischt-330/</a> (was ich diese Woche so gelesen habe) - - - http://activitystrea.ms/schema/1.0/post - 2017-08-20T09:14:07+00:00 - 2017-08-20T09:14:07+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-20:noticeId=977369:objectType=thread:crc32=2f800b86 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=977268:objectType=note - New note by atarifrosch - Fast ständig husten müssen ist echt anstrengend … naja, n8-quak. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-19T21:59:20+00:00 - 2017-08-19T21:59:20+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=977268:objectType=thread:crc32=deda767a - - - - - - - tag:social.stopwatchingus-heidelberg.de,2017-08-19:fave:18330:activity:977146:2017-08-19T21:39:26+02:00 - Favorite - atarifrosch favorited something by einebienezwo: Ich mach gerade Kompetenztraining.<br /> Ich trainiere die Kompetenz, eine halb aufgegessene Gummibärchentüte nicht ganz aufzuessen. - http://activitystrea.ms/schema/1.0/favorite - 2017-08-19T19:39:26+00:00 - 2017-08-19T19:39:26+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gnusocial.de,2017-08-19:noticeId=11011264:objectType=note - New note by einebienezwo - Ich mach gerade Kompetenztraining.<br /> Ich trainiere die Kompetenz, eine halb aufgegessene Gummibärchentüte nicht ganz aufzuessen. - - - - - - - https://gnusocial.de/conversation/9363945 - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=977242:objectType=comment - New comment by atarifrosch - Wir hatten hier schon Ordnungsdienst auf'm Radweg. Fotografisch dokumentiert (nicht von mir, Bekannter hatte es gesehen). Da hatte grad 'ne Pizzeria neu eröffnet … - - - http://activitystrea.ms/schema/1.0/post - 2017-08-19T19:38:53+00:00 - 2017-08-19T19:38:53+00:00 - - - - https://gnusocial.de/conversation/9363813 - - - - - - - - tag:social.stopwatchingus-heidelberg.de,2017-08-19:fave:18330:activity:977180:2017-08-19T21:37:36+02:00 - Favorite - atarifrosch favorited something by jcaktiv: BTW Hallo zusammen &lt;3, wo ich schon mal wieder hier bin - http://activitystrea.ms/schema/1.0/favorite - 2017-08-19T19:37:36+00:00 - 2017-08-19T19:37:36+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:quitter.se,2017-08-19:noticeId=17372467:objectType=note - New note by jcaktiv - BTW Hallo zusammen &lt;3, wo ich schon mal wieder hier bin - - - - - - - tag:quitter.se,2017-08-19:objectType=thread:nonce=46c1c433d88aaa9f - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=976985:objectType=comment - New comment by atarifrosch - Jo, oder einfach mal nachfragen, so als Realitätsabgleich. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-19T10:34:50+00:00 - 2017-08-19T10:34:50+00:00 - - - - https://gnusocial.de/conversation/9362516 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=976983:objectType=note - New note by atarifrosch - Schöne Alternative zu mit Werbung überladenen kommerziellen Anbietern: <a href="http://ifconfig.at/" title="http://ifconfig.at/" class="attachment" id="attachment-450636" rel="nofollow external">http://ifconfig.at/</a> – eigene IP, Hostname etc. abfragen, mit curl dann auch in Textform zur lokalen Weiterverarbeitung in Scripten etc. Leider (noch?) kein https. - - - http://activitystrea.ms/schema/1.0/post - 2017-08-19T10:33:04+00:00 - 2017-08-19T10:33:04+00:00 - - tag:social.stopwatchingus-heidelberg.de,2017-08-19:noticeId=976983:objectType=thread:crc32=4a3593c0 - - - - - - diff --git a/test/fixtures/tesla_mock/emelie.atom b/test/fixtures/tesla_mock/emelie.atom deleted file mode 100644 index ddaa1c6ca..000000000 --- a/test/fixtures/tesla_mock/emelie.atom +++ /dev/null @@ -1,306 +0,0 @@ - - - https://mastodon.social/users/emelie.atom - emelie 🎨 - 23 / #Sweden / #Artist / #Equestrian / #GameDev - -If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰 - 2019-02-04T20:22:19Z - https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png - - https://mastodon.social/users/emelie - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/emelie - emelie - emelie@mastodon.social - <p>23 / <a href="https://mastodon.social/tags/sweden" class="mention hashtag" rel="tag">#<span>Sweden</span></a> / <a href="https://mastodon.social/tags/artist" class="mention hashtag" rel="tag">#<span>Artist</span></a> / <a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag">#<span>Equestrian</span></a> / <a href="https://mastodon.social/tags/gamedev" class="mention hashtag" rel="tag">#<span>GameDev</span></a></p><p>If I ain&apos;t spending time with my pets, I&apos;m probably drawing. 🐴 🐱 🐰</p> - - - - emelie - emelie 🎨 - 23 / #Sweden / #Artist / #Equestrian / #GameDev - -If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰 - public - - - - - - - https://mastodon.social/users/emelie/statuses/101850331907006641 - 2019-04-01T09:58:50Z - 2019-04-01T09:58:50Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Me: I&apos;m going to make this vital change to my world building in the morning, no way I&apos;ll forget this, it&apos;s too big of a deal<br />Also me: forgets</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101849626603073336 - 2019-04-01T06:59:28Z - 2019-04-01T06:59:28Z - New status by emelie - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - - <p><span class="h-card"><a href="https://mastodon.social/@Fergant" class="u-url mention">@<span>Fergant</span></a></span> Dom är i stort sett religiös skrift vid det här laget 👏👏</p><p>har dock bara läst svenska översättningen, kanske är dags att jag läser dom på engelska</p> - - - public - - - - - - - https://mastodon.social/users/emelie/statuses/101849580030237068 - 2019-04-01T06:47:37Z - 2019-04-01T06:47:37Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>What&apos;s you people&apos;s favourite fantasy books? Give me some hot tips 🌞</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101849550599949363 - 2019-04-01T06:40:08Z - 2019-04-01T06:40:08Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Stick them legs out 💃 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p> - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101849191533152720 - 2019-04-01T05:08:49Z - 2019-04-01T05:08:49Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>long 🐱 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p> - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101849165031453009 - 2019-04-01T05:02:05Z - 2019-04-01T05:02:05Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p> - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101846512530748693 - 2019-03-31T17:47:31Z - 2019-03-31T17:47:31Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Hello look at this boy having a decent haircut for once <a href="https://mastodon.social/tags/mastohorses" class="mention hashtag" rel="tag">#<span>mastohorses</span></a> <a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag">#<span>equestrian</span></a></p> - - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101846181093805500 - 2019-03-31T16:23:14Z - 2019-03-31T16:23:14Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Sorry did I disturb the who-is-the-longest-cat competition ? <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p> - - - - public - - - - - - https://mastodon.social/users/emelie/statuses/101845897513133849 - 2019-03-31T15:11:07Z - 2019-03-31T15:11:07Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - more earthsea ramblings - <p>I&apos;m re-watching Tales from Earthsea for the first time since I read the books, and that Therru doesn&apos;t squash Cob like a spider, as Orm Embar did is a wasted opportunity tbh</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101841219051533307 - 2019-03-30T19:21:19Z - 2019-03-30T19:21:19Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>I gave my cats some mackerel and they ate it all in 0.3 seconds, and now they won&apos;t stop meowing for more, and I&apos;m tired plz shut up</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101839949762341381 - 2019-03-30T13:58:31Z - 2019-03-30T13:58:31Z - New status by emelie - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - - <p>yet I&apos;m confused about this american dude with a gun, like the heck r ya doin in mah ghibli</p> - - public - - - - - - - https://mastodon.social/users/emelie/statuses/101839928677863590 - 2019-03-30T13:53:09Z - 2019-03-30T13:53:09Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>2 hours into Ni no Kuni 2 and I&apos;ve already sold my soul to this game</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101836329521599438 - 2019-03-29T22:37:51Z - 2019-03-29T22:37:51Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>Pippi Longstocking the original one-punch /man</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101835905282948341 - 2019-03-29T20:49:57Z - 2019-03-29T20:49:57Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>I&apos;ve had so much wine I thought I had a 3rd brother</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101835878059204660 - 2019-03-29T20:43:02Z - 2019-03-29T20:43:02Z - New status by emelie - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - - <p>ååååhhh booi</p> - - public - - - - - - https://mastodon.social/users/emelie/statuses/101835848050598939 - 2019-03-29T20:35:24Z - 2019-03-29T20:35:24Z - New status by emelie - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - - <p><span class="h-card"><a href="https://thraeryn.net/@thraeryn" class="u-url mention">@<span>thraeryn</span></a></span> if I spent 1 hour and a half watching this monstrosity, I need to</p> - - - public - - - - - - - https://mastodon.social/users/emelie/statuses/101835823138262290 - 2019-03-29T20:29:04Z - 2019-03-29T20:29:04Z - New status by emelie - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - - medical, fluids mention - <p><span class="h-card"><a href="https://icosahedron.website/@Trev" class="u-url mention">@<span>Trev</span></a></span> *hugs* ✨</p> - - - public - - - - - - diff --git a/test/fixtures/tesla_mock/framatube.org-video.json b/test/fixtures/tesla_mock/framatube.org-video.json index 3d53f0c97..1fa529886 100644 --- a/test/fixtures/tesla_mock/framatube.org-video.json +++ b/test/fixtures/tesla_mock/framatube.org-video.json @@ -1 +1 @@ -{"type":"Video","id":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206","name":"Déframasoftisons Internet [Framasoft]","duration":"PT3622S","uuid":"6050732a-8a7a-43d4-a6cd-809525a1d206","tag":[{"type":"Hashtag","name":"déframasoftisons"},{"type":"Hashtag","name":"EPN23"},{"type":"Hashtag","name":"framaconf"},{"type":"Hashtag","name":"Framasoft"},{"type":"Hashtag","name":"pyg"}],"category":{"identifier":"15","name":"Science & Technology"},"views":122,"sensitive":false,"waitTranscoding":false,"state":1,"commentsEnabled":true,"downloadEnabled":true,"published":"2020-05-24T18:34:31.569Z","originallyPublishedAt":"2019-11-30T23:00:00.000Z","updated":"2020-07-05T09:01:01.720Z","mediaType":"text/markdown","content":"Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?\r\n\r\nTranscription par @april...","support":null,"subtitleLanguage":[],"icon":{"type":"Image","url":"https://framatube.org/static/thumbnails/6050732a-8a7a-43d4-a6cd-809525a1d206.jpg","mediaType":"image/jpeg","width":223,"height":122},"url":[{"type":"Link","mediaType":"text/html","href":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080,"size":1157359410,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309939","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent&xt=urn:btih:381c9429900552e23a4eb506318f1fa01e4d63a8&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480,"size":250095131,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309941","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent&xt=urn:btih:a181dcbb5368ab5c31cc9ff07634becb72c344ee&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360,"size":171357733,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309942","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent&xt=urn:btih:aedfa9479ea04a175eee0b0bd0bda64076308746&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720,"size":497100839,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309943","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent&xt=urn:btih:71971668f82a3b24ac71bc3a982848dd8dc5a5f5&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240,"size":113038439,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309944","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent&xt=urn:btih:c42aa6c95efb28d9f114ebd98537f7b00fa72246&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240},{"type":"Link","mediaType":"application/x-mpegURL","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/master.m3u8","tag":[{"type":"Infohash","name":"f7428214539626e062f300f2ca4cf9154575144e"},{"type":"Infohash","name":"46e236dffb1ea6b9123a5396cbe88e97dd94cc6c"},{"type":"Infohash","name":"11f1045830b5d786c788f2594d19f128764e7d87"},{"type":"Infohash","name":"4327ad3e0d84de100130a27e9ab6fe40c4284f0e"},{"type":"Infohash","name":"41e2eee8e7b23a63c23a77c40a46de11492a4831"},{"type":"Link","name":"sha256","mediaType":"application/json","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/segments-sha256.json"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080,"size":1156777472,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309940","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent&xt=urn:btih:0204d780ebfab0d5d9d3476a038e812ad792deeb&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480,"size":249562889,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309945","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent&xt=urn:btih:5d14f38ded29de629668fe1cfc61a75f4cce2628&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360,"size":170836415,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309946","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent&xt=urn:btih:30125488789080ad405ebcee6c214945f31b8f30&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720,"size":496533741,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309947","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent&xt=urn:btih:8ed1e8bccde709901c26e315fc8f53bfd26d1ba6&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240,"size":112529249,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309948","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent&xt=urn:btih:8b452bf4e70b9078d4e74ca8b5523cc9dc70d10a&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240}]}],"likes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/likes","dislikes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/dislikes","shares":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/announces","comments":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/comments","attributedTo":[{"type":"Person","id":"https://framatube.org/accounts/framasoft"},{"type":"Group","id":"https://framatube.org/video-channels/bf54d359-cfad-4935-9d45-9d6be93f63e8"}],"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://framatube.org/accounts/framasoft/followers"],"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"RsaSignature2017":"https://w3id.org/security#RsaSignature2017"},{"pt":"https://joinpeertube.org/ns#","sc":"http://schema.org#","Hashtag":"as:Hashtag","uuid":"sc:identifier","category":"sc:category","licence":"sc:license","subtitleLanguage":"sc:subtitleLanguage","sensitive":"as:sensitive","language":"sc:inLanguage","Infohash":"pt:Infohash","Playlist":"pt:Playlist","PlaylistElement":"pt:PlaylistElement","originallyPublishedAt":"sc:datePublished","views":{"@type":"sc:Number","@id":"pt:views"},"state":{"@type":"sc:Number","@id":"pt:state"},"size":{"@type":"sc:Number","@id":"pt:size"},"fps":{"@type":"sc:Number","@id":"pt:fps"},"startTimestamp":{"@type":"sc:Number","@id":"pt:startTimestamp"},"stopTimestamp":{"@type":"sc:Number","@id":"pt:stopTimestamp"},"position":{"@type":"sc:Number","@id":"pt:position"},"commentsEnabled":{"@type":"sc:Boolean","@id":"pt:commentsEnabled"},"downloadEnabled":{"@type":"sc:Boolean","@id":"pt:downloadEnabled"},"waitTranscoding":{"@type":"sc:Boolean","@id":"pt:waitTranscoding"},"support":{"@type":"sc:Text","@id":"pt:support"},"likes":{"@id":"as:likes","@type":"@id"},"dislikes":{"@id":"as:dislikes","@type":"@id"},"playlists":{"@id":"pt:playlists","@type":"@id"},"shares":{"@id":"as:shares","@type":"@id"},"comments":{"@id":"as:comments","@type":"@id"}}]} \ No newline at end of file +{"type":"Create","id":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/activity","actor":"https://framatube.org/accounts/framasoft","object":{"type":"Video","id":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206","name":"Déframasoftisons Internet [Framasoft]","duration":"PT3622S","uuid":"6050732a-8a7a-43d4-a6cd-809525a1d206","tag":[{"type":"Hashtag","name":"déframasoftisons"},{"type":"Hashtag","name":"EPN23"},{"type":"Hashtag","name":"framaconf"},{"type":"Hashtag","name":"Framasoft"},{"type":"Hashtag","name":"pyg"}],"category":{"identifier":"15","name":"Science & Technology"},"views":154,"sensitive":false,"waitTranscoding":false,"state":1,"commentsEnabled":true,"downloadEnabled":true,"published":"2020-05-24T18:34:31.569Z","originallyPublishedAt":"2019-11-30T23:00:00.000Z","updated":"2020-08-17T11:01:02.994Z","mediaType":"text/markdown","content":"Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?\r\n\r\nTranscription par @aprilorg ici : https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft","support":null,"subtitleLanguage":[],"icon":[{"type":"Image","url":"https://framatube.org/static/thumbnails/6050732a-8a7a-43d4-a6cd-809525a1d206.jpg","mediaType":"image/jpeg","width":223,"height":122},{"type":"Image","url":"https://framatube.org/static/previews/6050732a-8a7a-43d4-a6cd-809525a1d206.jpg","mediaType":"image/jpeg","width":850,"height":480}],"url":[{"type":"Link","mediaType":"text/html","href":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080,"size":1157359410,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309939","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.torrent&xt=urn:btih:381c9429900552e23a4eb506318f1fa01e4d63a8&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720,"size":497100839,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309943","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.torrent&xt=urn:btih:71971668f82a3b24ac71bc3a982848dd8dc5a5f5&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480,"size":250095131,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309941","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.torrent&xt=urn:btih:a181dcbb5368ab5c31cc9ff07634becb72c344ee&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360,"size":171357733,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309942","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.torrent&xt=urn:btih:aedfa9479ea04a175eee0b0bd0bda64076308746&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4&ws=https%3A%2F%2Fpeertube.live%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240,"size":113038439,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309944","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.torrent&xt=urn:btih:c42aa6c95efb28d9f114ebd98537f7b00fa72246&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fwebseed%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Fpeertube.iselfhost.com%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4&ws=https%3A%2F%2Ftube.privacytools.io%2Fstatic%2Fredundancy%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240.mp4","height":240},{"type":"Link","mediaType":"application/x-mpegURL","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/master.m3u8","tag":[{"type":"Infohash","name":"f7428214539626e062f300f2ca4cf9154575144e"},{"type":"Infohash","name":"46e236dffb1ea6b9123a5396cbe88e97dd94cc6c"},{"type":"Infohash","name":"11f1045830b5d786c788f2594d19f128764e7d87"},{"type":"Infohash","name":"4327ad3e0d84de100130a27e9ab6fe40c4284f0e"},{"type":"Infohash","name":"41e2eee8e7b23a63c23a77c40a46de11492a4831"},{"type":"Link","name":"sha256","mediaType":"application/json","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/segments-sha256.json"},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080,"size":1156777472,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309940","height":1080,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent","height":1080},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-hls.torrent&xt=urn:btih:0204d780ebfab0d5d9d3476a038e812ad792deeb&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-1080-fragmented.mp4","height":1080},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720,"size":496533741,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309947","height":720,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent","height":720},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-hls.torrent&xt=urn:btih:8ed1e8bccde709901c26e315fc8f53bfd26d1ba6&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-720-fragmented.mp4","height":720},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480,"size":249562889,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309945","height":480,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent","height":480},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-hls.torrent&xt=urn:btih:5d14f38ded29de629668fe1cfc61a75f4cce2628&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-480-fragmented.mp4","height":480},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360,"size":170836415,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309946","height":360,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent","height":360},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-hls.torrent&xt=urn:btih:30125488789080ad405ebcee6c214945f31b8f30&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-360-fragmented.mp4","height":360},{"type":"Link","mediaType":"video/mp4","href":"https://framatube.org/static/streaming-playlists/hls/6050732a-8a7a-43d4-a6cd-809525a1d206/6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240,"size":112529249,"fps":25},{"type":"Link","rel":["metadata","video/mp4"],"mediaType":"application/json","href":"https://framatube.org/api/v1/videos/6050732a-8a7a-43d4-a6cd-809525a1d206/metadata/1309948","height":240,"fps":25},{"type":"Link","mediaType":"application/x-bittorrent","href":"https://framatube.org/static/torrents/6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent","height":240},{"type":"Link","mediaType":"application/x-bittorrent;x-scheme-handler/magnet","href":"magnet:?xs=https%3A%2F%2Fframatube.org%2Fstatic%2Ftorrents%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-hls.torrent&xt=urn:btih:8b452bf4e70b9078d4e74ca8b5523cc9dc70d10a&dn=D%C3%A9framasoftisons+Internet+%5BFramasoft%5D&tr=wss%3A%2F%2Fframatube.org%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fframatube.org%2Ftracker%2Fannounce&ws=https%3A%2F%2Fframatube.org%2Fstatic%2Fstreaming-playlists%2Fhls%2F6050732a-8a7a-43d4-a6cd-809525a1d206%2F6050732a-8a7a-43d4-a6cd-809525a1d206-240-fragmented.mp4","height":240}]}],"likes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/likes","dislikes":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/dislikes","shares":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/announces","comments":"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206/comments","attributedTo":[{"type":"Person","id":"https://framatube.org/accounts/framasoft"},{"type":"Group","id":"https://framatube.org/video-channels/bf54d359-cfad-4935-9d45-9d6be93f63e8"}],"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://framatube.org/accounts/framasoft/followers"]},"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://framatube.org/accounts/framasoft/followers"],"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"RsaSignature2017":"https://w3id.org/security#RsaSignature2017"},{"pt":"https://joinpeertube.org/ns#","sc":"http://schema.org#","Hashtag":"as:Hashtag","uuid":"sc:identifier","category":"sc:category","licence":"sc:license","subtitleLanguage":"sc:subtitleLanguage","sensitive":"as:sensitive","language":"sc:inLanguage","Infohash":"pt:Infohash","Playlist":"pt:Playlist","PlaylistElement":"pt:PlaylistElement","originallyPublishedAt":"sc:datePublished","views":{"@type":"sc:Number","@id":"pt:views"},"state":{"@type":"sc:Number","@id":"pt:state"},"size":{"@type":"sc:Number","@id":"pt:size"},"fps":{"@type":"sc:Number","@id":"pt:fps"},"startTimestamp":{"@type":"sc:Number","@id":"pt:startTimestamp"},"stopTimestamp":{"@type":"sc:Number","@id":"pt:stopTimestamp"},"position":{"@type":"sc:Number","@id":"pt:position"},"commentsEnabled":{"@type":"sc:Boolean","@id":"pt:commentsEnabled"},"downloadEnabled":{"@type":"sc:Boolean","@id":"pt:downloadEnabled"},"waitTranscoding":{"@type":"sc:Boolean","@id":"pt:waitTranscoding"},"support":{"@type":"sc:Text","@id":"pt:support"},"likes":{"@id":"as:likes","@type":"@id"},"dislikes":{"@id":"as:dislikes","@type":"@id"},"playlists":{"@id":"pt:playlists","@type":"@id"},"shares":{"@id":"as:shares","@type":"@id"},"comments":{"@id":"as:comments","@type":"@id"}}]} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml b/test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml deleted file mode 100644 index 490467708..000000000 --- a/test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml +++ /dev/null @@ -1,460 +0,0 @@ - - - GNU social - http://gs.example.org/index.php/api/statuses/user_timeline/1.atom - lambda timeline - Updates from lambda on gs.example.org! - http://gs.example.org/theme/neo-gnu/default-avatar-profile.png - 2017-05-05T12:09:57+00:00 - - http://activitystrea.ms/schema/1.0/person - http://gs.example.org:4040/index.php/user/1 - lambda - - - - - lambda - lambda - - - - - - - - - - - - - tag:gs.example.org,2017-05-04:noticeId=84:objectType=note - lambda repeated a notice by lambda2 - RT @<a href="http://gs.example.org/index.php/user/7" class="h-card mention">lambda2</a> Hello! - - http://activitystrea.ms/schema/1.0/share - 2017-05-04T16:38:50+00:00 - 2017-05-04T16:38:50+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.example.org,2017-05-01:noticeId=67:objectType=note - - Hello! - - http://activitystrea.ms/schema/1.0/post - 2017-05-01T08:41:04+00:00 - 2017-05-01T08:41:04+00:00 - - http://activitystrea.ms/schema/1.0/person - http://gs.example.org/index.php/user/7 - lambda2 - - - - - - lambda2 - lambda2 - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-05-01:noticeId=67:objectType=note - New note by lambda2 - Hello! - - - - - tag:gs.example.org,2017-05-01:objectType=thread:nonce=cffa792cb95fe417 - - - http://gs.example.org/index.php/api/statuses/user_timeline/7.atom - lambda2 - - - - http://gs.example.org/avatar/7-96-20170501084054.png - 2017-05-01T16:33:10+00:00 - - - - - - tag:gs.example.org,2017-05-01:objectType=thread:nonce=cffa792cb95fe417 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-30:noticeId=63:objectType=note - New note by lambda - what now? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-30T10:09:57+00:00 - 2017-04-30T10:09:57+00:00 - - - - tag:gs.example.org,2017-04-30:objectType=thread:nonce=1bbb60991ae9874b - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-30:noticeId=61:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain5" class="h-card mention">lain5</a> Hello! - - - http://activitystrea.ms/schema/1.0/post - 2017-04-30T10:07:26+00:00 - 2017-04-30T10:07:26+00:00 - - tag:gs.example.org,2017-04-30:objectType=thread:nonce=1bbb60991ae9874b - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-29:noticeId=59:objectType=note - New note by lambda - ey - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T17:04:59+00:00 - 2017-04-29T17:04:59+00:00 - - tag:gs.example.org,2017-04-29:objectType=thread:nonce=4cc42c2c61a0f4bd - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-29:noticeId=58:objectType=note - New note by lambda - Another one. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T17:02:47+00:00 - 2017-04-29T17:02:47+00:00 - - tag:gs.example.org,2017-04-29:objectType=thread:nonce=53e9b8f1d6d38d13 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-29:noticeId=57:objectType=note - New note by lambda - Let's see if this comes over. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T17:01:39+00:00 - 2017-04-29T17:01:39+00:00 - - tag:gs.example.org,2017-04-29:objectType=thread:nonce=238a7bd3ffc7c9cc - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org,2017-04-29:noticeId=56:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain5" class="h-card mention">lain5</a> Hey! - - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T16:38:13+00:00 - 2017-04-29T16:38:13+00:00 - - tag:gs.example.org,2017-04-29:objectType=thread:nonce=2629d3a398171b0f - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note - New note by lambda - hey. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:16:13+00:00 - 2017-04-25T18:16:13+00:00 - - - - http://pleroma.example.org:4000/contexts/8f6f45d4-8e4d-4e1a-a2de-09f27367d2d0 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=53:objectType=note - New note by lambda - and this? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:14:34+00:00 - 2017-04-25T18:14:34+00:00 - - - - http://pleroma.example.org:4000/contexts/24779b0e-91ad-487e-81bd-6cf5bb437b09 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=52:objectType=note - New note by lambda - yeah it does :) - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:13:31+00:00 - 2017-04-25T18:13:31+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=e0dc24b1a93ab6b3 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=50:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain5" class="h-card mention">lain5</a> Let's try with one that originates here! - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:10:28+00:00 - 2017-04-25T18:10:28+00:00 - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=e0dc24b1a93ab6b3 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=48:objectType=note - New note by lambda - works? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:08:44+00:00 - 2017-04-25T18:08:44+00:00 - - - - http://pleroma.example.org:4000/contexts/24779b0e-91ad-487e-81bd-6cf5bb437b09 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=46:objectType=note - New note by lambda - Let's send you an answer. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:05:31+00:00 - 2017-04-25T18:05:31+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=73c7bcf6658f7ce3 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=44:objectType=note - New note by lambda - Hey. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T18:01:09+00:00 - 2017-04-25T18:01:09+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=6e7c8fc2823380b4 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=43:objectType=note - New note by lambda - What's coming to you? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:58:41+00:00 - 2017-04-25T17:58:41+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=6e7c8fc2823380b4 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=42:objectType=note - New note by lambda - Now this is podracing. - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:57:40+00:00 - 2017-04-25T17:57:40+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=6e7c8fc2823380b4 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=39:objectType=note - New note by lambda - Sure looks like it! - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:48:27+00:00 - 2017-04-25T17:48:27+00:00 - - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=4c6114a75bb4cea5 - - - - - - - - tag:gs.example.org:4040,2017-04-25:subscription:1:person:6:2017-04-25T17:47:47+00:00 - lambda (lambda)'s status on Tuesday, 25-Apr-2017 17:47:47 UTC - <a href="http://gs.example.org:4040/index.php/lambda">lambda</a> started following <a href="http://pleroma.example.org:4000/users/lain5">l</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-25T17:47:47+00:00 - 2017-04-25T17:47:47+00:00 - - http://activitystrea.ms/schema/1.0/person - http://pleroma.example.org:4000/users/lain5 - l - lambadalambda - - - - - - lain5 - l - lambadalambda - - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=119acad17515314c - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=36:objectType=note - New note by lambda - @<a href="http://pleroma.example.org:4000/users/lain5" class="h-card mention">lain5</a> Hey, how are you? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:46:22+00:00 - 2017-04-25T17:46:22+00:00 - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=9c5ec19a18191372 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.example.org:4040,2017-04-25:noticeId=35:objectType=note - New note by lambda - @lain5@pleroma.example.org does this not work? - - - http://activitystrea.ms/schema/1.0/post - 2017-04-25T17:42:31+00:00 - 2017-04-25T17:42:31+00:00 - - tag:gs.example.org:4040,2017-04-25:objectType=thread:nonce=fc841d7f52caa363 - - - - - - diff --git a/test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom b/test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom deleted file mode 100644 index b5f3d923b..000000000 --- a/test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom +++ /dev/null @@ -1,342 +0,0 @@ - - - https://mamot.fr/users/Skruyb.atom - The 7th Son - Fr and En. -Posts will disappear on a regular basis. - 2017-04-28T13:54:23Z - https://mamot.fr/system/accounts/avatars/000/026/213/original/d95dbcfc76f77f4c.jpg?1493230984 - - https://mamot.fr/users/Skruyb - http://activitystrea.ms/schema/1.0/person - https://mamot.fr/users/Skruyb - Skruyb - Skruyb@mamot.fr - <p>Fr and En.<br />Posts will disappear on a regular basis.</p> - - - - Skruyb - The 7th Son - Fr and En. -Posts will disappear on a regular basis. - public - - - - - - - - tag:mamot.fr,2017-05-10:objectId=1299665:objectType=Status - 2017-05-10T20:06:59Z - 2017-05-10T20:06:59Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pouets.ovh/@noName" class="u-url mention">@<span>noName</span></a></span></p><p>Pour comparer faut avoir tester... Ô wait!!! 😁</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1299185:objectType=Status - 2017-05-10T19:52:14Z - 2017-05-10T19:52:14Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://witches.town/@Dhveszak" class="u-url mention">@<span>Dhveszak</span></a></span></p><p>Toi!! Tu vises le ministère de la propagande avoue!!!!!!!</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1299019:objectType=Status - 2017-05-10T19:47:19Z - 2017-05-10T19:47:19Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Facebook s&apos;attaque aux sites internet &quot;trompeurs&quot;</p><p><a href="http://u.afp.com/4W4z" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">u.afp.com/4W4z</span><span class="invisible"></span></a></p><p>J&apos;attends de voir que Facebook s&apos;attaque à lui même... rien qu&apos;à lire leurs conditions générales d&apos;utilisation, le respect de la vie privée...</p><p>Charité bien ordonnée... Parfois l&apos;égoïsme aurait du bon.</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1298889:objectType=Status - 2017-05-10T19:43:18Z - 2017-05-10T19:43:18Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://octodon.social/@Balise" class="u-url mention">@<span>Balise</span></a></span></p><p>Fait comme moi, annonce que tu fais dans le flou artistique et que seuls des esprits éclairés pourront en percevoir la beauté et apprécier. Globalement après ça, tout le monde trouve les photos cool :-p</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1298728:objectType=Status - 2017-05-10T19:38:39Z - 2017-05-10T19:38:39Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@applecandy" class="u-url mention">@<span>applecandy</span></a></span></p><p>Lucky you!!!</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1298431:objectType=Status - 2017-05-10T19:26:32Z - 2017-05-10T19:26:32Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Est-ce que je suis le seul qui lorsqu&apos;il commence à compter les arbres sur le bord de la route n&apos;arrive pas à s&apos;arrêter de compter?</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1298224:objectType=Status - 2017-05-10T19:18:17Z - 2017-05-10T19:18:17Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Ca y est j&apos;ai une nouvelle passion. Mettre les bouchons qui trainent par terre dans le bons sens avec mon pied 🙌</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1297450:objectType=Status - 2017-05-10T18:53:37Z - 2017-05-10T18:53:37Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Ok. On est capable d&apos;envoyer des mecs dans l&apos;espace, avoir des voitures autonomes, des trucs intelligents de partout mais pas tous les bâtiments accessibles aux personnes à mobilité réduite, les émissions sur le services publics avec une personne faisant la traduction pour les sourds et malentendants de manière systématique...</p><p>J&apos;ai du louper un truc dans l&apos;ordre des priorités Oo</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1297292:objectType=Status - 2017-05-10T18:48:17Z - 2017-05-10T18:48:17Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>J&apos;ai comme envie de faire un truc mais je ne sais pas quoi mais pourtant c&apos;est comme si je ressentais l&apos;idée dans ma tête mais c&apos;est pas clair...</p><p>Fuck!!! J&apos;vais aller draguer Josiane à la compta ça va me changer les idées!!!</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1296598:objectType=Status - 2017-05-10T18:25:11Z - 2017-05-10T18:25:11Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Smeablog" class="u-url mention">@<span>Smeablog</span></a></span></p><p>Pas faux MDR!!!!</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1296571:objectType=Status - 2017-05-10T18:24:13Z - 2017-05-10T18:24:13Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Smeablog" class="u-url mention">@<span>Smeablog</span></a></span></p><p>Ca ne change pas la finalité malheureusement, ça ne m&apos;ouvre pas ce à quoi je veux accéder 😭</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1296475:objectType=Status - 2017-05-10T18:20:50Z - 2017-05-10T18:20:50Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Arrrgghhhhhhh!!!!</p><p>Quand t&apos;es sur le point de cliquer sur un lien dans le fil public global et que BOOM ça se met à jour... J&apos;ose même pas imaginer combien j&apos;ai ouvert de pages web sans le vouloir!!!</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1296426:objectType=Status - 2017-05-10T18:19:17Z - 2017-05-10T18:19:17Z - Skruyb shared a status by Isaluini@mastodon.social - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:mastodon.social,2017-05-10:objectId=5587049:objectType=Status - 2017-05-10T18:18:59Z - 2017-05-10T18:19:00Z - New status by Isaluini@mastodon.social - - https://mastodon.social/users/Isaluini - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/Isaluini - Isaluini - Isaluini@mastodon.social - <p>Adicciones: Escribir, diseñar, cine, café, humor negro, música y dibujar. | Jedi. Bueno, no. Algún día (?) | Gratitude.</p> - - - - Isaluini - Isa - Adicciones: Escribir, diseñar, cine, café, humor negro, música y dibujar. | Jedi. Bueno, no. Algún día (?) | Gratitude. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>♫ <br><a href="https://www.youtube.com/watch?v=pT68FS3YbQ4"><span class="invisible">https://www.</span><span class="ellipsis">youtube.com/watch?v=pT68FS3YbQ</span><span class="invisible">4</span></a></p> - - public - - - <p>♫ <br><a href="https://www.youtube.com/watch?v=pT68FS3YbQ4"><span class="invisible">https://www.</span><span class="ellipsis">youtube.com/watch?v=pT68FS3YbQ</span><span class="invisible">4</span></a></p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1295893:objectType=Status - 2017-05-10T18:01:51Z - 2017-05-10T18:01:51Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Chat2Gouttieres" class="u-url mention">@<span>Chat2Gouttieres</span></a></span></p><p>Ah bah après faut savoir mettre à profit ce savoir ^^</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1295815:objectType=Status - 2017-05-10T18:00:02Z - 2017-05-10T18:00:02Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Chat2Gouttieres" class="u-url mention">@<span>Chat2Gouttieres</span></a></span></p><p>Exactement. On a les jeux mais pas le pain encore.</p><p>Finalement on a rien inventé :-p</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1295778:objectType=Status - 2017-05-10T17:58:52Z - 2017-05-10T17:58:52Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@Chat2Gouttieres" class="u-url mention">@<span>Chat2Gouttieres</span></a></span></p><p>C&apos;est ça visiblement dans notre société dite moderne... &quot;Créer l&apos;illusion que&quot; Oo.</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1294943:objectType=Status - 2017-05-10T17:31:44Z - 2017-05-10T17:31:44Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - Hey. - <p><span class="h-card"><a href="https://mastodon.social/@lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span></p><p>Hey!!!</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1294914:objectType=Status - 2017-05-10T17:30:40Z - 2017-05-10T17:30:40Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mamot.fr/@EloClemTiti" class="u-url mention">@<span>EloClemTiti</span></a></span></p><p>J&apos;ai souvent cette impression en effet 😂</p> - - - public - - - - - - tag:mamot.fr,2017-05-10:objectId=1294148:objectType=Status - 2017-05-10T17:02:01Z - 2017-05-10T17:02:01Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Les gars, les boss veulent voir de l&apos;avancement!! Une idée?</p><p>On fait comme d&apos;habitude. On divise nos tâches en 25.000 tâches unitaires, on fout du vert au maximum et on crée l&apos;illusion que ça a bien avancé!</p><p>Deal!!</p><p>Bob, tu choisis quel vert on utilise<br />Alice, t&apos;es en charge de la typo<br />Moi, je m&apos;occupe qu&apos;on prend bien le dernier template ppt fournit par la comm interne.</p><p>Des winners qu&apos;on est!!!! Des WI-NNERS!!!</p> - - public - - - - - tag:mamot.fr,2017-05-10:objectId=1293995:objectType=Status - 2017-05-10T16:57:53Z - 2017-05-10T16:57:53Z - New status by Skruyb - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://mastodon.social/@SauceHair" class="u-url mention">@<span>SauceHair</span></a></span></p><p>Cool!!</p><p>Bon courage.</p> - - - public - - - - - diff --git a/test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom b/test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom deleted file mode 100644 index 4d732b109..000000000 --- a/test/fixtures/tesla_mock/https___mastodon.social_users_lambadalambda.atom +++ /dev/null @@ -1,464 +0,0 @@ - - - https://mastodon.social/users/lambadalambda.atom - Critical Value - - 2017-04-16T21:47:25Z - https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif - - https://mastodon.social/users/lambadalambda - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/lambadalambda - lambadalambda - lambadalambda@mastodon.social - - - - lambadalambda - Critical Value - public - - - - - - - - tag:mastodon.social,2017-05-04:objectId=4991300:objectType=Status - 2017-05-04T14:10:30Z - 2017-05-04T14:10:30Z - Delete - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/delete - - - - - tag:mastodon.social,2017-05-04:objectId=4980289:objectType=Status - 2017-05-04T07:43:23Z - 2017-05-04T07:43:23Z - Delete - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/delete - - - - - tag:mastodon.social,2017-05-03:objectId=4952899:objectType=Status - 2017-05-03T17:26:43Z - 2017-05-03T17:26:43Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> OK!!</p> - - - public - - - - - - tag:mastodon.social,2017-05-03:objectId=4952810:objectType=Status - 2017-05-03T17:24:34Z - 2017-05-03T17:24:34Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> yeah :)</p> - - - public - - - - - - tag:mastodon.social,2017-05-03:objectId=4950388:objectType=Status - 2017-05-03T16:22:00Z - 2017-05-03T16:22:00Z - lambadalambda shared a status by lambadalambda@social.heldscal.la - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.heldscal.la,2017-05-03:noticeId=2030733:objectType=note - 2017-05-03T12:29:20Z - 2017-05-03T12:29:31Z - New status by lambadalambda@social.heldscal.la - - https://social.heldscal.la/user/23211 - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - lambadalambda@social.heldscal.la - Call me Deacon Blues. - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Time for work. <a href="https://social.heldscal.la/file/953c117a1e7e4c763755d2ac29cf1aae08e025599f4a4cc11ddff4082c07f969.jpg">https://social.heldscal.la/attachment/120552</a> - - - public - - - Time for work. <a href="https://social.heldscal.la/file/953c117a1e7e4c763755d2ac29cf1aae08e025599f4a4cc11ddff4082c07f969.jpg">https://social.heldscal.la/attachment/120552</a> - - public - - - - - tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status - 2017-05-03T08:21:09Z - 2017-05-03T08:21:09Z - lambadalambda shared a status by lain@pleroma.soykaf.com - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - https://pleroma.soykaf.com/objects/4c1bda26-902e-4525-9fcd-b9fd44925193 - 2017-05-03T08:04:44Z - 2017-05-03T08:05:52Z - New status by lain@pleroma.soykaf.com - - https://pleroma.soykaf.com/users/lain - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - lain@pleroma.soykaf.com - Test account - - - - lain - Lain Iwakura - Test account - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - public - - - Added returning the entries as xml... let's see if the mastodon hammering stops now. - - public - - - - - tag:mastodon.social,2017-05-02:objectId=4905499:objectType=Status - 2017-05-02T19:34:21Z - 2017-05-02T19:34:21Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> yay!</p> - - - public - - - - - - tag:mastodon.social,2017-05-02:objectId=4905442:objectType=Status - 2017-05-02T19:33:33Z - 2017-05-02T19:33:33Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> so?</p> - - - public - - - - - - tag:mastodon.social,2017-05-02:objectId=4901603:objectType=Status - 2017-05-02T18:33:06Z - 2017-05-02T18:33:06Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> hey</p> - - - public - - - - - - tag:mastodon.social,2017-05-01:objectId=4836720:objectType=Status - 2017-05-01T18:52:16Z - 2017-05-01T18:52:16Z - lambadalambda shared a status by lain@pleroma.soykaf.com - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - https://pleroma.soykaf.com/objects/7b41bb51-9aba-436a-82d9-dd3f5aca98c9 - 2017-05-01T18:50:54Z - 2017-05-01T18:50:57Z - New status by lain@pleroma.soykaf.com - - https://pleroma.soykaf.com/users/lain - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - lain@pleroma.soykaf.com - Test account - - - - lain - Lain Iwakura - Test account - public - - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <a href="https://mastodon.social/users/lambadalambda">@lambadalambda@mastodon.social</a> you're an all-star. - - - public - - - - <a href="https://mastodon.social/users/lambadalambda">@lambadalambda@mastodon.social</a> you're an all-star. - - public - - - - - tag:mastodon.social,2017-05-01:objectId=4836142:objectType=Status - 2017-05-01T18:38:47Z - 2017-05-01T18:38:47Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey now!</p> - - - public - - - - - - tag:mastodon.social,2017-05-01:objectId=4836055:objectType=Status - 2017-05-01T18:37:04Z - 2017-05-01T18:37:04Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> hello</p> - - - public - - - - - - tag:mastodon.social,2017-05-01:objectId=4834850:objectType=Status - 2017-05-01T18:10:43Z - 2017-05-01T18:10:43Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey!</p> - - - public - - - - - tag:mastodon.social,2017-04-29:objectId=4694455:objectType=Status - 2017-04-29T18:39:12Z - 2017-04-29T18:39:12Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>@lain@pleroma.soykaf.com What&apos;s up?</p> - - public - - - - - tag:mastodon.social,2017-04-29:objectId=4694384:objectType=Status - 2017-04-29T18:37:32Z - 2017-04-29T18:37:32Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://social.heldscal.la/lain" class="u-url mention">@<span>lain</span></a></span> Hey.</p> - - - public - - - - - tag:mastodon.social,2017-04-07:objectId=1874242:objectType=Status - 2017-04-07T11:02:56Z - 2017-04-07T11:02:56Z - lambadalambda shared a status by 0xroy@social.wxcafe.net - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.wxcafe.net,2017-04-07:objectId=72554:objectType=Status - 2017-04-07T11:01:59Z - 2017-04-07T11:02:00Z - New status by 0xroy@social.wxcafe.net - - https://social.wxcafe.net/users/0xroy - http://activitystrea.ms/schema/1.0/person - https://social.wxcafe.net/users/0xroy - 0xroy - 0xroy@social.wxcafe.net - ta caution weeb | discussions privées : <a href="https://%F0%9F%92%8C.0xroy.me"><span class="invisible">https://</span><span class="">💌.0xroy.me</span><span class="invisible"></span></a> - - - - 0xroy - 「R O Y 🍵 B O S」 - ta caution weeb | discussions privées : https://💌.0xroy.me - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>someone pls eli5 matrix (protocol) and riot</p> - - public - - - <p>someone pls eli5 matrix (protocol) and riot</p> - - public - - - - - tag:mastodon.social,2017-04-06:objectId=1768247:objectType=Status - 2017-04-06T11:10:19Z - 2017-04-06T11:10:19Z - lambadalambda shared a status by areyoutoo@mastodon.xyz - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:mastodon.xyz,2017-04-05:objectId=133327:objectType=Status - 2017-04-05T17:36:41Z - 2017-04-05T18:12:14Z - New status by areyoutoo@mastodon.xyz - - https://mastodon.xyz/users/areyoutoo - http://activitystrea.ms/schema/1.0/person - https://mastodon.xyz/users/areyoutoo - areyoutoo - areyoutoo@mastodon.xyz - devops | retired gamedev | always boost puppy pics - - - - areyoutoo - Raw Butter - devops | retired gamedev | always boost puppy pics - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>Some UX thoughts for <a href="https://mastodon.xyz/tags/mastodev">#<span>mastodev</span></a>:</p><p>- Would be nice if I could work on multiple draft toots? Clicking to reply to someone seems to erase any draft I had been working on.</p><p>- Kinda risky to click on the Federated Timeline if it loads new toots and scrolls 10ms before I click on something.</p><p>I probably don't know enough web frontend to help, but it might be fun to try.</p> - - - public - - - <p>Some UX thoughts for <a href="https://mastodon.xyz/tags/mastodev">#<span>mastodev</span></a>:</p><p>- Would be nice if I could work on multiple draft toots? Clicking to reply to someone seems to erase any draft I had been working on.</p><p>- Kinda risky to click on the Federated Timeline if it loads new toots and scrolls 10ms before I click on something.</p><p>I probably don't know enough web frontend to help, but it might be fun to try.</p> - - public - - - - - tag:mastodon.social,2017-04-06:objectId=1764509:objectType=Status - 2017-04-06T10:15:38Z - 2017-04-06T10:15:38Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - This is a test for cw federation - <p>This is a test for cw federation body text.</p> - - public - - - - - tag:mastodon.social,2017-04-05:objectId=1645208:objectType=Status - 2017-04-05T07:14:53Z - 2017-04-05T07:14:53Z - lambadalambda shared a status by lambadalambda@social.heldscal.la - http://activitystrea.ms/schema/1.0/activity - http://activitystrea.ms/schema/1.0/share - - tag:social.heldscal.la,2017-04-05:noticeId=1502088:objectType=note - 2017-04-05T06:12:09Z - 2017-04-05T07:12:47Z - New status by lambadalambda@social.heldscal.la - - https://social.heldscal.la/user/23211 - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - lambadalambda@social.heldscal.la - Call me Deacon Blues. - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - public - - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - Federation 101: <a href="https://www.youtube.com/watch?v=t1lYU5CA40o">https://www.youtube.com/watch?v=t1lYU5CA40o</a> - - public - - - Federation 101: <a href="https://www.youtube.com/watch?v=t1lYU5CA40o">https://www.youtube.com/watch?v=t1lYU5CA40o</a> - - public - - - - - tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status - 2017-04-05T05:44:48Z - 2017-04-05T05:44:48Z - New status by lambadalambda - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> just a test.</p> - - - public - - - - diff --git a/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json b/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json index c42f3a53c..ca76d6e17 100644 --- a/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json +++ b/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json @@ -8,6 +8,7 @@ "preferredUsername": "mike", "name": "Mike Macgirvin (Osada)", "updated": "2018-08-29T03:09:11Z", + "discoverable": "true", "icon": { "type": "Image", "mediaType": "image/jpeg", @@ -51,4 +52,4 @@ "created": "2018-10-17T07:16:28Z", "signatureValue": "WbfFVIPImkd3yNu6brz0CvZaeV242rwAbH0vy8DM4vfnXCxLr5Uv/Wj9gwP+tbooTxGaahAKBeqlGkQp8RLEo37LATrKMRLA/0V6DeeV+C5ORWR9B4WxyWiD3s/9Wf+KesFMtktNLAcMZ5PfnOS/xNYerhnpkp/gWPxtkglmLIWJv+w18A5zZ01JCxsO4QljHbhYaEUPHUfQ97abrkLECeam+FThVwdO6BFCtbjoNXHfzjpSZL/oKyBpi5/fpnqMqOLOQPs5WgBBZJvjEYYkQcoPTyxYI5NGpNbzIjGHPQNuACnOelH16A7L+q4swLWDIaEFeXQ2/5bmqVKZDZZ6usNP4QyTVszwd8jqo27qcDTNibXDUTsTdKpNQvM/3UncBuzuzmUV3FczhtGshIU1/pRVZiQycpVqPlGLvXhP/yZCe+1siyqDd+3uMaS2vkHTObSl5r+VYof+c+TcjrZXHSWnQTg8/X3zkoBWosrQ93VZcwjzMxQoARYv6rphbOoTz7RPmGAXYUt3/PDWkqDlmQDwCpLNNkJo1EidyefZBdD9HXQpCBO0ZU0NHb0JmPvg/+zU0krxlv70bm3RHA/maBETVjroIWzt7EwQEg5pL2hVnvSBG+1wF3BtRVe77etkPOHxLnYYIcAMLlVKCcgDd89DPIziQyruvkx1busHI08=" } -} +} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom b/test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom deleted file mode 100644 index 17d1956e8..000000000 --- a/test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom +++ /dev/null @@ -1,231 +0,0 @@ - - - https://pawoo.net/users/pekorino.atom - モノエ - シアトル・米国 - -GNUsocial 英語版 -http://shitposter.club/mono - - - 2017-05-07T09:28:20Z - https://img.pawoo.net/accounts/avatars/000/128/378/original/e1fce04a36a1ad90.jpg - - https://pawoo.net/users/pekorino - http://activitystrea.ms/schema/1.0/person - https://pawoo.net/users/pekorino - pekorino - pekorino@pawoo.net - <p>シアトル・米国</p><p>GNUsocial 英語版<br /><a href="http://shitposter.club/mono" rel="nofollow noopener" target="_blank"><span class="invisible">http://</span><span class="">shitposter.club/mono</span><span class="invisible"></span></a> </p> - - - - pekorino - モノエ - シアトル・米国 - -GNUsocial 英語版 -http://shitposter.club/mono - - - public - - - - - - - tag:pawoo.net,2017-05-07:objectId=9319211:objectType=Status - 2017-05-07T09:56:35Z - 2017-05-07T09:56:35Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> <span class="h-card"><a href="https://shitposter.club/rw" class="u-url mention">@<span>rw</span></a></span> <span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> <span class="h-card"><a href="https://shitposter.club/mono" class="u-url mention">@<span>mono</span></a></span> </p><p>i have to wait for someone to respond to this before i can follow because i dont think this software has a direct follow by url option</p> - - - - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9318595:objectType=Status - 2017-05-07T09:54:39Z - 2017-05-07T09:54:39Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/mono" class="u-url mention">@<span>mono</span></a></span> <span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> <span class="h-card"><a href="https://shitposter.club/rw" class="u-url mention">@<span>rw</span></a></span> <span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> <br />please respond</p> - - - - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9313978:objectType=Status - 2017-05-07T09:39:17Z - 2017-05-07T09:39:17Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> <br />mastodon is so slow. browser crashed twice trying to set avatar</p> - - - public - - - - - tag:pawoo.net,2017-05-07:objectId=9312691:objectType=Status - 2017-05-07T09:34:38Z - 2017-05-07T09:34:38Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/hardbass2k8" class="u-url mention">@<span>hardbass2k8</span></a></span> <a href="https://pawoo.net/media/mZJjLpbPU72GFEz2Svk" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/mZJjLpbPU72GFE</span><span class="invisible">z2Svk</span></a></p> - - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9312379:objectType=Status - 2017-05-07T09:33:29Z - 2017-05-07T09:33:29Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/hardbass2k8" class="u-url mention">@<span>hardbass2k8</span></a></span> <a href="https://pawoo.net/media/nt5JHBEHyTN2bqzdcGU" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/nt5JHBEHyTN2bq</span><span class="invisible">zdcGU</span></a></p> - - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9311765:objectType=Status - 2017-05-07T09:31:26Z - 2017-05-07T09:31:26Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><a href="https://pawoo.net/media/C4RV6ubsEtvS04DX6qs" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/C4RV6ubsEtvS04</span><span class="invisible">DX6qs</span></a></p> - - - public - - - - - tag:pawoo.net,2017-05-07:objectId=9311610:objectType=Status - 2017-05-07T09:30:59Z - 2017-05-07T09:30:59Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><a href="https://pawoo.net/media/MBmkeEdrjs8pAtCHN6s" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/MBmkeEdrjs8pAt</span><span class="invisible">CHN6s</span></a></p> - - - public - - - - - tag:pawoo.net,2017-05-07:objectId=9307782:objectType=Status - 2017-05-07T09:16:47Z - 2017-05-07T09:16:47Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/mono" class="u-url mention">@<span>mono</span></a></span></p><p>test</p> - - - public - - - - - tag:pawoo.net,2017-05-07:objectId=9307444:objectType=Status - 2017-05-07T09:15:42Z - 2017-05-07T09:15:42Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/hardbass2k8" class="u-url mention">@<span>hardbass2k8</span></a></span> テスト</p> - - - public - - - - - - tag:pawoo.net,2017-05-07:objectId=9307239:objectType=Status - 2017-05-07T09:14:58Z - 2017-05-07T09:14:58Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>ててててててテスト</p> - - public - - - - - tag:pawoo.net,2017-04-20:objectId=2212164:objectType=Status - 2017-04-20T06:19:18Z - 2017-04-20T06:19:18Z - New status by pekorino - http://activitystrea.ms/schema/1.0/comment - http://activitystrea.ms/schema/1.0/post - <p><span class="h-card"><a href="https://shitposter.club/mono" class="u-url mention">@<span>mono</span></a></span> <a href="https://pawoo.net/media/iMbjMBVPfZJX3lUC2Sc" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">pawoo.net/media/iMbjMBVPfZJX3l</span><span class="invisible">UC2Sc</span></a></p> - - - - public - - - - - - tag:pawoo.net,2017-04-20:objectId=2206216:objectType=Status - 2017-04-20T05:57:59Z - 2017-04-20T05:57:59Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>テスト</p> - - public - - - - - tag:pawoo.net,2017-04-20:objectId=2204702:objectType=Status - 2017-04-20T05:52:09Z - 2017-04-20T05:52:09Z - New status by pekorino - http://activitystrea.ms/schema/1.0/note - http://activitystrea.ms/schema/1.0/post - <p>HELLOWORLD</p> - - public - - - - diff --git a/test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml b/test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml deleted file mode 100644 index a2a2629a6..000000000 --- a/test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml +++ /dev/null @@ -1 +0,0 @@ -https://pleroma.soykaf.com/users/lain/feed.atomlain's timeline2017-05-05T08:38:03.385598https://pleroma.soykaf.com/users/lainhttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/lainlainLain IwakuraTest accountlainhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/579e4224-b2ab-4ffa-8bbe-f7197a0a38d1lain repeated a noticeRT In just seven days, I can make you a man!<br> -- The Rocky Horror Picture Show2017-05-05T08:38:03.3855902017-05-05T08:38:03.385598https://pleroma.soykaf.com/contexts/e8673466-9642-4c9e-8781-f0f69d6b15aehttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/53dd40f4-3069-45a1-863b-94a9b317093dNew note by fortuneIn just seven days, I can make you a man!<br> -- The Rocky Horror Picture Show2017-05-05T02:10:02.9308022017-05-05T08:38:03.423539https://pleroma.soykaf.com/contexts/e8673466-9642-4c9e-8781-f0f69d6b15aehttps://pleroma.soykaf.com/users/fortunehttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/fortunefortunefortuneThe trusty unix fortune filefortunehttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/2bc86888-a256-4771-bb53-903f375804f9New note by lainRTs federating into pleroma now.2017-05-04T18:18:50.2764702017-05-04T18:18:50.276476https://pleroma.soykaf.com/contexts/b7ae9350-f317-48aa-8058-2668091bb280http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/902b1f50-f295-4189-8c15-9c880919e121New favorite by lainlain favorited something2017-05-04T08:03:01.3088902017-05-04T08:03:01.308927http://activitystrea.ms/schema/1.0/notetag:gs.smuglo.li,2017-05-03:noticeId=2164642:objectType=commenthttps://pleroma.soykaf.com/contexts/9419f742-aaba-4eb5-89a2-8b599e8bf43chttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/4e396e66-b063-454c-92c6-583506a9a2deNew note by lainClassic.<br><a href='https://pleroma.soykaf.com/media/adc36781-9765-4d9a-b57c-99b7a99108b2/mikodaemonstop.jpg'>https://pleroma.soykaf.com/media/adc36781-9765-4d9a-b57c-99b7a99108b2/mikodaemonstop.jpg</a>2017-05-04T07:59:45.1806192017-05-04T07:59:45.180628https://pleroma.soykaf.com/contexts/6afd9659-41e6-406d-ae97-43b880722861http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/85d183e9-c935-4655-a1e6-8d69a4108235New note by lainん?<br><a href='https://pleroma.soykaf.com/media/ab144c6d-a38c-4d35-a60b-9a998becc094/n.gif'>https://pleroma.soykaf.com/media/ab144c6d-a38c-4d35-a60b-9a998becc094/n.gif</a>2017-05-04T07:58:08.8107162017-05-04T07:58:08.810726https://pleroma.soykaf.com/contexts/2e1aa616-86ce-4b50-9c81-63045a972156http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/7c5c45bb-e4d9-4f72-b4c6-0314afbd3553New note by lainyeah.2017-05-04T07:55:17.3352902017-05-04T07:55:17.335299https://pleroma.soykaf.com/contexts/702c06cf-56ff-4a2f-bf5a-150bc00bb168http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/f33f5f54-1c1d-4462-b9ed-229bb635dfd8New note by lainyeah.2017-05-04T07:49:24.9314842017-05-04T07:49:24.931492https://pleroma.soykaf.com/contexts/c4932e7a-00cb-431a-b4ec-7404cb9daf65http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/0709bc79-7ac5-4983-b6d0-2205bf5ceba3New favorite by lainlain favorited something2017-05-03T20:08:11.2945792017-05-03T20:08:11.294587http://activitystrea.ms/schema/1.0/notetag:pawoo.net,2017-05-03:objectId=7967690:objectType=Statushttps://pleroma.soykaf.com/contexts/07a4b34d-6255-4bb2-8c73-c295a09ac952http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/72c0288e-62d8-43d9-b3d8-1a9d78be8375New note by lain<a href='https://pawoo.net/users/God_Emperor_of_Dune'>@God_Emperor_of_Dune@pawoo.net</a> no man, just some fun domination play among buddies, nothing homo about it.2017-05-03T20:01:00.9983142017-05-03T20:01:00.998322https://pleroma.soykaf.com/contexts/07a4b34d-6255-4bb2-8c73-c295a09ac952http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/d846409e-cf2a-4b68-a149-d5de34a91b0dNew note by lain<a href='https://social.heldscal.la/user/24974'>@dtluna@social.heldscal.la</a> btfo.<br><a href='https://pleroma.soykaf.com/media/fbe42e87-5574-4544-89ba-29ddf46227fa/pnc__picked_media_1889ce61-4961-4fea-8a14-04fe6783ebf6.jpg'>https://pleroma.soykaf.com/media/fbe42e87-5574-4544-89ba-29ddf46227fa/pnc__picked_media_1889ce61-4961-4fea-8a14-04fe6783ebf6.jpg</a>2017-05-03T20:00:15.8609952017-05-03T20:00:15.861002https://pleroma.soykaf.com/contexts/0e88f35e-1a38-4181-bef9-5cbb0d943c63http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/9075265f-f3b2-40e8-809f-10714f05a1fdNew note by lain#nohomo <br><a href='https://pleroma.soykaf.com/media/5cc5ad91-d637-4c45-a691-5ea778dc1bb3/pnc__picked_media_f62dc9ae-ea23-4fe6-bf85-cb75a129ab34.jpg'>https://pleroma.soykaf.com/media/5cc5ad91-d637-4c45-a691-5ea778dc1bb3/pnc__picked_media_f62dc9ae-ea23-4fe6-bf85-cb75a129ab34.jpg</a>2017-05-03T19:50:38.5891062017-05-03T19:50:38.589113https://pleroma.soykaf.com/contexts/07a4b34d-6255-4bb2-8c73-c295a09ac952http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/7924e992-0a95-40d9-8d17-7278c6c634c9New favorite by lainlain favorited something2017-05-03T18:32:59.2733752017-05-03T18:32:59.273382http://activitystrea.ms/schema/1.0/notetag:gs.smuglo.li,2017-05-03:noticeId=2164774:objectType=commenthttps://pleroma.soykaf.com/contexts/9419f742-aaba-4eb5-89a2-8b599e8bf43chttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/569571ba-f54c-41b0-bde4-0fede54599f0New note by lain<a href='https://gs.smuglo.li/user/2'>@nepfag@gs.smuglo.li</a>@gs.smuglo.li I'll do proper subfolders soon, for now it's one per attachment + thumbs etc.2017-05-03T18:27:01.4499492017-05-03T18:27:01.449956https://pleroma.soykaf.com/contexts/9419f742-aaba-4eb5-89a2-8b599e8bf43chttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/b6cc5d7c-0785-4785-a689-f1b05dc9b24dlain repeated a noticeRT <p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey now!</p>2017-05-03T18:13:48.8910612017-05-03T18:13:48.891069https://pleroma.soykaf.com/contexts/ec6fdd27-0ec1-4672-8408-5a8e5a9c094bhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posttag:mastodon.social,2017-05-01:objectId=4836142:objectType=StatusNew note by lambadalambda@mastodon.social<p><span class="h-card"><a href="https://pleroma.soykaf.com/users/lain" class="u-url mention">@<span>lain</span></a></span> Hey now!</p>2017-05-01T18:38:49.3653912017-05-03T18:13:48.934745https://pleroma.soykaf.com/contexts/ec6fdd27-0ec1-4672-8408-5a8e5a9c094bhttps://mastodon.social/users/lambadalambdahttp://activitystrea.ms/schema/1.0/personhttps://mastodon.social/users/lambadalambdalambadalambda@mastodon.socialCritical Valuenillambadalambda@mastodon.socialhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/3c09eb31-4ba8-4ff5-b4fa-8f6f74d58bf0lain repeated a noticeRT Haha, salmons from mastodon didn't work because it's not implementing conversation id...2017-05-03T18:13:15.1480412017-05-03T18:13:15.148049tag:social.heldscal.la,2017-05-01:objectType=thread:nonce=86cda6c734401d80http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posttag:social.heldscal.la,2017-05-01:noticeId=2000425:objectType=noteNew note by lambadalambda@social.heldscal.laHaha, salmons from mastodon didn't work because it's not implementing conversation id...2017-05-01T18:39:36.2163772017-05-03T18:13:15.171143tag:social.heldscal.la,2017-05-01:objectType=thread:nonce=86cda6c734401d80https://social.heldscal.la/user/23211http://activitystrea.ms/schema/1.0/personhttps://social.heldscal.la/user/23211lambadalambda@social.heldscal.laConstance Variablenillambadalambda@social.heldscal.lahttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/b8fc83d5-d7c0-4b5f-8976-0317b51935eaNew note by lain.<br><a href='https://pleroma.soykaf.com/media/563008a7-9a60-47ac-a263-22835729adf6/1492530528735.png'>https://pleroma.soykaf.com/media/563008a7-9a60-47ac-a263-22835729adf6/1492530528735.png</a>2017-05-03T18:12:50.7452412017-05-03T18:12:50.745249https://pleroma.soykaf.com/contexts/9419f742-aaba-4eb5-89a2-8b599e8bf43chttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/ac93ecef-cde0-48e8-ae4b-19e3b94dbe30lain repeated a noticeRT Awright, which one of you hid my PENIS ENVY?2017-05-03T18:08:49.2310012017-05-03T18:08:49.235354https://pleroma.soykaf.com/contexts/a9132cf8-6afa-4dd8-8b29-7b6fcab623b8http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/04e15c66-4936-4930-a134-32841f088bcfNew note by fortuneAwright, which one of you hid my PENIS ENVY?2017-05-01T19:40:03.1699962017-05-03T18:08:49.285347https://pleroma.soykaf.com/contexts/a9132cf8-6afa-4dd8-8b29-7b6fcab623b8https://pleroma.soykaf.com/users/fortunehttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/fortunefortunefortuneThe trusty unix fortune filefortunehttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/54b10fa9-d602-4a0f-b659-e6d3f7bc8c4clain repeated a noticeRT He is a man capable of turning any colour into grey.<br> -- John LeCarre2017-05-03T17:44:47.5789842017-05-03T17:44:47.578996https://pleroma.soykaf.com/contexts/8aebc8e5-5352-4047-8b74-4098a5830ccahttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/70ded299-184d-49cd-af17-23c0950536aaNew note by fortuneHe is a man capable of turning any colour into grey.<br> -- John LeCarre2017-05-02T08:40:03.4194652017-05-03T17:44:47.646192https://pleroma.soykaf.com/contexts/8aebc8e5-5352-4047-8b74-4098a5830ccahttps://pleroma.soykaf.com/users/fortunehttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/fortunefortunefortuneThe trusty unix fortune filefortunehttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/sharehttps://pleroma.soykaf.com/activities/eff9fe49-8fc9-48e6-a1a0-921aa25c8118lain repeated a noticeRT The real trouble with women is that they have *all* the pussy.2017-05-03T17:30:22.5960372017-05-03T17:30:22.596048https://pleroma.soykaf.com/contexts/8c88c9df-4e40-4f54-b15f-c21848d1a8e2http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/0b9b008d-49eb-48a9-a18d-172ce7d01ea2New note by fortuneThe real trouble with women is that they have *all* the pussy.2017-05-02T12:10:03.6030862017-05-03T17:30:22.683141https://pleroma.soykaf.com/contexts/8c88c9df-4e40-4f54-b15f-c21848d1a8e2https://pleroma.soykaf.com/users/fortunehttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/fortunefortunefortuneThe trusty unix fortune filefortunehttp://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/5d90bb26-ce23-4a5b-8dbd-651011780007New favorite by lainlain favorited something2017-05-03T17:28:20.9679262017-05-03T17:28:20.967935http://activitystrea.ms/schema/1.0/notetag:mastodon.social,2017-05-03:objectId=4952899:objectType=Statushttps://pleroma.soykaf.com/contexts/42701ab4-964a-441a-a372-f51bd183e441 \ No newline at end of file diff --git a/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml b/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml deleted file mode 100644 index 26fdebb49..000000000 --- a/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml +++ /dev/null @@ -1,54 +0,0 @@ - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T08:51:48+00:00 - 2017-05-05T08:51:48+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/1 - moonman - EMAIL:shitposterclub@gmail.com XMPP: moon@talk.shitposter.club Matrix Ed25519 fingerprint: 2HuDUTEz3iFN5N3xl6PYp9xZW/EWhgbbt78SrFy4w8o - - - - - - moonman - Generic Enemy - EMAIL:shitposterclub@gmail.com XMPP: moon@talk.shitposter.club Matrix Ed25519 fingerprint: 2HuDUTEz3iFN5N3xl6PYp9xZW/EWhgbbt78SrFy4w8o - - The Moon - - - homepage - https://shitposter.club/moonman - true - - - - - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26 - - - - - https://shitposter.club/api/statuses/user_timeline/1.atom - Generic Enemy - - - - https://shitposter.club/avatar/1-96-20170503024316.jpeg - 2017-05-05T11:43:58+00:00 - - - - - diff --git a/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml b/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml deleted file mode 100644 index 31df7c2a6..000000000 --- a/test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml +++ /dev/null @@ -1,454 +0,0 @@ - - - GNU social - https://shitposter.club/api/statuses/user_timeline/1.atom - moonman timeline - Updates from moonman on Shitposter Club! - https://shitposter.club/avatar/1-96-20170503024316.jpeg - 2017-05-05T13:24:09+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/1 - moonman - EMAIL:shitposterclub@gmail.com XMPP: moon@talk.shitposter.club Matrix Ed25519 fingerprint: 2HuDUTEz3iFN5N3xl6PYp9xZW/EWhgbbt78SrFy4w8o - - - - - - moonman - Generic Enemy - EMAIL:shitposterclub@gmail.com XMPP: moon@talk.shitposter.club Matrix Ed25519 fingerprint: 2HuDUTEz3iFN5N3xl6PYp9xZW/EWhgbbt78SrFy4w8o - - The Moon - - - homepage - https://shitposter.club/moonman - true - - - - - - - - - - - - - - tag:shitposter.club,2017-05-05:subscription:1:person:23190:2017-05-05T11:43:58+00:00 - Generic Enemy (moonman)'s status on Friday, 05-May-2017 11:43:58 UTC - <a href="https://shitposter.club/moonman">Generic Enemy</a> started following <a href="https://noagendasocial.com/@Ma5on">Mason</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-05-05T11:43:58+00:00 - 2017-05-05T11:43:58+00:00 - - http://activitystrea.ms/schema/1.0/person - https://noagendasocial.com/users/Ma5on - Mason - - - - - - ma5on - Mason - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=abffa9c14a054d3b - - - - - - - tag:shitposter.club,2017-05-05:subscription:1:person:14357:2017-05-05T10:29:03+00:00 - Generic Enemy (moonman)'s status on Friday, 05-May-2017 10:29:03 UTC - <a href="https://shitposter.club/moonman">Generic Enemy</a> started following <a href="https://mastodon.cloud/@ohyran">Jens Reuterberg</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-05-05T10:29:03+00:00 - 2017-05-05T10:29:03+00:00 - - http://activitystrea.ms/schema/1.0/person - https://mastodon.cloud/users/ohyran - Jens Reuterberg - RPG-nerd, illustrator, Open Source enthusiast, KDE dude, designer and gay lefty. Might be a cliché - but we will soon find out! - - - - - - ohyran - Jens Reuterberg - RPG-nerd, illustrator, Open Source enthusiast, KDE dude, designer and gay lefty. Might be a cliché - but we will soon find out! - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=937151d4825a85bf - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828637:objectType=note - New note by moonman - basicall i would just rather have ppl say &quot;i like x and y&quot; than &quot;i'm a nerd&quot; the term can be retired. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:24:54+00:00 - 2017-05-05T10:24:54+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=65992b0b9b5e6931 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828579:objectType=comment - New comment by moonman - @<a href="https://gs.smuglo.li/user/35497" class="h-card mention" title="Bokuro Bokusawa">boco</a> to be honest i've turned right around and been cruel to other people, i said i'd never do it but it happens again eventually. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:20:33+00:00 - 2017-05-05T10:20:33+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=c997fc73d7f8a8f0 - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828554:objectType=comment - New comment by moonman - @<a href="https://mastodon.cloud/users/ohyran" class="h-card mention" title="Jens Reuterberg">ohyran</a> i won't ever get over bullying but i agree otherwise. i don't go to comic shops too often these days but i got dragged to one last year and the sheer diversity of people enjoying comics now compared to years ago was striking and it pleased me. and i noticed a couple years ago because of youtube i find things i truly enjoy watching, like in-depth videos about electronic parts, didn't exist 20 years ago. it's pretty great. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:18:10+00:00 - 2017-05-05T10:18:10+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - - tag:shitposter.club,2017-05-05:fave:1:comment:2828502:2017-05-05T10:12:52+00:00 - Favorite - moonman favorited something by ohyran: <p><span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> fair enough - that distinction makes it clearer...</p><p>On the other hand - those of us who did "pay the price" of being nerdy little kids in the 80's and 90's should strive to get past it anyway (mental health wise not "just get over it") and see the "nerd culture" thing as a blessing of sorts. We are in the optimal spot to do it. (not saying that that is something easy btw just that NOW is the best of time to start talking about it)</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T10:12:52+00:00 - 2017-05-05T10:12:52+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:mastodon.cloud,2017-05-05:objectId=6334570:objectType=Status - New comment by ohyran - <p><span class="h-card"><a href="https://shitposter.club/moonman" class="u-url mention">@<span>moonman</span></a></span> fair enough - that distinction makes it clearer...</p><p>On the other hand - those of us who did "pay the price" of being nerdy little kids in the 80's and 90's should strive to get past it anyway (mental health wise not "just get over it") and see the "nerd culture" thing as a blessing of sorts. We are in the optimal spot to do it. (not saying that that is something easy btw just that NOW is the best of time to start talking about it)</p> - - - - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828496:objectType=note - New note by moonman - things are better now, a lot less kids in america get beaten up and called a fag. still too many. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:11:31+00:00 - 2017-05-05T10:11:31+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=c997fc73d7f8a8f0 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828457:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/21787" class="h-card mention" title="Yukari">cutscenes</a> @<a href="https://gs.smuglo.li/user/28250" class="h-card mention" title="Bricky">thatbrickster</a> @<a href="https://gs.smuglo.li/user/35497" class="h-card mention" title="Bokuro Bokusawa">boco</a> i never understood this because nerds had pocket protectors, which was a draftsman engineer thing and therefore smart, while geeks were people in carnivals who bit heads off small animals. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:07:57+00:00 - 2017-05-05T10:07:57+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828435:objectType=comment - New comment by moonman - @<a href="https://mastodon.cloud/users/ohyran" class="h-card mention" title="Jens Reuterberg">ohyran</a> since i didn't specify i'm talking about people subjected to physical and psychological abuse and not people that are just mad that more people like comic books now. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:05:07+00:00 - 2017-05-05T10:05:07+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828326:objectType=note - New note by moonman - if you were a &quot;nerd&quot; before, like, 2001 you have permanent excuse to hate this kind of shit.   <a href="https://shitposter.club/file/b79fa5644be0d6f22679136e67b7bf45c9c4a74a55c32dd2d0cf15de4ddd5be5.gif" title="https://shitposter.club/file/b79fa5644be0d6f22679136e67b7bf45c9c4a74a55c32dd2d0cf15de4ddd5be5.gif" class="attachment" id="attachment-662105" rel="nofollow external">https://shitposter.club/attachment/662105</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:47:42+00:00 - 2017-05-05T09:47:42+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=efae3a23b6e05767 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828250:objectType=note - New note by moonman - <a href="https://shitposter.club/file/1283e2d4dd8f96b8eeb5d9a16b318e210868aa11386cf0d593891e4c75c9126e.gif" title="https://shitposter.club/file/1283e2d4dd8f96b8eeb5d9a16b318e210868aa11386cf0d593891e4c75c9126e.gif" class="attachment" id="attachment-662098" rel="nofollow external">https://shitposter.club/attachment/662098</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:39:06+00:00 - 2017-05-05T09:39:06+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=ea8ffae90546f0ab - - - - - - - - tag:shitposter.club,2017-05-05:fave:1:comment:2828161:2017-05-05T09:28:19+00:00 - Favorite - moonman favorited something by kro: @<a href="https://shitposter.club/user/1" class="h-card u-url p-nickname mention" title="Generic Enemy">moonman</a> Till Brooklyn? - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:28:19+00:00 - 2017-05-05T09:28:19+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:gs.smuglo.li,2017-05-05:noticeId=2188587:objectType=comment - New comment by kro - @<a href="https://shitposter.club/user/1" class="h-card u-url p-nickname mention" title="Generic Enemy">moonman</a> Till Brooklyn? - - - - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=d7aa6b5b057ca555 - - - - - - - tag:shitposter.club,2017-05-05:fave:1:comment:2828125:2017-05-05T09:24:56+00:00 - Favorite - moonman favorited something by hardbass2k8: this has obviously interesting implications in various places, for example:<br /> the nationalism of the nazis might not have been real, who would have thought?<br /> socialism is usually promoted to implementation by real douchebags!<br /> your local social justice people might want diversity but they don't want you, m/19, white, why?<br /> amateur soccer club, they want to be the best in the amateur league but actually they just get drunk after training and are 50% overweight.<br /> This is because humans are not capable of telepathy, so if you join a group it doesn't magically align every little bit of your being with the declared group goals.<br /> <br /> Even though you see unmanned group beliefs flying around from time to time, generally groups are created from a bunch of people. they are not a container for people, they are the people inside them.<br /> <br /> so if you see a group that appears to be cool don't think of it as cool because its goals are cool but because its members are cool. if they aren't, tough cookies. don't be the retard and end up on the camp watchtower. - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:24:56+00:00 - 2017-05-05T09:24:56+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828125:objectType=comment - New comment by hardbass2k8 - this has obviously interesting implications in various places, for example:<br /> the nationalism of the nazis might not have been real, who would have thought?<br /> socialism is usually promoted to implementation by real douchebags!<br /> your local social justice people might want diversity but they don't want you, m/19, white, why?<br /> amateur soccer club, they want to be the best in the amateur league but actually they just get drunk after training and are 50% overweight.<br /> This is because humans are not capable of telepathy, so if you join a group it doesn't magically align every little bit of your being with the declared group goals.<br /> <br /> Even though you see unmanned group beliefs flying around from time to time, generally groups are created from a bunch of people. they are not a container for people, they are the people inside them.<br /> <br /> so if you see a group that appears to be cool don't think of it as cool because its goals are cool but because its members are cool. if they aren't, tough cookies. don't be the retard and end up on the camp watchtower. - - - - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=51b227fe92f6babf - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828128:objectType=note - New note by moonman - In a valid remake of They live, signs would say REBEL, and DON'T GET MARRIED AND HAVE KIDS - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:24:23+00:00 - 2017-05-05T09:24:23+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=b74397fa766b82c9 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828104:objectType=note - New note by moonman - <a href="https://shitposter.club/file/4d34178bde99599f31a28928e1666fbd58448d8a22e94ed82222496e4a45cb07.gif" title="https://shitposter.club/file/4d34178bde99599f31a28928e1666fbd58448d8a22e94ed82222496e4a45cb07.gif" class="attachment" id="attachment-662049" rel="nofollow external">https://shitposter.club/attachment/662049</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:21:01+00:00 - 2017-05-05T09:21:01+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=d7aa6b5b057ca555 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828102:objectType=note - New note by moonman - when ppl find out i haven't always been serious  <a href="https://shitposter.club/file/5859fa95875342cc65dba0d852f726db158ce28198c326d5f13d9de7c0d2c449.gif" title="https://shitposter.club/file/5859fa95875342cc65dba0d852f726db158ce28198c326d5f13d9de7c0d2c449.gif" class="attachment" id="attachment-662053" rel="nofollow external">https://shitposter.club/attachment/662053</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:20:45+00:00 - 2017-05-05T09:20:45+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=0a025ac5a570b4ec - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828086:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> @<a href="https://gs.smuglo.li/user/35497" class="h-card mention" title="Bokuro Bokusawa">boco</a> you are being too serious lol - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:17:19+00:00 - 2017-05-05T09:17:19+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26 - - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828085:objectType=note - New note by moonman - shitposter dot club  <a href="https://shitposter.club/file/9b084c7210b16abbf4d28594b924a07ef4a2a06f89d901a4c42fb1e243291263.gif" title="https://shitposter.club/file/9b084c7210b16abbf4d28594b924a07ef4a2a06f89d901a4c42fb1e243291263.gif" class="attachment" id="attachment-662047" rel="nofollow external">https://shitposter.club/attachment/662047</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:16:50+00:00 - 2017-05-05T09:16:50+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=d1ae088a1b91e5e5 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2828061:objectType=note - New note by moonman - even when i lie i tell the truth, is that so hard to understand? - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:15:07+00:00 - 2017-05-05T09:15:07+00:00 - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=a516e4b8506b8ef5 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2828052:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9591" class="h-card mention" title="warum hei&#xDF;en deutschl&#xE4;nder deutschl&#xE4;nder">hardbass2k8</a> history, anthropology. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T09:14:22+00:00 - 2017-05-05T09:14:22+00:00 - - - - tag:shitposter.club,2017-05-05:objectType=thread:nonce=fe4d7f35b13403ba - - - - - - - diff --git a/test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.json b/test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.json deleted file mode 100644 index 4b7b4df44..000000000 --- a/test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.json +++ /dev/null @@ -1 +0,0 @@ -{"@context":["https://www.w3.org/ns/activitystreams","https://shitposter.club/schemas/litepub-0.1.jsonld",{"@language":"und"}],"actor":"https://shitposter.club/users/moonman","attachment":[],"attributedTo":"https://shitposter.club/users/moonman","cc":["https://shitposter.club/users/moonman/followers"],"content":"@neimzr4luzerz @dolus childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English","context":"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26","conversation":"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26","id":"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment","inReplyTo":"tag:shitposter.club,2017-05-05:noticeId=2827849:objectType=comment","inReplyToStatusId":2827849,"published":"2017-05-05T08:51:48Z","sensitive":false,"summary":null,"tag":[],"to":["https://www.w3.org/ns/activitystreams#Public"],"type":"Note"} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml b/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml deleted file mode 100644 index 6cba5c28f..000000000 --- a/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml +++ /dev/null @@ -1,591 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-05T12:01:21+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda - Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2063249:2017-05-05T11:40:21+00:00 - Favorite - lambadalambda favorited something by tatiana: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> they will start complaining about this, but won't come up with any solutions)</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T11:40:21+00:00 - 2017-05-05T11:40:21+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:social.weho.st,2017-05-05:objectId=172033:objectType=Status - New comment by tatiana - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> they will start complaining about this, but won't come up with any solutions)</p> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2063041:2017-05-05T11:27:28+00:00 - Favorite - lambadalambda favorited something by kat: @<a href="https://social.heldscal.la/lambadalambda" class="h-card mention" title="Constance Variable">lambadalambda</a> if the admin reading mine would delete a few it would be really useful in prioritising.  - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T11:27:28+00:00 - 2017-05-05T11:27:28+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:quitter.se,2017-05-05:noticeId=11807959:objectType=comment - New comment by kat - @<a href="https://social.heldscal.la/lambadalambda" class="h-card mention" title="Constance Variable">lambadalambda</a> if the admin reading mine would delete a few it would be really useful in prioritising.  - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:noticeId=2062924:objectType=note - lambadalambda repeated a notice by nielsk - RT @nielsk @<a href="https://social.heldscal.la/user/23211" class="h-card u-url p-nickname mention" title="Constance Variable">lambadalambda</a> but there are soooo many, where should I start to read? - - http://activitystrea.ms/schema/1.0/share - 2017-05-05T11:09:37+00:00 - 2017-05-05T11:09:37+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:mastodon.social,2017-05-05:objectId=5024471:objectType=Status - - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> but there are soooo many, where should I start to read?</p> - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T11:05:18+00:00 - 2017-05-05T11:05:18+00:00 - - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/nielsk - nielsk - Sysadmin by day and ehm… sysadmin by night. Besides that old video games, Japan, economics and some other stuff - - - - - - nielsk - nielsk - Sysadmin by day and ehm… sysadmin by night. Besides that old video games, Japan, economics and some other stuff - - - - http://activitystrea.ms/schema/1.0/comment - tag:mastodon.social,2017-05-05:objectId=5024471:objectType=Status - New comment by nielsk - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> but there are soooo many, where should I start to read?</p> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - https://mastodon.social/users/nielsk.atom - nielsk - - - https://social.heldscal.la/avatar/29849-96-20170428120041.jpeg - 2017-05-05T11:06:32+00:00 - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2062875:2017-05-05T11:09:27+00:00 - Favorite - lambadalambda favorited something by nielsk: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> but there are soooo many, where should I start to read?</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T11:09:27+00:00 - 2017-05-05T11:09:27+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:mastodon.social,2017-05-05:objectId=5024471:objectType=Status - New comment by nielsk - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> but there are soooo many, where should I start to read?</p> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2062863:2017-05-05T11:09:11+00:00 - Favorite - lambadalambda favorited something by kasil: <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> surely, google is not that evil !</p> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T11:09:11+00:00 - 2017-05-05T11:09:11+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:loutre.info,2017-05-05:objectId=23331:objectType=Status - New comment by kasil - <p><span class="h-card"><a href="https://social.heldscal.la/lambadalambda" class="u-url mention">@<span>lambadalambda</span></a></span> surely, google is not that evil !</p> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-05:noticeId=2062767:objectType=comment - New comment by lambadalambda - @<a href="https://sealion.club/user/4" class="h-card u-url p-nickname mention" title="dewoo &#x274E;">dwmatiz</a> dunno, probably. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:55:17+00:00 - 2017-05-05T10:55:17+00:00 - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-05:noticeId=2062705:objectType=comment - New comment by lambadalambda - @<a href="https://gs.smuglo.li/user/28250" class="h-card u-url p-nickname mention" title="Bricky">thatbrickster</a> I do it, too. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:48:12+00:00 - 2017-05-05T10:48:12+00:00 - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-05-05:noticeId=2062620:objectType=comment - New comment by lambadalambda - @<a href="https://social.tchncs.de/users/israuor" class="h-card u-url p-nickname mention" title="Israuor &#x2642;">israuor</a> @<a href="https://mastodon.gougere.fr/users/bortzmeyer" class="h-card u-url p-nickname mention" title="S. Bortzmeyer &#x2705;">bortzmeyer</a> so, 99%. 100% for 'normal' people. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:38:52+00:00 - 2017-05-05T10:38:52+00:00 - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-05:noticeId=2062583:objectType=note - New note by lambadalambda - I wonder what'll happen when people realize the admin at their mail hoster can read all their e-mails. - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T10:35:45+00:00 - 2017-05-05T10:35:45+00:00 - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=e95b99adc050e198 - - - - - - - tag:social.heldscal.la,2017-05-05:subscription:23211:person:35708:2017-05-05T09:34:46+00:00 - Constance Variable (lambadalambda@social.heldscal.la)'s status on Friday, 05-May-2017 09:34:46 UTC - <a href="https://social.heldscal.la/lambadalambda">Constance Variable</a> started following <a href="https://mastodon.social/@milouse">milouse</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-05-05T09:34:46+00:00 - 2017-05-05T09:34:46+00:00 - - http://activitystrea.ms/schema/1.0/person - https://mastodon.social/users/milouse - milouse - #Scout leader #sgdf, interested in #openweb, #semanticweb, #privacy, #foss and #socialeconomy. 0xA714ECAC8C9CEE3D - - - - - - milouse - milouse - #Scout leader #sgdf, interested in #openweb, #semanticweb, #privacy, #foss and #socialeconomy. 0xA714ECAC8C9CEE3D - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=26ca19a355bb6135 - - - - - - - tag:social.heldscal.la,2017-05-05:noticeId=2061871:objectType=note - lambadalambda repeated a notice by safebot - RT @<a href="https://gs.smuglo.li/user/25857" class="h-card u-url p-nickname mention" title="safebot">safebot</a> #<span class="tag"><a href="https://social.heldscal.la/tag/cheers" rel="tag">cheers</a></span> <a href="https://gs.smuglo.li/attachment/456444" title="https://gs.smuglo.li/attachment/456444" rel="nofollow external noreferrer" class="attachment" id="attachment-432334">https://gs.smuglo.li/attachment/456444</a> - - http://activitystrea.ms/schema/1.0/share - 2017-05-05T09:16:17+00:00 - 2017-05-05T09:16:17+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.smuglo.li,2017-05-05:noticeId=2188073:objectType=note - - #<span class="tag"><a href="https://gs.smuglo.li/tag/cheers" rel="tag">cheers</a></span> <a href="https://gs.smuglo.li/file/5099e73c83da778cd032a721e96880f99a868b712be2975d08238547a5ba06c7.jpg" title="https://gs.smuglo.li/file/5099e73c83da778cd032a721e96880f99a868b712be2975d08238547a5ba06c7.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/456444</a> - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T08:36:53+00:00 - 2017-05-05T08:36:53+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.smuglo.li/user/25857 - safebot - - - - - - safebot - safebot - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.smuglo.li,2017-05-05:noticeId=2188073:objectType=note - New note by safebot - #<span class="tag"><a href="https://gs.smuglo.li/tag/cheers" rel="tag">cheers</a></span> <a href="https://gs.smuglo.li/file/5099e73c83da778cd032a721e96880f99a868b712be2975d08238547a5ba06c7.jpg" title="https://gs.smuglo.li/file/5099e73c83da778cd032a721e96880f99a868b712be2975d08238547a5ba06c7.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/456444</a> - - - - - https://gs.smuglo.li/conversation/1009429 - - - - https://gs.smuglo.li/api/statuses/user_timeline/25857.atom - safebot - - - https://social.heldscal.la/avatar/25719-original-20161215233234.jpeg - 2017-05-05T12:00:57+00:00 - - - - https://gs.smuglo.li/conversation/1009429 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:12:50+00:00 - 2017-05-05T09:12:50+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> @<a href="https://gs.smuglo.li/user/2326" class="h-card mention" title="Dolus_McHonest">dolus</a> childhood poring over Strong's concordance and a koine Greek dictionary, fast forward to 2017 and some fuckstick who translates japanese jackoff material tells me you just need to make it sound right in English - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061696:2017-05-05T09:06:10+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> <br /> <span class="greentext">&gt; (((common era)))</span> - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T09:06:10+00:00 - 2017-05-05T09:06:10+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827918:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> <br /> <span class="greentext">&gt; (((common era)))</span> - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:note:2061673:2017-05-05T08:58:28+00:00 - Favorite - lambadalambda favorited something by moonman: discussion is one thing but any argument I've heard over and over again for the last three decades is going to go unanswered. - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:58:28+00:00 - 2017-05-05T08:58:28+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-05-05:noticeId=2827895:objectType=note - New note by moonman - discussion is one thing but any argument I've heard over and over again for the last three decades is going to go unanswered. - - - - - - - https://shitposter.club/conversation/1390494 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061280:2017-05-05T08:47:38+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> sex is for procreation and as an expression of intimacy between commited couples, it is a sacramental act - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:47:38+00:00 - 2017-05-05T08:47:38+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827561:objectType=comment - New comment by moonman - @<a href="https://shitposter.club/user/9655" class="h-card mention" title="Solidarity for Pigs">neimzr4luzerz</a> sex is for procreation and as an expression of intimacy between commited couples, it is a sacramental act - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=55ead90125cd4bd4 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:note:2061535:2017-05-05T08:40:55+00:00 - Favorite - lambadalambda favorited something by fortune: What did Mickey Mouse get for Christmas?<br /> <br /> A Dan Quayle watch.<br /> <br /> -- heard from a Mike Dukakis field worker - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:40:55+00:00 - 2017-05-05T08:40:55+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-05:noticeId=2061535:objectType=note - New note by fortune - What did Mickey Mouse get for Christmas?<br /> <br /> A Dan Quayle watch.<br /> <br /> -- heard from a Mike Dukakis field worker - - - - - - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=5185e5c145ee4762 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061421:2017-05-05T08:36:27+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://maly.io/users/sonya" class="h-card mention" title="Sonya Mann ✅">sonya</a> banned from 4chan. you better watch ou. i'm trouble, y'hear? - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:36:27+00:00 - 2017-05-05T08:36:27+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827689:objectType=comment - New comment by moonman - @<a href="https://maly.io/users/sonya" class="h-card mention" title="Sonya Mann ✅">sonya</a> banned from 4chan. you better watch ou. i'm trouble, y'hear? - - - - - - - https://shitposter.club/conversation/1389345 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061351:2017-05-05T08:28:03+00:00 - Favorite - lambadalambda favorited something by moonman: @<a href="https://social.heldscal.la/user/29138" class="h-card mention" title="Claes Wallin (韋嘉誠)">clacke</a> is that the sequel to Time Crisis - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:28:03+00:00 - 2017-05-05T08:28:03+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827630:objectType=comment - New comment by moonman - @<a href="https://social.heldscal.la/user/29138" class="h-card mention" title="Claes Wallin (韋嘉誠)">clacke</a> is that the sequel to Time Crisis - - - - - - - https://shitposter.club/conversation/1385528 - - - - - - - tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061339:2017-05-05T08:21:05+00:00 - Favorite - lambadalambda favorited something by hardbass2k8: @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> pretty sure it's money laundering - - http://activitystrea.ms/schema/1.0/favorite - 2017-05-05T08:21:05+00:00 - 2017-05-05T08:21:05+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2017-05-05:noticeId=2827617:objectType=comment - New comment by hardbass2k8 - @<a href="https://social.heldscal.la/user/23211" class="h-card mention" title="Constance Variable">lambadalambda</a> pretty sure it's money laundering - - - - - - - https://shitposter.club/conversation/1387523 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-05-05:noticeId=2061303:objectType=note - New note by lambadalambda - It's got tattoos, it's got a pierced hood<br /> It's got generation X<br /> It's got lesbians, and vitriol<br /> And sadomasochistic latex sex<br /> It's got Mighty Morphin' power brokers<br /> And Tanya Harding nude<br /> Macrobiotic lacto-vegan non-confrontational free range food<br /> It's got the handshake, peace talk, non-aggression pact<br /> A multicultural integration of segregated historical facts<br /> <br /> #<span class="tag"><a href="https://social.heldscal.la/tag/nsfw" rel="tag">nsfw</a></span> <a href="https://social.heldscal.la/file/61c13b99c92f40ec4865e7a3830da340b187e3de70d94b8da38fd2138bbede3a.jpg" title="https://social.heldscal.la/file/61c13b99c92f40ec4865e7a3830da340b187e3de70d94b8da38fd2138bbede3a.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432199">https://social.heldscal.la/attachment/432199</a> <a href="https://social.heldscal.la/file/a88bba1a324da68ee2cfdbcd1c4cde60bd9553298244d6f81731270b71aa80df.jpg" title="https://social.heldscal.la/file/a88bba1a324da68ee2cfdbcd1c4cde60bd9553298244d6f81731270b71aa80df.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432200">https://social.heldscal.la/attachment/432200</a> <a href="https://social.heldscal.la/file/887329a303250e73dc2eea06b1f0512fcac4b9d1b534068f03c45f00d5b21c39.jpg" title="https://social.heldscal.la/file/887329a303250e73dc2eea06b1f0512fcac4b9d1b534068f03c45f00d5b21c39.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432201">https://social.heldscal.la/attachment/432201</a> <a href="https://social.heldscal.la/file/6d7a1ec15c1368c4c68810434d24da528606fcbccdd1da97b25affafeeb6ffda.jpg" title="https://social.heldscal.la/file/6d7a1ec15c1368c4c68810434d24da528606fcbccdd1da97b25affafeeb6ffda.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432202">https://social.heldscal.la/attachment/432202</a> <a href="https://social.heldscal.la/file/2f55f2bb028eb9be744cc82b35a6b86b496d8c3924c700aff55a872ff11df54c.jpg" title="https://social.heldscal.la/file/2f55f2bb028eb9be744cc82b35a6b86b496d8c3924c700aff55a872ff11df54c.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-432203">https://social.heldscal.la/attachment/432203</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-05-05T08:17:08+00:00 - 2017-05-05T08:17:08+00:00 - - tag:social.heldscal.la,2017-05-05:objectType=thread:nonce=bb6f4343036970e8 - - - - - - - - - - - - diff --git a/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml b/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml deleted file mode 100644 index f70fbc695..000000000 --- a/test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml +++ /dev/null @@ -1,719 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/29191.atom - shp timeline - Updates from shp on social.heldscal.la! - https://social.heldscal.la/avatar/29191-96-20170421154949.jpeg - 2017-05-05T11:57:06+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/29191 - shp - cofe - - - - - - shp - shp - cofe - - cofe - - - - - - - - - - - - - - tag:social.heldscal.la,2017-04-29:noticeId=1967657:objectType=note - shp repeated a notice by lain - RT @<a href="https://social.heldscal.la/user/37181" class="h-card u-url p-nickname mention" title="Lain Iwakura">lain</a> @<a href="https://social.heldscal.la/user/29191" class="h-card u-url p-nickname mention" title="shp">shp</a> @<a href="https://social.heldscal.la/user/23211" class="h-card u-url p-nickname mention">lambadalambda</a> cofe. - - http://activitystrea.ms/schema/1.0/share - 2017-04-29T18:19:34+00:00 - 2017-04-29T18:19:34+00:00 - - http://activitystrea.ms/schema/1.0/activity - https://pleroma.soykaf.com/activities/43d12c05-db3f-4f3d-bee1-d676f264490c - - <a href="https://pleroma.soykaf.com/users/shp">@shp</a> <a href="https://social.heldscal.la/user/23211">@lambadalambda@social.heldscal.la</a> cofe. - - http://activitystrea.ms/schema/1.0/post - 2017-04-29T18:14:36+00:00 - 2017-04-29T18:14:36+00:00 - - http://activitystrea.ms/schema/1.0/person - https://pleroma.soykaf.com/users/lain - lain - Test account - - - - - - lain - Lain Iwakura - Test account - - - - http://activitystrea.ms/schema/1.0/note - https://pleroma.soykaf.com/activities/43d12c05-db3f-4f3d-bee1-d676f264490c - New note by lain - <a href="https://pleroma.soykaf.com/users/shp">@shp</a> <a href="https://social.heldscal.la/user/23211">@lambadalambda@social.heldscal.la</a> cofe. - - - - - tag:social.heldscal.la,2017-04-29:objectType=thread:nonce=e0b75431888efdab - - - https://pleroma.soykaf.com/users/lain/feed.atom - Lain Iwakura - - - https://social.heldscal.la/avatar/43188-96-20170429172422.jpeg - 2017-05-05T08:38:03+00:00 - - - - tag:social.heldscal.la,2017-04-29:objectType=thread:nonce=e0b75431888efdab - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:29558:2017-04-27T17:26:37+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:26:37 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://gs.smuglo.li/kfist">KFist</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:26:37+00:00 - 2017-04-27T17:26:37+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.smuglo.li/user/28051 - KFist - I stream thanks to @nepfag. I also drink, shitpost, and fly planes. I visited Japan and it changed my life. Do you love your station? - - - - - - kfist - KFist - I stream thanks to @nepfag. I also drink, shitpost, and fly planes. I visited Japan and it changed my life. Do you love your station? - - homepage - http://smuglo.li:8000/stream.m3u - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=f766240d13ed9c2e - - - - - - - tag:social.heldscal.la,2017-04-27:noticeId=1933030:objectType=note - shp repeated a notice by shpbot - RT @<a href="https://gs.archae.me/user/4687" class="h-card u-url p-nickname mention" title="shpbot">shpbot</a> &gt;QuakeC - - http://activitystrea.ms/schema/1.0/share - 2017-04-27T17:21:10+00:00 - 2017-04-27T17:21:10+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.archae.me,2017-04-27:noticeId=760881:objectType=note - - <span class='greentext'>&gt;QuakeC</span> - - http://activitystrea.ms/schema/1.0/post - 2017-04-27T17:15:13+00:00 - 2017-04-27T17:15:13+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.archae.me/user/4687 - shpbot - - - - - - shpbot - shpbot - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.archae.me,2017-04-27:noticeId=760881:objectType=note - New note by shpbot - <span class='greentext'>&gt;QuakeC</span> - - - - - https://gs.archae.me/conversation/318362 - - - https://gs.archae.me/api/statuses/user_timeline/4687.atom - shpbot - - - https://social.heldscal.la/avatar/31581-original-20170405170019.jpeg - 2017-05-05T11:45:08+00:00 - - - - https://gs.archae.me/conversation/318362 - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:23226:2017-04-27T17:20:48+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:20:48 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="http://quitter.se/taknamay">Internet Turtle Ⓐ 🏴 ✅</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:20:48+00:00 - 2017-04-27T17:20:48+00:00 - - http://activitystrea.ms/schema/1.0/person - http://quitter.se/user/115823 - Internet Turtle Ⓐ 🏴 ✅ - Scheme programmer, Novice esperantist, Spiritual naturalist - Will listen to your problems for free - XMPP: DarkDungeons94 at chatme.im - - - - - - taknamay - Internet Turtle Ⓐ 🏴 ✅ - Scheme programmer, Novice esperantist, Spiritual naturalist - Will listen to your problems for free - XMPP: DarkDungeons94 at chatme.im - - New Jersey, United States - - - homepage - https://quitter.se/taknamay - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=a66b1fb22020c152 - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:29302:2017-04-27T17:20:33+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:20:33 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://icosahedron.website/@Trev">Chillidan Stormrave</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:20:33+00:00 - 2017-04-27T17:20:33+00:00 - - http://activitystrea.ms/schema/1.0/person - https://icosahedron.website/users/Trev - Trev Prime - web tech, music, ethics. radical individualist. kinda queer. love thy neighbor. always open for conversation. - - - - - - trev - Trev Prime - web tech, music, ethics. radical individualist. kinda queer. love thy neighbor. always open for conversation. - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=781c05bd64ad9520 - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:29367:2017-04-27T17:20:27+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:20:27 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://gs.kawa-kun.com/aya">射命丸 文</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:20:27+00:00 - 2017-04-27T17:20:27+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.kawa-kun.com/user/4885 - 射命丸 文 - Traditional Reporter of Fantasy - - - - - - aya - 射命丸 文 - Traditional Reporter of Fantasy - - Gensōkyō - - - homepage - https://danbooru.donmai.us - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=5921da7a934e47ca - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:27773:2017-04-27T17:20:18+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:20:18 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://gs.smuglo.li/japananon">JapanAnon</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:20:18+00:00 - 2017-04-27T17:20:18+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.smuglo.li/user/27299 - JapanAnon - 匿名でしていてね! - - - - - - japananon - JapanAnon - 匿名でしていてね! - - ワイヤード - - - homepage - http://www.anonymous-japan.org - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=ae3d819865886cba - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:36560:2017-04-27T17:19:30+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:19:30 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://shitposter.club/wareya">wareya</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:19:30+00:00 - 2017-04-27T17:19:30+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/15439 - wareya - Who are you to defy such a perfect being that is the machine? 日本語難しいけど頑張るぜ github.com/wareya wareya.moe Short: reya or war, never "ware" - - - - - - wareya - wareya - Who are you to defy such a perfect being that is the machine? 日本語難しいけど頑張るぜ github.com/wareya wareya.moe Short: reya or war, never "ware" - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=bd88a3cd20b5a418 - - - - - - - tag:social.heldscal.la,2017-04-27:subscription:29191:person:41176:2017-04-27T17:19:21+00:00 - shp (shp@social.heldscal.la)'s status on Thursday, 27-Apr-2017 17:19:21 UTC - <a href="https://social.heldscal.la/shp">shp</a> started following <a href="https://hakui.club/takeshitakenji">竹下憲二 (白)</a>. - - http://activitystrea.ms/schema/1.0/follow - 2017-04-27T17:19:21+00:00 - 2017-04-27T17:19:21+00:00 - - http://activitystrea.ms/schema/1.0/person - https://hakui.club/user/6 - 竹下憲二 (白) - Oh boy. - - - - - - takeshitakenji - 竹下憲二 (白) - Oh boy. - - Seattle, WA - - - homepage - http://gs.kawa-kun.com - true - - - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=b139a673deba6963 - - - - - - - tag:social.heldscal.la,2017-04-27:fave:29191:note:1932205:2017-04-27T17:17:46+00:00 - Favorite - shp favorited something by dolus: Looks like Merry is pussing out and caving to pressure. Sad. <a href="https://gs.smuglo.li/file/23e37de3c321248d3f322d8ec042372914568ab4c9431a94e568a61b8146587f.png" title="https://gs.smuglo.li/file/23e37de3c321248d3f322d8ec042372914568ab4c9431a94e568a61b8146587f.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432294</a> <a href="https://gs.smuglo.li/file/e5a9549a19986d59d51750090910f47c186787adf02b2b6ac58df37556887297.png" title="https://gs.smuglo.li/file/e5a9549a19986d59d51750090910f47c186787adf02b2b6ac58df37556887297.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432295</a> <a href="https://gs.smuglo.li/file/2fdfabbc8ab0b8dc135903a8c48c29b440d1f97446b98ced4ad14a54d3b5d41f.png" title="https://gs.smuglo.li/file/2fdfabbc8ab0b8dc135903a8c48c29b440d1f97446b98ced4ad14a54d3b5d41f.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432296</a> <a href="https://gs.smuglo.li/file/af605d7c6fe3a8c26c6d334c2a8e0005f7e86a266f14a5b3755e7d3ac4e226de.png" title="https://gs.smuglo.li/file/af605d7c6fe3a8c26c6d334c2a8e0005f7e86a266f14a5b3755e7d3ac4e226de.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432297</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-27T17:17:46+00:00 - 2017-04-27T17:17:46+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.smuglo.li,2017-04-27:noticeId=2065465:objectType=note - New note by dolus - Looks like Merry is pussing out and caving to pressure. Sad. <a href="https://gs.smuglo.li/file/23e37de3c321248d3f322d8ec042372914568ab4c9431a94e568a61b8146587f.png" title="https://gs.smuglo.li/file/23e37de3c321248d3f322d8ec042372914568ab4c9431a94e568a61b8146587f.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432294</a> <a href="https://gs.smuglo.li/file/e5a9549a19986d59d51750090910f47c186787adf02b2b6ac58df37556887297.png" title="https://gs.smuglo.li/file/e5a9549a19986d59d51750090910f47c186787adf02b2b6ac58df37556887297.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432295</a> <a href="https://gs.smuglo.li/file/2fdfabbc8ab0b8dc135903a8c48c29b440d1f97446b98ced4ad14a54d3b5d41f.png" title="https://gs.smuglo.li/file/2fdfabbc8ab0b8dc135903a8c48c29b440d1f97446b98ced4ad14a54d3b5d41f.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432296</a> <a href="https://gs.smuglo.li/file/af605d7c6fe3a8c26c6d334c2a8e0005f7e86a266f14a5b3755e7d3ac4e226de.png" title="https://gs.smuglo.li/file/af605d7c6fe3a8c26c6d334c2a8e0005f7e86a266f14a5b3755e7d3ac4e226de.png" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432297</a> - - - - - - - https://gs.smuglo.li/conversation/927473 - - - - - - - tag:social.heldscal.la,2017-04-27:fave:29191:note:1932492:2017-04-27T17:13:55+00:00 - Favorite - shp favorited something by zemichi: <a href="https://gs.smuglo.li/file/1d45ea4ffc95f15037f361b56ad6b89f8451b70ad1ff7a03b7bb0345b8e2227c.jpg" title="https://gs.smuglo.li/file/1d45ea4ffc95f15037f361b56ad6b89f8451b70ad1ff7a03b7bb0345b8e2227c.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432344</a><br /> that's a lot of loli - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-27T17:13:55+00:00 - 2017-04-27T17:13:55+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.smuglo.li,2017-04-27:noticeId=2065713:objectType=note - New note by zemichi - <a href="https://gs.smuglo.li/file/1d45ea4ffc95f15037f361b56ad6b89f8451b70ad1ff7a03b7bb0345b8e2227c.jpg" title="https://gs.smuglo.li/file/1d45ea4ffc95f15037f361b56ad6b89f8451b70ad1ff7a03b7bb0345b8e2227c.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432344</a><br /> that's a lot of loli - - - - - - - https://gs.smuglo.li/conversation/927673 - - - - - - - tag:social.heldscal.la,2017-04-27:fave:29191:note:1932559:2017-04-27T17:12:46+00:00 - Favorite - shp favorited something by gsimg: <a href="https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg" title="https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg" rel="nofollow noreferrer" class="attachment">https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg</a> #<span class="tag"><a href="https://gs.kawa-kun.com/tag/nsfw" rel="tag">nsfw</a></span> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-27T17:12:46+00:00 - 2017-04-27T17:12:46+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.kawa-kun.com,2017-04-27:noticeId=1608309:objectType=note - New note by gsimg - <a href="https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg" title="https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg" rel="nofollow noreferrer" class="attachment">https://gs.kawa-kun.com/file/3435c5cafda46f31cad5abb5837b3521b7b458198507073a496f4d10bad3633b.jpg</a> #<span class="tag"><a href="https://gs.kawa-kun.com/tag/nsfw" rel="tag">nsfw</a></span> - - - - - - - https://gs.kawa-kun.com/conversation/690817 - - - - - - - tag:social.heldscal.la,2017-04-27:fave:29191:note:1932601:2017-04-27T17:12:28+00:00 - Favorite - shp favorited something by zemichi: <a href="https://gs.smuglo.li/file/5d9114fafea7b9866c9d852bcfeaf66aade65ae26149758346bc5ade7e3fa8f0.jpg" title="https://gs.smuglo.li/file/5d9114fafea7b9866c9d852bcfeaf66aade65ae26149758346bc5ade7e3fa8f0.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432372</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-27T17:12:28+00:00 - 2017-04-27T17:12:28+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:gs.smuglo.li,2017-04-27:noticeId=2065821:objectType=note - New note by zemichi - <a href="https://gs.smuglo.li/file/5d9114fafea7b9866c9d852bcfeaf66aade65ae26149758346bc5ade7e3fa8f0.jpg" title="https://gs.smuglo.li/file/5d9114fafea7b9866c9d852bcfeaf66aade65ae26149758346bc5ade7e3fa8f0.jpg" rel="nofollow noreferrer" class="attachment">https://gs.smuglo.li/attachment/432372</a> - - - - - - - https://gs.smuglo.li/conversation/927760 - - - - - - - tag:social.heldscal.la,2017-04-27:noticeId=1932867:objectType=note - shp repeated a notice by shpbot - RT @<a href="https://gs.archae.me/user/4687" class="h-card u-url p-nickname mention" title="shpbot">shpbot</a> <a href="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" title="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-237676">https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg</a> #<span class="tag"><a href="https://social.heldscal.la/tag/2hu" rel="tag">2hu</a></span> #<span class="tag"><a href="https://social.heldscal.la/tag/ordinarymagician" rel="tag">ordinarymagician</a></span> :thinking: <a href="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" title="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-312306">https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg</a> - - http://activitystrea.ms/schema/1.0/share - 2017-04-27T17:11:35+00:00 - 2017-04-27T17:11:35+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.archae.me,2017-04-27:noticeId=760830:objectType=note - - <a href="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" title="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg</a> #<span class="tag"><a href="https://gs.archae.me/tag/2hu" rel="tag">2hu</a></span> #<span class="tag"><a href="https://gs.archae.me/tag/ordinarymagician" rel="tag">ordinarymagician</a></span> :thinking: <a href="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" title="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg</a> - - http://activitystrea.ms/schema/1.0/post - 2017-04-27T17:00:08+00:00 - 2017-04-27T17:00:08+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.archae.me/user/4687 - shpbot - - - - - - shpbot - shpbot - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.archae.me,2017-04-27:noticeId=760830:objectType=note - New note by shpbot - <a href="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" title="https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/cbf7fbbee1127a9870e871305ca7de70f1eb1bbb79ffe5b3b0f33e37514d14d8.jpg</a> #<span class="tag"><a href="https://gs.archae.me/tag/2hu" rel="tag">2hu</a></span> #<span class="tag"><a href="https://gs.archae.me/tag/ordinarymagician" rel="tag">ordinarymagician</a></span> :thinking: <a href="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" title="https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/abf3f82d9ce28d2293d858af26c75bb5d4fdd091c0d90ca7bc72ea7efba220e4.jpg</a> - - - - - https://gs.archae.me/conversation/318317 - - - - - https://gs.archae.me/api/statuses/user_timeline/4687.atom - shpbot - - - https://social.heldscal.la/avatar/31581-original-20170405170019.jpeg - 2017-05-05T11:45:08+00:00 - - - - https://gs.archae.me/conversation/318317 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-27:noticeId=1932815:objectType=note - New note by shp - federation issues with SPC atm it seems - - - http://activitystrea.ms/schema/1.0/post - 2017-04-27T17:08:55+00:00 - 2017-04-27T17:08:55+00:00 - - tag:social.heldscal.la,2017-04-27:objectType=thread:nonce=645a13c841f51769 - - - - - - - tag:social.heldscal.la,2017-04-26:fave:29191:note:1907285:2017-04-26T06:59:07+00:00 - Favorite - shp favorited something by lambadalambda: Is this the most offensive video on the net? <a href="https://social.heldscal.la/file/4c34bfb81a8155c265031bc48f7e69c29eb0d2941c57daf63f80e17b0e2e5f47.webm" title="https://social.heldscal.la/file/4c34bfb81a8155c265031bc48f7e69c29eb0d2941c57daf63f80e17b0e2e5f47.webm" rel="nofollow noreferrer" class="attachment">https://social.heldscal.la/attachment/402251</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-26T06:59:07+00:00 - 2017-04-26T06:59:07+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-26:noticeId=1907285:objectType=note - New note by lambadalambda - Is this the most offensive video on the net? <a href="https://social.heldscal.la/file/4c34bfb81a8155c265031bc48f7e69c29eb0d2941c57daf63f80e17b0e2e5f47.webm" title="https://social.heldscal.la/file/4c34bfb81a8155c265031bc48f7e69c29eb0d2941c57daf63f80e17b0e2e5f47.webm" rel="nofollow external noreferrer" class="attachment" id="attachment-402251">https://social.heldscal.la/attachment/402251</a> - - - - - - - tag:social.heldscal.la,2017-04-26:objectType=thread:nonce=07b02e1328f456af - - - - - - - tag:social.heldscal.la,2017-04-26:noticeId=1907951:objectType=note - shp repeated a notice by shpbot - RT @<a href="https://gs.archae.me/user/4687" class="h-card u-url p-nickname mention" title="shpbot">shpbot</a> <a href="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" title="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" rel="nofollow external noreferrer" class="attachment" id="attachment-346198">https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg</a> - - http://activitystrea.ms/schema/1.0/share - 2017-04-26T06:58:19+00:00 - 2017-04-26T06:58:19+00:00 - - http://activitystrea.ms/schema/1.0/activity - tag:gs.archae.me,2017-04-26:noticeId=752596:objectType=note - - <a href="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" title="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg</a> - - http://activitystrea.ms/schema/1.0/post - 2017-04-26T06:15:07+00:00 - 2017-04-26T06:15:07+00:00 - - http://activitystrea.ms/schema/1.0/person - https://gs.archae.me/user/4687 - shpbot - - - - - - shpbot - shpbot - - - - http://activitystrea.ms/schema/1.0/note - tag:gs.archae.me,2017-04-26:noticeId=752596:objectType=note - New note by shpbot - <a href="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" title="https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg" rel="nofollow noreferrer" class="attachment">https://shitposter.club/file/718db06b564841331c72f9df767f8c9459e20c4dddbf0d4e61cd08ecbee7739d.jpg</a> - - - - - https://gs.archae.me/conversation/314010 - - - https://gs.archae.me/api/statuses/user_timeline/4687.atom - shpbot - - - https://social.heldscal.la/avatar/31581-original-20170405170019.jpeg - 2017-05-05T11:45:08+00:00 - - - - https://gs.archae.me/conversation/314010 - - - - - - - tag:social.heldscal.la,2017-04-26:fave:29191:note:1907341:2017-04-26T06:58:16+00:00 - Favorite - shp favorited something by moonman: <a href="https://shitposter.club/file/1377b0894e983599c11e739e406243cabed9f8af7961a2550ecaf97e32de8e60.jpg" title="https://shitposter.club/file/1377b0894e983599c11e739e406243cabed9f8af7961a2550ecaf97e32de8e60.jpg" class="attachment" rel="nofollow">https://shitposter.club/attachment/630989</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-26T06:58:16+00:00 - 2017-04-26T06:58:16+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2017-04-26:noticeId=2681941:objectType=note - New note by moonman - <a href="https://shitposter.club/file/1377b0894e983599c11e739e406243cabed9f8af7961a2550ecaf97e32de8e60.jpg" title="https://shitposter.club/file/1377b0894e983599c11e739e406243cabed9f8af7961a2550ecaf97e32de8e60.jpg" class="attachment" rel="nofollow">https://shitposter.club/attachment/630989</a> - - - - - - - https://shitposter.club/conversation/1300990 - - - - - - - tag:social.heldscal.la,2017-04-26:fave:29191:comment:1907412:2017-04-26T06:57:56+00:00 - Favorite - shp favorited something by lambadalambda: @<a href="https://gs.smuglo.li/user/2" class="h-card u-url p-nickname mention" title="nepfag">nepfag</a> <a href="https://cherubini.casa/why-i-shut-down-wizards-town-and-left-mastodon-6d4e631346b3?source=linkShare-89c2f851e979-1493184822&amp;gi=a6a47c5466a0" title="https://cherubini.casa/why-i-shut-down-wizards-town-and-left-mastodon-6d4e631346b3?source=linkShare-89c2f851e979-1493184822&amp;gi=a6a47c5466a0" rel="nofollow noreferrer" class="attachment">https://social.heldscal.la/url/402273</a> - - http://activitystrea.ms/schema/1.0/favorite - 2017-04-26T06:57:56+00:00 - 2017-04-26T06:57:56+00:00 - - http://activitystrea.ms/schema/1.0/comment - tag:social.heldscal.la,2017-04-26:noticeId=1907412:objectType=comment - New comment by lambadalambda - @<a href="https://gs.smuglo.li/user/2" class="h-card u-url p-nickname mention" title="nepfag">nepfag</a> <a href="https://cherubini.casa/why-i-shut-down-wizards-town-and-left-mastodon-6d4e631346b3?source=linkShare-89c2f851e979-1493184822&amp;gi=a6a47c5466a0" title="https://cherubini.casa/why-i-shut-down-wizards-town-and-left-mastodon-6d4e631346b3?source=linkShare-89c2f851e979-1493184822&amp;gi=a6a47c5466a0" rel="nofollow external noreferrer" class="attachment" id="attachment-402273">https://social.heldscal.la/url/402273</a> - - - - - - - tag:social.heldscal.la,2017-04-26:objectType=thread:nonce=85c21eda7aaa7259 - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:social.heldscal.la,2017-04-26:noticeId=1907942:objectType=note - New note by shp - #<span class="tag"><a href="https://social.heldscal.la/tag/cofe" rel="tag">cofe</a></span> time my friends <a href="https://social.heldscal.la/file/ec254b45b3a86ff74bc08bc7e065cb681d77cf7d4cedc9cdcf59e16adf311da3.png" title="https://social.heldscal.la/file/ec254b45b3a86ff74bc08bc7e065cb681d77cf7d4cedc9cdcf59e16adf311da3.png" rel="nofollow external noreferrer" class="attachment" id="attachment-402381">https://social.heldscal.la/attachment/402381</a> - - - http://activitystrea.ms/schema/1.0/post - 2017-04-26T06:57:18+00:00 - 2017-04-26T06:57:18+00:00 - - tag:social.heldscal.la,2017-04-26:objectType=thread:nonce=9c9d9373bccfaf70 - - - - - - - - diff --git a/test/fixtures/tesla_mock/sakamoto.atom b/test/fixtures/tesla_mock/sakamoto.atom deleted file mode 100644 index 648946795..000000000 --- a/test/fixtures/tesla_mock/sakamoto.atom +++ /dev/null @@ -1 +0,0 @@ -http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056New note by eal<a href='https://shitposter.club/user/5381'>@shpuld</a> <a href='https://pleroma.hjkos.com/users/hj'>@hj</a> IM NOT GAY DAD2017-08-04T12:51:26.130592Z2017-08-04T12:51:26.130592Zhttps://pleroma.hjkos.com/contexts/53093c74-2100-4bf4-aac6-66d1973d03efhttps://social.sakamoto.gq/users/ealhttp://activitystrea.ms/schema/1.0/personhttps://social.sakamoto.gq/users/ealeal坂本(・ヮ・)eal \ No newline at end of file diff --git a/test/fixtures/tesla_mock/sakamoto_eal_feed.atom b/test/fixtures/tesla_mock/sakamoto_eal_feed.atom deleted file mode 100644 index 9340d9038..000000000 --- a/test/fixtures/tesla_mock/sakamoto_eal_feed.atom +++ /dev/null @@ -1 +0,0 @@ -https://social.sakamoto.gq/users/eal/feed.atomeal's timeline2017-08-04T14:19:12.683854https://social.sakamoto.gq/users/ealhttp://activitystrea.ms/schema/1.0/personhttps://social.sakamoto.gq/users/ealeal坂本(・ヮ・)ealhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/b79a1721-23f3-45a5-9610-adb08c2afae5New note by ealHonestly, I like all smileys that are not emoji.2017-08-04T14:19:12.675999Z2017-08-04T14:19:12.675999Zhttps://social.sakamoto.gq/contexts/e05ede92-8db9-4963-8b8e-e71a5797d68fhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/45475bf3-2dfc-4d9e-8eae-1f4f86f48982New note by ealThen again, I like all smileys/emoticons that are not emoji.<br>2017-08-04T14:19:10.113373Z2017-08-04T14:19:10.113373Zhttps://social.sakamoto.gq/contexts/852d1605-4dcb-4ba7-9ba4-dfc37ed62fbchttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/8f8fd6d6-cc63-40c6-a5d0-1c0e4f919368New note by ealI love the russian-style smiley.2017-08-04T14:18:30.478552Z2017-08-04T14:18:30.478552Zhttps://social.sakamoto.gq/contexts/852d1605-4dcb-4ba7-9ba4-dfc37ed62fbchttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/6e69df95-f2ad-4b8e-af4a-e93ff93d64e1eal started following https://cybre.space/users/0x3Feal started following https://cybre.space/users/0x3F2017-08-04T14:17:24.942193Z2017-08-04T14:17:24.942193Zhttp://activitystrea.ms/schema/1.0/personhttps://cybre.space/users/0x3Fhttps://cybre.space/users/0x3Fhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/54c5e260-0185-4267-a2a6-f5dd9c76c2c9eal started following https://niu.moe/users/ryeeal started following https://niu.moe/users/rye2017-08-04T14:16:35.604739Z2017-08-04T14:16:35.604739Zhttp://activitystrea.ms/schema/1.0/personhttps://niu.moe/users/ryehttps://niu.moe/users/ryehttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/092ca863-19a8-416c-85d7-d3f23b3c0203eal started following https://mastodon.xyz/users/rafudesueal started following https://mastodon.xyz/users/rafudesu2017-08-04T14:16:10.993429Z2017-08-04T14:16:10.993429Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.xyz/users/rafudesuhttps://mastodon.xyz/users/rafudesuhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/be5cf702-b127-423b-a6be-5f78f01a4289eal started following https://gs.kawa-kun.com/user/2eal started following https://gs.kawa-kun.com/user/22017-08-04T14:15:41.804611Z2017-08-04T14:15:41.804611Zhttp://activitystrea.ms/schema/1.0/personhttps://gs.kawa-kun.com/user/2https://gs.kawa-kun.com/user/2http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/4951e2a1-9bae-4e87-8e98-e6d2f8a52338eal started following https://gs.kawa-kun.com/user/4885eal started following https://gs.kawa-kun.com/user/48852017-08-04T14:15:00.135352Z2017-08-04T14:15:00.135352Zhttp://activitystrea.ms/schema/1.0/personhttps://gs.kawa-kun.com/user/4885https://gs.kawa-kun.com/user/4885http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/cadf8745-b9ee-4f6c-af32-bfddb70e4607eal started following https://mastodon.social/users/Murassaeal started following https://mastodon.social/users/Murassa2017-08-04T14:14:36.339560Z2017-08-04T14:14:36.339560Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.social/users/Murassahttps://mastodon.social/users/Murassahttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/a52c9aab-f0e6-4ccb-8dd3-9f417e72a41ceal started following https://mastodon.social/users/rysiekeal started following https://mastodon.social/users/rysiek2017-08-04T14:13:04.061572Z2017-08-04T14:13:04.061572Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.social/users/rysiekhttps://mastodon.social/users/rysiekhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/738bc887-4cca-4b36-8c86-2b54d4c54732eal started following https://mastodon.hasameli.com/users/munineal started following https://mastodon.hasameli.com/users/munin2017-08-04T14:12:10.514155Z2017-08-04T14:12:10.514155Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.hasameli.com/users/muninhttps://mastodon.hasameli.com/users/muninhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/dc66ad5a-b776-4180-a8aa-e4c1bf7cb703eal started following https://cybre.space/users/nightpooleal started following https://cybre.space/users/nightpool2017-08-04T14:11:16.046148Z2017-08-04T14:11:16.046148Zhttp://activitystrea.ms/schema/1.0/personhttps://cybre.space/users/nightpoolhttps://cybre.space/users/nightpoolhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/9c5c00d7-3ce4-4c11-b965-dc5c2bda86c5New note by eal<a href='https://mastodon.zombocloud.com/users/staticsafe'>@staticsafe</a> privet )))2017-08-04T14:10:08.812247Z2017-08-04T14:10:08.812247Zhttps://social.sakamoto.gq/contexts/12a33823-0327-4c1c-a591-850ea79331b5http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/49798053-1f40-4a71-ad33-106e90630863eal started following https://social.homunyan.com/users/animeirleal started following https://social.homunyan.com/users/animeirl2017-08-04T14:09:44.904792Z2017-08-04T14:09:44.904792Zhttp://activitystrea.ms/schema/1.0/personhttps://social.homunyan.com/users/animeirlhttps://social.homunyan.com/users/animeirlhttp://activitystrea.ms/schema/1.0/favoritehttps://social.sakamoto.gq/activities/2d83a1c5-70a6-45d3-9b84-59d6a70fbb17New favorite by ealeal favorited something2017-08-04T14:07:27.210044Z2017-08-04T14:07:27.210044Zhttp://activitystrea.ms/schema/1.0/notehttps://pleroma.soykaf.com/objects/b831e52f-4ed4-438e-95b4-888897f64f09https://pleroma.hjkos.com/contexts/3ed48205-1e72-4e19-a618-89a0d2ca811ehttp://activitystrea.ms/schema/1.0/favoritehttps://social.sakamoto.gq/activities/06d28bed-544a-496b-8414-1c6d439273b5New favorite by ealeal favorited something2017-08-04T14:05:37.280200Z2017-08-04T14:05:37.280200Zhttp://activitystrea.ms/schema/1.0/notetag:toot-lab.reclaim.technology,2017-08-04:objectId=1166030:objectType=Statustag:p2px.me,2017-08-04:objectType=thread:nonce=f8bfc4d13db6ce91http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/72bf19d4-9ad4-4b2f-9cd0-f0d70f4e931beal started following https://mstdn.jp/users/nullkaleal started following https://mstdn.jp/users/nullkal2017-08-04T14:05:04.148904Z2017-08-04T14:05:04.148904Zhttp://activitystrea.ms/schema/1.0/personhttps://mstdn.jp/users/nullkalhttps://mstdn.jp/users/nullkalhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://social.sakamoto.gq/objects/b0e89515-7621-4e09-b23d-83e192324107New note by eal<a href='https://p2px.me/user/1'>@stitchxd</a> test also2017-08-04T14:04:38.699051Z2017-08-04T14:04:38.699051Ztag:p2px.me,2017-08-04:objectType=thread:nonce=f8bfc4d13db6ce91http://activitystrea.ms/schema/1.0/favoritehttps://social.sakamoto.gq/activities/d8d2006b-6b23-45d6-ba27-39d27587777dNew favorite by ealeal favorited something2017-08-04T14:04:32.106626Z2017-08-04T14:04:32.106626Zhttp://activitystrea.ms/schema/1.0/notetag:p2px.me,2017-08-04:noticeId=222109:objectType=notetag:p2px.me,2017-08-04:objectType=thread:nonce=f8bfc4d13db6ce91http://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://social.sakamoto.gq/activities/cb9db95d-ec27-41fa-bebd-5375fc13acb9eal started following https://mastodon.social/users/Gargroneal started following https://mastodon.social/users/Gargron2017-08-04T14:04:04.325531Z2017-08-04T14:04:04.325531Zhttp://activitystrea.ms/schema/1.0/personhttps://mastodon.social/users/Gargronhttps://mastodon.social/users/Gargron \ No newline at end of file diff --git a/test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed b/test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed deleted file mode 100644 index b24ef7ab6..000000000 --- a/test/fixtures/tesla_mock/shp@pleroma.soykaf.com.feed +++ /dev/null @@ -1 +0,0 @@ -https://pleroma.soykaf.com/users/shp/feed.atomshp's timeline2017-09-14T08:31:48.911686https://pleroma.soykaf.com/users/shphttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/shpshpshpcofeshphttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://pleroma.soykaf.com/activities/0b5f5ef2-020a-4f9e-a92b-a2bf21224644shp started following https://pleroma.soykaf.com/users/goozshp started following https://pleroma.soykaf.com/users/gooz2017-09-14T08:31:48.911226Z2017-09-14T08:31:48.911226Zhttp://activitystrea.ms/schema/1.0/personhttps://pleroma.soykaf.com/users/goozhttps://pleroma.soykaf.com/users/goozhttp://activitystrea.ms/schema/1.0/activityhttp://activitystrea.ms/schema/1.0/followhttps://pleroma.soykaf.com/activities/d928b7f7-dc10-478c-859b-cd604770da60shp started following https://niu.moe/users/xiaoyongmaoshp started following https://niu.moe/users/xiaoyongmao2017-09-14T08:16:52.674253Z2017-09-14T08:16:52.674253Zhttp://activitystrea.ms/schema/1.0/personhttps://niu.moe/users/xiaoyongmaohttps://niu.moe/users/xiaoyongmaohttp://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/3f5089b3-f1e5-47b6-8bfe-a9c4a860e724New favorite by shpshp favorited something2017-09-14T08:12:18.213055Z2017-09-14T08:12:18.213055Zhttp://activitystrea.ms/schema/1.0/notehttps://mastodon.xyz/users/Azurolu/statuses/8346804tag:mastodon.xyz,2017-09-14:objectId=3669709:objectType=Conversationhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/0def9b19-6b0f-44e0-96b3-543fa06a4010New note by shp<a href='https://niu.moe/users/Pasty'>@Pasty</a> I love the peach<br><a href="https://pleroma.soykaf.com/media/7e8bd209-dbd4-481a-a62c-d302d68df16d/__hinanawi_tenshi_touhou_drawn_by_e_o__8c6824f52dd494f6026607570179265f.jpg" class='attachment'>__hinanawi_tenshi_touhou_drawn_…</a>2017-09-14T08:12:04.367142Z2017-09-14T08:12:04.367142Ztag:niu.moe,2017-09-14:objectId=1660781:objectType=Conversationhttp://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/a4170edf-d273-4b82-931d-662aaf3872f3New favorite by shpshp favorited something2017-09-14T08:10:26.205104Z2017-09-14T08:10:26.205104Zhttp://activitystrea.ms/schema/1.0/notehttps://niu.moe/users/NekoiNemo/statuses/3210992tag:niu.moe,2017-09-14:objectId=1660761:objectType=Conversationhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/c50c47a0-fac5-4781-a7e6-f20e7226d5fcNew note by shp<a href='https://freezepeach.xyz/user/3458'>@hakui</a> <a href='https://pleroma.soykaf.com/users/lain'>@lain</a> you guys are forgetting the pancakes jeez2017-09-14T08:09:30.088418Z2017-09-14T08:09:30.088418Zhttps://pleroma.soykaf.com/contexts/ac9c98ee-3eca-4b4b-9620-64b5e85e2623http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/2af9f622-5986-483c-83a1-ac59a9035b50New favorite by shpshp favorited something2017-09-14T08:09:16.346235Z2017-09-14T08:09:16.346235Zhttp://activitystrea.ms/schema/1.0/notetag:freezepeach.xyz,2017-09-14:noticeId=3926191:objectType=commenthttps://pleroma.soykaf.com/contexts/ac9c98ee-3eca-4b4b-9620-64b5e85e2623http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/f52aad69-5828-4e0e-bb7b-f2f0869d3ff0New note by shp<a href='https://gs.smuglo.li/user/253'>@kro</a> I'll probs try some of those 2hu mangos2017-09-14T08:09:13.262835Z2017-09-14T08:09:13.262835Ztag:gs.smuglo.li,2017-09-14:objectType=thread:nonce=c4ac2016e07c4123http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/35743658-efee-46cf-9cdf-487b95709cd5New favorite by shpshp favorited something2017-09-14T08:09:00.517534Z2017-09-14T08:09:00.517534Zhttp://activitystrea.ms/schema/1.0/notetag:gs.smuglo.li,2017-09-14:noticeId=4113226:objectType=commenttag:gs.smuglo.li,2017-09-14:objectType=thread:nonce=c4ac2016e07c4123http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/22258ba8-58dc-4e09-b476-fe28d3307377New favorite by shpshp favorited something2017-09-14T08:08:38.087136Z2017-09-14T08:08:38.087136Zhttp://activitystrea.ms/schema/1.0/notehttps://pleroma.soykaf.com/objects/13d7809e-5dca-4117-8738-887759392f2chttps://pleroma.soykaf.com/contexts/ac9c98ee-3eca-4b4b-9620-64b5e85e2623http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/f56d640a-0dbd-48af-80b1-06d0dbd26774New note by shp<a href='https://social.sakamoto.gq/users/eal'>@eal</a> ...but neither does my phone<br><br>low brightness, very dark wallpaper (pic related, but even darker, couldn't find the actual version)<br><a href="https://pleroma.soykaf.com/media/6d1b8d57-80ae-41d6-bdea-58fea09ecdf4/phonewallpaper.png" class='attachment'>phonewallpaper.png</a>2017-09-14T08:07:23.081214Z2017-09-14T08:07:23.081214Zhttps://pleroma.soykaf.com/contexts/f4c5d56e-fc58-467b-a8a5-10515c012355http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/d313df1d-121c-4ab8-abd1-e6aedcf55cbdNew note by shp<a href='https://niu.moe/users/Pasty'>@Pasty</a> y-you too2017-09-14T07:55:26.153486Z2017-09-14T07:55:26.153486Ztag:niu.moe,2017-09-14:objectId=1660616:objectType=Conversationhttp://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/7b642424-4edb-48cc-8711-1eafb4745269New note by shp<a href='https://social.sakamoto.gq/users/eal'>@eal</a> bothers me more when sleeping, wore one for nearly 2 years2017-09-14T07:54:53.449227Z2017-09-14T07:54:53.449227Zhttps://pleroma.soykaf.com/contexts/f4c5d56e-fc58-467b-a8a5-10515c012355http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/5bc1bff1-88c3-489d-8efd-7e4755690a18New note by shpquick test2017-09-14T07:54:09.045525Z2017-09-14T07:54:09.045525Zhttps://pleroma.soykaf.com/contexts/cd770c2a-408e-4895-988c-60319298f219http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/956f1fb5-6f2f-433e-ab71-7f732b76f4beNew note by shphad some trouble getting sleep last night. only used phone to check the time a few times (v essential to have a near-black wallpaper to not blind yourself when you do that). can't rember the last time I rolled in the bed for longer than an hour like that2017-09-14T07:51:23.557775Z2017-09-14T07:51:23.557775Zhttps://pleroma.soykaf.com/contexts/f4c5d56e-fc58-467b-a8a5-10515c012355http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/d935d9f2-ebc7-4ff2-b65a-fbf418a60935New note by shp<a href='https://gs.smuglo.li/user/253'>@kro</a> doesn't sound like a bad idea at all2017-09-14T07:49:55.702555Z2017-09-14T07:49:55.702555Ztag:gs.smuglo.li,2017-09-14:objectType=thread:nonce=c4ac2016e07c4123http://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/342c8803-ee16-487d-9488-a39d763073f6New favorite by shpshp favorited something2017-09-14T07:49:41.875840Z2017-09-14T07:49:41.875840Zhttp://activitystrea.ms/schema/1.0/notetag:anticapitalist.party,2017-09-14:objectId=3322865:objectType=Statustag:anticapitalist.party,2017-09-14:objectId=1251751:objectType=Conversationhttp://activitystrea.ms/schema/1.0/favoritehttps://pleroma.soykaf.com/activities/5d98a19b-dd55-4077-9841-142937c613adNew favorite by shpshp favorited something2017-09-14T07:49:30.584265Z2017-09-14T07:49:30.584265Zhttp://activitystrea.ms/schema/1.0/notetag:gs.smuglo.li,2017-09-14:noticeId=4113170:objectType=commenttag:gs.smuglo.li,2017-09-14:objectType=thread:nonce=c4ac2016e07c4123http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/fdf3626a-50ba-458b-9bf7-b5f2cfa505fcNew note by shp<a href='https://pleroma.hjkos.com/users/hj'>@hj</a> c time2017-09-14T07:48:52.805422Z2017-09-14T07:48:52.805422Zhttps://pleroma.hjkos.com/contexts/dc4a3a3e-d366-4c0c-8789-8a9bee3537d9http://activitystrea.ms/schema/1.0/notehttp://activitystrea.ms/schema/1.0/posthttps://pleroma.soykaf.com/objects/c7c8eb17-b669-4827-9fbc-90f1fc54e4b1New note by shp<a href='https://sunshinegardens.org/users/tbny'>@tbny</a> err.. mediterranean from finnish*2017-09-14T07:46:52.764234Z2017-09-14T07:46:52.764234Zhttps://pleroma.soykaf.com/contexts/ac9c98ee-3eca-4b4b-9620-64b5e85e2623 \ No newline at end of file diff --git a/test/fixtures/tesla_mock/spc_5381.atom b/test/fixtures/tesla_mock/spc_5381.atom deleted file mode 100644 index c3288e97b..000000000 --- a/test/fixtures/tesla_mock/spc_5381.atom +++ /dev/null @@ -1,438 +0,0 @@ - - - GNU social - https://shitposter.club/api/statuses/user_timeline/5381.atom - shpuld timeline - Updates from shpuld on Shitposter Club! - https://shitposter.club/avatar/5381-96-20171230093854.png - 2018-02-23T13:42:22+00:00 - - http://activitystrea.ms/schema/1.0/person - https://shitposter.club/user/5381 - shpuld - - - - - - shpuld - shp - - - - - - - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:comment:7387801:2018-02-23T13:39:40+00:00 - Favorite - shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <a href="https://mstdn.io/media/_Ee-x91XN0udpfZVO_U" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/_Ee-x91XN0udpfZ</span><span class="invisible">VO_U</span></a></p> - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:39:40+00:00 - 2018-02-23T13:39:40+00:00 - - http://activitystrea.ms/schema/1.0/comment - https://mstdn.io/users/mayuutann/statuses/99574950785668071 - New comment by mayuutann - <p><span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <a href="https://mstdn.io/media/_Ee-x91XN0udpfZVO_U" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/_Ee-x91XN0udpfZ</span><span class="invisible">VO_U</span></a></p> - - - - - - - https://freezepeach.xyz/conversation/4182511 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387723:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://pleroma.soykaf.com/users/lain" class="h-card mention" title="&#x2468; lain &#x2468;">lain</a> how naive~ - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:30:15+00:00 - 2018-02-23T13:30:15+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=2f09acf104aebfe3 - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387703:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://pleroma.soykaf.com/users/lain" class="h-card mention" title="&#x2468; lain &#x2468;">lain</a> you expect anyone to believe that?? - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:28:08+00:00 - 2018-02-23T13:28:08+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=2f09acf104aebfe3 - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387639:objectType=comment - New comment by shpuld - @<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> pacyuri!! <a href="https://shitposter.club/file/eea140be45df3f993c4533026bf9a78fe8facd296d2fa0c6d02b2e347c5dc30e.jpg" title="https://shitposter.club/file/eea140be45df3f993c4533026bf9a78fe8facd296d2fa0c6d02b2e347c5dc30e.jpg" class="attachment" id="attachment-1589462" rel="nofollow external">https://shitposter.club/attachment/1589462</a> - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:20:38+00:00 - 2018-02-23T13:20:38+00:00 - - - - https://freezepeach.xyz/conversation/4183220 - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387611:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> why is pacyu eating a pizza so cute - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:18:07+00:00 - 2018-02-23T13:18:07+00:00 - - - - https://freezepeach.xyz/conversation/4183220 - - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:comment:7387600:2018-02-23T13:17:52+00:00 - Favorite - shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> pichu! <a href="https://mstdn.io/media/Crv5eubz1KO0dgBEulI" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/Crv5eubz1KO0dgB</span><span class="invisible">EulI</span></a></p> - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:17:52+00:00 - 2018-02-23T13:17:52+00:00 - - http://activitystrea.ms/schema/1.0/comment - https://mstdn.io/users/mayuutann/statuses/99574863865459283 - New comment by mayuutann - <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> <span class="h-card"><a href="https://gs.smuglo.li/histoire" class="u-url mention">@<span>histoire</span></a></span> <span class="h-card"><a href="https://freezepeach.xyz/hakui" class="u-url mention">@<span>hakui</span></a></span> pichu! <a href="https://mstdn.io/media/Crv5eubz1KO0dgBEulI" rel="nofollow"><span class="invisible">https://</span><span class="ellipsis">mstdn.io/media/Crv5eubz1KO0dgB</span><span class="invisible">EulI</span></a></p> - - - - - - - https://freezepeach.xyz/conversation/4182511 - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:comment:7387544:2018-02-23T13:12:43+00:00 - Favorite - shpuld favorited something by mayuutann: <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> wa~~i!! :blobcheer:</p> - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:12:43+00:00 - 2018-02-23T13:12:43+00:00 - - http://activitystrea.ms/schema/1.0/comment - https://mstdn.io/users/mayuutann/statuses/99574840290947233 - New comment by mayuutann - <p><span class="h-card"><a href="https://shitposter.club/shpuld" class="u-url mention">@<span>shpuld</span></a></span> wa~~i!! :blobcheer:</p> - - - - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387555:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> more!! - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:12:23+00:00 - 2018-02-23T13:12:23+00:00 - - - - https://freezepeach.xyz/conversation/4183220 - - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:note:7387537:2018-02-23T13:12:19+00:00 - Favorite - shpuld favorited something by hakui: you have pacyupacyu'd for: 45 minutes 03 seconds - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:12:19+00:00 - 2018-02-23T13:12:19+00:00 - - http://activitystrea.ms/schema/1.0/note - tag:freezepeach.xyz,2018-02-23:noticeId=6451332:objectType=note - New note by hakui - you have pacyupacyu'd for: 45 minutes 03 seconds - - - - - - - https://freezepeach.xyz/conversation/4183220 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387539:objectType=comment - New comment by shpuld - @<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> ndndnd~ - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:11:04+00:00 - 2018-02-23T13:11:04+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387518:objectType=comment - New comment by shpuld - @<a href="https://mstdn.io/users/mayuutann" class="h-card mention" title="Mayutan&#x2615;">mayuutann</a> well done! mayumayu is so energetic - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:08:50+00:00 - 2018-02-23T13:08:50+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=d05e2b056274c5ab - - - - - - - - tag:shitposter.club,2018-02-23:fave:5381:note:7387503:2018-02-23T13:08:00+00:00 - Favorite - shpuld favorited something by mayuutann: <p>done with FIGURE MAT!!<br /> (Posted with IFTTT)</p> - - http://activitystrea.ms/schema/1.0/favorite - 2018-02-23T13:08:00+00:00 - 2018-02-23T13:08:00+00:00 - - http://activitystrea.ms/schema/1.0/note - https://mstdn.io/users/mayuutann/statuses/99574825526201897 - New note by mayuutann - <p>done with FIGURE MAT!!<br /> (Posted with IFTTT)</p> - - - - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=c6aaa9b91e8d242f - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387486:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://a.weirder.earth/users/mutstd" class="h-card mention" title="Mutant Standard">mutstd</a> @<a href="https://donphan.social/users/Siphonay" class="h-card mention" title="Siphonay">siphonay</a> jokes on you I'm oppressively shitposting myself - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:05:44+00:00 - 2018-02-23T13:05:44+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=5d306467336c9661 - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387466:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> @<a href="https://a.weirder.earth/users/mutstd" class="h-card mention" title="Mutant Standard">mutstd</a> @<a href="https://donphan.social/users/Siphonay" class="h-card mention" title="Siphonay">siphonay</a> how does it feel being hostile - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:04:10+00:00 - 2018-02-23T13:04:10+00:00 - - - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=5d306467336c9661 - - - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387459:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> gorogoro - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:03:32+00:00 - 2018-02-23T13:03:32+00:00 - - - - https://freezepeach.xyz/conversation/4181784 - - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387432:objectType=comment - New comment by shpuld - @<a href="https://freezepeach.xyz/user/3458" class="h-card mention" title="&#x5FA1;&#x5712;&#x306F;&#x304F;&#x3044;">hakui</a> ndnd - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T13:02:05+00:00 - 2018-02-23T13:02:05+00:00 - - - - https://freezepeach.xyz/conversation/4181784 - - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2018-02-23:noticeId=7387367:objectType=note - New note by shpuld - dear diary: I'm trying to do work but I can only think of tenshi eating a corndog - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T12:56:03+00:00 - 2018-02-23T12:56:03+00:00 - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=57f316da416743fc - - - - - - - http://activitystrea.ms/schema/1.0/note - tag:shitposter.club,2018-02-23:noticeId=7387354:objectType=note - New note by shpuld - jesus christ it's such a fridey at work - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T12:53:50+00:00 - 2018-02-23T12:53:50+00:00 - - tag:shitposter.club,2018-02-23:objectType=thread:nonce=c05eb5e91bdcbdb7 - - - - - - - http://activitystrea.ms/schema/1.0/comment - tag:shitposter.club,2018-02-23:noticeId=7387343:objectType=comment - New comment by shpuld - @<a href="https://gs.smuglo.li/user/589" class="h-card mention" title="&#x16DE;&#x16A9;&#x16B3;&#x16C1;&#x16DE;&#x16A9;&#x16B3;&#x16C1;">dokidoki</a> give them free upgrades to krokodil - - - http://activitystrea.ms/schema/1.0/post - 2018-02-23T12:53:15+00:00 - 2018-02-23T12:53:15+00:00 - - - - https://gs.smuglo.li/conversation/3934774 - - - - - - - diff --git a/test/fixtures/tesla_mock/wedistribute-create-article.json b/test/fixtures/tesla_mock/wedistribute-create-article.json new file mode 100644 index 000000000..3cfef8b99 --- /dev/null +++ b/test/fixtures/tesla_mock/wedistribute-create-article.json @@ -0,0 +1 @@ +{"@context":["https:\/\/www.w3.org\/ns\/activitystreams"],"type":"Create","actor":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog","object":{"@context":["https:\/\/www.w3.org\/ns\/activitystreams"],"type":"Article","name":"The end is near: Mastodon plans to drop OStatus support","content":"\n

The days of OStatus are numbered. The venerable protocol has served as a glue between many different types of servers since the early days of the Fediverse, connecting StatusNet (now GNU Social) to Friendica, Hubzilla, Mastodon, and Pleroma.<\/p>\n\n\n\n

Now that many fediverse platforms support ActivityPub as a successor protocol, Mastodon appears to be drawing a line in the sand. In a Patreon update<\/a>, Eugen Rochko writes:<\/p>\n\n\n\n

...OStatus...has overstayed its welcome in the code...and now that most of the network uses ActivityPub, it's time for it to go. <\/p>Eugen Rochko, Mastodon creator<\/cite><\/blockquote>\n\n\n\n

The pull request<\/a> to remove Pubsubhubbub and Salmon, two of the main components of OStatus, has already been merged into Mastodon's master branch.<\/p>\n\n\n\n

Some projects will be left in the dark as a side effect of this. GNU Social and PostActiv, for example, both only communicate using OStatus. While some discussion<\/a> exists regarding adopting ActivityPub for GNU Social, and a plugin is in development<\/a>, it hasn't been formally adopted yet. We just hope that the Free Software Foundation's instance<\/a> gets updated in time!<\/p>\n","summary":"One of the largest platforms in the federated social web is dropping the protocol that it started with.","attributedTo":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog","url":"https:\/\/wedistribute.org\/2019\/07\/mastodon-drops-ostatus\/","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public","https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog\/followers"],"id":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810","likes":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810\/likes","shares":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810\/shares"},"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public","https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog\/followers"],"id":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85809"} \ No newline at end of file diff --git a/test/fixtures/unfollow.xml b/test/fixtures/unfollow.xml deleted file mode 100644 index 7a8f8fd2e..000000000 --- a/test/fixtures/unfollow.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - GNU social - https://social.heldscal.la/api/statuses/user_timeline/23211.atom - lambadalambda timeline - Updates from lambadalambda on social.heldscal.la! - https://social.heldscal.la/avatar/23211-96-20170416114255.jpeg - 2017-05-07T09:54:49+00:00 - - http://activitystrea.ms/schema/1.0/person - https://social.heldscal.la/user/23211 - lambadalambda -

Call me Deacon Blues. - - - - - - lambadalambda - Constance Variable - Call me Deacon Blues. - - Berlin - - - homepage - https://heldscal.la - true - - - - - - - - - - - - - undo:tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00 - Constance Variable (lambadalambda@social.heldscal.la)'s status on Sunday, 07-May-2017 09:54:49 UTC - <a href="https://social.heldscal.la/lambadalambda">Constance Variable</a> stopped following <a href="https://pawoo.net/@pekorino">mono</a>. - - http://activitystrea.ms/schema/1.0/unfollow - 2017-05-07T09:54:49+00:00 - 2017-05-07T09:54:49+00:00 - - http://activitystrea.ms/schema/1.0/person - https://pawoo.net/users/pekorino - mono - http://shitposter.club/mono 孤独のグルメ - - - - - pekorino - mono - http://shitposter.club/mono 孤独のグルメ - - - tag:social.heldscal.la,2017-05-07:objectType=thread:nonce=6e80caf94e03029f - - - - - - diff --git a/test/instance_static/emoji/blobs.gg/blank.png b/test/instance_static/emoji/blobs.gg/blank.png new file mode 100644 index 000000000..8f50fa023 Binary files /dev/null and b/test/instance_static/emoji/blobs.gg/blank.png differ diff --git a/test/instance_static/emoji/blobs.gg/pack.json b/test/instance_static/emoji/blobs.gg/pack.json new file mode 100644 index 000000000..481891b08 --- /dev/null +++ b/test/instance_static/emoji/blobs.gg/pack.json @@ -0,0 +1,11 @@ +{ + "files": { + "blank": "blank.png" + }, + "pack": { + "description": "Test description", + "homepage": "https://pleroma.social", + "license": "Test license", + "share-files": true + } +} \ No newline at end of file diff --git a/test/tasks/pleroma_test.exs b/test/mix/pleroma_test.exs similarity index 100% rename from test/tasks/pleroma_test.exs rename to test/mix/pleroma_test.exs diff --git a/test/tasks/app_test.exs b/test/mix/tasks/pleroma/app_test.exs similarity index 100% rename from test/tasks/app_test.exs rename to test/mix/tasks/pleroma/app_test.exs diff --git a/test/tasks/config_test.exs b/test/mix/tasks/pleroma/config_test.exs similarity index 93% rename from test/tasks/config_test.exs rename to test/mix/tasks/pleroma/config_test.exs index fb12e7fb3..f36648829 100644 --- a/test/tasks/config_test.exs +++ b/test/mix/tasks/pleroma/config_test.exs @@ -40,6 +40,19 @@ test "error if file with custom settings doesn't exist" do on_exit(fn -> Application.put_env(:quack, :level, initial) end) end + @tag capture_log: true + test "config migration refused when deprecated settings are found" do + clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"]) + assert Repo.all(ConfigDB) == [] + + Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") + + assert_received {:mix_shell, :error, [message]} + + assert message =~ + "Migration is not allowed until all deprecation warnings have been resolved." + end + test "filtered settings are migrated to db" do assert Repo.all(ConfigDB) == [] diff --git a/test/tasks/count_statuses_test.exs b/test/mix/tasks/pleroma/count_statuses_test.exs similarity index 100% rename from test/tasks/count_statuses_test.exs rename to test/mix/tasks/pleroma/count_statuses_test.exs diff --git a/test/tasks/database_test.exs b/test/mix/tasks/pleroma/database_test.exs similarity index 76% rename from test/tasks/database_test.exs rename to test/mix/tasks/pleroma/database_test.exs index 3a28aa133..292a5ef5f 100644 --- a/test/tasks/database_test.exs +++ b/test/mix/tasks/pleroma/database_test.exs @@ -3,14 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.DatabaseTest do + use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Activity alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI - use Pleroma.DataCase - import Pleroma.Factory setup_all do @@ -130,40 +131,45 @@ test "it turns OrderedCollection likes into empty arrays" do describe "ensure_expiration" do test "it adds to expiration old statuses" do - %{id: activity_id1} = insert(:note_activity) + activity1 = insert(:note_activity) - %{id: activity_id2} = - insert(:note_activity, %{inserted_at: NaiveDateTime.from_iso8601!("2015-01-23 23:50:07")}) + {:ok, inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z") + activity2 = insert(:note_activity, %{inserted_at: inserted_at}) - %{id: activity_id3} = activity3 = insert(:note_activity) + %{id: activity_id3} = insert(:note_activity) - expires_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(60 * 61, :second) - |> NaiveDateTime.truncate(:second) + expires_at = DateTime.add(DateTime.utc_now(), 60 * 61) - Pleroma.ActivityExpiration.create(activity3, expires_at) + Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ + activity_id: activity_id3, + expires_at: expires_at + }) Mix.Tasks.Pleroma.Database.run(["ensure_expiration"]) - expirations = - Pleroma.ActivityExpiration - |> order_by(:activity_id) - |> Repo.all() + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity1.id}, + scheduled_at: + activity1.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: 365) + ) - assert [ - %Pleroma.ActivityExpiration{ - activity_id: ^activity_id1 - }, - %Pleroma.ActivityExpiration{ - activity_id: ^activity_id2, - scheduled_at: ~N[2016-01-23 23:50:07] - }, - %Pleroma.ActivityExpiration{ - activity_id: ^activity_id3, - scheduled_at: ^expires_at - } - ] = expirations + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity2.id}, + scheduled_at: + activity2.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: 365) + ) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity_id3}, + scheduled_at: expires_at + ) end end end diff --git a/test/tasks/digest_test.exs b/test/mix/tasks/pleroma/digest_test.exs similarity index 89% rename from test/tasks/digest_test.exs rename to test/mix/tasks/pleroma/digest_test.exs index 0b444c86d..69dccb745 100644 --- a/test/tasks/digest_test.exs +++ b/test/mix/tasks/pleroma/digest_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.DigestTest do use Pleroma.DataCase diff --git a/test/tasks/ecto/migrate_test.exs b/test/mix/tasks/pleroma/ecto/migrate_test.exs similarity index 100% rename from test/tasks/ecto/migrate_test.exs rename to test/mix/tasks/pleroma/ecto/migrate_test.exs diff --git a/test/tasks/ecto/rollback_test.exs b/test/mix/tasks/pleroma/ecto/rollback_test.exs similarity index 100% rename from test/tasks/ecto/rollback_test.exs rename to test/mix/tasks/pleroma/ecto/rollback_test.exs diff --git a/test/tasks/ecto/ecto_test.exs b/test/mix/tasks/pleroma/ecto_test.exs similarity index 100% rename from test/tasks/ecto/ecto_test.exs rename to test/mix/tasks/pleroma/ecto_test.exs diff --git a/test/mix/tasks/pleroma/email_test.exs b/test/mix/tasks/pleroma/email_test.exs new file mode 100644 index 000000000..9523aefd8 --- /dev/null +++ b/test/mix/tasks/pleroma/email_test.exs @@ -0,0 +1,127 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.EmailTest do + use Pleroma.DataCase + + import Swoosh.TestAssertions + + alias Pleroma.Config + alias Pleroma.Tests.ObanHelpers + + import Pleroma.Factory + + setup_all do + Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(Mix.Shell.IO) + end) + + :ok + end + + setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true) + setup do: clear_config([:instance, :account_activation_required], true) + + describe "pleroma.email test" do + test "Sends test email with no given address" do + mail_to = Config.get([:instance, :email]) + + :ok = Mix.Tasks.Pleroma.Email.run(["test"]) + + ObanHelpers.perform_all() + + assert_receive {:mix_shell, :info, [message]} + assert message =~ "Test email has been sent" + + assert_email_sent( + to: mail_to, + html_body: ~r/a test email was requested./i + ) + end + + test "Sends test email with given address" do + mail_to = "hewwo@example.com" + + :ok = Mix.Tasks.Pleroma.Email.run(["test", "--to", mail_to]) + + ObanHelpers.perform_all() + + assert_receive {:mix_shell, :info, [message]} + assert message =~ "Test email has been sent" + + assert_email_sent( + to: mail_to, + html_body: ~r/a test email was requested./i + ) + end + + test "Sends confirmation emails" do + local_user1 = + insert(:user, %{ + confirmation_pending: true, + confirmation_token: "mytoken", + deactivated: false, + email: "local1@pleroma.com", + local: true + }) + + local_user2 = + insert(:user, %{ + confirmation_pending: true, + confirmation_token: "mytoken", + deactivated: false, + email: "local2@pleroma.com", + local: true + }) + + :ok = Mix.Tasks.Pleroma.Email.run(["resend_confirmation_emails"]) + + ObanHelpers.perform_all() + + assert_email_sent(to: {local_user1.name, local_user1.email}) + assert_email_sent(to: {local_user2.name, local_user2.email}) + end + + test "Does not send confirmation email to inappropriate users" do + # confirmed user + insert(:user, %{ + confirmation_pending: false, + confirmation_token: "mytoken", + deactivated: false, + email: "confirmed@pleroma.com", + local: true + }) + + # remote user + insert(:user, %{ + deactivated: false, + email: "remote@not-pleroma.com", + local: false + }) + + # deactivated user = + insert(:user, %{ + deactivated: true, + email: "deactivated@pleroma.com", + local: false + }) + + # invisible user + insert(:user, %{ + deactivated: false, + email: "invisible@pleroma.com", + local: true, + invisible: true + }) + + :ok = Mix.Tasks.Pleroma.Email.run(["resend_confirmation_emails"]) + + ObanHelpers.perform_all() + + refute_email_sent() + end + end +end diff --git a/test/tasks/emoji_test.exs b/test/mix/tasks/pleroma/emoji_test.exs similarity index 97% rename from test/tasks/emoji_test.exs rename to test/mix/tasks/pleroma/emoji_test.exs index 499f098c2..0fb8603ac 100644 --- a/test/tasks/emoji_test.exs +++ b/test/mix/tasks/pleroma/emoji_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Mix.Tasks.Pleroma.EmojiTest do use ExUnit.Case, async: true diff --git a/test/tasks/frontend_test.exs b/test/mix/tasks/pleroma/frontend_test.exs similarity index 83% rename from test/tasks/frontend_test.exs rename to test/mix/tasks/pleroma/frontend_test.exs index 0ca2b9a28..6f9ec14cd 100644 --- a/test/tasks/frontend_test.exs +++ b/test/mix/tasks/pleroma/frontend_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.FrontendTest do +defmodule Mix.Tasks.Pleroma.FrontendTest do use Pleroma.DataCase alias Mix.Tasks.Pleroma.Frontend @@ -48,11 +48,18 @@ test "it also works given a file" do } }) + folder = Path.join([@dir, "frontends", "pleroma", "fantasy"]) + previously_existing = Path.join([folder, "temp"]) + File.mkdir_p!(folder) + File.write!(previously_existing, "yey") + assert File.exists?(previously_existing) + capture_io(fn -> Frontend.run(["install", "pleroma", "--file", "test/fixtures/tesla_mock/frontend.zip"]) end) - assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"])) + assert File.exists?(Path.join([folder, "test.txt"])) + refute File.exists?(previously_existing) end test "it downloads and unzips unknown frontends" do diff --git a/test/tasks/instance_test.exs b/test/mix/tasks/pleroma/instance_test.exs similarity index 90% rename from test/tasks/instance_test.exs rename to test/mix/tasks/pleroma/instance_test.exs index 3b4c041d9..8a02710ee 100644 --- a/test/tasks/instance_test.exs +++ b/test/mix/tasks/pleroma/instance_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.InstanceTest do +defmodule Mix.Tasks.Pleroma.InstanceTest do use ExUnit.Case setup do @@ -63,7 +63,13 @@ test "running gen" do "--uploads-dir", "test/uploads", "--static-dir", - "./test/../test/instance/static/" + "./test/../test/instance/static/", + "--strip-uploads", + "y", + "--dedupe-uploads", + "n", + "--anonymize-uploads", + "n" ]) end @@ -82,6 +88,7 @@ test "running gen" do assert generated_config =~ "password: \"dbpass\"" assert generated_config =~ "configurable_from_database: true" assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]" + assert generated_config =~ "filters: [Pleroma.Upload.Filter.ExifTool]" assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql() assert File.exists?(Path.expand("./test/instance/static/robots.txt")) end diff --git a/test/tasks/refresh_counter_cache_test.exs b/test/mix/tasks/pleroma/refresh_counter_cache_test.exs similarity index 100% rename from test/tasks/refresh_counter_cache_test.exs rename to test/mix/tasks/pleroma/refresh_counter_cache_test.exs diff --git a/test/tasks/relay_test.exs b/test/mix/tasks/pleroma/relay_test.exs similarity index 57% rename from test/tasks/relay_test.exs rename to test/mix/tasks/pleroma/relay_test.exs index e5225b64c..cf48e7dda 100644 --- a/test/tasks/relay_test.exs +++ b/test/mix/tasks/pleroma/relay_test.exs @@ -81,6 +81,80 @@ test "relay is unfollowed" do assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"] refute "#{target_instance}/followers" in User.following(local_user) end + + test "unfollow when relay is dead" do + user = insert(:user) + target_instance = user.ap_id + + Mix.Tasks.Pleroma.Relay.run(["follow", target_instance]) + + %User{ap_id: follower_id} = local_user = Relay.get_actor() + target_user = User.get_cached_by_ap_id(target_instance) + follow_activity = Utils.fetch_latest_follow(local_user, target_user) + User.follow(local_user, target_user) + + assert "#{target_instance}/followers" in User.following(local_user) + + Tesla.Mock.mock(fn %{method: :get, url: ^target_instance} -> + %Tesla.Env{status: 404} + end) + + Pleroma.Repo.delete(user) + Cachex.clear(:user_cache) + + Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance]) + + cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"]) + assert cancelled_activity.data["state"] == "accept" + + assert [] == + ActivityPub.fetch_activities( + [], + %{ + type: "Undo", + actor_id: follower_id, + skip_preload: true, + invisible_actors: true + } + ) + end + + test "force unfollow when relay is dead" do + user = insert(:user) + target_instance = user.ap_id + + Mix.Tasks.Pleroma.Relay.run(["follow", target_instance]) + + %User{ap_id: follower_id} = local_user = Relay.get_actor() + target_user = User.get_cached_by_ap_id(target_instance) + follow_activity = Utils.fetch_latest_follow(local_user, target_user) + User.follow(local_user, target_user) + + assert "#{target_instance}/followers" in User.following(local_user) + + Tesla.Mock.mock(fn %{method: :get, url: ^target_instance} -> + %Tesla.Env{status: 404} + end) + + Pleroma.Repo.delete(user) + Cachex.clear(:user_cache) + + Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance, "--force"]) + + cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"]) + assert cancelled_activity.data["state"] == "cancelled" + + [undo_activity] = + ActivityPub.fetch_activities( + [], + %{type: "Undo", actor_id: follower_id, skip_preload: true, invisible_actors: true} + ) + + assert undo_activity.data["type"] == "Undo" + assert undo_activity.data["actor"] == local_user.ap_id + assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"] + refute "#{target_instance}/followers" in User.following(local_user) + end end describe "mix pleroma.relay list" do diff --git a/test/tasks/robots_txt_test.exs b/test/mix/tasks/pleroma/robots_txt_test.exs similarity index 100% rename from test/tasks/robots_txt_test.exs rename to test/mix/tasks/pleroma/robots_txt_test.exs diff --git a/test/tasks/uploads_test.exs b/test/mix/tasks/pleroma/uploads_test.exs similarity index 100% rename from test/tasks/uploads_test.exs rename to test/mix/tasks/pleroma/uploads_test.exs diff --git a/test/tasks/user_test.exs b/test/mix/tasks/pleroma/user_test.exs similarity index 88% rename from test/tasks/user_test.exs rename to test/mix/tasks/pleroma/user_test.exs index ce43a9cc7..ce819f815 100644 --- a/test/tasks/user_test.exs +++ b/test/mix/tasks/pleroma/user_test.exs @@ -225,47 +225,69 @@ test "no user to deactivate" do test "All statuses set" do user = insert(:user) - Mix.Tasks.Pleroma.User.run(["set", user.nickname, "--moderator", "--admin", "--locked"]) + Mix.Tasks.Pleroma.User.run([ + "set", + user.nickname, + "--admin", + "--confirmed", + "--locked", + "--moderator" + ]) assert_received {:mix_shell, :info, [message]} - assert message =~ ~r/Moderator status .* true/ + assert message =~ ~r/Admin status .* true/ + + assert_received {:mix_shell, :info, [message]} + assert message =~ ~r/Confirmation pending .* false/ assert_received {:mix_shell, :info, [message]} assert message =~ ~r/Locked status .* true/ assert_received {:mix_shell, :info, [message]} - assert message =~ ~r/Admin status .* true/ + assert message =~ ~r/Moderator status .* true/ user = User.get_cached_by_nickname(user.nickname) assert user.is_moderator - assert user.locked + assert user.is_locked assert user.is_admin + refute user.confirmation_pending end test "All statuses unset" do - user = insert(:user, locked: true, is_moderator: true, is_admin: true) + user = + insert(:user, + is_locked: true, + is_moderator: true, + is_admin: true, + confirmation_pending: true + ) Mix.Tasks.Pleroma.User.run([ "set", user.nickname, - "--no-moderator", "--no-admin", - "--no-locked" + "--no-confirmed", + "--no-locked", + "--no-moderator" ]) assert_received {:mix_shell, :info, [message]} - assert message =~ ~r/Moderator status .* false/ + assert message =~ ~r/Admin status .* false/ + + assert_received {:mix_shell, :info, [message]} + assert message =~ ~r/Confirmation pending .* true/ assert_received {:mix_shell, :info, [message]} assert message =~ ~r/Locked status .* false/ assert_received {:mix_shell, :info, [message]} - assert message =~ ~r/Admin status .* false/ + assert message =~ ~r/Moderator status .* false/ user = User.get_cached_by_nickname(user.nickname) refute user.is_moderator - refute user.locked + refute user.is_locked refute user.is_admin + assert user.confirmation_pending end test "no user to set status" do @@ -554,4 +576,44 @@ test "it prints an error message when user is not exist" do assert message =~ "Could not change user tags" end end + + describe "bulk confirm and unconfirm" do + test "confirm all" do + user1 = insert(:user, confirmation_pending: true) + user2 = insert(:user, confirmation_pending: true) + + assert user1.confirmation_pending + assert user2.confirmation_pending + + Mix.Tasks.Pleroma.User.run(["confirm_all"]) + + user1 = User.get_cached_by_nickname(user1.nickname) + user2 = User.get_cached_by_nickname(user2.nickname) + + refute user1.confirmation_pending + refute user2.confirmation_pending + end + + test "unconfirm all" do + user1 = insert(:user, confirmation_pending: false) + user2 = insert(:user, confirmation_pending: false) + admin = insert(:user, is_admin: true, confirmation_pending: false) + mod = insert(:user, is_moderator: true, confirmation_pending: false) + + refute user1.confirmation_pending + refute user2.confirmation_pending + + Mix.Tasks.Pleroma.User.run(["unconfirm_all"]) + + user1 = User.get_cached_by_nickname(user1.nickname) + user2 = User.get_cached_by_nickname(user2.nickname) + admin = User.get_cached_by_nickname(admin.nickname) + mod = User.get_cached_by_nickname(mod.nickname) + + assert user1.confirmation_pending + assert user2.confirmation_pending + refute admin.confirmation_pending + refute mod.confirmation_pending + end + end end diff --git a/test/activity/ir/topics_test.exs b/test/pleroma/activity/ir/topics_test.exs similarity index 96% rename from test/activity/ir/topics_test.exs rename to test/pleroma/activity/ir/topics_test.exs index 14a6e6b71..4ddcea1ec 100644 --- a/test/activity/ir/topics_test.exs +++ b/test/pleroma/activity/ir/topics_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Activity.Ir.TopicsTest do use Pleroma.DataCase diff --git a/test/activity_test.exs b/test/pleroma/activity_test.exs similarity index 96% rename from test/activity_test.exs rename to test/pleroma/activity_test.exs index 2a92327d1..ee6a99cc3 100644 --- a/test/activity_test.exs +++ b/test/pleroma/activity_test.exs @@ -185,15 +185,6 @@ test "find all statuses for unauthenticated users when `limit_to_local_content` end end - test "add an activity with an expiration" do - activity = insert(:note_activity) - insert(:expiration_in_the_future, %{activity_id: activity.id}) - - Pleroma.ActivityExpiration - |> where([a], a.activity_id == ^activity.id) - |> Repo.one!() - end - test "all_by_ids_with_object/1" do %{id: id1} = insert(:note_activity) %{id: id2} = insert(:note_activity) diff --git a/test/application_requirements_test.exs b/test/pleroma/application_requirements_test.exs similarity index 81% rename from test/application_requirements_test.exs rename to test/pleroma/application_requirements_test.exs index 21d24ddd0..c505ae229 100644 --- a/test/application_requirements_test.exs +++ b/test/pleroma/application_requirements_test.exs @@ -4,9 +4,12 @@ defmodule Pleroma.ApplicationRequirementsTest do use Pleroma.DataCase + import ExUnit.CaptureLog import Mock + alias Pleroma.ApplicationRequirements + alias Pleroma.Config alias Pleroma.Repo describe "check_welcome_message_config!/1" do @@ -70,42 +73,42 @@ test "doesn't do anything if account confirmation is required and mailer is enab setup do: clear_config([:database, :rum_enabled]) test "raises if rum is enabled and detects unapplied rum migrations" do - Pleroma.Config.put([:database, :rum_enabled], true) + Config.put([:database, :rum_enabled], true) with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do - assert_raise Pleroma.ApplicationRequirements.VerifyError, + assert_raise ApplicationRequirements.VerifyError, "Unapplied RUM Migrations detected", fn -> - capture_log(&Pleroma.ApplicationRequirements.verify!/0) + capture_log(&ApplicationRequirements.verify!/0) end end end test "raises if rum is disabled and detects rum migrations" do - Pleroma.Config.put([:database, :rum_enabled], false) + Config.put([:database, :rum_enabled], false) with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do - assert_raise Pleroma.ApplicationRequirements.VerifyError, + assert_raise ApplicationRequirements.VerifyError, "RUM Migrations detected", fn -> - capture_log(&Pleroma.ApplicationRequirements.verify!/0) + capture_log(&ApplicationRequirements.verify!/0) end end end test "doesn't do anything if rum enabled and applied migrations" do - Pleroma.Config.put([:database, :rum_enabled], true) + Config.put([:database, :rum_enabled], true) with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do - assert Pleroma.ApplicationRequirements.verify!() == :ok + assert ApplicationRequirements.verify!() == :ok end end test "doesn't do anything if rum disabled" do - Pleroma.Config.put([:database, :rum_enabled], false) + Config.put([:database, :rum_enabled], false) with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do - assert Pleroma.ApplicationRequirements.verify!() == :ok + assert ApplicationRequirements.verify!() == :ok end end end @@ -130,17 +133,17 @@ test "doesn't do anything if rum disabled" do setup do: clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check]) test "raises if it detects unapplied migrations" do - assert_raise Pleroma.ApplicationRequirements.VerifyError, + assert_raise ApplicationRequirements.VerifyError, "Unapplied Migrations detected", fn -> - capture_log(&Pleroma.ApplicationRequirements.verify!/0) + capture_log(&ApplicationRequirements.verify!/0) end end test "doesn't do anything if disabled" do - Pleroma.Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true) + Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true) - assert :ok == Pleroma.ApplicationRequirements.verify!() + assert :ok == ApplicationRequirements.verify!() end end end diff --git a/test/bbs/handler_test.exs b/test/pleroma/bbs/handler_test.exs similarity index 100% rename from test/bbs/handler_test.exs rename to test/pleroma/bbs/handler_test.exs diff --git a/test/bookmark_test.exs b/test/pleroma/bookmark_test.exs similarity index 100% rename from test/bookmark_test.exs rename to test/pleroma/bookmark_test.exs diff --git a/test/captcha_test.exs b/test/pleroma/captcha_test.exs similarity index 100% rename from test/captcha_test.exs rename to test/pleroma/captcha_test.exs diff --git a/test/chat/message_reference_test.exs b/test/pleroma/chat/message_reference_test.exs similarity index 100% rename from test/chat/message_reference_test.exs rename to test/pleroma/chat/message_reference_test.exs diff --git a/test/chat_test.exs b/test/pleroma/chat_test.exs similarity index 78% rename from test/chat_test.exs rename to test/pleroma/chat_test.exs index 332f2180a..9e8a9ebf0 100644 --- a/test/chat_test.exs +++ b/test/pleroma/chat_test.exs @@ -26,6 +26,28 @@ test "it creates a chat for a user and recipient" do assert chat.id end + test "deleting the user deletes the chat" do + user = insert(:user) + other_user = insert(:user) + + {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id) + + Repo.delete(user) + + refute Chat.get_by_id(chat.id) + end + + test "deleting the recipient deletes the chat" do + user = insert(:user) + other_user = insert(:user) + + {:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id) + + Repo.delete(other_user) + + refute Chat.get_by_id(chat.id) + end + test "it returns and bumps a chat for a user and recipient if it already exists" do user = insert(:user) other_user = insert(:user) diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs new file mode 100644 index 000000000..0cfed4555 --- /dev/null +++ b/test/pleroma/config/deprecation_warnings_test.exs @@ -0,0 +1,140 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Config.DeprecationWarningsTest do + use ExUnit.Case + use Pleroma.Tests.Helpers + + import ExUnit.CaptureLog + + alias Pleroma.Config + alias Pleroma.Config.DeprecationWarnings + + test "check_old_mrf_config/0" do + clear_config([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.NoOpPolicy) + clear_config([:instance, :mrf_transparency], true) + clear_config([:instance, :mrf_transparency_exclusions], []) + + assert capture_log(fn -> DeprecationWarnings.check_old_mrf_config() end) =~ + """ + !!!DEPRECATION WARNING!!! + Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later: + + * `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies` + * `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency` + * `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions` + """ + end + + test "move_namespace_and_warn/2" do + old_group1 = [:group, :key] + old_group2 = [:group, :key2] + old_group3 = [:group, :key3] + + new_group1 = [:another_group, :key4] + new_group2 = [:another_group, :key5] + new_group3 = [:another_group, :key6] + + clear_config(old_group1, 1) + clear_config(old_group2, 2) + clear_config(old_group3, 3) + + clear_config(new_group1) + clear_config(new_group2) + clear_config(new_group3) + + config_map = [ + {old_group1, new_group1, "\n error :key"}, + {old_group2, new_group2, "\n error :key2"}, + {old_group3, new_group3, "\n error :key3"} + ] + + assert capture_log(fn -> + DeprecationWarnings.move_namespace_and_warn( + config_map, + "Warning preface" + ) + end) =~ "Warning preface\n error :key\n error :key2\n error :key3" + + assert Config.get(new_group1) == 1 + assert Config.get(new_group2) == 2 + assert Config.get(new_group3) == 3 + end + + test "check_media_proxy_whitelist_config/0" do + clear_config([:media_proxy, :whitelist], ["https://example.com", "example2.com"]) + + assert capture_log(fn -> + DeprecationWarnings.check_media_proxy_whitelist_config() + end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option" + end + + test "check_welcome_message_config/0" do + clear_config([:instance, :welcome_user_nickname], "LainChan") + + assert capture_log(fn -> + DeprecationWarnings.check_welcome_message_config() + end) =~ "Your config is using the old namespace for Welcome messages configuration." + end + + test "check_hellthread_threshold/0" do + clear_config([:mrf_hellthread, :threshold], 16) + + assert capture_log(fn -> + DeprecationWarnings.check_hellthread_threshold() + end) =~ "You are using the old configuration mechanism for the hellthread filter." + end + + test "check_activity_expiration_config/0" do + clear_config(Pleroma.ActivityExpiration, enabled: true) + + assert capture_log(fn -> + DeprecationWarnings.check_activity_expiration_config() + end) =~ "Your config is using old namespace for activity expiration configuration." + end + + describe "check_gun_pool_options/0" do + test "await_up_timeout" do + config = Config.get(:connections_pool) + clear_config(:connections_pool, Keyword.put(config, :await_up_timeout, 5_000)) + + assert capture_log(fn -> + DeprecationWarnings.check_gun_pool_options() + end) =~ + "Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`." + end + + test "pool timeout" do + old_config = [ + federation: [ + size: 50, + max_waiting: 10, + timeout: 10_000 + ], + media: [ + size: 50, + max_waiting: 10, + timeout: 10_000 + ], + upload: [ + size: 25, + max_waiting: 5, + timeout: 15_000 + ], + default: [ + size: 10, + max_waiting: 2, + timeout: 5_000 + ] + ] + + clear_config(:pools, old_config) + + assert capture_log(fn -> + DeprecationWarnings.check_gun_pool_options() + end) =~ + "Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings" + end + end +end diff --git a/test/config/holder_test.exs b/test/pleroma/config/holder_test.exs similarity index 100% rename from test/config/holder_test.exs rename to test/pleroma/config/holder_test.exs diff --git a/test/config/loader_test.exs b/test/pleroma/config/loader_test.exs similarity index 100% rename from test/config/loader_test.exs rename to test/pleroma/config/loader_test.exs diff --git a/test/config/transfer_task_test.exs b/test/pleroma/config/transfer_task_test.exs similarity index 100% rename from test/config/transfer_task_test.exs rename to test/pleroma/config/transfer_task_test.exs diff --git a/test/config/config_db_test.exs b/test/pleroma/config_db_test.exs similarity index 100% rename from test/config/config_db_test.exs rename to test/pleroma/config_db_test.exs diff --git a/test/config_test.exs b/test/pleroma/config_test.exs similarity index 100% rename from test/config_test.exs rename to test/pleroma/config_test.exs diff --git a/test/conversation/participation_test.exs b/test/pleroma/conversation/participation_test.exs similarity index 100% rename from test/conversation/participation_test.exs rename to test/pleroma/conversation/participation_test.exs diff --git a/test/conversation_test.exs b/test/pleroma/conversation_test.exs similarity index 100% rename from test/conversation_test.exs rename to test/pleroma/conversation_test.exs diff --git a/test/docs/generator_test.exs b/test/pleroma/docs/generator_test.exs similarity index 97% rename from test/docs/generator_test.exs rename to test/pleroma/docs/generator_test.exs index b32918a69..43877244d 100644 --- a/test/docs/generator_test.exs +++ b/test/pleroma/docs/generator_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Docs.GeneratorTest do use ExUnit.Case, async: true alias Pleroma.Docs.Generator diff --git a/test/earmark_renderer_test.exs b/test/pleroma/earmark_renderer_test.exs similarity index 100% rename from test/earmark_renderer_test.exs rename to test/pleroma/earmark_renderer_test.exs diff --git a/test/web/activity_pub/object_validators/types/date_time_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/date_time_test.exs similarity index 76% rename from test/web/activity_pub/object_validators/types/date_time_test.exs rename to test/pleroma/ecto_type/activity_pub/object_validators/date_time_test.exs index 43be8e936..812463454 100644 --- a/test/web/activity_pub/object_validators/types/date_time_test.exs +++ b/test/pleroma/ecto_type/activity_pub/object_validators/date_time_test.exs @@ -1,4 +1,8 @@ -defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.DateTimeTest do alias Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime use Pleroma.DataCase diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/object_id_test.exs similarity index 92% rename from test/web/activity_pub/object_validators/types/object_id_test.exs rename to test/pleroma/ecto_type/activity_pub/object_validators/object_id_test.exs index e0ab76379..732e2365f 100644 --- a/test/web/activity_pub/object_validators/types/object_id_test.exs +++ b/test/pleroma/ecto_type/activity_pub/object_validators/object_id_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectIDTest do alias Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID use Pleroma.DataCase diff --git a/test/web/activity_pub/object_validators/types/recipients_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs similarity index 78% rename from test/web/activity_pub/object_validators/types/recipients_test.exs rename to test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs index 053916bdd..2e6a0c83d 100644 --- a/test/web/activity_pub/object_validators/types/recipients_test.exs +++ b/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs @@ -1,4 +1,8 @@ -defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.RecipientsTest do alias Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients use Pleroma.DataCase diff --git a/test/web/activity_pub/object_validators/types/safe_text_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/safe_text_test.exs similarity index 92% rename from test/web/activity_pub/object_validators/types/safe_text_test.exs rename to test/pleroma/ecto_type/activity_pub/object_validators/safe_text_test.exs index 9c08606f6..7eddd2388 100644 --- a/test/web/activity_pub/object_validators/types/safe_text_test.exs +++ b/test/pleroma/ecto_type/activity_pub/object_validators/safe_text_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.SafeTextTest do +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.SafeTextTest do use Pleroma.DataCase alias Pleroma.EctoType.ActivityPub.ObjectValidators.SafeText diff --git a/test/emails/admin_email_test.exs b/test/pleroma/emails/admin_email_test.exs similarity index 96% rename from test/emails/admin_email_test.exs rename to test/pleroma/emails/admin_email_test.exs index e24231e27..155057f3e 100644 --- a/test/emails/admin_email_test.exs +++ b/test/pleroma/emails/admin_email_test.exs @@ -63,7 +63,7 @@ test "new unapproved registration email" do assert res.html_body == """

New account for review: @#{account.nickname}

Plz let me in
- Visit AdminFE + Visit AdminFE """ end end diff --git a/test/emails/mailer_test.exs b/test/pleroma/emails/mailer_test.exs similarity index 100% rename from test/emails/mailer_test.exs rename to test/pleroma/emails/mailer_test.exs diff --git a/test/emails/user_email_test.exs b/test/pleroma/emails/user_email_test.exs similarity index 100% rename from test/emails/user_email_test.exs rename to test/pleroma/emails/user_email_test.exs diff --git a/test/emoji/formatter_test.exs b/test/pleroma/emoji/formatter_test.exs similarity index 100% rename from test/emoji/formatter_test.exs rename to test/pleroma/emoji/formatter_test.exs diff --git a/test/emoji/loader_test.exs b/test/pleroma/emoji/loader_test.exs similarity index 100% rename from test/emoji/loader_test.exs rename to test/pleroma/emoji/loader_test.exs diff --git a/test/pleroma/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs new file mode 100644 index 000000000..70d1eaa1b --- /dev/null +++ b/test/pleroma/emoji/pack_test.exs @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Emoji.PackTest do + use ExUnit.Case, async: true + alias Pleroma.Emoji.Pack + + @emoji_path Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + + setup do + pack_path = Path.join(@emoji_path, "dump_pack") + File.mkdir(pack_path) + + File.write!(Path.join(pack_path, "pack.json"), """ + { + "files": { }, + "pack": { + "description": "Dump pack", "homepage": "https://pleroma.social", + "license": "Test license", "share-files": true + }} + """) + + {:ok, pack} = Pleroma.Emoji.Pack.load_pack("dump_pack") + + on_exit(fn -> + File.rm_rf!(pack_path) + end) + + {:ok, pack: pack} + end + + describe "add_file/4" do + test "add emojies from zip file", %{pack: pack} do + file = %Plug.Upload{ + content_type: "application/zip", + filename: "emojis.zip", + path: Path.absname("test/fixtures/emojis.zip") + } + + {:ok, updated_pack} = Pack.add_file(pack, nil, nil, file) + + assert updated_pack.files == %{ + "a_trusted_friend-128" => "128px/a_trusted_friend-128.png", + "auroraborealis" => "auroraborealis.png", + "baby_in_a_box" => "1000px/baby_in_a_box.png", + "bear" => "1000px/bear.png", + "bear-128" => "128px/bear-128.png" + } + + assert updated_pack.files_count == 5 + end + end + + test "returns error when zip file is bad", %{pack: pack} do + file = %Plug.Upload{ + content_type: "application/zip", + filename: "emojis.zip", + path: Path.absname("test/instance_static/emoji/test_pack/blank.png") + } + + assert Pack.add_file(pack, nil, nil, file) == {:error, :einval} + end + + test "returns pack when zip file is empty", %{pack: pack} do + file = %Plug.Upload{ + content_type: "application/zip", + filename: "emojis.zip", + path: Path.absname("test/fixtures/empty.zip") + } + + {:ok, updated_pack} = Pack.add_file(pack, nil, nil, file) + assert updated_pack == pack + end + + test "add emoji file", %{pack: pack} do + file = %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_path}/test_pack/blank.png" + } + + {:ok, updated_pack} = Pack.add_file(pack, "test_blank", "test_blank.png", file) + + assert updated_pack.files == %{ + "test_blank" => "test_blank.png" + } + + assert updated_pack.files_count == 1 + end +end diff --git a/test/emoji_test.exs b/test/pleroma/emoji_test.exs similarity index 97% rename from test/emoji_test.exs rename to test/pleroma/emoji_test.exs index b36047578..1dd3c58c6 100644 --- a/test/emoji_test.exs +++ b/test/pleroma/emoji_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.EmojiTest do - use ExUnit.Case, async: true + use ExUnit.Case alias Pleroma.Emoji describe "is_unicode_emoji?/1" do diff --git a/test/filter_test.exs b/test/pleroma/filter_test.exs similarity index 100% rename from test/filter_test.exs rename to test/pleroma/filter_test.exs diff --git a/test/following_relationship_test.exs b/test/pleroma/following_relationship_test.exs similarity index 100% rename from test/following_relationship_test.exs rename to test/pleroma/following_relationship_test.exs diff --git a/test/formatter_test.exs b/test/pleroma/formatter_test.exs similarity index 100% rename from test/formatter_test.exs rename to test/pleroma/formatter_test.exs diff --git a/test/gun/conneciton_pool_test.exs b/test/pleroma/gun/connection_pool_test.exs similarity index 100% rename from test/gun/conneciton_pool_test.exs rename to test/pleroma/gun/connection_pool_test.exs diff --git a/test/healthcheck_test.exs b/test/pleroma/healthcheck_test.exs similarity index 100% rename from test/healthcheck_test.exs rename to test/pleroma/healthcheck_test.exs diff --git a/test/html_test.exs b/test/pleroma/html_test.exs similarity index 100% rename from test/html_test.exs rename to test/pleroma/html_test.exs diff --git a/test/http/adapter_helper/gun_test.exs b/test/pleroma/http/adapter_helper/gun_test.exs similarity index 100% rename from test/http/adapter_helper/gun_test.exs rename to test/pleroma/http/adapter_helper/gun_test.exs diff --git a/test/http/adapter_helper/hackney_test.exs b/test/pleroma/http/adapter_helper/hackney_test.exs similarity index 100% rename from test/http/adapter_helper/hackney_test.exs rename to test/pleroma/http/adapter_helper/hackney_test.exs diff --git a/test/http/adapter_helper_test.exs b/test/pleroma/http/adapter_helper_test.exs similarity index 100% rename from test/http/adapter_helper_test.exs rename to test/pleroma/http/adapter_helper_test.exs diff --git a/test/http/ex_aws_test.exs b/test/pleroma/http/ex_aws_test.exs similarity index 100% rename from test/http/ex_aws_test.exs rename to test/pleroma/http/ex_aws_test.exs diff --git a/test/http/request_builder_test.exs b/test/pleroma/http/request_builder_test.exs similarity index 100% rename from test/http/request_builder_test.exs rename to test/pleroma/http/request_builder_test.exs diff --git a/test/http/tzdata_test.exs b/test/pleroma/http/tzdata_test.exs similarity index 100% rename from test/http/tzdata_test.exs rename to test/pleroma/http/tzdata_test.exs diff --git a/test/http_test.exs b/test/pleroma/http_test.exs similarity index 100% rename from test/http_test.exs rename to test/pleroma/http_test.exs diff --git a/test/web/instances/instance_test.exs b/test/pleroma/instances/instance_test.exs similarity index 65% rename from test/web/instances/instance_test.exs rename to test/pleroma/instances/instance_test.exs index 5d4efcebe..4f0805100 100644 --- a/test/web/instances/instance_test.exs +++ b/test/pleroma/instances/instance_test.exs @@ -99,37 +99,54 @@ test "does NOT modify `unreachable_since` value of existing record in case it's end end - test "Scrapes favicon URLs" do - Tesla.Mock.mock(fn %{url: "https://favicon.example.org/"} -> - %Tesla.Env{ - status: 200, - body: ~s[] - } - end) + describe "get_or_update_favicon/1" do + test "Scrapes favicon URLs" do + Tesla.Mock.mock(fn %{url: "https://favicon.example.org/"} -> + %Tesla.Env{ + status: 200, + body: ~s[] + } + end) - assert "https://favicon.example.org/favicon.png" == - Instance.get_or_update_favicon(URI.parse("https://favicon.example.org/")) - end + assert "https://favicon.example.org/favicon.png" == + Instance.get_or_update_favicon(URI.parse("https://favicon.example.org/")) + end - test "Returns nil on too long favicon URLs" do - clear_config([:instances_favicons, :enabled], true) + test "Returns nil on too long favicon URLs" do + long_favicon_url = + "https://Lorem.ipsum.dolor.sit.amet/consecteturadipiscingelit/Praesentpharetrapurusutaliquamtempus/Mauriseulaoreetarcu/atfacilisisorci/Nullamporttitor/nequesedfeugiatmollis/dolormagnaefficiturlorem/nonpretiumsapienorcieurisus/Nullamveleratsem/Maecenassedaccumsanexnam/favicon.png" - long_favicon_url = - "https://Lorem.ipsum.dolor.sit.amet/consecteturadipiscingelit/Praesentpharetrapurusutaliquamtempus/Mauriseulaoreetarcu/atfacilisisorci/Nullamporttitor/nequesedfeugiatmollis/dolormagnaefficiturlorem/nonpretiumsapienorcieurisus/Nullamveleratsem/Maecenassedaccumsanexnam/favicon.png" + Tesla.Mock.mock(fn %{url: "https://long-favicon.example.org/"} -> + %Tesla.Env{ + status: 200, + body: + ~s[] + } + end) - Tesla.Mock.mock(fn %{url: "https://long-favicon.example.org/"} -> - %Tesla.Env{ - status: 200, - body: ~s[] - } - end) + assert capture_log(fn -> + assert nil == + Instance.get_or_update_favicon( + URI.parse("https://long-favicon.example.org/") + ) + end) =~ + "Instance.get_or_update_favicon(\"long-favicon.example.org\") error: %Postgrex.Error{" + end - assert capture_log(fn -> - assert nil == - Instance.get_or_update_favicon( - URI.parse("https://long-favicon.example.org/") - ) - end) =~ - "Instance.get_or_update_favicon(\"long-favicon.example.org\") error: %Postgrex.Error{" + test "Handles not getting a favicon URL properly" do + Tesla.Mock.mock(fn %{url: "https://no-favicon.example.org/"} -> + %Tesla.Env{ + status: 200, + body: ~s[

I wil look down and whisper "GNO.."

] + } + end) + + refute capture_log(fn -> + assert nil == + Instance.get_or_update_favicon( + URI.parse("https://no-favicon.example.org/") + ) + end) =~ "Instance.scrape_favicon(\"https://no-favicon.example.org/\") error: " + end end end diff --git a/test/web/instances/instances_test.exs b/test/pleroma/instances_test.exs similarity index 100% rename from test/web/instances/instances_test.exs rename to test/pleroma/instances_test.exs diff --git a/test/federation/federation_test.exs b/test/pleroma/integration/federation_test.exs similarity index 100% rename from test/federation/federation_test.exs rename to test/pleroma/integration/federation_test.exs diff --git a/test/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs similarity index 99% rename from test/integration/mastodon_websocket_test.exs rename to test/pleroma/integration/mastodon_websocket_test.exs index 76fbc8bda..0f2e6cc2b 100644 --- a/test/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -78,7 +78,7 @@ test "receives well formatted events" do Pleroma.Repo.insert( OAuth.App.register_changeset(%OAuth.App{}, %{ client_name: "client", - scopes: ["scope"], + scopes: ["read"], redirect_uris: "url" }) ) diff --git a/test/job_queue_monitor_test.exs b/test/pleroma/job_queue_monitor_test.exs similarity index 100% rename from test/job_queue_monitor_test.exs rename to test/pleroma/job_queue_monitor_test.exs diff --git a/test/keys_test.exs b/test/pleroma/keys_test.exs similarity index 100% rename from test/keys_test.exs rename to test/pleroma/keys_test.exs diff --git a/test/list_test.exs b/test/pleroma/list_test.exs similarity index 100% rename from test/list_test.exs rename to test/pleroma/list_test.exs diff --git a/test/marker_test.exs b/test/pleroma/marker_test.exs similarity index 93% rename from test/marker_test.exs rename to test/pleroma/marker_test.exs index 5b6d0b4a4..7b3943c7b 100644 --- a/test/marker_test.exs +++ b/test/pleroma/marker_test.exs @@ -33,8 +33,8 @@ test "return empty multi" do test "returns user markers" do user = insert(:user) marker = insert(:marker, user: user) - insert(:notification, user: user) - insert(:notification, user: user) + insert(:notification, user: user, activity: insert(:note_activity)) + insert(:notification, user: user, activity: insert(:note_activity)) insert(:marker, timeline: "home", user: user) assert Marker.get_markers( diff --git a/test/mfa/backup_codes_test.exs b/test/pleroma/mfa/backup_codes_test.exs similarity index 63% rename from test/mfa/backup_codes_test.exs rename to test/pleroma/mfa/backup_codes_test.exs index 7bc01b36b..41adb1e96 100644 --- a/test/mfa/backup_codes_test.exs +++ b/test/pleroma/mfa/backup_codes_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.MFA.BackupCodesTest do use Pleroma.DataCase diff --git a/test/mfa/totp_test.exs b/test/pleroma/mfa/totp_test.exs similarity index 72% rename from test/mfa/totp_test.exs rename to test/pleroma/mfa/totp_test.exs index 50153d208..9edb6fd54 100644 --- a/test/mfa/totp_test.exs +++ b/test/pleroma/mfa/totp_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.MFA.TOTPTest do use Pleroma.DataCase diff --git a/test/mfa_test.exs b/test/pleroma/mfa_test.exs similarity index 100% rename from test/mfa_test.exs rename to test/pleroma/mfa_test.exs diff --git a/test/migration_helper/notification_backfill_test.exs b/test/pleroma/migration_helper/notification_backfill_test.exs similarity index 100% rename from test/migration_helper/notification_backfill_test.exs rename to test/pleroma/migration_helper/notification_backfill_test.exs diff --git a/test/moderation_log_test.exs b/test/pleroma/moderation_log_test.exs similarity index 100% rename from test/moderation_log_test.exs rename to test/pleroma/moderation_log_test.exs diff --git a/test/notification_test.exs b/test/pleroma/notification_test.exs similarity index 98% rename from test/notification_test.exs rename to test/pleroma/notification_test.exs index a09b08675..a74fb7bc2 100644 --- a/test/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -179,17 +179,19 @@ test "does not create a notification for subscribed users if status is a reply" describe "create_notification" do @tag needs_streamer: true test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do - user = insert(:user) + %{user: user, token: oauth_token} = oauth_access(["read"]) task = Task.async(fn -> - Streamer.get_topic_and_add_socket("user", user) + {:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token) assert_receive {:render_with_user, _, _, _}, 4_000 end) task_user_notification = Task.async(fn -> - Streamer.get_topic_and_add_socket("user:notification", user) + {:ok, _topic} = + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) + assert_receive {:render_with_user, _, _, _}, 4_000 end) @@ -344,7 +346,7 @@ test "it creates notifications when someone likes user's status with a filtered describe "follow / follow_request notifications" do test "it creates `follow` notification for approved Follow activity" do user = insert(:user) - followed_user = insert(:user, locked: false) + followed_user = insert(:user, is_locked: false) {:ok, _, _, _activity} = CommonAPI.follow(user, followed_user) assert FollowingRelationship.following?(user, followed_user) @@ -359,7 +361,7 @@ test "it creates `follow` notification for approved Follow activity" do test "it creates `follow_request` notification for pending Follow activity" do user = insert(:user) - followed_user = insert(:user, locked: true) + followed_user = insert(:user, is_locked: true) {:ok, _, _, _activity} = CommonAPI.follow(user, followed_user) refute FollowingRelationship.following?(user, followed_user) @@ -381,7 +383,7 @@ test "it creates `follow_request` notification for pending Follow activity" do test "it doesn't create a notification for follow-unfollow-follow chains" do user = insert(:user) - followed_user = insert(:user, locked: false) + followed_user = insert(:user, is_locked: false) {:ok, _, _, _activity} = CommonAPI.follow(user, followed_user) assert FollowingRelationship.following?(user, followed_user) @@ -395,10 +397,10 @@ test "it doesn't create a notification for follow-unfollow-follow chains" do end test "dismisses the notification on follow request rejection" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) follower = insert(:user) {:ok, _, _, _follow_activity} = CommonAPI.follow(follower, user) - assert [notification] = Notification.for_user(user) + assert [_notification] = Notification.for_user(user) {:ok, _follower} = CommonAPI.reject_follow_request(follower, user) assert [] = Notification.for_user(user) end diff --git a/test/object/containment_test.exs b/test/pleroma/object/containment_test.exs similarity index 100% rename from test/object/containment_test.exs rename to test/pleroma/object/containment_test.exs diff --git a/test/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs similarity index 80% rename from test/object/fetcher_test.exs rename to test/pleroma/object/fetcher_test.exs index 16cfa7f5c..7df6af7fe 100644 --- a/test/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -6,10 +6,12 @@ defmodule Pleroma.Object.FetcherTest do use Pleroma.DataCase alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Object alias Pleroma.Object.Fetcher - import Tesla.Mock + import Mock + import Tesla.Mock setup do mock(fn @@ -19,6 +21,17 @@ defmodule Pleroma.Object.FetcherTest do %{method: :get, url: "https://mastodon.example.org/users/userisgone404"} -> %Tesla.Env{status: 404} + %{ + method: :get, + url: + "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" + } -> + %Tesla.Env{ + status: 200, + headers: [{"content-type", "application/json"}], + body: File.read!("test/fixtures/spoofed-object.json") + } + env -> apply(HttpRequestMock, :request, [env]) end) @@ -32,19 +45,22 @@ defmodule Pleroma.Object.FetcherTest do %{method: :get, url: "https://social.sakamoto.gq/notice/9wTkLEnuq47B25EehM"} -> %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/fetch_mocks/9wTkLEnuq47B25EehM.json") + body: File.read!("test/fixtures/fetch_mocks/9wTkLEnuq47B25EehM.json"), + headers: HttpRequestMock.activitypub_object_headers() } %{method: :get, url: "https://social.sakamoto.gq/users/eal"} -> %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/fetch_mocks/eal.json") + body: File.read!("test/fixtures/fetch_mocks/eal.json"), + headers: HttpRequestMock.activitypub_object_headers() } %{method: :get, url: "https://busshi.moe/users/tuxcrafting/statuses/104410921027210069"} -> %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/fetch_mocks/104410921027210069.json") + body: File.read!("test/fixtures/fetch_mocks/104410921027210069.json"), + headers: HttpRequestMock.activitypub_object_headers() } %{method: :get, url: "https://busshi.moe/users/tuxcrafting"} -> @@ -71,20 +87,20 @@ test "it works when fetching the OP actor errors out" do setup do: clear_config([:instance, :federation_incoming_replies_max_depth]) test "it returns thread depth exceeded error if thread depth is exceeded" do - Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + Config.put([:instance, :federation_incoming_replies_max_depth], 0) assert {:error, "Max thread distance exceeded."} = Fetcher.fetch_object_from_id(@ap_id, depth: 1) end test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do - Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + Config.put([:instance, :federation_incoming_replies_max_depth], 0) assert {:ok, _} = Fetcher.fetch_object_from_id(@ap_id) end test "it fetches object if requested depth does not exceed max thread depth" do - Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10) + Config.put([:instance, :federation_incoming_replies_max_depth], 10) assert {:ok, _} = Fetcher.fetch_object_from_id(@ap_id, depth: 10) end @@ -120,6 +136,23 @@ test "it fetches an object" do assert object == object_again end + + test "Return MRF reason when fetched status is rejected by one" do + clear_config([:mrf_keyword, :reject], ["yeah"]) + clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) + + assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} == + Fetcher.fetch_object_from_id( + "http://mastodon.example.org/@admin/99541947525187367" + ) + end + + test "it does not fetch a spoofed object uploaded on an instance as an attachment" do + assert {:error, _} = + Fetcher.fetch_object_from_id( + "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" + ) + end end describe "implementation quirks" do @@ -212,7 +245,7 @@ test "it can refetch pruned objects" do Pleroma.Signature, [:passthrough], [] do - Pleroma.Config.put([:activitypub, :sign_object_fetches], true) + Config.put([:activitypub, :sign_object_fetches], true) Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") @@ -223,7 +256,7 @@ test "it can refetch pruned objects" do Pleroma.Signature, [:passthrough], [] do - Pleroma.Config.put([:activitypub, :sign_object_fetches], false) + Config.put([:activitypub, :sign_object_fetches], false) Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") diff --git a/test/object_test.exs b/test/pleroma/object_test.exs similarity index 95% rename from test/object_test.exs rename to test/pleroma/object_test.exs index 198d3b1cf..5d4e6fb84 100644 --- a/test/object_test.exs +++ b/test/pleroma/object_test.exs @@ -82,7 +82,7 @@ test "Disabled via config" do Pleroma.Config.put([:instance, :cleanup_attachments], false) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -116,7 +116,7 @@ test "in subdirectories" do Pleroma.Config.put([:instance, :cleanup_attachments], true) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -155,7 +155,7 @@ test "with dedupe enabled" do File.mkdir_p!(uploads_dir) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -188,7 +188,7 @@ test "with objects that have legacy data.url attribute" do Pleroma.Config.put([:instance, :cleanup_attachments], true) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -225,7 +225,7 @@ test "With custom base_url" do Pleroma.Config.put([:instance, :cleanup_attachments], true) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -281,7 +281,11 @@ test "does not fetch unknown objects when fetch_remote is false" do setup do mock(fn %{method: :get, url: "https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"} -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/poll_original.json")} + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/poll_original.json"), + headers: HttpRequestMock.activitypub_object_headers() + } env -> apply(HttpRequestMock, :request, [env]) @@ -315,7 +319,8 @@ test "refetches if the time since the last refetch is greater than the interval" mock_modified.(%Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/poll_modified.json") + body: File.read!("test/fixtures/tesla_mock/poll_modified.json"), + headers: HttpRequestMock.activitypub_object_headers() }) updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1) @@ -359,7 +364,8 @@ test "does not refetch if the time since the last refetch is greater than the in mock_modified.(%Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/poll_modified.json") + body: File.read!("test/fixtures/tesla_mock/poll_modified.json"), + headers: HttpRequestMock.activitypub_object_headers() }) updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: 100) @@ -387,7 +393,8 @@ test "preserves internal fields on refetch", %{mock_modified: mock_modified} do mock_modified.(%Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/poll_modified.json") + body: File.read!("test/fixtures/tesla_mock/poll_modified.json"), + headers: HttpRequestMock.activitypub_object_headers() }) updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1) diff --git a/test/otp_version_test.exs b/test/pleroma/otp_version_test.exs similarity index 100% rename from test/otp_version_test.exs rename to test/pleroma/otp_version_test.exs diff --git a/test/pagination_test.exs b/test/pleroma/pagination_test.exs similarity index 100% rename from test/pagination_test.exs rename to test/pleroma/pagination_test.exs diff --git a/test/registration_test.exs b/test/pleroma/registration_test.exs similarity index 100% rename from test/registration_test.exs rename to test/pleroma/registration_test.exs diff --git a/test/migrations/20200716195806_autolinker_to_linkify_test.exs b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs similarity index 93% rename from test/migrations/20200716195806_autolinker_to_linkify_test.exs rename to test/pleroma/repo/migrations/autolinker_to_linkify_test.exs index 250d11c61..84f520fe4 100644 --- a/test/migrations/20200716195806_autolinker_to_linkify_test.exs +++ b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Repo.Migrations.AutolinkerToLinkifyTest do use Pleroma.DataCase import Pleroma.Factory diff --git a/test/migrations/20200802170532_fix_legacy_tags_test.exs b/test/pleroma/repo/migrations/fix_legacy_tags_test.exs similarity index 83% rename from test/migrations/20200802170532_fix_legacy_tags_test.exs rename to test/pleroma/repo/migrations/fix_legacy_tags_test.exs index 3b4dee407..432055e45 100644 --- a/test/migrations/20200802170532_fix_legacy_tags_test.exs +++ b/test/pleroma/repo/migrations/fix_legacy_tags_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Repo.Migrations.FixLegacyTagsTest do alias Pleroma.User use Pleroma.DataCase diff --git a/test/migrations/20200722185515_fix_malformed_formatter_config_test.exs b/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs similarity index 93% rename from test/migrations/20200722185515_fix_malformed_formatter_config_test.exs rename to test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs index d3490478e..61528599a 100644 --- a/test/migrations/20200722185515_fix_malformed_formatter_config_test.exs +++ b/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Repo.Migrations.FixMalformedFormatterConfigTest do use Pleroma.DataCase import Pleroma.Factory diff --git a/test/migrations/20200724133313_move_welcome_settings_test.exs b/test/pleroma/repo/migrations/move_welcome_settings_test.exs similarity index 96% rename from test/migrations/20200724133313_move_welcome_settings_test.exs rename to test/pleroma/repo/migrations/move_welcome_settings_test.exs index 739f24547..53d05a55a 100644 --- a/test/migrations/20200724133313_move_welcome_settings_test.exs +++ b/test/pleroma/repo/migrations/move_welcome_settings_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Repo.Migrations.MoveWelcomeSettingsTest do use Pleroma.DataCase import Pleroma.Factory diff --git a/test/repo_test.exs b/test/pleroma/repo_test.exs similarity index 64% rename from test/repo_test.exs rename to test/pleroma/repo_test.exs index 92e827c95..155791be2 100644 --- a/test/repo_test.exs +++ b/test/pleroma/repo_test.exs @@ -37,7 +37,9 @@ test "get one-to-one assoc from repo" do test "get one-to-many assoc from repo" do user = insert(:user) - notification = refresh_record(insert(:notification, user: user)) + + notification = + refresh_record(insert(:notification, user: user, activity: insert(:note_activity))) assert Repo.get_assoc(user, :notifications) == {:ok, [notification]} end @@ -47,4 +49,32 @@ test "return error if has not assoc " do assert Repo.get_assoc(token, :user) == {:error, :not_found} end end + + describe "chunk_stream/3" do + test "fetch records one-by-one" do + users = insert_list(50, :user) + + {fetch_users, 50} = + from(t in User) + |> Repo.chunk_stream(5) + |> Enum.reduce({[], 0}, fn %User{} = user, {acc, count} -> + {acc ++ [user], count + 1} + end) + + assert users == fetch_users + end + + test "fetch records in bulk" do + users = insert_list(50, :user) + + {fetch_users, 10} = + from(t in User) + |> Repo.chunk_stream(5, :batches) + |> Enum.reduce({[], 0}, fn users, {acc, count} -> + {acc ++ users, count + 1} + end) + + assert users == fetch_users + end + end end diff --git a/test/report_note_test.exs b/test/pleroma/report_note_test.exs similarity index 100% rename from test/report_note_test.exs rename to test/pleroma/report_note_test.exs diff --git a/test/reverse_proxy/reverse_proxy_test.exs b/test/pleroma/reverse_proxy_test.exs similarity index 100% rename from test/reverse_proxy/reverse_proxy_test.exs rename to test/pleroma/reverse_proxy_test.exs diff --git a/test/runtime_test.exs b/test/pleroma/runtime_test.exs similarity index 69% rename from test/runtime_test.exs rename to test/pleroma/runtime_test.exs index a1a6c57cd..010594fcd 100644 --- a/test/runtime_test.exs +++ b/test/pleroma/runtime_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.RuntimeTest do use ExUnit.Case, async: true test "it loads custom runtime modules" do - assert {:module, RuntimeModule} == Code.ensure_compiled(RuntimeModule) + assert {:module, Fixtures.Modules.RuntimeModule} == + Code.ensure_compiled(Fixtures.Modules.RuntimeModule) end end diff --git a/test/safe_jsonb_set_test.exs b/test/pleroma/safe_jsonb_set_test.exs similarity index 71% rename from test/safe_jsonb_set_test.exs rename to test/pleroma/safe_jsonb_set_test.exs index 748540570..8b1274545 100644 --- a/test/safe_jsonb_set_test.exs +++ b/test/pleroma/safe_jsonb_set_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.SafeJsonbSetTest do use Pleroma.DataCase diff --git a/test/scheduled_activity_test.exs b/test/pleroma/scheduled_activity_test.exs similarity index 100% rename from test/scheduled_activity_test.exs rename to test/pleroma/scheduled_activity_test.exs diff --git a/test/signature_test.exs b/test/pleroma/signature_test.exs similarity index 100% rename from test/signature_test.exs rename to test/pleroma/signature_test.exs diff --git a/test/stats_test.exs b/test/pleroma/stats_test.exs similarity index 82% rename from test/stats_test.exs rename to test/pleroma/stats_test.exs index f09d8d31a..74bf785b0 100644 --- a/test/stats_test.exs +++ b/test/pleroma/stats_test.exs @@ -4,7 +4,10 @@ defmodule Pleroma.StatsTest do use Pleroma.DataCase + import Pleroma.Factory + + alias Pleroma.Stats alias Pleroma.Web.CommonAPI describe "user count" do @@ -13,7 +16,7 @@ test "it ignores internal users" do _internal = insert(:user, local: true, nickname: nil) _internal = Pleroma.Web.ActivityPub.Relay.get_actor() - assert match?(%{stats: %{user_count: 1}}, Pleroma.Stats.calculate_stat_data()) + assert match?(%{stats: %{user_count: 1}}, Stats.calculate_stat_data()) end end @@ -47,23 +50,23 @@ test "on new status" do end) assert %{"direct" => 3, "private" => 4, "public" => 1, "unlisted" => 2} = - Pleroma.Stats.get_status_visibility_count() + Stats.get_status_visibility_count() end test "on status delete" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"}) - assert %{"public" => 1} = Pleroma.Stats.get_status_visibility_count() + assert %{"public" => 1} = Stats.get_status_visibility_count() CommonAPI.delete(activity.id, user) - assert %{"public" => 0} = Pleroma.Stats.get_status_visibility_count() + assert %{"public" => 0} = Stats.get_status_visibility_count() end test "on status visibility update" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"}) - assert %{"public" => 1, "private" => 0} = Pleroma.Stats.get_status_visibility_count() + assert %{"public" => 1, "private" => 0} = Stats.get_status_visibility_count() {:ok, _} = CommonAPI.update_activity_scope(activity.id, %{visibility: "private"}) - assert %{"public" => 0, "private" => 1} = Pleroma.Stats.get_status_visibility_count() + assert %{"public" => 0, "private" => 1} = Stats.get_status_visibility_count() end test "doesn't count unrelated activities" do @@ -75,7 +78,7 @@ test "doesn't count unrelated activities" do CommonAPI.repeat(activity.id, other_user) assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 0} = - Pleroma.Stats.get_status_visibility_count() + Stats.get_status_visibility_count() end end @@ -110,10 +113,10 @@ test "single instance" do end) assert %{"direct" => 10, "private" => 0, "public" => 1, "unlisted" => 5} = - Pleroma.Stats.get_status_visibility_count(local_instance) + Stats.get_status_visibility_count(local_instance) assert %{"direct" => 0, "private" => 20, "public" => 0, "unlisted" => 0} = - Pleroma.Stats.get_status_visibility_count(instance2) + Stats.get_status_visibility_count(instance2) end end end diff --git a/test/upload/filter/anonymize_filename_test.exs b/test/pleroma/upload/filter/anonymize_filename_test.exs similarity index 97% rename from test/upload/filter/anonymize_filename_test.exs rename to test/pleroma/upload/filter/anonymize_filename_test.exs index 19b915cc8..7ef01ce91 100644 --- a/test/upload/filter/anonymize_filename_test.exs +++ b/test/pleroma/upload/filter/anonymize_filename_test.exs @@ -13,7 +13,7 @@ defmodule Pleroma.Upload.Filter.AnonymizeFilenameTest do upload_file = %Upload{ name: "an… image.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg") } diff --git a/test/upload/filter/dedupe_test.exs b/test/pleroma/upload/filter/dedupe_test.exs similarity index 96% rename from test/upload/filter/dedupe_test.exs rename to test/pleroma/upload/filter/dedupe_test.exs index 75c7198e1..92a3d7df3 100644 --- a/test/upload/filter/dedupe_test.exs +++ b/test/pleroma/upload/filter/dedupe_test.exs @@ -18,7 +18,7 @@ test "adds shasum" do upload = %Upload{ name: "an… image.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), tempfile: Path.absname("test/fixtures/image_tmp.jpg") } diff --git a/test/upload/filter/exiftool_test.exs b/test/pleroma/upload/filter/exiftool_test.exs similarity index 97% rename from test/upload/filter/exiftool_test.exs rename to test/pleroma/upload/filter/exiftool_test.exs index d4cd4ba11..6b978b64c 100644 --- a/test/upload/filter/exiftool_test.exs +++ b/test/pleroma/upload/filter/exiftool_test.exs @@ -16,7 +16,7 @@ test "apply exiftool filter" do upload = %Pleroma.Upload{ name: "image_with_GPS_data.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/DSCN0010.jpg"), tempfile: Path.absname("test/fixtures/DSCN0010_tmp.jpg") } diff --git a/test/upload/filter/mogrifun_test.exs b/test/pleroma/upload/filter/mogrifun_test.exs similarity index 96% rename from test/upload/filter/mogrifun_test.exs rename to test/pleroma/upload/filter/mogrifun_test.exs index dc1e9e78f..fc2f68276 100644 --- a/test/upload/filter/mogrifun_test.exs +++ b/test/pleroma/upload/filter/mogrifun_test.exs @@ -17,7 +17,7 @@ test "apply mogrify filter" do upload = %Upload{ name: "an… image.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), tempfile: Path.absname("test/fixtures/image_tmp.jpg") } diff --git a/test/upload/filter/mogrify_test.exs b/test/pleroma/upload/filter/mogrify_test.exs similarity index 96% rename from test/upload/filter/mogrify_test.exs rename to test/pleroma/upload/filter/mogrify_test.exs index bf64b96b3..6dee02e8b 100644 --- a/test/upload/filter/mogrify_test.exs +++ b/test/pleroma/upload/filter/mogrify_test.exs @@ -18,7 +18,7 @@ test "apply mogrify filter" do upload = %Pleroma.Upload{ name: "an… image.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), tempfile: Path.absname("test/fixtures/image_tmp.jpg") } diff --git a/test/upload/filter_test.exs b/test/pleroma/upload/filter_test.exs similarity index 96% rename from test/upload/filter_test.exs rename to test/pleroma/upload/filter_test.exs index 352b66402..09394929c 100644 --- a/test/upload/filter_test.exs +++ b/test/pleroma/upload/filter_test.exs @@ -20,7 +20,7 @@ test "applies filters" do upload = %Pleroma.Upload{ name: "an… image.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), tempfile: Path.absname("test/fixtures/image_tmp.jpg") } diff --git a/test/upload_test.exs b/test/pleroma/upload_test.exs similarity index 81% rename from test/upload_test.exs rename to test/pleroma/upload_test.exs index b06b54487..f52d4dff6 100644 --- a/test/upload_test.exs +++ b/test/pleroma/upload_test.exs @@ -11,7 +11,7 @@ defmodule Pleroma.UploadTest do alias Pleroma.Uploaders.Uploader @upload_file %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "image.jpg" } @@ -112,7 +112,7 @@ test "does not allow descriptions longer than the post limit" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "image.jpg" } @@ -124,7 +124,7 @@ test "returns a media url" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "image.jpg" } @@ -140,7 +140,7 @@ test "copies the file to the configured folder with deduping" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "an [image.jpg" } @@ -156,7 +156,7 @@ test "copies the file to the configured folder without deduping" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "an [image.jpg" } @@ -165,63 +165,31 @@ test "copies the file to the configured folder without deduping" do assert data["name"] == "an [image.jpg" end - test "fixes incorrect content type" do - File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") - - file = %Plug.Upload{ - content_type: "application/octet-stream", - path: Path.absname("test/fixtures/image_tmp.jpg"), - filename: "an [image.jpg" + test "fixes incorrect content type when base64 is given" do + params = %{ + img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}" } - {:ok, data} = Upload.store(file, filters: [Pleroma.Upload.Filter.Dedupe]) + {:ok, data} = Upload.store(params) assert hd(data["url"])["mediaType"] == "image/jpeg" end - test "adds missing extension" do + test "adds extension when base64 is given" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image_tmp.jpg"), - filename: "an [image" + params = %{ + img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}" } - {:ok, data} = Upload.store(file) - assert data["name"] == "an [image.jpg" - end - - test "fixes incorrect file extension" do - File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image_tmp.jpg"), - filename: "an [image.blah" - } - - {:ok, data} = Upload.store(file) - assert data["name"] == "an [image.jpg" - end - - test "don't modify filename of an unknown type" do - File.cp("test/fixtures/test.txt", "test/fixtures/test_tmp.txt") - - file = %Plug.Upload{ - content_type: "text/plain", - path: Path.absname("test/fixtures/test_tmp.txt"), - filename: "test.txt" - } - - {:ok, data} = Upload.store(file) - assert data["name"] == "test.txt" + {:ok, data} = Upload.store(params) + assert String.ends_with?(data["name"], ".jpg") end test "copies the file to the configured folder with anonymizing filename" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "an [image.jpg" } @@ -235,7 +203,7 @@ test "escapes invalid characters in url" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "an… image.jpg" } @@ -250,7 +218,7 @@ test "escapes reserved uri characters" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: ":?#[]@!$&\\'()*+,;=.jpg" } @@ -272,7 +240,7 @@ test "returns a media url with configured base_url" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "image.jpg" } diff --git a/test/uploaders/local_test.exs b/test/pleroma/uploaders/local_test.exs similarity index 95% rename from test/uploaders/local_test.exs rename to test/pleroma/uploaders/local_test.exs index 18122ff6c..1ce7be485 100644 --- a/test/uploaders/local_test.exs +++ b/test/pleroma/uploaders/local_test.exs @@ -19,7 +19,7 @@ test "put file to local folder" do file = %Pleroma.Upload{ name: "image.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: file_path, tempfile: Path.absname("test/fixtures/image_tmp.jpg") } @@ -38,7 +38,7 @@ test "deletes local file" do file = %Pleroma.Upload{ name: "image.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: file_path, tempfile: Path.absname("test/fixtures/image_tmp.jpg") } diff --git a/test/uploaders/s3_test.exs b/test/pleroma/uploaders/s3_test.exs similarity index 98% rename from test/uploaders/s3_test.exs rename to test/pleroma/uploaders/s3_test.exs index d949c90a5..e7a013dd8 100644 --- a/test/uploaders/s3_test.exs +++ b/test/pleroma/uploaders/s3_test.exs @@ -56,7 +56,7 @@ test "it returns path with bucket namespace when namespace is set" do setup do file_upload = %Pleroma.Upload{ name: "image-tet.jpg", - content_type: "image/jpg", + content_type: "image/jpeg", path: "test_folder/image-tet.jpg", tempfile: Path.absname("test/instance_static/add/shortcode.png") } diff --git a/test/pleroma/user/import_test.exs b/test/pleroma/user/import_test.exs new file mode 100644 index 000000000..e404deeb5 --- /dev/null +++ b/test/pleroma/user/import_test.exs @@ -0,0 +1,76 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.ImportTest do + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + + use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + describe "follow_import" do + test "it imports user followings from list" do + [user1, user2, user3] = insert_list(3, :user) + + identifiers = [ + user2.ap_id, + user3.nickname + ] + + {:ok, job} = User.Import.follow_import(user1, identifiers) + + assert {:ok, result} = ObanHelpers.perform(job) + assert is_list(result) + assert result == [user2, user3] + assert User.following?(user1, user2) + assert User.following?(user1, user3) + end + end + + describe "blocks_import" do + test "it imports user blocks from list" do + [user1, user2, user3] = insert_list(3, :user) + + identifiers = [ + user2.ap_id, + user3.nickname + ] + + {:ok, job} = User.Import.blocks_import(user1, identifiers) + + assert {:ok, result} = ObanHelpers.perform(job) + assert is_list(result) + assert result == [user2, user3] + assert User.blocks?(user1, user2) + assert User.blocks?(user1, user3) + end + end + + describe "mutes_import" do + test "it imports user mutes from list" do + [user1, user2, user3] = insert_list(3, :user) + + identifiers = [ + user2.ap_id, + user3.nickname + ] + + {:ok, job} = User.Import.mutes_import(user1, identifiers) + + assert {:ok, result} = ObanHelpers.perform(job) + assert is_list(result) + assert result == [user2, user3] + assert User.mutes?(user1, user2) + assert User.mutes?(user1, user3) + end + end +end diff --git a/test/user/notification_setting_test.exs b/test/pleroma/user/notification_setting_test.exs similarity index 100% rename from test/user/notification_setting_test.exs rename to test/pleroma/user/notification_setting_test.exs diff --git a/test/pleroma/user/query_test.exs b/test/pleroma/user/query_test.exs new file mode 100644 index 000000000..e2f5c7d81 --- /dev/null +++ b/test/pleroma/user/query_test.exs @@ -0,0 +1,37 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.QueryTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.User.Query + alias Pleroma.Web.ActivityPub.InternalFetchActor + + import Pleroma.Factory + + describe "internal users" do + test "it filters out internal users by default" do + %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor() + + assert [_user] = User |> Repo.all() + assert [] == %{} |> Query.build() |> Repo.all() + end + + test "it filters out users without nickname by default" do + insert(:user, %{nickname: nil}) + + assert [_user] = User |> Repo.all() + assert [] == %{} |> Query.build() |> Repo.all() + end + + test "it returns internal users when enabled" do + %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor() + insert(:user, %{nickname: nil}) + + assert %{internal: true} |> Query.build() |> Repo.aggregate(:count) == 2 + end + end +end diff --git a/test/user/welcome_chat_massage_test.exs b/test/pleroma/user/welcome_chat_message_test.exs similarity index 100% rename from test/user/welcome_chat_massage_test.exs rename to test/pleroma/user/welcome_chat_message_test.exs diff --git a/test/user/welcome_email_test.exs b/test/pleroma/user/welcome_email_test.exs similarity index 100% rename from test/user/welcome_email_test.exs rename to test/pleroma/user/welcome_email_test.exs diff --git a/test/user/welcome_message_test.exs b/test/pleroma/user/welcome_message_test.exs similarity index 100% rename from test/user/welcome_message_test.exs rename to test/pleroma/user/welcome_message_test.exs diff --git a/test/user_invite_token_test.exs b/test/pleroma/user_invite_token_test.exs similarity index 100% rename from test/user_invite_token_test.exs rename to test/pleroma/user_invite_token_test.exs diff --git a/test/user_relationship_test.exs b/test/pleroma/user_relationship_test.exs similarity index 100% rename from test/user_relationship_test.exs rename to test/pleroma/user_relationship_test.exs diff --git a/test/user_search_test.exs b/test/pleroma/user_search_test.exs similarity index 86% rename from test/user_search_test.exs rename to test/pleroma/user_search_test.exs index 01976bf58..c4b805005 100644 --- a/test/user_search_test.exs +++ b/test/pleroma/user_search_test.exs @@ -17,6 +17,46 @@ defmodule Pleroma.UserSearchTest do describe "User.search" do setup do: clear_config([:instance, :limit_to_local_content]) + test "returns a resolved user as the first result" do + Pleroma.Config.put([:instance, :limit_to_local_content], false) + user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"}) + _user = insert(:user, %{nickname: "com_user"}) + + [first_user, _second_user] = User.search("https://lain.com/users/lain", resolve: true) + + assert first_user.id == user.id + end + + test "returns a user with matching ap_id as the first result" do + user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"}) + _user = insert(:user, %{nickname: "com_user"}) + + [first_user, _second_user] = User.search("https://lain.com/users/lain") + + assert first_user.id == user.id + end + + test "doesn't die if two users have the same uri" do + insert(:user, %{uri: "https://gensokyo.2hu/@raymoo"}) + insert(:user, %{uri: "https://gensokyo.2hu/@raymoo"}) + assert [_first_user, _second_user] = User.search("https://gensokyo.2hu/@raymoo") + end + + test "returns a user with matching uri as the first result" do + user = + insert(:user, %{ + nickname: "no_relation", + ap_id: "https://lain.com/users/lain", + uri: "https://lain.com/@lain" + }) + + _user = insert(:user, %{nickname: "com_user"}) + + [first_user, _second_user] = User.search("https://lain.com/@lain") + + assert first_user.id == user.id + end + test "excludes invisible users from results" do user = insert(:user, %{nickname: "john t1000"}) insert(:user, %{invisible: true, nickname: "john t800"}) @@ -25,6 +65,14 @@ test "excludes invisible users from results" do assert found_user.id == user.id end + test "excludes users when discoverable is false" do + insert(:user, %{nickname: "john 3000", discoverable: false}) + insert(:user, %{nickname: "john 3001"}) + + users = User.search("john") + assert Enum.count(users) == 1 + end + test "excludes service actors from results" do insert(:user, actor_type: "Application", nickname: "user1") service = insert(:user, actor_type: "Service", nickname: "user2") diff --git a/test/user_test.exs b/test/pleroma/user_test.exs similarity index 97% rename from test/user_test.exs rename to test/pleroma/user_test.exs index 301d8f05e..d8ac652af 100644 --- a/test/user_test.exs +++ b/test/pleroma/user_test.exs @@ -174,7 +174,7 @@ test "ap_following returns the following collection for the user" do test "returns all pending follow requests" do unlocked = insert(:user) - locked = insert(:user, locked: true) + locked = insert(:user, is_locked: true) follower = insert(:user) CommonAPI.follow(follower, unlocked) @@ -187,7 +187,7 @@ test "returns all pending follow requests" do end test "doesn't return already accepted or duplicate follow requests" do - locked = insert(:user, locked: true) + locked = insert(:user, is_locked: true) pending_follower = insert(:user) accepted_follower = insert(:user) @@ -201,7 +201,7 @@ test "doesn't return already accepted or duplicate follow requests" do end test "doesn't return follow requests for deactivated accounts" do - locked = insert(:user, locked: true) + locked = insert(:user, is_locked: true) pending_follower = insert(:user, %{deactivated: true}) CommonAPI.follow(pending_follower, locked) @@ -211,7 +211,7 @@ test "doesn't return follow requests for deactivated accounts" do end test "clears follow requests when requester is blocked" do - followed = insert(:user, locked: true) + followed = insert(:user, is_locked: true) follower = insert(:user) CommonAPI.follow(follower, followed) @@ -299,8 +299,8 @@ test "can't subscribe to a user who blocked us" do end test "local users do not automatically follow local locked accounts" do - follower = insert(:user, locked: true) - followed = insert(:user, locked: true) + follower = insert(:user, is_locked: true) + followed = insert(:user, is_locked: true) {:ok, follower} = User.maybe_direct_follow(follower, followed) @@ -509,7 +509,12 @@ test "it sends a confirm email" do cng = User.register_changeset(%User{}, @full_user_data) {:ok, registered_user} = User.register(cng) ObanHelpers.perform_all() - assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(registered_user)) + + Pleroma.Emails.UserEmail.account_confirmation_email(registered_user) + # temporary hackney fix until hackney max_connections bug is fixed + # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 + |> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) + |> assert_email_sent() end test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do @@ -971,23 +976,6 @@ test "it sets the follower_count property" do end end - describe "follow_import" do - test "it imports user followings from list" do - [user1, user2, user3] = insert_list(3, :user) - - identifiers = [ - user2.ap_id, - user3.nickname - ] - - {:ok, job} = User.follow_import(user1, identifiers) - - assert {:ok, result} = ObanHelpers.perform(job) - assert is_list(result) - assert result == [user2, user3] - end - end - describe "mutes" do test "it mutes people" do user = insert(:user) @@ -1194,23 +1182,6 @@ test "follows take precedence over domain blocks" do end end - describe "blocks_import" do - test "it imports user blocks from list" do - [user1, user2, user3] = insert_list(3, :user) - - identifiers = [ - user2.ap_id, - user3.nickname - ] - - {:ok, job} = User.blocks_import(user1, identifiers) - - assert {:ok, result} = ObanHelpers.perform(job) - assert is_list(result) - assert result == [user2, user3] - end - end - describe "get_recipients_from_activity" do test "works for announces" do actor = insert(:user) @@ -1389,7 +1360,7 @@ test "it deactivates a user, all follow relationships and all activities", %{use follower = insert(:user) {:ok, follower} = User.follow(follower, user) - locked_user = insert(:user, name: "locked", locked: true) + locked_user = insert(:user, name: "locked", is_locked: true) {:ok, _} = User.follow(user, locked_user, :follow_pending) object = insert(:note, user: user) @@ -1479,7 +1450,7 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do note_count: 9, follower_count: 9, following_count: 9001, - locked: true, + is_locked: true, confirmation_pending: true, password_reset_pending: true, approval_pending: true, @@ -1505,7 +1476,7 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do user = User.get_by_id(user.id) assert %User{ - bio: nil, + bio: "", raw_bio: nil, email: nil, name: nil, @@ -1521,7 +1492,7 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do note_count: 0, follower_count: 0, following_count: 0, - locked: false, + is_locked: false, confirmation_pending: false, password_reset_pending: false, approval_pending: false, @@ -1676,7 +1647,7 @@ test "returns true when the account is itself" do assert User.visible_for(user, user) == :visible end - test "returns false when the account is unauthenticated and auth is required" do + test "returns false when the account is unconfirmed and confirmation is required" do Pleroma.Config.put([:instance, :account_activation_required], true) user = insert(:user, local: true, confirmation_pending: true) @@ -1685,14 +1656,23 @@ test "returns false when the account is unauthenticated and auth is required" do refute User.visible_for(user, other_user) == :visible end - test "returns true when the account is unauthenticated and auth is not required" do + test "returns true when the account is unconfirmed and confirmation is required but the account is remote" do + Pleroma.Config.put([:instance, :account_activation_required], true) + + user = insert(:user, local: false, confirmation_pending: true) + other_user = insert(:user, local: true) + + assert User.visible_for(user, other_user) == :visible + end + + test "returns true when the account is unconfirmed and confirmation is not required" do user = insert(:user, local: true, confirmation_pending: true) other_user = insert(:user, local: true) assert User.visible_for(user, other_user) == :visible end - test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do + test "returns true when the account is unconfirmed and being viewed by a privileged account (confirmation required)" do Pleroma.Config.put([:instance, :account_activation_required], true) user = insert(:user, local: true, confirmation_pending: true) diff --git a/test/pleroma/utils_test.exs b/test/pleroma/utils_test.exs new file mode 100644 index 000000000..460f7e0b5 --- /dev/null +++ b/test/pleroma/utils_test.exs @@ -0,0 +1,15 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.UtilsTest do + use ExUnit.Case, async: true + + describe "tmp_dir/1" do + test "returns unique temporary directory" do + {:ok, path} = Pleroma.Utils.tmp_dir("emoji") + assert path =~ ~r/\/emoji-(.*)-#{:os.getpid()}-(.*)/ + File.rm_rf(path) + end + end +end diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs similarity index 96% rename from test/web/activity_pub/activity_pub_controller_test.exs rename to test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 0517571f2..b696a24f4 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -156,21 +156,6 @@ test "it returns error when user is not found", %{conn: conn} do assert response == "Not found" end - - test "it requires authentication if instance is NOT federating", %{ - conn: conn - } do - user = insert(:user) - - conn = - put_req_header( - conn, - "accept", - "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" - ) - - ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user) - end end describe "mastodon compatibility routes" do @@ -338,18 +323,6 @@ test "cached purged after object deletion", %{conn: conn} do assert "Not found" == json_response(conn2, :not_found) end - - test "it requires authentication if instance is NOT federating", %{ - conn: conn - } do - user = insert(:user) - note = insert(:note) - uuid = String.split(note.data["id"], "/") |> List.last() - - conn = put_req_header(conn, "accept", "application/activity+json") - - ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user) - end end describe "/activities/:uuid" do @@ -421,18 +394,6 @@ test "cached purged after activity deletion", %{conn: conn} do assert "Not found" == json_response(conn2, :not_found) end - - test "it requires authentication if instance is NOT federating", %{ - conn: conn - } do - user = insert(:user) - activity = insert(:note_activity) - uuid = String.split(activity.data["id"], "/") |> List.last() - - conn = put_req_header(conn, "accept", "application/activity+json") - - ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user) - end end describe "/inbox" do @@ -893,15 +854,6 @@ test "it returns an announce activity in a collection", %{conn: conn} do assert response(conn, 200) =~ announce_activity.data["object"] end - - test "it requires authentication if instance is NOT federating", %{ - conn: conn - } do - user = insert(:user) - conn = put_req_header(conn, "accept", "application/activity+json") - - ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user) - end end describe "POST /users/:nickname/outbox (C2S)" do @@ -1487,9 +1439,9 @@ test "POST /api/ap/upload_media", %{conn: conn} do desc = "Description of the image" image = %Plug.Upload{ - content_type: "image/jpg", + content_type: "bad/content-type", path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" + filename: "an_image.png" } object = @@ -1504,6 +1456,7 @@ test "POST /api/ap/upload_media", %{conn: conn} do assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"] assert is_binary(object_href) assert object_mediatype == "image/jpeg" + assert String.ends_with?(object_href, ".jpg") activity_request = %{ "@context" => "https://www.w3.org/ns/activitystreams", diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs similarity index 91% rename from test/web/activity_pub/activity_pub_test.exs rename to test/pleroma/web/activity_pub/activity_pub_test.exs index b579bb0bb..c6ca37847 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -239,7 +239,7 @@ test "drops activities beyond a certain limit" do } } - assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data) + assert {:error, :remote_limit} = ActivityPub.insert(data) end test "doesn't drop activities with content being null" do @@ -386,9 +386,11 @@ test "can be fetched into a timeline" do end describe "create activities" do - test "it reverts create" do - user = insert(:user) + setup do + [user: insert(:user)] + end + test "it reverts create", %{user: user} do with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do assert {:error, :reverted} = ActivityPub.create(%{ @@ -407,9 +409,47 @@ test "it reverts create" do assert Repo.aggregate(Object, :count, :id) == 0 end - test "removes doubled 'to' recipients" do - user = insert(:user) + test "creates activity if expiration is not configured and expires_at is not passed", %{ + user: user + } do + clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false) + assert {:ok, _} = + ActivityPub.create(%{ + to: ["user1", "user2"], + actor: user, + context: "", + object: %{ + "to" => ["user1", "user2"], + "type" => "Note", + "content" => "testing" + } + }) + end + + test "rejects activity if expires_at present but expiration is not configured", %{user: user} do + clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false) + + assert {:error, :expired_activities_disabled} = + ActivityPub.create(%{ + to: ["user1", "user2"], + actor: user, + context: "", + object: %{ + "to" => ["user1", "user2"], + "type" => "Note", + "content" => "testing" + }, + additional: %{ + "expires_at" => DateTime.utc_now() + } + }) + + assert Repo.aggregate(Activity, :count, :id) == 0 + assert Repo.aggregate(Object, :count, :id) == 0 + end + + test "removes doubled 'to' recipients", %{user: user} do {:ok, activity} = ActivityPub.create(%{ to: ["user1", "user1", "user2"], @@ -427,9 +467,7 @@ test "removes doubled 'to' recipients" do assert activity.recipients == ["user1", "user2", user.ap_id] end - test "increases user note count only for public activities" do - user = insert(:user) - + test "increases user note count only for public activities", %{user: user} do {:ok, _} = CommonAPI.post(User.get_cached_by_id(user.id), %{ status: "1", @@ -458,8 +496,7 @@ test "increases user note count only for public activities" do assert user.note_count == 2 end - test "increases replies count" do - user = insert(:user) + test "increases replies count", %{user: user} do user2 = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "1", visibility: "public"}) @@ -468,22 +505,22 @@ test "increases replies count" do # public {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "public")) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) + assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 1 # unlisted {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "unlisted")) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) + assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 2 # private {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "private")) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) + assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 2 # direct {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "direct")) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) + assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 2 end end @@ -992,7 +1029,7 @@ test "returns reblogs for users for whom reblogs have not been muted" do describe "uploading files" do setup do test_file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -1083,7 +1120,7 @@ test "creates an undo activity for the last follow" do test "creates an undo activity for a pending follow request" do follower = insert(:user) - followed = insert(:user, %{locked: true}) + followed = insert(:user, %{is_locked: true}) {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) {:ok, activity} = ActivityPub.unfollow(follower, followed) @@ -1373,19 +1410,25 @@ test "doesn't crash when follower and following counters are hidden" do mock(fn env -> case env.url do "http://localhost:4001/users/masto_hidden_counters/following" -> - json(%{ - "@context" => "https://www.w3.org/ns/activitystreams", - "id" => "http://localhost:4001/users/masto_hidden_counters/followers" - }) + json( + %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "http://localhost:4001/users/masto_hidden_counters/followers" + }, + headers: HttpRequestMock.activitypub_object_headers() + ) "http://localhost:4001/users/masto_hidden_counters/following?page=1" -> %Tesla.Env{status: 403, body: ""} "http://localhost:4001/users/masto_hidden_counters/followers" -> - json(%{ - "@context" => "https://www.w3.org/ns/activitystreams", - "id" => "http://localhost:4001/users/masto_hidden_counters/following" - }) + json( + %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "http://localhost:4001/users/masto_hidden_counters/following" + }, + headers: HttpRequestMock.activitypub_object_headers() + ) "http://localhost:4001/users/masto_hidden_counters/followers?page=1" -> %Tesla.Env{status: 403, body: ""} @@ -2077,18 +2120,25 @@ test "it just returns the input if the user has no following/follower addresses" end describe "global activity expiration" do - setup do: clear_config([:mrf, :policies]) - test "creates an activity expiration for local Create activities" do - Pleroma.Config.put( - [:mrf, :policies], - Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy + clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy) + + {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"}) + {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"}) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity.id}, + scheduled_at: + activity.inserted_at + |> DateTime.from_naive!("Etc/UTC") + |> Timex.shift(days: 365) ) - {:ok, %{id: id_create}} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"}) - {:ok, _follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"}) - - assert [%{activity_id: ^id_create}] = Pleroma.ActivityExpiration |> Repo.all() + refute_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: follow.id} + ) end end @@ -2133,4 +2183,95 @@ test "does nothing with a clashing nickname and the same ap id" do assert user.nickname == orig_user.nickname end end + + describe "reply filtering" do + test "`following` still contains announcements by friends" do + user = insert(:user) + followed = insert(:user) + not_followed = insert(:user) + + User.follow(user, followed) + + {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"}) + + {:ok, not_followed_to_followed} = + CommonAPI.post(not_followed, %{ + status: "Also hello", + in_reply_to_status_id: followed_post.id + }) + + {:ok, retoot} = CommonAPI.repeat(not_followed_to_followed.id, followed) + + params = + %{} + |> Map.put(:type, ["Create", "Announce"]) + |> Map.put(:blocking_user, user) + |> Map.put(:muting_user, user) + |> Map.put(:reply_filtering_user, user) + |> Map.put(:reply_visibility, "following") + |> Map.put(:announce_filtering_user, user) + |> Map.put(:user, user) + + activities = + [user.ap_id | User.following(user)] + |> ActivityPub.fetch_activities(params) + + followed_post_id = followed_post.id + retoot_id = retoot.id + + assert [%{id: ^followed_post_id}, %{id: ^retoot_id}] = activities + + assert length(activities) == 2 + end + + # This test is skipped because, while this is the desired behavior, + # there seems to be no good way to achieve it with the method that + # we currently use for detecting to who a reply is directed. + # This is a TODO and should be fixed by a later rewrite of the code + # in question. + @tag skip: true + test "`following` still contains self-replies by friends" do + user = insert(:user) + followed = insert(:user) + not_followed = insert(:user) + + User.follow(user, followed) + + {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"}) + {:ok, not_followed_post} = CommonAPI.post(not_followed, %{status: "Also hello"}) + + {:ok, _followed_to_not_followed} = + CommonAPI.post(followed, %{status: "sup", in_reply_to_status_id: not_followed_post.id}) + + {:ok, _followed_self_reply} = + CommonAPI.post(followed, %{status: "Also cofe", in_reply_to_status_id: followed_post.id}) + + params = + %{} + |> Map.put(:type, ["Create", "Announce"]) + |> Map.put(:blocking_user, user) + |> Map.put(:muting_user, user) + |> Map.put(:reply_filtering_user, user) + |> Map.put(:reply_visibility, "following") + |> Map.put(:announce_filtering_user, user) + |> Map.put(:user, user) + + activities = + [user.ap_id | User.following(user)] + |> ActivityPub.fetch_activities(params) + + assert length(activities) == 2 + end + end + + test "allow fetching of accounts with an empty string name field" do + Tesla.Mock.mock(fn + %{method: :get, url: "https://princess.cat/users/mewmew"} -> + file = File.read!("test/fixtures/mewmew_no_name.json") + %Tesla.Env{status: 200, body: file, headers: HttpRequestMock.activitypub_object_headers()} + end) + + {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew") + assert user.name == " " + end end diff --git a/test/web/activity_pub/mrf/activity_expiration_policy_test.exs b/test/pleroma/web/activity_pub/mrf/activity_expiration_policy_test.exs similarity index 90% rename from test/web/activity_pub/mrf/activity_expiration_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/activity_expiration_policy_test.exs index f25cf8b12..e7370d4ef 100644 --- a/test/web/activity_pub/mrf/activity_expiration_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/activity_expiration_policy_test.exs @@ -18,11 +18,11 @@ test "adds `expires_at` property" do "object" => %{"type" => "Note"} }) - assert Timex.diff(expires_at, NaiveDateTime.utc_now(), :days) == 364 + assert Timex.diff(expires_at, DateTime.utc_now(), :days) == 364 end test "keeps existing `expires_at` if it less than the config setting" do - expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: 1) + expires_at = DateTime.utc_now() |> Timex.shift(days: 1) assert {:ok, %{"type" => "Create", "expires_at" => ^expires_at}} = ActivityExpirationPolicy.filter(%{ @@ -35,7 +35,7 @@ test "keeps existing `expires_at` if it less than the config setting" do end test "overwrites existing `expires_at` if it greater than the config setting" do - too_distant_future = NaiveDateTime.utc_now() |> Timex.shift(years: 2) + too_distant_future = DateTime.utc_now() |> Timex.shift(years: 2) assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} = ActivityExpirationPolicy.filter(%{ @@ -46,7 +46,7 @@ test "overwrites existing `expires_at` if it greater than the config setting" do "object" => %{"type" => "Note"} }) - assert Timex.diff(expires_at, NaiveDateTime.utc_now(), :days) == 364 + assert Timex.diff(expires_at, DateTime.utc_now(), :days) == 364 end test "ignores remote activities" do diff --git a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/anti_followbot_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs diff --git a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/anti_link_spam_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs diff --git a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs similarity index 100% rename from test/web/activity_pub/mrf/ensure_re_prepended_test.exs rename to test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs diff --git a/test/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs b/test/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs new file mode 100644 index 000000000..86dd9ddae --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicyTest do + use Pleroma.DataCase + import Pleroma.Factory + + alias Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy + @public "https://www.w3.org/ns/activitystreams#Public" + + defp generate_messages(actor) do + {%{ + "actor" => actor.ap_id, + "type" => "Create", + "object" => %{}, + "to" => [@public, "f"], + "cc" => [actor.follower_address, "d"] + }, + %{ + "actor" => actor.ap_id, + "type" => "Create", + "object" => %{"to" => ["f", actor.follower_address], "cc" => ["d", @public]}, + "to" => ["f", actor.follower_address], + "cc" => ["d", @public] + }} + end + + test "removes from the federated timeline by nickname heuristics 1" do + actor = insert(:user, %{nickname: "annoying_ebooks@example.com"}) + + {message, except_message} = generate_messages(actor) + + assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message} + end + + test "removes from the federated timeline by nickname heuristics 2" do + actor = insert(:user, %{nickname: "cirnonewsnetworkbot@meow.cat"}) + + {message, except_message} = generate_messages(actor) + + assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message} + end + + test "removes from the federated timeline by actor type Application" do + actor = insert(:user, %{actor_type: "Application"}) + + {message, except_message} = generate_messages(actor) + + assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message} + end + + test "removes from the federated timeline by actor type Service" do + actor = insert(:user, %{actor_type: "Service"}) + + {message, except_message} = generate_messages(actor) + + assert ForceBotUnlistedPolicy.filter(message) == {:ok, except_message} + end +end diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/pleroma/web/activity_pub/mrf/hellthread_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/hellthread_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/hellthread_policy_test.exs diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/keyword_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs diff --git a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs similarity index 95% rename from test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs index 313d59a66..1710c4d2a 100644 --- a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs @@ -22,6 +22,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do } } + setup do: clear_config([:media_proxy, :enabled], true) + test "it prefetches media proxy URIs" do with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do MediaProxyWarmingPolicy.filter(@message) diff --git a/test/web/activity_pub/mrf/mention_policy_test.exs b/test/pleroma/web/activity_pub/mrf/mention_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/mention_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/mention_policy_test.exs diff --git a/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs b/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs diff --git a/test/web/activity_pub/mrf/normalize_markup_test.exs b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs similarity index 100% rename from test/web/activity_pub/mrf/normalize_markup_test.exs rename to test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs diff --git a/test/web/activity_pub/mrf/object_age_policy_test.exs b/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/object_age_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs diff --git a/test/web/activity_pub/mrf/reject_non_public_test.exs b/test/pleroma/web/activity_pub/mrf/reject_non_public_test.exs similarity index 92% rename from test/web/activity_pub/mrf/reject_non_public_test.exs rename to test/pleroma/web/activity_pub/mrf/reject_non_public_test.exs index 58b46b9a2..e08eb3ba6 100644 --- a/test/web/activity_pub/mrf/reject_non_public_test.exs +++ b/test/pleroma/web/activity_pub/mrf/reject_non_public_test.exs @@ -21,7 +21,7 @@ test "it's allowed when address is public" do "type" => "Create" } - assert {:ok, message} = RejectNonPublic.filter(message) + assert {:ok, _message} = RejectNonPublic.filter(message) end test "it's allowed when cc address contain public address" do @@ -34,7 +34,7 @@ test "it's allowed when cc address contain public address" do "type" => "Create" } - assert {:ok, message} = RejectNonPublic.filter(message) + assert {:ok, _message} = RejectNonPublic.filter(message) end end @@ -50,7 +50,7 @@ test "it's allowed when addrer of message in the follower addresses of user and } Pleroma.Config.put([:mrf_rejectnonpublic, :allow_followersonly], true) - assert {:ok, message} = RejectNonPublic.filter(message) + assert {:ok, _message} = RejectNonPublic.filter(message) end test "it's rejected when addrer of message in the follower addresses of user and it disabled in config" do @@ -80,7 +80,7 @@ test "it's allows when direct messages are allow" do } Pleroma.Config.put([:mrf_rejectnonpublic, :allow_direct], true) - assert {:ok, message} = RejectNonPublic.filter(message) + assert {:ok, _message} = RejectNonPublic.filter(message) end test "it's reject when direct messages aren't allow" do diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/simple_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/simple_policy_test.exs diff --git a/test/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/steal_emoji_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs diff --git a/test/web/activity_pub/mrf/subchain_policy_test.exs b/test/pleroma/web/activity_pub/mrf/subchain_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/subchain_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/subchain_policy_test.exs diff --git a/test/web/activity_pub/mrf/tag_policy_test.exs b/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs similarity index 98% rename from test/web/activity_pub/mrf/tag_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/tag_policy_test.exs index 6ff71d640..ffc30ba62 100644 --- a/test/web/activity_pub/mrf/tag_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs @@ -29,7 +29,7 @@ test "allows non-local follow requests" do actor = insert(:user, tags: ["mrf_tag:disable-remote-subscription"]) follower = insert(:user, tags: ["mrf_tag:disable-remote-subscription"], local: true) message = %{"object" => actor.ap_id, "type" => "Follow", "actor" => follower.ap_id} - assert {:ok, message} = TagPolicy.filter(message) + assert {:ok, _message} = TagPolicy.filter(message) end end diff --git a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs b/test/pleroma/web/activity_pub/mrf/user_allow_list_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/user_allowlist_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/user_allow_list_policy_test.exs diff --git a/test/web/activity_pub/mrf/vocabulary_policy_test.exs b/test/pleroma/web/activity_pub/mrf/vocabulary_policy_test.exs similarity index 100% rename from test/web/activity_pub/mrf/vocabulary_policy_test.exs rename to test/pleroma/web/activity_pub/mrf/vocabulary_policy_test.exs diff --git a/test/web/activity_pub/mrf/mrf_test.exs b/test/pleroma/web/activity_pub/mrf_test.exs similarity index 91% rename from test/web/activity_pub/mrf/mrf_test.exs rename to test/pleroma/web/activity_pub/mrf_test.exs index a63b25423..e8cdde2e1 100644 --- a/test/web/activity_pub/mrf/mrf_test.exs +++ b/test/pleroma/web/activity_pub/mrf_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.ActivityPub.MRFTest do use ExUnit.Case, async: true use Pleroma.Tests.Helpers @@ -61,6 +65,8 @@ test "matches are case-insensitive" do describe "describe/0" do test "it works as expected with noop policy" do + clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy]) + expected = %{ mrf_policies: ["NoOpPolicy"], exclusions: false diff --git a/test/web/activity_pub/object_validators/accept_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/accept_validation_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/accept_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/accept_validation_test.exs diff --git a/test/web/activity_pub/object_validators/announce_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs similarity index 97% rename from test/web/activity_pub/object_validators/announce_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs index 623342f76..4771c4698 100644 --- a/test/web/activity_pub/object_validators/announce_validation_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnouncValidationTest do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidationTest do use Pleroma.DataCase alias Pleroma.Object diff --git a/test/web/activity_pub/object_validators/note_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs similarity index 76% rename from test/web/activity_pub/object_validators/note_validator_test.exs rename to test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs index 30c481ffb..cc6dab872 100644 --- a/test/web/activity_pub/object_validators/note_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do use Pleroma.DataCase - alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator alias Pleroma.Web.ActivityPub.Utils import Pleroma.Factory @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do end test "a basic note validates", %{note: note} do - %{valid?: true} = NoteValidator.cast_and_validate(note) + %{valid?: true} = ArticleNoteValidator.cast_and_validate(note) end end end diff --git a/test/web/activity_pub/object_validators/attachment_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs similarity index 98% rename from test/web/activity_pub/object_validators/attachment_validator_test.exs rename to test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs index 558bb3131..760388e80 100644 --- a/test/web/activity_pub/object_validators/attachment_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs @@ -56,7 +56,7 @@ test "it handles our own uploads" do user = insert(:user) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } diff --git a/test/web/activity_pub/object_validators/block_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/block_validation_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/block_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/block_validation_test.exs diff --git a/test/web/activity_pub/object_validators/chat_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/chat_validation_test.exs similarity index 97% rename from test/web/activity_pub/object_validators/chat_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/chat_validation_test.exs index 50bf03515..d7e299224 100644 --- a/test/web/activity_pub/object_validators/chat_validation_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/chat_validation_test.exs @@ -69,6 +69,7 @@ test "validates for a basic object we build", %{valid_chat_message: valid_chat_m assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, []) assert Map.put(valid_chat_message, "attachment", nil) == object + assert match?(%{"firefox" => _}, object["emoji"]) end test "validates for a basic object with an attachment", %{ @@ -76,7 +77,7 @@ test "validates for a basic object with an attachment", %{ user: user } do file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -97,7 +98,7 @@ test "validates for a basic object with an attachment in an array", %{ user: user } do file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -118,7 +119,7 @@ test "validates for a basic object with an attachment but without content", %{ user: user } do file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } diff --git a/test/web/activity_pub/object_validators/delete_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/delete_validation_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/delete_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/delete_validation_test.exs diff --git a/test/web/activity_pub/object_validators/emoji_react_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/emoji_react_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs diff --git a/test/web/activity_pub/object_validators/follow_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/follow_validation_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/follow_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/follow_validation_test.exs diff --git a/test/web/activity_pub/object_validators/like_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/like_validation_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/like_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/like_validation_test.exs diff --git a/test/web/activity_pub/object_validators/reject_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/reject_validation_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/reject_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/reject_validation_test.exs diff --git a/test/web/activity_pub/object_validators/undo_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/undo_handling_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/undo_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/undo_handling_test.exs diff --git a/test/web/activity_pub/object_validators/update_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/update_handling_test.exs similarity index 100% rename from test/web/activity_pub/object_validators/update_validation_test.exs rename to test/pleroma/web/activity_pub/object_validators/update_handling_test.exs diff --git a/test/web/activity_pub/pipeline_test.exs b/test/pleroma/web/activity_pub/pipeline_test.exs similarity index 100% rename from test/web/activity_pub/pipeline_test.exs rename to test/pleroma/web/activity_pub/pipeline_test.exs diff --git a/test/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs similarity index 100% rename from test/web/activity_pub/publisher_test.exs rename to test/pleroma/web/activity_pub/publisher_test.exs diff --git a/test/web/activity_pub/relay_test.exs b/test/pleroma/web/activity_pub/relay_test.exs similarity index 76% rename from test/web/activity_pub/relay_test.exs rename to test/pleroma/web/activity_pub/relay_test.exs index 9d657ac4f..3284980f7 100644 --- a/test/web/activity_pub/relay_test.exs +++ b/test/pleroma/web/activity_pub/relay_test.exs @@ -63,6 +63,46 @@ test "returns activity" do assert activity.data["to"] == [user.ap_id] refute "#{user.ap_id}/followers" in User.following(service_actor) end + + test "force unfollow when target service is dead" do + user = insert(:user) + user_ap_id = user.ap_id + user_id = user.id + + Tesla.Mock.mock(fn %{method: :get, url: ^user_ap_id} -> + %Tesla.Env{status: 404} + end) + + service_actor = Relay.get_actor() + CommonAPI.follow(service_actor, user) + assert "#{user.ap_id}/followers" in User.following(service_actor) + + assert Pleroma.Repo.get_by( + Pleroma.FollowingRelationship, + follower_id: service_actor.id, + following_id: user_id + ) + + Pleroma.Repo.delete(user) + Cachex.clear(:user_cache) + + assert {:ok, %Activity{} = activity} = Relay.unfollow(user_ap_id, %{force: true}) + + assert refresh_record(service_actor).following_count == 0 + + refute Pleroma.Repo.get_by( + Pleroma.FollowingRelationship, + follower_id: service_actor.id, + following_id: user_id + ) + + assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay" + assert user.ap_id in activity.recipients + assert activity.data["type"] == "Undo" + assert activity.data["actor"] == service_actor.ap_id + assert activity.data["to"] == [user_ap_id] + refute "#{user.ap_id}/followers" in User.following(service_actor) + end end describe "publish/1" do diff --git a/test/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs similarity index 100% rename from test/web/activity_pub/side_effects_test.exs rename to test/pleroma/web/activity_pub/side_effects_test.exs diff --git a/test/web/activity_pub/transmogrifier/accept_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs similarity index 96% rename from test/web/activity_pub/transmogrifier/accept_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs index 77d468f5c..c6ff96f08 100644 --- a/test/web/activity_pub/transmogrifier/accept_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs @@ -46,7 +46,7 @@ test "it works for incoming accepts which were pre-accepted" do test "it works for incoming accepts which are referenced by IRI only" do follower = insert(:user) - followed = insert(:user, locked: true) + followed = insert(:user, is_locked: true) {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) @@ -72,7 +72,7 @@ test "it works for incoming accepts which are referenced by IRI only" do test "it fails for incoming accepts which cannot be correlated" do follower = insert(:user) - followed = insert(:user, locked: true) + followed = insert(:user, is_locked: true) accept_data = File.read!("test/fixtures/mastodon-accept-activity.json") diff --git a/test/web/activity_pub/transmogrifier/announce_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs similarity index 95% rename from test/web/activity_pub/transmogrifier/announce_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs index e895636b5..99c296c74 100644 --- a/test/web/activity_pub/transmogrifier/announce_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs @@ -60,7 +60,11 @@ test "it works for incoming announces, fetching the announced object" do Tesla.Mock.mock(fn %{method: :get} -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/mastodon-note-object.json")} + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/mastodon-note-object.json"), + headers: HttpRequestMock.activitypub_object_headers() + } end) _user = insert(:user, local: false, ap_id: data["actor"]) @@ -144,7 +148,7 @@ test "it rejects incoming announces with an inlined activity from another origin _user = insert(:user, local: false, ap_id: data["actor"]) - assert {:error, e} = Transmogrifier.handle_incoming(data) + assert {:error, _e} = Transmogrifier.handle_incoming(data) end test "it does not clobber the addressing on announce activities" do diff --git a/test/web/activity_pub/transmogrifier/answer_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs similarity index 100% rename from test/web/activity_pub/transmogrifier/answer_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs diff --git a/test/pleroma/web/activity_pub/transmogrifier/article_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/article_handling_test.exs new file mode 100644 index 000000000..b0ae804c5 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/article_handling_test.exs @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.ArticleHandlingTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Object.Fetcher + alias Pleroma.Web.ActivityPub.Transmogrifier + + test "Pterotype (Wordpress Plugin) Article" do + Tesla.Mock.mock(fn %{url: "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/wedistribute-user.json"), + headers: HttpRequestMock.activitypub_object_headers() + } + end) + + data = + File.read!("test/fixtures/tesla_mock/wedistribute-create-article.json") |> Jason.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + object = Object.normalize(data["object"]) + + assert object.data["name"] == "The end is near: Mastodon plans to drop OStatus support" + + assert object.data["summary"] == + "One of the largest platforms in the federated social web is dropping the protocol that it started with." + + assert object.data["url"] == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/" + end + + test "Plume Article" do + Tesla.Mock.mock(fn + %{url: "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json"), + headers: HttpRequestMock.activitypub_object_headers() + } + + %{url: "https://baptiste.gelez.xyz/@/BaptisteGelez"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json"), + headers: HttpRequestMock.activitypub_object_headers() + } + end) + + {:ok, object} = + Fetcher.fetch_object_from_id( + "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/" + ) + + assert object.data["name"] == "This Month in Plume: June 2018" + + assert object.data["url"] == + "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/" + end + + test "Prismo Article" do + Tesla.Mock.mock(fn %{url: "https://prismo.news/@mxb"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json"), + headers: HttpRequestMock.activitypub_object_headers() + } + end) + + data = File.read!("test/fixtures/prismo-url-map.json") |> Jason.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + object = Object.normalize(data["object"]) + + assert object.data["url"] == "https://prismo.news/posts/83" + end +end diff --git a/test/web/activity_pub/transmogrifier/audio_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs similarity index 97% rename from test/web/activity_pub/transmogrifier/audio_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs index 0636d00c5..181eb7b09 100644 --- a/test/web/activity_pub/transmogrifier/audio_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs @@ -48,7 +48,8 @@ test "Funkwhale Audio object" do %{url: "https://channels.tests.funkwhale.audio/federation/actors/compositions"} -> %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/funkwhale_channel.json") + body: File.read!("test/fixtures/tesla_mock/funkwhale_channel.json"), + headers: HttpRequestMock.activitypub_object_headers() } end) diff --git a/test/web/activity_pub/transmogrifier/block_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs similarity index 100% rename from test/web/activity_pub/transmogrifier/block_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/chat_message_test.exs b/test/pleroma/web/activity_pub/transmogrifier/chat_message_test.exs similarity index 100% rename from test/web/activity_pub/transmogrifier/chat_message_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/chat_message_test.exs diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs similarity index 100% rename from test/web/activity_pub/transmogrifier/delete_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs similarity index 100% rename from test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/event_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs similarity index 88% rename from test/web/activity_pub/transmogrifier/event_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs index 7f1ef2cbd..d7c55cfbe 100644 --- a/test/web/activity_pub/transmogrifier/event_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs @@ -13,13 +13,15 @@ test "Mobilizon Event object" do %{url: "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"} -> %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/mobilizon.org-event.json") + body: File.read!("test/fixtures/tesla_mock/mobilizon.org-event.json"), + headers: HttpRequestMock.activitypub_object_headers() } %{url: "https://mobilizon.org/@tcit"} -> %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/mobilizon.org-user.json") + body: File.read!("test/fixtures/tesla_mock/mobilizon.org-user.json"), + headers: HttpRequestMock.activitypub_object_headers() } end) diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs similarity index 98% rename from test/web/activity_pub/transmogrifier/follow_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs index 757d90941..4ef8210ad 100644 --- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs @@ -65,7 +65,7 @@ test "it works for incoming follow requests" do end test "with locked accounts, it does create a Follow, but not an Accept" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) data = File.read!("test/fixtures/mastodon-follow-activity.json") @@ -188,7 +188,7 @@ test "it works for incoming follow requests from hubzilla" do test "it works for incoming follows to locked account" do pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin") - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) data = File.read!("test/fixtures/mastodon-follow-activity.json") diff --git a/test/web/activity_pub/transmogrifier/like_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs similarity index 100% rename from test/web/activity_pub/transmogrifier/like_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/question_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs similarity index 69% rename from test/web/activity_pub/transmogrifier/question_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs index c82361828..d2822ce75 100644 --- a/test/web/activity_pub/transmogrifier/question_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs @@ -106,12 +106,63 @@ test "Mastodon Question activity with HTML tags in plaintext" do assert Enum.sort(object.data["oneOf"]) == Enum.sort(options) end - test "returns an error if received a second time" do + test "Mastodon Question activity with custom emojis" do + options = [ + %{ + "type" => "Note", + "name" => ":blobcat:", + "replies" => %{"totalItems" => 0, "type" => "Collection"} + }, + %{ + "type" => "Note", + "name" => ":blobfox:", + "replies" => %{"totalItems" => 0, "type" => "Collection"} + } + ] + + tag = [ + %{ + "icon" => %{ + "type" => "Image", + "url" => "https://blob.cat/emoji/custom/blobcats/blobcat.png" + }, + "id" => "https://blob.cat/emoji/custom/blobcats/blobcat.png", + "name" => ":blobcat:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + }, + %{ + "icon" => %{"type" => "Image", "url" => "https://blob.cat/emoji/blobfox/blobfox.png"}, + "id" => "https://blob.cat/emoji/blobfox/blobfox.png", + "name" => ":blobfox:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + } + ] + + data = + File.read!("test/fixtures/mastodon-question-activity.json") + |> Poison.decode!() + |> Kernel.put_in(["object", "oneOf"], options) + |> Kernel.put_in(["object", "tag"], tag) + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + object = Object.normalize(activity, false) + + assert object.data["oneOf"] == options + + assert object.data["emoji"] == %{ + "blobcat" => "https://blob.cat/emoji/custom/blobcats/blobcat.png", + "blobfox" => "https://blob.cat/emoji/blobfox/blobfox.png" + } + end + + test "returns same activity if received a second time" do data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!() assert {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) - assert {:error, {:validate_object, {:error, _}}} = Transmogrifier.handle_incoming(data) + assert {:ok, ^activity} = Transmogrifier.handle_incoming(data) end test "accepts a Question with no content" do diff --git a/test/web/activity_pub/transmogrifier/reject_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs similarity index 95% rename from test/web/activity_pub/transmogrifier/reject_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs index 7592fbe1c..5c1451def 100644 --- a/test/web/activity_pub/transmogrifier/reject_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs @@ -14,7 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.RejectHandlingTest do test "it fails for incoming rejects which cannot be correlated" do follower = insert(:user) - followed = insert(:user, locked: true) + followed = insert(:user, is_locked: true) accept_data = File.read!("test/fixtures/mastodon-reject-activity.json") @@ -33,7 +33,7 @@ test "it fails for incoming rejects which cannot be correlated" do test "it works for incoming rejects which are referenced by IRI only" do follower = insert(:user) - followed = insert(:user, locked: true) + followed = insert(:user, is_locked: true) {:ok, follower} = User.follow(follower, followed) {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs similarity index 100% rename from test/web/activity_pub/transmogrifier/undo_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs diff --git a/test/web/activity_pub/transmogrifier/user_update_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs similarity index 99% rename from test/web/activity_pub/transmogrifier/user_update_handling_test.exs rename to test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs index 64636656c..7c4d16db7 100644 --- a/test/web/activity_pub/transmogrifier/user_update_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs @@ -154,6 +154,6 @@ test "it works for incoming update activities which lock the account" do {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(update_data) user = User.get_cached_by_ap_id(user.ap_id) - assert user.locked == true + assert user.is_locked == true end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs new file mode 100644 index 000000000..69c953a2e --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.VideoHandlingTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Object.Fetcher + alias Pleroma.Web.ActivityPub.Transmogrifier + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + test "skip converting the content when it is nil" do + data = + File.read!("test/fixtures/tesla_mock/framatube.org-video.json") + |> Jason.decode!() + |> Kernel.put_in(["object", "content"], nil) + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert object = Object.normalize(activity, false) + + assert object.data["content"] == nil + end + + test "it converts content of object to html" do + data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!() + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert object = Object.normalize(activity, false) + + assert object.data["content"] == + "

Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?

Transcription par @aprilorg ici : https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft

" + end + + test "it remaps video URLs as attachments if necessary" do + {:ok, object} = + Fetcher.fetch_object_from_id( + "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" + ) + + assert object.data["url"] == + "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" + + assert object.data["attachment"] == [ + %{ + "type" => "Link", + "mediaType" => "video/mp4", + "name" => nil, + "url" => [ + %{ + "href" => + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ] + + data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!() + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert object = Object.normalize(activity, false) + + assert object.data["attachment"] == [ + %{ + "type" => "Link", + "mediaType" => "video/mp4", + "name" => nil, + "url" => [ + %{ + "href" => + "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ] + + assert object.data["url"] == + "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206" + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs similarity index 86% rename from test/web/activity_pub/transmogrifier_test.exs rename to test/pleroma/web/activity_pub/transmogrifier_test.exs index 3fa41b0c7..e39af1dfc 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do alias Pleroma.Activity alias Pleroma.Object - alias Pleroma.Object.Fetcher alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.ActivityPub.Transmogrifier @@ -45,15 +44,6 @@ test "it works for incoming notices with tag not being an array (kroeg)" do assert "test" in object.data["tag"] end - test "it works for incoming notices with url not being a string (prismo)" do - data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!() - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - object = Object.normalize(data["object"]) - - assert object.data["url"] == "https://prismo.news/posts/83" - end - test "it cleans up incoming notices which are not really DMs" do user = insert(:user) other_user = insert(:user) @@ -105,18 +95,19 @@ test "it fetches reply-to activities if we don't have them" do object = data["object"] - |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873") + |> Map.put("inReplyTo", "https://mstdn.io/users/mayuutann/statuses/99568293732299394") data = Map.put(data, "object", object) {:ok, returned_activity} = Transmogrifier.handle_incoming(data) returned_object = Object.normalize(returned_activity, false) - assert activity = + assert %Activity{} = Activity.get_create_by_object_ap_id( - "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" ) - assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + assert returned_object.data["inReplyTo"] == + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" end test "it does not fetch reply-to activities beyond max replies depth limit" do @@ -140,8 +131,7 @@ test "it does not fetch reply-to activities beyond max replies depth limit" do "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" ) - assert returned_object.data["inReplyToAtomUri"] == - "https://shitposter.club/notice/2827873" + assert returned_object.data["inReplyTo"] == "https://shitposter.club/notice/2827873" end end @@ -216,6 +206,16 @@ test "it works for incoming notices" do assert user.note_count == 1 end + test "it works for incoming notices without the sensitive property but an nsfw hashtag" do + data = File.read!("test/fixtures/mastodon-post-activity-nsfw.json") |> Poison.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + object_data = Object.normalize(data["object"], false).data + + assert object_data["sensitive"] == true + end + test "it works for incoming notices with hashtags" do data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!() @@ -355,83 +355,6 @@ test "it works for incoming unfollows with an existing follow" do refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) end - test "skip converting the content when it is nil" do - object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" - - {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id) - - result = - Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil})) - - assert result["content"] == nil - end - - test "it converts content of object to html" do - object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" - - {:ok, %{"content" => content_markdown}} = - Fetcher.fetch_and_contain_remote_object_from_id(object_id) - - {:ok, %Pleroma.Object{data: %{"content" => content}} = object} = - Fetcher.fetch_object_from_id(object_id) - - assert content_markdown == - "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..." - - assert content == - "

Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership

Twenty Years in Jail: FreeBSD’s Jails, Then and Now

Jails started as a limited virtualization system, but over the last two years they’ve…

" - - assert object.data["mediaType"] == "text/html" - end - - test "it remaps video URLs as attachments if necessary" do - {:ok, object} = - Fetcher.fetch_object_from_id( - "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" - ) - - assert object.data["url"] == - "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" - - assert object.data["attachment"] == [ - %{ - "type" => "Link", - "mediaType" => "video/mp4", - "url" => [ - %{ - "href" => - "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } - ] - } - ] - - {:ok, object} = - Fetcher.fetch_object_from_id( - "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206" - ) - - assert object.data["attachment"] == [ - %{ - "type" => "Link", - "mediaType" => "video/mp4", - "url" => [ - %{ - "href" => - "https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } - ] - } - ] - - assert object.data["url"] == - "https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206" - end - test "it accepts Flag activities" do user = insert(:user) other_user = insert(:user) @@ -1072,7 +995,7 @@ test "returns not modified object when hasn't containts inReplyTo field", %{data assert Transmogrifier.fix_in_reply_to(data) == data end - test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do + test "returns object with inReplyTo when denied incoming reply", %{data: data} do Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) object_with_reply = @@ -1080,26 +1003,22 @@ test "returns object with inReplyToAtomUri when denied incoming reply", %{data: modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873" - assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" object_with_reply = Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"}) modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"} - assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" object_with_reply = Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"]) modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"] - assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" object_with_reply = Map.put(data["object"], "inReplyTo", []) modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == [] - assert modified_object["inReplyToAtomUri"] == "" end @tag capture_log: true @@ -1108,19 +1027,17 @@ test "returns modified object when allowed incoming reply", %{data: data} do Map.put( data["object"], "inReplyTo", - "https://shitposter.club/notice/2827873" + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" ) Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5) modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) assert modified_object["inReplyTo"] == - "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" - - assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" assert modified_object["context"] == - "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" + "tag:shitposter.club,2018-02-22:objectType=thread:nonce=e5a7c72d60a9c0e4" end end @@ -1139,75 +1056,7 @@ test "fixes data for object when url is map" do } end - test "fixes data for video object" do - object = %{ - "type" => "Video", - "url" => [ - %{ - "type" => "Link", - "mimeType" => "video/mp4", - "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" - }, - %{ - "type" => "Link", - "mimeType" => "video/mp4", - "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4" - }, - %{ - "type" => "Link", - "mimeType" => "text/html", - "href" => "https://peertube.-2d4c2d1630e3" - }, - %{ - "type" => "Link", - "mimeType" => "text/html", - "href" => "https://peertube.-2d4c2d16377-42" - } - ] - } - - assert Transmogrifier.fix_url(object) == %{ - "attachment" => [ - %{ - "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4", - "mimeType" => "video/mp4", - "type" => "Link" - } - ], - "type" => "Video", - "url" => "https://peertube.-2d4c2d1630e3" - } - end - - test "fixes url for not Video object" do - object = %{ - "type" => "Text", - "url" => [ - %{ - "type" => "Link", - "mimeType" => "text/html", - "href" => "https://peertube.-2d4c2d1630e3" - }, - %{ - "type" => "Link", - "mimeType" => "text/html", - "href" => "https://peertube.-2d4c2d16377-42" - } - ] - } - - assert Transmogrifier.fix_url(object) == %{ - "type" => "Text", - "url" => "https://peertube.-2d4c2d1630e3" - } - - assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{ - "type" => "Text", - "url" => "" - } - end - - test "retunrs not modified object" do + test "returns non-modified object" do assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"} end end @@ -1222,7 +1071,9 @@ test "returns nil when cannot normalize object" do @tag capture_log: true test "returns {:ok, %Object{}} for success case" do assert {:ok, %Object{}} = - Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873") + Transmogrifier.get_obj_helper( + "https://mstdn.io/users/mayuutann/statuses/99568293732299394" + ) end end diff --git a/test/web/activity_pub/utils_test.exs b/test/pleroma/web/activity_pub/utils_test.exs similarity index 99% rename from test/web/activity_pub/utils_test.exs rename to test/pleroma/web/activity_pub/utils_test.exs index d50213545..be9cd7d13 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/pleroma/web/activity_pub/utils_test.exs @@ -193,7 +193,7 @@ test "fetches only Create activities" do describe "update_follow_state_for_all/2" do test "updates the state of all Follow activities with the same actor and object" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) follower = insert(:user) {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user) @@ -217,7 +217,7 @@ test "updates the state of all Follow activities with the same actor and object" describe "update_follow_state/2" do test "updates the state of the given follow activity" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) follower = insert(:user) {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user) diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/pleroma/web/activity_pub/views/object_view_test.exs similarity index 100% rename from test/web/activity_pub/views/object_view_test.exs rename to test/pleroma/web/activity_pub/views/object_view_test.exs diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs similarity index 100% rename from test/web/activity_pub/views/user_view_test.exs rename to test/pleroma/web/activity_pub/views/user_view_test.exs diff --git a/test/web/activity_pub/visibilty_test.exs b/test/pleroma/web/activity_pub/visibility_test.exs similarity index 100% rename from test/web/activity_pub/visibilty_test.exs rename to test/pleroma/web/activity_pub/visibility_test.exs diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs similarity index 97% rename from test/web/admin_api/controllers/admin_api_controller_test.exs rename to test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs index dbf478edf..cba6b43d3 100644 --- a/test/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs @@ -203,7 +203,7 @@ test "single user", %{admin: admin, conn: conn} do assert user.note_count == 0 assert user.follower_count == 0 assert user.following_count == 0 - assert user.bio == nil + assert user.bio == "" assert user.name == nil assert called(Pleroma.Web.Federator.publish(:_)) @@ -1510,6 +1510,56 @@ test "excludes reblogs by default", %{conn: conn, user: user} do end end + describe "GET /api/pleroma/admin/users/:nickname/chats" do + setup do + user = insert(:user) + recipients = insert_list(3, :user) + + Enum.each(recipients, fn recipient -> + CommonAPI.post_chat_message(user, recipient, "yo") + end) + + %{user: user} + end + + test "renders user's chats", %{conn: conn, user: user} do + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats") + + assert json_response(conn, 200) |> length() == 3 + end + end + + describe "GET /api/pleroma/admin/users/:nickname/chats unauthorized" do + setup do + user = insert(:user) + recipient = insert(:user) + CommonAPI.post_chat_message(user, recipient, "yo") + %{conn: conn} = oauth_access(["read:chats"]) + %{conn: conn, user: user} + end + + test "returns 403", %{conn: conn, user: user} do + conn + |> get("/api/pleroma/admin/users/#{user.nickname}/chats") + |> json_response(403) + end + end + + describe "GET /api/pleroma/admin/users/:nickname/chats unauthenticated" do + setup do + user = insert(:user) + recipient = insert(:user) + CommonAPI.post_chat_message(user, recipient, "yo") + %{conn: build_conn(), user: user} + end + + test "returns 403", %{conn: conn, user: user} do + conn + |> get("/api/pleroma/admin/users/#{user.nickname}/chats") + |> json_response(403) + end + end + describe "GET /api/pleroma/admin/moderation_log" do setup do moderator = insert(:user, is_moderator: true) @@ -1927,7 +1977,12 @@ test "it resend emails for two users", %{conn: conn, admin: admin} do }" ObanHelpers.perform_all() - assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(first_user)) + + Pleroma.Emails.UserEmail.account_confirmation_email(first_user) + # temporary hackney fix until hackney max_connections bug is fixed + # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 + |> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) + |> assert_email_sent() end end diff --git a/test/pleroma/web/admin_api/controllers/chat_controller_test.exs b/test/pleroma/web/admin_api/controllers/chat_controller_test.exs new file mode 100644 index 000000000..bd4c9c9d1 --- /dev/null +++ b/test/pleroma/web/admin_api/controllers/chat_controller_test.exs @@ -0,0 +1,219 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ChatControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + + alias Pleroma.Chat + alias Pleroma.Chat.MessageReference + alias Pleroma.Config + alias Pleroma.ModerationLog + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.Web.CommonAPI + + defp admin_setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} + end + + describe "DELETE /api/pleroma/admin/chats/:id/messages/:message_id" do + setup do: admin_setup() + + test "it deletes a message from the chat", %{conn: conn, admin: admin} do + user = insert(:user) + recipient = insert(:user) + + {:ok, message} = + CommonAPI.post_chat_message(user, recipient, "Hello darkness my old friend") + + object = Object.normalize(message, false) + + chat = Chat.get(user.id, recipient.ap_id) + recipient_chat = Chat.get(recipient.id, user.ap_id) + + cm_ref = MessageReference.for_chat_and_object(chat, object) + recipient_cm_ref = MessageReference.for_chat_and_object(recipient_chat, object) + + result = + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}") + |> json_response_and_validate_schema(200) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deleted chat message ##{cm_ref.id}" + + assert result["id"] == cm_ref.id + refute MessageReference.get_by_id(cm_ref.id) + refute MessageReference.get_by_id(recipient_cm_ref.id) + assert %{data: %{"type" => "Tombstone"}} = Object.get_by_id(object.id) + end + end + + describe "GET /api/pleroma/admin/chats/:id/messages" do + setup do: admin_setup() + + test "it paginates", %{conn: conn} do + user = insert(:user) + recipient = insert(:user) + + Enum.each(1..30, fn _ -> + {:ok, _} = CommonAPI.post_chat_message(user, recipient, "hey") + end) + + chat = Chat.get(user.id, recipient.ap_id) + + result = + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages") + |> json_response_and_validate_schema(200) + + assert length(result) == 20 + + result = + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}") + |> json_response_and_validate_schema(200) + + assert length(result) == 10 + end + + test "it returns the messages for a given chat", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + third_user = insert(:user) + + {:ok, _} = CommonAPI.post_chat_message(user, other_user, "hey") + {:ok, _} = CommonAPI.post_chat_message(user, third_user, "hey") + {:ok, _} = CommonAPI.post_chat_message(user, other_user, "how are you?") + {:ok, _} = CommonAPI.post_chat_message(other_user, user, "fine, how about you?") + + chat = Chat.get(user.id, other_user.ap_id) + + result = + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages") + |> json_response_and_validate_schema(200) + + result + |> Enum.each(fn message -> + assert message["chat_id"] == chat.id |> to_string() + end) + + assert length(result) == 3 + end + end + + describe "GET /api/pleroma/admin/chats/:id" do + setup do: admin_setup() + + test "it returns a chat", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id) + + result = + conn + |> get("/api/pleroma/admin/chats/#{chat.id}") + |> json_response_and_validate_schema(200) + + assert result["id"] == to_string(chat.id) + assert %{} = result["sender"] + assert %{} = result["receiver"] + refute result["account"] + end + end + + describe "unauthorized chat moderation" do + setup do + user = insert(:user) + recipient = insert(:user) + + {:ok, message} = CommonAPI.post_chat_message(user, recipient, "Yo") + object = Object.normalize(message, false) + chat = Chat.get(user.id, recipient.ap_id) + cm_ref = MessageReference.for_chat_and_object(chat, object) + + %{conn: conn} = oauth_access(["read:chats", "write:chats"]) + %{conn: conn, chat: chat, cm_ref: cm_ref} + end + + test "DELETE /api/pleroma/admin/chats/:id/messages/:message_id", %{ + conn: conn, + chat: chat, + cm_ref: cm_ref + } do + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}") + |> json_response(403) + + assert MessageReference.get_by_id(cm_ref.id) == cm_ref + end + + test "GET /api/pleroma/admin/chats/:id/messages", %{conn: conn, chat: chat} do + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages") + |> json_response(403) + end + + test "GET /api/pleroma/admin/chats/:id", %{conn: conn, chat: chat} do + conn + |> get("/api/pleroma/admin/chats/#{chat.id}") + |> json_response(403) + end + end + + describe "unauthenticated chat moderation" do + setup do + user = insert(:user) + recipient = insert(:user) + + {:ok, message} = CommonAPI.post_chat_message(user, recipient, "Yo") + object = Object.normalize(message, false) + chat = Chat.get(user.id, recipient.ap_id) + cm_ref = MessageReference.for_chat_and_object(chat, object) + + %{conn: build_conn(), chat: chat, cm_ref: cm_ref} + end + + test "DELETE /api/pleroma/admin/chats/:id/messages/:message_id", %{ + conn: conn, + chat: chat, + cm_ref: cm_ref + } do + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/pleroma/admin/chats/#{chat.id}/messages/#{cm_ref.id}") + |> json_response(403) + + assert MessageReference.get_by_id(cm_ref.id) == cm_ref + end + + test "GET /api/pleroma/admin/chats/:id/messages", %{conn: conn, chat: chat} do + conn + |> get("/api/pleroma/admin/chats/#{chat.id}/messages") + |> json_response(403) + end + + test "GET /api/pleroma/admin/chats/:id", %{conn: conn, chat: chat} do + conn + |> get("/api/pleroma/admin/chats/#{chat.id}") + |> json_response(403) + end + end +end diff --git a/test/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs similarity index 100% rename from test/web/admin_api/controllers/config_controller_test.exs rename to test/pleroma/web/admin_api/controllers/config_controller_test.exs diff --git a/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs b/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs new file mode 100644 index 000000000..5f7b042f6 --- /dev/null +++ b/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs @@ -0,0 +1,106 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.InstanceDocumentControllerTest do + use Pleroma.Web.ConnCase, async: true + import Pleroma.Factory + alias Pleroma.Config + + @dir "test/tmp/instance_static" + @default_instance_panel ~s(

Welcome to Pleroma!

) + + setup do + File.mkdir_p!(@dir) + on_exit(fn -> File.rm_rf(@dir) end) + end + + setup do: clear_config([:instance, :static_dir], @dir) + + setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} + end + + describe "GET /api/pleroma/admin/instance_document/:name" do + test "return the instance document url", %{conn: conn} do + conn = get(conn, "/api/pleroma/admin/instance_document/instance-panel") + + assert content = html_response(conn, 200) + assert String.contains?(content, @default_instance_panel) + end + + test "it returns 403 if requested by a non-admin" do + non_admin_user = insert(:user) + token = insert(:oauth_token, user: non_admin_user) + + conn = + build_conn() + |> assign(:user, non_admin_user) + |> assign(:token, token) + |> get("/api/pleroma/admin/instance_document/instance-panel") + + assert json_response(conn, :forbidden) + end + + test "it returns 404 if the instance document with the given name doesn't exist", %{ + conn: conn + } do + conn = get(conn, "/api/pleroma/admin/instance_document/1234") + + assert json_response_and_validate_schema(conn, 404) + end + end + + describe "PATCH /api/pleroma/admin/instance_document/:name" do + test "uploads the instance document", %{conn: conn} do + image = %Plug.Upload{ + content_type: "text/html", + path: Path.absname("test/fixtures/custom_instance_panel.html"), + filename: "custom_instance_panel.html" + } + + conn = + conn + |> put_req_header("content-type", "multipart/form-data") + |> patch("/api/pleroma/admin/instance_document/instance-panel", %{ + "file" => image + }) + + assert %{"url" => url} = json_response_and_validate_schema(conn, 200) + index = get(build_conn(), url) + assert html_response(index, 200) == "

Custom instance panel

" + end + end + + describe "DELETE /api/pleroma/admin/instance_document/:name" do + test "deletes the instance document", %{conn: conn} do + File.mkdir!(@dir <> "/instance/") + File.write!(@dir <> "/instance/panel.html", "Custom instance panel") + + conn_resp = + conn + |> get("/api/pleroma/admin/instance_document/instance-panel") + + assert html_response(conn_resp, 200) == "Custom instance panel" + + conn + |> delete("/api/pleroma/admin/instance_document/instance-panel") + |> json_response_and_validate_schema(200) + + conn_resp = + conn + |> get("/api/pleroma/admin/instance_document/instance-panel") + + assert content = html_response(conn_resp, 200) + assert String.contains?(content, @default_instance_panel) + end + end +end diff --git a/test/web/admin_api/controllers/invite_controller_test.exs b/test/pleroma/web/admin_api/controllers/invite_controller_test.exs similarity index 100% rename from test/web/admin_api/controllers/invite_controller_test.exs rename to test/pleroma/web/admin_api/controllers/invite_controller_test.exs diff --git a/test/web/admin_api/controllers/media_proxy_cache_controller_test.exs b/test/pleroma/web/admin_api/controllers/media_proxy_cache_controller_test.exs similarity index 100% rename from test/web/admin_api/controllers/media_proxy_cache_controller_test.exs rename to test/pleroma/web/admin_api/controllers/media_proxy_cache_controller_test.exs diff --git a/test/web/admin_api/controllers/oauth_app_controller_test.exs b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs similarity index 100% rename from test/web/admin_api/controllers/oauth_app_controller_test.exs rename to test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs diff --git a/test/web/admin_api/controllers/relay_controller_test.exs b/test/pleroma/web/admin_api/controllers/relay_controller_test.exs similarity index 100% rename from test/web/admin_api/controllers/relay_controller_test.exs rename to test/pleroma/web/admin_api/controllers/relay_controller_test.exs diff --git a/test/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs similarity index 100% rename from test/web/admin_api/controllers/report_controller_test.exs rename to test/pleroma/web/admin_api/controllers/report_controller_test.exs diff --git a/test/web/admin_api/controllers/status_controller_test.exs b/test/pleroma/web/admin_api/controllers/status_controller_test.exs similarity index 100% rename from test/web/admin_api/controllers/status_controller_test.exs rename to test/pleroma/web/admin_api/controllers/status_controller_test.exs diff --git a/test/web/admin_api/search_test.exs b/test/pleroma/web/admin_api/search_test.exs similarity index 96% rename from test/web/admin_api/search_test.exs rename to test/pleroma/web/admin_api/search_test.exs index b974cedd5..d88867c52 100644 --- a/test/web/admin_api/search_test.exs +++ b/test/pleroma/web/admin_api/search_test.exs @@ -177,5 +177,14 @@ test "it returns unapproved user" do assert total == 3 assert count == 1 end + + test "it returns non-discoverable users" do + insert(:user) + insert(:user, discoverable: false) + + {:ok, _results, total} = Search.user() + + assert total == 2 + end end end diff --git a/test/web/admin_api/views/report_view_test.exs b/test/pleroma/web/admin_api/views/report_view_test.exs similarity index 100% rename from test/web/admin_api/views/report_view_test.exs rename to test/pleroma/web/admin_api/views/report_view_test.exs diff --git a/test/web/api_spec/schema_examples_test.exs b/test/pleroma/web/api_spec/schema_examples_test.exs similarity index 100% rename from test/web/api_spec/schema_examples_test.exs rename to test/pleroma/web/api_spec/schema_examples_test.exs diff --git a/test/web/auth/auth_test_controller_test.exs b/test/pleroma/web/auth/auth_controller_test.exs similarity index 99% rename from test/web/auth/auth_test_controller_test.exs rename to test/pleroma/web/auth/auth_controller_test.exs index fed52b7f3..498554060 100644 --- a/test/web/auth/auth_test_controller_test.exs +++ b/test/pleroma/web/auth/auth_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Tests.AuthTestControllerTest do +defmodule Pleroma.Web.Auth.AuthControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory diff --git a/test/web/auth/authenticator_test.exs b/test/pleroma/web/auth/authenticator_test.exs similarity index 100% rename from test/web/auth/authenticator_test.exs rename to test/pleroma/web/auth/authenticator_test.exs diff --git a/test/web/auth/basic_auth_test.exs b/test/pleroma/web/auth/basic_auth_test.exs similarity index 100% rename from test/web/auth/basic_auth_test.exs rename to test/pleroma/web/auth/basic_auth_test.exs diff --git a/test/web/auth/pleroma_authenticator_test.exs b/test/pleroma/web/auth/pleroma_authenticator_test.exs similarity index 100% rename from test/web/auth/pleroma_authenticator_test.exs rename to test/pleroma/web/auth/pleroma_authenticator_test.exs diff --git a/test/web/auth/totp_authenticator_test.exs b/test/pleroma/web/auth/totp_authenticator_test.exs similarity index 100% rename from test/web/auth/totp_authenticator_test.exs rename to test/pleroma/web/auth/totp_authenticator_test.exs diff --git a/test/web/chat_channel_test.exs b/test/pleroma/web/chat_channel_test.exs similarity index 87% rename from test/web/chat_channel_test.exs rename to test/pleroma/web/chat_channel_test.exs index f18f3a212..32170873d 100644 --- a/test/web/chat_channel_test.exs +++ b/test/pleroma/web/chat_channel_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.ChatChannelTest do use Pleroma.Web.ChannelCase alias Pleroma.Web.ChatChannel diff --git a/test/web/common_api/common_api_utils_test.exs b/test/pleroma/web/common_api/utils_test.exs similarity index 100% rename from test/web/common_api/common_api_utils_test.exs rename to test/pleroma/web/common_api/utils_test.exs diff --git a/test/web/common_api/common_api_test.exs b/test/pleroma/web/common_api_test.exs similarity index 89% rename from test/web/common_api/common_api_test.exs rename to test/pleroma/web/common_api_test.exs index 28bb6db30..c5b90ad84 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -4,11 +4,14 @@ defmodule Pleroma.Web.CommonAPITest do use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Activity alias Pleroma.Chat alias Pleroma.Conversation.Participation alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier @@ -18,6 +21,7 @@ defmodule Pleroma.Web.CommonAPITest do import Pleroma.Factory import Mock + import Ecto.Query, only: [from: 2] require Pleroma.Constants @@ -25,6 +29,23 @@ defmodule Pleroma.Web.CommonAPITest do setup do: clear_config([:instance, :limit]) setup do: clear_config([:instance, :max_pinned_statuses]) + describe "posting polls" do + test "it posts a poll" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "who is the best", + poll: %{expires_in: 600, options: ["reimu", "marisa"]} + }) + + object = Object.normalize(activity) + + assert object.data["type"] == "Question" + assert object.data["oneOf"] |> length() == 2 + end + end + describe "blocking" do setup do blocker = insert(:user) @@ -74,12 +95,26 @@ test "it blocks and does not federate if outgoing blocks are disabled", %{ describe "posting chat messages" do setup do: clear_config([:instance, :chat_limit]) + test "it posts a self-chat" do + author = insert(:user) + recipient = author + + {:ok, activity} = + CommonAPI.post_chat_message( + author, + recipient, + "remember to buy milk when milk truk arive" + ) + + assert activity.data["type"] == "Create" + end + test "it posts a chat message without content but with an attachment" do author = insert(:user) recipient = insert(:user) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -601,21 +636,21 @@ test "it validates character limits are correctly enforced" do assert {:error, "The status is over the character limit"} = CommonAPI.post(user, %{status: "foobar"}) - assert {:ok, activity} = CommonAPI.post(user, %{status: "12345"}) + assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"}) end test "it can handle activities that expire" do user = insert(:user) - expires_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.truncate(:second) - |> NaiveDateTime.add(1_000_000, :second) + expires_at = DateTime.add(DateTime.utc_now(), 1_000_000) assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000}) - assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id) - assert expiration.scheduled_at == expires_at + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity.id}, + scheduled_at: expires_at + ) end end @@ -819,6 +854,69 @@ test "should unpin when deleting a status", %{user: user, activity: activity} do [user: user, activity: activity] end + test "marks notifications as read after mute" do + author = insert(:user) + activity = insert(:note_activity, user: author) + + friend1 = insert(:user) + friend2 = insert(:user) + + {:ok, reply_activity} = + CommonAPI.post( + friend2, + %{ + status: "@#{author.nickname} @#{friend1.nickname} test reply", + in_reply_to_status_id: activity.id + } + ) + + {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id) + {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1) + + assert Repo.aggregate( + from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id), + :count + ) == 1 + + unread_notifications = + Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id)) + + assert Enum.any?(unread_notifications, fn n -> + n.type == "favourite" && n.activity_id == favorite_activity.id + end) + + assert Enum.any?(unread_notifications, fn n -> + n.type == "reblog" && n.activity_id == repeat_activity.id + end) + + assert Enum.any?(unread_notifications, fn n -> + n.type == "mention" && n.activity_id == reply_activity.id + end) + + {:ok, _} = CommonAPI.add_mute(author, activity) + assert CommonAPI.thread_muted?(author, activity) + + assert Repo.aggregate( + from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id), + :count + ) == 1 + + read_notifications = + Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id)) + + assert Enum.any?(read_notifications, fn n -> + n.type == "favourite" && n.activity_id == favorite_activity.id + end) + + assert Enum.any?(read_notifications, fn n -> + n.type == "reblog" && n.activity_id == repeat_activity.id + end) + + assert Enum.any?(read_notifications, fn n -> + n.type == "mention" && n.activity_id == reply_activity.id + end) + end + test "add mute", %{user: user, activity: activity} do {:ok, _} = CommonAPI.add_mute(user, activity) assert CommonAPI.thread_muted?(user, activity) @@ -987,7 +1085,7 @@ test "also unsubscribes a user" do test "cancels a pending follow for a local user" do follower = insert(:user) - followed = insert(:user, locked: true) + followed = insert(:user, is_locked: true) assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(follower, followed) @@ -1009,7 +1107,7 @@ test "cancels a pending follow for a local user" do test "cancels a pending follow for a remote user" do follower = insert(:user) - followed = insert(:user, locked: true, local: false, ap_enabled: true) + followed = insert(:user, is_locked: true, local: false, ap_enabled: true) assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(follower, followed) @@ -1032,7 +1130,7 @@ test "cancels a pending follow for a remote user" do describe "accept_follow_request/2" do test "after acceptance, it sets all existing pending follow request states to 'accept'" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) follower = insert(:user) follower_two = insert(:user) @@ -1052,7 +1150,7 @@ test "after acceptance, it sets all existing pending follow request states to 'a end test "after rejection, it sets all existing pending follow request states to 'reject'" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) follower = insert(:user) follower_two = insert(:user) @@ -1072,7 +1170,7 @@ test "after rejection, it sets all existing pending follow request states to 're end test "doesn't create a following relationship if the corresponding follow request doesn't exist" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) not_follower = insert(:user) CommonAPI.accept_follow_request(not_follower, user) @@ -1137,4 +1235,24 @@ test "respects visibility=private" do assert Visibility.get_visibility(activity) == "private" end end + + describe "get_user/1" do + test "gets user by ap_id" do + user = insert(:user) + assert CommonAPI.get_user(user.ap_id) == user + end + + test "gets user by guessed nickname" do + user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom") + assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user + end + + test "fallback" do + assert %User{ + name: "", + ap_id: "", + nickname: "erroruser@example.com" + } = CommonAPI.get_user("") + end + end end diff --git a/test/pleroma/web/endpoint/metrics_exporter_test.exs b/test/pleroma/web/endpoint/metrics_exporter_test.exs new file mode 100644 index 000000000..875addc96 --- /dev/null +++ b/test/pleroma/web/endpoint/metrics_exporter_test.exs @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Endpoint.MetricsExporterTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Web.Endpoint.MetricsExporter + + defp config do + Application.get_env(:prometheus, MetricsExporter) + end + + describe "with default config" do + test "does NOT expose app metrics", %{conn: conn} do + conn + |> get(config()[:path]) + |> json_response(404) + end + end + + describe "when enabled" do + setup do + initial_config = config() + on_exit(fn -> Application.put_env(:prometheus, MetricsExporter, initial_config) end) + + Application.put_env( + :prometheus, + MetricsExporter, + Keyword.put(initial_config, :enabled, true) + ) + end + + test "serves app metrics", %{conn: conn} do + conn = get(conn, config()[:path]) + assert response = response(conn, 200) + + for metric <- [ + "http_requests_total", + "http_request_duration_microseconds", + "phoenix_controller_call_duration", + "telemetry_scrape_duration", + "erlang_vm_memory_atom_bytes_total" + ] do + assert response =~ ~r/#{metric}/ + end + end + + test "when IP whitelist configured, " <> + "serves app metrics only if client IP is whitelisted", + %{conn: conn} do + Application.put_env( + :prometheus, + MetricsExporter, + Keyword.put(config(), :ip_whitelist, ["127.127.127.127", {1, 1, 1, 1}, '255.255.255.255']) + ) + + conn + |> get(config()[:path]) + |> json_response(404) + + conn + |> Map.put(:remote_ip, {127, 127, 127, 127}) + |> get(config()[:path]) + |> response(200) + end + end +end diff --git a/test/web/fallback_test.exs b/test/pleroma/web/fallback_test.exs similarity index 100% rename from test/web/fallback_test.exs rename to test/pleroma/web/fallback_test.exs diff --git a/test/pleroma/web/fed_sockets/fed_registry_test.exs b/test/pleroma/web/fed_sockets/fed_registry_test.exs new file mode 100644 index 000000000..73aaced46 --- /dev/null +++ b/test/pleroma/web/fed_sockets/fed_registry_test.exs @@ -0,0 +1,124 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FedRegistryTest do + use ExUnit.Case + + alias Pleroma.Web.FedSockets + alias Pleroma.Web.FedSockets.FedRegistry + alias Pleroma.Web.FedSockets.SocketInfo + + @good_domain "http://good.domain" + @good_domain_origin "good.domain:80" + + setup do + start_supervised({Pleroma.Web.FedSockets.Supervisor, []}) + build_test_socket(@good_domain) + Process.sleep(10) + + :ok + end + + describe "add_fed_socket/1 without conflicting sockets" do + test "can be added" do + Process.sleep(10) + assert {:ok, %SocketInfo{origin: origin}} = FedRegistry.get_fed_socket(@good_domain_origin) + assert origin == "good.domain:80" + end + + test "multiple origins can be added" do + build_test_socket("http://anothergood.domain") + Process.sleep(10) + + assert {:ok, %SocketInfo{origin: origin_1}} = + FedRegistry.get_fed_socket(@good_domain_origin) + + assert {:ok, %SocketInfo{origin: origin_2}} = + FedRegistry.get_fed_socket("anothergood.domain:80") + + assert origin_1 == "good.domain:80" + assert origin_2 == "anothergood.domain:80" + assert FedRegistry.list_all() |> Enum.count() == 2 + end + end + + describe "add_fed_socket/1 when duplicate sockets conflict" do + setup do + build_test_socket(@good_domain) + build_test_socket(@good_domain) + Process.sleep(10) + :ok + end + + test "will be ignored" do + assert {:ok, %SocketInfo{origin: origin, pid: _pid_one}} = + FedRegistry.get_fed_socket(@good_domain_origin) + + assert origin == "good.domain:80" + + assert FedRegistry.list_all() |> Enum.count() == 1 + end + + test "the newer process will be closed" do + pid_two = build_test_socket(@good_domain) + + assert {:ok, %SocketInfo{origin: origin, pid: _pid_one}} = + FedRegistry.get_fed_socket(@good_domain_origin) + + assert origin == "good.domain:80" + Process.sleep(10) + + refute Process.alive?(pid_two) + + assert FedRegistry.list_all() |> Enum.count() == 1 + end + end + + describe "get_fed_socket/1" do + test "returns missing for unknown hosts" do + assert {:error, :missing} = FedRegistry.get_fed_socket("not_a_dmoain") + end + + test "returns rejected for hosts previously rejected" do + "rejected.domain:80" + |> FedSockets.uri_for_origin() + |> FedRegistry.set_host_rejected() + + assert {:error, :rejected} = FedRegistry.get_fed_socket("rejected.domain:80") + end + + test "can retrieve a previously added SocketInfo" do + build_test_socket(@good_domain) + Process.sleep(10) + assert {:ok, %SocketInfo{origin: origin}} = FedRegistry.get_fed_socket(@good_domain_origin) + assert origin == "good.domain:80" + end + + test "removes references to SocketInfos when the process crashes" do + assert {:ok, %SocketInfo{origin: origin, pid: pid}} = + FedRegistry.get_fed_socket(@good_domain_origin) + + assert origin == "good.domain:80" + + Process.exit(pid, :testing) + Process.sleep(100) + assert {:error, :missing} = FedRegistry.get_fed_socket(@good_domain_origin) + end + end + + def build_test_socket(uri) do + Kernel.spawn(fn -> fed_socket_almost(uri) end) + end + + def fed_socket_almost(origin) do + FedRegistry.add_fed_socket(origin) + + receive do + :close -> + :ok + after + 5_000 -> :timeout + end + end +end diff --git a/test/pleroma/web/fed_sockets/fetch_registry_test.exs b/test/pleroma/web/fed_sockets/fetch_registry_test.exs new file mode 100644 index 000000000..7bd2d995a --- /dev/null +++ b/test/pleroma/web/fed_sockets/fetch_registry_test.exs @@ -0,0 +1,67 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.FetchRegistryTest do + use ExUnit.Case + + alias Pleroma.Web.FedSockets.FetchRegistry + alias Pleroma.Web.FedSockets.FetchRegistry.FetchRegistryData + + @json_message "hello" + @json_reply "hello back" + + setup do + start_supervised( + {Pleroma.Web.FedSockets.Supervisor, + [ + ping_interval: 8, + connection_duration: 15, + rejection_duration: 5, + fed_socket_fetches: [default: 10, interval: 10] + ]} + ) + + :ok + end + + test "fetches can be stored" do + uuid = FetchRegistry.register_fetch(@json_message) + + assert {:error, :waiting} = FetchRegistry.check_fetch(uuid) + end + + test "fetches can return" do + uuid = FetchRegistry.register_fetch(@json_message) + task = Task.async(fn -> FetchRegistry.register_fetch_received(uuid, @json_reply) end) + + assert {:error, :waiting} = FetchRegistry.check_fetch(uuid) + Task.await(task) + + assert {:ok, %FetchRegistryData{received_json: received_json}} = + FetchRegistry.check_fetch(uuid) + + assert received_json == @json_reply + end + + test "fetches are deleted once popped from stack" do + uuid = FetchRegistry.register_fetch(@json_message) + task = Task.async(fn -> FetchRegistry.register_fetch_received(uuid, @json_reply) end) + Task.await(task) + + assert {:ok, %FetchRegistryData{received_json: received_json}} = + FetchRegistry.check_fetch(uuid) + + assert received_json == @json_reply + assert {:ok, @json_reply} = FetchRegistry.pop_fetch(uuid) + + assert {:error, :missing} = FetchRegistry.check_fetch(uuid) + end + + test "fetches can time out" do + uuid = FetchRegistry.register_fetch(@json_message) + assert {:error, :waiting} = FetchRegistry.check_fetch(uuid) + Process.sleep(500) + assert {:error, :missing} = FetchRegistry.check_fetch(uuid) + end +end diff --git a/test/pleroma/web/fed_sockets/socket_info_test.exs b/test/pleroma/web/fed_sockets/socket_info_test.exs new file mode 100644 index 000000000..db3d6edcd --- /dev/null +++ b/test/pleroma/web/fed_sockets/socket_info_test.exs @@ -0,0 +1,118 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.FedSockets.SocketInfoTest do + use ExUnit.Case + + alias Pleroma.Web.FedSockets + alias Pleroma.Web.FedSockets.SocketInfo + + describe "uri_for_origin" do + test "provides the fed_socket URL given the origin information" do + endpoint = "example.com:4000" + assert FedSockets.uri_for_origin(endpoint) =~ "ws://" + assert FedSockets.uri_for_origin(endpoint) =~ endpoint + end + end + + describe "origin" do + test "will provide the origin field given a url" do + endpoint = "example.com:4000" + assert SocketInfo.origin("ws://#{endpoint}") == endpoint + assert SocketInfo.origin("http://#{endpoint}") == endpoint + assert SocketInfo.origin("https://#{endpoint}") == endpoint + end + + test "will proide the origin field given a uri" do + endpoint = "example.com:4000" + uri = URI.parse("http://#{endpoint}") + + assert SocketInfo.origin(uri) == endpoint + end + end + + describe "touch" do + test "will update the TTL" do + endpoint = "example.com:4000" + socket = SocketInfo.build("ws://#{endpoint}") + Process.sleep(2) + touched_socket = SocketInfo.touch(socket) + + assert socket.connected_until < touched_socket.connected_until + end + end + + describe "expired?" do + setup do + start_supervised( + {Pleroma.Web.FedSockets.Supervisor, + [ + ping_interval: 8, + connection_duration: 5, + rejection_duration: 5, + fed_socket_rejections: [lazy: true] + ]} + ) + + :ok + end + + test "tests if the TTL is exceeded" do + endpoint = "example.com:4000" + socket = SocketInfo.build("ws://#{endpoint}") + refute SocketInfo.expired?(socket) + Process.sleep(10) + + assert SocketInfo.expired?(socket) + end + end + + describe "creating outgoing connection records" do + test "can be passed a string" do + assert %{conn_pid: :pid, origin: _origin} = SocketInfo.build("example.com:4000", :pid) + end + + test "can be passed a URI" do + uri = URI.parse("http://example.com:4000") + assert %{conn_pid: :pid, origin: origin} = SocketInfo.build(uri, :pid) + assert origin =~ "example.com:4000" + end + + test "will include the port number" do + assert %{conn_pid: :pid, origin: origin} = SocketInfo.build("http://example.com:4000", :pid) + + assert origin =~ ":4000" + end + + test "will provide the port if missing" do + assert %{conn_pid: :pid, origin: "example.com:80"} = + SocketInfo.build("http://example.com", :pid) + + assert %{conn_pid: :pid, origin: "example.com:443"} = + SocketInfo.build("https://example.com", :pid) + end + end + + describe "creating incoming connection records" do + test "can be passed a string" do + assert %{pid: _, origin: _origin} = SocketInfo.build("example.com:4000") + end + + test "can be passed a URI" do + uri = URI.parse("example.com:4000") + assert %{pid: _, origin: _origin} = SocketInfo.build(uri) + end + + test "will include the port number" do + assert %{pid: _, origin: origin} = SocketInfo.build("http://example.com:4000") + + assert origin =~ ":4000" + end + + test "will provide the port if missing" do + assert %{pid: _, origin: "example.com:80"} = SocketInfo.build("http://example.com") + assert %{pid: _, origin: "example.com:443"} = SocketInfo.build("https://example.com") + end + end +end diff --git a/test/web/federator_test.exs b/test/pleroma/web/federator_test.exs similarity index 100% rename from test/web/federator_test.exs rename to test/pleroma/web/federator_test.exs diff --git a/test/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs similarity index 95% rename from test/web/feed/tag_controller_test.exs rename to test/pleroma/web/feed/tag_controller_test.exs index 868e40965..e4084b0e5 100644 --- a/test/web/feed/tag_controller_test.exs +++ b/test/pleroma/web/feed/tag_controller_test.exs @@ -8,6 +8,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do import Pleroma.Factory import SweetXml + alias Pleroma.Config alias Pleroma.Object alias Pleroma.Web.CommonAPI alias Pleroma.Web.Feed.FeedView @@ -15,7 +16,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do setup do: clear_config([:feed]) test "gets a feed (ATOM)", %{conn: conn} do - Pleroma.Config.put( + Config.put( [:feed, :post_title], %{max_length: 25, omission: "..."} ) @@ -82,7 +83,7 @@ test "gets a feed (ATOM)", %{conn: conn} do end test "gets a feed (RSS)", %{conn: conn} do - Pleroma.Config.put( + Config.put( [:feed, :post_title], %{max_length: 25, omission: "..."} ) @@ -157,7 +158,7 @@ test "gets a feed (RSS)", %{conn: conn} do response = conn |> put_req_header("accept", "application/rss+xml") - |> get(tag_feed_path(conn, :feed, "pleromaart")) + |> get(tag_feed_path(conn, :feed, "pleromaart.rss")) |> response(200) xml = parse(response) @@ -183,14 +184,12 @@ test "gets a feed (RSS)", %{conn: conn} do end describe "private instance" do - setup do: clear_config([:instance, :public]) + setup do: clear_config([:instance, :public], false) test "returns 404 for tags feed", %{conn: conn} do - Config.put([:instance, :public], false) - conn |> put_req_header("accept", "application/rss+xml") - |> get(tag_feed_path(conn, :feed, "pleromaart")) + |> get(tag_feed_path(conn, :feed, "pleromaart.rss")) |> response(404) end end diff --git a/test/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs similarity index 94% rename from test/web/feed/user_controller_test.exs rename to test/pleroma/web/feed/user_controller_test.exs index 9a5610baa..eabfe3a63 100644 --- a/test/web/feed/user_controller_test.exs +++ b/test/pleroma/web/feed/user_controller_test.exs @@ -13,7 +13,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do alias Pleroma.User alias Pleroma.Web.CommonAPI - setup do: clear_config([:instance, :federating], true) + setup do: clear_config([:static_fe, :enabled], false) describe "feed" do setup do: clear_config([:feed]) @@ -192,6 +192,16 @@ test "returns 404 when the user is remote", %{conn: conn} do |> get(user_feed_path(conn, :feed, user.nickname)) |> response(404) end + + test "does not require authentication on non-federating instances", %{conn: conn} do + clear_config([:instance, :federating], false) + user = insert(:user) + + conn + |> put_req_header("accept", "application/rss+xml") + |> get("/users/#{user.nickname}/feed.rss") + |> response(200) + end end # Note: see ActivityPubControllerTest for JSON format tests @@ -206,7 +216,7 @@ test "with html format, it redirects to user feed", %{conn: conn} do |> response(200) assert response == - Fallback.RedirectController.redirector_with_meta( + Pleroma.Web.Fallback.RedirectController.redirector_with_meta( conn, %{user: user} ).resp_body diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs similarity index 99% rename from test/web/mastodon_api/controllers/account_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index 17a1e7d66..dcc0c81ec 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -32,7 +32,7 @@ test "works by id" do test "works by nickname" do user = insert(:user) - assert %{"id" => user_id} = + assert %{"id" => _user_id} = build_conn() |> get("/api/v1/accounts/#{user.nickname}") |> json_response_and_validate_schema(200) @@ -43,7 +43,7 @@ test "works by nickname for remote users" do user = insert(:user, nickname: "user@example.com", local: false) - assert %{"id" => user_id} = + assert %{"id" => _user_id} = build_conn() |> get("/api/v1/accounts/#{user.nickname}") |> json_response_and_validate_schema(200) @@ -380,7 +380,7 @@ test "gets an users media, excludes reblogs", %{conn: conn} do other_user = insert(:user) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -706,7 +706,7 @@ test "following / unfollowing a user", %{conn: conn} do end test "cancelling follow request", %{conn: conn} do - %{id: other_user_id} = insert(:user, %{locked: true}) + %{id: other_user_id} = insert(:user, %{is_locked: true}) assert %{"id" => ^other_user_id, "following" => false, "requested" => true} = conn @@ -1429,10 +1429,10 @@ test "returns an error if captcha is invalid", %{conn: conn} do test "returns lists to which the account belongs" do %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) - assert {:ok, %Pleroma.List{id: list_id} = list} = Pleroma.List.create("Test List", user) + assert {:ok, %Pleroma.List{id: _list_id} = list} = Pleroma.List.create("Test List", user) {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) - assert [%{"id" => list_id, "title" => "Test List"}] = + assert [%{"id" => _list_id, "title" => "Test List"}] = conn |> get("/api/v1/accounts/#{other_user.id}/lists") |> json_response_and_validate_schema(200) @@ -1442,7 +1442,10 @@ test "returns lists to which the account belongs" do describe "verify_credentials" do test "verify_credentials" do %{user: user, conn: conn} = oauth_access(["read:accounts"]) - [notification | _] = insert_list(7, :notification, user: user) + + [notification | _] = + insert_list(7, :notification, user: user, activity: insert(:note_activity)) + Pleroma.Notification.set_read_up_to(user, notification.id) conn = get(conn, "/api/v1/accounts/verify_credentials") diff --git a/test/web/mastodon_api/controllers/app_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/app_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/app_controller_test.exs diff --git a/test/web/mastodon_api/controllers/auth_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs similarity index 93% rename from test/web/mastodon_api/controllers/auth_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs index 4fa95fce1..bf2438fe2 100644 --- a/test/web/mastodon_api/controllers/auth_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs @@ -61,7 +61,7 @@ test "redirects to the getting-started page when referer is not present", %{conn end test "it returns 204", %{conn: conn} do - assert json_response(conn, :no_content) + assert empty_json_response(conn) end test "it creates a PasswordResetToken record for user", %{user: user} do @@ -91,7 +91,7 @@ test "it returns 204", %{conn: conn} do assert conn |> post("/auth/password?nickname=#{user.nickname}") - |> json_response(:no_content) + |> empty_json_response() ObanHelpers.perform_all() token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) @@ -112,7 +112,7 @@ test "it doesn't fail when a user has no email", %{conn: conn} do assert conn |> post("/auth/password?nickname=#{user.nickname}") - |> json_response(:no_content) + |> empty_json_response() end end @@ -125,24 +125,21 @@ test "it doesn't fail when a user has no email", %{conn: conn} do test "it returns 204 when user is not found", %{conn: conn, user: user} do conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") - assert conn - |> json_response(:no_content) + assert empty_json_response(conn) end test "it returns 204 when user is not local", %{conn: conn, user: user} do {:ok, user} = Repo.update(Ecto.Changeset.change(user, local: false)) conn = post(conn, "/auth/password?email=#{user.email}") - assert conn - |> json_response(:no_content) + assert empty_json_response(conn) end test "it returns 204 when user is deactivated", %{conn: conn, user: user} do {:ok, user} = Repo.update(Ecto.Changeset.change(user, deactivated: true, local: true)) conn = post(conn, "/auth/password?email=#{user.email}") - assert conn - |> json_response(:no_content) + assert empty_json_response(conn) end end diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/conversation_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs diff --git a/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/custom_emoji_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/custom_emoji_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/custom_emoji_controller_test.exs diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/domain_block_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/domain_block_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/domain_block_controller_test.exs diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/filter_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs similarity index 98% rename from test/web/mastodon_api/controllers/follow_request_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs index 6749e0e83..a9dd7cd30 100644 --- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -12,7 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do describe "locked accounts" do setup do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) %{conn: conn} = oauth_access(["follow"], user: user) %{user: user, conn: conn} end diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/instance_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs similarity index 86% rename from test/web/mastodon_api/controllers/list_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/list_controller_test.exs index 57a9ef4a4..091ec006c 100644 --- a/test/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs @@ -67,7 +67,7 @@ test "adding users to a list" do assert following == [other_user.follower_address] end - test "removing users from a list" do + test "removing users from a list, body params" do %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) third_user = insert(:user) @@ -85,6 +85,24 @@ test "removing users from a list" do assert following == [third_user.follower_address] end + test "removing users from a list, query params" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) + other_user = insert(:user) + third_user = insert(:user) + {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.follow(list, other_user) + {:ok, list} = Pleroma.List.follow(list, third_user) + + assert %{} == + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/v1/lists/#{list.id}/accounts?account_ids[]=#{other_user.id}") + |> json_response_and_validate_schema(:ok) + + %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) + assert following == [third_user.follower_address] + end + test "listing users in a list" do %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs similarity index 98% rename from test/web/mastodon_api/controllers/marker_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs index 6dd40fb4a..9f0481120 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs @@ -11,7 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do test "gets markers with correct scopes", %{conn: conn} do user = insert(:user) token = insert(:oauth_token, user: user, scopes: ["read:statuses"]) - insert_list(7, :notification, user: user) + insert_list(7, :notification, user: user, activity: insert(:note_activity)) {:ok, %{"notifications" => marker}} = Pleroma.Marker.upsert( diff --git a/test/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs similarity index 97% rename from test/web/mastodon_api/controllers/media_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index 906fd940f..d2bd57515 100644 --- a/test/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do setup do image = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -74,7 +74,7 @@ test "/api/v2/media", %{conn: conn, user: user, image: image} do setup %{user: actor} do file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -106,7 +106,7 @@ test "/api/v1/media/:id good request", %{conn: conn, object: object} do setup %{user: actor} do file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/notification_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs diff --git a/test/web/mastodon_api/controllers/poll_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/poll_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/poll_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/poll_controller_test.exs diff --git a/test/web/mastodon_api/controllers/report_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/report_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/report_controller_test.exs diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/scheduled_activity_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/scheduled_activity_controller_test.exs diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs similarity index 97% rename from test/web/mastodon_api/controllers/search_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/search_controller_test.exs index 24d1959f8..04dc6f445 100644 --- a/test/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs @@ -282,18 +282,18 @@ test "search fetches remote statuses and prefers them over other results", %{con capture_log(fn -> {:ok, %{id: activity_id}} = CommonAPI.post(insert(:user), %{ - status: "check out https://shitposter.club/notice/2827873" + status: "check out http://mastodon.example.org/@admin/99541947525187367" }) results = conn - |> get("/api/v1/search?q=https://shitposter.club/notice/2827873") + |> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367") |> json_response_and_validate_schema(200) - [status, %{"id" => ^activity_id}] = results["statuses"] - - assert status["uri"] == - "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" + assert [ + %{"url" => "http://mastodon.example.org/@admin/99541947525187367"}, + %{"id" => ^activity_id} + ] = results["statuses"] end) end diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs similarity index 95% rename from test/web/mastodon_api/controllers/status_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index f221884e7..436608e51 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -4,9 +4,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo alias Pleroma.Activity - alias Pleroma.ActivityExpiration alias Pleroma.Config alias Pleroma.Conversation.Participation alias Pleroma.Object @@ -29,8 +29,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: oauth_access(["write:statuses"]) test "posting a status does not increment reblog_count when relaying", %{conn: conn} do - Pleroma.Config.put([:instance, :federating], true) - Pleroma.Config.get([:instance, :allow_relay], true) + Config.put([:instance, :federating], true) + Config.get([:instance, :allow_relay], true) response = conn @@ -103,7 +103,9 @@ test "posting a status", %{conn: conn} do # An activity that will expire: # 2 hours - expires_in = 120 * 60 + expires_in = 2 * 60 * 60 + + expires_at = DateTime.add(DateTime.utc_now(), expires_in) conn_four = conn @@ -113,29 +115,22 @@ test "posting a status", %{conn: conn} do "expires_in" => expires_in }) - assert fourth_response = - %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200) + assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200) - assert activity = Activity.get_by_id(fourth_id) - assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) + assert Activity.get_by_id(fourth_id) - estimated_expires_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(expires_in) - |> NaiveDateTime.truncate(:second) - - # This assert will fail if the test takes longer than a minute. I sure hope it never does: - assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 - - assert fourth_response["pleroma"]["expires_at"] == - NaiveDateTime.to_iso8601(expiration.scheduled_at) + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: fourth_id}, + scheduled_at: expires_at + ) end test "it fails to create a status if `expires_in` is less or equal than an hour", %{ conn: conn } do - # 1 hour - expires_in = 60 * 60 + # 1 minute + expires_in = 1 * 60 assert %{"error" => "Expiry date is too soon"} = conn @@ -146,8 +141,8 @@ test "it fails to create a status if `expires_in` is less or equal than an hour" }) |> json_response_and_validate_schema(422) - # 30 minutes - expires_in = 30 * 60 + # 5 minutes + expires_in = 5 * 60 assert %{"error" => "Expiry date is too soon"} = conn @@ -160,8 +155,8 @@ test "it fails to create a status if `expires_in` is less or equal than an hour" end test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do - Pleroma.Config.put([:mrf_keyword, :reject], ["GNO"]) - Pleroma.Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) + Config.put([:mrf_keyword, :reject], ["GNO"]) + Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} = conn @@ -172,7 +167,7 @@ test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do test "posting an undefined status with an attachment", %{user: user, conn: conn} do file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -413,7 +408,7 @@ test "creates a scheduled activity with a media attachment", %{user: user, conn: |> Kernel.<>("Z") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -942,7 +937,7 @@ test "reblogged status for another user" do |> get("/api/v1/statuses/#{reblog_activity1.id}") assert %{ - "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2}, + "reblog" => %{"id" => _id, "reblogged" => false, "reblogs_count" => 2}, "reblogged" => false, "favourited" => false, "bookmarked" => false @@ -1146,6 +1141,52 @@ test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do |> post("/api/v1/statuses/#{activity_two.id}/pin") |> json_response_and_validate_schema(400) end + + test "on pin removes deletion job, on unpin reschedule deletion" do + %{conn: conn} = oauth_access(["write:accounts", "write:statuses"]) + expires_in = 2 * 60 * 60 + + expires_at = DateTime.add(DateTime.utc_now(), expires_in) + + assert %{"id" => id} = + conn + |> put_req_header("content-type", "application/json") + |> post("api/v1/statuses", %{ + "status" => "oolong", + "expires_in" => expires_in + }) + |> json_response_and_validate_schema(200) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: id}, + scheduled_at: expires_at + ) + + assert %{"id" => ^id, "pinned" => true} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{id}/pin") + |> json_response_and_validate_schema(200) + + refute_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: id}, + scheduled_at: expires_at + ) + + assert %{"id" => ^id, "pinned" => false} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{id}/unpin") + |> json_response_and_validate_schema(200) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: id}, + scheduled_at: expires_at + ) + end end describe "cards" do @@ -1681,19 +1722,17 @@ test "returns the favorites of a user" do test "expires_at is nil for another user" do %{conn: conn, user: user} = oauth_access(["read:statuses"]) + expires_at = DateTime.add(DateTime.utc_now(), 1_000_000) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000}) - expires_at = - activity.id - |> ActivityExpiration.get_by_activity_id() - |> Map.get(:scheduled_at) - |> NaiveDateTime.to_iso8601() - - assert %{"pleroma" => %{"expires_at" => ^expires_at}} = + assert %{"pleroma" => %{"expires_at" => a_expires_at}} = conn |> get("/api/v1/statuses/#{activity.id}") |> json_response_and_validate_schema(:ok) + {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at) + assert DateTime.diff(expires_at, a_expires_at) == 0 + %{conn: conn} = oauth_access(["read:statuses"]) assert %{"pleroma" => %{"expires_at" => nil}} = diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/subscription_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/suggestion_controller_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/suggestion_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/suggestion_controller_test.exs diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs similarity index 97% rename from test/web/mastodon_api/controllers/timeline_controller_test.exs rename to test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index 517cabcff..c6e0268fd 100644 --- a/test/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -114,8 +114,16 @@ test "doesn't return replies if follower is posting with blocked user" do {:ok, _reply_from_friend} = CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee}) - res_conn = get(conn, "/api/v1/timelines/public") - [%{"id" => ^activity_id}] = json_response_and_validate_schema(res_conn, 200) + # Still shows replies from yourself + {:ok, %{id: reply_from_me}} = + CommonAPI.post(blocker, %{status: "status", in_reply_to_status_id: reply_from_blockee}) + + response = + get(conn, "/api/v1/timelines/public") + |> json_response_and_validate_schema(200) + + assert length(response) == 2 + [%{"id" => ^reply_from_me}, %{"id" => ^activity_id}] = response end test "doesn't return replies if follow is posting with users from blocked domain" do diff --git a/test/web/masto_fe_controller_test.exs b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs similarity index 97% rename from test/web/masto_fe_controller_test.exs rename to test/pleroma/web/mastodon_api/masto_fe_controller_test.exs index f3b54b5f2..ed8add8d2 100644 --- a/test/web/masto_fe_controller_test.exs +++ b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.MastodonAPI.MastoFEController do +defmodule Pleroma.Web.MastodonAPI.MastoFEControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Config diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs similarity index 100% rename from test/web/mastodon_api/mastodon_api_controller_test.exs rename to test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/pleroma/web/mastodon_api/mastodon_api_test.exs similarity index 100% rename from test/web/mastodon_api/mastodon_api_test.exs rename to test/pleroma/web/mastodon_api/mastodon_api_test.exs diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs similarity index 98% rename from test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs rename to test/pleroma/web/mastodon_api/update_credentials_test.exs index 2e6704726..ed1921c91 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do +defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do alias Pleroma.Repo alias Pleroma.User @@ -222,7 +222,7 @@ test "updates the user's name", %{conn: conn} do test "updates the user's avatar", %{user: user, conn: conn} do new_avatar = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -246,7 +246,7 @@ test "updates the user's avatar", %{user: user, conn: conn} do test "updates the user's banner", %{user: user, conn: conn} do new_header = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -265,7 +265,7 @@ test "updates the user's banner", %{user: user, conn: conn} do test "updates the user's background", %{conn: conn, user: user} do new_header = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs similarity index 93% rename from test/web/mastodon_api/views/account_view_test.exs rename to test/pleroma/web/mastodon_api/views/account_view_test.exs index 68a5d0091..203e61c71 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do use Pleroma.DataCase + alias Pleroma.Config alias Pleroma.User alias Pleroma.UserRelationship alias Pleroma.Web.CommonAPI @@ -68,7 +69,7 @@ test "Represent a user account" do sensitive: false, pleroma: %{ actor_type: "Person", - discoverable: false + discoverable: true }, fields: [] }, @@ -116,9 +117,6 @@ test "is nil when :instances_favicons is disabled", %{user: user} do end end - test "Favicon when :instance_favicons is enabled" do - end - test "Represent the user account for the account owner" do user = insert(:user) @@ -169,7 +167,7 @@ test "Represent a Service(bot) account" do sensitive: false, pleroma: %{ actor_type: "Service", - discoverable: false + discoverable: true }, fields: [] }, @@ -334,7 +332,7 @@ test "represent a relationship for the user blocking a domain" do test "represent a relationship for the user with a pending follow request" do user = insert(:user) - other_user = insert(:user, locked: true) + other_user = insert(:user, is_locked: true) {:ok, user, other_user, _} = CommonAPI.follow(user, other_user) user = User.get_cached_by_id(user.id) @@ -451,7 +449,7 @@ test "shows unread_conversation_count only to the account owner" do test "shows unread_count only to the account owner" do user = insert(:user) - insert_list(7, :notification, user: user) + insert_list(7, :notification, user: user, activity: insert(:note_activity)) other_user = insert(:user) user = User.get_cached_by_ap_id(user.ap_id) @@ -483,7 +481,7 @@ test "shows zero when no follow requests are pending" do end test "shows non-zero when follow requests are pending" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) @@ -495,7 +493,7 @@ test "shows non-zero when follow requests are pending" do end test "decreases when accepting a follow request" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) @@ -512,7 +510,7 @@ test "decreases when accepting a follow request" do end test "decreases when rejecting a follow request" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) @@ -529,22 +527,23 @@ test "decreases when rejecting a follow request" do end test "shows non-zero when historical unapproved requests are present" do - user = insert(:user, locked: true) + user = insert(:user, is_locked: true) assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) - {:ok, user} = User.update_and_set_cache(user, %{locked: false}) + {:ok, user} = User.update_and_set_cache(user, %{is_locked: false}) assert %{locked: false, follow_requests_count: 1} = AccountView.render("show.json", %{user: user, for: user}) end end - test "uses mediaproxy urls when it's enabled" do + test "uses mediaproxy urls when it's enabled (regardless of media preview proxy state)" do clear_config([:media_proxy, :enabled], true) + clear_config([:media_preview_proxy, :enabled]) user = insert(:user, @@ -553,20 +552,24 @@ test "uses mediaproxy urls when it's enabled" do emoji: %{"joker_smile" => "https://evil.website/society.png"} ) - AccountView.render("show.json", %{user: user, skip_visibility_check: true}) - |> Enum.all?(fn - {key, url} when key in [:avatar, :avatar_static, :header, :header_static] -> - String.starts_with?(url, Pleroma.Web.base_url()) + with media_preview_enabled <- [false, true] do + Config.put([:media_preview_proxy, :enabled], media_preview_enabled) - {:emojis, emojis} -> - Enum.all?(emojis, fn %{url: url, static_url: static_url} -> - String.starts_with?(url, Pleroma.Web.base_url()) && - String.starts_with?(static_url, Pleroma.Web.base_url()) - end) + AccountView.render("show.json", %{user: user, skip_visibility_check: true}) + |> Enum.all?(fn + {key, url} when key in [:avatar, :avatar_static, :header, :header_static] -> + String.starts_with?(url, Pleroma.Web.base_url()) - _ -> - true - end) - |> assert() + {:emojis, emojis} -> + Enum.all?(emojis, fn %{url: url, static_url: static_url} -> + String.starts_with?(url, Pleroma.Web.base_url()) && + String.starts_with?(static_url, Pleroma.Web.base_url()) + end) + + _ -> + true + end) + |> assert() + end end end diff --git a/test/web/mastodon_api/views/conversation_view_test.exs b/test/pleroma/web/mastodon_api/views/conversation_view_test.exs similarity index 100% rename from test/web/mastodon_api/views/conversation_view_test.exs rename to test/pleroma/web/mastodon_api/views/conversation_view_test.exs diff --git a/test/web/mastodon_api/views/list_view_test.exs b/test/pleroma/web/mastodon_api/views/list_view_test.exs similarity index 100% rename from test/web/mastodon_api/views/list_view_test.exs rename to test/pleroma/web/mastodon_api/views/list_view_test.exs diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/pleroma/web/mastodon_api/views/marker_view_test.exs similarity index 100% rename from test/web/mastodon_api/views/marker_view_test.exs rename to test/pleroma/web/mastodon_api/views/marker_view_test.exs diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs similarity index 100% rename from test/web/mastodon_api/views/notification_view_test.exs rename to test/pleroma/web/mastodon_api/views/notification_view_test.exs diff --git a/test/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs similarity index 100% rename from test/web/mastodon_api/views/poll_view_test.exs rename to test/pleroma/web/mastodon_api/views/poll_view_test.exs diff --git a/test/web/mastodon_api/views/scheduled_activity_view_test.exs b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs similarity index 98% rename from test/web/mastodon_api/views/scheduled_activity_view_test.exs rename to test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs index fbfd873ef..04f73f5a0 100644 --- a/test/web/mastodon_api/views/scheduled_activity_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs @@ -22,7 +22,7 @@ test "A scheduled activity with a media attachment" do |> NaiveDateTime.to_iso8601() file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs similarity index 100% rename from test/web/mastodon_api/views/status_view_test.exs rename to test/pleroma/web/mastodon_api/views/status_view_test.exs diff --git a/test/web/mastodon_api/views/subscription_view_test.exs b/test/pleroma/web/mastodon_api/views/subscription_view_test.exs similarity index 100% rename from test/web/mastodon_api/views/subscription_view_test.exs rename to test/pleroma/web/mastodon_api/views/subscription_view_test.exs diff --git a/test/web/media_proxy/invalidations/http_test.exs b/test/pleroma/web/media_proxy/invalidation/http_test.exs similarity index 88% rename from test/web/media_proxy/invalidations/http_test.exs rename to test/pleroma/web/media_proxy/invalidation/http_test.exs index a1bef5237..13d081325 100644 --- a/test/web/media_proxy/invalidations/http_test.exs +++ b/test/pleroma/web/media_proxy/invalidation/http_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.MediaProxy.Invalidation.HttpTest do use ExUnit.Case alias Pleroma.Web.MediaProxy.Invalidation diff --git a/test/web/media_proxy/invalidations/script_test.exs b/test/pleroma/web/media_proxy/invalidation/script_test.exs similarity index 83% rename from test/web/media_proxy/invalidations/script_test.exs rename to test/pleroma/web/media_proxy/invalidation/script_test.exs index 51833ab18..692cbb2df 100644 --- a/test/web/media_proxy/invalidations/script_test.exs +++ b/test/pleroma/web/media_proxy/invalidation/script_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.MediaProxy.Invalidation.ScriptTest do use ExUnit.Case alias Pleroma.Web.MediaProxy.Invalidation diff --git a/test/web/media_proxy/invalidation_test.exs b/test/pleroma/web/media_proxy/invalidation_test.exs similarity index 93% rename from test/web/media_proxy/invalidation_test.exs rename to test/pleroma/web/media_proxy/invalidation_test.exs index 926ae74ca..aa1435ac0 100644 --- a/test/web/media_proxy/invalidation_test.exs +++ b/test/pleroma/web/media_proxy/invalidation_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.MediaProxy.InvalidationTest do use ExUnit.Case use Pleroma.Tests.Helpers diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs new file mode 100644 index 000000000..e9b584822 --- /dev/null +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -0,0 +1,342 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do + use Pleroma.Web.ConnCase + + import Mock + + alias Pleroma.Web.MediaProxy + alias Plug.Conn + + setup do + on_exit(fn -> Cachex.clear(:banned_urls_cache) end) + end + + describe "Media Proxy" do + setup do + clear_config([:media_proxy, :enabled], true) + clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") + + [url: MediaProxy.encode_url("https://google.fn/test.png")] + end + + test "it returns 404 when disabled", %{conn: conn} do + clear_config([:media_proxy, :enabled], false) + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/hhgfh/eeeee") + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/hhgfh/eeee/fff") + end + + test "it returns 403 for invalid signature", %{conn: conn, url: url} do + Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") + %{path: path} = URI.parse(url) + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, path) + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, "/proxy/hhgfh/eeee") + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, "/proxy/hhgfh/eeee/fff") + end + + test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do + invalid_url = String.replace(url, "test.png", "test-file.png") + response = get(conn, invalid_url) + assert response.status == 302 + assert redirected_to(response) == url + end + + test "it performs ReverseProxy.call with valid signature", %{conn: conn, url: url} do + with_mock Pleroma.ReverseProxy, + call: fn _conn, _url, _opts -> %Conn{status: :success} end do + assert %Conn{status: :success} = get(conn, url) + end + end + + test "it returns 404 when url is in banned_urls cache", %{conn: conn, url: url} do + MediaProxy.put_in_banned_urls("https://google.fn/test.png") + + with_mock Pleroma.ReverseProxy, + call: fn _conn, _url, _opts -> %Conn{status: :success} end do + assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url) + end + end + end + + describe "Media Preview Proxy" do + def assert_dependencies_installed do + missing_dependencies = Pleroma.Helpers.MediaHelper.missing_dependencies() + + assert missing_dependencies == [], + "Error: missing dependencies (please refer to `docs/installation`): #{ + inspect(missing_dependencies) + }" + end + + setup do + clear_config([:media_proxy, :enabled], true) + clear_config([:media_preview_proxy, :enabled], true) + clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") + + original_url = "https://google.fn/test.png" + + [ + url: MediaProxy.encode_preview_url(original_url), + media_proxy_url: MediaProxy.encode_url(original_url) + ] + end + + test "returns 404 when media proxy is disabled", %{conn: conn} do + clear_config([:media_proxy, :enabled], false) + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/preview/hhgfh/eeeee") + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/preview/hhgfh/fff") + end + + test "returns 404 when disabled", %{conn: conn} do + clear_config([:media_preview_proxy, :enabled], false) + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/preview/hhgfh/eeeee") + + assert %Conn{ + status: 404, + resp_body: "Not Found" + } = get(conn, "/proxy/preview/hhgfh/fff") + end + + test "it returns 403 for invalid signature", %{conn: conn, url: url} do + Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") + %{path: path} = URI.parse(url) + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, path) + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, "/proxy/preview/hhgfh/eeee") + + assert %Conn{ + status: 403, + resp_body: "Forbidden" + } = get(conn, "/proxy/preview/hhgfh/eeee/fff") + end + + test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do + invalid_url = String.replace(url, "test.png", "test-file.png") + response = get(conn, invalid_url) + assert response.status == 302 + assert redirected_to(response) == url + end + + test "responds with 424 Failed Dependency if HEAD request to media proxy fails", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 500, body: ""} + end) + + response = get(conn, url) + assert response.status == 424 + assert response.resp_body == "Can't fetch HTTP headers (HTTP 500)." + end + + test "redirects to media proxy URI on unsupported content type", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]} + end) + + response = get(conn, url) + assert response.status == 302 + assert redirected_to(response) == media_proxy_url + end + + test "with `static=true` and GIF image preview requested, responds with JPEG image", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + assert_dependencies_installed() + + # Setting a high :min_content_length to ensure this scenario is not affected by its logic + clear_config([:media_preview_proxy, :min_content_length], 1_000_000_000) + + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{ + status: 200, + body: "", + headers: [{"content-type", "image/gif"}, {"content-length", "1001718"}] + } + + %{method: :get, url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.gif")} + end) + + response = get(conn, url <> "?static=true") + + assert response.status == 200 + assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"] + assert response.resp_body != "" + end + + test "with GIF image preview requested and no `static` param, redirects to media proxy URI", + %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]} + end) + + response = get(conn, url) + + assert response.status == 302 + assert redirected_to(response) == media_proxy_url + end + + test "with `static` param and non-GIF image preview requested, " <> + "redirects to media preview proxy URI without `static` param", + %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} + end) + + response = get(conn, url <> "?static=true") + + assert response.status == 302 + assert redirected_to(response) == url + end + + test "with :min_content_length setting not matched by Content-Length header, " <> + "redirects to media proxy URI", + %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + clear_config([:media_preview_proxy, :min_content_length], 100_000) + + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{ + status: 200, + body: "", + headers: [{"content-type", "image/gif"}, {"content-length", "5000"}] + } + end) + + response = get(conn, url) + + assert response.status == 302 + assert redirected_to(response) == media_proxy_url + end + + test "thumbnails PNG images into PNG", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + assert_dependencies_installed() + + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]} + + %{method: :get, url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.png")} + end) + + response = get(conn, url) + + assert response.status == 200 + assert Conn.get_resp_header(response, "content-type") == ["image/png"] + assert response.resp_body != "" + end + + test "thumbnails JPEG images into JPEG", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + assert_dependencies_installed() + + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} + + %{method: :get, url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + end) + + response = get(conn, url) + + assert response.status == 200 + assert Conn.get_resp_header(response, "content-type") == ["image/jpeg"] + assert response.resp_body != "" + end + + test "redirects to media proxy URI in case of thumbnailing error", %{ + conn: conn, + url: url, + media_proxy_url: media_proxy_url + } do + Tesla.Mock.mock(fn + %{method: "head", url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]} + + %{method: :get, url: ^media_proxy_url} -> + %Tesla.Env{status: 200, body: "error"} + end) + + response = get(conn, url) + + assert response.status == 302 + assert redirected_to(response) == media_proxy_url + end + end +end diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/pleroma/web/media_proxy_test.exs similarity index 74% rename from test/web/media_proxy/media_proxy_test.exs rename to test/pleroma/web/media_proxy_test.exs index 72885cfdd..0e6df826c 100644 --- a/test/web/media_proxy/media_proxy_test.exs +++ b/test/pleroma/web/media_proxy_test.exs @@ -6,9 +6,16 @@ defmodule Pleroma.Web.MediaProxyTest do use ExUnit.Case use Pleroma.Tests.Helpers + alias Pleroma.Config alias Pleroma.Web.Endpoint alias Pleroma.Web.MediaProxy + defp decode_result(encoded) do + [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") + {:ok, decoded} = MediaProxy.decode_url(sig, base64) + decoded + end + describe "when enabled" do setup do: clear_config([:media_proxy, :enabled], true) @@ -35,7 +42,7 @@ test "encodes and decodes URL" do assert String.starts_with?( encoded, - Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()) + Config.get([:media_proxy, :base_url], Pleroma.Web.base_url()) ) assert String.ends_with?(encoded, "/logo.png") @@ -75,6 +82,64 @@ test "validates signature" do assert MediaProxy.decode_url(sig, base64) == {:error, :invalid_signature} end + def test_verify_request_path_and_url(request_path, url, expected_result) do + assert MediaProxy.verify_request_path_and_url(request_path, url) == expected_result + + assert MediaProxy.verify_request_path_and_url( + %Plug.Conn{ + params: %{"filename" => Path.basename(request_path)}, + request_path: request_path + }, + url + ) == expected_result + end + + test "if first arg of `verify_request_path_and_url/2` is a Plug.Conn without \"filename\" " <> + "parameter, `verify_request_path_and_url/2` returns :ok " do + assert MediaProxy.verify_request_path_and_url( + %Plug.Conn{params: %{}, request_path: "/some/path"}, + "https://instance.com/file.jpg" + ) == :ok + + assert MediaProxy.verify_request_path_and_url( + %Plug.Conn{params: %{}, request_path: "/path/to/file.jpg"}, + "https://instance.com/file.jpg" + ) == :ok + end + + test "`verify_request_path_and_url/2` preserves the encoded or decoded path" do + test_verify_request_path_and_url( + "/Hello world.jpg", + "http://pleroma.social/Hello world.jpg", + :ok + ) + + test_verify_request_path_and_url( + "/Hello%20world.jpg", + "http://pleroma.social/Hello%20world.jpg", + :ok + ) + + test_verify_request_path_and_url( + "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", + "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", + :ok + ) + + test_verify_request_path_and_url( + # Note: `conn.request_path` returns encoded url + "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg", + "https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg", + :ok + ) + + test_verify_request_path_and_url( + "/my%2Flong%2Furl%2F2019%2F07%2FS", + "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", + {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"} + ) + end + test "uses the configured base_url" do base_url = "https://cache.pleroma.social" clear_config([:media_proxy, :base_url], base_url) @@ -124,12 +189,6 @@ test "does not encode remote urls" do end end - defp decode_result(encoded) do - [_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/") - {:ok, decoded} = MediaProxy.decode_url(sig, base64) - decoded - end - describe "whitelist" do setup do: clear_config([:media_proxy, :enabled], true) diff --git a/test/web/metadata/player_view_test.exs b/test/pleroma/web/metadata/player_view_test.exs similarity index 100% rename from test/web/metadata/player_view_test.exs rename to test/pleroma/web/metadata/player_view_test.exs diff --git a/test/web/metadata/feed_test.exs b/test/pleroma/web/metadata/providers/feed_test.exs similarity index 100% rename from test/web/metadata/feed_test.exs rename to test/pleroma/web/metadata/providers/feed_test.exs diff --git a/test/web/metadata/opengraph_test.exs b/test/pleroma/web/metadata/providers/open_graph_test.exs similarity index 100% rename from test/web/metadata/opengraph_test.exs rename to test/pleroma/web/metadata/providers/open_graph_test.exs diff --git a/test/web/metadata/rel_me_test.exs b/test/pleroma/web/metadata/providers/rel_me_test.exs similarity index 100% rename from test/web/metadata/rel_me_test.exs rename to test/pleroma/web/metadata/providers/rel_me_test.exs diff --git a/test/web/metadata/restrict_indexing_test.exs b/test/pleroma/web/metadata/providers/restrict_indexing_test.exs similarity index 65% rename from test/web/metadata/restrict_indexing_test.exs rename to test/pleroma/web/metadata/providers/restrict_indexing_test.exs index aad0bac42..6b3a65372 100644 --- a/test/web/metadata/restrict_indexing_test.exs +++ b/test/pleroma/web/metadata/providers/restrict_indexing_test.exs @@ -14,8 +14,14 @@ test "for remote user" do test "for local user" do assert Pleroma.Web.Metadata.Providers.RestrictIndexing.build_tags(%{ - user: %Pleroma.User{local: true} + user: %Pleroma.User{local: true, discoverable: true} }) == [] end + + test "for local user when discoverable is false" do + assert Pleroma.Web.Metadata.Providers.RestrictIndexing.build_tags(%{ + user: %Pleroma.User{local: true, discoverable: false} + }) == [{:meta, [name: "robots", content: "noindex, noarchive"], []}] + end end end diff --git a/test/web/metadata/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs similarity index 100% rename from test/web/metadata/twitter_card_test.exs rename to test/pleroma/web/metadata/providers/twitter_card_test.exs diff --git a/test/web/metadata/utils_test.exs b/test/pleroma/web/metadata/utils_test.exs similarity index 100% rename from test/web/metadata/utils_test.exs rename to test/pleroma/web/metadata/utils_test.exs diff --git a/test/web/metadata/metadata_test.exs b/test/pleroma/web/metadata_test.exs similarity index 57% rename from test/web/metadata/metadata_test.exs rename to test/pleroma/web/metadata_test.exs index 9d3121b7b..ca6cbe67f 100644 --- a/test/web/metadata/metadata_test.exs +++ b/test/pleroma/web/metadata_test.exs @@ -16,7 +16,14 @@ test "for remote user" do end test "for local user" do - user = insert(:user) + user = insert(:user, discoverable: false) + + assert Pleroma.Web.Metadata.build_tags(%{user: user}) =~ + "" + end + + test "for local user set to discoverable" do + user = insert(:user, discoverable: true) refute Pleroma.Web.Metadata.build_tags(%{user: user}) =~ "" @@ -24,11 +31,19 @@ test "for local user" do end describe "no metadata for private instances" do - test "for local user" do + test "for local user set to discoverable" do clear_config([:instance, :public], false) - user = insert(:user, bio: "This is my secret fedi account bio") + user = insert(:user, bio: "This is my secret fedi account bio", discoverable: true) assert "" = Pleroma.Web.Metadata.build_tags(%{user: user}) end + + test "search exclusion metadata is included" do + clear_config([:instance, :public], false) + user = insert(:user, bio: "This is my secret fedi account bio", discoverable: false) + + assert ~s() == + Pleroma.Web.Metadata.build_tags(%{user: user}) + end end end diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/pleroma/web/mongoose_im_controller_test.exs similarity index 97% rename from test/web/mongooseim/mongoose_im_controller_test.exs rename to test/pleroma/web/mongoose_im_controller_test.exs index 5176cde84..e3a8aa3d8 100644 --- a/test/web/mongooseim/mongoose_im_controller_test.exs +++ b/test/pleroma/web/mongoose_im_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.MongooseIMController do +defmodule Pleroma.Web.MongooseIMControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory diff --git a/test/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs similarity index 100% rename from test/web/node_info_test.exs rename to test/pleroma/web/node_info_test.exs diff --git a/test/web/oauth/app_test.exs b/test/pleroma/web/o_auth/app_test.exs similarity index 100% rename from test/web/oauth/app_test.exs rename to test/pleroma/web/o_auth/app_test.exs diff --git a/test/web/oauth/authorization_test.exs b/test/pleroma/web/o_auth/authorization_test.exs similarity index 100% rename from test/web/oauth/authorization_test.exs rename to test/pleroma/web/o_auth/authorization_test.exs diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/pleroma/web/o_auth/ldap_authorization_test.exs similarity index 100% rename from test/web/oauth/ldap_authorization_test.exs rename to test/pleroma/web/o_auth/ldap_authorization_test.exs diff --git a/test/web/oauth/mfa_controller_test.exs b/test/pleroma/web/o_auth/mfa_controller_test.exs similarity index 100% rename from test/web/oauth/mfa_controller_test.exs rename to test/pleroma/web/o_auth/mfa_controller_test.exs diff --git a/test/web/oauth/oauth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs similarity index 99% rename from test/web/oauth/oauth_controller_test.exs rename to test/pleroma/web/o_auth/o_auth_controller_test.exs index 1200126b8..a00df8cc7 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -77,7 +77,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", % } ) - assert response = html_response(conn, 302) + assert html_response(conn, 302) redirect_query = URI.parse(redirected_to(conn)).query assert %{"state" => state_param} = URI.decode_query(redirect_query) @@ -119,7 +119,7 @@ test "with user-bound registration, GET /oauth//callback redirects to } ) - assert response = html_response(conn, 302) + assert html_response(conn, 302) assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ end @@ -182,7 +182,7 @@ test "on authentication error, GET /oauth//callback redirects to `redi } ) - assert response = html_response(conn, 302) + assert html_response(conn, 302) assert redirected_to(conn) == app.redirect_uris assert get_flash(conn, :error) == "Failed to authenticate: (error description)." end @@ -238,7 +238,7 @@ test "with valid params, POST /oauth/register?op=register redirects to `redirect } ) - assert response = html_response(conn, 302) + assert html_response(conn, 302) assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ end @@ -268,7 +268,7 @@ test "with unlisted `redirect_uri`, POST /oauth/register?op=register results in } ) - assert response = html_response(conn, 401) + assert html_response(conn, 401) end test "with invalid params, POST /oauth/register?op=register renders registration_details page", @@ -336,7 +336,7 @@ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_ } ) - assert response = html_response(conn, 302) + assert html_response(conn, 302) assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ end @@ -367,7 +367,7 @@ test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in H } ) - assert response = html_response(conn, 401) + assert html_response(conn, 401) end test "with invalid params, POST /oauth/register?op=connect renders registration_details page", diff --git a/test/web/oauth/token/utils_test.exs b/test/pleroma/web/o_auth/token/utils_test.exs similarity index 100% rename from test/web/oauth/token/utils_test.exs rename to test/pleroma/web/o_auth/token/utils_test.exs diff --git a/test/web/oauth/token_test.exs b/test/pleroma/web/o_auth/token_test.exs similarity index 79% rename from test/web/oauth/token_test.exs rename to test/pleroma/web/o_auth/token_test.exs index 40d71eb59..c88b9cc98 100644 --- a/test/web/oauth/token_test.exs +++ b/test/pleroma/web/o_auth/token_test.exs @@ -69,17 +69,4 @@ test "deletes all tokens of a user" do assert tokens == 2 end - - test "deletes expired tokens" do - insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3)) - insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3)) - t3 = insert(:oauth_token) - t4 = insert(:oauth_token, valid_until: Timex.shift(Timex.now(), minutes: 10)) - {tokens, _} = Token.delete_expired_tokens() - assert tokens == 2 - available_tokens = Pleroma.Repo.all(Token) - - token_ids = available_tokens |> Enum.map(& &1.id) - assert token_ids == [t3.id, t4.id] - end end diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/pleroma/web/o_status/o_status_controller_test.exs similarity index 94% rename from test/web/ostatus/ostatus_controller_test.exs rename to test/pleroma/web/o_status/o_status_controller_test.exs index ee498f4b5..65b2c22db 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/pleroma/web/o_status/o_status_controller_test.exs @@ -7,7 +7,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do import Pleroma.Factory - alias Pleroma.Config alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -21,7 +20,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do :ok end - setup do: clear_config([:instance, :federating], true) + setup do: clear_config([:static_fe, :enabled], false) describe "Mastodon compatibility routes" do setup %{conn: conn} do @@ -215,15 +214,16 @@ test "404s a non-existing notice", %{conn: conn} do assert response(conn, 404) end - test "it requires authentication if instance is NOT federating", %{ + test "does not require authentication on non-federating instances", %{ conn: conn } do - user = insert(:user) + clear_config([:instance, :federating], false) note_activity = insert(:note_activity) - conn = put_req_header(conn, "accept", "text/html") - - ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}", user) + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{note_activity.id}") + |> response(200) end end @@ -325,14 +325,16 @@ test "404s when attachment isn't audio or video", %{conn: conn} do |> response(404) end - test "it requires authentication if instance is NOT federating", %{ + test "does not require authentication on non-federating instances", %{ conn: conn, note_activity: note_activity } do - user = insert(:user) - conn = put_req_header(conn, "accept", "text/html") + clear_config([:instance, :federating], false) - ensure_federating_or_authenticated(conn, "/notice/#{note_activity.id}/embed_player", user) + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{note_activity.id}/embed_player") + |> response(200) end end end diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/account_controller_test.exs similarity index 100% rename from test/web/pleroma_api/controllers/account_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/account_controller_test.exs diff --git a/test/web/pleroma_api/controllers/chat_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs similarity index 89% rename from test/web/pleroma_api/controllers/chat_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs index 44a78a738..6381f9757 100644 --- a/test/web/pleroma_api/controllers/chat_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do - use Pleroma.Web.ConnCase, async: true + use Pleroma.Web.ConnCase alias Pleroma.Chat alias Pleroma.Chat.MessageReference @@ -105,7 +105,7 @@ test "it fails if there is no content", %{conn: conn, user: user} do test "it works with an attachment", %{conn: conn, user: user} do file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -201,17 +201,39 @@ test "it paginates", %{conn: conn, user: user} do chat = Chat.get(user.id, recipient.ap_id) - result = - conn - |> get("/api/v1/pleroma/chats/#{chat.id}/messages") - |> json_response_and_validate_schema(200) + response = get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages") + result = json_response_and_validate_schema(response, 200) + + [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ") + api_endpoint = "/api/v1/pleroma/chats/" + + assert String.match?( + next, + ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*; rel=\"next\"$) + ) + + assert String.match?( + prev, + ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&min_id=.*; rel=\"prev\"$) + ) assert length(result) == 20 - result = - conn - |> get("/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}") - |> json_response_and_validate_schema(200) + response = + get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}") + + result = json_response_and_validate_schema(response, 200) + [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ") + + assert String.match?( + next, + ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*; rel=\"next\"$) + ) + + assert String.match?( + prev, + ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*&min_id=.*; rel=\"prev\"$) + ) assert length(result) == 10 end @@ -240,12 +262,10 @@ test "it returns the messages for a given chat", %{conn: conn, user: user} do assert length(result) == 3 # Trying to get the chat of a different user - result = - conn - |> assign(:user, other_user) - |> get("/api/v1/pleroma/chats/#{chat.id}/messages") - - assert result |> json_response(404) + conn + |> assign(:user, other_user) + |> get("/api/v1/pleroma/chats/#{chat.id}/messages") + |> json_response_and_validate_schema(404) end end diff --git a/test/web/pleroma_api/controllers/conversation_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs similarity index 100% rename from test/web/pleroma_api/controllers/conversation_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs new file mode 100644 index 000000000..82de86ee3 --- /dev/null +++ b/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs @@ -0,0 +1,357 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EmojiFileControllerTest do + use Pleroma.Web.ConnCase + + import Tesla.Mock + import Pleroma.Factory + + @emoji_path Path.join( + Pleroma.Config.get!([:instance, :static_dir]), + "emoji" + ) + setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false) + + setup do: clear_config([:instance, :public], true) + + setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + admin_conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + Pleroma.Emoji.reload() + {:ok, %{admin_conn: admin_conn}} + end + + describe "POST/PATCH/DELETE /api/pleroma/emoji/packs/files?name=:name" do + setup do + pack_file = "#{@emoji_path}/test_pack/pack.json" + original_content = File.read!(pack_file) + + on_exit(fn -> + File.write!(pack_file, original_content) + end) + + :ok + end + + test "upload zip file with emojies", %{admin_conn: admin_conn} do + on_exit(fn -> + [ + "128px/a_trusted_friend-128.png", + "auroraborealis.png", + "1000px/baby_in_a_box.png", + "1000px/bear.png", + "128px/bear-128.png" + ] + |> Enum.each(fn path -> File.rm_rf!("#{@emoji_path}/test_pack/#{path}") end) + end) + + resp = + admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{ + file: %Plug.Upload{ + content_type: "application/zip", + filename: "emojis.zip", + path: Path.absname("test/fixtures/emojis.zip") + } + }) + |> json_response_and_validate_schema(200) + + assert resp == %{ + "a_trusted_friend-128" => "128px/a_trusted_friend-128.png", + "auroraborealis" => "auroraborealis.png", + "baby_in_a_box" => "1000px/baby_in_a_box.png", + "bear" => "1000px/bear.png", + "bear-128" => "128px/bear-128.png", + "blank" => "blank.png", + "blank2" => "blank2.png" + } + + Enum.each(Map.values(resp), fn path -> + assert File.exists?("#{@emoji_path}/test_pack/#{path}") + end) + end + + test "create shortcode exists", %{admin_conn: admin_conn} do + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank", + filename: "dir/blank.png", + file: %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_path}/test_pack/blank.png" + } + }) + |> json_response_and_validate_schema(:conflict) == %{ + "error" => "An emoji with the \"blank\" shortcode already exists" + } + end + + test "don't rewrite old emoji", %{admin_conn: admin_conn} do + on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end) + + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank3", + filename: "dir/blank.png", + file: %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_path}/test_pack/blank.png" + } + }) + |> json_response_and_validate_schema(200) == %{ + "blank" => "blank.png", + "blank2" => "blank2.png", + "blank3" => "dir/blank.png" + } + + assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") + + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank", + new_shortcode: "blank2", + new_filename: "dir_2/blank_3.png" + }) + |> json_response_and_validate_schema(:conflict) == %{ + "error" => + "New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option" + } + end + + test "rewrite old emoji with force option", %{admin_conn: admin_conn} do + on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end) + + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank3", + filename: "dir/blank.png", + file: %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_path}/test_pack/blank.png" + } + }) + |> json_response_and_validate_schema(200) == %{ + "blank" => "blank.png", + "blank2" => "blank2.png", + "blank3" => "dir/blank.png" + } + + assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") + + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank3", + new_shortcode: "blank4", + new_filename: "dir_2/blank_3.png", + force: true + }) + |> json_response_and_validate_schema(200) == %{ + "blank" => "blank.png", + "blank2" => "blank2.png", + "blank4" => "dir_2/blank_3.png" + } + + assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") + end + + test "with empty filename", %{admin_conn: admin_conn} do + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank2", + filename: "", + file: %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_path}/test_pack/blank.png" + } + }) + |> json_response_and_validate_schema(422) == %{ + "error" => "pack name, shortcode or filename cannot be empty" + } + end + + test "add file with not loaded pack", %{admin_conn: admin_conn} do + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=not_loaded", %{ + shortcode: "blank3", + filename: "dir/blank.png", + file: %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_path}/test_pack/blank.png" + } + }) + |> json_response_and_validate_schema(:not_found) == %{ + "error" => "pack \"not_loaded\" is not found" + } + end + + test "remove file with not loaded pack", %{admin_conn: admin_conn} do + assert admin_conn + |> delete("/api/pleroma/emoji/packs/files?name=not_loaded&shortcode=blank3") + |> json_response_and_validate_schema(:not_found) == %{ + "error" => "pack \"not_loaded\" is not found" + } + end + + test "remove file with empty shortcode", %{admin_conn: admin_conn} do + assert admin_conn + |> delete("/api/pleroma/emoji/packs/files?name=not_loaded&shortcode=") + |> json_response_and_validate_schema(:not_found) == %{ + "error" => "pack \"not_loaded\" is not found" + } + end + + test "update file with not loaded pack", %{admin_conn: admin_conn} do + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> patch("/api/pleroma/emoji/packs/files?name=not_loaded", %{ + shortcode: "blank4", + new_shortcode: "blank3", + new_filename: "dir_2/blank_3.png" + }) + |> json_response_and_validate_schema(:not_found) == %{ + "error" => "pack \"not_loaded\" is not found" + } + end + + test "new with shortcode as file with update", %{admin_conn: admin_conn} do + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank4", + filename: "dir/blank.png", + file: %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_path}/test_pack/blank.png" + } + }) + |> json_response_and_validate_schema(200) == %{ + "blank" => "blank.png", + "blank4" => "dir/blank.png", + "blank2" => "blank2.png" + } + + assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") + + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank4", + new_shortcode: "blank3", + new_filename: "dir_2/blank_3.png" + }) + |> json_response_and_validate_schema(200) == %{ + "blank3" => "dir_2/blank_3.png", + "blank" => "blank.png", + "blank2" => "blank2.png" + } + + refute File.exists?("#{@emoji_path}/test_pack/dir/") + assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") + + assert admin_conn + |> delete("/api/pleroma/emoji/packs/files?name=test_pack&shortcode=blank3") + |> json_response_and_validate_schema(200) == %{ + "blank" => "blank.png", + "blank2" => "blank2.png" + } + + refute File.exists?("#{@emoji_path}/test_pack/dir_2/") + + on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir") end) + end + + test "new with shortcode from url", %{admin_conn: admin_conn} do + mock(fn + %{ + method: :get, + url: "https://test-blank/blank_url.png" + } -> + text(File.read!("#{@emoji_path}/test_pack/blank.png")) + end) + + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank_url", + file: "https://test-blank/blank_url.png" + }) + |> json_response_and_validate_schema(200) == %{ + "blank_url" => "blank_url.png", + "blank" => "blank.png", + "blank2" => "blank2.png" + } + + assert File.exists?("#{@emoji_path}/test_pack/blank_url.png") + + on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/blank_url.png") end) + end + + test "new without shortcode", %{admin_conn: admin_conn} do + on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end) + + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{ + file: %Plug.Upload{ + filename: "shortcode.png", + path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png" + } + }) + |> json_response_and_validate_schema(200) == %{ + "shortcode" => "shortcode.png", + "blank" => "blank.png", + "blank2" => "blank2.png" + } + end + + test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do + assert admin_conn + |> delete("/api/pleroma/emoji/packs/files?name=test_pack&shortcode=blank3") + |> json_response_and_validate_schema(:bad_request) == %{ + "error" => "Emoji \"blank3\" does not exist" + } + end + + test "update non existing emoji", %{admin_conn: admin_conn} do + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank3", + new_shortcode: "blank4", + new_filename: "dir_2/blank_3.png" + }) + |> json_response_and_validate_schema(:bad_request) == %{ + "error" => "Emoji \"blank3\" does not exist" + } + end + + test "update with empty shortcode", %{admin_conn: admin_conn} do + assert %{ + "error" => "Missing field: new_shortcode." + } = + admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{ + shortcode: "blank", + new_filename: "dir_2/blank_3.png" + }) + |> json_response_and_validate_schema(:bad_request) + end + end +end diff --git a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs similarity index 56% rename from test/web/pleroma_api/controllers/emoji_pack_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs index e113bb15f..3445f0ca0 100644 --- a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs @@ -37,11 +37,11 @@ test "GET /api/pleroma/emoji/packs when :public: false", %{conn: conn} do test "GET /api/pleroma/emoji/packs", %{conn: conn} do resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200) - assert resp["count"] == 3 + assert resp["count"] == 4 assert resp["packs"] |> Map.keys() - |> length() == 3 + |> length() == 4 shared = resp["packs"]["test_pack"] assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"} @@ -58,7 +58,7 @@ test "GET /api/pleroma/emoji/packs", %{conn: conn} do |> get("/api/pleroma/emoji/packs?page_size=1") |> json_response_and_validate_schema(200) - assert resp["count"] == 3 + assert resp["count"] == 4 packs = Map.keys(resp["packs"]) @@ -71,7 +71,7 @@ test "GET /api/pleroma/emoji/packs", %{conn: conn} do |> get("/api/pleroma/emoji/packs?page_size=1&page=2") |> json_response_and_validate_schema(200) - assert resp["count"] == 3 + assert resp["count"] == 4 packs = Map.keys(resp["packs"]) assert length(packs) == 1 [pack2] = packs @@ -81,18 +81,28 @@ test "GET /api/pleroma/emoji/packs", %{conn: conn} do |> get("/api/pleroma/emoji/packs?page_size=1&page=3") |> json_response_and_validate_schema(200) - assert resp["count"] == 3 + assert resp["count"] == 4 packs = Map.keys(resp["packs"]) assert length(packs) == 1 [pack3] = packs - assert [pack1, pack2, pack3] |> Enum.uniq() |> length() == 3 + + resp = + conn + |> get("/api/pleroma/emoji/packs?page_size=1&page=4") + |> json_response_and_validate_schema(200) + + assert resp["count"] == 4 + packs = Map.keys(resp["packs"]) + assert length(packs) == 1 + [pack4] = packs + assert [pack1, pack2, pack3, pack4] |> Enum.uniq() |> length() == 4 end describe "GET /api/pleroma/emoji/packs/remote" do test "shareable instance", %{admin_conn: admin_conn, conn: conn} do resp = conn - |> get("/api/pleroma/emoji/packs") + |> get("/api/pleroma/emoji/packs?page=2&page_size=1") |> json_response_and_validate_schema(200) mock(fn @@ -102,12 +112,12 @@ test "shareable instance", %{admin_conn: admin_conn, conn: conn} do %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} -> json(%{metadata: %{features: ["shareable_emoji_packs"]}}) - %{method: :get, url: "https://example.com/api/pleroma/emoji/packs"} -> + %{method: :get, url: "https://example.com/api/pleroma/emoji/packs?page=2&page_size=1"} -> json(resp) end) assert admin_conn - |> get("/api/pleroma/emoji/packs/remote?url=https://example.com") + |> get("/api/pleroma/emoji/packs/remote?url=https://example.com&page=2&page_size=1") |> json_response_and_validate_schema(200) == resp end @@ -128,11 +138,11 @@ test "non shareable instance", %{admin_conn: admin_conn} do end end - describe "GET /api/pleroma/emoji/packs/:name/archive" do + describe "GET /api/pleroma/emoji/packs/archive?name=:name" do test "download shared pack", %{conn: conn} do resp = conn - |> get("/api/pleroma/emoji/packs/test_pack/archive") + |> get("/api/pleroma/emoji/packs/archive?name=test_pack") |> response(200) {:ok, arch} = :zip.unzip(resp, [:memory]) @@ -143,7 +153,7 @@ test "download shared pack", %{conn: conn} do test "non existing pack", %{conn: conn} do assert conn - |> get("/api/pleroma/emoji/packs/test_pack_for_import/archive") + |> get("/api/pleroma/emoji/packs/archive?name=test_pack_for_import") |> json_response_and_validate_schema(:not_found) == %{ "error" => "Pack test_pack_for_import does not exist" } @@ -151,7 +161,7 @@ test "non existing pack", %{conn: conn} do test "non downloadable pack", %{conn: conn} do assert conn - |> get("/api/pleroma/emoji/packs/test_pack_nonshared/archive") + |> get("/api/pleroma/emoji/packs/archive?name=test_pack_nonshared") |> json_response_and_validate_schema(:forbidden) == %{ "error" => "Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" @@ -173,28 +183,28 @@ test "shared pack from remote and non shared from fallback-src", %{ %{ method: :get, - url: "https://example.com/api/pleroma/emoji/packs/test_pack" + url: "https://example.com/api/pleroma/emoji/pack?name=test_pack" } -> conn - |> get("/api/pleroma/emoji/packs/test_pack") + |> get("/api/pleroma/emoji/pack?name=test_pack") |> json_response_and_validate_schema(200) |> json() %{ method: :get, - url: "https://example.com/api/pleroma/emoji/packs/test_pack/archive" + url: "https://example.com/api/pleroma/emoji/packs/archive?name=test_pack" } -> conn - |> get("/api/pleroma/emoji/packs/test_pack/archive") + |> get("/api/pleroma/emoji/packs/archive?name=test_pack") |> response(200) |> text() %{ method: :get, - url: "https://example.com/api/pleroma/emoji/packs/test_pack_nonshared" + url: "https://example.com/api/pleroma/emoji/pack?name=test_pack_nonshared" } -> conn - |> get("/api/pleroma/emoji/packs/test_pack_nonshared") + |> get("/api/pleroma/emoji/pack?name=test_pack_nonshared") |> json_response_and_validate_schema(200) |> json() @@ -218,7 +228,7 @@ test "shared pack from remote and non shared from fallback-src", %{ assert File.exists?("#{@emoji_path}/test_pack2/blank.png") assert admin_conn - |> delete("/api/pleroma/emoji/packs/test_pack2") + |> delete("/api/pleroma/emoji/pack?name=test_pack2") |> json_response_and_validate_schema(200) == "ok" refute File.exists?("#{@emoji_path}/test_pack2") @@ -239,7 +249,7 @@ test "shared pack from remote and non shared from fallback-src", %{ assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png") assert admin_conn - |> delete("/api/pleroma/emoji/packs/test_pack_nonshared2") + |> delete("/api/pleroma/emoji/pack?name=test_pack_nonshared2") |> json_response_and_validate_schema(200) == "ok" refute File.exists?("#{@emoji_path}/test_pack_nonshared2") @@ -279,14 +289,14 @@ test "checksum fail", %{admin_conn: admin_conn} do %{ method: :get, - url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha" + url: "https://example.com/api/pleroma/emoji/pack?name=pack_bad_sha" } -> {:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha") %Tesla.Env{status: 200, body: Jason.encode!(pack)} %{ method: :get, - url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha/archive" + url: "https://example.com/api/pleroma/emoji/packs/archive?name=pack_bad_sha" } -> %Tesla.Env{ status: 200, @@ -316,7 +326,7 @@ test "other error", %{admin_conn: admin_conn} do %{ method: :get, - url: "https://example.com/api/pleroma/emoji/packs/test_pack" + url: "https://example.com/api/pleroma/emoji/pack?name=test_pack" } -> {:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack") %Tesla.Env{status: 200, body: Jason.encode!(pack)} @@ -336,7 +346,7 @@ test "other error", %{admin_conn: admin_conn} do end end - describe "PATCH /api/pleroma/emoji/packs/:name" do + describe "PATCH /api/pleroma/emoji/pack?name=:name" do setup do pack_file = "#{@emoji_path}/test_pack/pack.json" original_content = File.read!(pack_file) @@ -358,7 +368,9 @@ test "other error", %{admin_conn: admin_conn} do test "for a pack without a fallback source", ctx do assert ctx[:admin_conn] |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/test_pack", %{"metadata" => ctx[:new_data]}) + |> patch("/api/pleroma/emoji/pack?name=test_pack", %{ + "metadata" => ctx[:new_data] + }) |> json_response_and_validate_schema(200) == ctx[:new_data] assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data] @@ -384,7 +396,7 @@ test "for a pack with a fallback source", ctx do assert ctx[:admin_conn] |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data}) + |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data}) |> json_response_and_validate_schema(200) == new_data_with_sha assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha @@ -404,304 +416,17 @@ test "when the fallback source doesn't have all the files", ctx do assert ctx[:admin_conn] |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data}) + |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data}) |> json_response_and_validate_schema(:bad_request) == %{ "error" => "The fallback archive does not have all files specified in pack.json" } end end - describe "POST/PATCH/DELETE /api/pleroma/emoji/packs/:name/files" do - setup do - pack_file = "#{@emoji_path}/test_pack/pack.json" - original_content = File.read!(pack_file) - - on_exit(fn -> - File.write!(pack_file, original_content) - end) - - :ok - end - - test "create shortcode exists", %{admin_conn: admin_conn} do - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> post("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank", - filename: "dir/blank.png", - file: %Plug.Upload{ - filename: "blank.png", - path: "#{@emoji_path}/test_pack/blank.png" - } - }) - |> json_response_and_validate_schema(:conflict) == %{ - "error" => "An emoji with the \"blank\" shortcode already exists" - } - end - - test "don't rewrite old emoji", %{admin_conn: admin_conn} do - on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end) - - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> post("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank3", - filename: "dir/blank.png", - file: %Plug.Upload{ - filename: "blank.png", - path: "#{@emoji_path}/test_pack/blank.png" - } - }) - |> json_response_and_validate_schema(200) == %{ - "blank" => "blank.png", - "blank2" => "blank2.png", - "blank3" => "dir/blank.png" - } - - assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") - - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank", - new_shortcode: "blank2", - new_filename: "dir_2/blank_3.png" - }) - |> json_response_and_validate_schema(:conflict) == %{ - "error" => - "New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option" - } - end - - test "rewrite old emoji with force option", %{admin_conn: admin_conn} do - on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end) - - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> post("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank3", - filename: "dir/blank.png", - file: %Plug.Upload{ - filename: "blank.png", - path: "#{@emoji_path}/test_pack/blank.png" - } - }) - |> json_response_and_validate_schema(200) == %{ - "blank" => "blank.png", - "blank2" => "blank2.png", - "blank3" => "dir/blank.png" - } - - assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") - - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank3", - new_shortcode: "blank4", - new_filename: "dir_2/blank_3.png", - force: true - }) - |> json_response_and_validate_schema(200) == %{ - "blank" => "blank.png", - "blank2" => "blank2.png", - "blank4" => "dir_2/blank_3.png" - } - - assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") - end - - test "with empty filename", %{admin_conn: admin_conn} do - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> post("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank2", - filename: "", - file: %Plug.Upload{ - filename: "blank.png", - path: "#{@emoji_path}/test_pack/blank.png" - } - }) - |> json_response_and_validate_schema(:bad_request) == %{ - "error" => "pack name, shortcode or filename cannot be empty" - } - end - - test "add file with not loaded pack", %{admin_conn: admin_conn} do - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> post("/api/pleroma/emoji/packs/not_loaded/files", %{ - shortcode: "blank3", - filename: "dir/blank.png", - file: %Plug.Upload{ - filename: "blank.png", - path: "#{@emoji_path}/test_pack/blank.png" - } - }) - |> json_response_and_validate_schema(:bad_request) == %{ - "error" => "pack \"not_loaded\" is not found" - } - end - - test "remove file with not loaded pack", %{admin_conn: admin_conn} do - assert admin_conn - |> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=blank3") - |> json_response_and_validate_schema(:bad_request) == %{ - "error" => "pack \"not_loaded\" is not found" - } - end - - test "remove file with empty shortcode", %{admin_conn: admin_conn} do - assert admin_conn - |> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=") - |> json_response_and_validate_schema(:bad_request) == %{ - "error" => "pack name or shortcode cannot be empty" - } - end - - test "update file with not loaded pack", %{admin_conn: admin_conn} do - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/not_loaded/files", %{ - shortcode: "blank4", - new_shortcode: "blank3", - new_filename: "dir_2/blank_3.png" - }) - |> json_response_and_validate_schema(:bad_request) == %{ - "error" => "pack \"not_loaded\" is not found" - } - end - - test "new with shortcode as file with update", %{admin_conn: admin_conn} do - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> post("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank4", - filename: "dir/blank.png", - file: %Plug.Upload{ - filename: "blank.png", - path: "#{@emoji_path}/test_pack/blank.png" - } - }) - |> json_response_and_validate_schema(200) == %{ - "blank" => "blank.png", - "blank4" => "dir/blank.png", - "blank2" => "blank2.png" - } - - assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") - - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank4", - new_shortcode: "blank3", - new_filename: "dir_2/blank_3.png" - }) - |> json_response_and_validate_schema(200) == %{ - "blank3" => "dir_2/blank_3.png", - "blank" => "blank.png", - "blank2" => "blank2.png" - } - - refute File.exists?("#{@emoji_path}/test_pack/dir/") - assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") - - assert admin_conn - |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") - |> json_response_and_validate_schema(200) == %{ - "blank" => "blank.png", - "blank2" => "blank2.png" - } - - refute File.exists?("#{@emoji_path}/test_pack/dir_2/") - - on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir") end) - end - - test "new with shortcode from url", %{admin_conn: admin_conn} do - mock(fn - %{ - method: :get, - url: "https://test-blank/blank_url.png" - } -> - text(File.read!("#{@emoji_path}/test_pack/blank.png")) - end) - - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> post("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank_url", - file: "https://test-blank/blank_url.png" - }) - |> json_response_and_validate_schema(200) == %{ - "blank_url" => "blank_url.png", - "blank" => "blank.png", - "blank2" => "blank2.png" - } - - assert File.exists?("#{@emoji_path}/test_pack/blank_url.png") - - on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/blank_url.png") end) - end - - test "new without shortcode", %{admin_conn: admin_conn} do - on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end) - - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> post("/api/pleroma/emoji/packs/test_pack/files", %{ - file: %Plug.Upload{ - filename: "shortcode.png", - path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png" - } - }) - |> json_response_and_validate_schema(200) == %{ - "shortcode" => "shortcode.png", - "blank" => "blank.png", - "blank2" => "blank2.png" - } - end - - test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do - assert admin_conn - |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") - |> json_response_and_validate_schema(:bad_request) == %{ - "error" => "Emoji \"blank3\" does not exist" - } - end - - test "update non existing emoji", %{admin_conn: admin_conn} do - assert admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank3", - new_shortcode: "blank4", - new_filename: "dir_2/blank_3.png" - }) - |> json_response_and_validate_schema(:bad_request) == %{ - "error" => "Emoji \"blank3\" does not exist" - } - end - - test "update with empty shortcode", %{admin_conn: admin_conn} do - assert %{ - "error" => "Missing field: new_shortcode." - } = - admin_conn - |> put_req_header("content-type", "multipart/form-data") - |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ - shortcode: "blank", - new_filename: "dir_2/blank_3.png" - }) - |> json_response_and_validate_schema(:bad_request) - end - end - - describe "POST/DELETE /api/pleroma/emoji/packs/:name" do + describe "POST/DELETE /api/pleroma/emoji/pack?name=:name" do test "creating and deleting a pack", %{admin_conn: admin_conn} do assert admin_conn - |> post("/api/pleroma/emoji/packs/test_created") + |> post("/api/pleroma/emoji/pack?name=test_created") |> json_response_and_validate_schema(200) == "ok" assert File.exists?("#{@emoji_path}/test_created/pack.json") @@ -713,7 +438,7 @@ test "creating and deleting a pack", %{admin_conn: admin_conn} do } assert admin_conn - |> delete("/api/pleroma/emoji/packs/test_created") + |> delete("/api/pleroma/emoji/pack?name=test_created") |> json_response_and_validate_schema(200) == "ok" refute File.exists?("#{@emoji_path}/test_created/pack.json") @@ -726,7 +451,7 @@ test "if pack exists", %{admin_conn: admin_conn} do File.write!(Path.join(path, "pack.json"), pack_file) assert admin_conn - |> post("/api/pleroma/emoji/packs/test_created") + |> post("/api/pleroma/emoji/pack?name=test_created") |> json_response_and_validate_schema(:conflict) == %{ "error" => "A pack named \"test_created\" already exists" } @@ -736,7 +461,7 @@ test "if pack exists", %{admin_conn: admin_conn} do test "with empty name", %{admin_conn: admin_conn} do assert admin_conn - |> post("/api/pleroma/emoji/packs/ ") + |> post("/api/pleroma/emoji/pack?name= ") |> json_response_and_validate_schema(:bad_request) == %{ "error" => "pack name cannot be empty" } @@ -745,7 +470,7 @@ test "with empty name", %{admin_conn: admin_conn} do test "deleting nonexisting pack", %{admin_conn: admin_conn} do assert admin_conn - |> delete("/api/pleroma/emoji/packs/non_existing") + |> delete("/api/pleroma/emoji/pack?name=non_existing") |> json_response_and_validate_schema(:not_found) == %{ "error" => "Pack non_existing does not exist" } @@ -753,7 +478,7 @@ test "deleting nonexisting pack", %{admin_conn: admin_conn} do test "deleting with empty name", %{admin_conn: admin_conn} do assert admin_conn - |> delete("/api/pleroma/emoji/packs/ ") + |> delete("/api/pleroma/emoji/pack?name= ") |> json_response_and_validate_schema(:bad_request) == %{ "error" => "pack name cannot be empty" } @@ -801,7 +526,7 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do } end - describe "GET /api/pleroma/emoji/packs/:name" do + describe "GET /api/pleroma/emoji/pack?name=:name" do test "shows pack.json", %{conn: conn} do assert %{ "files" => files, @@ -816,7 +541,7 @@ test "shows pack.json", %{conn: conn} do } } = conn - |> get("/api/pleroma/emoji/packs/test_pack") + |> get("/api/pleroma/emoji/pack?name=test_pack") |> json_response_and_validate_schema(200) assert files == %{"blank" => "blank.png", "blank2" => "blank2.png"} @@ -826,7 +551,7 @@ test "shows pack.json", %{conn: conn} do "files_count" => 2 } = conn - |> get("/api/pleroma/emoji/packs/test_pack?page_size=1") + |> get("/api/pleroma/emoji/pack?name=test_pack&page_size=1") |> json_response_and_validate_schema(200) assert files |> Map.keys() |> length() == 1 @@ -836,15 +561,33 @@ test "shows pack.json", %{conn: conn} do "files_count" => 2 } = conn - |> get("/api/pleroma/emoji/packs/test_pack?page_size=1&page=2") + |> get("/api/pleroma/emoji/pack?name=test_pack&page_size=1&page=2") |> json_response_and_validate_schema(200) assert files |> Map.keys() |> length() == 1 end + test "for pack name with special chars", %{conn: conn} do + assert %{ + "files" => _files, + "files_count" => 1, + "pack" => %{ + "can-download" => true, + "description" => "Test description", + "download-sha256" => _, + "homepage" => "https://pleroma.social", + "license" => "Test license", + "share-files" => true + } + } = + conn + |> get("/api/pleroma/emoji/pack?name=blobs.gg") + |> json_response_and_validate_schema(200) + end + test "non existing pack", %{conn: conn} do assert conn - |> get("/api/pleroma/emoji/packs/non_existing") + |> get("/api/pleroma/emoji/pack?name=non_existing") |> json_response_and_validate_schema(:not_found) == %{ "error" => "Pack non_existing does not exist" } @@ -852,7 +595,7 @@ test "non existing pack", %{conn: conn} do test "error name", %{conn: conn} do assert conn - |> get("/api/pleroma/emoji/packs/ ") + |> get("/api/pleroma/emoji/pack?name= ") |> json_response_and_validate_schema(:bad_request) == %{ "error" => "pack name cannot be empty" } diff --git a/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs similarity index 100% rename from test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs diff --git a/test/web/pleroma_api/controllers/mascot_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/mascot_controller_test.exs similarity index 92% rename from test/web/pleroma_api/controllers/mascot_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/mascot_controller_test.exs index e2ead6e15..289119d45 100644 --- a/test/web/pleroma_api/controllers/mascot_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/mascot_controller_test.exs @@ -24,7 +24,7 @@ test "mascot upload" do assert json_response_and_validate_schema(ret_conn, 415) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } @@ -34,7 +34,7 @@ test "mascot upload" do |> put_req_header("content-type", "multipart/form-data") |> put("/api/v1/pleroma/mascot", %{"file" => file}) - assert %{"id" => _, "type" => image} = json_response_and_validate_schema(conn, 200) + assert %{"id" => _, "type" => _image} = json_response_and_validate_schema(conn, 200) end test "mascot retrieving" do @@ -48,7 +48,7 @@ test "mascot retrieving" do # When a user sets their mascot, we should get that back file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } diff --git a/test/web/pleroma_api/controllers/notification_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/notification_controller_test.exs similarity index 100% rename from test/web/pleroma_api/controllers/notification_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/notification_controller_test.exs diff --git a/test/web/pleroma_api/controllers/scrobble_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs similarity index 100% rename from test/web/pleroma_api/controllers/scrobble_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs diff --git a/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs similarity index 98% rename from test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs index d23d08a00..22988c881 100644 --- a/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do use Pleroma.Web.ConnCase diff --git a/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs new file mode 100644 index 000000000..433c97e81 --- /dev/null +++ b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs @@ -0,0 +1,235 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do + use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo + + alias Pleroma.Config + alias Pleroma.Tests.ObanHelpers + + import Pleroma.Factory + import Mock + + setup do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + describe "POST /api/pleroma/follow_import" do + setup do: oauth_access(["follow"]) + + test "it returns HTTP 200", %{conn: conn} do + user2 = insert(:user) + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"}) + |> json_response_and_validate_schema(200) + end + + test "it imports follow lists from file", %{conn: conn} do + user2 = insert(:user) + + with_mocks([ + {File, [], + read!: fn "follow_list.txt" -> + "Account address,Show boosts\n#{user2.ap_id},true" + end} + ]) do + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{ + "list" => %Plug.Upload{path: "follow_list.txt"} + }) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == [user2] + end + end + + test "it imports new-style mastodon follow lists", %{conn: conn} do + user2 = insert(:user) + + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{ + "list" => "Account address,Show boosts\n#{user2.ap_id},true" + }) + |> json_response_and_validate_schema(200) + + assert response == "job started" + end + + test "requires 'follow' or 'write:follows' permissions" do + token1 = insert(:oauth_token, scopes: ["read", "write"]) + token2 = insert(:oauth_token, scopes: ["follow"]) + token3 = insert(:oauth_token, scopes: ["something"]) + another_user = insert(:user) + + for token <- [token1, token2, token3] do + conn = + build_conn() + |> put_req_header("authorization", "Bearer #{token.token}") + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"}) + + if token == token3 do + assert %{"error" => "Insufficient permissions: follow | write:follows."} == + json_response(conn, 403) + else + assert json_response(conn, 200) + end + end + end + + test "it imports follows with different nickname variations", %{conn: conn} do + users = [user2, user3, user4, user5, user6] = insert_list(5, :user) + + identifiers = + [ + user2.ap_id, + user3.nickname, + " ", + "@" <> user4.nickname, + user5.nickname <> "@localhost", + "@" <> user6.nickname <> "@localhost" + ] + |> Enum.join("\n") + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/follow_import", %{"list" => identifiers}) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + end + end + + describe "POST /api/pleroma/blocks_import" do + # Note: "follow" or "write:blocks" permission is required + setup do: oauth_access(["write:blocks"]) + + test "it returns HTTP 200", %{conn: conn} do + user2 = insert(:user) + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"}) + |> json_response_and_validate_schema(200) + end + + test "it imports blocks users from file", %{conn: conn} do + users = [user2, user3] = insert_list(2, :user) + + with_mocks([ + {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} + ]) do + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/blocks_import", %{ + "list" => %Plug.Upload{path: "blocks_list.txt"} + }) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + end + end + + test "it imports blocks with different nickname variations", %{conn: conn} do + users = [user2, user3, user4, user5, user6] = insert_list(5, :user) + + identifiers = + [ + user2.ap_id, + user3.nickname, + "@" <> user4.nickname, + user5.nickname <> "@localhost", + "@" <> user6.nickname <> "@localhost" + ] + |> Enum.join(" ") + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/blocks_import", %{"list" => identifiers}) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + end + end + + describe "POST /api/pleroma/mutes_import" do + # Note: "follow" or "write:mutes" permission is required + setup do: oauth_access(["write:mutes"]) + + test "it returns HTTP 200", %{user: user, conn: conn} do + user2 = insert(:user) + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/mutes_import", %{"list" => "#{user2.ap_id}"}) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == [user2] + assert Pleroma.User.mutes?(user, user2) + end + + test "it imports mutes users from file", %{user: user, conn: conn} do + users = [user2, user3] = insert_list(2, :user) + + with_mocks([ + {File, [], read!: fn "mutes_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} + ]) do + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/mutes_import", %{ + "list" => %Plug.Upload{path: "mutes_list.txt"} + }) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + assert Enum.all?(users, &Pleroma.User.mutes?(user, &1)) + end + end + + test "it imports mutes with different nickname variations", %{user: user, conn: conn} do + users = [user2, user3, user4, user5, user6] = insert_list(5, :user) + + identifiers = + [ + user2.ap_id, + user3.nickname, + "@" <> user4.nickname, + user5.nickname <> "@localhost", + "@" <> user6.nickname <> "@localhost" + ] + |> Enum.join(" ") + + assert "job started" == + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/mutes_import", %{"list" => identifiers}) + |> json_response_and_validate_schema(200) + + assert [{:ok, job_result}] = ObanHelpers.perform_all() + assert job_result == users + assert Enum.all?(users, &Pleroma.User.mutes?(user, &1)) + end + end +end diff --git a/test/web/pleroma_api/views/chat/message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs similarity index 95% rename from test/web/pleroma_api/views/chat/message_reference_view_test.exs rename to test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs index 40dbae3cd..f171a1e55 100644 --- a/test/web/pleroma_api/views/chat/message_reference_view_test.exs +++ b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceViewTest do +defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do use Pleroma.DataCase alias Pleroma.Chat @@ -19,7 +19,7 @@ test "it displays a chat message" do recipient = insert(:user) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } diff --git a/test/web/pleroma_api/views/chat_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_view_test.exs similarity index 100% rename from test/web/pleroma_api/views/chat_view_test.exs rename to test/pleroma/web/pleroma_api/views/chat_view_test.exs diff --git a/test/web/pleroma_api/views/scrobble_view_test.exs b/test/pleroma/web/pleroma_api/views/scrobble_view_test.exs similarity index 91% rename from test/web/pleroma_api/views/scrobble_view_test.exs rename to test/pleroma/web/pleroma_api/views/scrobble_view_test.exs index 6bdb56509..0f43cbdc3 100644 --- a/test/web/pleroma_api/views/scrobble_view_test.exs +++ b/test/pleroma/web/pleroma_api/views/scrobble_view_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.PleromaAPI.StatusViewTest do +defmodule Pleroma.Web.PleromaAPI.ScrobbleViewTest do use Pleroma.DataCase alias Pleroma.Web.PleromaAPI.ScrobbleView diff --git a/test/plugs/admin_secret_authentication_plug_test.exs b/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs similarity index 88% rename from test/plugs/admin_secret_authentication_plug_test.exs rename to test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs index 14094eda8..33394722a 100644 --- a/test/plugs/admin_secret_authentication_plug_test.exs +++ b/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs @@ -2,16 +2,16 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.AdminSecretAuthenticationPlugTest do +defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlugTest do use Pleroma.Web.ConnCase import Mock import Pleroma.Factory - alias Pleroma.Plugs.AdminSecretAuthenticationPlug - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.PlugHelper - alias Pleroma.Plugs.RateLimiter + alias Pleroma.Web.Plugs.AdminSecretAuthenticationPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.PlugHelper + alias Pleroma.Web.Plugs.RateLimiter test "does nothing if a user is assigned", %{conn: conn} do user = insert(:user) diff --git a/test/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs similarity index 93% rename from test/plugs/authentication_plug_test.exs rename to test/pleroma/web/plugs/authentication_plug_test.exs index 777ae15ae..af39352e2 100644 --- a/test/plugs/authentication_plug_test.exs +++ b/test/pleroma/web/plugs/authentication_plug_test.exs @@ -2,13 +2,13 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.AuthenticationPlugTest do +defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.AuthenticationPlug - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.PlugHelper alias Pleroma.User + alias Pleroma.Web.Plugs.AuthenticationPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.PlugHelper import ExUnit.CaptureLog import Pleroma.Factory @@ -118,7 +118,7 @@ test "it returns false when hash invalid" do "psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" assert capture_log(fn -> - refute Pleroma.Plugs.AuthenticationPlug.checkpw("password", hash) + refute AuthenticationPlug.checkpw("password", hash) end) =~ "[error] Password hash not recognized" end end diff --git a/test/plugs/basic_auth_decoder_plug_test.exs b/test/pleroma/web/plugs/basic_auth_decoder_plug_test.exs similarity index 89% rename from test/plugs/basic_auth_decoder_plug_test.exs rename to test/pleroma/web/plugs/basic_auth_decoder_plug_test.exs index a6063d4f6..2d6af228c 100644 --- a/test/plugs/basic_auth_decoder_plug_test.exs +++ b/test/pleroma/web/plugs/basic_auth_decoder_plug_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.BasicAuthDecoderPlugTest do +defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.BasicAuthDecoderPlug + alias Pleroma.Web.Plugs.BasicAuthDecoderPlug defp basic_auth_enc(username, password) do "Basic " <> Base.encode64("#{username}:#{password}") diff --git a/test/plugs/cache_control_test.exs b/test/pleroma/web/plugs/cache_control_test.exs similarity index 92% rename from test/plugs/cache_control_test.exs rename to test/pleroma/web/plugs/cache_control_test.exs index 6b567e81d..fcf3d2be8 100644 --- a/test/plugs/cache_control_test.exs +++ b/test/pleroma/web/plugs/cache_control_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.CacheControlTest do +defmodule Pleroma.Web.Plugs.CacheControlTest do use Pleroma.Web.ConnCase alias Plug.Conn diff --git a/test/plugs/cache_test.exs b/test/pleroma/web/plugs/cache_test.exs similarity index 98% rename from test/plugs/cache_test.exs rename to test/pleroma/web/plugs/cache_test.exs index 8b231c881..93a66f5d3 100644 --- a/test/plugs/cache_test.exs +++ b/test/pleroma/web/plugs/cache_test.exs @@ -2,11 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.CacheTest do +defmodule Pleroma.Web.Plugs.CacheTest do use ExUnit.Case, async: true use Plug.Test - alias Pleroma.Plugs.Cache + alias Pleroma.Web.Plugs.Cache @miss_resp {200, [ diff --git a/test/plugs/ensure_authenticated_plug_test.exs b/test/pleroma/web/plugs/ensure_authenticated_plug_test.exs similarity index 96% rename from test/plugs/ensure_authenticated_plug_test.exs rename to test/pleroma/web/plugs/ensure_authenticated_plug_test.exs index a0667c5e0..92ff19282 100644 --- a/test/plugs/ensure_authenticated_plug_test.exs +++ b/test/pleroma/web/plugs/ensure_authenticated_plug_test.exs @@ -2,11 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do +defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.EnsureAuthenticatedPlug alias Pleroma.User + alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug describe "without :if_func / :unless_func options" do test "it halts if user is NOT assigned", %{conn: conn} do diff --git a/test/plugs/ensure_public_or_authenticated_plug_test.exs b/test/pleroma/web/plugs/ensure_public_or_authenticated_plug_test.exs similarity index 89% rename from test/plugs/ensure_public_or_authenticated_plug_test.exs rename to test/pleroma/web/plugs/ensure_public_or_authenticated_plug_test.exs index fc2934369..211443a55 100644 --- a/test/plugs/ensure_public_or_authenticated_plug_test.exs +++ b/test/pleroma/web/plugs/ensure_public_or_authenticated_plug_test.exs @@ -2,12 +2,12 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlugTest do +defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlugTest do use Pleroma.Web.ConnCase, async: true alias Pleroma.Config - alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.User + alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug setup do: clear_config([:instance, :public]) diff --git a/test/plugs/ensure_user_key_plug_test.exs b/test/pleroma/web/plugs/ensure_user_key_plug_test.exs similarity index 86% rename from test/plugs/ensure_user_key_plug_test.exs rename to test/pleroma/web/plugs/ensure_user_key_plug_test.exs index 633c05447..f912ef755 100644 --- a/test/plugs/ensure_user_key_plug_test.exs +++ b/test/pleroma/web/plugs/ensure_user_key_plug_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.EnsureUserKeyPlugTest do +defmodule Pleroma.Web.Plugs.EnsureUserKeyPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.EnsureUserKeyPlug + alias Pleroma.Web.Plugs.EnsureUserKeyPlug test "if the conn has a user key set, it does nothing", %{conn: conn} do conn = diff --git a/test/web/plugs/federating_plug_test.exs b/test/pleroma/web/plugs/federating_plug_test.exs similarity index 80% rename from test/web/plugs/federating_plug_test.exs rename to test/pleroma/web/plugs/federating_plug_test.exs index 2f8aadadc..a4652f6c5 100644 --- a/test/web/plugs/federating_plug_test.exs +++ b/test/pleroma/web/plugs/federating_plug_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.FederatingPlugTest do +defmodule Pleroma.Web.Plugs.FederatingPlugTest do use Pleroma.Web.ConnCase setup do: clear_config([:instance, :federating]) @@ -12,7 +12,7 @@ test "returns and halt the conn when federating is disabled" do conn = build_conn() - |> Pleroma.Web.FederatingPlug.call(%{}) + |> Pleroma.Web.Plugs.FederatingPlug.call(%{}) assert conn.status == 404 assert conn.halted @@ -23,7 +23,7 @@ test "does nothing when federating is enabled" do conn = build_conn() - |> Pleroma.Web.FederatingPlug.call(%{}) + |> Pleroma.Web.Plugs.FederatingPlug.call(%{}) refute conn.status refute conn.halted diff --git a/test/plugs/frontend_static_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs similarity index 62% rename from test/plugs/frontend_static_test.exs rename to test/pleroma/web/plugs/frontend_static_plug_test.exs index 6f4923048..8b7b022fc 100644 --- a/test/plugs/frontend_static_test.exs +++ b/test/pleroma/web/plugs/frontend_static_plug_test.exs @@ -2,9 +2,9 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.FrontendStaticPlugTest do - alias Pleroma.Plugs.FrontendStatic +defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do use Pleroma.Web.ConnCase + import Mock @dir "test/tmp/instance_static" @@ -21,7 +21,7 @@ test "init will give a static plug config + the frontend type" do at: "/admin", frontend_type: :admin ] - |> FrontendStatic.init() + |> Pleroma.Web.Plugs.FrontendStatic.init() assert opts[:at] == ["admin"] assert opts[:frontend_type] == :admin @@ -54,4 +54,24 @@ test "overrides existing static files for the `pleroma/admin` path", %{conn: con index = get(conn, "/pleroma/admin/") assert html_response(index, 200) == "from frontend plug" end + + test "exclude invalid path", %{conn: conn} do + name = "pleroma-fe" + ref = "dist" + clear_config([:media_proxy, :enabled], true) + clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") + clear_config([:frontends, :primary], %{"name" => name, "ref" => ref}) + path = "#{@dir}/frontends/#{name}/#{ref}" + + File.mkdir_p!("#{path}/proxy/rr/ss") + File.write!("#{path}/proxy/rr/ss/Ek7w8WPVcAApOvN.jpg:large", "FB image") + + url = + Pleroma.Web.MediaProxy.encode_url("https://pbs.twimg.com/media/Ek7w8WPVcAApOvN.jpg:large") + + with_mock Pleroma.ReverseProxy, + call: fn _conn, _url, _opts -> %Plug.Conn{status: :success} end do + assert %Plug.Conn{status: :success} = get(conn, url) + end + end end diff --git a/test/plugs/http_security_plug_test.exs b/test/pleroma/web/plugs/http_security_plug_test.exs similarity index 100% rename from test/plugs/http_security_plug_test.exs rename to test/pleroma/web/plugs/http_security_plug_test.exs diff --git a/test/plugs/http_signature_plug_test.exs b/test/pleroma/web/plugs/http_signature_plug_test.exs similarity index 100% rename from test/plugs/http_signature_plug_test.exs rename to test/pleroma/web/plugs/http_signature_plug_test.exs diff --git a/test/plugs/idempotency_plug_test.exs b/test/pleroma/web/plugs/idempotency_plug_test.exs similarity index 97% rename from test/plugs/idempotency_plug_test.exs rename to test/pleroma/web/plugs/idempotency_plug_test.exs index 21fa0fbcf..4a7835993 100644 --- a/test/plugs/idempotency_plug_test.exs +++ b/test/pleroma/web/plugs/idempotency_plug_test.exs @@ -2,11 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.IdempotencyPlugTest do +defmodule Pleroma.Web.Plugs.IdempotencyPlugTest do use ExUnit.Case, async: true use Plug.Test - alias Pleroma.Plugs.IdempotencyPlug + alias Pleroma.Web.Plugs.IdempotencyPlug alias Plug.Conn test "returns result from cache" do diff --git a/test/plugs/instance_static_test.exs b/test/pleroma/web/plugs/instance_static_test.exs similarity index 97% rename from test/plugs/instance_static_test.exs rename to test/pleroma/web/plugs/instance_static_test.exs index d42ba817e..5b30011d3 100644 --- a/test/plugs/instance_static_test.exs +++ b/test/pleroma/web/plugs/instance_static_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.InstanceStaticPlugTest do +defmodule Pleroma.Web.Plugs.InstanceStaticTest do use Pleroma.Web.ConnCase @dir "test/tmp/instance_static" diff --git a/test/plugs/legacy_authentication_plug_test.exs b/test/pleroma/web/plugs/legacy_authentication_plug_test.exs similarity index 90% rename from test/plugs/legacy_authentication_plug_test.exs rename to test/pleroma/web/plugs/legacy_authentication_plug_test.exs index 3b8c07627..2016a31a8 100644 --- a/test/plugs/legacy_authentication_plug_test.exs +++ b/test/pleroma/web/plugs/legacy_authentication_plug_test.exs @@ -2,15 +2,15 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do +defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlugTest do use Pleroma.Web.ConnCase import Pleroma.Factory - alias Pleroma.Plugs.LegacyAuthenticationPlug - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.Plugs.PlugHelper alias Pleroma.User + alias Pleroma.Web.Plugs.LegacyAuthenticationPlug + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Web.Plugs.PlugHelper setup do user = diff --git a/test/plugs/mapped_identity_to_signature_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs similarity index 100% rename from test/plugs/mapped_identity_to_signature_plug_test.exs rename to test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs diff --git a/test/plugs/oauth_plug_test.exs b/test/pleroma/web/plugs/o_auth_plug_test.exs similarity index 92% rename from test/plugs/oauth_plug_test.exs rename to test/pleroma/web/plugs/o_auth_plug_test.exs index f74c068cd..b9d722f76 100644 --- a/test/plugs/oauth_plug_test.exs +++ b/test/pleroma/web/plugs/o_auth_plug_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.OAuthPlugTest do +defmodule Pleroma.Web.Plugs.OAuthPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.OAuthPlug + alias Pleroma.Web.Plugs.OAuthPlug import Pleroma.Factory @session_opts [ @@ -16,7 +16,7 @@ defmodule Pleroma.Plugs.OAuthPlugTest do setup %{conn: conn} do user = insert(:user) - {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create_token(insert(:oauth_app), user) + {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create(insert(:oauth_app), user) %{user: user, token: token, conn: conn} end diff --git a/test/plugs/oauth_scopes_plug_test.exs b/test/pleroma/web/plugs/o_auth_scopes_plug_test.exs similarity index 98% rename from test/plugs/oauth_scopes_plug_test.exs rename to test/pleroma/web/plugs/o_auth_scopes_plug_test.exs index 334316043..982a70bf9 100644 --- a/test/plugs/oauth_scopes_plug_test.exs +++ b/test/pleroma/web/plugs/o_auth_scopes_plug_test.exs @@ -2,11 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.OAuthScopesPlugTest do +defmodule Pleroma.Web.Plugs.OAuthScopesPlugTest do use Pleroma.Web.ConnCase - alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Repo + alias Pleroma.Web.Plugs.OAuthScopesPlug import Mock import Pleroma.Factory diff --git a/test/web/plugs/plug_test.exs b/test/pleroma/web/plugs/plug_helper_test.exs similarity index 94% rename from test/web/plugs/plug_test.exs rename to test/pleroma/web/plugs/plug_helper_test.exs index 943e484e7..670d699f0 100644 --- a/test/web/plugs/plug_test.exs +++ b/test/pleroma/web/plugs/plug_helper_test.exs @@ -2,12 +2,12 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.PlugTest do +defmodule Pleroma.Web.Plugs.PlugHelperTest do @moduledoc "Tests for the functionality added via `use Pleroma.Web, :plug`" - alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug - alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug - alias Pleroma.Plugs.PlugHelper + alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug + alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug + alias Pleroma.Web.Plugs.PlugHelper import Mock diff --git a/test/plugs/rate_limiter_test.exs b/test/pleroma/web/plugs/rate_limiter_test.exs similarity index 98% rename from test/plugs/rate_limiter_test.exs rename to test/pleroma/web/plugs/rate_limiter_test.exs index 4d3d694f4..249c78b37 100644 --- a/test/plugs/rate_limiter_test.exs +++ b/test/pleroma/web/plugs/rate_limiter_test.exs @@ -2,12 +2,12 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.RateLimiterTest do +defmodule Pleroma.Web.Plugs.RateLimiterTest do use Pleroma.Web.ConnCase alias Phoenix.ConnTest alias Pleroma.Config - alias Pleroma.Plugs.RateLimiter + alias Pleroma.Web.Plugs.RateLimiter alias Plug.Conn import Pleroma.Factory @@ -19,7 +19,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do describe "config" do @limiter_name :test_init - setup do: clear_config([Pleroma.Plugs.RemoteIp, :enabled]) + setup do: clear_config([Pleroma.Web.Plugs.RemoteIp, :enabled]) test "config is required for plug to work" do Config.put([:rate_limit, @limiter_name], {1, 1}) diff --git a/test/plugs/remote_ip_test.exs b/test/pleroma/web/plugs/remote_ip_test.exs similarity index 57% rename from test/plugs/remote_ip_test.exs rename to test/pleroma/web/plugs/remote_ip_test.exs index 752ab32e7..0bdb4c168 100644 --- a/test/plugs/remote_ip_test.exs +++ b/test/pleroma/web/plugs/remote_ip_test.exs @@ -2,14 +2,28 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.RemoteIpTest do - use ExUnit.Case, async: true +defmodule Pleroma.Web.Plugs.RemoteIpTest do + use ExUnit.Case use Plug.Test - alias Pleroma.Plugs.RemoteIp + alias Pleroma.Web.Plugs.RemoteIp - import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2] - setup do: clear_config(RemoteIp) + import Pleroma.Tests.Helpers, only: [clear_config: 2] + + setup do: + clear_config(RemoteIp, + enabled: true, + headers: ["x-forwarded-for"], + proxies: [], + reserved: [ + "127.0.0.0/8", + "::1/128", + "fc00::/7", + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16" + ] + ) test "disabled" do Pleroma.Config.put(RemoteIp, enabled: false) @@ -25,8 +39,6 @@ test "disabled" do end test "enabled" do - Pleroma.Config.put(RemoteIp, enabled: true) - conn = conn(:get, "/") |> put_req_header("x-forwarded-for", "1.1.1.1") @@ -54,8 +66,6 @@ test "custom headers" do end test "custom proxies" do - Pleroma.Config.put(RemoteIp, enabled: true) - conn = conn(:get, "/") |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2") @@ -72,4 +82,27 @@ test "custom proxies" do assert conn.remote_ip == {1, 1, 1, 1} end + + test "proxies set without CIDR format" do + Pleroma.Config.put([RemoteIp, :proxies], ["173.245.48.1"]) + + conn = + conn(:get, "/") + |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1") + |> RemoteIp.call(nil) + + assert conn.remote_ip == {1, 1, 1, 1} + end + + test "proxies set `nonsensical` CIDR" do + Pleroma.Config.put([RemoteIp, :reserved], ["127.0.0.0/8"]) + Pleroma.Config.put([RemoteIp, :proxies], ["10.0.0.3/24"]) + + conn = + conn(:get, "/") + |> put_req_header("x-forwarded-for", "10.0.0.3, 1.1.1.1") + |> RemoteIp.call(nil) + + assert conn.remote_ip == {1, 1, 1, 1} + end end diff --git a/test/plugs/session_authentication_plug_test.exs b/test/pleroma/web/plugs/session_authentication_plug_test.exs similarity index 92% rename from test/plugs/session_authentication_plug_test.exs rename to test/pleroma/web/plugs/session_authentication_plug_test.exs index 0949ecfed..2b4d5bc0c 100644 --- a/test/plugs/session_authentication_plug_test.exs +++ b/test/pleroma/web/plugs/session_authentication_plug_test.exs @@ -2,11 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SessionAuthenticationPlugTest do +defmodule Pleroma.Web.Plugs.SessionAuthenticationPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.SessionAuthenticationPlug alias Pleroma.User + alias Pleroma.Web.Plugs.SessionAuthenticationPlug setup %{conn: conn} do session_opts = [ diff --git a/test/plugs/set_format_plug_test.exs b/test/pleroma/web/plugs/set_format_plug_test.exs similarity index 89% rename from test/plugs/set_format_plug_test.exs rename to test/pleroma/web/plugs/set_format_plug_test.exs index 7a1dfe9bf..e95d751fa 100644 --- a/test/plugs/set_format_plug_test.exs +++ b/test/pleroma/web/plugs/set_format_plug_test.exs @@ -2,11 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SetFormatPlugTest do +defmodule Pleroma.Web.Plugs.SetFormatPlugTest do use ExUnit.Case, async: true use Plug.Test - alias Pleroma.Plugs.SetFormatPlug + alias Pleroma.Web.Plugs.SetFormatPlug test "set format from params" do conn = diff --git a/test/plugs/set_locale_plug_test.exs b/test/pleroma/web/plugs/set_locale_plug_test.exs similarity index 92% rename from test/plugs/set_locale_plug_test.exs rename to test/pleroma/web/plugs/set_locale_plug_test.exs index 7114b1557..773f48a5b 100644 --- a/test/plugs/set_locale_plug_test.exs +++ b/test/pleroma/web/plugs/set_locale_plug_test.exs @@ -2,11 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SetLocalePlugTest do +defmodule Pleroma.Web.Plugs.SetLocalePlugTest do use ExUnit.Case, async: true use Plug.Test - alias Pleroma.Plugs.SetLocalePlug + alias Pleroma.Web.Plugs.SetLocalePlug alias Plug.Conn test "default locale is `en`" do diff --git a/test/plugs/set_user_session_id_plug_test.exs b/test/pleroma/web/plugs/set_user_session_id_plug_test.exs similarity index 90% rename from test/plugs/set_user_session_id_plug_test.exs rename to test/pleroma/web/plugs/set_user_session_id_plug_test.exs index 7f1a1e98b..a89b5628f 100644 --- a/test/plugs/set_user_session_id_plug_test.exs +++ b/test/pleroma/web/plugs/set_user_session_id_plug_test.exs @@ -2,11 +2,11 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.SetUserSessionIdPlugTest do +defmodule Pleroma.Web.Plugs.SetUserSessionIdPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.SetUserSessionIdPlug alias Pleroma.User + alias Pleroma.Web.Plugs.SetUserSessionIdPlug setup %{conn: conn} do session_opts = [ diff --git a/test/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs similarity index 93% rename from test/plugs/uploaded_media_plug_test.exs rename to test/pleroma/web/plugs/uploaded_media_plug_test.exs index 20b13dfac..7c8313121 100644 --- a/test/plugs/uploaded_media_plug_test.exs +++ b/test/pleroma/web/plugs/uploaded_media_plug_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.UploadedMediaPlugTest do +defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do use Pleroma.Web.ConnCase alias Pleroma.Upload @@ -11,7 +11,7 @@ defp upload_file(context) do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image_tmp.jpg"), filename: "nice_tf.jpg" } diff --git a/test/plugs/user_enabled_plug_test.exs b/test/pleroma/web/plugs/user_enabled_plug_test.exs similarity index 93% rename from test/plugs/user_enabled_plug_test.exs rename to test/pleroma/web/plugs/user_enabled_plug_test.exs index b219d8abf..71c56f03a 100644 --- a/test/plugs/user_enabled_plug_test.exs +++ b/test/pleroma/web/plugs/user_enabled_plug_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserEnabledPlugTest do +defmodule Pleroma.Web.Plugs.UserEnabledPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.UserEnabledPlug + alias Pleroma.Web.Plugs.UserEnabledPlug import Pleroma.Factory setup do: clear_config([:instance, :account_activation_required]) diff --git a/test/plugs/user_fetcher_plug_test.exs b/test/pleroma/web/plugs/user_fetcher_plug_test.exs similarity index 89% rename from test/plugs/user_fetcher_plug_test.exs rename to test/pleroma/web/plugs/user_fetcher_plug_test.exs index 0496f14dd..b4f875d2d 100644 --- a/test/plugs/user_fetcher_plug_test.exs +++ b/test/pleroma/web/plugs/user_fetcher_plug_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserFetcherPlugTest do +defmodule Pleroma.Web.Plugs.UserFetcherPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.UserFetcherPlug + alias Pleroma.Web.Plugs.UserFetcherPlug import Pleroma.Factory setup do diff --git a/test/plugs/user_is_admin_plug_test.exs b/test/pleroma/web/plugs/user_is_admin_plug_test.exs similarity index 89% rename from test/plugs/user_is_admin_plug_test.exs rename to test/pleroma/web/plugs/user_is_admin_plug_test.exs index 8bc00e444..b550568c1 100644 --- a/test/plugs/user_is_admin_plug_test.exs +++ b/test/pleroma/web/plugs/user_is_admin_plug_test.exs @@ -2,10 +2,10 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Plugs.UserIsAdminPlugTest do +defmodule Pleroma.Web.Plugs.UserIsAdminPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.Plugs.UserIsAdminPlug + alias Pleroma.Web.Plugs.UserIsAdminPlug import Pleroma.Factory test "accepts a user that is an admin" do diff --git a/test/web/preload/instance_test.exs b/test/pleroma/web/preload/providers/instance_test.exs similarity index 85% rename from test/web/preload/instance_test.exs rename to test/pleroma/web/preload/providers/instance_test.exs index a46f28312..8493f2a94 100644 --- a/test/web/preload/instance_test.exs +++ b/test/pleroma/web/preload/providers/instance_test.exs @@ -45,4 +45,12 @@ test "it renders the node_info", %{"/nodeinfo/2.0.json" => nodeinfo} do assert metadata.private == false assert metadata.suggestions == %{enabled: false} end + + test "it renders the frontend configurations", %{ + "/api/pleroma/frontend_configurations" => fe_configs + } do + assert %{ + pleroma_fe: %{background: "/images/city.jpg", logo: "/static/logo.png"} + } = fe_configs + end end diff --git a/test/web/preload/timeline_test.exs b/test/pleroma/web/preload/providers/timeline_test.exs similarity index 100% rename from test/web/preload/timeline_test.exs rename to test/pleroma/web/preload/providers/timeline_test.exs diff --git a/test/web/preload/user_test.exs b/test/pleroma/web/preload/providers/user_test.exs similarity index 100% rename from test/web/preload/user_test.exs rename to test/pleroma/web/preload/providers/user_test.exs diff --git a/test/web/push/impl_test.exs b/test/pleroma/web/push/impl_test.exs similarity index 99% rename from test/web/push/impl_test.exs rename to test/pleroma/web/push/impl_test.exs index aeb5c1fbd..7d8cc999a 100644 --- a/test/web/push/impl_test.exs +++ b/test/pleroma/web/push/impl_test.exs @@ -5,6 +5,8 @@ defmodule Pleroma.Web.Push.ImplTest do use Pleroma.DataCase + import Pleroma.Factory + alias Pleroma.Notification alias Pleroma.Object alias Pleroma.User @@ -13,8 +15,6 @@ defmodule Pleroma.Web.Push.ImplTest do alias Pleroma.Web.Push.Impl alias Pleroma.Web.Push.Subscription - import Pleroma.Factory - setup do Tesla.Mock.mock(fn %{method: :post, url: "https://example.com/example/1234"} -> @@ -219,7 +219,7 @@ test "builds content for chat messages with no content" do recipient = insert(:user) file = %Plug.Upload{ - content_type: "image/jpg", + content_type: "image/jpeg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } diff --git a/test/web/rel_me_test.exs b/test/pleroma/web/rel_me_test.exs similarity index 100% rename from test/web/rel_me_test.exs rename to test/pleroma/web/rel_me_test.exs diff --git a/test/web/rich_media/helpers_test.exs b/test/pleroma/web/rich_media/helpers_test.exs similarity index 74% rename from test/web/rich_media/helpers_test.exs rename to test/pleroma/web/rich_media/helpers_test.exs index 8264a9c41..4c9ee77d0 100644 --- a/test/web/rich_media/helpers_test.exs +++ b/test/pleroma/web/rich_media/helpers_test.exs @@ -6,7 +6,6 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do use Pleroma.DataCase alias Pleroma.Config - alias Pleroma.Object alias Pleroma.Web.CommonAPI alias Pleroma.Web.RichMedia.Helpers @@ -64,41 +63,6 @@ test "crawls valid, complete URLs" do Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end - test "refuses to crawl URLs from posts marked sensitive" do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - status: "http://example.com/ogp", - sensitive: true - }) - - %Object{} = object = Object.normalize(activity) - - assert object.data["sensitive"] - - Config.put([:rich_media, :enabled], true) - - assert %{} = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - end - - test "refuses to crawl URLs from posts tagged NSFW" do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - status: "http://example.com/ogp #nsfw" - }) - - %Object{} = object = Object.normalize(activity) - - assert object.data["sensitive"] - - Config.put([:rich_media, :enabled], true) - - assert %{} = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - end - test "refuses to crawl URLs of private network from posts" do user = insert(:user) diff --git a/test/web/rich_media/aws_signed_url_test.exs b/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs similarity index 97% rename from test/web/rich_media/aws_signed_url_test.exs rename to test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs index 1ceae1a31..2f17bebd7 100644 --- a/test/web/rich_media/aws_signed_url_test.exs +++ b/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.RichMedia.TTL.AwsSignedUrlTest do +defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrlTest do use ExUnit.Case, async: true test "s3 signed url is parsed correct for expiration time" do diff --git a/test/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs similarity index 97% rename from test/web/rich_media/parser_test.exs rename to test/pleroma/web/rich_media/parser_test.exs index b8ef2cccf..6d00c2af5 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/pleroma/web/rich_media/parser_test.exs @@ -87,9 +87,7 @@ test "returns error when no metadata present" do end test "doesn't just add a title" do - assert Parser.parse("http://example.com/non-ogp") == - {:error, - "Found metadata was invalid or incomplete: %{\"url\" => \"http://example.com/non-ogp\"}"} + assert {:error, {:invalid_metadata, _}} = Parser.parse("http://example.com/non-ogp") end test "parses ogp" do diff --git a/test/web/rich_media/parsers/twitter_card_test.exs b/test/pleroma/web/rich_media/parsers/twitter_card_test.exs similarity index 100% rename from test/web/rich_media/parsers/twitter_card_test.exs rename to test/pleroma/web/rich_media/parsers/twitter_card_test.exs diff --git a/test/web/static_fe/static_fe_controller_test.exs b/test/pleroma/web/static_fe/static_fe_controller_test.exs similarity index 79% rename from test/web/static_fe/static_fe_controller_test.exs rename to test/pleroma/web/static_fe/static_fe_controller_test.exs index 1598bf675..19506f1d8 100644 --- a/test/web/static_fe/static_fe_controller_test.exs +++ b/test/pleroma/web/static_fe/static_fe_controller_test.exs @@ -1,15 +1,17 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Activity - alias Pleroma.Config alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.CommonAPI import Pleroma.Factory setup_all do: clear_config([:static_fe, :enabled], true) - setup do: clear_config([:instance, :federating], true) setup %{conn: conn} do conn = put_req_header(conn, "accept", "text/html") @@ -70,8 +72,27 @@ test "pagination, page 2", %{conn: conn, user: user} do refute html =~ ">test29<" end - test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do - ensure_federating_or_authenticated(conn, "/users/#{user.nickname}", user) + test "does not require authentication on non-federating instances", %{ + conn: conn, + user: user + } do + clear_config([:instance, :federating], false) + + conn = get(conn, "/users/#{user.nickname}") + + assert html_response(conn, 200) =~ user.nickname + end + + test "returns 404 for local user with `restrict_unauthenticated/profiles/local` setting", %{ + conn: conn + } do + clear_config([:restrict_unauthenticated, :profiles, :local], true) + + local_user = insert(:user, local: true) + + conn + |> get("/users/#{local_user.nickname}") + |> html_response(404) end end @@ -183,10 +204,28 @@ test "302 for remote cached status", %{conn: conn, user: user} do assert html_response(conn, 302) =~ "redirected" end - test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do + test "does not require authentication on non-federating instances", %{ + conn: conn, + user: user + } do + clear_config([:instance, :federating], false) + {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"}) - ensure_federating_or_authenticated(conn, "/notice/#{activity.id}", user) + conn = get(conn, "/notice/#{activity.id}") + + assert html_response(conn, 200) =~ "testing a thing!" + end + + test "returns 404 for local public activity with `restrict_unauthenticated/activities/local` setting", + %{conn: conn, user: user} do + clear_config([:restrict_unauthenticated, :activities, :local], true) + + {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"}) + + conn + |> get("/notice/#{activity.id}") + |> html_response(404) end end end diff --git a/test/web/streamer/streamer_test.exs b/test/pleroma/web/streamer_test.exs similarity index 64% rename from test/web/streamer/streamer_test.exs rename to test/pleroma/web/streamer_test.exs index d56d74464..185724a9f 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -21,92 +21,148 @@ defmodule Pleroma.Web.StreamerTest do setup do: clear_config([:instance, :skip_thread_containment]) - describe "get_topic without an user" do + describe "get_topic/_ (unauthenticated)" do test "allows public" do - assert {:ok, "public"} = Streamer.get_topic("public", nil) - assert {:ok, "public:local"} = Streamer.get_topic("public:local", nil) - assert {:ok, "public:media"} = Streamer.get_topic("public:media", nil) - assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil) + assert {:ok, "public"} = Streamer.get_topic("public", nil, nil) + assert {:ok, "public:local"} = Streamer.get_topic("public:local", nil, nil) + assert {:ok, "public:media"} = Streamer.get_topic("public:media", nil, nil) + assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil) end test "allows hashtag streams" do - assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", nil, %{"tag" => "cofe"}) + assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", nil, nil, %{"tag" => "cofe"}) end test "disallows user streams" do - assert {:error, _} = Streamer.get_topic("user", nil) - assert {:error, _} = Streamer.get_topic("user:notification", nil) - assert {:error, _} = Streamer.get_topic("direct", nil) + assert {:error, _} = Streamer.get_topic("user", nil, nil) + assert {:error, _} = Streamer.get_topic("user:notification", nil, nil) + assert {:error, _} = Streamer.get_topic("direct", nil, nil) end test "disallows list streams" do - assert {:error, _} = Streamer.get_topic("list", nil, %{"list" => 42}) + assert {:error, _} = Streamer.get_topic("list", nil, nil, %{"list" => 42}) end end - describe "get_topic with an user" do - setup do - user = insert(:user) - {:ok, %{user: user}} + describe "get_topic/_ (authenticated)" do + setup do: oauth_access(["read"]) + + test "allows public streams (regardless of OAuth token scopes)", %{ + user: user, + token: read_oauth_token + } do + with oauth_token <- [nil, read_oauth_token] do + assert {:ok, "public"} = Streamer.get_topic("public", user, oauth_token) + assert {:ok, "public:local"} = Streamer.get_topic("public:local", user, oauth_token) + assert {:ok, "public:media"} = Streamer.get_topic("public:media", user, oauth_token) + + assert {:ok, "public:local:media"} = + Streamer.get_topic("public:local:media", user, oauth_token) + end end - test "allows public streams", %{user: user} do - assert {:ok, "public"} = Streamer.get_topic("public", user) - assert {:ok, "public:local"} = Streamer.get_topic("public:local", user) - assert {:ok, "public:media"} = Streamer.get_topic("public:media", user) - assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", user) - end + test "allows user streams (with proper OAuth token scopes)", %{ + user: user, + token: read_oauth_token + } do + %{token: read_notifications_token} = oauth_access(["read:notifications"], user: user) + %{token: read_statuses_token} = oauth_access(["read:statuses"], user: user) + %{token: badly_scoped_token} = oauth_access(["irrelevant:scope"], user: user) - test "allows user streams", %{user: user} do expected_user_topic = "user:#{user.id}" - expected_notif_topic = "user:notification:#{user.id}" + expected_notification_topic = "user:notification:#{user.id}" expected_direct_topic = "direct:#{user.id}" - assert {:ok, ^expected_user_topic} = Streamer.get_topic("user", user) - assert {:ok, ^expected_notif_topic} = Streamer.get_topic("user:notification", user) - assert {:ok, ^expected_direct_topic} = Streamer.get_topic("direct", user) + expected_pleroma_chat_topic = "user:pleroma_chat:#{user.id}" + + for valid_user_token <- [read_oauth_token, read_statuses_token] do + assert {:ok, ^expected_user_topic} = Streamer.get_topic("user", user, valid_user_token) + + assert {:ok, ^expected_direct_topic} = + Streamer.get_topic("direct", user, valid_user_token) + + assert {:ok, ^expected_pleroma_chat_topic} = + Streamer.get_topic("user:pleroma_chat", user, valid_user_token) + end + + for invalid_user_token <- [read_notifications_token, badly_scoped_token], + user_topic <- ["user", "direct", "user:pleroma_chat"] do + assert {:error, :unauthorized} = Streamer.get_topic(user_topic, user, invalid_user_token) + end + + for valid_notification_token <- [read_oauth_token, read_notifications_token] do + assert {:ok, ^expected_notification_topic} = + Streamer.get_topic("user:notification", user, valid_notification_token) + end + + for invalid_notification_token <- [read_statuses_token, badly_scoped_token] do + assert {:error, :unauthorized} = + Streamer.get_topic("user:notification", user, invalid_notification_token) + end end - test "allows hashtag streams", %{user: user} do - assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", user, %{"tag" => "cofe"}) + test "allows hashtag streams (regardless of OAuth token scopes)", %{ + user: user, + token: read_oauth_token + } do + for oauth_token <- [nil, read_oauth_token] do + assert {:ok, "hashtag:cofe"} = + Streamer.get_topic("hashtag", user, oauth_token, %{"tag" => "cofe"}) + end end - test "disallows registering to an user stream", %{user: user} do + test "disallows registering to another user's stream", %{user: user, token: read_oauth_token} do another_user = insert(:user) - assert {:error, _} = Streamer.get_topic("user:#{another_user.id}", user) - assert {:error, _} = Streamer.get_topic("user:notification:#{another_user.id}", user) - assert {:error, _} = Streamer.get_topic("direct:#{another_user.id}", user) + assert {:error, _} = Streamer.get_topic("user:#{another_user.id}", user, read_oauth_token) + + assert {:error, _} = + Streamer.get_topic("user:notification:#{another_user.id}", user, read_oauth_token) + + assert {:error, _} = Streamer.get_topic("direct:#{another_user.id}", user, read_oauth_token) end - test "allows list stream that are owned by the user", %{user: user} do + test "allows list stream that are owned by the user (with `read` or `read:lists` scopes)", %{ + user: user, + token: read_oauth_token + } do + %{token: read_lists_token} = oauth_access(["read:lists"], user: user) + %{token: invalid_token} = oauth_access(["irrelevant:scope"], user: user) {:ok, list} = List.create("Test", user) - assert {:error, _} = Streamer.get_topic("list:#{list.id}", user) - assert {:ok, _} = Streamer.get_topic("list", user, %{"list" => list.id}) + + assert {:error, _} = Streamer.get_topic("list:#{list.id}", user, read_oauth_token) + + for valid_token <- [read_oauth_token, read_lists_token] do + assert {:ok, _} = Streamer.get_topic("list", user, valid_token, %{"list" => list.id}) + end + + assert {:error, _} = Streamer.get_topic("list", user, invalid_token, %{"list" => list.id}) end - test "disallows list stream that are not owned by the user", %{user: user} do + test "disallows list stream that are not owned by the user", %{user: user, token: oauth_token} do another_user = insert(:user) {:ok, list} = List.create("Test", another_user) - assert {:error, _} = Streamer.get_topic("list:#{list.id}", user) - assert {:error, _} = Streamer.get_topic("list", user, %{"list" => list.id}) + + assert {:error, _} = Streamer.get_topic("list:#{list.id}", user, oauth_token) + assert {:error, _} = Streamer.get_topic("list", user, oauth_token, %{"list" => list.id}) end end describe "user streams" do setup do - user = insert(:user) + %{user: user, token: token} = oauth_access(["read"]) notify = insert(:notification, user: user, activity: build(:note_activity)) - {:ok, %{user: user, notify: notify}} + {:ok, %{user: user, notify: notify, token: token}} end - test "it streams the user's post in the 'user' stream", %{user: user} do - Streamer.get_topic_and_add_socket("user", user) + test "it streams the user's post in the 'user' stream", %{user: user, token: oauth_token} do + Streamer.get_topic_and_add_socket("user", user, oauth_token) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) + assert_receive {:render_with_user, _, _, ^activity} refute Streamer.filtered_by_user?(user, activity) end - test "it streams boosts of the user in the 'user' stream", %{user: user} do - Streamer.get_topic_and_add_socket("user", user) + test "it streams boosts of the user in the 'user' stream", %{user: user, token: oauth_token} do + Streamer.get_topic_and_add_socket("user", user, oauth_token) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) @@ -117,9 +173,10 @@ test "it streams boosts of the user in the 'user' stream", %{user: user} do end test "it does not stream announces of the user's own posts in the 'user' stream", %{ - user: user + user: user, + token: oauth_token } do - Streamer.get_topic_and_add_socket("user", user) + Streamer.get_topic_and_add_socket("user", user, oauth_token) other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) @@ -129,9 +186,10 @@ test "it does not stream announces of the user's own posts in the 'user' stream" end test "it does stream notifications announces of the user's own posts in the 'user' stream", %{ - user: user + user: user, + token: oauth_token } do - Streamer.get_topic_and_add_socket("user", user) + Streamer.get_topic_and_add_socket("user", user, oauth_token) other_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) @@ -145,8 +203,11 @@ test "it does stream notifications announces of the user's own posts in the 'use refute Streamer.filtered_by_user?(user, notification) end - test "it streams boosts of mastodon user in the 'user' stream", %{user: user} do - Streamer.get_topic_and_add_socket("user", user) + test "it streams boosts of mastodon user in the 'user' stream", %{ + user: user, + token: oauth_token + } do + Streamer.get_topic_and_add_socket("user", user, oauth_token) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) @@ -164,21 +225,34 @@ test "it streams boosts of mastodon user in the 'user' stream", %{user: user} do refute Streamer.filtered_by_user?(user, announce) end - test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do - Streamer.get_topic_and_add_socket("user", user) + test "it sends notify to in the 'user' stream", %{ + user: user, + token: oauth_token, + notify: notify + } do + Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.stream("user", notify) + assert_receive {:render_with_user, _, _, ^notify} refute Streamer.filtered_by_user?(user, notify) end - test "it sends notify to in the 'user:notification' stream", %{user: user, notify: notify} do - Streamer.get_topic_and_add_socket("user:notification", user) + test "it sends notify to in the 'user:notification' stream", %{ + user: user, + token: oauth_token, + notify: notify + } do + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.stream("user:notification", notify) + assert_receive {:render_with_user, _, _, ^notify} refute Streamer.filtered_by_user?(user, notify) end - test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} do + test "it sends chat messages to the 'user:pleroma_chat' stream", %{ + user: user, + token: oauth_token + } do other_user = insert(:user) {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno") @@ -187,7 +261,7 @@ test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} d cm_ref = MessageReference.for_chat_and_object(chat, object) cm_ref = %{cm_ref | chat: chat, object: object} - Streamer.get_topic_and_add_socket("user:pleroma_chat", user) + Streamer.get_topic_and_add_socket("user:pleroma_chat", user, oauth_token) Streamer.stream("user:pleroma_chat", {user, cm_ref}) text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) @@ -196,7 +270,7 @@ test "it sends chat messages to the 'user:pleroma_chat' stream", %{user: user} d assert_receive {:text, ^text} end - test "it sends chat messages to the 'user' stream", %{user: user} do + test "it sends chat messages to the 'user' stream", %{user: user, token: oauth_token} do other_user = insert(:user) {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey cirno") @@ -205,7 +279,7 @@ test "it sends chat messages to the 'user' stream", %{user: user} do cm_ref = MessageReference.for_chat_and_object(chat, object) cm_ref = %{cm_ref | chat: chat, object: object} - Streamer.get_topic_and_add_socket("user", user) + Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.stream("user", {user, cm_ref}) text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) @@ -214,7 +288,10 @@ test "it sends chat messages to the 'user' stream", %{user: user} do assert_receive {:text, ^text} end - test "it sends chat message notifications to the 'user:notification' stream", %{user: user} do + test "it sends chat message notifications to the 'user:notification' stream", %{ + user: user, + token: oauth_token + } do other_user = insert(:user) {:ok, create_activity} = CommonAPI.post_chat_message(other_user, user, "hey") @@ -223,19 +300,21 @@ test "it sends chat message notifications to the 'user:notification' stream", %{ Repo.get_by(Pleroma.Notification, user_id: user.id, activity_id: create_activity.id) |> Repo.preload(:activity) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.stream("user:notification", notify) + assert_receive {:render_with_user, _, _, ^notify} refute Streamer.filtered_by_user?(user, notify) end test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{ - user: user + user: user, + token: oauth_token } do blocked = insert(:user) {:ok, _user_relationship} = User.block(user, blocked) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, activity} = CommonAPI.post(user, %{status: ":("}) {:ok, _} = CommonAPI.favorite(blocked, activity.id) @@ -244,14 +323,15 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl end test "it doesn't send notify to the 'user:notification' stream when a thread is muted", %{ - user: user + user: user, + token: oauth_token } do user2 = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) {:ok, _} = CommonAPI.add_mute(user, activity) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) @@ -260,12 +340,13 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is end test "it sends favorite to 'user:notification' stream'", %{ - user: user + user: user, + token: oauth_token } do user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) assert_receive {:render_with_user, _, "notification.json", notif} @@ -274,13 +355,14 @@ test "it sends favorite to 'user:notification' stream'", %{ end test "it doesn't send the 'user:notification' stream' when a domain is blocked", %{ - user: user + user: user, + token: oauth_token } do user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) refute_receive _ @@ -288,7 +370,8 @@ test "it doesn't send the 'user:notification' stream' when a domain is blocked", end test "it sends follow activities to the 'user:notification' stream", %{ - user: user + user: user, + token: oauth_token } do user_url = user.ap_id user2 = insert(:user) @@ -303,7 +386,7 @@ test "it sends follow activities to the 'user:notification' stream", %{ %Tesla.Env{status: 200, body: body} end) - Streamer.get_topic_and_add_socket("user:notification", user) + Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user) assert_receive {:render_with_user, _, "notification.json", notif} @@ -312,51 +395,53 @@ test "it sends follow activities to the 'user:notification' stream", %{ end end - test "it sends to public authenticated" do - user = insert(:user) - other_user = insert(:user) + describe "public streams" do + test "it sends to public (authenticated)" do + %{user: user, token: oauth_token} = oauth_access(["read"]) + other_user = insert(:user) - Streamer.get_topic_and_add_socket("public", other_user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) - {:ok, activity} = CommonAPI.post(user, %{status: "Test"}) - assert_receive {:render_with_user, _, _, ^activity} - refute Streamer.filtered_by_user?(user, activity) + {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) + assert_receive {:render_with_user, _, _, ^activity} + refute Streamer.filtered_by_user?(other_user, activity) + end + + test "it sends to public (unauthenticated)" do + user = insert(:user) + + Streamer.get_topic_and_add_socket("public", nil, nil) + + {:ok, activity} = CommonAPI.post(user, %{status: "Test"}) + activity_id = activity.id + assert_receive {:text, event} + assert %{"event" => "update", "payload" => payload} = Jason.decode!(event) + assert %{"id" => ^activity_id} = Jason.decode!(payload) + + {:ok, _} = CommonAPI.delete(activity.id, user) + assert_receive {:text, event} + assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) + end + + test "handles deletions" do + %{user: user, token: oauth_token} = oauth_access(["read"]) + other_user = insert(:user) + {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) + + Streamer.get_topic_and_add_socket("public", user, oauth_token) + + {:ok, _} = CommonAPI.delete(activity.id, other_user) + activity_id = activity.id + assert_receive {:text, event} + assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) + end end - test "works for deletions" do - user = insert(:user) - other_user = insert(:user) - {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) - - Streamer.get_topic_and_add_socket("public", user) - - {:ok, _} = CommonAPI.delete(activity.id, other_user) - activity_id = activity.id - assert_receive {:text, event} - assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) - end - - test "it sends to public unauthenticated" do - user = insert(:user) - - Streamer.get_topic_and_add_socket("public", nil) - - {:ok, activity} = CommonAPI.post(user, %{status: "Test"}) - activity_id = activity.id - assert_receive {:text, event} - assert %{"event" => "update", "payload" => payload} = Jason.decode!(event) - assert %{"id" => ^activity_id} = Jason.decode!(payload) - - {:ok, _} = CommonAPI.delete(activity.id, user) - assert_receive {:text, event} - assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) - end - - describe "thread_containment" do + describe "thread_containment/2" do test "it filters to user if recipients invalid and thread containment is enabled" do Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) - user = insert(:user) + %{user: user, token: oauth_token} = oauth_access(["read"]) User.follow(user, author, :follow_accept) activity = @@ -368,7 +453,7 @@ test "it filters to user if recipients invalid and thread containment is enabled ) ) - Streamer.get_topic_and_add_socket("public", user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) assert_receive {:render_with_user, _, _, ^activity} assert Streamer.filtered_by_user?(user, activity) @@ -377,7 +462,7 @@ test "it filters to user if recipients invalid and thread containment is enabled test "it sends message if recipients invalid and thread containment is disabled" do Pleroma.Config.put([:instance, :skip_thread_containment], true) author = insert(:user) - user = insert(:user) + %{user: user, token: oauth_token} = oauth_access(["read"]) User.follow(user, author, :follow_accept) activity = @@ -389,7 +474,7 @@ test "it sends message if recipients invalid and thread containment is disabled" ) ) - Streamer.get_topic_and_add_socket("public", user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) assert_receive {:render_with_user, _, _, ^activity} @@ -400,6 +485,7 @@ test "it sends message if recipients invalid and thread containment is enabled b Pleroma.Config.put([:instance, :skip_thread_containment], false) author = insert(:user) user = insert(:user, skip_thread_containment: true) + %{token: oauth_token} = oauth_access(["read"], user: user) User.follow(user, author, :follow_accept) activity = @@ -411,7 +497,7 @@ test "it sends message if recipients invalid and thread containment is enabled b ) ) - Streamer.get_topic_and_add_socket("public", user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) assert_receive {:render_with_user, _, _, ^activity} @@ -420,23 +506,26 @@ test "it sends message if recipients invalid and thread containment is enabled b end describe "blocks" do - test "it filters messages involving blocked users" do - user = insert(:user) + setup do: oauth_access(["read"]) + + test "it filters messages involving blocked users", %{user: user, token: oauth_token} do blocked_user = insert(:user) {:ok, _user_relationship} = User.block(user, blocked_user) - Streamer.get_topic_and_add_socket("public", user) + Streamer.get_topic_and_add_socket("public", user, oauth_token) {:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"}) assert_receive {:render_with_user, _, _, ^activity} assert Streamer.filtered_by_user?(user, activity) end - test "it filters messages transitively involving blocked users" do - blocker = insert(:user) + test "it filters messages transitively involving blocked users", %{ + user: blocker, + token: blocker_token + } do blockee = insert(:user) friend = insert(:user) - Streamer.get_topic_and_add_socket("public", blocker) + Streamer.get_topic_and_add_socket("public", blocker, blocker_token) {:ok, _user_relationship} = User.block(blocker, blockee) @@ -458,8 +547,9 @@ test "it filters messages transitively involving blocked users" do end describe "lists" do - test "it doesn't send unwanted DMs to list" do - user_a = insert(:user) + setup do: oauth_access(["read"]) + + test "it doesn't send unwanted DMs to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) user_c = insert(:user) @@ -468,7 +558,7 @@ test "it doesn't send unwanted DMs to list" do {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) - Streamer.get_topic_and_add_socket("list", user_a, %{"list" => list.id}) + Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) {:ok, _activity} = CommonAPI.post(user_b, %{ @@ -479,14 +569,13 @@ test "it doesn't send unwanted DMs to list" do refute_receive _ end - test "it doesn't send unwanted private posts to list" do - user_a = insert(:user) + test "it doesn't send unwanted private posts to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) - Streamer.get_topic_and_add_socket("list", user_a, %{"list" => list.id}) + Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) {:ok, _activity} = CommonAPI.post(user_b, %{ @@ -497,8 +586,7 @@ test "it doesn't send unwanted private posts to list" do refute_receive _ end - test "it sends wanted private posts to list" do - user_a = insert(:user) + test "it sends wanted private posts to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) {:ok, user_a} = User.follow(user_a, user_b) @@ -506,7 +594,7 @@ test "it sends wanted private posts to list" do {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) - Streamer.get_topic_and_add_socket("list", user_a, %{"list" => list.id}) + Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) {:ok, activity} = CommonAPI.post(user_b, %{ @@ -520,8 +608,9 @@ test "it sends wanted private posts to list" do end describe "muted reblogs" do - test "it filters muted reblogs" do - user1 = insert(:user) + setup do: oauth_access(["read"]) + + test "it filters muted reblogs", %{user: user1, token: user1_token} do user2 = insert(:user) user3 = insert(:user) CommonAPI.follow(user1, user2) @@ -529,34 +618,38 @@ test "it filters muted reblogs" do {:ok, create_activity} = CommonAPI.post(user3, %{status: "I'm kawen"}) - Streamer.get_topic_and_add_socket("user", user1) + Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2) assert_receive {:render_with_user, _, _, ^announce_activity} assert Streamer.filtered_by_user?(user1, announce_activity) end - test "it filters reblog notification for reblog-muted actors" do - user1 = insert(:user) + test "it filters reblog notification for reblog-muted actors", %{ + user: user1, + token: user1_token + } do user2 = insert(:user) CommonAPI.follow(user1, user2) CommonAPI.hide_reblogs(user1, user2) {:ok, create_activity} = CommonAPI.post(user1, %{status: "I'm kawen"}) - Streamer.get_topic_and_add_socket("user", user1) + Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2) assert_receive {:render_with_user, _, "notification.json", notif} assert Streamer.filtered_by_user?(user1, notif) end - test "it send non-reblog notification for reblog-muted actors" do - user1 = insert(:user) + test "it send non-reblog notification for reblog-muted actors", %{ + user: user1, + token: user1_token + } do user2 = insert(:user) CommonAPI.follow(user1, user2) CommonAPI.hide_reblogs(user1, user2) {:ok, create_activity} = CommonAPI.post(user1, %{status: "I'm kawen"}) - Streamer.get_topic_and_add_socket("user", user1) + Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id) assert_receive {:render_with_user, _, "notification.json", notif} @@ -564,27 +657,28 @@ test "it send non-reblog notification for reblog-muted actors" do end end - test "it filters posts from muted threads" do - user = insert(:user) - user2 = insert(:user) - Streamer.get_topic_and_add_socket("user", user2) - {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) - {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) - {:ok, _} = CommonAPI.add_mute(user2, activity) - assert_receive {:render_with_user, _, _, ^activity} - assert Streamer.filtered_by_user?(user2, activity) + describe "muted threads" do + test "it filters posts from muted threads" do + user = insert(:user) + %{user: user2, token: user2_token} = oauth_access(["read"]) + Streamer.get_topic_and_add_socket("user", user2, user2_token) + + {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) + {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) + {:ok, _} = CommonAPI.add_mute(user2, activity) + + assert_receive {:render_with_user, _, _, ^activity} + assert Streamer.filtered_by_user?(user2, activity) + end end describe "direct streams" do - setup do - :ok - end + setup do: oauth_access(["read"]) - test "it sends conversation update to the 'direct' stream", %{} do - user = insert(:user) + test "it sends conversation update to the 'direct' stream", %{user: user, token: oauth_token} do another_user = insert(:user) - Streamer.get_topic_and_add_socket("direct", user) + Streamer.get_topic_and_add_socket("direct", user, oauth_token) {:ok, _create_activity} = CommonAPI.post(another_user, %{ @@ -602,11 +696,11 @@ test "it sends conversation update to the 'direct' stream", %{} do assert last_status["pleroma"]["direct_conversation_id"] == participation.id end - test "it doesn't send conversation update to the 'direct' stream when the last message in the conversation is deleted" do - user = insert(:user) + test "it doesn't send conversation update to the 'direct' stream when the last message in the conversation is deleted", + %{user: user, token: oauth_token} do another_user = insert(:user) - Streamer.get_topic_and_add_socket("direct", user) + Streamer.get_topic_and_add_socket("direct", user, oauth_token) {:ok, create_activity} = CommonAPI.post(another_user, %{ @@ -629,10 +723,12 @@ test "it doesn't send conversation update to the 'direct' stream when the last m refute_receive _ end - test "it sends conversation update to the 'direct' stream when a message is deleted" do - user = insert(:user) + test "it sends conversation update to the 'direct' stream when a message is deleted", %{ + user: user, + token: oauth_token + } do another_user = insert(:user) - Streamer.get_topic_and_add_socket("direct", user) + Streamer.get_topic_and_add_socket("direct", user, oauth_token) {:ok, create_activity} = CommonAPI.post(another_user, %{ diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/pleroma/web/twitter_api/controller_test.exs similarity index 100% rename from test/web/twitter_api/twitter_api_controller_test.exs rename to test/pleroma/web/twitter_api/controller_test.exs diff --git a/test/web/twitter_api/password_controller_test.exs b/test/pleroma/web/twitter_api/password_controller_test.exs similarity index 93% rename from test/web/twitter_api/password_controller_test.exs rename to test/pleroma/web/twitter_api/password_controller_test.exs index 231a46c67..a5e9e2178 100644 --- a/test/web/twitter_api/password_controller_test.exs +++ b/test/pleroma/web/twitter_api/password_controller_test.exs @@ -37,7 +37,7 @@ test "it shows password reset form", %{conn: conn} do test "it returns HTTP 200", %{conn: conn} do user = insert(:user) {:ok, token} = PasswordResetToken.create_token(user) - {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{}) + {:ok, _access_token} = Token.create(insert(:oauth_app), user, %{}) params = %{ "password" => "test", @@ -62,7 +62,7 @@ test "it sets password_reset_pending to false", %{conn: conn} do user = insert(:user, password_reset_pending: true) {:ok, token} = PasswordResetToken.create_token(user) - {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{}) + {:ok, _access_token} = Token.create(insert(:oauth_app), user, %{}) params = %{ "password" => "test", diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs similarity index 98% rename from test/web/twitter_api/remote_follow_controller_test.exs rename to test/pleroma/web/twitter_api/remote_follow_controller_test.exs index f7e54c26a..3852c7ce9 100644 --- a/test/web/twitter_api/remote_follow_controller_test.exs +++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs @@ -227,7 +227,7 @@ test "follows", %{conn: conn} do } ) - {:ok, %{token: token}} = MFA.Token.create_token(user) + {:ok, %{token: token}} = MFA.Token.create(user) user2 = insert(:user) otp_token = TOTP.generate_token(otp_secret) @@ -256,7 +256,7 @@ test "returns error when auth code is incorrect", %{conn: conn} do } ) - {:ok, %{token: token}} = MFA.Token.create_token(user) + {:ok, %{token: token}} = MFA.Token.create(user) user2 = insert(:user) otp_token = TOTP.generate_token(TOTP.generate_secret()) diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/pleroma/web/twitter_api/twitter_api_test.exs similarity index 100% rename from test/web/twitter_api/twitter_api_test.exs rename to test/pleroma/web/twitter_api/twitter_api_test.exs diff --git a/test/web/twitter_api/util_controller_test.exs b/test/pleroma/web/twitter_api/util_controller_test.exs similarity index 71% rename from test/web/twitter_api/util_controller_test.exs rename to test/pleroma/web/twitter_api/util_controller_test.exs index 354d77b56..60f2fb052 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/pleroma/web/twitter_api/util_controller_test.exs @@ -21,170 +21,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do setup do: clear_config([:instance]) setup do: clear_config([:frontend_configurations, :pleroma_fe]) - describe "POST /api/pleroma/follow_import" do - setup do: oauth_access(["follow"]) - - test "it returns HTTP 200", %{conn: conn} do - user2 = insert(:user) - - response = - conn - |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"}) - |> json_response(:ok) - - assert response == "job started" - end - - test "it imports follow lists from file", %{user: user1, conn: conn} do - user2 = insert(:user) - - with_mocks([ - {File, [], - read!: fn "follow_list.txt" -> - "Account address,Show boosts\n#{user2.ap_id},true" - end} - ]) do - response = - conn - |> post("/api/pleroma/follow_import", %{"list" => %Plug.Upload{path: "follow_list.txt"}}) - |> json_response(:ok) - - assert response == "job started" - - assert ObanHelpers.member?( - %{ - "op" => "follow_import", - "follower_id" => user1.id, - "followed_identifiers" => [user2.ap_id] - }, - all_enqueued(worker: Pleroma.Workers.BackgroundWorker) - ) - end - end - - test "it imports new-style mastodon follow lists", %{conn: conn} do - user2 = insert(:user) - - response = - conn - |> post("/api/pleroma/follow_import", %{ - "list" => "Account address,Show boosts\n#{user2.ap_id},true" - }) - |> json_response(:ok) - - assert response == "job started" - end - - test "requires 'follow' or 'write:follows' permissions" do - token1 = insert(:oauth_token, scopes: ["read", "write"]) - token2 = insert(:oauth_token, scopes: ["follow"]) - token3 = insert(:oauth_token, scopes: ["something"]) - another_user = insert(:user) - - for token <- [token1, token2, token3] do - conn = - build_conn() - |> put_req_header("authorization", "Bearer #{token.token}") - |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"}) - - if token == token3 do - assert %{"error" => "Insufficient permissions: follow | write:follows."} == - json_response(conn, 403) - else - assert json_response(conn, 200) - end - end - end - - test "it imports follows with different nickname variations", %{conn: conn} do - [user2, user3, user4, user5, user6] = insert_list(5, :user) - - identifiers = - [ - user2.ap_id, - user3.nickname, - " ", - "@" <> user4.nickname, - user5.nickname <> "@localhost", - "@" <> user6.nickname <> "@localhost" - ] - |> Enum.join("\n") - - response = - conn - |> post("/api/pleroma/follow_import", %{"list" => identifiers}) - |> json_response(:ok) - - assert response == "job started" - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == [user2, user3, user4, user5, user6] - end - end - - describe "POST /api/pleroma/blocks_import" do - # Note: "follow" or "write:blocks" permission is required - setup do: oauth_access(["write:blocks"]) - - test "it returns HTTP 200", %{conn: conn} do - user2 = insert(:user) - - response = - conn - |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"}) - |> json_response(:ok) - - assert response == "job started" - end - - test "it imports blocks users from file", %{user: user1, conn: conn} do - user2 = insert(:user) - user3 = insert(:user) - - with_mocks([ - {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} - ]) do - response = - conn - |> post("/api/pleroma/blocks_import", %{"list" => %Plug.Upload{path: "blocks_list.txt"}}) - |> json_response(:ok) - - assert response == "job started" - - assert ObanHelpers.member?( - %{ - "op" => "blocks_import", - "blocker_id" => user1.id, - "blocked_identifiers" => [user2.ap_id, user3.ap_id] - }, - all_enqueued(worker: Pleroma.Workers.BackgroundWorker) - ) - end - end - - test "it imports blocks with different nickname variations", %{conn: conn} do - [user2, user3, user4, user5, user6] = insert_list(5, :user) - - identifiers = - [ - user2.ap_id, - user3.nickname, - "@" <> user4.nickname, - user5.nickname <> "@localhost", - "@" <> user6.nickname <> "@localhost" - ] - |> Enum.join(" ") - - response = - conn - |> post("/api/pleroma/blocks_import", %{"list" => identifiers}) - |> json_response(:ok) - - assert response == "job started" - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == [user2, user3, user4, user5, user6] - end - end - describe "PUT /api/pleroma/notification_settings" do setup do: oauth_access(["write:accounts"]) @@ -594,7 +430,7 @@ test "with proper permissions and valid password", %{conn: conn, user: user} do user = User.get_by_id(user.id) assert user.deactivated == true assert user.name == nil - assert user.bio == nil + assert user.bio == "" assert user.password_hash == nil end end diff --git a/test/web/uploader_controller_test.exs b/test/pleroma/web/uploader_controller_test.exs similarity index 100% rename from test/web/uploader_controller_test.exs rename to test/pleroma/web/uploader_controller_test.exs diff --git a/test/web/views/error_view_test.exs b/test/pleroma/web/views/error_view_test.exs similarity index 100% rename from test/web/views/error_view_test.exs rename to test/pleroma/web/views/error_view_test.exs diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs similarity index 100% rename from test/web/web_finger/web_finger_controller_test.exs rename to test/pleroma/web/web_finger/web_finger_controller_test.exs diff --git a/test/web/web_finger/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs similarity index 100% rename from test/web/web_finger/web_finger_test.exs rename to test/pleroma/web/web_finger_test.exs diff --git a/test/workers/cron/digest_emails_worker_test.exs b/test/pleroma/workers/cron/digest_emails_worker_test.exs similarity index 100% rename from test/workers/cron/digest_emails_worker_test.exs rename to test/pleroma/workers/cron/digest_emails_worker_test.exs diff --git a/test/workers/cron/new_users_digest_worker_test.exs b/test/pleroma/workers/cron/new_users_digest_worker_test.exs similarity index 100% rename from test/workers/cron/new_users_digest_worker_test.exs rename to test/pleroma/workers/cron/new_users_digest_worker_test.exs diff --git a/test/pleroma/workers/purge_expired_activity_test.exs b/test/pleroma/workers/purge_expired_activity_test.exs new file mode 100644 index 000000000..b5938776d --- /dev/null +++ b/test/pleroma/workers/purge_expired_activity_test.exs @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredActivityTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + alias Pleroma.Workers.PurgeExpiredActivity + + test "enqueue job" do + activity = insert(:note_activity) + + assert {:ok, _} = + PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: DateTime.add(DateTime.utc_now(), 3601) + }) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredActivity, + args: %{activity_id: activity.id} + ) + + assert {:ok, _} = + perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: activity.id}) + + assert %Oban.Job{} = Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) + end + + test "error if user was not found" do + activity = insert(:note_activity) + + assert {:ok, _} = + PurgeExpiredActivity.enqueue(%{ + activity_id: activity.id, + expires_at: DateTime.add(DateTime.utc_now(), 3601) + }) + + user = Pleroma.User.get_by_ap_id(activity.actor) + Pleroma.Repo.delete(user) + + assert {:error, :user_not_found} = + perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: activity.id}) + end + + test "error if actiivity was not found" do + assert {:ok, _} = + PurgeExpiredActivity.enqueue(%{ + activity_id: "some_id", + expires_at: DateTime.add(DateTime.utc_now(), 3601) + }) + + assert {:error, :activity_not_found} = + perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: "some_if"}) + end +end diff --git a/test/pleroma/workers/purge_expired_token_test.exs b/test/pleroma/workers/purge_expired_token_test.exs new file mode 100644 index 000000000..fb7708c3f --- /dev/null +++ b/test/pleroma/workers/purge_expired_token_test.exs @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.PurgeExpiredTokenTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + setup do: clear_config([:oauth2, :clean_expired_tokens], true) + + test "purges expired oauth token" do + user = insert(:user) + app = insert(:oauth_app) + + {:ok, %{id: id}} = Pleroma.Web.OAuth.Token.create(app, user) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredToken, + args: %{token_id: id, mod: Pleroma.Web.OAuth.Token} + ) + + assert {:ok, %{id: ^id}} = + perform_job(Pleroma.Workers.PurgeExpiredToken, %{ + token_id: id, + mod: Pleroma.Web.OAuth.Token + }) + + assert Repo.aggregate(Pleroma.Web.OAuth.Token, :count, :id) == 0 + end + + test "purges expired mfa token" do + authorization = insert(:oauth_authorization) + + {:ok, %{id: id}} = Pleroma.MFA.Token.create(authorization.user, authorization) + + assert_enqueued( + worker: Pleroma.Workers.PurgeExpiredToken, + args: %{token_id: id, mod: Pleroma.MFA.Token} + ) + + assert {:ok, %{id: ^id}} = + perform_job(Pleroma.Workers.PurgeExpiredToken, %{ + token_id: id, + mod: Pleroma.MFA.Token + }) + + assert Repo.aggregate(Pleroma.MFA.Token, :count, :id) == 0 + end +end diff --git a/test/workers/scheduled_activity_worker_test.exs b/test/pleroma/workers/scheduled_activity_worker_test.exs similarity index 100% rename from test/workers/scheduled_activity_worker_test.exs rename to test/pleroma/workers/scheduled_activity_worker_test.exs diff --git a/test/xml_builder_test.exs b/test/pleroma/xml_builder_test.exs similarity index 100% rename from test/xml_builder_test.exs rename to test/pleroma/xml_builder_test.exs diff --git a/test/support/captcha_mock.ex b/test/support/captcha/mock.ex similarity index 100% rename from test/support/captcha_mock.ex rename to test/support/captcha/mock.ex diff --git a/test/support/channel_case.ex b/test/support/channel_case.ex index d63a0f06b..114184a9f 100644 --- a/test/support/channel_case.ex +++ b/test/support/channel_case.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.ChannelCase do using do quote do # Import conveniences for testing with channels - use Phoenix.ChannelTest + import Phoenix.ChannelTest use Pleroma.Tests.Helpers # The default endpoint for testing diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 7ef681258..47cb65a80 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -22,7 +22,8 @@ defmodule Pleroma.Web.ConnCase do using do quote do # Import conveniences for testing with connections - use Phoenix.ConnTest + import Plug.Conn + import Phoenix.ConnTest use Pleroma.Tests.Helpers import Pleroma.Web.Router.Helpers @@ -111,28 +112,6 @@ defp json_response_and_validate_schema( defp json_response_and_validate_schema(conn, _status) do flunk("Response schema not found for #{conn.method} #{conn.request_path} #{conn.status}") end - - defp ensure_federating_or_authenticated(conn, url, user) do - initial_setting = Config.get([:instance, :federating]) - on_exit(fn -> Config.put([:instance, :federating], initial_setting) end) - - Config.put([:instance, :federating], false) - - conn - |> get(url) - |> response(403) - - conn - |> assign(:user, user) - |> get(url) - |> response(200) - - Config.put([:instance, :federating], true) - - conn - |> get(url) - |> response(200) - end end end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index ba8848952..d5456521c 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -27,6 +27,21 @@ defmodule Pleroma.DataCase do import Ecto.Query import Pleroma.DataCase use Pleroma.Tests.Helpers + + # Sets up OAuth access with specified scopes + defp oauth_access(scopes, opts \\ []) do + user = + Keyword.get_lazy(opts, :user, fn -> + Pleroma.Factory.insert(:user) + end) + + token = + Keyword.get_lazy(opts, :oauth_token, fn -> + Pleroma.Factory.insert(:oauth_token, user: user, scopes: scopes) + end) + + %{user: user, token: token} + end end end diff --git a/test/support/factory.ex b/test/support/factory.ex index 486eda8da..fb82be0c4 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -31,6 +31,7 @@ def user_factory do nickname: sequence(:nickname, &"nick#{&1}"), password_hash: Pbkdf2.hash_pwd_salt("test"), bio: sequence(:bio, &"Tester Number #{&1}"), + discoverable: true, last_digest_emailed_at: NaiveDateTime.utc_now(), last_refreshed_at: NaiveDateTime.utc_now(), notification_settings: %Pleroma.User.NotificationSetting{}, @@ -200,25 +201,6 @@ def note_activity_factory(attrs \\ %{}) do |> Map.merge(attrs) end - defp expiration_offset_by_minutes(attrs, minutes) do - scheduled_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.add(:timer.minutes(minutes), :millisecond) - |> NaiveDateTime.truncate(:second) - - %Pleroma.ActivityExpiration{} - |> Map.merge(attrs) - |> Map.put(:scheduled_at, scheduled_at) - end - - def expiration_in_the_past_factory(attrs \\ %{}) do - expiration_offset_by_minutes(attrs, -60) - end - - def expiration_in_the_future_factory(attrs \\ %{}) do - expiration_offset_by_minutes(attrs, 61) - end - def article_activity_factory do article = insert(:article) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index d9be248dc..93464ebff 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -5,6 +5,8 @@ defmodule HttpRequestMock do require Logger + def activitypub_object_headers, do: [{"content-type", "application/activity+json"}] + def request( %Tesla.Env{ url: url, @@ -34,7 +36,8 @@ def get("https://osada.macgirvin.com/channel/mike", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json") + body: File.read!("test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json"), + headers: activitypub_object_headers() }} end @@ -42,7 +45,8 @@ def get("https://shitposter.club/users/moonman", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/moonman@shitposter.club.json") + body: File.read!("test/fixtures/tesla_mock/moonman@shitposter.club.json"), + headers: activitypub_object_headers() }} end @@ -50,7 +54,8 @@ def get("https://mastodon.social/users/emelie/statuses/101849165031453009", _, _ {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/status.emelie.json") + body: File.read!("test/fixtures/tesla_mock/status.emelie.json"), + headers: activitypub_object_headers() }} end @@ -66,7 +71,8 @@ def get("https://mastodon.social/users/emelie", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/emelie.json") + body: File.read!("test/fixtures/tesla_mock/emelie.json"), + headers: activitypub_object_headers() }} end @@ -78,7 +84,8 @@ def get("https://mastodon.sdf.org/users/rinpatch", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/rinpatch.json") + body: File.read!("test/fixtures/tesla_mock/rinpatch.json"), + headers: activitypub_object_headers() }} end @@ -86,7 +93,8 @@ def get("https://patch.cx/objects/tesla_mock/poll_attachment", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/poll_attachment.json") + body: File.read!("test/fixtures/tesla_mock/poll_attachment.json"), + headers: activitypub_object_headers() }} end @@ -99,15 +107,8 @@ def get( {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/webfinger_emelie.json") - }} - end - - def get("https://mastodon.social/users/emelie.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/emelie.atom") + body: File.read!("test/fixtures/tesla_mock/webfinger_emelie.json"), + headers: activitypub_object_headers() }} end @@ -120,7 +121,8 @@ def get( {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/mike@osada.macgirvin.com.json") + body: File.read!("test/fixtures/tesla_mock/mike@osada.macgirvin.com.json"), + headers: activitypub_object_headers() }} end @@ -137,14 +139,6 @@ def get( }} end - def get("https://pawoo.net/users/pekorino.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/https___pawoo.net_users_pekorino.atom") - }} - end - def get( "https://pawoo.net/.well-known/webfinger?resource=acct:https://pawoo.net/users/pekorino", _, @@ -158,19 +152,6 @@ def get( }} end - def get( - "https://social.stopwatchingus-heidelberg.de/api/statuses/user_timeline/18330.atom", - _, - _, - _ - ) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/atarifrosch_feed.xml") - }} - end - def get( "https://social.stopwatchingus-heidelberg.de/.well-known/webfinger?resource=acct:https://social.stopwatchingus-heidelberg.de/user/18330", _, @@ -184,27 +165,6 @@ def get( }} end - def get("https://mamot.fr/users/Skruyb.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/https___mamot.fr_users_Skruyb.atom") - }} - end - - def get( - "https://mamot.fr/.well-known/webfinger?resource=acct:https://mamot.fr/users/Skruyb", - _, - _, - [{"accept", "application/xrd+xml,application/jrd+json"}] - ) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/skruyb@mamot.fr.atom") - }} - end - def get( "https://social.heldscal.la/.well-known/webfinger?resource=nonexistant@social.heldscal.la", _, @@ -240,7 +200,8 @@ def get( {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/lucifermysticus.json") + body: File.read!("test/fixtures/tesla_mock/lucifermysticus.json"), + headers: activitypub_object_headers() }} end @@ -248,7 +209,8 @@ def get("https://prismo.news/@mxb", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json") + body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json"), + headers: activitypub_object_headers() }} end @@ -261,7 +223,8 @@ def get( {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json") + body: File.read!("test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json"), + headers: activitypub_object_headers() }} end @@ -269,7 +232,8 @@ def get("https://niu.moe/users/rye", _, _, [{"accept", "application/activity+jso {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/rye.json") + body: File.read!("test/fixtures/tesla_mock/rye.json"), + headers: activitypub_object_headers() }} end @@ -277,7 +241,8 @@ def get("https://n1u.moe/users/rye", _, _, [{"accept", "application/activity+jso {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/rye.json") + body: File.read!("test/fixtures/tesla_mock/rye.json"), + headers: activitypub_object_headers() }} end @@ -296,7 +261,8 @@ def get("https://puckipedia.com/", _, _, [{"accept", "application/activity+json" {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/puckipedia.com.json") + body: File.read!("test/fixtures/tesla_mock/puckipedia.com.json"), + headers: activitypub_object_headers() }} end @@ -304,7 +270,8 @@ def get("https://peertube.moe/accounts/7even", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/7even.json") + body: File.read!("test/fixtures/tesla_mock/7even.json"), + headers: activitypub_object_headers() }} end @@ -312,7 +279,8 @@ def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/peertube.moe-vid.json") + body: File.read!("test/fixtures/tesla_mock/peertube.moe-vid.json"), + headers: activitypub_object_headers() }} end @@ -320,7 +288,8 @@ def get("https://framatube.org/accounts/framasoft", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/https___framatube.org_accounts_framasoft.json") + body: File.read!("test/fixtures/tesla_mock/https___framatube.org_accounts_framasoft.json"), + headers: activitypub_object_headers() }} end @@ -328,7 +297,8 @@ def get("https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206 {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/framatube.org-video.json") + body: File.read!("test/fixtures/tesla_mock/framatube.org-video.json"), + headers: activitypub_object_headers() }} end @@ -336,7 +306,8 @@ def get("https://peertube.social/accounts/craigmaloney", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/craigmaloney.json") + body: File.read!("test/fixtures/tesla_mock/craigmaloney.json"), + headers: activitypub_object_headers() }} end @@ -344,7 +315,8 @@ def get("https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34 {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/peertube-social.json") + body: File.read!("test/fixtures/tesla_mock/peertube-social.json"), + headers: activitypub_object_headers() }} end @@ -354,7 +326,8 @@ def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _, {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/mobilizon.org-event.json") + body: File.read!("test/fixtures/tesla_mock/mobilizon.org-event.json"), + headers: activitypub_object_headers() }} end @@ -362,7 +335,8 @@ def get("https://mobilizon.org/@tcit", _, _, [{"accept", "application/activity+j {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/mobilizon.org-user.json") + body: File.read!("test/fixtures/tesla_mock/mobilizon.org-user.json"), + headers: activitypub_object_headers() }} end @@ -370,7 +344,8 @@ def get("https://baptiste.gelez.xyz/@/BaptisteGelez", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json") + body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json"), + headers: activitypub_object_headers() }} end @@ -378,7 +353,8 @@ def get("https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june- {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json") + body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json"), + headers: activitypub_object_headers() }} end @@ -386,7 +362,8 @@ def get("https://wedistribute.org/wp-json/pterotype/v1/object/85810", _, _, _) d {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/wedistribute-article.json") + body: File.read!("test/fixtures/tesla_mock/wedistribute-article.json"), + headers: activitypub_object_headers() }} end @@ -394,7 +371,8 @@ def get("https://wedistribute.org/wp-json/pterotype/v1/actor/-blog", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/wedistribute-user.json") + body: File.read!("test/fixtures/tesla_mock/wedistribute-user.json"), + headers: activitypub_object_headers() }} end @@ -402,7 +380,8 @@ def get("http://mastodon.example.org/users/admin", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json") + body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json"), + headers: activitypub_object_headers() }} end @@ -412,7 +391,8 @@ def get("http://mastodon.example.org/users/relay", _, _, [ {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/relay@mastdon.example.org.json") + body: File.read!("test/fixtures/tesla_mock/relay@mastdon.example.org.json"), + headers: activitypub_object_headers() }} end @@ -507,19 +487,6 @@ def get("https://mamot.fr/.well-known/host-meta", _, _, _) do }} end - def get( - "https://mamot.fr/.well-known/webfinger?resource=https://mamot.fr/users/Skruyb", - _, - _, - _ - ) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/skruyb@mamot.fr.atom") - }} - end - def get("http://pawoo.net/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ @@ -545,7 +512,8 @@ def get( {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/pekorino@pawoo.net_host_meta.json") + body: File.read!("test/fixtures/tesla_mock/pekorino@pawoo.net_host_meta.json"), + headers: activitypub_object_headers() }} end @@ -606,7 +574,8 @@ def get( {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/mastodon-note-object.json") + body: File.read!("test/fixtures/mastodon-note-object.json"), + headers: activitypub_object_headers() }} end @@ -630,7 +599,8 @@ def get("https://mstdn.io/users/mayuutann", _, _, [{"accept", "application/activ {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/mayumayu.json") + body: File.read!("test/fixtures/tesla_mock/mayumayu.json"), + headers: activitypub_object_headers() }} end @@ -643,18 +613,8 @@ def get( {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/mayumayupost.json") - }} - end - - def get("https://pleroma.soykaf.com/users/lain/feed.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___pleroma.soykaf.com_users_lain_feed.atom.xml" - ) + body: File.read!("test/fixtures/tesla_mock/mayumayupost.json"), + headers: activitypub_object_headers() }} end @@ -670,17 +630,6 @@ def get(url, _, _, [{"accept", "application/xrd+xml,application/jrd+json"}]) }} end - def get("https://shitposter.club/api/statuses/user_timeline/1.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___shitposter.club_api_statuses_user_timeline_1.atom.xml" - ) - }} - end - def get( "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/1", _, @@ -694,37 +643,10 @@ def get( }} end - def get("https://shitposter.club/notice/2827873", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/https___shitposter.club_notice_2827873.json") - }} - end - - def get("https://shitposter.club/api/statuses/show/2827873.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___shitposter.club_api_statuses_show_2827873.atom.xml" - ) - }} - end - def get("https://testing.pleroma.lol/objects/b319022a-4946-44c5-9de9-34801f95507b", _, _, _) do {:ok, %Tesla.Env{status: 200}} end - def get("https://shitposter.club/api/statuses/user_timeline/5381.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/spc_5381.atom") - }} - end - def get( "https://shitposter.club/.well-known/webfinger?resource=https://shitposter.club/user/5381", _, @@ -746,14 +668,6 @@ def get("http://shitposter.club/.well-known/host-meta", _, _, _) do }} end - def get("https://shitposter.club/api/statuses/show/7369654.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/7369654.atom") - }} - end - def get("https://shitposter.club/notice/4027863", _, _, _) do {:ok, %Tesla.Env{ @@ -762,14 +676,6 @@ def get("https://shitposter.club/notice/4027863", _, _, _) do }} end - def get("https://social.sakamoto.gq/users/eal/feed.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/tesla_mock/sakamoto_eal_feed.atom") - }} - end - def get("http://social.sakamoto.gq/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ @@ -791,15 +697,6 @@ def get( }} end - def get( - "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056", - _, - _, - [{"accept", "application/atom+xml"}] - ) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sakamoto.atom")}} - end - def get("http://mastodon.social/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{ @@ -853,28 +750,6 @@ def get( {:ok, %Tesla.Env{status: 406, body: ""}} end - def get("http://gs.example.org/index.php/api/statuses/user_timeline/1.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/http__gs.example.org_index.php_api_statuses_user_timeline_1.atom.xml" - ) - }} - end - - def get("https://social.heldscal.la/api/statuses/user_timeline/29191.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_29191.atom.xml" - ) - }} - end - def get("http://squeet.me/.well-known/host-meta", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/squeet.me_host_meta")}} @@ -954,7 +829,8 @@ def get( {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/winterdienst_webfinger.json") + body: File.read!("test/fixtures/tesla_mock/winterdienst_webfinger.json"), + headers: activitypub_object_headers() }} end @@ -996,17 +872,6 @@ def get( }} end - def get("https://social.heldscal.la/api/statuses/user_timeline/23211.atom", _, _, _) do - {:ok, - %Tesla.Env{ - status: 200, - body: - File.read!( - "test/fixtures/tesla_mock/https___social.heldscal.la_api_statuses_user_timeline_23211.atom.xml" - ) - }} - end - def get( "https://social.heldscal.la/.well-known/webfinger?resource=https://social.heldscal.la/user/23211", _, @@ -1036,17 +901,22 @@ def get("https://social.heldscal.la/.well-known/host-meta", _, _, _) do }} end - def get("https://mastodon.social/users/lambadalambda.atom", _, _, _) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.atom")}} - end - def get("https://mastodon.social/users/lambadalambda", _, _, _) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/lambadalambda.json")}} + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/lambadalambda.json"), + headers: activitypub_object_headers() + }} end def get("https://apfed.club/channel/indio", _, _, _) do {:ok, - %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/osada-user-indio.json")}} + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/osada-user-indio.json"), + headers: activitypub_object_headers() + }} end def get("https://social.heldscal.la/user/23211", _, _, [{"accept", "application/activity+json"}]) do @@ -1069,7 +939,8 @@ def get("http://localhost:4001/users/masto_closed/followers", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/users_mock/masto_closed_followers.json") + body: File.read!("test/fixtures/users_mock/masto_closed_followers.json"), + headers: activitypub_object_headers() }} end @@ -1077,7 +948,8 @@ def get("http://localhost:4001/users/masto_closed/followers?page=1", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/users_mock/masto_closed_followers_page.json") + body: File.read!("test/fixtures/users_mock/masto_closed_followers_page.json"), + headers: activitypub_object_headers() }} end @@ -1085,7 +957,8 @@ def get("http://localhost:4001/users/masto_closed/following", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/users_mock/masto_closed_following.json") + body: File.read!("test/fixtures/users_mock/masto_closed_following.json"), + headers: activitypub_object_headers() }} end @@ -1093,7 +966,8 @@ def get("http://localhost:4001/users/masto_closed/following?page=1", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/users_mock/masto_closed_following_page.json") + body: File.read!("test/fixtures/users_mock/masto_closed_following_page.json"), + headers: activitypub_object_headers() }} end @@ -1101,7 +975,8 @@ def get("http://localhost:8080/followers/fuser3", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/users_mock/friendica_followers.json") + body: File.read!("test/fixtures/users_mock/friendica_followers.json"), + headers: activitypub_object_headers() }} end @@ -1109,7 +984,8 @@ def get("http://localhost:8080/following/fuser3", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/users_mock/friendica_following.json") + body: File.read!("test/fixtures/users_mock/friendica_following.json"), + headers: activitypub_object_headers() }} end @@ -1117,7 +993,8 @@ def get("http://localhost:4001/users/fuser2/followers", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/users_mock/pleroma_followers.json") + body: File.read!("test/fixtures/users_mock/pleroma_followers.json"), + headers: activitypub_object_headers() }} end @@ -1125,7 +1002,8 @@ def get("http://localhost:4001/users/fuser2/following", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/users_mock/pleroma_following.json") + body: File.read!("test/fixtures/users_mock/pleroma_following.json"), + headers: activitypub_object_headers() }} end @@ -1223,7 +1101,8 @@ def get("https://info.pleroma.site/activity.json", _, _, [ {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity.json") + body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity.json"), + headers: activitypub_object_headers() }} end @@ -1237,7 +1116,8 @@ def get("https://info.pleroma.site/activity2.json", _, _, [ {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity2.json") + body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity2.json"), + headers: activitypub_object_headers() }} end @@ -1251,7 +1131,8 @@ def get("https://info.pleroma.site/activity3.json", _, _, [ {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json") + body: File.read!("test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json"), + headers: activitypub_object_headers() }} end @@ -1284,7 +1165,12 @@ def get("https://www.patreon.com/posts/mastodon-2-9-and-28121681", _, _, _) do end def get("http://mastodon.example.org/@admin/99541947525187367", _, _, _) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/mastodon-post-activity.json")}} + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/mastodon-post-activity.json"), + headers: activitypub_object_headers() + }} end def get("https://info.pleroma.site/activity4.json", _, _, _) do @@ -1311,7 +1197,8 @@ def get("https://skippers-bin.com/notes/7x9tmrp97i", _, _, _) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/misskey_poll_no_end_date.json") + body: File.read!("test/fixtures/tesla_mock/misskey_poll_no_end_date.json"), + headers: activitypub_object_headers() }} end @@ -1320,11 +1207,21 @@ def get("https://example.org/emoji/firedfox.png", _, _, _) do end def get("https://skippers-bin.com/users/7v1w1r8ce6", _, _, _) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/sjw.json")}} + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/sjw.json"), + headers: activitypub_object_headers() + }} end def get("https://patch.cx/users/rin", _, _, _) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/rin.json")}} + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/rin.json"), + headers: activitypub_object_headers() + }} end def get( @@ -1334,12 +1231,20 @@ def get( _ ) do {:ok, - %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/funkwhale_audio.json")}} + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/funkwhale_audio.json"), + headers: activitypub_object_headers() + }} end def get("https://channels.tests.funkwhale.audio/federation/actors/compositions", _, _, _) do {:ok, - %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/funkwhale_channel.json")}} + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/funkwhale_channel.json"), + headers: activitypub_object_headers() + }} end def get("http://example.com/rel_me/error", _, _, _) do @@ -1347,7 +1252,12 @@ def get("http://example.com/rel_me/error", _, _, _) do end def get("https://relay.mastodon.host/actor", _, _, _) do - {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/relay/relay.json")}} + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/relay/relay.json"), + headers: activitypub_object_headers() + }} end def get("http://localhost:4001/", _, "", [{"accept", "text/html"}]) do diff --git a/test/support/web_push_http_client_mock.ex b/test/support/web_push_http_client_mock.ex deleted file mode 100644 index 3cd12957d..000000000 --- a/test/support/web_push_http_client_mock.ex +++ /dev/null @@ -1,23 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.WebPushHttpClientMock do - def get(url, headers \\ [], options \\ []) do - { - res, - %Tesla.Env{status: status} - } = Pleroma.HTTP.request(:get, url, "", headers, options) - - {res, %{status_code: status}} - end - - def post(url, body, headers \\ [], options \\ []) do - { - res, - %Tesla.Env{status: status} - } = Pleroma.HTTP.request(:post, url, body, headers, options) - - {res, %{status_code: status}} - end -end diff --git a/test/tasks/email_test.exs b/test/tasks/email_test.exs deleted file mode 100644 index c3af7ef68..000000000 --- a/test/tasks/email_test.exs +++ /dev/null @@ -1,54 +0,0 @@ -defmodule Mix.Tasks.Pleroma.EmailTest do - use Pleroma.DataCase - - import Swoosh.TestAssertions - - alias Pleroma.Config - alias Pleroma.Tests.ObanHelpers - - setup_all do - Mix.shell(Mix.Shell.Process) - - on_exit(fn -> - Mix.shell(Mix.Shell.IO) - end) - - :ok - end - - setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true) - - describe "pleroma.email test" do - test "Sends test email with no given address" do - mail_to = Config.get([:instance, :email]) - - :ok = Mix.Tasks.Pleroma.Email.run(["test"]) - - ObanHelpers.perform_all() - - assert_receive {:mix_shell, :info, [message]} - assert message =~ "Test email has been sent" - - assert_email_sent( - to: mail_to, - html_body: ~r/a test email was requested./i - ) - end - - test "Sends test email with given address" do - mail_to = "hewwo@example.com" - - :ok = Mix.Tasks.Pleroma.Email.run(["test", "--to", mail_to]) - - ObanHelpers.perform_all() - - assert_receive {:mix_shell, :info, [message]} - assert message =~ "Test email has been sent" - - assert_email_sent( - to: mail_to, - html_body: ~r/a test email was requested./i - ) - end - end -end diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs deleted file mode 100644 index d4db44c63..000000000 --- a/test/web/media_proxy/media_proxy_controller_test.exs +++ /dev/null @@ -1,121 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do - use Pleroma.Web.ConnCase - - import Mock - - alias Pleroma.Web.MediaProxy - alias Pleroma.Web.MediaProxy.MediaProxyController - alias Plug.Conn - - setup do - on_exit(fn -> Cachex.clear(:banned_urls_cache) end) - end - - test "it returns 404 when MediaProxy disabled", %{conn: conn} do - clear_config([:media_proxy, :enabled], false) - - assert %Conn{ - status: 404, - resp_body: "Not Found" - } = get(conn, "/proxy/hhgfh/eeeee") - - assert %Conn{ - status: 404, - resp_body: "Not Found" - } = get(conn, "/proxy/hhgfh/eeee/fff") - end - - describe "" do - setup do - clear_config([:media_proxy, :enabled], true) - clear_config([Pleroma.Web.Endpoint, :secret_key_base], "00000000000") - [url: MediaProxy.encode_url("https://google.fn/test.png")] - end - - test "it returns 403 for invalid signature", %{conn: conn, url: url} do - Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], "000") - %{path: path} = URI.parse(url) - - assert %Conn{ - status: 403, - resp_body: "Forbidden" - } = get(conn, path) - - assert %Conn{ - status: 403, - resp_body: "Forbidden" - } = get(conn, "/proxy/hhgfh/eeee") - - assert %Conn{ - status: 403, - resp_body: "Forbidden" - } = get(conn, "/proxy/hhgfh/eeee/fff") - end - - test "redirects on valid url when filename is invalidated", %{conn: conn, url: url} do - invalid_url = String.replace(url, "test.png", "test-file.png") - response = get(conn, invalid_url) - assert response.status == 302 - assert redirected_to(response) == url - end - - test "it performs ReverseProxy.call with valid signature", %{conn: conn, url: url} do - with_mock Pleroma.ReverseProxy, - call: fn _conn, _url, _opts -> %Conn{status: :success} end do - assert %Conn{status: :success} = get(conn, url) - end - end - - test "it returns 404 when url is in banned_urls cache", %{conn: conn, url: url} do - MediaProxy.put_in_banned_urls("https://google.fn/test.png") - - with_mock Pleroma.ReverseProxy, - call: fn _conn, _url, _opts -> %Conn{status: :success} end do - assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url) - end - end - end - - describe "filename_matches/3" do - test "preserves the encoded or decoded path" do - assert MediaProxyController.filename_matches( - %{"filename" => "/Hello world.jpg"}, - "/Hello world.jpg", - "http://pleroma.social/Hello world.jpg" - ) == :ok - - assert MediaProxyController.filename_matches( - %{"filename" => "/Hello%20world.jpg"}, - "/Hello%20world.jpg", - "http://pleroma.social/Hello%20world.jpg" - ) == :ok - - assert MediaProxyController.filename_matches( - %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}, - "/my%2Flong%2Furl%2F2019%2F07%2FS.jpg", - "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" - ) == :ok - - assert MediaProxyController.filename_matches( - %{"filename" => "/my%2Flong%2Furl%2F2019%2F07%2FS.jp"}, - "/my%2Flong%2Furl%2F2019%2F07%2FS.jp", - "http://pleroma.social/my%2Flong%2Furl%2F2019%2F07%2FS.jpg" - ) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"} - end - - test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do - # conn.request_path will return encoded url - request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg" - - assert MediaProxyController.filename_matches( - true, - request_path, - "https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg" - ) == :ok - end - end -end diff --git a/test/workers/cron/clear_oauth_token_worker_test.exs b/test/workers/cron/clear_oauth_token_worker_test.exs deleted file mode 100644 index 67836f34f..000000000 --- a/test/workers/cron/clear_oauth_token_worker_test.exs +++ /dev/null @@ -1,22 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.ClearOauthTokenWorkerTest do - use Pleroma.DataCase - - import Pleroma.Factory - alias Pleroma.Workers.Cron.ClearOauthTokenWorker - - setup do: clear_config([:oauth2, :clean_expired_tokens]) - - test "deletes expired tokens" do - insert(:oauth_token, - valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -60 * 10) - ) - - Pleroma.Config.put([:oauth2, :clean_expired_tokens], true) - ClearOauthTokenWorker.perform(%Oban.Job{}) - assert Pleroma.Repo.all(Pleroma.Web.OAuth.Token) == [] - end -end diff --git a/test/workers/cron/purge_expired_activities_worker_test.exs b/test/workers/cron/purge_expired_activities_worker_test.exs deleted file mode 100644 index d1acd9ae6..000000000 --- a/test/workers/cron/purge_expired_activities_worker_test.exs +++ /dev/null @@ -1,84 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorkerTest do - use Pleroma.DataCase - - alias Pleroma.ActivityExpiration - alias Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker - - import Pleroma.Factory - import ExUnit.CaptureLog - - setup do - clear_config([ActivityExpiration, :enabled]) - end - - test "deletes an expiration activity" do - Pleroma.Config.put([ActivityExpiration, :enabled], true) - activity = insert(:note_activity) - - naive_datetime = - NaiveDateTime.add( - NaiveDateTime.utc_now(), - -:timer.minutes(2), - :millisecond - ) - - expiration = - insert( - :expiration_in_the_past, - %{activity_id: activity.id, scheduled_at: naive_datetime} - ) - - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) - - refute Pleroma.Repo.get(Pleroma.Activity, activity.id) - refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id) - end - - test "works with ActivityExpirationPolicy" do - Pleroma.Config.put([ActivityExpiration, :enabled], true) - - clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy) - - user = insert(:user) - - days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365) - - {:ok, %{id: id} = activity} = Pleroma.Web.CommonAPI.post(user, %{status: "cofe"}) - - past_date = - NaiveDateTime.utc_now() |> Timex.shift(days: -days) |> NaiveDateTime.truncate(:second) - - activity - |> Repo.preload(:expiration) - |> Map.get(:expiration) - |> Ecto.Changeset.change(%{scheduled_at: past_date}) - |> Repo.update!() - - Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{}) - - assert [%{data: %{"type" => "Delete", "deleted_activity_id" => ^id}}] = - Pleroma.Repo.all(Pleroma.Activity) - end - - describe "delete_activity/1" do - test "adds log message if activity isn't find" do - assert capture_log([level: :error], fn -> - PurgeExpiredActivitiesWorker.delete_activity(%ActivityExpiration{ - activity_id: "test-activity" - }) - end) =~ "Couldn't delete expired activity: not found activity" - end - - test "adds log message if actor isn't find" do - assert capture_log([level: :error], fn -> - PurgeExpiredActivitiesWorker.delete_activity(%ActivityExpiration{ - activity_id: "test-activity" - }) - end) =~ "Couldn't delete expired activity: not found activity" - end - end -end