forked from AkkomaGang/akkoma
Merge remote-tracking branch 'origin/develop' into fix-slow-relationships
This commit is contained in:
commit
39bcf93007
68 changed files with 2139 additions and 834 deletions
|
@ -1,9 +1,5 @@
|
||||||
image: elixir:1.8.1
|
image: elixir:1.8.1
|
||||||
|
|
||||||
services:
|
|
||||||
- name: postgres:9.6.2
|
|
||||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
POSTGRES_DB: pleroma_test
|
POSTGRES_DB: pleroma_test
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
@ -17,58 +13,60 @@ cache:
|
||||||
- deps
|
- deps
|
||||||
- _build
|
- _build
|
||||||
stages:
|
stages:
|
||||||
- lint
|
- build
|
||||||
- test
|
- test
|
||||||
- analysis
|
- deploy
|
||||||
- docs_build
|
|
||||||
- docs_deploy
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- mix local.hex --force
|
- mix local.hex --force
|
||||||
- mix local.rebar --force
|
- mix local.rebar --force
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
- mix deps.get
|
- mix deps.get
|
||||||
- mix compile --force
|
- mix compile --force
|
||||||
- mix ecto.create
|
|
||||||
- mix ecto.migrate
|
|
||||||
|
|
||||||
lint:
|
docs-build:
|
||||||
stage: lint
|
stage: build
|
||||||
script:
|
|
||||||
- mix format --check-formatted
|
|
||||||
|
|
||||||
unit-testing:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- mix test --trace --preload-modules
|
|
||||||
|
|
||||||
analysis:
|
|
||||||
stage: analysis
|
|
||||||
script:
|
|
||||||
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
|
|
||||||
|
|
||||||
docs_build:
|
|
||||||
stage: docs_build
|
|
||||||
services:
|
|
||||||
only:
|
only:
|
||||||
- master@pleroma/pleroma
|
- master@pleroma/pleroma
|
||||||
- develop@pleroma/pleroma
|
- develop@pleroma/pleroma
|
||||||
variables:
|
variables:
|
||||||
MIX_ENV: dev
|
MIX_ENV: dev
|
||||||
before_script:
|
script:
|
||||||
- mix local.hex --force
|
|
||||||
- mix local.rebar --force
|
|
||||||
- mix deps.get
|
- mix deps.get
|
||||||
- mix compile
|
- mix compile
|
||||||
script:
|
|
||||||
- mix docs
|
- mix docs
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- priv/static/doc
|
- priv/static/doc
|
||||||
|
|
||||||
docs_deploy:
|
unit-testing:
|
||||||
stage: docs_deploy
|
stage: test
|
||||||
image: alpine:3.9
|
|
||||||
services:
|
services:
|
||||||
|
- name: postgres:9.6.2
|
||||||
|
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||||
|
script:
|
||||||
|
- mix ecto.create
|
||||||
|
- mix ecto.migrate
|
||||||
|
- mix test --trace --preload-modules
|
||||||
|
|
||||||
|
lint:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- mix format --check-formatted
|
||||||
|
|
||||||
|
analysis:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- mix deps.get
|
||||||
|
- mix credo --strict --only=warnings,todo,fixme,consistency,readability
|
||||||
|
|
||||||
|
|
||||||
|
docs-deploy:
|
||||||
|
stage: deploy
|
||||||
|
image: alpine:3.9
|
||||||
only:
|
only:
|
||||||
- master@pleroma/pleroma
|
- master@pleroma/pleroma
|
||||||
- develop@pleroma/pleroma
|
- develop@pleroma/pleroma
|
||||||
|
|
403
CC-BY-NC-ND-4.0
Normal file
403
CC-BY-NC-ND-4.0
Normal file
|
@ -0,0 +1,403 @@
|
||||||
|
Attribution-NonCommercial-NoDerivatives 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-NonCommercial-NoDerivatives 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-NonCommercial-NoDerivatives 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. 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.
|
||||||
|
|
||||||
|
c. 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.
|
||||||
|
|
||||||
|
d. 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.
|
||||||
|
|
||||||
|
e. Licensed Material means the artistic or literary work, database,
|
||||||
|
or other material to which the Licensor applied this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
f. 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.
|
||||||
|
|
||||||
|
g. Licensor means the individual(s) or entity(ies) granting rights
|
||||||
|
under this Public License.
|
||||||
|
|
||||||
|
h. NonCommercial means not primarily intended for or directed towards
|
||||||
|
commercial advantage or monetary compensation. For purposes of
|
||||||
|
this Public License, the exchange of the Licensed Material for
|
||||||
|
other material subject to Copyright and Similar Rights by digital
|
||||||
|
file-sharing or similar means is NonCommercial provided there is
|
||||||
|
no payment of monetary compensation in connection with the
|
||||||
|
exchange.
|
||||||
|
|
||||||
|
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, for NonCommercial purposes only; and
|
||||||
|
|
||||||
|
b. produce and reproduce, but not Share, Adapted Material
|
||||||
|
for NonCommercial purposes only.
|
||||||
|
|
||||||
|
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, including when
|
||||||
|
the Licensed Material is used other than for NonCommercial
|
||||||
|
purposes.
|
||||||
|
|
||||||
|
|
||||||
|
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, 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.
|
||||||
|
|
||||||
|
For the avoidance of doubt, You do not have permission under
|
||||||
|
this Public License to Share Adapted Material.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
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 for NonCommercial purposes
|
||||||
|
only and provided You do not Share Adapted Material;
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
427
CC-BY-SA-4.0
Normal file
427
CC-BY-SA-4.0
Normal file
|
@ -0,0 +1,427 @@
|
||||||
|
Attribution-ShareAlike 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-ShareAlike 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-ShareAlike 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. BY-SA Compatible License means a license listed at
|
||||||
|
creativecommons.org/compatiblelicenses, approved by Creative
|
||||||
|
Commons as essentially the equivalent of this Public License.
|
||||||
|
|
||||||
|
d. 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.
|
||||||
|
|
||||||
|
e. 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.
|
||||||
|
|
||||||
|
f. 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.
|
||||||
|
|
||||||
|
g. License Elements means the license attributes listed in the name
|
||||||
|
of a Creative Commons Public License. The License Elements of this
|
||||||
|
Public License are Attribution and ShareAlike.
|
||||||
|
|
||||||
|
h. Licensed Material means the artistic or literary work, database,
|
||||||
|
or other material to which the Licensor applied this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
i. 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.
|
||||||
|
|
||||||
|
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||||
|
under this Public License.
|
||||||
|
|
||||||
|
k. 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.
|
||||||
|
|
||||||
|
l. 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.
|
||||||
|
|
||||||
|
m. 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. Additional offer from the Licensor -- Adapted Material.
|
||||||
|
Every recipient of Adapted Material from You
|
||||||
|
automatically receives an offer from the Licensor to
|
||||||
|
exercise the Licensed Rights in the Adapted Material
|
||||||
|
under the conditions of the Adapter's License You apply.
|
||||||
|
|
||||||
|
c. 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.
|
||||||
|
|
||||||
|
b. ShareAlike.
|
||||||
|
|
||||||
|
In addition to the conditions in Section 3(a), if You Share
|
||||||
|
Adapted Material You produce, the following conditions also apply.
|
||||||
|
|
||||||
|
1. The Adapter's License You apply must be a Creative Commons
|
||||||
|
license with the same License Elements, this version or
|
||||||
|
later, or a BY-SA Compatible License.
|
||||||
|
|
||||||
|
2. You must include the text of, or the URI or hyperlink to, the
|
||||||
|
Adapter's License You apply. You may satisfy this condition
|
||||||
|
in any reasonable manner based on the medium, means, and
|
||||||
|
context in which You Share Adapted Material.
|
||||||
|
|
||||||
|
3. You may not offer or impose any additional or different terms
|
||||||
|
or conditions on, or apply any Effective Technological
|
||||||
|
Measures to, Adapted Material that restrict exercise of the
|
||||||
|
rights granted under the Adapter's License You apply.
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
including for purposes of Section 3(b); 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.
|
48
COPYING
Normal file
48
COPYING
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
Unless otherwise stated this repository is copyright © 2017-2019
|
||||||
|
Pleroma Authors <https://pleroma.social/>, and is distributed under
|
||||||
|
The GNU Affero General Public License Version 3, you should have received a
|
||||||
|
copy of the license file as AGPL-3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The following files are copyright © 2019 shitposter.club, and are distributed
|
||||||
|
under the Creative Commons Attribution-ShareAlike 4.0 International license,
|
||||||
|
you should have received a copy of the license file as CC-BY-SA-4.0.
|
||||||
|
|
||||||
|
priv/static/images/pleroma-fox-tan.png
|
||||||
|
priv/static/images/pleroma-fox-tan-smol.png
|
||||||
|
priv/static/images/pleroma-tan.png
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The following files are copyright © 2017-2019 Pleroma Authors
|
||||||
|
<https://pleroma.social/>, and are distributed under the Creative Commons
|
||||||
|
Attribution-ShareAlike 4.0 International license, you should have received
|
||||||
|
a copy of the license file as CC-BY-SA-4.0.
|
||||||
|
|
||||||
|
priv/static/images/avi.png
|
||||||
|
priv/static/images/banner.png
|
||||||
|
priv/static/instance/thumbnail.jpeg
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
All photos published on Unsplash can be used for free. You can use them for
|
||||||
|
commercial and noncommercial purposes. You do not need to ask permission from
|
||||||
|
or provide credit to the photographer or Unsplash, although it is appreciated
|
||||||
|
when possible.
|
||||||
|
|
||||||
|
More precisely, Unsplash grants you an irrevocable, nonexclusive, worldwide
|
||||||
|
copyright license to download, copy, modify, distribute, perform, and use
|
||||||
|
photos from Unsplash for free, including for commercial purposes, without
|
||||||
|
permission from or attributing the photographer or Unsplash. This license
|
||||||
|
does not include the right to compile photos from Unsplash to replicate
|
||||||
|
a similar or competing service.
|
||||||
|
|
||||||
|
priv/static/images/city.jpg
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The files present under the priv/static/finmoji directory are copyright
|
||||||
|
Finland <https://finland.fi/emoji/>, and are distributed under the Creative
|
||||||
|
Commons Attribution-NonCommercial-NoDerivatives 4.0 International license, you
|
||||||
|
should have received a copy of the license file as CC-BY-NC-ND-4.0.
|
10
README.md
10
README.md
|
@ -1,14 +1,16 @@
|
||||||
# Pleroma
|
# Pleroma
|
||||||
|
|
||||||
|
**Note**: This readme as well as complete documentation is also availible at <https://docs-develop.pleroma.social>
|
||||||
|
|
||||||
## About Pleroma
|
## About Pleroma
|
||||||
|
|
||||||
Pleroma is a microblogging server software that can federate (= exchange messages with) other servers that support the same federation standards (OStatus and ActivityPub). What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Pleroma will federate with all servers that implement either OStatus or ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
|
Pleroma is a microblogging server software that can federate (= exchange messages with) other servers that support the same federation standards (OStatus and ActivityPub). What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Pleroma will federate with all servers that implement either OStatus or ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.
|
||||||
|
|
||||||
Pleroma is written in Elixir, high-performance and can run on small devices like a Raspberry Pi.
|
Pleroma is written in Elixir, high-performance and can run on small devices like a Raspberry Pi.
|
||||||
|
|
||||||
For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md).
|
For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://docs.joinmastodon.org/api/guidelines/).
|
||||||
|
|
||||||
- [Client Applications for Pleroma](docs/Clients.md)
|
- [Client Applications for Pleroma](https://docs-develop.pleroma.social/clients.html)
|
||||||
|
|
||||||
No release has been made yet, but several servers have been online for months already. If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>.
|
No release has been made yet, but several servers have been online for months already. If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at <https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org>.
|
||||||
|
|
||||||
|
@ -28,7 +30,7 @@ While we don’t provide docker files, other people have written very good ones.
|
||||||
|
|
||||||
* Run `mix deps.get` to install elixir dependencies.
|
* Run `mix deps.get` to install elixir dependencies.
|
||||||
* Run `mix pleroma.instance gen`. This will ask you questions about your instance and generate a configuration file in `config/generated_config.exs`. Check that and copy it to either `config/dev.secret.exs` or `config/prod.secret.exs`. It will also create a `config/setup_db.psql`, which you should run as the PostgreSQL superuser (i.e., `sudo -u postgres psql -f config/setup_db.psql`). It will create the database, user, and password you gave `mix pleroma.gen.instance` earlier, as well as set up the necessary extensions in the database. PostgreSQL superuser privileges are only needed for this step.
|
* Run `mix pleroma.instance gen`. This will ask you questions about your instance and generate a configuration file in `config/generated_config.exs`. Check that and copy it to either `config/dev.secret.exs` or `config/prod.secret.exs`. It will also create a `config/setup_db.psql`, which you should run as the PostgreSQL superuser (i.e., `sudo -u postgres psql -f config/setup_db.psql`). It will create the database, user, and password you gave `mix pleroma.gen.instance` earlier, as well as set up the necessary extensions in the database. PostgreSQL superuser privileges are only needed for this step.
|
||||||
* For these next steps, the default will be to run pleroma using the dev configuration file, `config/dev.secret.exs`. To run them using the prod config file, prefix each command at the shell with `MIX_ENV=prod`. For example: `MIX_ENV=prod mix phx.server`. Documentation for the config can be found at [`docs/config.md`](docs/config.md) in the repository, or at the "Configuration" page on <https://docs.pleroma.social>
|
* For these next steps, the default will be to run pleroma using the dev configuration file, `config/dev.secret.exs`. To run them using the prod config file, prefix each command at the shell with `MIX_ENV=prod`. For example: `MIX_ENV=prod mix phx.server`. Documentation for the config can be found at [`docs/config.md`](docs/config.md) in the repository, or at the "Configuration" page on <https://docs-develop.pleroma.social/config.html>
|
||||||
* Run `mix ecto.migrate` to run the database migrations. You will have to do this again after certain updates.
|
* Run `mix ecto.migrate` to run the database migrations. You will have to do this again after certain updates.
|
||||||
* You can check if your instance is configured correctly by running it with `mix phx.server` and checking the instance info endpoint at `/api/v1/instance`. If it shows your uri, name and email correctly, you are configured correctly. If it shows something like `localhost:4000`, your configuration is probably wrong, unless you are running a local development setup.
|
* You can check if your instance is configured correctly by running it with `mix phx.server` and checking the instance info endpoint at `/api/v1/instance`. If it shows your uri, name and email correctly, you are configured correctly. If it shows something like `localhost:4000`, your configuration is probably wrong, unless you are running a local development setup.
|
||||||
* The common and convenient way for adding HTTPS is by using Nginx as a reverse proxy. You can look at example Nginx configuration in `installation/pleroma.nginx`. If you need TLS/SSL certificates for HTTPS, you can look get some for free with letsencrypt: <https://letsencrypt.org/>. The simplest way to obtain and install a certificate is to use [Certbot.](https://certbot.eff.org) Depending on your specific setup, certbot may be able to get a certificate and configure your web server automatically.
|
* The common and convenient way for adding HTTPS is by using Nginx as a reverse proxy. You can look at example Nginx configuration in `installation/pleroma.nginx`. If you need TLS/SSL certificates for HTTPS, you can look get some for free with letsencrypt: <https://letsencrypt.org/>. The simplest way to obtain and install a certificate is to use [Certbot.](https://certbot.eff.org) Depending on your specific setup, certbot may be able to get a certificate and configure your web server automatically.
|
||||||
|
@ -66,7 +68,7 @@ This is useful for running Pleroma inside Tor or I2P.
|
||||||
|
|
||||||
## Customization and contribution
|
## Customization and contribution
|
||||||
|
|
||||||
The [Pleroma Wiki](https://git.pleroma.social/pleroma/pleroma/wikis/home) offers manuals and guides on how to further customize your instance to your liking and how you can contribute to the project.
|
The [Pleroma Documentation](https://docs-develop.pleroma.social/readme.html) offers manuals and guides on how to further customize your instance to your liking and how you can contribute to the project.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
|
@ -348,10 +348,10 @@
|
||||||
initial_timeout: 30,
|
initial_timeout: 30,
|
||||||
max_retries: 5
|
max_retries: 5
|
||||||
|
|
||||||
config :pleroma, Pleroma.Jobs,
|
config :pleroma_job_queue, :queues,
|
||||||
federator_incoming: [max_jobs: 50],
|
federator_incoming: 50,
|
||||||
federator_outgoing: [max_jobs: 50],
|
federator_outgoing: 50,
|
||||||
mailer: [max_jobs: 10]
|
mailer: 10
|
||||||
|
|
||||||
config :pleroma, :fetch_initial_posts,
|
config :pleroma, :fetch_initial_posts,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -378,6 +378,8 @@
|
||||||
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
|
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
|
||||||
uid: System.get_env("LDAP_UID") || "cn"
|
uid: System.get_env("LDAP_UID") || "cn"
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock
|
config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock
|
||||||
|
|
||||||
config :pleroma, Pleroma.Jobs, testing: [max_jobs: 2]
|
config :pleroma_job_queue, disabled: true
|
||||||
|
|
||||||
try do
|
try do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# Backup your instance
|
# Backup/Restore your instance
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
|
||||||
1. Stop the Pleroma service.
|
1. Stop the Pleroma service.
|
||||||
2. Go to the working directory of Pleroma (default is `/opt/pleroma`)
|
2. Go to the working directory of Pleroma (default is `/opt/pleroma`)
|
||||||
|
@ -6,7 +8,7 @@
|
||||||
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` and the `uploads` folder to your backup destination. If you have other modifications, copy those changes too.
|
||||||
5. Restart the Pleroma service.
|
5. Restart the Pleroma service.
|
||||||
|
|
||||||
## Restore your instance
|
## Restore
|
||||||
|
|
||||||
1. Stop the Pleroma service.
|
1. Stop the Pleroma service.
|
||||||
2. Go to the working directory of Pleroma (default is `/opt/pleroma`)
|
2. Go to the working directory of Pleroma (default is `/opt/pleroma`)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# Updating your instance
|
# Updating your instance
|
||||||
1. Stop the Pleroma service.
|
1. Go to the working directory of Pleroma (default is `/opt/pleroma`)
|
||||||
2. Go to the working directory of Pleroma (default is `/opt/pleroma`)
|
2. Run `git pull`. This pulls the latest changes from upstream.
|
||||||
3. Run `git pull`. This pulls the latest changes from upstream.
|
3. Run `mix deps.get`. This pulls in any new dependencies.
|
||||||
4. Run `mix deps.get`. 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]. This task performs database migrations, if there were any.
|
||||||
6. Restart the Pleroma service.
|
6. Start the Pleroma service.
|
||||||
|
|
||||||
[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file.
|
[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file.
|
||||||
|
|
|
@ -193,6 +193,44 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
||||||
* `port`: Port to bind to
|
* `port`: Port to bind to
|
||||||
* `dstport`: Port advertised in urls (optional, defaults to `port`)
|
* `dstport`: Port advertised in urls (optional, defaults to `port`)
|
||||||
|
|
||||||
|
## Pleroma.Web.Endpoint
|
||||||
|
`Phoenix` endpoint configuration, all configuration options can be viewed [here](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#module-dynamic-configuration), only common options are listed here
|
||||||
|
* `http` - a list containing http protocol configuration, all configuration options can be viewed [here](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html#module-options), only common options are listed here
|
||||||
|
- `ip` - a tuple consisting of 4 integers
|
||||||
|
- `port`
|
||||||
|
* `url` - a list containing the configuration for generating urls, accepts
|
||||||
|
- `host` - the host without the scheme and a post (e.g `example.com`, not `https://example.com:2020`)
|
||||||
|
- `scheme` - e.g `http`, `https`
|
||||||
|
- `port`
|
||||||
|
- `path`
|
||||||
|
|
||||||
|
|
||||||
|
**Important note**: if you modify anything inside these lists, default `config.exs` values will be overwritten, which may result in breakage, to make sure this does not happen please copy the default value for the list from `config.exs` and modify/add only what you need
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```elixir
|
||||||
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
|
url: [host: "example.com", port: 2020, scheme: "https"],
|
||||||
|
http: [
|
||||||
|
# start copied from config.exs
|
||||||
|
dispatch: [
|
||||||
|
{:_,
|
||||||
|
[
|
||||||
|
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
|
||||||
|
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
|
||||||
|
{Phoenix.Transports.WebSocket,
|
||||||
|
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
|
||||||
|
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
|
||||||
|
]}
|
||||||
|
# end copied from config.exs
|
||||||
|
],
|
||||||
|
port: 8080,
|
||||||
|
ip: {127, 0, 0, 1}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
|
||||||
|
|
||||||
## :activitypub
|
## :activitypub
|
||||||
* ``accept_blocks``: Whether to accept incoming block activities from other instances
|
* ``accept_blocks``: Whether to accept incoming block activities from other instances
|
||||||
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
||||||
|
@ -253,25 +291,25 @@ You can then do
|
||||||
curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken"
|
curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pleroma.Jobs
|
## :pleroma_job_queue
|
||||||
|
|
||||||
A list of job queues and their settings.
|
[Pleroma Job Queue](https://git.pleroma.social/pleroma/pleroma_job_queue) configuration: a list of queues with maximum concurrent jobs.
|
||||||
|
|
||||||
Job queue settings:
|
Pleroma has the following queues:
|
||||||
|
* `federator_outgoing` - Outgoing federation
|
||||||
* `max_jobs`: The maximum amount of parallel jobs running at the same time.
|
* `federator_incoming` - Incoming federation
|
||||||
|
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```exs
|
```elixir
|
||||||
config :pleroma, Pleroma.Jobs,
|
config :pleroma_job_queue, :queues,
|
||||||
federator_incoming: [max_jobs: 50],
|
federator_incoming: 50,
|
||||||
federator_outgoing: [max_jobs: 50]
|
federator_outgoing: 50
|
||||||
```
|
```
|
||||||
|
|
||||||
This config contains two queues: `federator_incoming` and `federator_outgoing`. Both have the `max_jobs` set to `50`.
|
This config contains two queues: `federator_incoming` and `federator_outgoing`. Both have the `max_jobs` set to `50`.
|
||||||
|
|
||||||
|
|
||||||
## Pleroma.Web.Federator.RetryQueue
|
## Pleroma.Web.Federator.RetryQueue
|
||||||
|
|
||||||
* `enabled`: If set to `true`, failed federation jobs will be retried
|
* `enabled`: If set to `true`, failed federation jobs will be retried
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# How to change the port or IP Pleroma listens to
|
|
||||||
To change the port or IP Pleroma listens to, head over to your generated config inside the Pleroma folder at config/prod.secret.exs and edit the following according to your needs.
|
|
||||||
```
|
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
|
||||||
[...]
|
|
||||||
http: [ip: {127, 0, 0, 1}, port: 4000]
|
|
||||||
```
|
|
|
@ -1,11 +1,11 @@
|
||||||
# Introduction to Pleroma
|
# Introduction to Pleroma
|
||||||
**What is Pleroma?**
|
## What is Pleroma?
|
||||||
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
|
Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3.
|
||||||
It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
|
It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing.
|
||||||
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
|
It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other.
|
||||||
One account on a instance is enough to talk to the entire fediverse!
|
One account on a instance is enough to talk to the entire fediverse!
|
||||||
|
|
||||||
**How can I use it?**
|
## How can I use it?
|
||||||
|
|
||||||
Pleroma instances are already widely deployed, a list can be found here:
|
Pleroma instances are already widely deployed, a list can be found here:
|
||||||
http://distsn.org/pleroma-instances.html
|
http://distsn.org/pleroma-instances.html
|
||||||
|
@ -14,14 +14,14 @@ If you don't feel like joining an existing instance, but instead prefer to deplo
|
||||||
Installation instructions can be found here:
|
Installation instructions can be found here:
|
||||||
[main Pleroma wiki](/)
|
[main Pleroma wiki](/)
|
||||||
|
|
||||||
**I got an account, now what?**
|
## I got an account, now what?
|
||||||
Great! Now you can explore the fediverse!
|
Great! Now you can explore the fediverse!
|
||||||
- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
|
- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password.
|
||||||
(If you don't have one yet, click on Register) :slightly_smiling_face:
|
(If you don't have one yet, click on Register) :slightly_smiling_face:
|
||||||
|
|
||||||
At this point you will have two columns in front of you.
|
At this point you will have two columns in front of you.
|
||||||
|
|
||||||
***left column***
|
### Left column
|
||||||
- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
|
- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers).
|
||||||
Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
|
Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000).
|
||||||
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
|
If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile:
|
||||||
|
@ -37,7 +37,7 @@ To post your status, simply press Submit.
|
||||||
|
|
||||||
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
|
- fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses.
|
||||||
|
|
||||||
***right column***
|
### Right column
|
||||||
This is where the interesting stuff happens! :slight_smile:
|
This is where the interesting stuff happens! :slight_smile:
|
||||||
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
Depending on the timeline you will see different statuses, but each status has a standard structure:
|
||||||
- Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
|
- Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status).
|
||||||
|
@ -46,7 +46,7 @@ Depending on the timeline you will see different statuses, but each status has a
|
||||||
- The text of the status, including mentions. If you click on a mention, it will automatically open the profile page of that person.
|
- The text of the status, including mentions. If you click on a mention, it will automatically open the profile page of that person.
|
||||||
- Four buttons (left to right): Reply, Repeat, Favorite, Delete.
|
- Four buttons (left to right): Reply, Repeat, Favorite, Delete.
|
||||||
|
|
||||||
**Mastodon interface**
|
## Mastodon interface
|
||||||
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:
|
If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile:
|
||||||
Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
|
Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks:
|
||||||
For more information on the Mastodon interface, please look here:
|
For more information on the Mastodon interface, please look here:
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Mix.Tasks.Pleroma.Common
|
alias Mix.Tasks.Pleroma.Common
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
@shortdoc "Manages Pleroma users"
|
@shortdoc "Manages Pleroma users"
|
||||||
|
@ -33,6 +32,10 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
|
|
||||||
mix pleroma.user rm NICKNAME
|
mix pleroma.user rm NICKNAME
|
||||||
|
|
||||||
|
## Delete the user's activities.
|
||||||
|
|
||||||
|
mix pleroma.user delete_activities NICKNAME
|
||||||
|
|
||||||
## Deactivate or activate the user's account.
|
## Deactivate or activate the user's account.
|
||||||
|
|
||||||
mix pleroma.user toggle_activated NICKNAME
|
mix pleroma.user toggle_activated NICKNAME
|
||||||
|
@ -202,7 +205,7 @@ def run(["unsubscribe", nickname]) do
|
||||||
{:ok, friends} = User.get_friends(user)
|
{:ok, friends} = User.get_friends(user)
|
||||||
|
|
||||||
Enum.each(friends, fn friend ->
|
Enum.each(friends, fn friend ->
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||||
User.unfollow(user, friend)
|
User.unfollow(user, friend)
|
||||||
|
@ -210,7 +213,7 @@ def run(["unsubscribe", nickname]) do
|
||||||
|
|
||||||
:timer.sleep(500)
|
:timer.sleep(500)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
if Enum.empty?(user.following) do
|
if Enum.empty?(user.following) do
|
||||||
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
|
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||||
|
@ -304,6 +307,18 @@ def run(["invite"]) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["delete_activities", nickname]) do
|
||||||
|
Common.start_pleroma()
|
||||||
|
|
||||||
|
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||||
|
User.delete_user_activities(user)
|
||||||
|
Mix.shell().info("User #{nickname} statuses deleted.")
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
Mix.shell().error("No local user #{nickname}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp set_moderator(user, value) do
|
defp set_moderator(user, value) do
|
||||||
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ def used_changeset(struct) do
|
||||||
|
|
||||||
def reset_password(token, data) do
|
def reset_password(token, data) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
%User{} = user <- Repo.get(User, token.user_id),
|
%User{} = user <- User.get_by_id(token.user_id),
|
||||||
{:ok, _user} <- User.reset_password(user, data),
|
{:ok, _user} <- User.reset_password(user, data),
|
||||||
{:ok, token} <- Repo.update(used_changeset(token)) do
|
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||||
{:ok, token}
|
{:ok, token}
|
||||||
|
|
|
@ -110,7 +110,6 @@ def start(_type, _args) do
|
||||||
worker(Pleroma.Web.Federator.RetryQueue, []),
|
worker(Pleroma.Web.Federator.RetryQueue, []),
|
||||||
worker(Pleroma.Stats, []),
|
worker(Pleroma.Stats, []),
|
||||||
worker(Pleroma.Web.Push, []),
|
worker(Pleroma.Web.Push, []),
|
||||||
worker(Pleroma.Jobs, []),
|
|
||||||
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary)
|
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary)
|
||||||
] ++
|
] ++
|
||||||
streamer_child() ++
|
streamer_child() ++
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Mailer do
|
||||||
use Swoosh.Mailer, otp_app: :pleroma
|
use Swoosh.Mailer, otp_app: :pleroma
|
||||||
|
|
||||||
def deliver_async(email, config \\ []) do
|
def deliver_async(email, config \\ []) do
|
||||||
Pleroma.Jobs.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
|
PleromaJobQueue.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform(:deliver_async, email, config), do: deliver(email, config)
|
def perform(:deliver_async, email, config), do: deliver(email, config)
|
||||||
|
|
|
@ -46,7 +46,7 @@ def from_string(<<_::integer-size(128)>> = flake), do: flake
|
||||||
|
|
||||||
def from_string(string) when is_binary(string) and byte_size(string) < 18 do
|
def from_string(string) when is_binary(string) and byte_size(string) < 18 do
|
||||||
case Integer.parse(string) do
|
case Integer.parse(string) do
|
||||||
{id, _} -> <<0::integer-size(64), id::integer-size(64)>>
|
{id, ""} -> <<0::integer-size(64), id::integer-size(64)>>
|
||||||
_ -> nil
|
_ -> nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,7 +38,6 @@ def init([ip, port]) do
|
||||||
defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.HTML
|
alias Pleroma.HTML
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
@ -111,7 +110,7 @@ def response("/main/all") do
|
||||||
end
|
end
|
||||||
|
|
||||||
def response("/notices/" <> id) do
|
def response("/notices/" <> id) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
true <- Visibility.is_public?(activity) do
|
true <- Visibility.is_public?(activity) do
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_activities_for_context(activity.data["context"])
|
ActivityPub.fetch_activities_for_context(activity.data["context"])
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Jobs do
|
|
||||||
@moduledoc """
|
|
||||||
A basic job queue
|
|
||||||
"""
|
|
||||||
use GenServer
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
def init(args) do
|
|
||||||
{:ok, args}
|
|
||||||
end
|
|
||||||
|
|
||||||
def start_link do
|
|
||||||
queues =
|
|
||||||
Pleroma.Config.get(Pleroma.Jobs)
|
|
||||||
|> Enum.map(fn {name, _} -> create_queue(name) end)
|
|
||||||
|> Enum.into(%{})
|
|
||||||
|
|
||||||
state = %{
|
|
||||||
queues: queues,
|
|
||||||
refs: %{}
|
|
||||||
}
|
|
||||||
|
|
||||||
GenServer.start_link(__MODULE__, state, name: __MODULE__)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_queue(name) do
|
|
||||||
{name, {:sets.new(), []}}
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Enqueues a job.
|
|
||||||
|
|
||||||
Returns `:ok`.
|
|
||||||
|
|
||||||
## Arguments
|
|
||||||
|
|
||||||
- `queue_name` - a queue name(must be specified in the config).
|
|
||||||
- `mod` - a worker module (must have `perform` function).
|
|
||||||
- `args` - a list of arguments for the `perform` function of the worker module.
|
|
||||||
- `priority` - a job priority (`0` by default).
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Enqueue `Module.perform/0` with `priority=1`:
|
|
||||||
|
|
||||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [])
|
|
||||||
:ok
|
|
||||||
|
|
||||||
Enqueue `Module.perform(:job_name)` with `priority=5`:
|
|
||||||
|
|
||||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:job_name], 5)
|
|
||||||
:ok
|
|
||||||
|
|
||||||
Enqueue `Module.perform(:another_job, data)` with `priority=1`:
|
|
||||||
|
|
||||||
iex> data = "foobar"
|
|
||||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:another_job, data])
|
|
||||||
:ok
|
|
||||||
|
|
||||||
Enqueue `Module.perform(:foobar_job, :foo, :bar, 42)` with `priority=1`:
|
|
||||||
|
|
||||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:foobar_job, :foo, :bar, 42])
|
|
||||||
:ok
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def enqueue(queue_name, mod, args, priority \\ 1)
|
|
||||||
|
|
||||||
if Mix.env() == :test do
|
|
||||||
def enqueue(_queue_name, mod, args, _priority) do
|
|
||||||
apply(mod, :perform, args)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
@spec enqueue(atom(), atom(), [any()], integer()) :: :ok
|
|
||||||
def enqueue(queue_name, mod, args, priority) do
|
|
||||||
GenServer.cast(__MODULE__, {:enqueue, queue_name, mod, args, priority})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_cast({:enqueue, queue_name, mod, args, priority}, state) do
|
|
||||||
{running_jobs, queue} = state[:queues][queue_name]
|
|
||||||
|
|
||||||
queue = enqueue_sorted(queue, {mod, args}, priority)
|
|
||||||
|
|
||||||
state =
|
|
||||||
state
|
|
||||||
|> update_queue(queue_name, {running_jobs, queue})
|
|
||||||
|> maybe_start_job(queue_name, running_jobs, queue)
|
|
||||||
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
|
|
||||||
queue_name = state.refs[ref]
|
|
||||||
|
|
||||||
{running_jobs, queue} = state[:queues][queue_name]
|
|
||||||
|
|
||||||
running_jobs = :sets.del_element(ref, running_jobs)
|
|
||||||
|
|
||||||
state =
|
|
||||||
state
|
|
||||||
|> remove_ref(ref)
|
|
||||||
|> update_queue(queue_name, {running_jobs, queue})
|
|
||||||
|> maybe_start_job(queue_name, running_jobs, queue)
|
|
||||||
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_start_job(state, queue_name, running_jobs, queue) do
|
|
||||||
if :sets.size(running_jobs) < Pleroma.Config.get([__MODULE__, queue_name, :max_jobs]) &&
|
|
||||||
queue != [] do
|
|
||||||
{{mod, args}, queue} = queue_pop(queue)
|
|
||||||
{:ok, pid} = Task.start(fn -> apply(mod, :perform, args) end)
|
|
||||||
mref = Process.monitor(pid)
|
|
||||||
|
|
||||||
state
|
|
||||||
|> add_ref(queue_name, mref)
|
|
||||||
|> update_queue(queue_name, {:sets.add_element(mref, running_jobs), queue})
|
|
||||||
else
|
|
||||||
state
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def enqueue_sorted(queue, element, priority) do
|
|
||||||
[%{item: element, priority: priority} | queue]
|
|
||||||
|> Enum.sort_by(fn %{priority: priority} -> priority end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def queue_pop([%{item: element} | queue]) do
|
|
||||||
{element, queue}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp add_ref(state, queue_name, ref) do
|
|
||||||
refs = Map.put(state[:refs], ref, queue_name)
|
|
||||||
Map.put(state, :refs, refs)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp remove_ref(state, ref) do
|
|
||||||
refs = Map.delete(state[:refs], ref)
|
|
||||||
Map.put(state, :refs, refs)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp update_queue(state, queue_name, data) do
|
|
||||||
queues = Map.put(state[:queues], queue_name, data)
|
|
||||||
Map.put(state, :queues, queues)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -80,7 +80,7 @@ def get_lists_from_activity(%Activity{actor: ap_id}) do
|
||||||
|
|
||||||
# Get lists to which the account belongs.
|
# Get lists to which the account belongs.
|
||||||
def get_lists_account_belongs(%User{} = owner, account_id) do
|
def get_lists_account_belongs(%User{} = owner, account_id) do
|
||||||
user = Repo.get(User, account_id)
|
user = User.get_by_id(account_id)
|
||||||
|
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Plugs.UserFetcherPlug do
|
defmodule Pleroma.Plugs.UserFetcherPlug do
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
|
||||||
def init(options) do
|
def init(options) do
|
||||||
|
@ -14,26 +12,10 @@ def init(options) do
|
||||||
|
|
||||||
def call(conn, _options) do
|
def call(conn, _options) do
|
||||||
with %{auth_credentials: %{username: username}} <- conn.assigns,
|
with %{auth_credentials: %{username: username}} <- conn.assigns,
|
||||||
{:ok, %User{} = user} <- user_fetcher(username) do
|
%User{} = user <- User.get_by_nickname_or_email(username) do
|
||||||
conn
|
assign(conn, :auth_user, user)
|
||||||
|> assign(:auth_user, user)
|
|
||||||
else
|
else
|
||||||
_ -> conn
|
_ -> conn
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp user_fetcher(username_or_email) do
|
|
||||||
{
|
|
||||||
:ok,
|
|
||||||
cond do
|
|
||||||
# First, try logging in as if it was a name
|
|
||||||
user = Repo.get_by(User, %{nickname: username_or_email}) ->
|
|
||||||
user
|
|
||||||
|
|
||||||
# If we get nil, we try using it as an email
|
|
||||||
user = Repo.get_by(User, %{email: username_or_email}) ->
|
|
||||||
user
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1088,28 +1088,27 @@ def delete(%User{} = user) do
|
||||||
# Remove all relationships
|
# Remove all relationships
|
||||||
{:ok, followers} = User.get_followers(user)
|
{:ok, followers} = User.get_followers(user)
|
||||||
|
|
||||||
followers
|
Enum.each(followers, fn follower -> User.unfollow(follower, user) end)
|
||||||
|> Enum.each(fn follower -> User.unfollow(follower, user) end)
|
|
||||||
|
|
||||||
{:ok, friends} = User.get_friends(user)
|
{:ok, friends} = User.get_friends(user)
|
||||||
|
|
||||||
friends
|
Enum.each(friends, fn followed -> User.unfollow(user, followed) end)
|
||||||
|> Enum.each(fn followed -> User.unfollow(user, followed) end)
|
|
||||||
|
|
||||||
query =
|
delete_user_activities(user)
|
||||||
from(a in Activity, where: a.actor == ^user.ap_id)
|
end
|
||||||
|> Activity.with_preloaded_object()
|
|
||||||
|
|
||||||
Repo.all(query)
|
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||||
|> Enum.each(fn activity ->
|
Activity
|
||||||
case activity.data["type"] do
|
|> where(actor: ^ap_id)
|
||||||
"Create" ->
|
|> Activity.with_preloaded_object()
|
||||||
ActivityPub.delete(Object.normalize(activity))
|
|> Repo.all()
|
||||||
|
|> Enum.each(fn
|
||||||
|
%{data: %{"type" => "Create"}} = activity ->
|
||||||
|
activity |> Object.normalize() |> ActivityPub.delete()
|
||||||
|
|
||||||
# TODO: Do something with likes, follows, repeats.
|
# TODO: Do something with likes, follows, repeats.
|
||||||
_ ->
|
_ ->
|
||||||
"Doing nothing"
|
"Doing nothing"
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
|
@ -1231,8 +1230,8 @@ def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
|
||||||
# this is because we have synchronous follow APIs and need to simulate them
|
# this is because we have synchronous follow APIs and need to simulate them
|
||||||
# with an async handshake
|
# with an async handshake
|
||||||
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
||||||
with %User{} = a <- Repo.get(User, a.id),
|
with %User{} = a <- User.get_by_id(a.id),
|
||||||
%User{} = b <- Repo.get(User, b.id) do
|
%User{} = b <- User.get_by_id(b.id) do
|
||||||
{:ok, a, b}
|
{:ok, a, b}
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
@ -1242,8 +1241,8 @@ def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
||||||
|
|
||||||
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
|
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
|
||||||
with :ok <- :timer.sleep(timeout),
|
with :ok <- :timer.sleep(timeout),
|
||||||
%User{} = a <- Repo.get(User, a.id),
|
%User{} = a <- User.get_by_id(a.id),
|
||||||
%User{} = b <- Repo.get(User, b.id) do
|
%User{} = b <- User.get_by_id(b.id) do
|
||||||
{:ok, a, b}
|
{:ok, a, b}
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
|
|
@ -354,7 +354,7 @@ def update_follow_state(
|
||||||
[state, actor, object]
|
[state, actor, object]
|
||||||
)
|
)
|
||||||
|
|
||||||
activity = Repo.get(Activity, activity.id)
|
activity = Activity.get_by_id(activity.id)
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
rescue
|
rescue
|
||||||
e ->
|
e ->
|
||||||
|
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.Web.UserSocket do
|
||||||
def connect(%{"token" => token}, socket) do
|
def connect(%{"token" => token}, socket) do
|
||||||
with true <- Pleroma.Config.get([:chat, :enabled]),
|
with true <- Pleroma.Config.get([:chat, :enabled]),
|
||||||
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
|
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
|
||||||
%User{} = user <- Pleroma.Repo.get(User, user_id) do
|
%User{} = user <- Pleroma.User.get_by_id(user_id) do
|
||||||
{:ok, assign(socket, :user_name, user.nickname)}
|
{:ok, assign(socket, :user_name, user.nickname)}
|
||||||
else
|
else
|
||||||
_e -> :error
|
_e -> :error
|
||||||
|
|
|
@ -31,7 +31,7 @@ def get_by_id_or_ap_id(id) do
|
||||||
def get_replied_to_activity(""), do: nil
|
def get_replied_to_activity(""), do: nil
|
||||||
|
|
||||||
def get_replied_to_activity(id) when not is_nil(id) do
|
def get_replied_to_activity(id) when not is_nil(id) do
|
||||||
Repo.get(Activity, id)
|
Activity.get_by_id(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_replied_to_activity(_), do: nil
|
def get_replied_to_activity(_), do: nil
|
||||||
|
@ -275,7 +275,7 @@ defp shortname(name) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_current_password(user, password) do
|
def confirm_current_password(user, password) do
|
||||||
with %User{local: true} = db_user <- Repo.get(User, user.id),
|
with %User{local: true} = db_user <- User.get_by_id(user.id),
|
||||||
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
||||||
{:ok, db_user}
|
{:ok, db_user}
|
||||||
else
|
else
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
defmodule Pleroma.Web.ControllerHelper do
|
defmodule Pleroma.Web.ControllerHelper do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
||||||
|
@falsy_param_values [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"]
|
||||||
|
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
|
||||||
|
def truthy_param?(value), do: value not in @falsy_param_values
|
||||||
|
|
||||||
def oauth_scopes(params, default) do
|
def oauth_scopes(params, default) do
|
||||||
# Note: `scopes` is used by Mastodon — supporting it but sticking to
|
# Note: `scopes` is used by Mastodon — supporting it but sticking to
|
||||||
# OAuth's standard `scope` wherever we control it
|
# OAuth's standard `scope` wherever we control it
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Federator do
|
defmodule Pleroma.Web.Federator do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Jobs
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
|
@ -31,39 +30,39 @@ def init do
|
||||||
# Client API
|
# Client API
|
||||||
|
|
||||||
def incoming_doc(doc) do
|
def incoming_doc(doc) do
|
||||||
Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc])
|
PleromaJobQueue.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc])
|
||||||
end
|
end
|
||||||
|
|
||||||
def incoming_ap_doc(params) do
|
def incoming_ap_doc(params) do
|
||||||
Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params])
|
PleromaJobQueue.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params])
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish(activity, priority \\ 1) do
|
def publish(activity, priority \\ 1) do
|
||||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_single_ap(params) do
|
def publish_single_ap(params) do
|
||||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params])
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params])
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_single_websub(websub) do
|
def publish_single_websub(websub) do
|
||||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub])
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub])
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_websub(websub) do
|
def verify_websub(websub) do
|
||||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_subscription(sub) do
|
def request_subscription(sub) do
|
||||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub])
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub])
|
||||||
end
|
end
|
||||||
|
|
||||||
def refresh_subscriptions do
|
def refresh_subscriptions do
|
||||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
|
||||||
end
|
end
|
||||||
|
|
||||||
def publish_single_salmon(params) do
|
def publish_single_salmon(params) do
|
||||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
|
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Job Worker Callbacks
|
# Job Worker Callbacks
|
||||||
|
|
|
@ -285,7 +285,7 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||||
with %User{} = user <- Repo.get(User, params["id"]) do
|
with %User{} = user <- User.get_by_id(params["id"]) do
|
||||||
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
|
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -319,7 +319,7 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
conn
|
conn
|
||||||
|> put_view(StatusView)
|
|> put_view(StatusView)
|
||||||
|
@ -328,7 +328,7 @@ def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
activities <-
|
activities <-
|
||||||
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
|
||||||
"blocking_user" => user,
|
"blocking_user" => user,
|
||||||
|
@ -460,7 +460,7 @@ def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
%User{} = user <- User.get_by_nickname(user.nickname),
|
%User{} = user <- User.get_by_nickname(user.nickname),
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
{:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
|
{:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
|
||||||
|
@ -471,7 +471,7 @@ def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
%User{} = user <- User.get_by_nickname(user.nickname),
|
%User{} = user <- User.get_by_nickname(user.nickname),
|
||||||
true <- Visibility.visible_for_user?(activity, user),
|
true <- Visibility.visible_for_user?(activity, user),
|
||||||
{:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
|
{:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
|
||||||
|
@ -593,7 +593,7 @@ def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def favourited_by(conn, %{"id" => id}) do
|
def favourited_by(conn, %{"id" => id}) do
|
||||||
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
|
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Activity.get_by_id(id) do
|
||||||
q = from(u in User, where: u.ap_id in ^likes)
|
q = from(u in User, where: u.ap_id in ^likes)
|
||||||
users = Repo.all(q)
|
users = Repo.all(q)
|
||||||
|
|
||||||
|
@ -606,7 +606,7 @@ def favourited_by(conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def reblogged_by(conn, %{"id" => id}) do
|
def reblogged_by(conn, %{"id" => id}) do
|
||||||
with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do
|
with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Activity.get_by_id(id) do
|
||||||
q = from(u in User, where: u.ap_id in ^announces)
|
q = from(u in User, where: u.ap_id in ^announces)
|
||||||
users = Repo.all(q)
|
users = Repo.all(q)
|
||||||
|
|
||||||
|
@ -657,7 +657,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
with %User{} = user <- Repo.get(User, id),
|
with %User{} = user <- User.get_by_id(id),
|
||||||
followers <- MastodonAPI.get_followers(user, params) do
|
followers <- MastodonAPI.get_followers(user, params) do
|
||||||
followers =
|
followers =
|
||||||
cond do
|
cond do
|
||||||
|
@ -674,7 +674,7 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
with %User{} = user <- Repo.get(User, id),
|
with %User{} = user <- User.get_by_id(id),
|
||||||
followers <- MastodonAPI.get_friends(user, params) do
|
followers <- MastodonAPI.get_friends(user, params) do
|
||||||
followers =
|
followers =
|
||||||
cond do
|
cond do
|
||||||
|
@ -699,7 +699,7 @@ def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||||
with %User{} = follower <- Repo.get(User, id),
|
with %User{} = follower <- User.get_by_id(id),
|
||||||
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -713,7 +713,7 @@ def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||||
with %User{} = follower <- Repo.get(User, id),
|
with %User{} = follower <- User.get_by_id(id),
|
||||||
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -727,7 +727,7 @@ def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) d
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
with %User{} = followed <- Repo.get(User, id),
|
with %User{} = followed <- User.get_by_id(id),
|
||||||
false <- User.following?(follower, followed),
|
false <- User.following?(follower, followed),
|
||||||
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
|
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|
@ -755,7 +755,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||||
with %User{} = followed <- Repo.get_by(User, nickname: uri),
|
with %User{} = followed <- User.get_by_nickname(uri),
|
||||||
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
|
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -769,7 +769,7 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
with %User{} = followed <- Repo.get(User, id),
|
with %User{} = followed <- User.get_by_id(id),
|
||||||
{:ok, follower} <- CommonAPI.unfollow(follower, followed) do
|
{:ok, follower} <- CommonAPI.unfollow(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -778,7 +778,7 @@ def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
with %User{} = muted <- Repo.get(User, id),
|
with %User{} = muted <- User.get_by_id(id),
|
||||||
{:ok, muter} <- User.mute(muter, muted) do
|
{:ok, muter} <- User.mute(muter, muted) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -792,7 +792,7 @@ def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||||
with %User{} = muted <- Repo.get(User, id),
|
with %User{} = muted <- User.get_by_id(id),
|
||||||
{:ok, muter} <- User.unmute(muter, muted) do
|
{:ok, muter} <- User.unmute(muter, muted) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|
@ -813,7 +813,7 @@ def mutes(%{assigns: %{user: user}} = conn, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- Repo.get(User, id),
|
with %User{} = blocked <- User.get_by_id(id),
|
||||||
{:ok, blocker} <- User.block(blocker, blocked),
|
{:ok, blocker} <- User.block(blocker, blocked),
|
||||||
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
||||||
conn
|
conn
|
||||||
|
@ -828,7 +828,7 @@ def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||||
with %User{} = blocked <- Repo.get(User, id),
|
with %User{} = blocked <- User.get_by_id(id),
|
||||||
{:ok, blocker} <- User.unblock(blocker, blocked),
|
{:ok, blocker} <- User.unblock(blocker, blocked),
|
||||||
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
|
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
|
||||||
conn
|
conn
|
||||||
|
@ -966,7 +966,7 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def bookmarks(%{assigns: %{user: user}} = conn, _) do
|
def bookmarks(%{assigns: %{user: user}} = conn, _) do
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
user.bookmarks
|
user.bookmarks
|
||||||
|
@ -1023,7 +1023,7 @@ def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" =>
|
||||||
accounts
|
accounts
|
||||||
|> Enum.each(fn account_id ->
|
|> Enum.each(fn account_id ->
|
||||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
%User{} = followed <- Repo.get(User, account_id) do
|
%User{} = followed <- User.get_by_id(account_id) do
|
||||||
Pleroma.List.follow(list, followed)
|
Pleroma.List.follow(list, followed)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
@ -1035,7 +1035,7 @@ def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_id
|
||||||
accounts
|
accounts
|
||||||
|> Enum.each(fn account_id ->
|
|> Enum.each(fn account_id ->
|
||||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||||
%User{} = followed <- Repo.get(Pleroma.User, account_id) do
|
%User{} = followed <- Pleroma.User.get_by_id(account_id) do
|
||||||
Pleroma.List.unfollow(list, followed)
|
Pleroma.List.unfollow(list, followed)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
@ -1249,16 +1249,22 @@ defp get_user_flavour(_) do
|
||||||
"glitch"
|
"glitch"
|
||||||
end
|
end
|
||||||
|
|
||||||
def login(conn, %{"code" => code}) do
|
def login(%{assigns: %{user: %User{}}} = conn, _params) do
|
||||||
|
redirect(conn, to: local_mastodon_root_path(conn))
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Local Mastodon FE login init action"
|
||||||
|
def login(conn, %{"code" => auth_token}) do
|
||||||
with {:ok, app} <- get_or_make_app(),
|
with {:ok, app} <- get_or_make_app(),
|
||||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id),
|
%Authorization{} = auth <- Repo.get_by(Authorization, token: auth_token, app_id: app.id),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||||
conn
|
conn
|
||||||
|> put_session(:oauth_token, token.token)
|
|> put_session(:oauth_token, token.token)
|
||||||
|> redirect(to: "/web/getting-started")
|
|> redirect(to: local_mastodon_root_path(conn))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc "Local Mastodon FE callback action"
|
||||||
def login(conn, _) do
|
def login(conn, _) do
|
||||||
with {:ok, app} <- get_or_make_app() do
|
with {:ok, app} <- get_or_make_app() do
|
||||||
path =
|
path =
|
||||||
|
@ -1276,6 +1282,8 @@ def login(conn, _) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp local_mastodon_root_path(conn), do: mastodon_api_path(conn, :index, ["getting-started"])
|
||||||
|
|
||||||
defp get_or_make_app do
|
defp get_or_make_app do
|
||||||
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
|
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
|
||||||
scopes = ["read", "write", "follow", "push"]
|
scopes = ["read", "write", "follow", "push"]
|
||||||
|
@ -1312,7 +1320,7 @@ def logout(conn, _) do
|
||||||
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
Logger.debug("Unimplemented, returning unmodified relationship")
|
Logger.debug("Unimplemented, returning unmodified relationship")
|
||||||
|
|
||||||
with %User{} = target <- Repo.get(User, id) do
|
with %User{} = target <- User.get_by_id(id) do
|
||||||
conn
|
conn
|
||||||
|> put_view(AccountView)
|
|> put_view(AccountView)
|
||||||
|> render("relationship.json", %{user: user, target: target})
|
|> render("relationship.json", %{user: user, target: target})
|
||||||
|
@ -1454,7 +1462,7 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
|
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, status_id),
|
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
data =
|
data =
|
||||||
StatusView.render(
|
StatusView.render(
|
||||||
|
|
|
@ -90,7 +90,7 @@ defp allow_request(stream, nil) when stream in @anonymous_streams do
|
||||||
# Authenticated streams.
|
# Authenticated streams.
|
||||||
defp allow_request(stream, {"access_token", access_token}) when stream in @streams do
|
defp allow_request(stream, {"access_token", access_token}) when stream in @streams do
|
||||||
with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token),
|
with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token),
|
||||||
user = %User{} <- Repo.get(User, user_id) do
|
user = %User{} <- User.get_by_id(user_id) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
_ -> {:error, 403}
|
_ -> {:error, 403}
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Auth.Authenticator
|
alias Pleroma.Web.Auth.Authenticator
|
||||||
|
alias Pleroma.Web.ControllerHelper
|
||||||
alias Pleroma.Web.OAuth.App
|
alias Pleroma.Web.OAuth.App
|
||||||
alias Pleroma.Web.OAuth.Authorization
|
alias Pleroma.Web.OAuth.Authorization
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
|
@ -19,7 +20,28 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||||
|
|
||||||
def authorize(conn, params) do
|
def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do
|
||||||
|
if ControllerHelper.truthy_param?(params["force_login"]) do
|
||||||
|
do_authorize(conn, params)
|
||||||
|
else
|
||||||
|
redirect_uri =
|
||||||
|
if is_binary(params["redirect_uri"]) do
|
||||||
|
params["redirect_uri"]
|
||||||
|
else
|
||||||
|
app = Repo.preload(token, :app).app
|
||||||
|
|
||||||
|
app.redirect_uris
|
||||||
|
|> String.split()
|
||||||
|
|> Enum.at(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect(conn, external: redirect_uri(conn, redirect_uri))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize(conn, params), do: do_authorize(conn, params)
|
||||||
|
|
||||||
|
defp do_authorize(conn, params) do
|
||||||
app = Repo.get_by(App, client_id: params["client_id"])
|
app = Repo.get_by(App, client_id: params["client_id"])
|
||||||
available_scopes = (app && app.scopes) || []
|
available_scopes = (app && app.scopes) || []
|
||||||
scopes = oauth_scopes(params, nil) || available_scopes
|
scopes = oauth_scopes(params, nil) || available_scopes
|
||||||
|
@ -51,13 +73,7 @@ def create_authorization(conn, %{
|
||||||
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
|
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
|
||||||
redirect_uri =
|
redirect_uri = redirect_uri(conn, redirect_uri)
|
||||||
if redirect_uri == "." do
|
|
||||||
# Special case: Local MastodonFE
|
|
||||||
mastodon_api_url(conn, :login)
|
|
||||||
else
|
|
||||||
redirect_uri
|
|
||||||
end
|
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
|
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
|
||||||
|
@ -108,7 +124,7 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||||
fixed_token = fix_padding(params["code"]),
|
fixed_token = fix_padding(params["code"]),
|
||||||
%Authorization{} = auth <-
|
%Authorization{} = auth <-
|
||||||
Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
||||||
%User{} = user <- Repo.get(User, auth.user_id),
|
%User{} = user <- User.get_by_id(auth.user_id),
|
||||||
{:ok, token} <- Token.exchange_token(app, auth),
|
{:ok, token} <- Token.exchange_token(app, auth),
|
||||||
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
|
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
|
||||||
response = %{
|
response = %{
|
||||||
|
@ -221,4 +237,9 @@ defp get_app_from_request(conn, params) do
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Special case: Local MastodonFE
|
||||||
|
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
||||||
|
|
||||||
|
defp redirect_uri(_conn, redirect_uri), do: redirect_uri
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
||||||
def exchange_token(app, auth) do
|
def exchange_token(app, auth) do
|
||||||
with {:ok, auth} <- Authorization.use_token(auth),
|
with {:ok, auth} <- Authorization.use_token(auth),
|
||||||
true <- auth.app_id == app.id do
|
true <- auth.app_id == app.id do
|
||||||
create_token(app, Repo.get(User, auth.user_id), auth.scopes)
|
create_token(app, User.get_by_id(auth.user_id), auth.scopes)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
defmodule Pleroma.Web.Router do
|
defmodule Pleroma.Web.Router do
|
||||||
use Pleroma.Web, :router
|
use Pleroma.Web, :router
|
||||||
|
|
||||||
|
pipeline :oauth do
|
||||||
|
plug(:fetch_session)
|
||||||
|
plug(Pleroma.Plugs.OAuthPlug)
|
||||||
|
end
|
||||||
|
|
||||||
pipeline :api do
|
pipeline :api do
|
||||||
plug(:accepts, ["json"])
|
plug(:accepts, ["json"])
|
||||||
plug(:fetch_session)
|
plug(:fetch_session)
|
||||||
|
@ -105,10 +110,6 @@ defmodule Pleroma.Web.Router do
|
||||||
plug(:accepts, ["json", "xml"])
|
plug(:accepts, ["json", "xml"])
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :oauth do
|
|
||||||
plug(:accepts, ["html", "json"])
|
|
||||||
end
|
|
||||||
|
|
||||||
pipeline :pleroma_api do
|
pipeline :pleroma_api do
|
||||||
plug(:accepts, ["html", "json"])
|
plug(:accepts, ["html", "json"])
|
||||||
end
|
end
|
||||||
|
@ -200,7 +201,11 @@ defmodule Pleroma.Web.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/oauth", Pleroma.Web.OAuth do
|
scope "/oauth", Pleroma.Web.OAuth do
|
||||||
get("/authorize", OAuthController, :authorize)
|
scope [] do
|
||||||
|
pipe_through(:oauth)
|
||||||
|
get("/authorize", OAuthController, :authorize)
|
||||||
|
end
|
||||||
|
|
||||||
post("/authorize", OAuthController, :create_authorization)
|
post("/authorize", OAuthController, :create_authorization)
|
||||||
post("/token", OAuthController, :token_exchange)
|
post("/token", OAuthController, :token_exchange)
|
||||||
post("/revoke", OAuthController, :token_revoke)
|
post("/revoke", OAuthController, :token_revoke)
|
||||||
|
@ -218,6 +223,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/accounts/search", MastodonAPIController, :account_search)
|
get("/accounts/search", MastodonAPIController, :account_search)
|
||||||
|
|
||||||
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
|
get("/accounts/:id/lists", MastodonAPIController, :account_lists)
|
||||||
|
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
|
||||||
|
|
||||||
get("/follow_requests", MastodonAPIController, :follow_requests)
|
get("/follow_requests", MastodonAPIController, :follow_requests)
|
||||||
get("/blocks", MastodonAPIController, :blocks)
|
get("/blocks", MastodonAPIController, :blocks)
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.Streamer do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
@ -82,7 +81,7 @@ def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
|
||||||
_ ->
|
_ ->
|
||||||
Pleroma.List.get_lists_from_activity(item)
|
Pleroma.List.get_lists_from_activity(item)
|
||||||
|> Enum.filter(fn list ->
|
|> Enum.filter(fn list ->
|
||||||
owner = Repo.get(User, list.user_id)
|
owner = User.get_by_id(list.user_id)
|
||||||
|
|
||||||
Visibility.visible_for_user?(item, owner)
|
Visibility.visible_for_user?(item, owner)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Emoji
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.PasswordResetToken
|
alias Pleroma.PasswordResetToken
|
||||||
|
@ -21,7 +22,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
|
|
||||||
def show_password_reset(conn, %{"token" => token}) do
|
def show_password_reset(conn, %{"token" => token}) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
%User{} = user <- Repo.get(User, token.user_id) do
|
%User{} = user <- User.get_by_id(token.user_id) do
|
||||||
render(conn, "password_reset.html", %{
|
render(conn, "password_reset.html", %{
|
||||||
token: token,
|
token: token,
|
||||||
user: user
|
user: user
|
||||||
|
@ -73,36 +74,52 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
|
def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
|
||||||
{err, followee} = OStatus.find_or_make_user(acct)
|
if is_status?(acct) do
|
||||||
avatar = User.avatar_url(followee)
|
{:ok, object} = ActivityPub.fetch_object_from_id(acct)
|
||||||
name = followee.nickname
|
%Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
|
||||||
id = followee.id
|
redirect(conn, to: "/notice/#{activity_id}")
|
||||||
|
|
||||||
if !!user do
|
|
||||||
conn
|
|
||||||
|> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
|
|
||||||
else
|
else
|
||||||
conn
|
{err, followee} = OStatus.find_or_make_user(acct)
|
||||||
|> render("follow_login.html", %{
|
avatar = User.avatar_url(followee)
|
||||||
error: false,
|
name = followee.nickname
|
||||||
acct: acct,
|
id = followee.id
|
||||||
avatar: avatar,
|
|
||||||
name: name,
|
if !!user do
|
||||||
id: id
|
conn
|
||||||
})
|
|> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> render("follow_login.html", %{
|
||||||
|
error: false,
|
||||||
|
acct: acct,
|
||||||
|
avatar: avatar,
|
||||||
|
name: name,
|
||||||
|
id: id
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp is_status?(acct) do
|
||||||
|
case ActivityPub.fetch_and_contain_remote_object_from_id(acct) do
|
||||||
|
{:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] ->
|
||||||
|
true
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_remote_follow(conn, %{
|
def do_remote_follow(conn, %{
|
||||||
"authorization" => %{"name" => username, "password" => password, "id" => id}
|
"authorization" => %{"name" => username, "password" => password, "id" => id}
|
||||||
}) do
|
}) do
|
||||||
followee = Repo.get(User, id)
|
followee = User.get_by_id(id)
|
||||||
avatar = User.avatar_url(followee)
|
avatar = User.avatar_url(followee)
|
||||||
name = followee.nickname
|
name = followee.nickname
|
||||||
|
|
||||||
with %User{} = user <- User.get_cached_by_nickname(username),
|
with %User{} = user <- User.get_cached_by_nickname(username),
|
||||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||||
%User{} = _followed <- Repo.get(User, id),
|
%User{} = _followed <- User.get_by_id(id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{:ok, follower} <- User.follow(user, followee),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||||
conn
|
conn
|
||||||
|
@ -124,7 +141,7 @@ def do_remote_follow(conn, %{
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
||||||
with %User{} = followee <- Repo.get(User, id),
|
with %User{} = followee <- User.get_by_id(id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{:ok, follower} <- User.follow(user, followee),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -20,7 +20,7 @@ def create_status(%User{} = user, %{"status" => _} = data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(%User{} = user, id) do
|
def delete(%User{} = user, id) do
|
||||||
with %Activity{data: %{"type" => _type}} <- Repo.get(Activity, id),
|
with %Activity{data: %{"type" => _type}} <- Activity.get_by_id(id),
|
||||||
{:ok, activity} <- CommonAPI.delete(id, user) do
|
{:ok, activity} <- CommonAPI.delete(id, user) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
@ -227,12 +227,9 @@ def get_user(user \\ nil, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
%{"screen_name" => nickname} ->
|
%{"screen_name" => nickname} ->
|
||||||
case target = Repo.get_by(User, nickname: nickname) do
|
case User.get_by_nickname(nickname) do
|
||||||
nil ->
|
nil -> {:error, "No user with such screen_name"}
|
||||||
{:error, "No user with such screen_name"}
|
target -> {:ok, target}
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:ok, target}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -270,7 +270,7 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
conn
|
conn
|
||||||
|> put_view(ActivityView)
|
|> put_view(ActivityView)
|
||||||
|
@ -342,7 +342,7 @@ def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id_or_ap_id(id) do
|
def get_by_id_or_ap_id(id) do
|
||||||
activity = Repo.get(Activity, id) || Activity.get_create_by_object_ap_id(id)
|
activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
|
||||||
|
|
||||||
if activity.data["type"] == "Create" do
|
if activity.data["type"] == "Create" do
|
||||||
activity
|
activity
|
||||||
|
@ -434,7 +434,7 @@ def password_reset(conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
||||||
with %User{} = user <- Repo.get(User, uid),
|
with %User{} = user <- User.get_by_id(uid),
|
||||||
true <- user.local,
|
true <- user.local,
|
||||||
true <- user.info.confirmation_pending,
|
true <- user.info.confirmation_pending,
|
||||||
true <- user.info.confirmation_token == token,
|
true <- user.info.confirmation_token == token,
|
||||||
|
@ -587,7 +587,7 @@ def friend_requests(conn, params) do
|
||||||
|
|
||||||
def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
with followed <- conn.assigns[:user],
|
with followed <- conn.assigns[:user],
|
||||||
%User{} = follower <- Repo.get(User, uid),
|
%User{} = follower <- User.get_by_id(uid),
|
||||||
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(UserView)
|
|> put_view(UserView)
|
||||||
|
@ -599,7 +599,7 @@ def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
|
|
||||||
def deny_friend_request(conn, %{"user_id" => uid} = _params) do
|
def deny_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
with followed <- conn.assigns[:user],
|
with followed <- conn.assigns[:user],
|
||||||
%User{} = follower <- Repo.get(User, uid),
|
%User{} = follower <- User.get_by_id(uid),
|
||||||
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(UserView)
|
|> put_view(UserView)
|
||||||
|
|
3
mix.exs
3
mix.exs
|
@ -93,7 +93,8 @@ defp deps do
|
||||||
{:timex, "~> 3.5"},
|
{:timex, "~> 3.5"},
|
||||||
{:auto_linker,
|
{:auto_linker,
|
||||||
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
git: "https://git.pleroma.social/pleroma/auto_linker.git",
|
||||||
ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"}
|
ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"},
|
||||||
|
{:pleroma_job_queue, "~> 0.2.0"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
1
mix.lock
1
mix.lock
|
@ -50,6 +50,7 @@
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [: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"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [: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"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
|
||||||
|
"pleroma_job_queue": {:hex, :pleroma_job_queue, "0.2.0", "879e660aa1cebe8dc6f0aaaa6aa48b4875e89cd961d4a585fd128e0773b31a18", [:mix], [], "hexpm"},
|
||||||
"plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
|
"plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
|
||||||
|
|
BIN
priv/static/images/pleroma-fox-tan-smol.png
Normal file
BIN
priv/static/images/pleroma-fox-tan-smol.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 854 KiB |
BIN
priv/static/images/pleroma-fox-tan.png
Normal file
BIN
priv/static/images/pleroma-fox-tan.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 MiB |
BIN
priv/static/images/pleroma-tan.png
Normal file
BIN
priv/static/images/pleroma-tan.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 533 KiB |
306
test/fixtures/httpoison_mock/emelie.atom
vendored
Normal file
306
test/fixtures/httpoison_mock/emelie.atom
vendored
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
|
||||||
|
<id>https://mastodon.social/users/emelie.atom</id>
|
||||||
|
<title>emelie 🎨</title>
|
||||||
|
<subtitle>23 / #Sweden / #Artist / #Equestrian / #GameDev
|
||||||
|
|
||||||
|
If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰</subtitle>
|
||||||
|
<updated>2019-02-04T20:22:19Z</updated>
|
||||||
|
<logo>https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png</logo>
|
||||||
|
<author>
|
||||||
|
<id>https://mastodon.social/users/emelie</id>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||||
|
<uri>https://mastodon.social/users/emelie</uri>
|
||||||
|
<name>emelie</name>
|
||||||
|
<email>emelie@mastodon.social</email>
|
||||||
|
<summary type="html"><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></summary>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie"/>
|
||||||
|
<link rel="avatar" type="image/png" media:width="120" media:height="120" href="https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png"/>
|
||||||
|
<link rel="header" type="image/png" media:width="700" media:height="335" href="https://files.mastodon.social/accounts/headers/000/015/657/original/847f331f3dd9e38b.png"/>
|
||||||
|
<poco:preferredUsername>emelie</poco:preferredUsername>
|
||||||
|
<poco:displayName>emelie 🎨</poco:displayName>
|
||||||
|
<poco:note>23 / #Sweden / #Artist / #Equestrian / #GameDev
|
||||||
|
|
||||||
|
If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰</poco:note>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
</author>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie.atom"/>
|
||||||
|
<link rel="hub" href="https://mastodon.social/api/push"/>
|
||||||
|
<link rel="salmon" href="https://mastodon.social/api/salmon/15657"/>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101850331907006641</id>
|
||||||
|
<published>2019-04-01T09:58:50Z</published>
|
||||||
|
<updated>2019-04-01T09:58:50Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101850331907006641"/>
|
||||||
|
<content type="html" xml:lang="en"><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></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101850331907006641"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17854598.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94383214:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849626603073336</id>
|
||||||
|
<published>2019-04-01T06:59:28Z</published>
|
||||||
|
<updated>2019-04-01T06:59:28Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849626603073336"/>
|
||||||
|
<content type="html" xml:lang="sv"><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></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://mastodon.social/users/Fergant"/>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849626603073336"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852590.atom"/>
|
||||||
|
<thr:in-reply-to ref="https://mastodon.social/users/Fergant/statuses/101849606513357387" href="https://mastodon.social/@Fergant/101849606513357387"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94362529:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849580030237068</id>
|
||||||
|
<published>2019-04-01T06:47:37Z</published>
|
||||||
|
<updated>2019-04-01T06:47:37Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849580030237068"/>
|
||||||
|
<content type="html" xml:lang="en"><p>What&apos;s you people&apos;s favourite fantasy books? Give me some hot tips 🌞</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849580030237068"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852464.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94362529:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849550599949363</id>
|
||||||
|
<published>2019-04-01T06:40:08Z</published>
|
||||||
|
<updated>2019-04-01T06:40:08Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849550599949363"/>
|
||||||
|
<content type="html" xml:lang="en"><p>Stick them legs out 💃 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="mastocats"/>
|
||||||
|
<link rel="enclosure" type="image/jpeg" length="516384" href="https://files.mastodon.social/media_attachments/files/013/051/707/original/125a310abe9a34aa.jpeg"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849550599949363"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852407.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94361580:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849191533152720</id>
|
||||||
|
<published>2019-04-01T05:08:49Z</published>
|
||||||
|
<updated>2019-04-01T05:08:49Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849191533152720"/>
|
||||||
|
<content type="html" xml:lang="en"><p>long 🐱 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="mastocats"/>
|
||||||
|
<link rel="enclosure" type="image/jpeg" length="305208" href="https://files.mastodon.social/media_attachments/files/013/049/940/original/f2dbbfe7de3a17d2.jpeg"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849191533152720"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17851663.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94351141:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849165031453009</id>
|
||||||
|
<published>2019-04-01T05:02:05Z</published>
|
||||||
|
<updated>2019-04-01T05:02:05Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849165031453009"/>
|
||||||
|
<content type="html" xml:lang="en"><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></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="mastocats"/>
|
||||||
|
<link rel="enclosure" type="video/mp4" length="9838915" href="https://files.mastodon.social/media_attachments/files/013/049/816/original/e7831178a5e0d6d4.mp4"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849165031453009"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17851558.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94350309:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101846512530748693</id>
|
||||||
|
<published>2019-03-31T17:47:31Z</published>
|
||||||
|
<updated>2019-03-31T17:47:31Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101846512530748693"/>
|
||||||
|
<content type="html" xml:lang="en"><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></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="equestrian"/>
|
||||||
|
<category term="mastohorses"/>
|
||||||
|
<link rel="enclosure" type="image/jpeg" length="461632" href="https://files.mastodon.social/media_attachments/files/013/033/387/original/301e8ab668cd61d2.jpeg"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101846512530748693"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17842424.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94256415:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101846181093805500</id>
|
||||||
|
<published>2019-03-31T16:23:14Z</published>
|
||||||
|
<updated>2019-03-31T16:23:14Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101846181093805500"/>
|
||||||
|
<content type="html" xml:lang="en"><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></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="mastocats"/>
|
||||||
|
<link rel="enclosure" type="image/jpeg" length="211384" href="https://files.mastodon.social/media_attachments/files/013/030/725/original/5b4886730cbbd25c.jpeg"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101846181093805500"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17841108.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94245239:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101845897513133849</id>
|
||||||
|
<published>2019-03-31T15:11:07Z</published>
|
||||||
|
<updated>2019-03-31T15:11:07Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101845897513133849"/>
|
||||||
|
<summary xml:lang="en">more earthsea ramblings</summary>
|
||||||
|
<content type="html" xml:lang="en"><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></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101845897513133849"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17840088.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94232455:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101841219051533307</id>
|
||||||
|
<published>2019-03-30T19:21:19Z</published>
|
||||||
|
<updated>2019-03-30T19:21:19Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101841219051533307"/>
|
||||||
|
<content type="html" xml:lang="en"><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></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101841219051533307"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17826587.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94075000:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101839949762341381</id>
|
||||||
|
<published>2019-03-30T13:58:31Z</published>
|
||||||
|
<updated>2019-03-30T13:58:31Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101839949762341381"/>
|
||||||
|
<content type="html" xml:lang="en"><p>yet I&apos;m confused about this american dude with a gun, like the heck r ya doin in mah ghibli</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101839949762341381"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17821757.atom"/>
|
||||||
|
<thr:in-reply-to ref="https://mastodon.social/users/emelie/statuses/101839928677863590" href="https://mastodon.social/@emelie/101839928677863590"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94026360:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101839928677863590</id>
|
||||||
|
<published>2019-03-30T13:53:09Z</published>
|
||||||
|
<updated>2019-03-30T13:53:09Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101839928677863590"/>
|
||||||
|
<content type="html" xml:lang="en"><p>2 hours into Ni no Kuni 2 and I&apos;ve already sold my soul to this game</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101839928677863590"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17821713.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94026360:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101836329521599438</id>
|
||||||
|
<published>2019-03-29T22:37:51Z</published>
|
||||||
|
<updated>2019-03-29T22:37:51Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101836329521599438"/>
|
||||||
|
<content type="html" xml:lang="en"><p>Pippi Longstocking the original one-punch /man</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101836329521599438"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17811608.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93907854:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101835905282948341</id>
|
||||||
|
<published>2019-03-29T20:49:57Z</published>
|
||||||
|
<updated>2019-03-29T20:49:57Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835905282948341"/>
|
||||||
|
<content type="html" xml:lang="en"><p>I&apos;ve had so much wine I thought I had a 3rd brother</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835905282948341"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809862.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93892966:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101835878059204660</id>
|
||||||
|
<published>2019-03-29T20:43:02Z</published>
|
||||||
|
<updated>2019-03-29T20:43:02Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835878059204660"/>
|
||||||
|
<content type="html" xml:lang="en"><p>ååååhhh booi</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835878059204660"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809734.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93892010:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101835848050598939</id>
|
||||||
|
<published>2019-03-29T20:35:24Z</published>
|
||||||
|
<updated>2019-03-29T20:35:24Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835848050598939"/>
|
||||||
|
<content type="html" xml:lang="en"><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></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://thraeryn.net/users/thraeryn"/>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835848050598939"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809591.atom"/>
|
||||||
|
<thr:in-reply-to ref="https://thraeryn.net/users/thraeryn/statuses/101835839202826007" href="https://thraeryn.net/@thraeryn/101835839202826007"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93888827:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101835823138262290</id>
|
||||||
|
<published>2019-03-29T20:29:04Z</published>
|
||||||
|
<updated>2019-03-29T20:29:04Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835823138262290"/>
|
||||||
|
<summary xml:lang="en">medical, fluids mention</summary>
|
||||||
|
<content type="html" xml:lang="en"><p><span class="h-card"><a href="https://icosahedron.website/@Trev" class="u-url mention">@<span>Trev</span></a></span> *hugs* ✨</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://icosahedron.website/users/Trev"/>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835823138262290"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809468.atom"/>
|
||||||
|
<thr:in-reply-to ref="https://icosahedron.website/users/Trev/statuses/101835812250051801" href="https://icosahedron.website/@Trev/101835812250051801"/>
|
||||||
|
<ostatus:conversation ref="tag:icosahedron.website,2019-03-29:objectId=12220882:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
</feed>
|
64
test/fixtures/httpoison_mock/status.emelie.json
vendored
Normal file
64
test/fixtures/httpoison_mock/status.emelie.json
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
{
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"Emoji": "toot:Emoji",
|
||||||
|
"focalPoint": {
|
||||||
|
"@container": "@list",
|
||||||
|
"@id": "toot:focalPoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "https://mastodon.social/users/emelie/statuses/101849165031453009",
|
||||||
|
"type": "Note",
|
||||||
|
"summary": null,
|
||||||
|
"inReplyTo": null,
|
||||||
|
"published": "2019-04-01T05:02:05Z",
|
||||||
|
"url": "https://mastodon.social/@emelie/101849165031453009",
|
||||||
|
"attributedTo": "https://mastodon.social/users/emelie",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [
|
||||||
|
"https://mastodon.social/users/emelie/followers"
|
||||||
|
],
|
||||||
|
"sensitive": false,
|
||||||
|
"atomUri": "https://mastodon.social/users/emelie/statuses/101849165031453009",
|
||||||
|
"inReplyToAtomUri": null,
|
||||||
|
"conversation": "tag:mastodon.social,2019-04-01:objectId=94350309:objectType=Conversation",
|
||||||
|
"content": "<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>",
|
||||||
|
"contentMap": {
|
||||||
|
"en": "<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>"
|
||||||
|
},
|
||||||
|
"attachment": [
|
||||||
|
{
|
||||||
|
"type": "Document",
|
||||||
|
"mediaType": "video/mp4",
|
||||||
|
"url": "https://files.mastodon.social/media_attachments/files/013/049/816/original/e7831178a5e0d6d4.mp4",
|
||||||
|
"name": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"type": "Hashtag",
|
||||||
|
"href": "https://mastodon.social/tags/mastocats",
|
||||||
|
"name": "#mastocats"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"replies": {
|
||||||
|
"id": "https://mastodon.social/users/emelie/statuses/101849165031453009/replies",
|
||||||
|
"type": "Collection",
|
||||||
|
"first": {
|
||||||
|
"type": "CollectionPage",
|
||||||
|
"partOf": "https://mastodon.social/users/emelie/statuses/101849165031453009/replies",
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
test/fixtures/httpoison_mock/webfinger_emelie.json
vendored
Normal file
36
test/fixtures/httpoison_mock/webfinger_emelie.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"aliases": [
|
||||||
|
"https://mastodon.social/@emelie",
|
||||||
|
"https://mastodon.social/users/emelie"
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "https://mastodon.social/@emelie",
|
||||||
|
"rel": "http://webfinger.net/rel/profile-page",
|
||||||
|
"type": "text/html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://mastodon.social/users/emelie.atom",
|
||||||
|
"rel": "http://schemas.google.com/g/2010#updates-from",
|
||||||
|
"type": "application/atom+xml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://mastodon.social/users/emelie",
|
||||||
|
"rel": "self",
|
||||||
|
"type": "application/activity+json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "https://mastodon.social/api/salmon/15657",
|
||||||
|
"rel": "salmon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "data:application/magic-public-key,RSA.u3CWs1oAJPE3ZJ9sj6Ut_Mu-mTE7MOijsQc8_6c73XVVuhIEomiozJIH7l8a7S1n5SYL4UuiwcubSOi7u1bbGpYnp5TYhN-Cxvq_P80V4_ncNIPSQzS49it7nSLeG5pA21lGPDA44huquES1un6p9gSmbTwngVX9oe4MYuUeh0Z7vijjU13Llz1cRq_ZgPQPgfz-2NJf-VeXnvyDZDYxZPVBBlrMl3VoGbu0M5L8SjY35559KCZ3woIvqRolcoHXfgvJMdPcJgSZVYxlCw3dA95q9jQcn6s87CPSUs7bmYEQCrDVn5m5NER5TzwBmP4cgJl9AaDVWQtRd4jFZNTxlQ==.AQAB",
|
||||||
|
"rel": "magic-public-key"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rel": "http://ostatus.org/schema/1.0/subscribe",
|
||||||
|
"template": "https://mastodon.social/authorize_interaction?uri={uri}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subject": "acct:emelie@mastodon.social"
|
||||||
|
}
|
|
@ -1,83 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.JobsTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
|
|
||||||
alias Jobs.WorkerMock
|
|
||||||
alias Pleroma.Jobs
|
|
||||||
|
|
||||||
setup do
|
|
||||||
state = %{
|
|
||||||
queues: Enum.into([Jobs.create_queue(:testing)], %{}),
|
|
||||||
refs: %{}
|
|
||||||
}
|
|
||||||
|
|
||||||
[state: state]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "creates queue" do
|
|
||||||
queue = Jobs.create_queue(:foobar)
|
|
||||||
|
|
||||||
assert {:foobar, set} = queue
|
|
||||||
assert :set == elem(set, 0) |> elem(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "enqueues an element according to priority" do
|
|
||||||
queue = [%{item: 1, priority: 2}]
|
|
||||||
|
|
||||||
new_queue = Jobs.enqueue_sorted(queue, 2, 1)
|
|
||||||
assert new_queue == [%{item: 2, priority: 1}, %{item: 1, priority: 2}]
|
|
||||||
|
|
||||||
new_queue = Jobs.enqueue_sorted(queue, 2, 3)
|
|
||||||
assert new_queue == [%{item: 1, priority: 2}, %{item: 2, priority: 3}]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "pop first item" do
|
|
||||||
queue = [%{item: 2, priority: 1}, %{item: 1, priority: 2}]
|
|
||||||
|
|
||||||
assert {2, [%{item: 1, priority: 2}]} = Jobs.queue_pop(queue)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "enqueue a job", %{state: state} do
|
|
||||||
assert {:noreply, new_state} =
|
|
||||||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state)
|
|
||||||
|
|
||||||
assert %{queues: %{testing: {running_jobs, []}}, refs: _} = new_state
|
|
||||||
assert :sets.size(running_jobs) == 1
|
|
||||||
assert [ref] = :sets.to_list(running_jobs)
|
|
||||||
assert %{refs: %{^ref => :testing}} = new_state
|
|
||||||
end
|
|
||||||
|
|
||||||
test "max jobs setting", %{state: state} do
|
|
||||||
max_jobs = Pleroma.Config.get([Jobs, :testing, :max_jobs])
|
|
||||||
|
|
||||||
{:noreply, state} =
|
|
||||||
Enum.reduce(1..(max_jobs + 1), {:noreply, state}, fn _, {:noreply, state} ->
|
|
||||||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state)
|
|
||||||
end)
|
|
||||||
|
|
||||||
assert %{
|
|
||||||
queues: %{
|
|
||||||
testing:
|
|
||||||
{running_jobs, [%{item: {WorkerMock, [:test_job, :foo, :bar]}, priority: 3}]}
|
|
||||||
}
|
|
||||||
} = state
|
|
||||||
|
|
||||||
assert :sets.size(running_jobs) == max_jobs
|
|
||||||
end
|
|
||||||
|
|
||||||
test "remove job after it finished", %{state: state} do
|
|
||||||
{:noreply, new_state} =
|
|
||||||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state)
|
|
||||||
|
|
||||||
%{queues: %{testing: {running_jobs, []}}} = new_state
|
|
||||||
[ref] = :sets.to_list(running_jobs)
|
|
||||||
|
|
||||||
assert {:noreply, %{queues: %{testing: {running_jobs, []}}, refs: %{}}} =
|
|
||||||
Jobs.handle_info({:DOWN, ref, :process, nil, nil}, new_state)
|
|
||||||
|
|
||||||
assert :sets.size(running_jobs) == 0
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -216,7 +216,7 @@ def oauth_app_factory do
|
||||||
redirect_uris: "https://example.com/callback",
|
redirect_uris: "https://example.com/callback",
|
||||||
scopes: ["read", "write", "follow", "push"],
|
scopes: ["read", "write", "follow", "push"],
|
||||||
website: "https://example.com",
|
website: "https://example.com",
|
||||||
client_id: "aaabbb==",
|
client_id: Ecto.UUID.generate(),
|
||||||
client_secret: "aaa;/&bbb"
|
client_secret: "aaa;/&bbb"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,6 +36,43 @@ def get("https://osada.macgirvin.com/channel/mike", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://mastodon.social/users/emelie/statuses/101849165031453009", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/httpoison_mock/status.emelie.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://mastodon.social/users/emelie", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/httpoison_mock/emelie.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(
|
||||||
|
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_
|
||||||
|
) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/httpoison_mock/webfinger_emelie.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://mastodon.social/users/emelie.atom", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/httpoison_mock/emelie.atom")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
"https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
|
"https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
|
||||||
_,
|
_,
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Jobs.WorkerMock do
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
def perform(:test_job, arg, arg2) do
|
|
||||||
Logger.debug({:perform, :test_job, arg, arg2})
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(:test_job, payload) do
|
|
||||||
Logger.debug({:perform, :test_job, payload})
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_job(payload) do
|
|
||||||
Pleroma.Jobs.enqueue(:testing, __MODULE__, [:test_job, payload])
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -248,4 +248,14 @@ test "invite token is generated" do
|
||||||
assert message =~ "Generated"
|
assert message =~ "Generated"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "running delete_activities" do
|
||||||
|
test "activities are deleted" do
|
||||||
|
%{nickname: nickname} = insert(:user)
|
||||||
|
|
||||||
|
assert :ok == Mix.Tasks.Pleroma.User.run(["delete_activities", nickname])
|
||||||
|
assert_received {:mix_shell, :info, [message]}
|
||||||
|
assert message == "User #{nickname} statuses deleted."
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -122,7 +122,7 @@ test "follow takes a user and another user" do
|
||||||
|
|
||||||
{:ok, user} = User.follow(user, followed)
|
{:ok, user} = User.follow(user, followed)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
followed = User.get_by_ap_id(followed.ap_id)
|
followed = User.get_by_ap_id(followed.ap_id)
|
||||||
assert followed.info.follower_count == 1
|
assert followed.info.follower_count == 1
|
||||||
|
@ -178,7 +178,7 @@ test "unfollow takes a user and another user" do
|
||||||
|
|
||||||
{:ok, user, _activity} = User.unfollow(user, followed)
|
{:ok, user, _activity} = User.unfollow(user, followed)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert user.following == []
|
assert user.following == []
|
||||||
end
|
end
|
||||||
|
@ -188,7 +188,7 @@ test "unfollow doesn't unfollow yourself" do
|
||||||
|
|
||||||
{:error, _} = User.unfollow(user, user)
|
{:error, _} = User.unfollow(user, user)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.following == [user.ap_id]
|
assert user.following == [user.ap_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -200,6 +200,13 @@ test "test if a user is following another user" do
|
||||||
refute User.following?(followed, user)
|
refute User.following?(followed, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "fetches correct profile for nickname beginning with number" do
|
||||||
|
# Use old-style integer ID to try to reproduce the problem
|
||||||
|
user = insert(:user, %{id: 1080})
|
||||||
|
userwithnumbers = insert(:user, %{nickname: "#{user.id}garbage"})
|
||||||
|
assert userwithnumbers == User.get_cached_by_nickname_or_id(userwithnumbers.nickname)
|
||||||
|
end
|
||||||
|
|
||||||
describe "user registration" do
|
describe "user registration" do
|
||||||
@full_user_data %{
|
@full_user_data %{
|
||||||
bio: "A guy",
|
bio: "A guy",
|
||||||
|
@ -679,7 +686,7 @@ test "blocks tear down cyclical follow relationships" do
|
||||||
assert User.following?(blocked, blocker)
|
assert User.following?(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = User.block(blocker, blocked)
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
blocked = Repo.get(User, blocked.id)
|
blocked = User.get_by_id(blocked.id)
|
||||||
|
|
||||||
assert User.blocks?(blocker, blocked)
|
assert User.blocks?(blocker, blocked)
|
||||||
|
|
||||||
|
@ -697,7 +704,7 @@ test "blocks tear down blocker->blocked follow relationships" do
|
||||||
refute User.following?(blocked, blocker)
|
refute User.following?(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = User.block(blocker, blocked)
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
blocked = Repo.get(User, blocked.id)
|
blocked = User.get_by_id(blocked.id)
|
||||||
|
|
||||||
assert User.blocks?(blocker, blocked)
|
assert User.blocks?(blocker, blocked)
|
||||||
|
|
||||||
|
@ -715,7 +722,7 @@ test "blocks tear down blocked->blocker follow relationships" do
|
||||||
assert User.following?(blocked, blocker)
|
assert User.following?(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = User.block(blocker, blocked)
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
blocked = Repo.get(User, blocked.id)
|
blocked = User.get_by_id(blocked.id)
|
||||||
|
|
||||||
assert User.blocks?(blocker, blocked)
|
assert User.blocks?(blocker, blocked)
|
||||||
|
|
||||||
|
@ -792,6 +799,16 @@ test ".deactivate can de-activate then re-activate a user" do
|
||||||
assert false == user.info.deactivated
|
assert false == user.info.deactivated
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test ".delete_user_activities deletes all create activities" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
|
||||||
|
{:ok, _} = User.delete_user_activities(user)
|
||||||
|
|
||||||
|
# TODO: Remove favorites, repeats, delete activities.
|
||||||
|
refute Activity.get_by_id(activity.id)
|
||||||
|
end
|
||||||
|
|
||||||
test ".delete deactivates a user, all follow relationships and all create activities" do
|
test ".delete deactivates a user, all follow relationships and all create activities" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
|
@ -809,9 +826,9 @@ test ".delete deactivates a user, all follow relationships and all create activi
|
||||||
|
|
||||||
{:ok, _} = User.delete(user)
|
{:ok, _} = User.delete(user)
|
||||||
|
|
||||||
followed = Repo.get(User, followed.id)
|
followed = User.get_by_id(followed.id)
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert user.info.deactivated
|
assert user.info.deactivated
|
||||||
|
|
||||||
|
@ -820,7 +837,7 @@ test ".delete deactivates a user, all follow relationships and all create activi
|
||||||
|
|
||||||
# TODO: Remove favorites, repeats, delete activities.
|
# TODO: Remove favorites, repeats, delete activities.
|
||||||
|
|
||||||
refute Repo.get(Activity, activity.id)
|
refute Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_public_key_for_ap_id fetches a user that's not in the db" do
|
test "get_public_key_for_ap_id fetches a user that's not in the db" do
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Instances
|
alias Pleroma.Instances
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectView
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
|
@ -51,7 +50,7 @@ test "it returns a json representation of the user with accept application/json"
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|> get("/users/#{user.nickname}")
|
|> get("/users/#{user.nickname}")
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
@ -66,7 +65,7 @@ test "it returns a json representation of the user with accept application/activ
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|> get("/users/#{user.nickname}")
|
|> get("/users/#{user.nickname}")
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
@ -84,7 +83,7 @@ test "it returns a json representation of the user with accept application/ld+js
|
||||||
)
|
)
|
||||||
|> get("/users/#{user.nickname}")
|
|> get("/users/#{user.nickname}")
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
@ -543,7 +542,7 @@ test "it works for more than 10 users", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
Enum.each(1..15, fn _ ->
|
Enum.each(1..15, fn _ ->
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
User.follow(user, other_user)
|
User.follow(user, other_user)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -218,18 +218,18 @@ test "increases user note count only for public activities" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "1", "visibility" => "public"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "1", "visibility" => "public"})
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "2", "visibility" => "unlisted"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "unlisted"})
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "2", "visibility" => "private"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "private"})
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "3", "visibility" => "direct"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "3", "visibility" => "direct"})
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.note_count == 2
|
assert user.info.note_count == 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ test "doesn't return blocked activities" do
|
||||||
{:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
|
{:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
|
||||||
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
||||||
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
||||||
activity_three = Repo.get(Activity, activity_three.id)
|
activity_three = Activity.get_by_id(activity_three.id)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
|
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
|
||||||
|
@ -380,7 +380,7 @@ test "doesn't return muted activities" do
|
||||||
{:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
|
{:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
|
||||||
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
||||||
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
||||||
activity_three = Repo.get(Activity, activity_three.id)
|
activity_three = Activity.get_by_id(activity_three.id)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
|
ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
|
||||||
|
@ -559,7 +559,7 @@ test "unliking a previously liked object" do
|
||||||
{:ok, _, _, object} = ActivityPub.unlike(user, object)
|
{:ok, _, _, object} = ActivityPub.unlike(user, object)
|
||||||
assert object.data["like_count"] == 0
|
assert object.data["like_count"] == 0
|
||||||
|
|
||||||
assert Repo.get(Activity, like_activity.id) == nil
|
assert Activity.get_by_id(like_activity.id) == nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -610,7 +610,7 @@ test "unannouncing a previously announced object" do
|
||||||
assert unannounce_activity.data["actor"] == user.ap_id
|
assert unannounce_activity.data["actor"] == user.ap_id
|
||||||
assert unannounce_activity.data["context"] == announce_activity.data["context"]
|
assert unannounce_activity.data["context"] == announce_activity.data["context"]
|
||||||
|
|
||||||
assert Repo.get(Activity, announce_activity.id) == nil
|
assert Activity.get_by_id(announce_activity.id) == nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -739,7 +739,7 @@ test "it creates a delete activity and deletes the original object" do
|
||||||
assert delete.data["actor"] == note.data["actor"]
|
assert delete.data["actor"] == note.data["actor"]
|
||||||
assert delete.data["object"] == note.data["object"]["id"]
|
assert delete.data["object"] == note.data["object"]["id"]
|
||||||
|
|
||||||
assert Repo.get(Activity, delete.id) != nil
|
assert Activity.get_by_id(delete.id) != nil
|
||||||
|
|
||||||
assert Repo.get(Object, object.id).data["type"] == "Tombstone"
|
assert Repo.get(Object, object.id).data["type"] == "Tombstone"
|
||||||
end
|
end
|
||||||
|
@ -748,23 +748,23 @@ test "decrements user note count only for public activities" do
|
||||||
user = insert(:user, info: %{note_count: 10})
|
user = insert(:user, info: %{note_count: 10})
|
||||||
|
|
||||||
{:ok, a1} =
|
{:ok, a1} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "public"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "public"})
|
||||||
|
|
||||||
{:ok, a2} =
|
{:ok, a2} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "unlisted"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "unlisted"})
|
||||||
|
|
||||||
{:ok, a3} =
|
{:ok, a3} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "private"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "private"})
|
||||||
|
|
||||||
{:ok, a4} =
|
{:ok, a4} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "direct"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "direct"})
|
||||||
|
|
||||||
{:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
{:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
||||||
{:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
{:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
||||||
{:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
{:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
||||||
{:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
{:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.note_count == 10
|
assert user.info.note_count == 10
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -461,7 +461,7 @@ test "it works for incoming deletes" do
|
||||||
|
|
||||||
{:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
refute Repo.get(Activity, activity.id)
|
refute Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it fails for incoming deletes with spoofed origin" do
|
test "it fails for incoming deletes with spoofed origin" do
|
||||||
|
@ -481,7 +481,7 @@ test "it fails for incoming deletes with spoofed origin" do
|
||||||
|
|
||||||
:error = Transmogrifier.handle_incoming(data)
|
:error = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert Repo.get(Activity, activity.id)
|
assert Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming unannounces with an existing notice" do
|
test "it works for incoming unannounces with an existing notice" do
|
||||||
|
@ -639,7 +639,7 @@ test "it works for incoming accepts which were pre-accepted" do
|
||||||
|
|
||||||
assert activity.data["object"] == follow_activity.data["id"]
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == true
|
assert User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -661,7 +661,7 @@ test "it works for incoming accepts which were orphaned" do
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
||||||
assert activity.data["object"] == follow_activity.data["id"]
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == true
|
assert User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -681,7 +681,7 @@ test "it works for incoming accepts which are referenced by IRI only" do
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
||||||
assert activity.data["object"] == follow_activity.data["id"]
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == true
|
assert User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -700,7 +700,7 @@ test "it fails for incoming accepts which cannot be correlated" do
|
||||||
|
|
||||||
:error = Transmogrifier.handle_incoming(accept_data)
|
:error = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
refute User.following?(follower, followed) == true
|
refute User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -719,7 +719,7 @@ test "it fails for incoming rejects which cannot be correlated" do
|
||||||
|
|
||||||
:error = Transmogrifier.handle_incoming(accept_data)
|
:error = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
refute User.following?(follower, followed) == true
|
refute User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -744,7 +744,7 @@ test "it works for incoming rejects which are orphaned" do
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(reject_data)
|
{:ok, activity} = Transmogrifier.handle_incoming(reject_data)
|
||||||
refute activity.local
|
refute activity.local
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == false
|
assert User.following?(follower, followed) == false
|
||||||
end
|
end
|
||||||
|
@ -766,7 +766,7 @@ test "it works for incoming rejects which are referenced by IRI only" do
|
||||||
|
|
||||||
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
|
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == false
|
assert User.following?(follower, followed) == false
|
||||||
end
|
end
|
||||||
|
@ -1020,7 +1020,7 @@ test "it upgrades a user to activitypub" do
|
||||||
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
|
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
|
||||||
assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
|
assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.note_count == 1
|
assert user.info.note_count == 1
|
||||||
|
|
||||||
{:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
|
{:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
|
||||||
|
@ -1031,10 +1031,10 @@ test "it upgrades a user to activitypub" do
|
||||||
# Wait for the background task
|
# Wait for the background task
|
||||||
:timer.sleep(1000)
|
:timer.sleep(1000)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.note_count == 1
|
assert user.info.note_count == 1
|
||||||
|
|
||||||
activity = Repo.get(Activity, activity.id)
|
activity = Activity.get_by_id(activity.id)
|
||||||
assert user.follower_address in activity.recipients
|
assert user.follower_address in activity.recipients
|
||||||
|
|
||||||
assert %{
|
assert %{
|
||||||
|
@ -1057,10 +1057,10 @@ test "it upgrades a user to activitypub" do
|
||||||
|
|
||||||
refute "..." in activity.recipients
|
refute "..." in activity.recipients
|
||||||
|
|
||||||
unrelated_activity = Repo.get(Activity, unrelated_activity.id)
|
unrelated_activity = Activity.get_by_id(unrelated_activity.id)
|
||||||
refute user.follower_address in unrelated_activity.recipients
|
refute user.follower_address in unrelated_activity.recipients
|
||||||
|
|
||||||
user_two = Repo.get(User, user_two.id)
|
user_two = User.get_by_id(user_two.id)
|
||||||
assert user.follower_address in user_two.following
|
assert user.follower_address in user_two.following
|
||||||
refute "..." in user_two.following
|
refute "..." in user_two.following
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||||
use Pleroma.Web.ConnCase
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
@ -101,13 +100,13 @@ test "it appends specified tags to users with specified nicknames", %{
|
||||||
user2: user2
|
user2: user2
|
||||||
} do
|
} do
|
||||||
assert json_response(conn, :no_content)
|
assert json_response(conn, :no_content)
|
||||||
assert Repo.get(User, user1.id).tags == ["x", "foo", "bar"]
|
assert User.get_by_id(user1.id).tags == ["x", "foo", "bar"]
|
||||||
assert Repo.get(User, user2.id).tags == ["y", "foo", "bar"]
|
assert User.get_by_id(user2.id).tags == ["y", "foo", "bar"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
|
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
|
||||||
assert json_response(conn, :no_content)
|
assert json_response(conn, :no_content)
|
||||||
assert Repo.get(User, user3.id).tags == ["unchanged"]
|
assert User.get_by_id(user3.id).tags == ["unchanged"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -137,13 +136,13 @@ test "it removes specified tags from users with specified nicknames", %{
|
||||||
user2: user2
|
user2: user2
|
||||||
} do
|
} do
|
||||||
assert json_response(conn, :no_content)
|
assert json_response(conn, :no_content)
|
||||||
assert Repo.get(User, user1.id).tags == []
|
assert User.get_by_id(user1.id).tags == []
|
||||||
assert Repo.get(User, user2.id).tags == ["y"]
|
assert User.get_by_id(user2.id).tags == ["y"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
|
test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
|
||||||
assert json_response(conn, :no_content)
|
assert json_response(conn, :no_content)
|
||||||
assert Repo.get(User, user3.id).tags == ["unchanged"]
|
assert User.get_by_id(user3.id).tags == ["unchanged"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -213,7 +212,7 @@ test "deactivates the user", %{conn: conn} do
|
||||||
conn
|
conn
|
||||||
|> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
|
|> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.deactivated == true
|
assert user.info.deactivated == true
|
||||||
assert json_response(conn, :no_content)
|
assert json_response(conn, :no_content)
|
||||||
end
|
end
|
||||||
|
@ -225,7 +224,7 @@ test "activates the user", %{conn: conn} do
|
||||||
conn
|
conn
|
||||||
|> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true})
|
|> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true})
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.deactivated == false
|
assert user.info.deactivated == false
|
||||||
assert json_response(conn, :no_content)
|
assert json_response(conn, :no_content)
|
||||||
end
|
end
|
||||||
|
|
|
@ -101,7 +101,7 @@ test "posting a status", %{conn: conn} do
|
||||||
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
|
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
|
||||||
json_response(conn_one, 200)
|
json_response(conn_one, 200)
|
||||||
|
|
||||||
assert Repo.get(Activity, id)
|
assert Activity.get_by_id(id)
|
||||||
|
|
||||||
conn_two =
|
conn_two =
|
||||||
conn
|
conn
|
||||||
|
@ -140,7 +140,7 @@ test "posting a sensitive status", %{conn: conn} do
|
||||||
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
|
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
|
||||||
|
|
||||||
assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
|
assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
|
||||||
assert Repo.get(Activity, id)
|
assert Activity.get_by_id(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "posting a status with OGP link preview", %{conn: conn} do
|
test "posting a status with OGP link preview", %{conn: conn} do
|
||||||
|
@ -155,7 +155,7 @@ test "posting a status with OGP link preview", %{conn: conn} do
|
||||||
})
|
})
|
||||||
|
|
||||||
assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
|
assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
|
||||||
assert Repo.get(Activity, id)
|
assert Activity.get_by_id(id)
|
||||||
Pleroma.Config.put([:rich_media, :enabled], false)
|
Pleroma.Config.put([:rich_media, :enabled], false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ test "posting a direct status", %{conn: conn} do
|
||||||
|> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
|
|> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
|
||||||
|
|
||||||
assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
|
assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
|
||||||
assert activity = Repo.get(Activity, id)
|
assert activity = Activity.get_by_id(id)
|
||||||
assert activity.recipients == [user2.ap_id, user1.ap_id]
|
assert activity.recipients == [user2.ap_id, user1.ap_id]
|
||||||
assert activity.data["to"] == [user2.ap_id]
|
assert activity.data["to"] == [user2.ap_id]
|
||||||
assert activity.data["cc"] == []
|
assert activity.data["cc"] == []
|
||||||
|
@ -289,7 +289,7 @@ test "replying to a status", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
|
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
|
||||||
|
|
||||||
activity = Repo.get(Activity, id)
|
activity = Activity.get_by_id(id)
|
||||||
|
|
||||||
assert activity.data["context"] == replied_to.data["context"]
|
assert activity.data["context"] == replied_to.data["context"]
|
||||||
assert activity.data["object"]["inReplyToStatusId"] == replied_to.id
|
assert activity.data["object"]["inReplyToStatusId"] == replied_to.id
|
||||||
|
@ -305,7 +305,7 @@ test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
|
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
|
||||||
|
|
||||||
activity = Repo.get(Activity, id)
|
activity = Activity.get_by_id(id)
|
||||||
|
|
||||||
assert activity
|
assert activity
|
||||||
end
|
end
|
||||||
|
@ -404,7 +404,7 @@ test "when you created it", %{conn: conn} do
|
||||||
|
|
||||||
assert %{} = json_response(conn, 200)
|
assert %{} = json_response(conn, 200)
|
||||||
|
|
||||||
refute Repo.get(Activity, activity.id)
|
refute Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "when you didn't create it", %{conn: conn} do
|
test "when you didn't create it", %{conn: conn} do
|
||||||
|
@ -418,7 +418,7 @@ test "when you didn't create it", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"error" => _} = json_response(conn, 403)
|
assert %{"error" => _} = json_response(conn, 403)
|
||||||
|
|
||||||
assert Repo.get(Activity, activity.id) == activity
|
assert Activity.get_by_id(activity.id) == activity
|
||||||
end
|
end
|
||||||
|
|
||||||
test "when you're an admin or moderator", %{conn: conn} do
|
test "when you're an admin or moderator", %{conn: conn} do
|
||||||
|
@ -441,8 +441,8 @@ test "when you're an admin or moderator", %{conn: conn} do
|
||||||
|
|
||||||
assert %{} = json_response(res_conn, 200)
|
assert %{} = json_response(res_conn, 200)
|
||||||
|
|
||||||
refute Repo.get(Activity, activity1.id)
|
refute Activity.get_by_id(activity1.id)
|
||||||
refute Repo.get(Activity, activity2.id)
|
refute Activity.get_by_id(activity2.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1112,8 +1112,8 @@ test "/api/v1/follow_requests works" do
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = Repo.get(User, other_user.id)
|
other_user = User.get_by_id(other_user.id)
|
||||||
|
|
||||||
assert User.following?(other_user, user) == false
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
@ -1132,8 +1132,8 @@ test "/api/v1/follow_requests/:id/authorize works" do
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = Repo.get(User, other_user.id)
|
other_user = User.get_by_id(other_user.id)
|
||||||
|
|
||||||
assert User.following?(other_user, user) == false
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
@ -1145,8 +1145,8 @@ test "/api/v1/follow_requests/:id/authorize works" do
|
||||||
assert relationship = json_response(conn, 200)
|
assert relationship = json_response(conn, 200)
|
||||||
assert to_string(other_user.id) == relationship["id"]
|
assert to_string(other_user.id) == relationship["id"]
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = Repo.get(User, other_user.id)
|
other_user = User.get_by_id(other_user.id)
|
||||||
|
|
||||||
assert User.following?(other_user, user) == true
|
assert User.following?(other_user, user) == true
|
||||||
end
|
end
|
||||||
|
@ -1169,7 +1169,7 @@ test "/api/v1/follow_requests/:id/reject works" do
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|
@ -1179,8 +1179,8 @@ test "/api/v1/follow_requests/:id/reject works" do
|
||||||
assert relationship = json_response(conn, 200)
|
assert relationship = json_response(conn, 200)
|
||||||
assert to_string(other_user.id) == relationship["id"]
|
assert to_string(other_user.id) == relationship["id"]
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = Repo.get(User, other_user.id)
|
other_user = User.get_by_id(other_user.id)
|
||||||
|
|
||||||
assert User.following?(other_user, user) == false
|
assert User.following?(other_user, user) == false
|
||||||
end
|
end
|
||||||
|
@ -1465,7 +1465,7 @@ test "following / unfollowing a user", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
|
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|
@ -1474,7 +1474,7 @@ test "following / unfollowing a user", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
|
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|
@ -1496,7 +1496,7 @@ test "muting / unmuting a user", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
|
assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|
@ -1532,7 +1532,7 @@ test "blocking / unblocking a user", %{conn: conn} do
|
||||||
|
|
||||||
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
|
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|
@ -1889,7 +1889,7 @@ test "get instance stats", %{conn: conn} do
|
||||||
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
|
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
|
||||||
|
|
||||||
# Stats should count users with missing or nil `info.deactivated` value
|
# Stats should count users with missing or nil `info.deactivated` value
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
info_change = Changeset.change(user.info, %{deactivated: nil})
|
info_change = Changeset.change(user.info, %{deactivated: nil})
|
||||||
|
|
||||||
{:ok, _user} =
|
{:ok, _user} =
|
||||||
|
@ -2265,4 +2265,30 @@ test "preserves parameters in link headers", %{conn: conn} do
|
||||||
assert link_header =~ ~r/max_id=#{notification1.id}/
|
assert link_header =~ ~r/max_id=#{notification1.id}/
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
|
||||||
|
# Need to set an old-style integer ID to reproduce the problem
|
||||||
|
# (these are no longer assigned to new accounts but were preserved
|
||||||
|
# for existing accounts during the migration to flakeIDs)
|
||||||
|
user_one = insert(:user, %{id: 1212})
|
||||||
|
user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
|
||||||
|
|
||||||
|
resp_one =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/accounts/#{user_one.id}")
|
||||||
|
|
||||||
|
resp_two =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/accounts/#{user_two.nickname}")
|
||||||
|
|
||||||
|
resp_three =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/accounts/#{user_two.id}")
|
||||||
|
|
||||||
|
acc_one = json_response(resp_one, 200)
|
||||||
|
acc_two = json_response(resp_two, 200)
|
||||||
|
acc_three = json_response(resp_three, 200)
|
||||||
|
refute acc_one == acc_two
|
||||||
|
assert acc_two == acc_three
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ test "Mention notification" do
|
||||||
mentioned_user = insert(:user)
|
mentioned_user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"})
|
||||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
id: to_string(notification.id),
|
id: to_string(notification.id),
|
||||||
|
@ -44,7 +44,7 @@ test "Favourite notification" do
|
||||||
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||||
{:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user)
|
{:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user)
|
||||||
{:ok, [notification]} = Notification.create_notifications(favorite_activity)
|
{:ok, [notification]} = Notification.create_notifications(favorite_activity)
|
||||||
create_activity = Repo.get(Activity, create_activity.id)
|
create_activity = Activity.get_by_id(create_activity.id)
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
id: to_string(notification.id),
|
id: to_string(notification.id),
|
||||||
|
@ -66,7 +66,7 @@ test "Reblog notification" do
|
||||||
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||||
{:ok, reblog_activity, _object} = CommonAPI.repeat(create_activity.id, another_user)
|
{:ok, reblog_activity, _object} = CommonAPI.repeat(create_activity.id, another_user)
|
||||||
{:ok, [notification]} = Notification.create_notifications(reblog_activity)
|
{:ok, [notification]} = Notification.create_notifications(reblog_activity)
|
||||||
reblog_activity = Repo.get(Activity, create_activity.id)
|
reblog_activity = Activity.get_by_id(create_activity.id)
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
id: to_string(notification.id),
|
id: to_string(notification.id),
|
||||||
|
|
|
@ -175,7 +175,7 @@ test "contains mentions" do
|
||||||
|
|
||||||
status = StatusView.render("status.json", %{activity: activity})
|
status = StatusView.render("status.json", %{activity: activity})
|
||||||
|
|
||||||
actor = Repo.get_by(User, ap_id: activity.actor)
|
actor = User.get_by_ap_id(activity.actor)
|
||||||
|
|
||||||
assert status.mentions ==
|
assert status.mentions ==
|
||||||
Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end)
|
Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end)
|
||||||
|
|
|
@ -10,261 +10,339 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|
||||||
alias Pleroma.Web.OAuth.Authorization
|
alias Pleroma.Web.OAuth.Authorization
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
|
|
||||||
test "redirects with oauth authorization" do
|
describe "GET /oauth/authorize" do
|
||||||
user = insert(:user)
|
setup do
|
||||||
app = insert(:oauth_app, scopes: ["read", "write", "follow"])
|
session_opts = [
|
||||||
|
store: :cookie,
|
||||||
|
key: "_test",
|
||||||
|
signing_salt: "cooldude"
|
||||||
|
]
|
||||||
|
|
||||||
conn =
|
[
|
||||||
build_conn()
|
app: insert(:oauth_app, redirect_uris: "https://redirect.url"),
|
||||||
|> post("/oauth/authorize", %{
|
conn:
|
||||||
"authorization" => %{
|
build_conn()
|
||||||
"name" => user.nickname,
|
|> Plug.Session.call(Plug.Session.init(session_opts))
|
||||||
"password" => "test",
|
|> fetch_session()
|
||||||
"client_id" => app.client_id,
|
]
|
||||||
"redirect_uri" => app.redirect_uris,
|
|
||||||
"scope" => "read write",
|
|
||||||
"state" => "statepassed"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
target = redirected_to(conn)
|
|
||||||
assert target =~ app.redirect_uris
|
|
||||||
|
|
||||||
query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
|
|
||||||
|
|
||||||
assert %{"state" => "statepassed", "code" => code} = query
|
|
||||||
auth = Repo.get_by(Authorization, token: code)
|
|
||||||
assert auth
|
|
||||||
assert auth.scopes == ["read", "write"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns 401 for wrong credentials", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
app = insert(:oauth_app)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> post("/oauth/authorize", %{
|
|
||||||
"authorization" => %{
|
|
||||||
"name" => user.nickname,
|
|
||||||
"password" => "wrong",
|
|
||||||
"client_id" => app.client_id,
|
|
||||||
"redirect_uri" => app.redirect_uris,
|
|
||||||
"state" => "statepassed",
|
|
||||||
"scope" => Enum.join(app.scopes, " ")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|> html_response(:unauthorized)
|
|
||||||
|
|
||||||
# Keep the details
|
|
||||||
assert result =~ app.client_id
|
|
||||||
assert result =~ app.redirect_uris
|
|
||||||
|
|
||||||
# Error message
|
|
||||||
assert result =~ "Invalid Username/Password"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns 401 for missing scopes", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
app = insert(:oauth_app)
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> post("/oauth/authorize", %{
|
|
||||||
"authorization" => %{
|
|
||||||
"name" => user.nickname,
|
|
||||||
"password" => "test",
|
|
||||||
"client_id" => app.client_id,
|
|
||||||
"redirect_uri" => app.redirect_uris,
|
|
||||||
"state" => "statepassed",
|
|
||||||
"scope" => ""
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|> html_response(:unauthorized)
|
|
||||||
|
|
||||||
# Keep the details
|
|
||||||
assert result =~ app.client_id
|
|
||||||
assert result =~ app.redirect_uris
|
|
||||||
|
|
||||||
# Error message
|
|
||||||
assert result =~ "This action is outside the authorized scopes"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns 401 for scopes beyond app scopes", %{conn: conn} do
|
|
||||||
user = insert(:user)
|
|
||||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
|
||||||
|
|
||||||
result =
|
|
||||||
conn
|
|
||||||
|> post("/oauth/authorize", %{
|
|
||||||
"authorization" => %{
|
|
||||||
"name" => user.nickname,
|
|
||||||
"password" => "test",
|
|
||||||
"client_id" => app.client_id,
|
|
||||||
"redirect_uri" => app.redirect_uris,
|
|
||||||
"state" => "statepassed",
|
|
||||||
"scope" => "read write follow"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|> html_response(:unauthorized)
|
|
||||||
|
|
||||||
# Keep the details
|
|
||||||
assert result =~ app.client_id
|
|
||||||
assert result =~ app.redirect_uris
|
|
||||||
|
|
||||||
# Error message
|
|
||||||
assert result =~ "This action is outside the authorized scopes"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "issues a token for an all-body request" do
|
|
||||||
user = insert(:user)
|
|
||||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
|
||||||
|
|
||||||
{:ok, auth} = Authorization.create_authorization(app, user, ["write"])
|
|
||||||
|
|
||||||
conn =
|
|
||||||
build_conn()
|
|
||||||
|> post("/oauth/token", %{
|
|
||||||
"grant_type" => "authorization_code",
|
|
||||||
"code" => auth.token,
|
|
||||||
"redirect_uri" => app.redirect_uris,
|
|
||||||
"client_id" => app.client_id,
|
|
||||||
"client_secret" => app.client_secret
|
|
||||||
})
|
|
||||||
|
|
||||||
assert %{"access_token" => token, "me" => ap_id} = json_response(conn, 200)
|
|
||||||
|
|
||||||
token = Repo.get_by(Token, token: token)
|
|
||||||
assert token
|
|
||||||
assert token.scopes == auth.scopes
|
|
||||||
assert user.ap_id == ap_id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
|
|
||||||
password = "testpassword"
|
|
||||||
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
|
|
||||||
|
|
||||||
app = insert(:oauth_app, scopes: ["read", "write"])
|
|
||||||
|
|
||||||
# Note: "scope" param is intentionally omitted
|
|
||||||
conn =
|
|
||||||
build_conn()
|
|
||||||
|> post("/oauth/token", %{
|
|
||||||
"grant_type" => "password",
|
|
||||||
"username" => user.nickname,
|
|
||||||
"password" => password,
|
|
||||||
"client_id" => app.client_id,
|
|
||||||
"client_secret" => app.client_secret
|
|
||||||
})
|
|
||||||
|
|
||||||
assert %{"access_token" => token} = json_response(conn, 200)
|
|
||||||
|
|
||||||
token = Repo.get_by(Token, token: token)
|
|
||||||
assert token
|
|
||||||
assert token.scopes == app.scopes
|
|
||||||
end
|
|
||||||
|
|
||||||
test "issues a token for request with HTTP basic auth client credentials" do
|
|
||||||
user = insert(:user)
|
|
||||||
app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"])
|
|
||||||
|
|
||||||
{:ok, auth} = Authorization.create_authorization(app, user, ["scope1", "scope2"])
|
|
||||||
assert auth.scopes == ["scope1", "scope2"]
|
|
||||||
|
|
||||||
app_encoded =
|
|
||||||
(URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret))
|
|
||||||
|> Base.encode64()
|
|
||||||
|
|
||||||
conn =
|
|
||||||
build_conn()
|
|
||||||
|> put_req_header("authorization", "Basic " <> app_encoded)
|
|
||||||
|> post("/oauth/token", %{
|
|
||||||
"grant_type" => "authorization_code",
|
|
||||||
"code" => auth.token,
|
|
||||||
"redirect_uri" => app.redirect_uris
|
|
||||||
})
|
|
||||||
|
|
||||||
assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)
|
|
||||||
|
|
||||||
assert scope == "scope1 scope2"
|
|
||||||
|
|
||||||
token = Repo.get_by(Token, token: token)
|
|
||||||
assert token
|
|
||||||
assert token.scopes == ["scope1", "scope2"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "rejects token exchange with invalid client credentials" do
|
|
||||||
user = insert(:user)
|
|
||||||
app = insert(:oauth_app)
|
|
||||||
|
|
||||||
{:ok, auth} = Authorization.create_authorization(app, user)
|
|
||||||
|
|
||||||
conn =
|
|
||||||
build_conn()
|
|
||||||
|> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=")
|
|
||||||
|> post("/oauth/token", %{
|
|
||||||
"grant_type" => "authorization_code",
|
|
||||||
"code" => auth.token,
|
|
||||||
"redirect_uri" => app.redirect_uris
|
|
||||||
})
|
|
||||||
|
|
||||||
assert resp = json_response(conn, 400)
|
|
||||||
assert %{"error" => _} = resp
|
|
||||||
refute Map.has_key?(resp, "access_token")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
|
|
||||||
setting = Pleroma.Config.get([:instance, :account_activation_required])
|
|
||||||
|
|
||||||
unless setting do
|
|
||||||
Pleroma.Config.put([:instance, :account_activation_required], true)
|
|
||||||
on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
password = "testpassword"
|
test "renders authentication page", %{app: app, conn: conn} do
|
||||||
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
|
conn =
|
||||||
info_change = Pleroma.User.Info.confirmation_changeset(user.info, :unconfirmed)
|
get(
|
||||||
|
conn,
|
||||||
|
"/oauth/authorize",
|
||||||
|
%{
|
||||||
|
"response_type" => "code",
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"redirect_uri" => app.redirect_uris,
|
||||||
|
"scope" => "read"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
{:ok, user} =
|
assert html_response(conn, 200) =~ ~s(type="submit")
|
||||||
user
|
end
|
||||||
|> Ecto.Changeset.change()
|
|
||||||
|> Ecto.Changeset.put_embed(:info, info_change)
|
|
||||||
|> Repo.update()
|
|
||||||
|
|
||||||
refute Pleroma.User.auth_active?(user)
|
test "renders authentication page if user is already authenticated but `force_login` is tru-ish",
|
||||||
|
%{app: app, conn: conn} do
|
||||||
|
token = insert(:oauth_token, app_id: app.id)
|
||||||
|
|
||||||
app = insert(:oauth_app)
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_session(:oauth_token, token.token)
|
||||||
|
|> get(
|
||||||
|
"/oauth/authorize",
|
||||||
|
%{
|
||||||
|
"response_type" => "code",
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"redirect_uri" => app.redirect_uris,
|
||||||
|
"scope" => "read",
|
||||||
|
"force_login" => "true"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
conn =
|
assert html_response(conn, 200) =~ ~s(type="submit")
|
||||||
build_conn()
|
end
|
||||||
|> post("/oauth/token", %{
|
|
||||||
"grant_type" => "password",
|
|
||||||
"username" => user.nickname,
|
|
||||||
"password" => password,
|
|
||||||
"client_id" => app.client_id,
|
|
||||||
"client_secret" => app.client_secret
|
|
||||||
})
|
|
||||||
|
|
||||||
assert resp = json_response(conn, 403)
|
test "redirects to app if user is already authenticated", %{app: app, conn: conn} do
|
||||||
assert %{"error" => _} = resp
|
token = insert(:oauth_token, app_id: app.id)
|
||||||
refute Map.has_key?(resp, "access_token")
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_session(:oauth_token, token.token)
|
||||||
|
|> get(
|
||||||
|
"/oauth/authorize",
|
||||||
|
%{
|
||||||
|
"response_type" => "code",
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"redirect_uri" => app.redirect_uris,
|
||||||
|
"scope" => "read"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert redirected_to(conn) == "https://redirect.url"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rejects an invalid authorization code" do
|
describe "POST /oauth/authorize" do
|
||||||
app = insert(:oauth_app)
|
test "redirects with oauth authorization" do
|
||||||
|
user = insert(:user)
|
||||||
|
app = insert(:oauth_app, scopes: ["read", "write", "follow"])
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn()
|
build_conn()
|
||||||
|> post("/oauth/token", %{
|
|> post("/oauth/authorize", %{
|
||||||
"grant_type" => "authorization_code",
|
"authorization" => %{
|
||||||
"code" => "Imobviouslyinvalid",
|
"name" => user.nickname,
|
||||||
"redirect_uri" => app.redirect_uris,
|
"password" => "test",
|
||||||
"client_id" => app.client_id,
|
"client_id" => app.client_id,
|
||||||
"client_secret" => app.client_secret
|
"redirect_uri" => app.redirect_uris,
|
||||||
})
|
"scope" => "read write",
|
||||||
|
"state" => "statepassed"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
assert resp = json_response(conn, 400)
|
target = redirected_to(conn)
|
||||||
assert %{"error" => _} = json_response(conn, 400)
|
assert target =~ app.redirect_uris
|
||||||
refute Map.has_key?(resp, "access_token")
|
|
||||||
|
query = URI.parse(target).query |> URI.query_decoder() |> Map.new()
|
||||||
|
|
||||||
|
assert %{"state" => "statepassed", "code" => code} = query
|
||||||
|
auth = Repo.get_by(Authorization, token: code)
|
||||||
|
assert auth
|
||||||
|
assert auth.scopes == ["read", "write"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 401 for wrong credentials", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> post("/oauth/authorize", %{
|
||||||
|
"authorization" => %{
|
||||||
|
"name" => user.nickname,
|
||||||
|
"password" => "wrong",
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"redirect_uri" => app.redirect_uris,
|
||||||
|
"state" => "statepassed",
|
||||||
|
"scope" => Enum.join(app.scopes, " ")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> html_response(:unauthorized)
|
||||||
|
|
||||||
|
# Keep the details
|
||||||
|
assert result =~ app.client_id
|
||||||
|
assert result =~ app.redirect_uris
|
||||||
|
|
||||||
|
# Error message
|
||||||
|
assert result =~ "Invalid Username/Password"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 401 for missing scopes", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> post("/oauth/authorize", %{
|
||||||
|
"authorization" => %{
|
||||||
|
"name" => user.nickname,
|
||||||
|
"password" => "test",
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"redirect_uri" => app.redirect_uris,
|
||||||
|
"state" => "statepassed",
|
||||||
|
"scope" => ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> html_response(:unauthorized)
|
||||||
|
|
||||||
|
# Keep the details
|
||||||
|
assert result =~ app.client_id
|
||||||
|
assert result =~ app.redirect_uris
|
||||||
|
|
||||||
|
# Error message
|
||||||
|
assert result =~ "This action is outside the authorized scopes"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 401 for scopes beyond app scopes", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> post("/oauth/authorize", %{
|
||||||
|
"authorization" => %{
|
||||||
|
"name" => user.nickname,
|
||||||
|
"password" => "test",
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"redirect_uri" => app.redirect_uris,
|
||||||
|
"state" => "statepassed",
|
||||||
|
"scope" => "read write follow"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> html_response(:unauthorized)
|
||||||
|
|
||||||
|
# Keep the details
|
||||||
|
assert result =~ app.client_id
|
||||||
|
assert result =~ app.redirect_uris
|
||||||
|
|
||||||
|
# Error message
|
||||||
|
assert result =~ "This action is outside the authorized scopes"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "POST /oauth/token" do
|
||||||
|
test "issues a token for an all-body request" do
|
||||||
|
user = insert(:user)
|
||||||
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
|
{:ok, auth} = Authorization.create_authorization(app, user, ["write"])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "authorization_code",
|
||||||
|
"code" => auth.token,
|
||||||
|
"redirect_uri" => app.redirect_uris,
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"client_secret" => app.client_secret
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"access_token" => token, "me" => ap_id} = json_response(conn, 200)
|
||||||
|
|
||||||
|
token = Repo.get_by(Token, token: token)
|
||||||
|
assert token
|
||||||
|
assert token.scopes == auth.scopes
|
||||||
|
assert user.ap_id == ap_id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
|
||||||
|
password = "testpassword"
|
||||||
|
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
|
||||||
|
|
||||||
|
app = insert(:oauth_app, scopes: ["read", "write"])
|
||||||
|
|
||||||
|
# Note: "scope" param is intentionally omitted
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "password",
|
||||||
|
"username" => user.nickname,
|
||||||
|
"password" => password,
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"client_secret" => app.client_secret
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"access_token" => token} = json_response(conn, 200)
|
||||||
|
|
||||||
|
token = Repo.get_by(Token, token: token)
|
||||||
|
assert token
|
||||||
|
assert token.scopes == app.scopes
|
||||||
|
end
|
||||||
|
|
||||||
|
test "issues a token for request with HTTP basic auth client credentials" do
|
||||||
|
user = insert(:user)
|
||||||
|
app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"])
|
||||||
|
|
||||||
|
{:ok, auth} = Authorization.create_authorization(app, user, ["scope1", "scope2"])
|
||||||
|
assert auth.scopes == ["scope1", "scope2"]
|
||||||
|
|
||||||
|
app_encoded =
|
||||||
|
(URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret))
|
||||||
|
|> Base.encode64()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> put_req_header("authorization", "Basic " <> app_encoded)
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "authorization_code",
|
||||||
|
"code" => auth.token,
|
||||||
|
"redirect_uri" => app.redirect_uris
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"access_token" => token, "scope" => scope} = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert scope == "scope1 scope2"
|
||||||
|
|
||||||
|
token = Repo.get_by(Token, token: token)
|
||||||
|
assert token
|
||||||
|
assert token.scopes == ["scope1", "scope2"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects token exchange with invalid client credentials" do
|
||||||
|
user = insert(:user)
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
{:ok, auth} = Authorization.create_authorization(app, user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=")
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "authorization_code",
|
||||||
|
"code" => auth.token,
|
||||||
|
"redirect_uri" => app.redirect_uris
|
||||||
|
})
|
||||||
|
|
||||||
|
assert resp = json_response(conn, 400)
|
||||||
|
assert %{"error" => _} = resp
|
||||||
|
refute Map.has_key?(resp, "access_token")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do
|
||||||
|
setting = Pleroma.Config.get([:instance, :account_activation_required])
|
||||||
|
|
||||||
|
unless setting do
|
||||||
|
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||||
|
on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
password = "testpassword"
|
||||||
|
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
|
||||||
|
info_change = Pleroma.User.Info.confirmation_changeset(user.info, :unconfirmed)
|
||||||
|
|
||||||
|
{:ok, user} =
|
||||||
|
user
|
||||||
|
|> Ecto.Changeset.change()
|
||||||
|
|> Ecto.Changeset.put_embed(:info, info_change)
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
refute Pleroma.User.auth_active?(user)
|
||||||
|
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "password",
|
||||||
|
"username" => user.nickname,
|
||||||
|
"password" => password,
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"client_secret" => app.client_secret
|
||||||
|
})
|
||||||
|
|
||||||
|
assert resp = json_response(conn, 403)
|
||||||
|
assert %{"error" => _} = resp
|
||||||
|
refute Map.has_key?(resp, "access_token")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects an invalid authorization code" do
|
||||||
|
app = insert(:oauth_app)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> post("/oauth/token", %{
|
||||||
|
"grant_type" => "authorization_code",
|
||||||
|
"code" => "Imobviouslyinvalid",
|
||||||
|
"redirect_uri" => app.redirect_uris,
|
||||||
|
"client_id" => app.client_id,
|
||||||
|
"client_secret" => app.client_secret
|
||||||
|
})
|
||||||
|
|
||||||
|
assert resp = json_response(conn, 400)
|
||||||
|
assert %{"error" => _} = json_response(conn, 400)
|
||||||
|
refute Map.has_key?(resp, "access_token")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -116,10 +116,10 @@ test "an announce activity" do
|
||||||
|
|
||||||
{:ok, announce, _object} = ActivityPub.announce(user, object)
|
{:ok, announce, _object} = ActivityPub.announce(user, object)
|
||||||
|
|
||||||
announce = Repo.get(Activity, announce.id)
|
announce = Activity.get_by_id(announce.id)
|
||||||
|
|
||||||
note_user = User.get_cached_by_ap_id(note.data["actor"])
|
note_user = User.get_cached_by_ap_id(note.data["actor"])
|
||||||
note = Repo.get(Activity, note.id)
|
note = Activity.get_by_id(note.id)
|
||||||
|
|
||||||
note_xml =
|
note_xml =
|
||||||
ActivityRepresenter.to_simple_form(note, note_user, true)
|
ActivityRepresenter.to_simple_form(note, note_user, true)
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.Web.OStatus
|
alias Pleroma.Web.OStatus
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
|
@ -32,10 +31,10 @@ test "it removes the mentioned activity" do
|
||||||
|
|
||||||
{:ok, [delete]} = OStatus.handle_incoming(incoming)
|
{:ok, [delete]} = OStatus.handle_incoming(incoming)
|
||||||
|
|
||||||
refute Repo.get(Activity, note.id)
|
refute Activity.get_by_id(note.id)
|
||||||
refute Repo.get(Activity, like.id)
|
refute Activity.get_by_id(like.id)
|
||||||
assert Object.get_by_ap_id(note.data["object"]["id"]).data["type"] == "Tombstone"
|
assert Object.get_by_ap_id(note.data["object"]["id"]).data["type"] == "Tombstone"
|
||||||
assert Repo.get(Activity, second_note.id)
|
assert Activity.get_by_id(second_note.id)
|
||||||
assert Object.get_by_ap_id(second_note.data["object"]["id"])
|
assert Object.get_by_ap_id(second_note.data["object"]["id"])
|
||||||
|
|
||||||
assert delete.data["type"] == "Delete"
|
assert delete.data["type"] == "Delete"
|
||||||
|
|
|
@ -154,7 +154,7 @@ test "handle incoming retweets - GS, subscription" do
|
||||||
assert "https://pleroma.soykaf.com/users/lain" in activity.data["to"]
|
assert "https://pleroma.soykaf.com/users/lain" in activity.data["to"]
|
||||||
refute activity.local
|
refute activity.local
|
||||||
|
|
||||||
retweeted_activity = Repo.get(Activity, retweeted_activity.id)
|
retweeted_activity = Activity.get_by_id(retweeted_activity.id)
|
||||||
assert retweeted_activity.data["type"] == "Create"
|
assert retweeted_activity.data["type"] == "Create"
|
||||||
assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain"
|
assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain"
|
||||||
refute retweeted_activity.local
|
refute retweeted_activity.local
|
||||||
|
@ -181,7 +181,7 @@ test "handle incoming retweets - GS, subscription - local message" do
|
||||||
assert user.ap_id in activity.data["to"]
|
assert user.ap_id in activity.data["to"]
|
||||||
refute activity.local
|
refute activity.local
|
||||||
|
|
||||||
retweeted_activity = Repo.get(Activity, retweeted_activity.id)
|
retweeted_activity = Activity.get_by_id(retweeted_activity.id)
|
||||||
assert note_activity.id == retweeted_activity.id
|
assert note_activity.id == retweeted_activity.id
|
||||||
assert retweeted_activity.data["type"] == "Create"
|
assert retweeted_activity.data["type"] == "Create"
|
||||||
assert retweeted_activity.data["actor"] == user.ap_id
|
assert retweeted_activity.data["actor"] == user.ap_id
|
||||||
|
@ -344,7 +344,7 @@ test "tries to use the information in poco fields" do
|
||||||
|
|
||||||
{:ok, user} = OStatus.find_or_make_user(uri)
|
{:ok, user} = OStatus.find_or_make_user(uri)
|
||||||
|
|
||||||
user = Repo.get(Pleroma.User, user.id)
|
user = Pleroma.User.get_by_id(user.id)
|
||||||
assert user.name == "Constance Variable"
|
assert user.name == "Constance Variable"
|
||||||
assert user.nickname == "lambadalambda@social.heldscal.la"
|
assert user.nickname == "lambadalambda@social.heldscal.la"
|
||||||
assert user.local == false
|
assert user.local == false
|
||||||
|
|
|
@ -99,7 +99,7 @@ test "it pushes an activity to remote accounts it's addressed to" do
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]})
|
{:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]})
|
||||||
user = Repo.get_by(User, ap_id: activity.data["actor"])
|
user = User.get_by_ap_id(activity.data["actor"])
|
||||||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
|
||||||
|
|
||||||
poster = fn url, _data, _headers ->
|
poster = fn url, _data, _headers ->
|
||||||
|
|
|
@ -719,7 +719,7 @@ test "with credentials", %{conn: conn, user: current_user} do
|
||||||
|> with_credentials(current_user.nickname, "test")
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|> post("/api/friendships/create.json", %{user_id: followed.id})
|
|> post("/api/friendships/create.json", %{user_id: followed.id})
|
||||||
|
|
||||||
current_user = Repo.get(User, current_user.id)
|
current_user = User.get_by_id(current_user.id)
|
||||||
assert User.ap_followers(followed) in current_user.following
|
assert User.ap_followers(followed) in current_user.following
|
||||||
|
|
||||||
assert json_response(conn, 200) ==
|
assert json_response(conn, 200) ==
|
||||||
|
@ -734,8 +734,8 @@ test "for restricted account", %{conn: conn, user: current_user} do
|
||||||
|> with_credentials(current_user.nickname, "test")
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|> post("/api/friendships/create.json", %{user_id: followed.id})
|
|> post("/api/friendships/create.json", %{user_id: followed.id})
|
||||||
|
|
||||||
current_user = Repo.get(User, current_user.id)
|
current_user = User.get_by_id(current_user.id)
|
||||||
followed = Repo.get(User, followed.id)
|
followed = User.get_by_id(followed.id)
|
||||||
|
|
||||||
refute User.ap_followers(followed) in current_user.following
|
refute User.ap_followers(followed) in current_user.following
|
||||||
|
|
||||||
|
@ -764,7 +764,7 @@ test "with credentials", %{conn: conn, user: current_user} do
|
||||||
|> with_credentials(current_user.nickname, "test")
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|> post("/api/friendships/destroy.json", %{user_id: followed.id})
|
|> post("/api/friendships/destroy.json", %{user_id: followed.id})
|
||||||
|
|
||||||
current_user = Repo.get(User, current_user.id)
|
current_user = User.get_by_id(current_user.id)
|
||||||
assert current_user.following == [current_user.ap_id]
|
assert current_user.following == [current_user.ap_id]
|
||||||
|
|
||||||
assert json_response(conn, 200) ==
|
assert json_response(conn, 200) ==
|
||||||
|
@ -788,7 +788,7 @@ test "with credentials", %{conn: conn, user: current_user} do
|
||||||
|> with_credentials(current_user.nickname, "test")
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|> post("/api/blocks/create.json", %{user_id: blocked.id})
|
|> post("/api/blocks/create.json", %{user_id: blocked.id})
|
||||||
|
|
||||||
current_user = Repo.get(User, current_user.id)
|
current_user = User.get_by_id(current_user.id)
|
||||||
assert User.blocks?(current_user, blocked)
|
assert User.blocks?(current_user, blocked)
|
||||||
|
|
||||||
assert json_response(conn, 200) ==
|
assert json_response(conn, 200) ==
|
||||||
|
@ -815,7 +815,7 @@ test "with credentials", %{conn: conn, user: current_user} do
|
||||||
|> with_credentials(current_user.nickname, "test")
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|> post("/api/blocks/destroy.json", %{user_id: blocked.id})
|
|> post("/api/blocks/destroy.json", %{user_id: blocked.id})
|
||||||
|
|
||||||
current_user = Repo.get(User, current_user.id)
|
current_user = User.get_by_id(current_user.id)
|
||||||
assert current_user.info.blocks == []
|
assert current_user.info.blocks == []
|
||||||
|
|
||||||
assert json_response(conn, 200) ==
|
assert json_response(conn, 200) ==
|
||||||
|
@ -846,7 +846,7 @@ test "with credentials", %{conn: conn, user: current_user} do
|
||||||
|> with_credentials(current_user.nickname, "test")
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|> post("/api/qvitter/update_avatar.json", %{img: avatar_image})
|
|> post("/api/qvitter/update_avatar.json", %{img: avatar_image})
|
||||||
|
|
||||||
current_user = Repo.get(User, current_user.id)
|
current_user = User.get_by_id(current_user.id)
|
||||||
assert is_map(current_user.avatar)
|
assert is_map(current_user.avatar)
|
||||||
|
|
||||||
assert json_response(conn, 200) ==
|
assert json_response(conn, 200) ==
|
||||||
|
@ -954,8 +954,8 @@ test "with credentials", %{conn: conn, user: current_user} do
|
||||||
|> with_credentials(current_user.nickname, "test")
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|> post(request_path)
|
|> post(request_path)
|
||||||
|
|
||||||
activity = Repo.get(Activity, note_activity.id)
|
activity = Activity.get_by_id(note_activity.id)
|
||||||
activity_user = Repo.get_by(User, ap_id: note_activity.data["actor"])
|
activity_user = User.get_by_ap_id(note_activity.data["actor"])
|
||||||
|
|
||||||
assert json_response(response, 200) ==
|
assert json_response(response, 200) ==
|
||||||
ActivityView.render("activity.json", %{
|
ActivityView.render("activity.json", %{
|
||||||
|
@ -992,8 +992,8 @@ test "with credentials", %{conn: conn, user: current_user} do
|
||||||
|> with_credentials(current_user.nickname, "test")
|
|> with_credentials(current_user.nickname, "test")
|
||||||
|> post(request_path)
|
|> post(request_path)
|
||||||
|
|
||||||
activity = Repo.get(Activity, note_activity.id)
|
activity = Activity.get_by_id(note_activity.id)
|
||||||
activity_user = Repo.get_by(User, ap_id: note_activity.data["actor"])
|
activity_user = User.get_by_ap_id(note_activity.data["actor"])
|
||||||
|
|
||||||
assert json_response(response, 200) ==
|
assert json_response(response, 200) ==
|
||||||
ActivityView.render("activity.json", %{
|
ActivityView.render("activity.json", %{
|
||||||
|
@ -1021,7 +1021,7 @@ test "it creates a new user", %{conn: conn} do
|
||||||
|
|
||||||
user = json_response(conn, 200)
|
user = json_response(conn, 200)
|
||||||
|
|
||||||
fetched_user = Repo.get_by(User, nickname: "lain")
|
fetched_user = User.get_by_nickname("lain")
|
||||||
assert user == UserView.render("show.json", %{user: fetched_user})
|
assert user == UserView.render("show.json", %{user: fetched_user})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1109,7 +1109,7 @@ test "it redirects to root url", %{conn: conn, user: user} do
|
||||||
test "it confirms the user account", %{conn: conn, user: user} do
|
test "it confirms the user account", %{conn: conn, user: user} do
|
||||||
get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}")
|
get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}")
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
refute user.info.confirmation_pending
|
refute user.info.confirmation_pending
|
||||||
refute user.info.confirmation_token
|
refute user.info.confirmation_token
|
||||||
|
@ -1727,7 +1727,7 @@ test "with credentials, valid password and matching new password and confirmatio
|
||||||
})
|
})
|
||||||
|
|
||||||
assert json_response(conn, 200) == %{"status" => "success"}
|
assert json_response(conn, 200) == %{"status" => "success"}
|
||||||
fetched_user = Repo.get(User, current_user.id)
|
fetched_user = User.get_by_id(current_user.id)
|
||||||
assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true
|
assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1768,8 +1768,8 @@ test "it lists friend requests" do
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = Repo.get(User, other_user.id)
|
other_user = User.get_by_id(other_user.id)
|
||||||
|
|
||||||
assert User.following?(other_user, user) == false
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
@ -1808,8 +1808,8 @@ test "it approves a friend request" do
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = Repo.get(User, other_user.id)
|
other_user = User.get_by_id(other_user.id)
|
||||||
|
|
||||||
assert User.following?(other_user, user) == false
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
@ -1831,8 +1831,8 @@ test "it denies a friend request" do
|
||||||
|
|
||||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = Repo.get(User, other_user.id)
|
other_user = User.get_by_id(other_user.id)
|
||||||
|
|
||||||
assert User.following?(other_user, user) == false
|
assert User.following?(other_user, user) == false
|
||||||
|
|
||||||
|
|
|
@ -275,7 +275,7 @@ test "it registers a new user and returns the user." do
|
||||||
|
|
||||||
{:ok, user} = TwitterAPI.register_user(data)
|
{:ok, user} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
fetched_user = Repo.get_by(User, nickname: "lain")
|
fetched_user = User.get_by_nickname("lain")
|
||||||
|
|
||||||
assert UserView.render("show.json", %{user: user}) ==
|
assert UserView.render("show.json", %{user: user}) ==
|
||||||
UserView.render("show.json", %{user: fetched_user})
|
UserView.render("show.json", %{user: fetched_user})
|
||||||
|
@ -293,7 +293,7 @@ test "it registers a new user with empty string in bio and returns the user." do
|
||||||
|
|
||||||
{:ok, user} = TwitterAPI.register_user(data)
|
{:ok, user} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
fetched_user = Repo.get_by(User, nickname: "lain")
|
fetched_user = User.get_by_nickname("lain")
|
||||||
|
|
||||||
assert UserView.render("show.json", %{user: user}) ==
|
assert UserView.render("show.json", %{user: user}) ==
|
||||||
UserView.render("show.json", %{user: fetched_user})
|
UserView.render("show.json", %{user: fetched_user})
|
||||||
|
@ -369,7 +369,7 @@ test "it registers a new user via invite token and returns the user." do
|
||||||
|
|
||||||
{:ok, user} = TwitterAPI.register_user(data)
|
{:ok, user} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
fetched_user = Repo.get_by(User, nickname: "vinny")
|
fetched_user = User.get_by_nickname("vinny")
|
||||||
token = Repo.get_by(UserInviteToken, token: token.token)
|
token = Repo.get_by(UserInviteToken, token: token.token)
|
||||||
|
|
||||||
assert token.used == true
|
assert token.used == true
|
||||||
|
@ -393,7 +393,7 @@ test "it returns an error if invalid token submitted" do
|
||||||
{:error, msg} = TwitterAPI.register_user(data)
|
{:error, msg} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
assert msg == "Invalid token"
|
assert msg == "Invalid token"
|
||||||
refute Repo.get_by(User, nickname: "GrimReaper")
|
refute User.get_by_nickname("GrimReaper")
|
||||||
end
|
end
|
||||||
|
|
||||||
@moduletag skip: "needs 'registrations_open: false' in config"
|
@moduletag skip: "needs 'registrations_open: false' in config"
|
||||||
|
@ -414,7 +414,7 @@ test "it returns an error if expired token submitted" do
|
||||||
{:error, msg} = TwitterAPI.register_user(data)
|
{:error, msg} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
assert msg == "Expired token"
|
assert msg == "Expired token"
|
||||||
refute Repo.get_by(User, nickname: "GrimReaper")
|
refute User.get_by_nickname("GrimReaper")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns the error on registration problems" do
|
test "it returns the error on registration problems" do
|
||||||
|
@ -429,7 +429,7 @@ test "it returns the error on registration problems" do
|
||||||
{:error, error_object} = TwitterAPI.register_user(data)
|
{:error, error_object} = TwitterAPI.register_user(data)
|
||||||
|
|
||||||
assert is_binary(error_object[:error])
|
assert is_binary(error_object[:error])
|
||||||
refute Repo.get_by(User, nickname: "lain")
|
refute User.get_by_nickname("lain")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it assigns an integer conversation_id" do
|
test "it assigns an integer conversation_id" do
|
||||||
|
|
|
@ -6,6 +6,11 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
describe "POST /api/pleroma/follow_import" do
|
describe "POST /api/pleroma/follow_import" do
|
||||||
test "it returns HTTP 200", %{conn: conn} do
|
test "it returns HTTP 200", %{conn: conn} do
|
||||||
user1 = insert(:user)
|
user1 = insert(:user)
|
||||||
|
@ -164,4 +169,26 @@ test "returns everything in :pleroma, :frontend_configurations", %{conn: conn} d
|
||||||
assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!()
|
assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "GET /ostatus_subscribe?acct=...." do
|
||||||
|
test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
get(
|
||||||
|
conn,
|
||||||
|
"/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert redirected_to(conn) =~ "/notice/"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "show follow account page if the `acct` is a account link", %{conn: conn} do
|
||||||
|
response =
|
||||||
|
get(
|
||||||
|
conn,
|
||||||
|
"/ostatus_subscribe?acct=https://mastodon.social/users/emelie"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert html_response(response, 200) =~ "Log in to follow"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -281,7 +281,7 @@ test "an announce activity" do
|
||||||
|
|
||||||
convo_id = Utils.context_to_conversation_id(activity.data["object"]["context"])
|
convo_id = Utils.context_to_conversation_id(activity.data["object"]["context"])
|
||||||
|
|
||||||
activity = Repo.get(Activity, activity.id)
|
activity = Activity.get_by_id(activity.id)
|
||||||
|
|
||||||
result = ActivityView.render("activity.json", activity: announce)
|
result = ActivityView.render("activity.json", activity: announce)
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,7 @@ test "A blocked user for the blocker" do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blocker = Repo.get(User, blocker.id)
|
blocker = User.get_by_id(blocker.id)
|
||||||
assert represented == UserView.render("show.json", %{user: user, for: blocker})
|
assert represented == UserView.render("show.json", %{user: user, for: blocker})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue