Merge remote-tracking branch 'origin/develop' into fix-slow-relationships

This commit is contained in:
lain 2019-04-02 16:10:38 +02:00
commit 39bcf93007
68 changed files with 2139 additions and 834 deletions

View file

@ -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

View file

403
CC-BY-NC-ND-4.0 Normal file
View 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
View 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
View 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.

View file

@ -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 dont 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

View file

@ -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"

View file

@ -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"

View file

@ -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`)

View file

@ -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.

View 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

View file

@ -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]
```

View file

@ -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:

View file

@ -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})

View file

@ -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}

View file

@ -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() ++

View file

@ -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)

View file

@ -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

View file

@ -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"])

View file

@ -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

View file

@ -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(

View file

@ -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

View file

@ -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 ->

View file

@ -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 ->

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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
_ -> _ ->

View file

@ -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)

View file

@ -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

View file

@ -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"},

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

306
test/fixtures/httpoison_mock/emelie.atom vendored Normal file
View 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">&lt;p&gt;23 / &lt;a href="https://mastodon.social/tags/sweden" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;Sweden&lt;/span&gt;&lt;/a&gt; / &lt;a href="https://mastodon.social/tags/artist" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;Artist&lt;/span&gt;&lt;/a&gt; / &lt;a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;Equestrian&lt;/span&gt;&lt;/a&gt; / &lt;a href="https://mastodon.social/tags/gamedev" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;GameDev&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;If I ain&amp;apos;t spending time with my pets, I&amp;apos;m probably drawing. 🐴 🐱 🐰&lt;/p&gt;</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">&lt;p&gt;Me: I&amp;apos;m going to make this vital change to my world building in the morning, no way I&amp;apos;ll forget this, it&amp;apos;s too big of a deal&lt;br /&gt;Also me: forgets&lt;/p&gt;</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">&lt;p&gt;&lt;span class="h-card"&gt;&lt;a href="https://mastodon.social/@Fergant" class="u-url mention"&gt;@&lt;span&gt;Fergant&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; Dom är i stort sett religiös skrift vid det här laget 👏👏&lt;/p&gt;&lt;p&gt;har dock bara läst svenska översättningen, kanske är dags att jag läser dom på engelska&lt;/p&gt;</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">&lt;p&gt;What&amp;apos;s you people&amp;apos;s favourite fantasy books? Give me some hot tips 🌞&lt;/p&gt;</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">&lt;p&gt;Stick them legs out 💃 &lt;a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastocats&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</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">&lt;p&gt;long 🐱 &lt;a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastocats&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</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">&lt;p&gt;You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 &lt;a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastocats&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</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">&lt;p&gt;Hello look at this boy having a decent haircut for once &lt;a href="https://mastodon.social/tags/mastohorses" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastohorses&lt;/span&gt;&lt;/a&gt; &lt;a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;equestrian&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</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">&lt;p&gt;Sorry did I disturb the who-is-the-longest-cat competition ? &lt;a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag"&gt;#&lt;span&gt;mastocats&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</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">&lt;p&gt;I&amp;apos;m re-watching Tales from Earthsea for the first time since I read the books, and that Therru doesn&amp;apos;t squash Cob like a spider, as Orm Embar did is a wasted opportunity tbh&lt;/p&gt;</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">&lt;p&gt;I gave my cats some mackerel and they ate it all in 0.3 seconds, and now they won&amp;apos;t stop meowing for more, and I&amp;apos;m tired plz shut up&lt;/p&gt;</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">&lt;p&gt;yet I&amp;apos;m confused about this american dude with a gun, like the heck r ya doin in mah ghibli&lt;/p&gt;</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">&lt;p&gt;2 hours into Ni no Kuni 2 and I&amp;apos;ve already sold my soul to this game&lt;/p&gt;</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">&lt;p&gt;Pippi Longstocking the original one-punch /man&lt;/p&gt;</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">&lt;p&gt;I&amp;apos;ve had so much wine I thought I had a 3rd brother&lt;/p&gt;</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">&lt;p&gt;ååååhhh booi&lt;/p&gt;</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">&lt;p&gt;&lt;span class="h-card"&gt;&lt;a href="https://thraeryn.net/@thraeryn" class="u-url mention"&gt;@&lt;span&gt;thraeryn&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; if I spent 1 hour and a half watching this monstrosity, I need to&lt;/p&gt;</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">&lt;p&gt;&lt;span class="h-card"&gt;&lt;a href="https://icosahedron.website/@Trev" class="u-url mention"&gt;@&lt;span&gt;Trev&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; *hugs* ✨&lt;/p&gt;</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>

View 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": []
}
}
}

View 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"
}

View file

@ -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

View file

@ -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

View file

@ -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",
_, _,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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),

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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 ->

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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