Merge remote-tracking branch 'origin/develop' into features/mastoapi/2.6.0-conversations
This commit is contained in:
commit
a9f805c871
|
@ -35,3 +35,6 @@ erl_crash.dump
|
||||||
|
|
||||||
# Editor config
|
# Editor config
|
||||||
/.vscode/
|
/.vscode/
|
||||||
|
|
||||||
|
# Prevent committing docs files
|
||||||
|
/priv/static/doc/*
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -8,6 +8,10 @@
|
||||||
# General application configuration
|
# General application configuration
|
||||||
config :pleroma, ecto_repos: [Pleroma.Repo]
|
config :pleroma, ecto_repos: [Pleroma.Repo]
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Repo,
|
||||||
|
types: Pleroma.PostgresTypes,
|
||||||
|
telemetry_event: [Pleroma.Repo.Instrumenter]
|
||||||
|
|
||||||
config :pleroma, Pleroma.Captcha,
|
config :pleroma, Pleroma.Captcha,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
seconds_valid: 60,
|
seconds_valid: 60,
|
||||||
|
@ -54,7 +58,13 @@
|
||||||
cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
|
cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
|
||||||
files: "https://mdii.sakura.ne.jp"
|
files: "https://mdii.sakura.ne.jp"
|
||||||
|
|
||||||
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
|
config :pleroma, :emoji,
|
||||||
|
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||||
|
groups: [
|
||||||
|
# Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
|
||||||
|
Finmoji: "/finmoji/128px/*-128.png",
|
||||||
|
Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
|
||||||
|
]
|
||||||
|
|
||||||
config :pleroma, :uri_schemes,
|
config :pleroma, :uri_schemes,
|
||||||
valid_schemes: [
|
valid_schemes: [
|
||||||
|
@ -87,6 +97,7 @@
|
||||||
|
|
||||||
# Configures the endpoint
|
# Configures the endpoint
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
|
instrumenters: [Pleroma.Web.Endpoint.Instrumenter],
|
||||||
url: [host: "localhost"],
|
url: [host: "localhost"],
|
||||||
http: [
|
http: [
|
||||||
dispatch: [
|
dispatch: [
|
||||||
|
@ -118,6 +129,11 @@
|
||||||
format: "$metadata[$level] $message",
|
format: "$metadata[$level] $message",
|
||||||
metadata: [:request_id]
|
metadata: [:request_id]
|
||||||
|
|
||||||
|
config :quack,
|
||||||
|
level: :warn,
|
||||||
|
meta: [:all],
|
||||||
|
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
|
||||||
|
|
||||||
config :mime, :types, %{
|
config :mime, :types, %{
|
||||||
"application/xml" => ["xml"],
|
"application/xml" => ["xml"],
|
||||||
"application/xrd+xml" => ["xrd+xml"],
|
"application/xrd+xml" => ["xrd+xml"],
|
||||||
|
@ -351,7 +367,10 @@
|
||||||
config :pleroma_job_queue, :queues,
|
config :pleroma_job_queue, :queues,
|
||||||
federator_incoming: 50,
|
federator_incoming: 50,
|
||||||
federator_outgoing: 50,
|
federator_outgoing: 50,
|
||||||
mailer: 10
|
web_push: 50,
|
||||||
|
mailer: 10,
|
||||||
|
transmogrifier: 20,
|
||||||
|
scheduled_activities: 10
|
||||||
|
|
||||||
config :pleroma, :fetch_initial_posts,
|
config :pleroma, :fetch_initial_posts,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -378,8 +397,31 @@
|
||||||
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"
|
||||||
|
|
||||||
|
oauth_consumer_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
|
||||||
|
|
||||||
|
ueberauth_providers =
|
||||||
|
for strategy <- oauth_consumer_strategies do
|
||||||
|
strategy_module_name = "Elixir.Ueberauth.Strategy.#{String.capitalize(strategy)}"
|
||||||
|
strategy_module = String.to_atom(strategy_module_name)
|
||||||
|
{String.to_atom(strategy), {strategy_module, [callback_params: ["state"]]}}
|
||||||
|
end
|
||||||
|
|
||||||
|
config :ueberauth,
|
||||||
|
Ueberauth,
|
||||||
|
base_path: "/oauth",
|
||||||
|
providers: ueberauth_providers
|
||||||
|
|
||||||
|
config :pleroma, :auth, oauth_consumer_strategies: oauth_consumer_strategies
|
||||||
|
|
||||||
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
|
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Sendmail
|
||||||
|
|
||||||
|
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.ScheduledActivity,
|
||||||
|
daily_user_limit: 25,
|
||||||
|
total_user_limit: 300,
|
||||||
|
enabled: true
|
||||||
|
|
||||||
# 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"
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
|
protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]
|
||||||
],
|
],
|
||||||
protocol: "http",
|
protocol: "http",
|
||||||
secure_cookie_flag: false,
|
|
||||||
debug_errors: true,
|
debug_errors: true,
|
||||||
code_reloader: true,
|
code_reloader: true,
|
||||||
check_origin: false,
|
check_origin: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
firefox, /emoji/Firefox.gif
|
firefox, /emoji/Firefox.gif, Gif,Fun
|
||||||
blank, /emoji/blank.png
|
blank, /emoji/blank.png, Fun
|
||||||
f_00b, /emoji/f_00b.png
|
f_00b, /emoji/f_00b.png
|
||||||
f_00b11b, /emoji/f_00b11b.png
|
f_00b11b, /emoji/f_00b11b.png
|
||||||
f_00b33b, /emoji/f_00b33b.png
|
f_00b33b, /emoji/f_00b33b.png
|
||||||
|
@ -28,4 +28,3 @@ f_33b00b, /emoji/f_33b00b.png
|
||||||
f_33b22b, /emoji/f_33b22b.png
|
f_33b22b, /emoji/f_33b22b.png
|
||||||
f_33h, /emoji/f_33h.png
|
f_33h, /emoji/f_33h.png
|
||||||
f_33t, /emoji/f_33t.png
|
f_33t, /emoji/f_33t.png
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,11 @@
|
||||||
|
|
||||||
config :pleroma_job_queue, disabled: true
|
config :pleroma_job_queue, disabled: true
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.ScheduledActivity,
|
||||||
|
daily_user_limit: 2,
|
||||||
|
total_user_limit: 3,
|
||||||
|
enabled: false
|
||||||
|
|
||||||
try do
|
try do
|
||||||
import_config "test.secret.exs"
|
import_config "test.secret.exs"
|
||||||
rescue
|
rescue
|
||||||
|
|
|
@ -58,6 +58,26 @@ Authentication is required and the user must be an admin.
|
||||||
- `password`
|
- `password`
|
||||||
- Response: User’s nickname
|
- Response: User’s nickname
|
||||||
|
|
||||||
|
## `/api/pleroma/admin/user/follow`
|
||||||
|
### Make a user follow another user
|
||||||
|
|
||||||
|
- Methods: `POST`
|
||||||
|
- Params:
|
||||||
|
- `follower`: The nickname of the follower
|
||||||
|
- `followed`: The nickname of the followed
|
||||||
|
- Response:
|
||||||
|
- "ok"
|
||||||
|
|
||||||
|
## `/api/pleroma/admin/user/unfollow`
|
||||||
|
### Make a user unfollow another user
|
||||||
|
|
||||||
|
- Methods: `POST`
|
||||||
|
- Params:
|
||||||
|
- `follower`: The nickname of the follower
|
||||||
|
- `followed`: The nickname of the followed
|
||||||
|
- Response:
|
||||||
|
- "ok"
|
||||||
|
|
||||||
## `/api/pleroma/admin/users/:nickname/toggle_activation`
|
## `/api/pleroma/admin/users/:nickname/toggle_activation`
|
||||||
|
|
||||||
### Toggle user activation
|
### Toggle user activation
|
||||||
|
|
|
@ -44,3 +44,9 @@ Has these additional fields under the `pleroma` object:
|
||||||
Has these additional fields under the `pleroma` object:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
- `is_seen`: true if the notification was read by the user
|
- `is_seen`: true if the notification was read by the user
|
||||||
|
|
||||||
|
## POST `/api/v1/statuses`
|
||||||
|
|
||||||
|
Additional parameters can be added to the JSON body/Form data:
|
||||||
|
|
||||||
|
- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
|
||||||
|
|
|
@ -10,7 +10,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
||||||
* Authentication: not required
|
* Authentication: not required
|
||||||
* Params: none
|
* Params: none
|
||||||
* Response: JSON
|
* Response: JSON
|
||||||
* Example response: `{"kalsarikannit_f":"/finmoji/128px/kalsarikannit_f-128.png","perkele":"/finmoji/128px/perkele-128.png","blobdab":"/emoji/blobdab.png","happiness":"/finmoji/128px/happiness-128.png"}`
|
* Example response: `[{"kalsarikannit_f":{"tags":["Finmoji"],"image_url":"/finmoji/128px/kalsarikannit_f-128.png"}},{"perkele":{"tags":["Finmoji"],"image_url":"/finmoji/128px/perkele-128.png"}},{"blobdab":{"tags":["SomeTag"],"image_url":"/emoji/blobdab.png"}},"happiness":{"tags":["Finmoji"],"image_url":"/finmoji/128px/happiness-128.png"}}]`
|
||||||
* Note: Same data as Mastodon API’s `/api/v1/custom_emojis` but in a different format
|
* Note: Same data as Mastodon API’s `/api/v1/custom_emojis` but in a different format
|
||||||
|
|
||||||
## `/api/pleroma/follow_import`
|
## `/api/pleroma/follow_import`
|
||||||
|
@ -27,14 +27,14 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
||||||
* Method: `GET`
|
* Method: `GET`
|
||||||
* Authentication: not required
|
* Authentication: not required
|
||||||
* Params: none
|
* Params: none
|
||||||
* Response: Provider specific JSON, the only guaranteed parameter is `type`
|
* Response: Provider specific JSON, the only guaranteed parameter is `type`
|
||||||
* Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint"}`
|
* Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint"}`
|
||||||
|
|
||||||
## `/api/pleroma/delete_account`
|
## `/api/pleroma/delete_account`
|
||||||
### Delete an account
|
### Delete an account
|
||||||
* Method `POST`
|
* Method `POST`
|
||||||
* Authentication: required
|
* Authentication: required
|
||||||
* Params:
|
* Params:
|
||||||
* `password`: user's password
|
* `password`: user's password
|
||||||
* Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
|
* Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
|
||||||
* Example response: `{"error": "Invalid password."}`
|
* Example response: `{"error": "Invalid password."}`
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Prometheus Metrics
|
||||||
|
|
||||||
|
Pleroma includes support for exporting metrics via the [prometheus_ex](https://github.com/deadtrickster/prometheus.ex) library.
|
||||||
|
|
||||||
|
## `/api/pleroma/app_metrics`
|
||||||
|
### Exports Prometheus application metrics
|
||||||
|
* Method: `GET`
|
||||||
|
* Authentication: not required
|
||||||
|
* Params: none
|
||||||
|
* Response: JSON
|
||||||
|
|
||||||
|
## Grafana
|
||||||
|
### Config example
|
||||||
|
The following is a config example to use with [Grafana](https://grafana.com)
|
||||||
|
|
||||||
|
```
|
||||||
|
- job_name: 'beam'
|
||||||
|
metrics_path: /api/pleroma/app_metrics
|
||||||
|
scheme: https
|
||||||
|
static_configs:
|
||||||
|
- targets: ['pleroma.soykaf.com']
|
||||||
|
```
|
|
@ -105,7 +105,7 @@ config :pleroma, Pleroma.Mailer,
|
||||||
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
|
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
|
||||||
|
|
||||||
## :logger
|
## :logger
|
||||||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
|
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog, and `Quack.Logger` to log to Slack
|
||||||
|
|
||||||
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
|
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
|
||||||
```
|
```
|
||||||
|
@ -128,6 +128,24 @@ config :logger, :ex_syslogger,
|
||||||
|
|
||||||
See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/)
|
See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/)
|
||||||
|
|
||||||
|
An example of logging info to local syslog, but warn to a Slack channel:
|
||||||
|
```
|
||||||
|
config :logger,
|
||||||
|
backends: [ {ExSyslogger, :ex_syslogger}, Quack.Logger ],
|
||||||
|
level: :info
|
||||||
|
|
||||||
|
config :logger, :ex_syslogger,
|
||||||
|
level: :info,
|
||||||
|
ident: "pleroma",
|
||||||
|
format: "$metadata[$level] $message"
|
||||||
|
|
||||||
|
config :quack,
|
||||||
|
level: :warn,
|
||||||
|
meta: [:all],
|
||||||
|
webhook_url: "https://hooks.slack.com/services/YOUR-API-KEY-HERE"
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [Quack Github](https://github.com/azohra/quack) for more details
|
||||||
|
|
||||||
## :frontend_configurations
|
## :frontend_configurations
|
||||||
|
|
||||||
|
@ -200,14 +218,14 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
||||||
- `port`
|
- `port`
|
||||||
* `url` - a list containing the configuration for generating urls, accepts
|
* `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`)
|
- `host` - the host without the scheme and a post (e.g `example.com`, not `https://example.com:2020`)
|
||||||
- `scheme` - e.g `http`, `https`
|
- `scheme` - e.g `http`, `https`
|
||||||
- `port`
|
- `port`
|
||||||
- `path`
|
- `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
|
**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:
|
Example:
|
||||||
```elixir
|
```elixir
|
||||||
config :pleroma, Pleroma.Web.Endpoint,
|
config :pleroma, Pleroma.Web.Endpoint,
|
||||||
url: [host: "example.com", port: 2020, scheme: "https"],
|
url: [host: "example.com", port: 2020, scheme: "https"],
|
||||||
|
@ -296,9 +314,13 @@ curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerando
|
||||||
[Pleroma Job Queue](https://git.pleroma.social/pleroma/pleroma_job_queue) configuration: a list of queues with maximum concurrent jobs.
|
[Pleroma Job Queue](https://git.pleroma.social/pleroma/pleroma_job_queue) configuration: a list of queues with maximum concurrent jobs.
|
||||||
|
|
||||||
Pleroma has the following queues:
|
Pleroma has the following queues:
|
||||||
|
|
||||||
* `federator_outgoing` - Outgoing federation
|
* `federator_outgoing` - Outgoing federation
|
||||||
* `federator_incoming` - Incoming federation
|
* `federator_incoming` - Incoming federation
|
||||||
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
|
* `mailer` - Email sender, see [`Pleroma.Mailer`](#pleroma-mailer)
|
||||||
|
* `transmogrifier` - Transmogrifier
|
||||||
|
* `web_push` - Web push notifications
|
||||||
|
* `scheduled_activities` - Scheduled activities, see [`Pleroma.ScheduledActivities`](#pleromascheduledactivity)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -372,6 +394,17 @@ config :auto_linker,
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Pleroma.ScheduledActivity
|
||||||
|
|
||||||
|
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)
|
||||||
|
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
|
||||||
|
* `enabled`: whether scheduled activities are sent to the job queue to be executed
|
||||||
|
|
||||||
|
## Pleroma.Web.Auth.Authenticator
|
||||||
|
|
||||||
|
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
|
||||||
|
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
|
||||||
|
|
||||||
## :ldap
|
## :ldap
|
||||||
|
|
||||||
Use LDAP for user authentication. When a user logs in to the Pleroma
|
Use LDAP for user authentication. When a user logs in to the Pleroma
|
||||||
|
@ -390,7 +423,61 @@ Pleroma account will be created with the same name as the LDAP user name.
|
||||||
* `base`: LDAP base, e.g. "dc=example,dc=com"
|
* `base`: LDAP base, e.g. "dc=example,dc=com"
|
||||||
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
|
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
|
||||||
|
|
||||||
## Pleroma.Web.Auth.Authenticator
|
## :auth
|
||||||
|
|
||||||
* `Pleroma.Web.Auth.PleromaAuthenticator`: default database authenticator
|
Authentication / authorization settings.
|
||||||
* `Pleroma.Web.Auth.LDAPAuthenticator`: LDAP authentication
|
|
||||||
|
* `auth_template`: authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.eex`.
|
||||||
|
* `oauth_consumer_template`: OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.
|
||||||
|
* `oauth_consumer_strategies`: the list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable.
|
||||||
|
|
||||||
|
# OAuth consumer mode
|
||||||
|
|
||||||
|
OAuth consumer mode allows sign in / sign up via external OAuth providers (e.g. Twitter, Facebook, Google, Microsoft, etc.).
|
||||||
|
Implementation is based on Ueberauth; see the list of [available strategies](https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies).
|
||||||
|
|
||||||
|
Note: each strategy is shipped as a separate dependency; in order to get the strategies, run `OAUTH_CONSUMER_STRATEGIES="..." mix deps.get`,
|
||||||
|
e.g. `OAUTH_CONSUMER_STRATEGIES="twitter facebook google microsoft" mix deps.get`.
|
||||||
|
The server should also be started with `OAUTH_CONSUMER_STRATEGIES="..." mix phx.server` in case you enable any strategies.
|
||||||
|
|
||||||
|
Note: each strategy requires separate setup (on external provider side and Pleroma side). Below are the guidelines on setting up most popular strategies.
|
||||||
|
|
||||||
|
* For Twitter, [register an app](https://developer.twitter.com/en/apps), configure callback URL to https://<your_host>/oauth/twitter/callback
|
||||||
|
|
||||||
|
* For Facebook, [register an app](https://developers.facebook.com/apps), configure callback URL to https://<your_host>/oauth/facebook/callback, enable Facebook Login service at https://developers.facebook.com/apps/<app_id>/fb-login/settings/
|
||||||
|
|
||||||
|
* For Google, [register an app](https://console.developers.google.com), configure callback URL to https://<your_host>/oauth/google/callback
|
||||||
|
|
||||||
|
* For Microsoft, [register an app](https://portal.azure.com), configure callback URL to https://<your_host>/oauth/microsoft/callback
|
||||||
|
|
||||||
|
Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings (if any — e.g. see Microsoft below) to `config/prod.secret.exs`,
|
||||||
|
per strategy's documentation (e.g. [ueberauth_twitter](https://github.com/ueberauth/ueberauth_twitter)). Example config basing on environment variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Twitter
|
||||||
|
config :ueberauth, Ueberauth.Strategy.Twitter.OAuth,
|
||||||
|
consumer_key: System.get_env("TWITTER_CONSUMER_KEY"),
|
||||||
|
consumer_secret: System.get_env("TWITTER_CONSUMER_SECRET")
|
||||||
|
|
||||||
|
# Facebook
|
||||||
|
config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
|
||||||
|
client_id: System.get_env("FACEBOOK_APP_ID"),
|
||||||
|
client_secret: System.get_env("FACEBOOK_APP_SECRET"),
|
||||||
|
redirect_uri: System.get_env("FACEBOOK_REDIRECT_URI")
|
||||||
|
|
||||||
|
# Google
|
||||||
|
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
|
||||||
|
client_id: System.get_env("GOOGLE_CLIENT_ID"),
|
||||||
|
client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
|
||||||
|
redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")
|
||||||
|
|
||||||
|
# Microsoft
|
||||||
|
config :ueberauth, Ueberauth.Strategy.Microsoft.OAuth,
|
||||||
|
client_id: System.get_env("MICROSOFT_CLIENT_ID"),
|
||||||
|
client_secret: System.get_env("MICROSOFT_CLIENT_SECRET")
|
||||||
|
|
||||||
|
config :ueberauth, Ueberauth,
|
||||||
|
providers: [
|
||||||
|
microsoft: {Ueberauth.Strategy.Microsoft, [callback_params: []]}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
|
@ -11,8 +11,43 @@ image files (in `/priv/static/emoji/custom`): `happy.png` and `sad.png`
|
||||||
|
|
||||||
content of `config/custom_emoji.txt`:
|
content of `config/custom_emoji.txt`:
|
||||||
```
|
```
|
||||||
happy, /emoji/custom/happy.png
|
happy, /emoji/custom/happy.png, Tag1,Tag2
|
||||||
sad, /emoji/custom/sad.png
|
sad, /emoji/custom/sad.png, Tag1
|
||||||
|
foo, /emoji/custom/foo.png
|
||||||
```
|
```
|
||||||
|
|
||||||
The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
|
The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
|
||||||
|
|
||||||
|
## Emoji tags (groups)
|
||||||
|
|
||||||
|
Default tags are set in `config.exs`.
|
||||||
|
```elixir
|
||||||
|
config :pleroma, :emoji,
|
||||||
|
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||||
|
groups: [
|
||||||
|
Finmoji: "/finmoji/128px/*-128.png",
|
||||||
|
Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Order of the `groups` matters, so to override default tags just put your group on top of the list. E.g:
|
||||||
|
```elixir
|
||||||
|
config :pleroma, :emoji,
|
||||||
|
shortcode_globs: ["/emoji/custom/**/*.png"],
|
||||||
|
groups: [
|
||||||
|
"Finmoji special": "/finmoji/128px/a_trusted_friend-128.png", # special file
|
||||||
|
"Cirno": "/emoji/custom/cirno*.png", # png files in /emoji/custom/ which start with `cirno`
|
||||||
|
"Special group": "/emoji/custom/special_folder/*.png", # png files in /emoji/custom/special_folder/
|
||||||
|
"Another group": "/emoji/custom/special_folder/*/.png", # png files in /emoji/custom/special_folder/ subfolders
|
||||||
|
Finmoji: "/finmoji/128px/*-128.png",
|
||||||
|
Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Priority of tags assigns in emoji.txt and custom.txt:
|
||||||
|
|
||||||
|
`tag in file > special group setting in config.exs > default setting in config.exs`
|
||||||
|
|
||||||
|
Priority for globs:
|
||||||
|
|
||||||
|
`special group setting in config.exs > default setting in config.exs`
|
||||||
|
|
|
@ -81,6 +81,14 @@ def run(["gen" | rest]) do
|
||||||
|
|
||||||
email = Common.get_option(options, :admin_email, "What is your admin email address?")
|
email = Common.get_option(options, :admin_email, "What is your admin email address?")
|
||||||
|
|
||||||
|
indexable =
|
||||||
|
Common.get_option(
|
||||||
|
options,
|
||||||
|
:indexable,
|
||||||
|
"Do you want search engines to index your site? (y/n)",
|
||||||
|
"y"
|
||||||
|
) === "y"
|
||||||
|
|
||||||
dbhost =
|
dbhost =
|
||||||
Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
|
Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
|
||||||
|
|
||||||
|
@ -142,6 +150,8 @@ def run(["gen" | rest]) do
|
||||||
Mix.shell().info("Writing #{psql_path}.")
|
Mix.shell().info("Writing #{psql_path}.")
|
||||||
File.write(psql_path, result_psql)
|
File.write(psql_path, result_psql)
|
||||||
|
|
||||||
|
write_robots_txt(indexable)
|
||||||
|
|
||||||
Mix.shell().info(
|
Mix.shell().info(
|
||||||
"\n" <>
|
"\n" <>
|
||||||
"""
|
"""
|
||||||
|
@ -163,4 +173,28 @@ def run(["gen" | rest]) do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp write_robots_txt(indexable) do
|
||||||
|
robots_txt =
|
||||||
|
EEx.eval_file(
|
||||||
|
Path.expand("robots_txt.eex", __DIR__),
|
||||||
|
indexable: indexable
|
||||||
|
)
|
||||||
|
|
||||||
|
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
|
||||||
|
|
||||||
|
unless File.exists?(static_dir) do
|
||||||
|
File.mkdir_p!(static_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
robots_txt_path = Path.join(static_dir, "robots.txt")
|
||||||
|
|
||||||
|
if File.exists?(robots_txt_path) do
|
||||||
|
File.cp!(robots_txt_path, "#{robots_txt_path}.bak")
|
||||||
|
Mix.shell().info("Backing up existing robots.txt to #{robots_txt_path}.bak")
|
||||||
|
end
|
||||||
|
|
||||||
|
File.write(robots_txt_path, robots_txt)
|
||||||
|
Mix.shell().info("Writing #{robots_txt_path}.")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-Agent: *
|
||||||
|
Disallow: <%= if indexable, do: "", else: "/" %>
|
|
@ -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"
|
||||||
|
@ -23,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
- `--password PASSWORD` - the user's password
|
- `--password PASSWORD` - the user's password
|
||||||
- `--moderator`/`--no-moderator` - whether the user is a moderator
|
- `--moderator`/`--no-moderator` - whether the user is a moderator
|
||||||
- `--admin`/`--no-admin` - whether the user is an admin
|
- `--admin`/`--no-admin` - whether the user is an admin
|
||||||
- `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
|
- `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
|
||||||
|
|
||||||
## Generate an invite link.
|
## Generate an invite link.
|
||||||
|
|
||||||
|
@ -33,6 +32,10 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
|
|
||||||
mix pleroma.user rm NICKNAME
|
mix pleroma.user rm NICKNAME
|
||||||
|
|
||||||
|
## Delete the user's activities.
|
||||||
|
|
||||||
|
mix pleroma.user delete_activities NICKNAME
|
||||||
|
|
||||||
## Deactivate or activate the user's account.
|
## Deactivate or activate the user's account.
|
||||||
|
|
||||||
mix pleroma.user toggle_activated NICKNAME
|
mix pleroma.user toggle_activated NICKNAME
|
||||||
|
@ -202,7 +205,7 @@ def run(["unsubscribe", nickname]) do
|
||||||
{:ok, friends} = User.get_friends(user)
|
{:ok, friends} = User.get_friends(user)
|
||||||
|
|
||||||
Enum.each(friends, fn friend ->
|
Enum.each(friends, fn friend ->
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||||
User.unfollow(user, friend)
|
User.unfollow(user, friend)
|
||||||
|
@ -210,7 +213,7 @@ def run(["unsubscribe", nickname]) do
|
||||||
|
|
||||||
:timer.sleep(500)
|
:timer.sleep(500)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
if Enum.empty?(user.following) do
|
if Enum.empty?(user.following) do
|
||||||
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
|
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||||
|
@ -304,6 +307,18 @@ def run(["invite"]) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run(["delete_activities", nickname]) do
|
||||||
|
Common.start_pleroma()
|
||||||
|
|
||||||
|
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||||
|
User.delete_user_activities(user)
|
||||||
|
Mix.shell().info("User #{nickname} statuses deleted.")
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
Mix.shell().error("No local user #{nickname}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp set_moderator(user, value) do
|
defp set_moderator(user, value) do
|
||||||
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ def used_changeset(struct) do
|
||||||
|
|
||||||
def reset_password(token, data) do
|
def reset_password(token, data) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
%User{} = user <- Repo.get(User, token.user_id),
|
%User{} = user <- User.get_by_id(token.user_id),
|
||||||
{:ok, _user} <- User.reset_password(user, data),
|
{:ok, _user} <- User.reset_password(user, data),
|
||||||
{:ok, token} <- Repo.update(used_changeset(token)) do
|
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||||
{:ok, token}
|
{:ok, token}
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule Pleroma.Activity do
|
||||||
field(:data, :map)
|
field(:data, :map)
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
field(:actor, :string)
|
field(:actor, :string)
|
||||||
field(:recipients, {:array, :string})
|
field(:recipients, {:array, :string}, default: [])
|
||||||
has_many(:notifications, Notification, on_delete: :delete_all)
|
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||||
|
|
||||||
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
||||||
|
|
|
@ -25,6 +25,7 @@ def start(_type, _args) do
|
||||||
import Cachex.Spec
|
import Cachex.Spec
|
||||||
|
|
||||||
Pleroma.Config.DeprecationWarnings.warn()
|
Pleroma.Config.DeprecationWarnings.warn()
|
||||||
|
setup_instrumenters()
|
||||||
|
|
||||||
# Define workers and child supervisors to be supervised
|
# Define workers and child supervisors to be supervised
|
||||||
children =
|
children =
|
||||||
|
@ -103,14 +104,15 @@ def start(_type, _args) do
|
||||||
],
|
],
|
||||||
id: :cachex_idem
|
id: :cachex_idem
|
||||||
),
|
),
|
||||||
worker(Pleroma.FlakeId, [])
|
worker(Pleroma.FlakeId, []),
|
||||||
|
worker(Pleroma.ScheduledActivityWorker, [])
|
||||||
] ++
|
] ++
|
||||||
hackney_pool_children() ++
|
hackney_pool_children() ++
|
||||||
[
|
[
|
||||||
worker(Pleroma.Web.Federator.RetryQueue, []),
|
worker(Pleroma.Web.Federator.RetryQueue, []),
|
||||||
worker(Pleroma.Stats, []),
|
worker(Pleroma.Stats, []),
|
||||||
worker(Pleroma.Web.Push, []),
|
worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init),
|
||||||
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary)
|
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init)
|
||||||
] ++
|
] ++
|
||||||
streamer_child() ++
|
streamer_child() ++
|
||||||
chat_child() ++
|
chat_child() ++
|
||||||
|
@ -126,6 +128,24 @@ def start(_type, _args) do
|
||||||
Supervisor.start_link(children, opts)
|
Supervisor.start_link(children, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp setup_instrumenters do
|
||||||
|
require Prometheus.Registry
|
||||||
|
|
||||||
|
:ok =
|
||||||
|
:telemetry.attach(
|
||||||
|
"prometheus-ecto",
|
||||||
|
[:pleroma, :repo, :query],
|
||||||
|
&Pleroma.Repo.Instrumenter.handle_event/4,
|
||||||
|
%{}
|
||||||
|
)
|
||||||
|
|
||||||
|
Prometheus.Registry.register_collector(:prometheus_process_collector)
|
||||||
|
Pleroma.Web.Endpoint.MetricsExporter.setup()
|
||||||
|
Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
|
||||||
|
Pleroma.Web.Endpoint.Instrumenter.setup()
|
||||||
|
Pleroma.Repo.Instrumenter.setup()
|
||||||
|
end
|
||||||
|
|
||||||
def enabled_hackney_pools do
|
def enabled_hackney_pools do
|
||||||
[:media] ++
|
[:media] ++
|
||||||
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
|
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
|
||||||
|
|
|
@ -57,4 +57,8 @@ def delete([parent_key | keys]) do
|
||||||
def delete(key) do
|
def delete(key) do
|
||||||
Application.delete_env(:pleroma, key)
|
Application.delete_env(:pleroma, key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
|
||||||
|
|
||||||
|
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,13 +8,19 @@ defmodule Pleroma.Emoji do
|
||||||
|
|
||||||
* the built-in Finmojis (if enabled in configuration),
|
* the built-in Finmojis (if enabled in configuration),
|
||||||
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
||||||
* glob paths
|
* glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
|
||||||
|
|
||||||
This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
|
This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
|
||||||
"""
|
"""
|
||||||
use GenServer
|
use GenServer
|
||||||
|
|
||||||
|
@type pattern :: Regex.t() | module() | String.t()
|
||||||
|
@type patterns :: pattern() | [pattern()]
|
||||||
|
@type group_patterns :: keyword(patterns())
|
||||||
|
|
||||||
@ets __MODULE__.Ets
|
@ets __MODULE__.Ets
|
||||||
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
|
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
|
||||||
|
@groups Application.get_env(:pleroma, :emoji)[:groups]
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def start_link do
|
def start_link do
|
||||||
|
@ -73,13 +79,14 @@ def code_change(_old_vsn, state, _extra) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp load do
|
defp load do
|
||||||
|
finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)
|
||||||
|
shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
|
||||||
|
|
||||||
emojis =
|
emojis =
|
||||||
(load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
|
(load_finmoji(finmoji_enabled) ++
|
||||||
load_from_file("config/emoji.txt") ++
|
load_from_file("config/emoji.txt") ++
|
||||||
load_from_file("config/custom_emoji.txt") ++
|
load_from_file("config/custom_emoji.txt") ++
|
||||||
load_from_globs(
|
load_from_globs(shortcode_globs))
|
||||||
Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
|
|
||||||
))
|
|
||||||
|> Enum.reject(fn value -> value == nil end)
|
|> Enum.reject(fn value -> value == nil end)
|
||||||
|
|
||||||
true = :ets.insert(@ets, emojis)
|
true = :ets.insert(@ets, emojis)
|
||||||
|
@ -151,9 +158,12 @@ defp load do
|
||||||
"white_nights",
|
"white_nights",
|
||||||
"woollysocks"
|
"woollysocks"
|
||||||
]
|
]
|
||||||
|
|
||||||
defp load_finmoji(true) do
|
defp load_finmoji(true) do
|
||||||
Enum.map(@finmoji, fn finmoji ->
|
Enum.map(@finmoji, fn finmoji ->
|
||||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
file_name = "/finmoji/128px/#{finmoji}-128.png"
|
||||||
|
group = match_extra(@groups, file_name)
|
||||||
|
{finmoji, file_name, to_string(group)}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -172,8 +182,14 @@ defp load_from_file_stream(stream) do
|
||||||
|> Stream.map(&String.trim/1)
|
|> Stream.map(&String.trim/1)
|
||||||
|> Stream.map(fn line ->
|
|> Stream.map(fn line ->
|
||||||
case String.split(line, ~r/,\s*/) do
|
case String.split(line, ~r/,\s*/) do
|
||||||
[name, file] -> {name, file}
|
[name, file, tags] ->
|
||||||
_ -> nil
|
{name, file, tags}
|
||||||
|
|
||||||
|
[name, file] ->
|
||||||
|
{name, file, to_string(match_extra(@groups, file))}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> Enum.to_list()
|
|> Enum.to_list()
|
||||||
|
@ -190,9 +206,40 @@ defp load_from_globs(globs) do
|
||||||
|> Enum.concat()
|
|> Enum.concat()
|
||||||
|
|
||||||
Enum.map(paths, fn path ->
|
Enum.map(paths, fn path ->
|
||||||
|
tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
|
||||||
shortcode = Path.basename(path, Path.extname(path))
|
shortcode = Path.basename(path, Path.extname(path))
|
||||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||||
{shortcode, external_path}
|
{shortcode, external_path, to_string(tag)}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Finds a matching group for the given emoji filename
|
||||||
|
"""
|
||||||
|
@spec match_extra(group_patterns(), String.t()) :: atom() | nil
|
||||||
|
def match_extra(group_patterns, filename) do
|
||||||
|
match_group_patterns(group_patterns, fn pattern ->
|
||||||
|
case pattern do
|
||||||
|
%Regex{} = regex -> Regex.match?(regex, filename)
|
||||||
|
string when is_binary(string) -> filename == string
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp match_group_patterns(group_patterns, matcher) do
|
||||||
|
Enum.find_value(group_patterns, fn {group, patterns} ->
|
||||||
|
patterns =
|
||||||
|
patterns
|
||||||
|
|> List.wrap()
|
||||||
|
|> Enum.map(fn pattern ->
|
||||||
|
if String.contains?(pattern, "*") do
|
||||||
|
~r(#{String.replace(pattern, "*", ".*")})
|
||||||
|
else
|
||||||
|
pattern
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
Enum.any?(patterns, matcher) && group
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -77,9 +77,9 @@ def emojify(text) do
|
||||||
def emojify(text, nil), do: text
|
def emojify(text, nil), do: text
|
||||||
|
|
||||||
def emojify(text, emoji, strip \\ false) do
|
def emojify(text, emoji, strip \\ false) do
|
||||||
Enum.reduce(emoji, text, fn {emoji, file}, text ->
|
Enum.reduce(emoji, text, fn emoji_data, text ->
|
||||||
emoji = HTML.strip_tags(emoji)
|
emoji = HTML.strip_tags(elem(emoji_data, 0))
|
||||||
file = HTML.strip_tags(file)
|
file = HTML.strip_tags(elem(emoji_data, 1))
|
||||||
|
|
||||||
html =
|
html =
|
||||||
if not strip do
|
if not strip do
|
||||||
|
@ -101,7 +101,7 @@ def demojify(text) do
|
||||||
def demojify(text, nil), do: text
|
def demojify(text, nil), do: text
|
||||||
|
|
||||||
def get_emoji(text) when is_binary(text) do
|
def get_emoji(text) when is_binary(text) do
|
||||||
Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_emoji(_), do: []
|
def get_emoji(_), do: []
|
||||||
|
|
|
@ -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"])
|
||||||
|
|
|
@ -28,27 +28,39 @@ def filter_tags(html, scrubber), do: Scrubber.scrub(html, scrubber)
|
||||||
def filter_tags(html), do: filter_tags(html, nil)
|
def filter_tags(html), do: filter_tags(html, nil)
|
||||||
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
|
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
|
||||||
|
|
||||||
def get_cached_scrubbed_html_for_object(content, scrubbers, object, module) do
|
def get_cached_scrubbed_html_for_activity(content, scrubbers, activity, key \\ "") do
|
||||||
key = "#{module}#{generate_scrubber_signature(scrubbers)}|#{object.id}"
|
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
|
||||||
Cachex.fetch!(:scrubber_cache, key, fn _key -> ensure_scrubbed_html(content, scrubbers) end)
|
|
||||||
|
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||||
|
ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_cached_stripped_html_for_object(content, object, module) do
|
def get_cached_stripped_html_for_activity(content, activity, key) do
|
||||||
get_cached_scrubbed_html_for_object(
|
get_cached_scrubbed_html_for_activity(
|
||||||
content,
|
content,
|
||||||
HtmlSanitizeEx.Scrubber.StripTags,
|
HtmlSanitizeEx.Scrubber.StripTags,
|
||||||
object,
|
activity,
|
||||||
module
|
key
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_scrubbed_html(
|
def ensure_scrubbed_html(
|
||||||
content,
|
content,
|
||||||
scrubbers
|
scrubbers,
|
||||||
|
false = _fake
|
||||||
) do
|
) do
|
||||||
{:commit, filter_tags(content, scrubbers)}
|
{:commit, filter_tags(content, scrubbers)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_scrubbed_html(
|
||||||
|
content,
|
||||||
|
scrubbers,
|
||||||
|
true = _fake
|
||||||
|
) do
|
||||||
|
{:ignore, filter_tags(content, scrubbers)}
|
||||||
|
end
|
||||||
|
|
||||||
defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
|
defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
|
||||||
generate_scrubber_signature([scrubber])
|
generate_scrubber_signature([scrubber])
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,7 +80,7 @@ def get_lists_from_activity(%Activity{actor: ap_id}) do
|
||||||
|
|
||||||
# Get lists to which the account belongs.
|
# Get lists to which the account belongs.
|
||||||
def get_lists_account_belongs(%User{} = owner, account_id) do
|
def get_lists_account_belongs(%User{} = owner, account_id) do
|
||||||
user = Repo.get(User, account_id)
|
user = User.get_by_id(account_id)
|
||||||
|
|
||||||
query =
|
query =
|
||||||
from(
|
from(
|
||||||
|
|
|
@ -44,6 +44,11 @@ def get_by_ap_id(ap_id) do
|
||||||
# Use this whenever possible, especially when walking graphs in an O(N) loop!
|
# Use this whenever possible, especially when walking graphs in an O(N) loop!
|
||||||
def normalize(%Activity{object: %Object{} = object}), do: object
|
def normalize(%Activity{object: %Object{} = object}), do: object
|
||||||
|
|
||||||
|
# A hack for fake activities
|
||||||
|
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}) do
|
||||||
|
%Object{id: "pleroma:fake_object_id", data: data}
|
||||||
|
end
|
||||||
|
|
||||||
# Catch and log Object.normalize() calls where the Activity's child object is not
|
# Catch and log Object.normalize() calls where the Activity's child object is not
|
||||||
# preloaded.
|
# preloaded.
|
||||||
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do
|
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Registration do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
alias Pleroma.Registration
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
|
||||||
|
|
||||||
|
schema "registrations" do
|
||||||
|
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||||
|
field(:provider, :string)
|
||||||
|
field(:uid, :string)
|
||||||
|
field(:info, :map, default: %{})
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def nickname(registration, default \\ nil),
|
||||||
|
do: Map.get(registration.info, "nickname", default)
|
||||||
|
|
||||||
|
def email(registration, default \\ nil),
|
||||||
|
do: Map.get(registration.info, "email", default)
|
||||||
|
|
||||||
|
def name(registration, default \\ nil),
|
||||||
|
do: Map.get(registration.info, "name", default)
|
||||||
|
|
||||||
|
def description(registration, default \\ nil),
|
||||||
|
do: Map.get(registration.info, "description", default)
|
||||||
|
|
||||||
|
def changeset(registration, params \\ %{}) do
|
||||||
|
registration
|
||||||
|
|> cast(params, [:user_id, :provider, :uid, :info])
|
||||||
|
|> validate_required([:provider, :uid])
|
||||||
|
|> foreign_key_constraint(:user_id)
|
||||||
|
|> unique_constraint(:uid, name: :registrations_provider_uid_index)
|
||||||
|
end
|
||||||
|
|
||||||
|
def bind_to_user(registration, user) do
|
||||||
|
registration
|
||||||
|
|> changeset(%{user_id: (user && user.id) || nil})
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_by_provider_uid(provider, uid) do
|
||||||
|
Repo.get_by(Registration,
|
||||||
|
provider: to_string(provider),
|
||||||
|
uid: to_string(uid)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,6 +8,10 @@ defmodule Pleroma.Repo do
|
||||||
adapter: Ecto.Adapters.Postgres,
|
adapter: Ecto.Adapters.Postgres,
|
||||||
migration_timestamps: [type: :naive_datetime_usec]
|
migration_timestamps: [type: :naive_datetime_usec]
|
||||||
|
|
||||||
|
defmodule Instrumenter do
|
||||||
|
use Prometheus.EctoInstrumenter
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Dynamically loads the repository url from the
|
Dynamically loads the repository url from the
|
||||||
DATABASE_URL environment variable.
|
DATABASE_URL environment variable.
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivity do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@min_offset :timer.minutes(5)
|
||||||
|
|
||||||
|
schema "scheduled_activities" do
|
||||||
|
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||||
|
field(:scheduled_at, :naive_datetime)
|
||||||
|
field(:params, :map)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> cast(attrs, [:scheduled_at, :params])
|
||||||
|
|> validate_required([:scheduled_at, :params])
|
||||||
|
|> validate_scheduled_at()
|
||||||
|
|> with_media_attachments()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(
|
||||||
|
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
|
||||||
|
)
|
||||||
|
when is_list(media_ids) do
|
||||||
|
media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids})
|
||||||
|
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("media_attachments", media_attachments)
|
||||||
|
|> Map.put("media_ids", media_ids)
|
||||||
|
|
||||||
|
put_change(changeset, :params, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(changeset), do: changeset
|
||||||
|
|
||||||
|
def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> cast(attrs, [:scheduled_at])
|
||||||
|
|> validate_required([:scheduled_at])
|
||||||
|
|> validate_scheduled_at()
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_scheduled_at(changeset) do
|
||||||
|
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
|
||||||
|
cond do
|
||||||
|
not far_enough?(scheduled_at) ->
|
||||||
|
[scheduled_at: "must be at least 5 minutes from now"]
|
||||||
|
|
||||||
|
exceeds_daily_user_limit?(changeset.data.user_id, scheduled_at) ->
|
||||||
|
[scheduled_at: "daily limit exceeded"]
|
||||||
|
|
||||||
|
exceeds_total_user_limit?(changeset.data.user_id) ->
|
||||||
|
[scheduled_at: "total limit exceeded"]
|
||||||
|
|
||||||
|
true ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def exceeds_daily_user_limit?(user_id, scheduled_at) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user_id)
|
||||||
|
|> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date))
|
||||||
|
|> select([sa], count(sa.id))
|
||||||
|
|> Repo.one()
|
||||||
|
|> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def exceeds_total_user_limit?(user_id) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user_id)
|
||||||
|
|> select([sa], count(sa.id))
|
||||||
|
|> Repo.one()
|
||||||
|
|> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def far_enough?(scheduled_at) when is_binary(scheduled_at) do
|
||||||
|
with {:ok, scheduled_at} <- Ecto.Type.cast(:naive_datetime, scheduled_at) do
|
||||||
|
far_enough?(scheduled_at)
|
||||||
|
else
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def far_enough?(scheduled_at) do
|
||||||
|
now = NaiveDateTime.utc_now()
|
||||||
|
diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
|
||||||
|
diff > @min_offset
|
||||||
|
end
|
||||||
|
|
||||||
|
def new(%User{} = user, attrs) do
|
||||||
|
%ScheduledActivity{user_id: user.id}
|
||||||
|
|> changeset(attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(%User{} = user, attrs) do
|
||||||
|
user
|
||||||
|
|> new(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(%User{} = user, scheduled_activity_id) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user.id)
|
||||||
|
|> where(id: ^scheduled_activity_id)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||||
|
scheduled_activity
|
||||||
|
|> update_changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(%ScheduledActivity{} = scheduled_activity) do
|
||||||
|
scheduled_activity
|
||||||
|
|> Repo.delete()
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(id) when is_binary(id) or is_integer(id) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(id: ^id)
|
||||||
|
|> select([sa], sa)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
|> case do
|
||||||
|
{1, [scheduled_activity]} -> {:ok, scheduled_activity}
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def for_user_query(%User{} = user) do
|
||||||
|
ScheduledActivity
|
||||||
|
|> where(user_id: ^user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def due_activities(offset \\ 0) do
|
||||||
|
naive_datetime =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(offset, :millisecond)
|
||||||
|
|
||||||
|
ScheduledActivity
|
||||||
|
|> where([sa], sa.scheduled_at < ^naive_datetime)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivityWorker do
|
||||||
|
@moduledoc """
|
||||||
|
Sends scheduled activities to the job queue.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
use GenServer
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@schedule_interval :timer.minutes(1)
|
||||||
|
|
||||||
|
def start_link do
|
||||||
|
GenServer.start_link(__MODULE__, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def init(_) do
|
||||||
|
if Config.get([ScheduledActivity, :enabled]) do
|
||||||
|
schedule_next()
|
||||||
|
{:ok, nil}
|
||||||
|
else
|
||||||
|
:ignore
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(:execute, scheduled_activity_id) do
|
||||||
|
try do
|
||||||
|
{:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id)
|
||||||
|
%User{} = user = User.get_cached_by_id(scheduled_activity.user_id)
|
||||||
|
{:ok, _result} = CommonAPI.post(user, scheduled_activity.params)
|
||||||
|
rescue
|
||||||
|
error ->
|
||||||
|
Logger.error(
|
||||||
|
"#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:perform, state) do
|
||||||
|
ScheduledActivity.due_activities(@schedule_interval)
|
||||||
|
|> Enum.each(fn scheduled_activity ->
|
||||||
|
PleromaJobQueue.enqueue(:scheduled_activities, __MODULE__, [:execute, scheduled_activity.id])
|
||||||
|
end)
|
||||||
|
|
||||||
|
schedule_next()
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp schedule_next do
|
||||||
|
Process.send_after(self(), :perform, @schedule_interval)
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,6 +13,7 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Formatter
|
alias Pleroma.Formatter
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Registration
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
|
@ -55,6 +56,7 @@ defmodule Pleroma.User do
|
||||||
field(:bookmarks, {:array, :string}, default: [])
|
field(:bookmarks, {:array, :string}, default: [])
|
||||||
field(:last_refreshed_at, :naive_datetime_usec)
|
field(:last_refreshed_at, :naive_datetime_usec)
|
||||||
has_many(:notifications, Notification)
|
has_many(:notifications, Notification)
|
||||||
|
has_many(:registrations, Registration)
|
||||||
embeds_one(:info, Pleroma.User.Info)
|
embeds_one(:info, Pleroma.User.Info)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
|
@ -216,7 +218,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
changeset =
|
changeset =
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||||
|> validate_required([:email, :name, :nickname, :password, :password_confirmation])
|
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||||
|> validate_confirmation(:password)
|
|> validate_confirmation(:password)
|
||||||
|> unique_constraint(:email)
|
|> unique_constraint(:email)
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|
@ -227,6 +229,13 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||||
|> validate_length(:name, min: 1, max: 100)
|
|> validate_length(:name, min: 1, max: 100)
|
||||||
|> put_change(:info, info_change)
|
|> put_change(:info, info_change)
|
||||||
|
|
||||||
|
changeset =
|
||||||
|
if opts[:external] do
|
||||||
|
changeset
|
||||||
|
else
|
||||||
|
validate_required(changeset, [:email])
|
||||||
|
end
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||||
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
||||||
|
@ -505,11 +514,10 @@ def get_by_nickname(nickname) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_by_email(email), do: Repo.get_by(User, email: email)
|
||||||
|
|
||||||
def get_by_nickname_or_email(nickname_or_email) do
|
def get_by_nickname_or_email(nickname_or_email) do
|
||||||
case user = Repo.get_by(User, nickname: nickname_or_email) do
|
get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
|
||||||
%User{} -> user
|
|
||||||
nil -> Repo.get_by(User, email: nickname_or_email)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_cached_user_info(user) do
|
def get_cached_user_info(user) do
|
||||||
|
@ -1088,28 +1096,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 +1238,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 +1249,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 ->
|
||||||
|
|
|
@ -113,15 +113,15 @@ def decrease_replies_count_if_reply(%Object{
|
||||||
|
|
||||||
def decrease_replies_count_if_reply(_object), do: :noop
|
def decrease_replies_count_if_reply(_object), do: :noop
|
||||||
|
|
||||||
def insert(map, local \\ true) when is_map(map) do
|
def insert(map, local \\ true, fake \\ false) when is_map(map) do
|
||||||
with nil <- Activity.normalize(map),
|
with nil <- Activity.normalize(map),
|
||||||
map <- lazy_put_activity_defaults(map),
|
map <- lazy_put_activity_defaults(map, fake),
|
||||||
:ok <- check_actor_is_active(map["actor"]),
|
:ok <- check_actor_is_active(map["actor"]),
|
||||||
{_, true} <- {:remote_limit_error, check_remote_limit(map)},
|
{_, true} <- {:remote_limit_error, check_remote_limit(map)},
|
||||||
{:ok, map} <- MRF.filter(map),
|
{:ok, map} <- MRF.filter(map),
|
||||||
|
{recipients, _, _} = get_recipients(map),
|
||||||
|
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
|
||||||
{:ok, object} <- insert_full_object(map) do
|
{:ok, object} <- insert_full_object(map) do
|
||||||
{recipients, _, _} = get_recipients(map)
|
|
||||||
|
|
||||||
{:ok, activity} =
|
{:ok, activity} =
|
||||||
Repo.insert(%Activity{
|
Repo.insert(%Activity{
|
||||||
data: map,
|
data: map,
|
||||||
|
@ -146,8 +146,23 @@ def insert(map, local \\ true) when is_map(map) do
|
||||||
stream_out(activity)
|
stream_out(activity)
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
%Activity{} = activity -> {:ok, activity}
|
%Activity{} = activity ->
|
||||||
error -> {:error, error}
|
{:ok, activity}
|
||||||
|
|
||||||
|
{:fake, true, map, recipients} ->
|
||||||
|
activity = %Activity{
|
||||||
|
data: map,
|
||||||
|
local: local,
|
||||||
|
actor: map["actor"],
|
||||||
|
recipients: recipients,
|
||||||
|
id: "pleroma:fakeid"
|
||||||
|
}
|
||||||
|
|
||||||
|
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||||
|
{:ok, activity}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
{:error, error}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -190,7 +205,7 @@ def stream_out(activity) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(%{to: to, actor: actor, context: context, object: object} = params) do
|
def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do
|
||||||
additional = params[:additional] || %{}
|
additional = params[:additional] || %{}
|
||||||
# only accept false as false value
|
# only accept false as false value
|
||||||
local = !(params[:local] == false)
|
local = !(params[:local] == false)
|
||||||
|
@ -201,13 +216,17 @@ def create(%{to: to, actor: actor, context: context, object: object} = params) d
|
||||||
%{to: to, actor: actor, published: published, context: context, object: object},
|
%{to: to, actor: actor, published: published, context: context, object: object},
|
||||||
additional
|
additional
|
||||||
),
|
),
|
||||||
{:ok, activity} <- insert(create_data, local),
|
{:ok, activity} <- insert(create_data, local, fake),
|
||||||
|
{:fake, false, activity} <- {:fake, fake, activity},
|
||||||
_ <- increase_replies_count_if_reply(create_data),
|
_ <- increase_replies_count_if_reply(create_data),
|
||||||
# Changing note count prior to enqueuing federation task in order to avoid
|
# Changing note count prior to enqueuing federation task in order to avoid
|
||||||
# race conditions on updating user.info
|
# race conditions on updating user.info
|
||||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
||||||
:ok <- maybe_federate(activity) do
|
:ok <- maybe_federate(activity) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
{:fake, true, activity} ->
|
||||||
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -954,7 +954,7 @@ defp strip_internal_tags(%{"tag" => tags} = object) do
|
||||||
|
|
||||||
defp strip_internal_tags(object), do: object
|
defp strip_internal_tags(object), do: object
|
||||||
|
|
||||||
defp user_upgrade_task(user) do
|
def perform(:user_upgrade, user) do
|
||||||
# we pass a fake user so that the followers collection is stripped away
|
# we pass a fake user so that the followers collection is stripped away
|
||||||
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
||||||
|
|
||||||
|
@ -999,28 +999,18 @@ defp user_upgrade_task(user) do
|
||||||
Repo.update_all(q, [])
|
Repo.update_all(q, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def upgrade_user_from_ap_id(ap_id, async \\ true) do
|
def upgrade_user_from_ap_id(ap_id) do
|
||||||
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
|
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
|
||||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
|
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
||||||
already_ap = User.ap_enabled?(user)
|
already_ap <- User.ap_enabled?(user),
|
||||||
|
{:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do
|
||||||
{:ok, user} =
|
unless already_ap do
|
||||||
User.upgrade_changeset(user, data)
|
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
|
||||||
|> Repo.update()
|
|
||||||
|
|
||||||
if !already_ap do
|
|
||||||
# This could potentially take a long time, do it in the background
|
|
||||||
if async do
|
|
||||||
Task.start(fn ->
|
|
||||||
user_upgrade_task(user)
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
user_upgrade_task(user)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
|
%User{} = user -> {:ok, user}
|
||||||
e -> e
|
e -> e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -99,7 +99,10 @@ def make_json_ld_header do
|
||||||
%{
|
%{
|
||||||
"@context" => [
|
"@context" => [
|
||||||
"https://www.w3.org/ns/activitystreams",
|
"https://www.w3.org/ns/activitystreams",
|
||||||
"#{Web.base_url()}/schemas/litepub-0.1.jsonld"
|
"#{Web.base_url()}/schemas/litepub-0.1.jsonld",
|
||||||
|
%{
|
||||||
|
"@language" => "und"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -175,18 +178,26 @@ def maybe_federate(_), do: :ok
|
||||||
Adds an id and a published data if they aren't there,
|
Adds an id and a published data if they aren't there,
|
||||||
also adds it to an included object
|
also adds it to an included object
|
||||||
"""
|
"""
|
||||||
def lazy_put_activity_defaults(map) do
|
def lazy_put_activity_defaults(map, fake \\ false) do
|
||||||
%{data: %{"id" => context}, id: context_id} = create_context(map["context"])
|
|
||||||
|
|
||||||
map =
|
map =
|
||||||
map
|
unless fake do
|
||||||
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
%{data: %{"id" => context}, id: context_id} = create_context(map["context"])
|
||||||
|> Map.put_new_lazy("published", &make_date/0)
|
|
||||||
|> Map.put_new("context", context)
|
map
|
||||||
|> Map.put_new("context_id", context_id)
|
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
||||||
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|
|> Map.put_new("context", context)
|
||||||
|
|> Map.put_new("context_id", context_id)
|
||||||
|
else
|
||||||
|
map
|
||||||
|
|> Map.put_new("id", "pleroma:fakeid")
|
||||||
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|
|> Map.put_new("context", "pleroma:fakecontext")
|
||||||
|
|> Map.put_new("context_id", -1)
|
||||||
|
end
|
||||||
|
|
||||||
if is_map(map["object"]) do
|
if is_map(map["object"]) do
|
||||||
object = lazy_put_object_defaults(map["object"], map)
|
object = lazy_put_object_defaults(map["object"], map, fake)
|
||||||
%{map | "object" => object}
|
%{map | "object" => object}
|
||||||
else
|
else
|
||||||
map
|
map
|
||||||
|
@ -196,7 +207,18 @@ def lazy_put_activity_defaults(map) do
|
||||||
@doc """
|
@doc """
|
||||||
Adds an id and published date if they aren't there.
|
Adds an id and published date if they aren't there.
|
||||||
"""
|
"""
|
||||||
def lazy_put_object_defaults(map, activity \\ %{}) do
|
def lazy_put_object_defaults(map, activity \\ %{}, fake)
|
||||||
|
|
||||||
|
def lazy_put_object_defaults(map, activity, true = _fake) do
|
||||||
|
map
|
||||||
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|
|> Map.put_new("id", "pleroma:fake_object_id")
|
||||||
|
|> Map.put_new("context", activity["context"])
|
||||||
|
|> Map.put_new("fake", true)
|
||||||
|
|> Map.put_new("context_id", activity["context_id"])
|
||||||
|
end
|
||||||
|
|
||||||
|
def lazy_put_object_defaults(map, activity, _fake) do
|
||||||
map
|
map
|
||||||
|> Map.put_new_lazy("id", &generate_object_id/0)
|
|> Map.put_new_lazy("id", &generate_object_id/0)
|
||||||
|> Map.put_new_lazy("published", &make_date/0)
|
|> Map.put_new_lazy("published", &make_date/0)
|
||||||
|
@ -354,7 +376,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 ->
|
||||||
|
@ -404,13 +426,15 @@ def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
|
||||||
activity.data
|
activity.data
|
||||||
),
|
),
|
||||||
where: activity.actor == ^follower_id,
|
where: activity.actor == ^follower_id,
|
||||||
|
# this is to use the index
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"? @> ?",
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||||
activity.data,
|
activity.data,
|
||||||
^%{object: followed_id}
|
activity.data,
|
||||||
|
^followed_id
|
||||||
),
|
),
|
||||||
order_by: [desc: :id],
|
order_by: [fragment("? desc nulls last", activity.id)],
|
||||||
limit: 1
|
limit: 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -567,13 +591,15 @@ def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do
|
||||||
activity.data
|
activity.data
|
||||||
),
|
),
|
||||||
where: activity.actor == ^blocker_id,
|
where: activity.actor == ^blocker_id,
|
||||||
|
# this is to use the index
|
||||||
where:
|
where:
|
||||||
fragment(
|
fragment(
|
||||||
"? @> ?",
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||||
activity.data,
|
activity.data,
|
||||||
^%{object: blocked_id}
|
activity.data,
|
||||||
|
^blocked_id
|
||||||
),
|
),
|
||||||
order_by: [desc: :id],
|
order_by: [fragment("? desc nulls last", activity.id)],
|
||||||
limit: 1
|
limit: 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,26 @@ def user_delete(conn, %{"nickname" => nickname}) do
|
||||||
|> json(nickname)
|
|> json(nickname)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||||
|
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
||||||
|
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
||||||
|
User.follow(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json("ok")
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||||
|
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
||||||
|
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
||||||
|
User.unfollow(follower, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> json("ok")
|
||||||
|
end
|
||||||
|
|
||||||
def user_create(
|
def user_create(
|
||||||
conn,
|
conn,
|
||||||
%{"nickname" => nickname, "email" => email, "password" => password}
|
%{"nickname" => nickname, "email" => email, "password" => password}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Auth.Authenticator do
|
defmodule Pleroma.Web.Auth.Authenticator do
|
||||||
|
alias Pleroma.Registration
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def implementation do
|
def implementation do
|
||||||
|
@ -12,14 +13,33 @@ def implementation do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
|
@callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()}
|
||||||
def get_user(plug), do: implementation().get_user(plug)
|
def get_user(plug, params), do: implementation().get_user(plug, params)
|
||||||
|
|
||||||
|
@callback create_from_registration(Plug.Conn.t(), Map.t(), Registration.t()) ::
|
||||||
|
{:ok, User.t()} | {:error, any()}
|
||||||
|
def create_from_registration(plug, params, registration),
|
||||||
|
do: implementation().create_from_registration(plug, params, registration)
|
||||||
|
|
||||||
|
@callback get_registration(Plug.Conn.t(), Map.t()) ::
|
||||||
|
{:ok, Registration.t()} | {:error, any()}
|
||||||
|
def get_registration(plug, params),
|
||||||
|
do: implementation().get_registration(plug, params)
|
||||||
|
|
||||||
@callback handle_error(Plug.Conn.t(), any()) :: any()
|
@callback handle_error(Plug.Conn.t(), any()) :: any()
|
||||||
def handle_error(plug, error), do: implementation().handle_error(plug, error)
|
def handle_error(plug, error), do: implementation().handle_error(plug, error)
|
||||||
|
|
||||||
@callback auth_template() :: String.t() | nil
|
@callback auth_template() :: String.t() | nil
|
||||||
def auth_template do
|
def auth_template do
|
||||||
implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
|
# Note: `config :pleroma, :auth_template, "..."` support is deprecated
|
||||||
|
implementation().auth_template() ||
|
||||||
|
Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
|
||||||
|
"show.html"
|
||||||
|
end
|
||||||
|
|
||||||
|
@callback oauth_consumer_template() :: String.t() | nil
|
||||||
|
def oauth_consumer_template do
|
||||||
|
implementation().oauth_consumer_template() ||
|
||||||
|
Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,14 +8,19 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@behaviour Pleroma.Web.Auth.Authenticator
|
@behaviour Pleroma.Web.Auth.Authenticator
|
||||||
|
@base Pleroma.Web.Auth.PleromaAuthenticator
|
||||||
|
|
||||||
@connection_timeout 10_000
|
@connection_timeout 10_000
|
||||||
@search_timeout 10_000
|
@search_timeout 10_000
|
||||||
|
|
||||||
def get_user(%Plug.Conn{} = conn) do
|
defdelegate get_registration(conn, params), to: @base
|
||||||
|
|
||||||
|
defdelegate create_from_registration(conn, params, registration), to: @base
|
||||||
|
|
||||||
|
def get_user(%Plug.Conn{} = conn, params) do
|
||||||
if Pleroma.Config.get([:ldap, :enabled]) do
|
if Pleroma.Config.get([:ldap, :enabled]) do
|
||||||
{name, password} =
|
{name, password} =
|
||||||
case conn.params do
|
case params do
|
||||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||||
{name, password}
|
{name, password}
|
||||||
|
|
||||||
|
@ -29,14 +34,14 @@ def get_user(%Plug.Conn{} = conn) do
|
||||||
|
|
||||||
{:error, {:ldap_connection_error, _}} ->
|
{:error, {:ldap_connection_error, _}} ->
|
||||||
# When LDAP is unavailable, try default authenticator
|
# When LDAP is unavailable, try default authenticator
|
||||||
Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
|
@base.get_user(conn, params)
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
error
|
error
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# Fall back to default authenticator
|
# Fall back to default authenticator
|
||||||
Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
|
@base.get_user(conn, params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -46,6 +51,8 @@ def handle_error(%Plug.Conn{} = _conn, error) do
|
||||||
|
|
||||||
def auth_template, do: nil
|
def auth_template, do: nil
|
||||||
|
|
||||||
|
def oauth_consumer_template, do: nil
|
||||||
|
|
||||||
defp ldap_user(name, password) do
|
defp ldap_user(name, password) do
|
||||||
ldap = Pleroma.Config.get(:ldap, [])
|
ldap = Pleroma.Config.get(:ldap, [])
|
||||||
host = Keyword.get(ldap, :host, "localhost")
|
host = Keyword.get(ldap, :host, "localhost")
|
||||||
|
|
|
@ -4,13 +4,15 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
|
alias Pleroma.Registration
|
||||||
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
@behaviour Pleroma.Web.Auth.Authenticator
|
@behaviour Pleroma.Web.Auth.Authenticator
|
||||||
|
|
||||||
def get_user(%Plug.Conn{} = conn) do
|
def get_user(%Plug.Conn{} = _conn, params) do
|
||||||
{name, password} =
|
{name, password} =
|
||||||
case conn.params do
|
case params do
|
||||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||||
{name, password}
|
{name, password}
|
||||||
|
|
||||||
|
@ -27,9 +29,69 @@ def get_user(%Plug.Conn{} = conn) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_registration(
|
||||||
|
%Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}},
|
||||||
|
_params
|
||||||
|
) do
|
||||||
|
registration = Registration.get_by_provider_uid(provider, uid)
|
||||||
|
|
||||||
|
if registration do
|
||||||
|
{:ok, registration}
|
||||||
|
else
|
||||||
|
info = auth.info
|
||||||
|
|
||||||
|
Registration.changeset(%Registration{}, %{
|
||||||
|
provider: to_string(provider),
|
||||||
|
uid: to_string(uid),
|
||||||
|
info: %{
|
||||||
|
"nickname" => info.nickname,
|
||||||
|
"email" => info.email,
|
||||||
|
"name" => info.name,
|
||||||
|
"description" => info.description
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials}
|
||||||
|
|
||||||
|
def create_from_registration(_conn, params, registration) do
|
||||||
|
nickname = value([params["nickname"], Registration.nickname(registration)])
|
||||||
|
email = value([params["email"], Registration.email(registration)])
|
||||||
|
name = value([params["name"], Registration.name(registration)]) || nickname
|
||||||
|
bio = value([params["bio"], Registration.description(registration)])
|
||||||
|
|
||||||
|
random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
|
||||||
|
|
||||||
|
with {:ok, new_user} <-
|
||||||
|
User.register_changeset(
|
||||||
|
%User{},
|
||||||
|
%{
|
||||||
|
email: email,
|
||||||
|
nickname: nickname,
|
||||||
|
name: name,
|
||||||
|
bio: bio,
|
||||||
|
password: random_password,
|
||||||
|
password_confirmation: random_password
|
||||||
|
},
|
||||||
|
external: true,
|
||||||
|
confirmed: true
|
||||||
|
)
|
||||||
|
|> Repo.insert(),
|
||||||
|
{:ok, _} <-
|
||||||
|
Registration.changeset(registration, %{user_id: new_user.id}) |> Repo.update() do
|
||||||
|
{:ok, new_user}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp value(list), do: Enum.find(list, &(to_string(&1) != ""))
|
||||||
|
|
||||||
def handle_error(%Plug.Conn{} = _conn, error) do
|
def handle_error(%Plug.Conn{} = _conn, error) do
|
||||||
error
|
error
|
||||||
end
|
end
|
||||||
|
|
||||||
def auth_template, do: nil
|
def auth_template, do: nil
|
||||||
|
|
||||||
|
def oauth_consumer_template, do: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -167,18 +167,21 @@ def post(user, %{"status" => status} = data) do
|
||||||
object,
|
object,
|
||||||
"emoji",
|
"emoji",
|
||||||
(Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
|
(Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
|
||||||
|> Enum.reduce(%{}, fn {name, file}, acc ->
|
|> Enum.reduce(%{}, fn {name, file, _}, acc ->
|
||||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||||
end)
|
end)
|
||||||
) do
|
) do
|
||||||
res =
|
res =
|
||||||
ActivityPub.create(%{
|
ActivityPub.create(
|
||||||
to: to,
|
%{
|
||||||
actor: user,
|
to: to,
|
||||||
context: context,
|
actor: user,
|
||||||
object: object,
|
context: context,
|
||||||
additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
|
object: object,
|
||||||
})
|
additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
|
||||||
|
},
|
||||||
|
Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
|
||||||
|
)
|
||||||
|
|
||||||
res
|
res
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||||
alias Pleroma.Web.Endpoint
|
alias Pleroma.Web.Endpoint
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
# This is a hack for twidere.
|
# This is a hack for twidere.
|
||||||
def get_by_id_or_ap_id(id) do
|
def get_by_id_or_ap_id(id) do
|
||||||
activity =
|
activity =
|
||||||
|
@ -31,7 +33,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
|
||||||
|
@ -240,15 +242,21 @@ def format_asctime(date) do
|
||||||
Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y")
|
Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y")
|
||||||
end
|
end
|
||||||
|
|
||||||
def date_to_asctime(date) do
|
def date_to_asctime(date) when is_binary(date) do
|
||||||
with {:ok, date, _offset} <- date |> DateTime.from_iso8601() do
|
with {:ok, date, _offset} <- DateTime.from_iso8601(date) do
|
||||||
format_asctime(date)
|
format_asctime(date)
|
||||||
else
|
else
|
||||||
_e ->
|
_e ->
|
||||||
|
Logger.warn("Date #{date} in wrong format, must be ISO 8601")
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def date_to_asctime(date) do
|
||||||
|
Logger.warn("Date #{date} in wrong format, must be ISO 8601")
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
def to_masto_date(%NaiveDateTime{} = date) do
|
def to_masto_date(%NaiveDateTime{} = date) do
|
||||||
date
|
date
|
||||||
|> NaiveDateTime.to_iso8601()
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
@ -275,7 +283,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
|
||||||
|
@ -285,7 +293,7 @@ def confirm_current_password(user, password) do
|
||||||
|
|
||||||
def emoji_from_profile(%{info: _info} = user) do
|
def emoji_from_profile(%{info: _info} = user) do
|
||||||
(Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
|
(Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
|
||||||
|> Enum.map(fn {shortcode, url} ->
|
|> Enum.map(fn {shortcode, url, _} ->
|
||||||
%{
|
%{
|
||||||
"type" => "Emoji",
|
"type" => "Emoji",
|
||||||
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
|
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -51,11 +51,22 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
plug(Plug.MethodOverride)
|
plug(Plug.MethodOverride)
|
||||||
plug(Plug.Head)
|
plug(Plug.Head)
|
||||||
|
|
||||||
|
secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
|
||||||
|
|
||||||
cookie_name =
|
cookie_name =
|
||||||
if Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
if secure_cookies,
|
||||||
do: "__Host-pleroma_key",
|
do: "__Host-pleroma_key",
|
||||||
else: "pleroma_key"
|
else: "pleroma_key"
|
||||||
|
|
||||||
|
same_site =
|
||||||
|
if Pleroma.Config.oauth_consumer_enabled?() do
|
||||||
|
# Note: "SameSite=Strict" prevents sign in with external OAuth provider
|
||||||
|
# (there would be no cookies during callback request from OAuth provider)
|
||||||
|
"SameSite=Lax"
|
||||||
|
else
|
||||||
|
"SameSite=Strict"
|
||||||
|
end
|
||||||
|
|
||||||
# The session will be stored in the cookie and signed,
|
# The session will be stored in the cookie and signed,
|
||||||
# this means its contents can be read but not tampered with.
|
# this means its contents can be read but not tampered with.
|
||||||
# Set :encryption_salt if you would also like to encrypt it.
|
# Set :encryption_salt if you would also like to encrypt it.
|
||||||
|
@ -65,11 +76,30 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
key: cookie_name,
|
key: cookie_name,
|
||||||
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
|
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
|
||||||
http_only: true,
|
http_only: true,
|
||||||
secure:
|
secure: secure_cookies,
|
||||||
Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
extra: same_site
|
||||||
extra: "SameSite=Strict"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Note: the plug and its configuration is compile-time this can't be upstreamed yet
|
||||||
|
if proxies = Pleroma.Config.get([__MODULE__, :reverse_proxies]) do
|
||||||
|
plug(RemoteIp, proxies: proxies)
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Instrumenter do
|
||||||
|
use Prometheus.PhoenixInstrumenter
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule PipelineInstrumenter do
|
||||||
|
use Prometheus.PlugPipelineInstrumenter
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule MetricsExporter do
|
||||||
|
use Prometheus.PlugExporter
|
||||||
|
end
|
||||||
|
|
||||||
|
plug(PipelineInstrumenter)
|
||||||
|
plug(MetricsExporter)
|
||||||
|
|
||||||
plug(Pleroma.Web.Router)
|
plug(Pleroma.Web.Router)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Pagination
|
alias Pleroma.Pagination
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def get_followers(user, params \\ %{}) do
|
def get_followers(user, params \\ %{}) do
|
||||||
|
@ -28,6 +29,12 @@ def get_notifications(user, params \\ %{}) do
|
||||||
|> Pagination.fetch_paginated(params)
|
|> Pagination.fetch_paginated(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_scheduled_activities(user, params \\ %{}) do
|
||||||
|
user
|
||||||
|
|> ScheduledActivity.for_user_query()
|
||||||
|
|> Pagination.fetch_paginated(params)
|
||||||
|
end
|
||||||
|
|
||||||
defp cast_params(params) do
|
defp cast_params(params) do
|
||||||
param_types = %{
|
param_types = %{
|
||||||
exclude_types: {:array, :string}
|
exclude_types: {:array, :string}
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Ecto.Changeset
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Filter
|
alias Pleroma.Filter
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.Stats
|
alias Pleroma.Stats
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
|
@ -25,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonView
|
alias Pleroma.Web.MastodonAPI.MastodonView
|
||||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||||
alias Pleroma.Web.MastodonAPI.ReportView
|
alias Pleroma.Web.MastodonAPI.ReportView
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
alias Pleroma.Web.OAuth.App
|
alias Pleroma.Web.OAuth.App
|
||||||
|
@ -178,14 +181,15 @@ def peers(conn, _params) do
|
||||||
|
|
||||||
defp mastodonized_emoji do
|
defp mastodonized_emoji do
|
||||||
Pleroma.Emoji.get_all()
|
Pleroma.Emoji.get_all()
|
||||||
|> Enum.map(fn {shortcode, relative_url} ->
|
|> Enum.map(fn {shortcode, relative_url, tags} ->
|
||||||
url = to_string(URI.merge(Web.base_url(), relative_url))
|
url = to_string(URI.merge(Web.base_url(), relative_url))
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"shortcode" => shortcode,
|
"shortcode" => shortcode,
|
||||||
"static_url" => url,
|
"static_url" => url,
|
||||||
"visible_in_picker" => true,
|
"visible_in_picker" => true,
|
||||||
"url" => url
|
"url" => url,
|
||||||
|
"tags" => String.split(tags, ",")
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
@ -285,7 +289,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 +323,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 +332,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,
|
||||||
|
@ -364,6 +368,55 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
||||||
|
conn
|
||||||
|
|> add_link_headers(:scheduled_statuses, scheduled_activities)
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("index.json", %{scheduled_activities: scheduled_activities})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
||||||
|
with %ScheduledActivity{} = scheduled_activity <-
|
||||||
|
ScheduledActivity.get(user, scheduled_activity_id) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
else
|
||||||
|
_ -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_scheduled_status(
|
||||||
|
%{assigns: %{user: user}} = conn,
|
||||||
|
%{"id" => scheduled_activity_id} = params
|
||||||
|
) do
|
||||||
|
with %ScheduledActivity{} = scheduled_activity <-
|
||||||
|
ScheduledActivity.get(user, scheduled_activity_id),
|
||||||
|
{:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
else
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
||||||
|
with %ScheduledActivity{} = scheduled_activity <-
|
||||||
|
ScheduledActivity.get(user, scheduled_activity_id),
|
||||||
|
{:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
else
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
||||||
when length(media_ids) > 0 do
|
when length(media_ids) > 0 do
|
||||||
params =
|
params =
|
||||||
|
@ -384,12 +437,27 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||||
_ -> Ecto.UUID.generate()
|
_ -> Ecto.UUID.generate()
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, activity} =
|
scheduled_at = params["scheduled_at"]
|
||||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
|
|
||||||
|
|
||||||
conn
|
if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
|
||||||
|> put_view(StatusView)
|
with {:ok, scheduled_activity} <-
|
||||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
|
||||||
|
conn
|
||||||
|
|> put_view(ScheduledActivityView)
|
||||||
|
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
params = Map.drop(params, ["scheduled_at"])
|
||||||
|
|
||||||
|
{:ok, activity} =
|
||||||
|
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
|
||||||
|
CommonAPI.post(user, params)
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
|
@ -460,7 +528,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 +539,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 +661,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 +674,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 +725,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 +742,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 +767,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 +781,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 +795,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 +823,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 +837,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 +846,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 +860,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 +881,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 +896,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 +1034,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 +1091,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 +1103,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)
|
||||||
|
@ -1091,9 +1159,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
token =
|
token = get_session(conn, :oauth_token)
|
||||||
conn
|
|
||||||
|> get_session(:oauth_token)
|
|
||||||
|
|
||||||
if user && token do
|
if user && token do
|
||||||
mastodon_emoji = mastodonized_emoji()
|
mastodon_emoji = mastodonized_emoji()
|
||||||
|
@ -1121,7 +1187,8 @@ def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
auto_play_gif: false,
|
auto_play_gif: false,
|
||||||
display_sensitive_media: false,
|
display_sensitive_media: false,
|
||||||
reduce_motion: false,
|
reduce_motion: false,
|
||||||
max_toot_chars: limit
|
max_toot_chars: limit,
|
||||||
|
mascot: "/images/pleroma-fox-tan-smol.png"
|
||||||
},
|
},
|
||||||
rights: %{
|
rights: %{
|
||||||
delete_others_notice: present?(user.info.is_moderator),
|
delete_others_notice: present?(user.info.is_moderator),
|
||||||
|
@ -1193,6 +1260,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do
|
||||||
|> render("index.html", %{initial_state: initial_state, flavour: flavour})
|
|> render("index.html", %{initial_state: initial_state, flavour: flavour})
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|
|> put_session(:return_to, conn.request_path)
|
||||||
|> redirect(to: "/web/login")
|
|> redirect(to: "/web/login")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1249,16 +1317,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 =
|
||||||
|
@ -1271,8 +1345,18 @@ def login(conn, _) do
|
||||||
scope: Enum.join(app.scopes, " ")
|
scope: Enum.join(app.scopes, " ")
|
||||||
)
|
)
|
||||||
|
|
||||||
conn
|
redirect(conn, to: path)
|
||||||
|> redirect(to: path)
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp local_mastodon_root_path(conn) do
|
||||||
|
case get_session(conn, :return_to) do
|
||||||
|
nil ->
|
||||||
|
mastodon_api_path(conn, :index, ["getting-started"])
|
||||||
|
|
||||||
|
return_to ->
|
||||||
|
delete_session(conn, :return_to)
|
||||||
|
return_to
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1312,7 +1396,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})
|
||||||
|
@ -1390,6 +1474,23 @@ def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
|
||||||
|
|
||||||
# fallback action
|
# fallback action
|
||||||
#
|
#
|
||||||
|
def errors(conn, {:error, %Changeset{} = changeset}) do
|
||||||
|
error_message =
|
||||||
|
changeset
|
||||||
|
|> Changeset.traverse_errors(fn {message, _opt} -> message end)
|
||||||
|
|> Enum.map_join(", ", fn {_k, v} -> v end)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_status(422)
|
||||||
|
|> json(%{error: error_message})
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors(conn, {:error, :not_found}) do
|
||||||
|
conn
|
||||||
|
|> put_status(404)
|
||||||
|
|> json(%{error: "Record not found"})
|
||||||
|
end
|
||||||
|
|
||||||
def errors(conn, _) do
|
def errors(conn, _) do
|
||||||
conn
|
conn
|
||||||
|> put_status(500)
|
|> put_status(500)
|
||||||
|
@ -1454,7 +1555,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(
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
|
||||||
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
|
||||||
|
def render("index.json", %{scheduled_activities: scheduled_activities}) do
|
||||||
|
render_many(scheduled_activities, ScheduledActivityView, "show.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
|
||||||
|
%{
|
||||||
|
id: to_string(scheduled_activity.id),
|
||||||
|
scheduled_at: CommonAPI.Utils.to_masto_date(scheduled_activity.scheduled_at),
|
||||||
|
params: status_params(scheduled_activity.params)
|
||||||
|
}
|
||||||
|
|> with_media_attachments(scheduled_activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do
|
||||||
|
try do
|
||||||
|
attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
|
||||||
|
Map.put(data, :media_attachments, attachments)
|
||||||
|
rescue
|
||||||
|
_ -> data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_media_attachments(data, _), do: data
|
||||||
|
|
||||||
|
defp status_params(params) do
|
||||||
|
data = %{
|
||||||
|
text: params["status"],
|
||||||
|
sensitive: params["sensitive"],
|
||||||
|
spoiler_text: params["spoiler_text"],
|
||||||
|
visibility: params["visibility"],
|
||||||
|
scheduled_at: params["scheduled_at"],
|
||||||
|
poll: params["poll"],
|
||||||
|
in_reply_to_id: params["in_reply_to_id"]
|
||||||
|
}
|
||||||
|
|
||||||
|
data =
|
||||||
|
if media_ids = params["media_ids"] do
|
||||||
|
Map.put(data, :media_ids, media_ids)
|
||||||
|
else
|
||||||
|
data
|
||||||
|
end
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
|
end
|
|
@ -147,10 +147,18 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
content =
|
content =
|
||||||
object
|
object
|
||||||
|> render_content()
|
|> render_content()
|
||||||
|> HTML.get_cached_scrubbed_html_for_object(
|
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||||
User.html_filter_policy(opts[:for]),
|
User.html_filter_policy(opts[:for]),
|
||||||
activity,
|
activity,
|
||||||
__MODULE__
|
"mastoapi:content"
|
||||||
|
)
|
||||||
|
|
||||||
|
summary =
|
||||||
|
(object["summary"] || "")
|
||||||
|
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||||
|
User.html_filter_policy(opts[:for]),
|
||||||
|
activity,
|
||||||
|
"mastoapi:summary"
|
||||||
)
|
)
|
||||||
|
|
||||||
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
||||||
|
@ -182,7 +190,7 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
||||||
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
|
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
|
||||||
pinned: pinned?(activity, user),
|
pinned: pinned?(activity, user),
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
spoiler_text: object["summary"] || "",
|
spoiler_text: summary,
|
||||||
visibility: get_visibility(object),
|
visibility: get_visibility(object),
|
||||||
media_attachments: attachments,
|
media_attachments: attachments,
|
||||||
mentions: mentions,
|
mentions: mentions,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -12,7 +12,7 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
|
||||||
# html content comes from DB already encoded, decode first and scrub after
|
# html content comes from DB already encoded, decode first and scrub after
|
||||||
|> HtmlEntities.decode()
|
|> HtmlEntities.decode()
|
||||||
|> String.replace(~r/<br\s?\/?>/, " ")
|
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||||
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
|
|> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|
||||||
|> Formatter.demojify()
|
|> Formatter.demojify()
|
||||||
|> Formatter.truncate()
|
|> Formatter.truncate()
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,8 +6,21 @@ defmodule Pleroma.Web.OAuth.FallbackController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
alias Pleroma.Web.OAuth.OAuthController
|
alias Pleroma.Web.OAuth.OAuthController
|
||||||
|
|
||||||
# No user/password
|
def call(conn, {:register, :generic_error}) do
|
||||||
def call(conn, _) do
|
conn
|
||||||
|
|> put_status(:internal_server_error)
|
||||||
|
|> put_flash(:error, "Unknown error, please check the details and try again.")
|
||||||
|
|> OAuthController.registration_details(conn.params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, {:register, _error}) do
|
||||||
|
conn
|
||||||
|
|> put_status(:unauthorized)
|
||||||
|
|> put_flash(:error, "Invalid Username/Password")
|
||||||
|
|> OAuthController.registration_details(conn.params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _error) do
|
||||||
conn
|
conn
|
||||||
|> put_status(:unauthorized)
|
|> put_status(:unauthorized)
|
||||||
|> put_flash(:error, "Invalid Username/Password")
|
|> put_flash(:error, "Invalid Username/Password")
|
||||||
|
|
|
@ -5,21 +5,46 @@
|
||||||
defmodule Pleroma.Web.OAuth.OAuthController do
|
defmodule Pleroma.Web.OAuth.OAuthController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Registration
|
||||||
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
|
||||||
|
|
||||||
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
|
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
|
||||||
|
|
||||||
|
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
||||||
|
|
||||||
plug(:fetch_session)
|
plug(:fetch_session)
|
||||||
plug(:fetch_flash)
|
plug(:fetch_flash)
|
||||||
|
|
||||||
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
|
||||||
|
@ -35,80 +60,73 @@ def authorize(conn, params) do
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_authorization(conn, %{
|
def create_authorization(
|
||||||
"authorization" =>
|
conn,
|
||||||
%{
|
%{"authorization" => auth_params} = params,
|
||||||
"client_id" => client_id,
|
opts \\ []
|
||||||
"redirect_uri" => redirect_uri
|
) do
|
||||||
} = auth_params
|
with {:ok, auth} <- do_create_authorization(conn, params, opts[:user]) do
|
||||||
}) do
|
after_create_authorization(conn, auth, auth_params)
|
||||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
|
else
|
||||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
error ->
|
||||||
true <- redirect_uri in String.split(app.redirect_uris),
|
handle_create_authorization_error(conn, error, auth_params)
|
||||||
scopes <- oauth_scopes(auth_params, []),
|
end
|
||||||
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
|
end
|
||||||
# Note: `scope` param is intentionally not optional in this context
|
|
||||||
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
def after_create_authorization(conn, auth, %{"redirect_uri" => redirect_uri} = auth_params) do
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
redirect_uri = redirect_uri(conn, redirect_uri)
|
||||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
|
|
||||||
redirect_uri =
|
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
||||||
if redirect_uri == "." do
|
render(conn, "results.html", %{
|
||||||
# Special case: Local MastodonFE
|
auth: auth
|
||||||
mastodon_api_url(conn, :login)
|
})
|
||||||
|
else
|
||||||
|
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||||
|
url = "#{redirect_uri}#{connector}"
|
||||||
|
url_params = %{:code => auth.token}
|
||||||
|
|
||||||
|
url_params =
|
||||||
|
if auth_params["state"] do
|
||||||
|
Map.put(url_params, :state, auth_params["state"])
|
||||||
else
|
else
|
||||||
redirect_uri
|
url_params
|
||||||
end
|
end
|
||||||
|
|
||||||
cond do
|
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
||||||
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
|
|
||||||
render(conn, "results.html", %{
|
|
||||||
auth: auth
|
|
||||||
})
|
|
||||||
|
|
||||||
true ->
|
redirect(conn, external: url)
|
||||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
|
||||||
url = "#{redirect_uri}#{connector}"
|
|
||||||
url_params = %{:code => auth.token}
|
|
||||||
|
|
||||||
url_params =
|
|
||||||
if auth_params["state"] do
|
|
||||||
Map.put(url_params, :state, auth_params["state"])
|
|
||||||
else
|
|
||||||
url_params
|
|
||||||
end
|
|
||||||
|
|
||||||
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
|
||||||
|
|
||||||
redirect(conn, external: url)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
{scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] ->
|
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
|
||||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
|
|
||||||
conn
|
|
||||||
|> put_flash(:error, "This action is outside the authorized scopes")
|
|
||||||
|> put_status(:unauthorized)
|
|
||||||
|> authorize(auth_params)
|
|
||||||
|
|
||||||
{:auth_active, false} ->
|
|
||||||
# Per https://github.com/tootsuite/mastodon/blob/
|
|
||||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
|
||||||
conn
|
|
||||||
|> put_flash(:error, "Your login is missing a confirmed e-mail address")
|
|
||||||
|> put_status(:forbidden)
|
|
||||||
|> authorize(auth_params)
|
|
||||||
|
|
||||||
error ->
|
|
||||||
Authenticator.handle_error(conn, error)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_create_authorization_error(conn, {scopes_issue, _}, auth_params)
|
||||||
|
when scopes_issue in [:unsupported_scopes, :missing_scopes] do
|
||||||
|
# Per https://github.com/tootsuite/mastodon/blob/
|
||||||
|
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "This action is outside the authorized scopes")
|
||||||
|
|> put_status(:unauthorized)
|
||||||
|
|> authorize(auth_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_create_authorization_error(conn, {:auth_active, false}, auth_params) do
|
||||||
|
# Per https://github.com/tootsuite/mastodon/blob/
|
||||||
|
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "Your login is missing a confirmed e-mail address")
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> authorize(auth_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_create_authorization_error(conn, error, _auth_params) do
|
||||||
|
Authenticator.handle_error(conn, error)
|
||||||
|
end
|
||||||
|
|
||||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||||
with %App{} = app <- get_app_from_request(conn, params),
|
with %App{} = app <- get_app_from_request(conn, params),
|
||||||
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 = %{
|
||||||
|
@ -133,9 +151,10 @@ def token_exchange(
|
||||||
conn,
|
conn,
|
||||||
%{"grant_type" => "password"} = params
|
%{"grant_type" => "password"} = params
|
||||||
) do
|
) do
|
||||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
|
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)},
|
||||||
%App{} = app <- get_app_from_request(conn, params),
|
%App{} = app <- get_app_from_request(conn, params),
|
||||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||||
|
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
||||||
scopes <- oauth_scopes(params, app.scopes),
|
scopes <- oauth_scopes(params, app.scopes),
|
||||||
[] <- scopes -- app.scopes,
|
[] <- scopes -- app.scopes,
|
||||||
true <- Enum.any?(scopes),
|
true <- Enum.any?(scopes),
|
||||||
|
@ -159,6 +178,11 @@ def token_exchange(
|
||||||
|> put_status(:forbidden)
|
|> put_status(:forbidden)
|
||||||
|> json(%{error: "Your login is missing a confirmed e-mail address"})
|
|> json(%{error: "Your login is missing a confirmed e-mail address"})
|
||||||
|
|
||||||
|
{:user_active, false} ->
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> json(%{error: "Your account is currently disabled"})
|
||||||
|
|
||||||
_error ->
|
_error ->
|
||||||
put_status(conn, 400)
|
put_status(conn, 400)
|
||||||
|> json(%{error: "Invalid credentials"})
|
|> json(%{error: "Invalid credentials"})
|
||||||
|
@ -189,6 +213,184 @@ def token_revoke(conn, %{"token" => token} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc "Prepares OAuth request to provider for Ueberauth"
|
||||||
|
def prepare_request(conn, %{"provider" => provider} = params) do
|
||||||
|
scope =
|
||||||
|
oauth_scopes(params, [])
|
||||||
|
|> Enum.join(" ")
|
||||||
|
|
||||||
|
state =
|
||||||
|
params
|
||||||
|
|> Map.delete("scopes")
|
||||||
|
|> Map.put("scope", scope)
|
||||||
|
|> Poison.encode!()
|
||||||
|
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.drop(~w(scope scopes client_id redirect_uri))
|
||||||
|
|> Map.put("state", state)
|
||||||
|
|
||||||
|
# Handing the request to Ueberauth
|
||||||
|
redirect(conn, to: o_auth_path(conn, :request, provider, params))
|
||||||
|
end
|
||||||
|
|
||||||
|
def request(conn, params) do
|
||||||
|
message =
|
||||||
|
if params["provider"] do
|
||||||
|
"Unsupported OAuth provider: #{params["provider"]}."
|
||||||
|
else
|
||||||
|
"Bad OAuth request."
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, message)
|
||||||
|
|> redirect(to: "/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do
|
||||||
|
params = callback_params(params)
|
||||||
|
messages = for e <- Map.get(failure, :errors, []), do: e.message
|
||||||
|
message = Enum.join(messages, "; ")
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "Failed to authenticate: #{message}.")
|
||||||
|
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def callback(conn, params) do
|
||||||
|
params = callback_params(params)
|
||||||
|
|
||||||
|
with {:ok, registration} <- Authenticator.get_registration(conn, params) do
|
||||||
|
user = Repo.preload(registration, :user).user
|
||||||
|
auth_params = Map.take(params, ~w(client_id redirect_uri scope scopes state))
|
||||||
|
|
||||||
|
if user do
|
||||||
|
create_authorization(
|
||||||
|
conn,
|
||||||
|
%{"authorization" => auth_params},
|
||||||
|
user: user
|
||||||
|
)
|
||||||
|
else
|
||||||
|
registration_params =
|
||||||
|
Map.merge(auth_params, %{
|
||||||
|
"nickname" => Registration.nickname(registration),
|
||||||
|
"email" => Registration.email(registration)
|
||||||
|
})
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_session(:registration_id, registration.id)
|
||||||
|
|> registration_details(registration_params)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "Failed to set up user account.")
|
||||||
|
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp callback_params(%{"state" => state} = params) do
|
||||||
|
Map.merge(params, Poison.decode!(state))
|
||||||
|
end
|
||||||
|
|
||||||
|
def registration_details(conn, params) do
|
||||||
|
render(conn, "register.html", %{
|
||||||
|
client_id: params["client_id"],
|
||||||
|
redirect_uri: params["redirect_uri"],
|
||||||
|
state: params["state"],
|
||||||
|
scopes: oauth_scopes(params, []),
|
||||||
|
nickname: params["nickname"],
|
||||||
|
email: params["email"]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
def register(conn, %{"op" => "connect"} = params) do
|
||||||
|
authorization_params = Map.put(params, "name", params["auth_name"])
|
||||||
|
create_authorization_params = %{"authorization" => authorization_params}
|
||||||
|
|
||||||
|
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||||
|
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||||
|
{_, {:ok, auth}} <-
|
||||||
|
{:create_authorization, do_create_authorization(conn, create_authorization_params)},
|
||||||
|
%User{} = user <- Repo.preload(auth, :user).user,
|
||||||
|
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
|
||||||
|
conn
|
||||||
|
|> put_session_registration_id(nil)
|
||||||
|
|> after_create_authorization(auth, authorization_params)
|
||||||
|
else
|
||||||
|
{:create_authorization, error} ->
|
||||||
|
{:register, handle_create_authorization_error(conn, error, create_authorization_params)}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:register, :generic_error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def register(conn, %{"op" => "register"} = params) do
|
||||||
|
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||||
|
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||||
|
{:ok, user} <- Authenticator.create_from_registration(conn, params, registration) do
|
||||||
|
conn
|
||||||
|
|> put_session_registration_id(nil)
|
||||||
|
|> create_authorization(
|
||||||
|
%{
|
||||||
|
"authorization" => %{
|
||||||
|
"client_id" => params["client_id"],
|
||||||
|
"redirect_uri" => params["redirect_uri"],
|
||||||
|
"scopes" => oauth_scopes(params, nil)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
user: user
|
||||||
|
)
|
||||||
|
else
|
||||||
|
{:error, changeset} ->
|
||||||
|
message =
|
||||||
|
Enum.map(changeset.errors, fn {field, {error, _}} ->
|
||||||
|
"#{field} #{error}"
|
||||||
|
end)
|
||||||
|
|> Enum.join("; ")
|
||||||
|
|
||||||
|
message =
|
||||||
|
String.replace(
|
||||||
|
message,
|
||||||
|
"ap_id has already been taken",
|
||||||
|
"nickname has already been taken"
|
||||||
|
)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> put_flash(:error, "Error: #{message}.")
|
||||||
|
|> registration_details(params)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:register, :generic_error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_create_authorization(
|
||||||
|
conn,
|
||||||
|
%{
|
||||||
|
"authorization" =>
|
||||||
|
%{
|
||||||
|
"client_id" => client_id,
|
||||||
|
"redirect_uri" => redirect_uri
|
||||||
|
} = auth_params
|
||||||
|
} = params,
|
||||||
|
user \\ nil
|
||||||
|
) do
|
||||||
|
with {_, {:ok, %User{} = user}} <-
|
||||||
|
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)},
|
||||||
|
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||||
|
true <- redirect_uri in String.split(app.redirect_uris),
|
||||||
|
scopes <- oauth_scopes(auth_params, []),
|
||||||
|
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
|
||||||
|
# Note: `scope` param is intentionally not optional in this context
|
||||||
|
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
||||||
|
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
|
||||||
|
Authorization.create_authorization(app, user, scopes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
|
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
|
||||||
# decoding it. Investigate sometime.
|
# decoding it. Investigate sometime.
|
||||||
defp fix_padding(token) do
|
defp fix_padding(token) do
|
||||||
|
@ -221,4 +423,14 @@ 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
|
||||||
|
|
||||||
|
defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
|
||||||
|
|
||||||
|
defp put_session_registration_id(conn, registration_id),
|
||||||
|
do: put_session(conn, :registration_id, registration_id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
||||||
def exchange_token(app, auth) do
|
def exchange_token(app, auth) do
|
||||||
with {:ok, auth} <- Authorization.use_token(auth),
|
with {:ok, auth} <- Authorization.use_token(auth),
|
||||||
true <- auth.app_id == app.id do
|
true <- auth.app_id == app.id do
|
||||||
create_token(app, Repo.get(User, auth.user_id), auth.scopes)
|
create_token(app, User.get_by_id(auth.user_id), auth.scopes)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ defmodule Pleroma.Web.Push.Impl do
|
||||||
@types ["Create", "Follow", "Announce", "Like"]
|
@types ["Create", "Follow", "Announce", "Like"]
|
||||||
|
|
||||||
@doc "Performs sending notifications for user subscriptions"
|
@doc "Performs sending notifications for user subscriptions"
|
||||||
@spec perform_send(Notification.t()) :: list(any)
|
@spec perform(Notification.t()) :: list(any) | :error
|
||||||
def perform_send(
|
def perform(
|
||||||
%{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} =
|
%{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} =
|
||||||
notif
|
notif
|
||||||
)
|
)
|
||||||
|
@ -50,7 +50,7 @@ def perform_send(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_send(_) do
|
def perform(_) do
|
||||||
Logger.warn("Unknown notification type")
|
Logger.warn("Unknown notification type")
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,18 +3,20 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Push do
|
defmodule Pleroma.Web.Push do
|
||||||
use GenServer
|
|
||||||
|
|
||||||
alias Pleroma.Web.Push.Impl
|
alias Pleroma.Web.Push.Impl
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
##############
|
def init do
|
||||||
# Client API #
|
unless enabled() do
|
||||||
##############
|
Logger.warn("""
|
||||||
|
VAPID key pair is not found. If you wish to enabled web push, please run
|
||||||
|
|
||||||
def start_link do
|
mix web_push.gen.keypair
|
||||||
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
|
|
||||||
|
and add the resulting output to your configuration file.
|
||||||
|
""")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def vapid_config do
|
def vapid_config do
|
||||||
|
@ -30,35 +32,5 @@ def enabled do
|
||||||
end
|
end
|
||||||
|
|
||||||
def send(notification),
|
def send(notification),
|
||||||
do: GenServer.cast(__MODULE__, {:send, notification})
|
do: PleromaJobQueue.enqueue(:web_push, Impl, [notification])
|
||||||
|
|
||||||
####################
|
|
||||||
# Server Callbacks #
|
|
||||||
####################
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def init(:ok) do
|
|
||||||
if enabled() do
|
|
||||||
{:ok, nil}
|
|
||||||
else
|
|
||||||
Logger.warn("""
|
|
||||||
VAPID key pair is not found. If you wish to enabled web push, please run
|
|
||||||
|
|
||||||
mix web_push.gen.keypair
|
|
||||||
|
|
||||||
and add the resulting output to your configuration file.
|
|
||||||
""")
|
|
||||||
|
|
||||||
:ignore
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_cast({:send, notification}, state) do
|
|
||||||
if enabled() do
|
|
||||||
Impl.perform_send(notification)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:noreply, state}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,16 @@
|
||||||
defmodule Pleroma.Web.Router do
|
defmodule Pleroma.Web.Router do
|
||||||
use Pleroma.Web, :router
|
use Pleroma.Web, :router
|
||||||
|
|
||||||
|
pipeline :browser do
|
||||||
|
plug(:accepts, ["html"])
|
||||||
|
plug(:fetch_session)
|
||||||
|
end
|
||||||
|
|
||||||
|
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 +115,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
|
||||||
|
@ -139,8 +145,12 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
||||||
pipe_through([:admin_api, :oauth_write])
|
pipe_through([:admin_api, :oauth_write])
|
||||||
|
|
||||||
|
post("/user/follow", AdminAPIController, :user_follow)
|
||||||
|
post("/user/unfollow", AdminAPIController, :user_unfollow)
|
||||||
|
|
||||||
get("/users", AdminAPIController, :list_users)
|
get("/users", AdminAPIController, :list_users)
|
||||||
get("/users/:nickname", AdminAPIController, :user_show)
|
get("/users/:nickname", AdminAPIController, :user_show)
|
||||||
|
|
||||||
delete("/user", AdminAPIController, :user_delete)
|
delete("/user", AdminAPIController, :user_delete)
|
||||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
||||||
post("/user", AdminAPIController, :user_create)
|
post("/user", AdminAPIController, :user_create)
|
||||||
|
@ -200,10 +210,24 @@ 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)
|
||||||
|
get("/registration_details", OAuthController, :registration_details)
|
||||||
|
|
||||||
|
scope [] do
|
||||||
|
pipe_through(:browser)
|
||||||
|
|
||||||
|
get("/prepare_request", OAuthController, :prepare_request)
|
||||||
|
get("/:provider", OAuthController, :request)
|
||||||
|
get("/:provider/callback", OAuthController, :callback)
|
||||||
|
post("/register", OAuthController, :register)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||||
|
@ -235,6 +259,9 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/notifications", MastodonAPIController, :notifications)
|
get("/notifications", MastodonAPIController, :notifications)
|
||||||
get("/notifications/:id", MastodonAPIController, :get_notification)
|
get("/notifications/:id", MastodonAPIController, :get_notification)
|
||||||
|
|
||||||
|
get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
|
||||||
|
get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
|
||||||
|
|
||||||
get("/lists", MastodonAPIController, :get_lists)
|
get("/lists", MastodonAPIController, :get_lists)
|
||||||
get("/lists/:id", MastodonAPIController, :get_list)
|
get("/lists/:id", MastodonAPIController, :get_list)
|
||||||
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
||||||
|
@ -272,6 +299,9 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
|
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
|
||||||
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
|
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
|
||||||
|
|
||||||
|
put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
|
||||||
|
delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
|
||||||
|
|
||||||
post("/media", MastodonAPIController, :upload)
|
post("/media", MastodonAPIController, :upload)
|
||||||
put("/media/:id", MastodonAPIController, :update_media)
|
put("/media/:id", MastodonAPIController, :update_media)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div class="scopes-input">
|
||||||
|
<%= label @form, :scope, "Permissions" %>
|
||||||
|
|
||||||
|
<div class="scopes">
|
||||||
|
<%= for scope <- @available_scopes do %>
|
||||||
|
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
|
||||||
|
<div class="scope">
|
||||||
|
<%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: assigns[:scope_param] || "scope[]" %>
|
||||||
|
<%= label @form, :"scope_#{scope}", String.capitalize(scope) %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<h2>Sign in with external provider</h2>
|
||||||
|
|
||||||
|
<%= form_for @conn, o_auth_path(@conn, :prepare_request), [method: "get"], fn f -> %>
|
||||||
|
<%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %>
|
||||||
|
|
||||||
|
<%= hidden_input f, :client_id, value: @client_id %>
|
||||||
|
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
||||||
|
<%= hidden_input f, :state, value: @state %>
|
||||||
|
|
||||||
|
<%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %>
|
||||||
|
<%= submit "Sign in with #{String.capitalize(strategy)}", name: "provider", value: strategy %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<%= if get_flash(@conn, :info) do %>
|
||||||
|
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
|
||||||
|
<% end %>
|
||||||
|
<%= if get_flash(@conn, :error) do %>
|
||||||
|
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<h2>Registration Details</h2>
|
||||||
|
|
||||||
|
<p>If you'd like to register a new account, please provide the details below.</p>
|
||||||
|
|
||||||
|
<%= form_for @conn, o_auth_path(@conn, :register), [], fn f -> %>
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<%= label f, :nickname, "Nickname" %>
|
||||||
|
<%= text_input f, :nickname, value: @nickname %>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<%= label f, :email, "Email" %>
|
||||||
|
<%= text_input f, :email, value: @email %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= submit "Proceed as new user", name: "op", value: "register" %>
|
||||||
|
|
||||||
|
<p>Alternatively, sign in to connect to existing account.</p>
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<%= label f, :auth_name, "Name or email" %>
|
||||||
|
<%= text_input f, :auth_name %>
|
||||||
|
</div>
|
||||||
|
<div class="input">
|
||||||
|
<%= label f, :password, "Password" %>
|
||||||
|
<%= password_input f, :password %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= submit "Proceed as existing user", name: "op", value: "connect" %>
|
||||||
|
|
||||||
|
<%= hidden_input f, :client_id, value: @client_id %>
|
||||||
|
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
||||||
|
<%= hidden_input f, :scope, value: Enum.join(@scopes, " ") %>
|
||||||
|
<%= hidden_input f, :state, value: @state %>
|
||||||
|
|
||||||
|
<% end %>
|
|
@ -4,7 +4,9 @@
|
||||||
<%= if get_flash(@conn, :error) do %>
|
<%= if get_flash(@conn, :error) do %>
|
||||||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<h2>OAuth Authorization</h2>
|
<h2>OAuth Authorization</h2>
|
||||||
|
|
||||||
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
|
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<%= label f, :name, "Name or email" %>
|
<%= label f, :name, "Name or email" %>
|
||||||
|
@ -14,22 +16,16 @@
|
||||||
<%= label f, :password, "Password" %>
|
<%= label f, :password, "Password" %>
|
||||||
<%= password_input f, :password %>
|
<%= password_input f, :password %>
|
||||||
</div>
|
</div>
|
||||||
<div class="scopes-input">
|
|
||||||
<%= label f, :scope, "Permissions" %>
|
<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f, scope_param: "authorization[scope][]"}) %>
|
||||||
<div class="scopes">
|
|
||||||
<%= for scope <- @available_scopes do %>
|
|
||||||
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
|
|
||||||
<div class="scope">
|
|
||||||
<%= checkbox f, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>
|
|
||||||
<%= label f, :"scope_#{scope}", String.capitalize(scope) %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= hidden_input f, :client_id, value: @client_id %>
|
<%= hidden_input f, :client_id, value: @client_id %>
|
||||||
<%= hidden_input f, :response_type, value: @response_type %>
|
<%= hidden_input f, :response_type, value: @response_type %>
|
||||||
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
|
||||||
<%= hidden_input f, :state, value: @state%>
|
<%= hidden_input f, :state, value: @state %>
|
||||||
<%= submit "Authorize" %>
|
<%= submit "Authorize" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%= if Pleroma.Config.oauth_consumer_enabled?() do %>
|
||||||
|
<%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
|
||||||
|
<% end %>
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias Comeonin.Pbkdf2
|
alias Comeonin.Pbkdf2
|
||||||
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Emoji
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.PasswordResetToken
|
alias Pleroma.PasswordResetToken
|
||||||
|
@ -21,7 +22,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
|
|
||||||
def show_password_reset(conn, %{"token" => token}) do
|
def show_password_reset(conn, %{"token" => token}) do
|
||||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||||
%User{} = user <- Repo.get(User, token.user_id) do
|
%User{} = user <- User.get_by_id(token.user_id) do
|
||||||
render(conn, "password_reset.html", %{
|
render(conn, "password_reset.html", %{
|
||||||
token: token,
|
token: token,
|
||||||
user: user
|
user: user
|
||||||
|
@ -73,36 +74,52 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
|
def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
|
||||||
{err, followee} = OStatus.find_or_make_user(acct)
|
if is_status?(acct) do
|
||||||
avatar = User.avatar_url(followee)
|
{:ok, object} = ActivityPub.fetch_object_from_id(acct)
|
||||||
name = followee.nickname
|
%Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
|
||||||
id = followee.id
|
redirect(conn, to: "/notice/#{activity_id}")
|
||||||
|
|
||||||
if !!user do
|
|
||||||
conn
|
|
||||||
|> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
|
|
||||||
else
|
else
|
||||||
conn
|
{err, followee} = OStatus.find_or_make_user(acct)
|
||||||
|> render("follow_login.html", %{
|
avatar = User.avatar_url(followee)
|
||||||
error: false,
|
name = followee.nickname
|
||||||
acct: acct,
|
id = followee.id
|
||||||
avatar: avatar,
|
|
||||||
name: name,
|
if !!user do
|
||||||
id: id
|
conn
|
||||||
})
|
|> render("follow.html", %{error: err, acct: acct, avatar: avatar, name: name, id: id})
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> render("follow_login.html", %{
|
||||||
|
error: false,
|
||||||
|
acct: acct,
|
||||||
|
avatar: avatar,
|
||||||
|
name: name,
|
||||||
|
id: id
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp is_status?(acct) do
|
||||||
|
case ActivityPub.fetch_and_contain_remote_object_from_id(acct) do
|
||||||
|
{:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] ->
|
||||||
|
true
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_remote_follow(conn, %{
|
def do_remote_follow(conn, %{
|
||||||
"authorization" => %{"name" => username, "password" => password, "id" => id}
|
"authorization" => %{"name" => username, "password" => password, "id" => id}
|
||||||
}) do
|
}) do
|
||||||
followee = Repo.get(User, id)
|
followee = User.get_by_id(id)
|
||||||
avatar = User.avatar_url(followee)
|
avatar = User.avatar_url(followee)
|
||||||
name = followee.nickname
|
name = followee.nickname
|
||||||
|
|
||||||
with %User{} = user <- User.get_cached_by_nickname(username),
|
with %User{} = user <- User.get_cached_by_nickname(username),
|
||||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||||
%User{} = _followed <- Repo.get(User, id),
|
%User{} = _followed <- User.get_by_id(id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{:ok, follower} <- User.follow(user, followee),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||||
conn
|
conn
|
||||||
|
@ -124,7 +141,7 @@ def do_remote_follow(conn, %{
|
||||||
end
|
end
|
||||||
|
|
||||||
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
|
||||||
with %User{} = followee <- Repo.get(User, id),
|
with %User{} = followee <- User.get_by_id(id),
|
||||||
{:ok, follower} <- User.follow(user, followee),
|
{:ok, follower} <- User.follow(user, followee),
|
||||||
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
|
||||||
conn
|
conn
|
||||||
|
@ -266,7 +283,13 @@ def version(conn, _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def emoji(conn, _params) do
|
def emoji(conn, _params) do
|
||||||
json(conn, Enum.into(Emoji.get_all(), %{}))
|
emoji =
|
||||||
|
Emoji.get_all()
|
||||||
|
|> Enum.map(fn {short_code, path, tags} ->
|
||||||
|
%{short_code => %{image_url: path, tags: String.split(tags, ",")}}
|
||||||
|
end)
|
||||||
|
|
||||||
|
json(conn, emoji)
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
|
||||||
|
|
|
@ -20,7 +20,7 @@ def create_status(%User{} = user, %{"status" => _} = data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(%User{} = user, id) do
|
def delete(%User{} = user, id) do
|
||||||
with %Activity{data: %{"type" => _type}} <- Repo.get(Activity, id),
|
with %Activity{data: %{"type" => _type}} <- Activity.get_by_id(id),
|
||||||
{:ok, activity} <- CommonAPI.delete(id, user) do
|
{:ok, activity} <- CommonAPI.delete(id, user) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
@ -227,12 +227,9 @@ def get_user(user \\ nil, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
%{"screen_name" => nickname} ->
|
%{"screen_name" => nickname} ->
|
||||||
case target = Repo.get_by(User, nickname: nickname) do
|
case User.get_by_nickname(nickname) do
|
||||||
nil ->
|
nil -> {:error, "No user with such screen_name"}
|
||||||
{:error, "No user with such screen_name"}
|
target -> {:ok, target}
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:ok, target}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -270,7 +270,7 @@ def unfollow(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
true <- Visibility.visible_for_user?(activity, user) do
|
true <- Visibility.visible_for_user?(activity, user) do
|
||||||
conn
|
conn
|
||||||
|> put_view(ActivityView)
|
|> put_view(ActivityView)
|
||||||
|
@ -342,7 +342,7 @@ def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_by_id_or_ap_id(id) do
|
def get_by_id_or_ap_id(id) do
|
||||||
activity = Repo.get(Activity, id) || Activity.get_create_by_object_ap_id(id)
|
activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
|
||||||
|
|
||||||
if activity.data["type"] == "Create" do
|
if activity.data["type"] == "Create" do
|
||||||
activity
|
activity
|
||||||
|
@ -434,7 +434,7 @@ def password_reset(conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
|
||||||
with %User{} = user <- Repo.get(User, uid),
|
with %User{} = user <- User.get_by_id(uid),
|
||||||
true <- user.local,
|
true <- user.local,
|
||||||
true <- user.info.confirmation_pending,
|
true <- user.info.confirmation_pending,
|
||||||
true <- user.info.confirmation_token == token,
|
true <- user.info.confirmation_token == token,
|
||||||
|
@ -587,7 +587,7 @@ def friend_requests(conn, params) do
|
||||||
|
|
||||||
def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
with followed <- conn.assigns[:user],
|
with followed <- conn.assigns[:user],
|
||||||
%User{} = follower <- Repo.get(User, uid),
|
%User{} = follower <- User.get_by_id(uid),
|
||||||
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(UserView)
|
|> put_view(UserView)
|
||||||
|
@ -599,7 +599,7 @@ def approve_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
|
|
||||||
def deny_friend_request(conn, %{"user_id" => uid} = _params) do
|
def deny_friend_request(conn, %{"user_id" => uid} = _params) do
|
||||||
with followed <- conn.assigns[:user],
|
with followed <- conn.assigns[:user],
|
||||||
%User{} = follower <- Repo.get(User, uid),
|
%User{} = follower <- User.get_by_id(uid),
|
||||||
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
||||||
conn
|
conn
|
||||||
|> put_view(UserView)
|
|> put_view(UserView)
|
||||||
|
|
|
@ -254,10 +254,10 @@ def render(
|
||||||
|
|
||||||
html =
|
html =
|
||||||
content
|
content
|
||||||
|> HTML.get_cached_scrubbed_html_for_object(
|
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||||
User.html_filter_policy(opts[:for]),
|
User.html_filter_policy(opts[:for]),
|
||||||
activity,
|
activity,
|
||||||
__MODULE__
|
"twitterapi:content"
|
||||||
)
|
)
|
||||||
|> Formatter.emojify(object["emoji"])
|
|> Formatter.emojify(object["emoji"])
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ def render(
|
||||||
if content do
|
if content do
|
||||||
content
|
content
|
||||||
|> String.replace(~r/<br\s?\/?>/, "\n")
|
|> String.replace(~r/<br\s?\/?>/, "\n")
|
||||||
|> HTML.get_cached_stripped_html_for_object(activity, __MODULE__)
|
|> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content")
|
||||||
else
|
else
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
|
|
24
mix.exs
24
mix.exs
|
@ -41,7 +41,7 @@ def project do
|
||||||
def application do
|
def application do
|
||||||
[
|
[
|
||||||
mod: {Pleroma.Application, []},
|
mod: {Pleroma.Application, []},
|
||||||
extra_applications: [:logger, :runtime_tools, :comeonin],
|
extra_applications: [:logger, :runtime_tools, :comeonin, :quack],
|
||||||
included_applications: [:ex_syslogger]
|
included_applications: [:ex_syslogger]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
@ -54,6 +54,12 @@ defp elixirc_paths(_), do: ["lib"]
|
||||||
#
|
#
|
||||||
# Type `mix help deps` for examples and options.
|
# Type `mix help deps` for examples and options.
|
||||||
defp deps do
|
defp deps do
|
||||||
|
oauth_strategies = String.split(System.get_env("OAUTH_CONSUMER_STRATEGIES") || "")
|
||||||
|
|
||||||
|
oauth_deps =
|
||||||
|
for s <- oauth_strategies,
|
||||||
|
do: {String.to_atom("ueberauth_#{s}"), ">= 0.0.0"}
|
||||||
|
|
||||||
[
|
[
|
||||||
{:phoenix, "~> 1.4.1"},
|
{:phoenix, "~> 1.4.1"},
|
||||||
{:plug_cowboy, "~> 2.0"},
|
{:plug_cowboy, "~> 2.0"},
|
||||||
|
@ -71,6 +77,7 @@ defp deps do
|
||||||
{:calendar, "~> 0.17.4"},
|
{:calendar, "~> 0.17.4"},
|
||||||
{:cachex, "~> 3.0.2"},
|
{:cachex, "~> 3.0.2"},
|
||||||
{:httpoison, "~> 1.2.0"},
|
{:httpoison, "~> 1.2.0"},
|
||||||
|
{:poison, "~> 3.0", override: true},
|
||||||
{:tesla, "~> 1.2"},
|
{:tesla, "~> 1.2"},
|
||||||
{:jason, "~> 1.0"},
|
{:jason, "~> 1.0"},
|
||||||
{:mogrify, "~> 0.6.1"},
|
{:mogrify, "~> 0.6.1"},
|
||||||
|
@ -91,11 +98,20 @@ defp deps do
|
||||||
{:floki, "~> 0.20.0"},
|
{:floki, "~> 0.20.0"},
|
||||||
{:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"},
|
{:ex_syslogger, github: "slashmili/ex_syslogger", tag: "1.4.0"},
|
||||||
{:timex, "~> 3.5"},
|
{:timex, "~> 3.5"},
|
||||||
|
{:ueberauth, "~> 0.4"},
|
||||||
{: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: "479dd343f4e563ff91215c8275f3b5c67e032850"},
|
||||||
{:pleroma_job_queue, "~> 0.2.0"}
|
{:pleroma_job_queue, "~> 0.2.0"},
|
||||||
]
|
{:telemetry, "~> 0.3"},
|
||||||
|
{:prometheus_ex, "~> 3.0"},
|
||||||
|
{:prometheus_plugs, "~> 1.1"},
|
||||||
|
{:prometheus_phoenix, "~> 1.2"},
|
||||||
|
{:prometheus_ecto, "~> 1.4"},
|
||||||
|
{:prometheus_process_collector, "~> 1.4"},
|
||||||
|
{:recon, github: "ferd/recon", tag: "2.4.0"},
|
||||||
|
{:quack, "~> 0.1.1"}
|
||||||
|
] ++ oauth_deps
|
||||||
end
|
end
|
||||||
|
|
||||||
# Aliases are shortcuts or tasks specific to the current project.
|
# Aliases are shortcuts or tasks specific to the current project.
|
||||||
|
|
18
mix.lock
18
mix.lock
|
@ -1,10 +1,11 @@
|
||||||
%{
|
%{
|
||||||
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd", [ref: "94193ca5f97c1f9fdf3d1469653e2d46fac34bcd"]},
|
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
|
||||||
|
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "479dd343f4e563ff91215c8275f3b5c67e032850", [ref: "479dd343f4e563ff91215c8275f3b5c67e032850"]},
|
||||||
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
|
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
|
||||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
|
||||||
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
"cachex": {:hex, :cachex, "3.0.2", "1351caa4e26e29f7d7ec1d29b53d6013f0447630bbf382b4fb5d5bad0209f203", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
"calendar": {:hex, :calendar, "0.17.4", "22c5e8d98a4db9494396e5727108dffb820ee0d18fed4b0aa8ab76e4f5bc32f1", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
|
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
|
||||||
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
|
"comeonin": {:hex, :comeonin, "4.1.1", "c7304fc29b45b897b34142a91122bc72757bc0c295e9e824999d5179ffc08416", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
|
"gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"},
|
||||||
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
"gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"},
|
||||||
"hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
|
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
|
||||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
@ -39,7 +40,7 @@
|
||||||
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
|
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
|
||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
|
||||||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
|
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||||
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
"mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"},
|
||||||
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
"mogrify": {:hex, :mogrify, "0.6.1", "de1b527514f2d95a7bbe9642eb556061afb337e220cf97adbf3a4e6438ed70af", [:mix], [], "hexpm"},
|
||||||
|
@ -57,7 +58,15 @@
|
||||||
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
|
||||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
|
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
|
||||||
"postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
"postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
|
"prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"},
|
||||||
|
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
|
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
|
||||||
|
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||||
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
|
"swoosh": {:hex, :swoosh, "0.20.0", "9a6c13822c9815993c03b6f8fccc370fcffb3c158d9754f67b1fdee6b3a5d928", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.12", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
|
||||||
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
|
||||||
|
@ -66,6 +75,7 @@
|
||||||
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
"tzdata": {:hex, :tzdata, "0.5.17", "50793e3d85af49736701da1a040c415c97dc1caf6464112fd9bd18f425d3053b", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
|
||||||
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
"unsafe": {:hex, :unsafe, "1.0.0", "7c21742cd05380c7875546b023481d3a26f52df8e5dfedcb9f958f322baae305", [:mix], [], "hexpm"},
|
||||||
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
"web_push_encryption": {:hex, :web_push_encryption, "0.2.1", "d42cecf73420d9dc0053ba3299cc8c8d6ff2be2487d67ca2a57265868e4d9a98", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateRegistrations do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:registrations, primary_key: false) do
|
||||||
|
add :id, :uuid, primary_key: true
|
||||||
|
add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
|
||||||
|
add :provider, :string
|
||||||
|
add :uid, :string
|
||||||
|
add :info, :map, default: %{}
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create unique_index(:registrations, [:provider, :uid])
|
||||||
|
create unique_index(:registrations, [:user_id, :provider, :uid])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateScheduledActivities do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:scheduled_activities) do
|
||||||
|
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||||
|
add(:scheduled_at, :naive_datetime, null: false)
|
||||||
|
add(:params, :map, null: false)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create(index(:scheduled_activities, [:scheduled_at]))
|
||||||
|
create(index(:scheduled_activities, [:user_id]))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.AddOauthTokenIndexes do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create(unique_index(:oauth_tokens, [:token]))
|
||||||
|
create(index(:oauth_tokens, [:app_id]))
|
||||||
|
create(index(:oauth_tokens, [:user_id]))
|
||||||
|
end
|
||||||
|
end
|
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 |
|
@ -0,0 +1,106 @@
|
||||||
|
defmodule Pleroma.EmojiTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
alias Pleroma.Emoji
|
||||||
|
|
||||||
|
describe "get_all/0" do
|
||||||
|
setup do
|
||||||
|
emoji_list = Emoji.get_all()
|
||||||
|
{:ok, emoji_list: emoji_list}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "first emoji", %{emoji_list: emoji_list} do
|
||||||
|
[emoji | _others] = emoji_list
|
||||||
|
{code, path, tags} = emoji
|
||||||
|
|
||||||
|
assert tuple_size(emoji) == 3
|
||||||
|
assert is_binary(code)
|
||||||
|
assert is_binary(path)
|
||||||
|
assert is_binary(tags)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "random emoji", %{emoji_list: emoji_list} do
|
||||||
|
emoji = Enum.random(emoji_list)
|
||||||
|
{code, path, tags} = emoji
|
||||||
|
|
||||||
|
assert tuple_size(emoji) == 3
|
||||||
|
assert is_binary(code)
|
||||||
|
assert is_binary(path)
|
||||||
|
assert is_binary(tags)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "match_extra/2" do
|
||||||
|
setup do
|
||||||
|
groups = [
|
||||||
|
"list of files": ["/emoji/custom/first_file.png", "/emoji/custom/second_file.png"],
|
||||||
|
"wildcard folder": "/emoji/custom/*/file.png",
|
||||||
|
"wildcard files": "/emoji/custom/folder/*.png",
|
||||||
|
"special file": "/emoji/custom/special.png"
|
||||||
|
]
|
||||||
|
|
||||||
|
{:ok, groups: groups}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "config for list of files", %{groups: groups} do
|
||||||
|
group =
|
||||||
|
groups
|
||||||
|
|> Emoji.match_extra("/emoji/custom/first_file.png")
|
||||||
|
|> to_string()
|
||||||
|
|
||||||
|
assert group == "list of files"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "config with wildcard folder", %{groups: groups} do
|
||||||
|
group =
|
||||||
|
groups
|
||||||
|
|> Emoji.match_extra("/emoji/custom/some_folder/file.png")
|
||||||
|
|> to_string()
|
||||||
|
|
||||||
|
assert group == "wildcard folder"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "config with wildcard folder and subfolders", %{groups: groups} do
|
||||||
|
group =
|
||||||
|
groups
|
||||||
|
|> Emoji.match_extra("/emoji/custom/some_folder/another_folder/file.png")
|
||||||
|
|> to_string()
|
||||||
|
|
||||||
|
assert group == "wildcard folder"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "config with wildcard files", %{groups: groups} do
|
||||||
|
group =
|
||||||
|
groups
|
||||||
|
|> Emoji.match_extra("/emoji/custom/folder/some_file.png")
|
||||||
|
|> to_string()
|
||||||
|
|
||||||
|
assert group == "wildcard files"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "config with wildcard files and subfolders", %{groups: groups} do
|
||||||
|
group =
|
||||||
|
groups
|
||||||
|
|> Emoji.match_extra("/emoji/custom/folder/another_folder/some_file.png")
|
||||||
|
|> to_string()
|
||||||
|
|
||||||
|
assert group == "wildcard files"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "config for special file", %{groups: groups} do
|
||||||
|
group =
|
||||||
|
groups
|
||||||
|
|> Emoji.match_extra("/emoji/custom/special.png")
|
||||||
|
|> to_string()
|
||||||
|
|
||||||
|
assert group == "special file"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "no mathing returns nil", %{groups: groups} do
|
||||||
|
group =
|
||||||
|
groups
|
||||||
|
|> Emoji.match_extra("/emoji/some_undefined.png")
|
||||||
|
|
||||||
|
refute group
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,306 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
|
||||||
|
<id>https://mastodon.social/users/emelie.atom</id>
|
||||||
|
<title>emelie 🎨</title>
|
||||||
|
<subtitle>23 / #Sweden / #Artist / #Equestrian / #GameDev
|
||||||
|
|
||||||
|
If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰</subtitle>
|
||||||
|
<updated>2019-02-04T20:22:19Z</updated>
|
||||||
|
<logo>https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png</logo>
|
||||||
|
<author>
|
||||||
|
<id>https://mastodon.social/users/emelie</id>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
|
||||||
|
<uri>https://mastodon.social/users/emelie</uri>
|
||||||
|
<name>emelie</name>
|
||||||
|
<email>emelie@mastodon.social</email>
|
||||||
|
<summary type="html"><p>23 / <a href="https://mastodon.social/tags/sweden" class="mention hashtag" rel="tag">#<span>Sweden</span></a> / <a href="https://mastodon.social/tags/artist" class="mention hashtag" rel="tag">#<span>Artist</span></a> / <a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag">#<span>Equestrian</span></a> / <a href="https://mastodon.social/tags/gamedev" class="mention hashtag" rel="tag">#<span>GameDev</span></a></p><p>If I ain&apos;t spending time with my pets, I&apos;m probably drawing. 🐴 🐱 🐰</p></summary>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie"/>
|
||||||
|
<link rel="avatar" type="image/png" media:width="120" media:height="120" href="https://files.mastodon.social/accounts/avatars/000/015/657/original/e7163f98280da1a4.png"/>
|
||||||
|
<link rel="header" type="image/png" media:width="700" media:height="335" href="https://files.mastodon.social/accounts/headers/000/015/657/original/847f331f3dd9e38b.png"/>
|
||||||
|
<poco:preferredUsername>emelie</poco:preferredUsername>
|
||||||
|
<poco:displayName>emelie 🎨</poco:displayName>
|
||||||
|
<poco:note>23 / #Sweden / #Artist / #Equestrian / #GameDev
|
||||||
|
|
||||||
|
If I ain't spending time with my pets, I'm probably drawing. 🐴 🐱 🐰</poco:note>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
</author>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie.atom"/>
|
||||||
|
<link rel="hub" href="https://mastodon.social/api/push"/>
|
||||||
|
<link rel="salmon" href="https://mastodon.social/api/salmon/15657"/>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101850331907006641</id>
|
||||||
|
<published>2019-04-01T09:58:50Z</published>
|
||||||
|
<updated>2019-04-01T09:58:50Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101850331907006641"/>
|
||||||
|
<content type="html" xml:lang="en"><p>Me: I&apos;m going to make this vital change to my world building in the morning, no way I&apos;ll forget this, it&apos;s too big of a deal<br />Also me: forgets</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101850331907006641"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17854598.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94383214:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849626603073336</id>
|
||||||
|
<published>2019-04-01T06:59:28Z</published>
|
||||||
|
<updated>2019-04-01T06:59:28Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849626603073336"/>
|
||||||
|
<content type="html" xml:lang="sv"><p><span class="h-card"><a href="https://mastodon.social/@Fergant" class="u-url mention">@<span>Fergant</span></a></span> Dom är i stort sett religiös skrift vid det här laget 👏👏</p><p>har dock bara läst svenska översättningen, kanske är dags att jag läser dom på engelska</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://mastodon.social/users/Fergant"/>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849626603073336"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852590.atom"/>
|
||||||
|
<thr:in-reply-to ref="https://mastodon.social/users/Fergant/statuses/101849606513357387" href="https://mastodon.social/@Fergant/101849606513357387"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94362529:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849580030237068</id>
|
||||||
|
<published>2019-04-01T06:47:37Z</published>
|
||||||
|
<updated>2019-04-01T06:47:37Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849580030237068"/>
|
||||||
|
<content type="html" xml:lang="en"><p>What&apos;s you people&apos;s favourite fantasy books? Give me some hot tips 🌞</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849580030237068"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852464.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94362529:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849550599949363</id>
|
||||||
|
<published>2019-04-01T06:40:08Z</published>
|
||||||
|
<updated>2019-04-01T06:40:08Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849550599949363"/>
|
||||||
|
<content type="html" xml:lang="en"><p>Stick them legs out 💃 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="mastocats"/>
|
||||||
|
<link rel="enclosure" type="image/jpeg" length="516384" href="https://files.mastodon.social/media_attachments/files/013/051/707/original/125a310abe9a34aa.jpeg"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849550599949363"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17852407.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94361580:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849191533152720</id>
|
||||||
|
<published>2019-04-01T05:08:49Z</published>
|
||||||
|
<updated>2019-04-01T05:08:49Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849191533152720"/>
|
||||||
|
<content type="html" xml:lang="en"><p>long 🐱 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="mastocats"/>
|
||||||
|
<link rel="enclosure" type="image/jpeg" length="305208" href="https://files.mastodon.social/media_attachments/files/013/049/940/original/f2dbbfe7de3a17d2.jpeg"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849191533152720"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17851663.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94351141:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101849165031453009</id>
|
||||||
|
<published>2019-04-01T05:02:05Z</published>
|
||||||
|
<updated>2019-04-01T05:02:05Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101849165031453009"/>
|
||||||
|
<content type="html" xml:lang="en"><p>You gotta take whatever bellyrubbing opportunity you can get before she changes her mind 🦁 <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="mastocats"/>
|
||||||
|
<link rel="enclosure" type="video/mp4" length="9838915" href="https://files.mastodon.social/media_attachments/files/013/049/816/original/e7831178a5e0d6d4.mp4"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101849165031453009"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17851558.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-04-01:objectId=94350309:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101846512530748693</id>
|
||||||
|
<published>2019-03-31T17:47:31Z</published>
|
||||||
|
<updated>2019-03-31T17:47:31Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101846512530748693"/>
|
||||||
|
<content type="html" xml:lang="en"><p>Hello look at this boy having a decent haircut for once <a href="https://mastodon.social/tags/mastohorses" class="mention hashtag" rel="tag">#<span>mastohorses</span></a> <a href="https://mastodon.social/tags/equestrian" class="mention hashtag" rel="tag">#<span>equestrian</span></a></p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="equestrian"/>
|
||||||
|
<category term="mastohorses"/>
|
||||||
|
<link rel="enclosure" type="image/jpeg" length="461632" href="https://files.mastodon.social/media_attachments/files/013/033/387/original/301e8ab668cd61d2.jpeg"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101846512530748693"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17842424.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94256415:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101846181093805500</id>
|
||||||
|
<published>2019-03-31T16:23:14Z</published>
|
||||||
|
<updated>2019-03-31T16:23:14Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101846181093805500"/>
|
||||||
|
<content type="html" xml:lang="en"><p>Sorry did I disturb the who-is-the-longest-cat competition ? <a href="https://mastodon.social/tags/mastocats" class="mention hashtag" rel="tag">#<span>mastocats</span></a></p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<category term="mastocats"/>
|
||||||
|
<link rel="enclosure" type="image/jpeg" length="211384" href="https://files.mastodon.social/media_attachments/files/013/030/725/original/5b4886730cbbd25c.jpeg"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101846181093805500"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17841108.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94245239:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101845897513133849</id>
|
||||||
|
<published>2019-03-31T15:11:07Z</published>
|
||||||
|
<updated>2019-03-31T15:11:07Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101845897513133849"/>
|
||||||
|
<summary xml:lang="en">more earthsea ramblings</summary>
|
||||||
|
<content type="html" xml:lang="en"><p>I&apos;m re-watching Tales from Earthsea for the first time since I read the books, and that Therru doesn&apos;t squash Cob like a spider, as Orm Embar did is a wasted opportunity tbh</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101845897513133849"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17840088.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-31:objectId=94232455:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101841219051533307</id>
|
||||||
|
<published>2019-03-30T19:21:19Z</published>
|
||||||
|
<updated>2019-03-30T19:21:19Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101841219051533307"/>
|
||||||
|
<content type="html" xml:lang="en"><p>I gave my cats some mackerel and they ate it all in 0.3 seconds, and now they won&apos;t stop meowing for more, and I&apos;m tired plz shut up</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101841219051533307"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17826587.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94075000:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101839949762341381</id>
|
||||||
|
<published>2019-03-30T13:58:31Z</published>
|
||||||
|
<updated>2019-03-30T13:58:31Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101839949762341381"/>
|
||||||
|
<content type="html" xml:lang="en"><p>yet I&apos;m confused about this american dude with a gun, like the heck r ya doin in mah ghibli</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101839949762341381"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17821757.atom"/>
|
||||||
|
<thr:in-reply-to ref="https://mastodon.social/users/emelie/statuses/101839928677863590" href="https://mastodon.social/@emelie/101839928677863590"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94026360:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101839928677863590</id>
|
||||||
|
<published>2019-03-30T13:53:09Z</published>
|
||||||
|
<updated>2019-03-30T13:53:09Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101839928677863590"/>
|
||||||
|
<content type="html" xml:lang="en"><p>2 hours into Ni no Kuni 2 and I&apos;ve already sold my soul to this game</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101839928677863590"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17821713.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-30:objectId=94026360:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101836329521599438</id>
|
||||||
|
<published>2019-03-29T22:37:51Z</published>
|
||||||
|
<updated>2019-03-29T22:37:51Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101836329521599438"/>
|
||||||
|
<content type="html" xml:lang="en"><p>Pippi Longstocking the original one-punch /man</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101836329521599438"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17811608.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93907854:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101835905282948341</id>
|
||||||
|
<published>2019-03-29T20:49:57Z</published>
|
||||||
|
<updated>2019-03-29T20:49:57Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835905282948341"/>
|
||||||
|
<content type="html" xml:lang="en"><p>I&apos;ve had so much wine I thought I had a 3rd brother</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835905282948341"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809862.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93892966:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101835878059204660</id>
|
||||||
|
<published>2019-03-29T20:43:02Z</published>
|
||||||
|
<updated>2019-03-29T20:43:02Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835878059204660"/>
|
||||||
|
<content type="html" xml:lang="en"><p>ååååhhh booi</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835878059204660"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809734.atom"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93892010:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101835848050598939</id>
|
||||||
|
<published>2019-03-29T20:35:24Z</published>
|
||||||
|
<updated>2019-03-29T20:35:24Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835848050598939"/>
|
||||||
|
<content type="html" xml:lang="en"><p><span class="h-card"><a href="https://thraeryn.net/@thraeryn" class="u-url mention">@<span>thraeryn</span></a></span> if I spent 1 hour and a half watching this monstrosity, I need to</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://thraeryn.net/users/thraeryn"/>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835848050598939"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809591.atom"/>
|
||||||
|
<thr:in-reply-to ref="https://thraeryn.net/users/thraeryn/statuses/101835839202826007" href="https://thraeryn.net/@thraeryn/101835839202826007"/>
|
||||||
|
<ostatus:conversation ref="tag:mastodon.social,2019-03-29:objectId=93888827:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<id>https://mastodon.social/users/emelie/statuses/101835823138262290</id>
|
||||||
|
<published>2019-03-29T20:29:04Z</published>
|
||||||
|
<updated>2019-03-29T20:29:04Z</updated>
|
||||||
|
<title>New status by emelie</title>
|
||||||
|
<activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
|
||||||
|
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
|
||||||
|
<link rel="alternate" type="application/activity+json" href="https://mastodon.social/users/emelie/statuses/101835823138262290"/>
|
||||||
|
<summary xml:lang="en">medical, fluids mention</summary>
|
||||||
|
<content type="html" xml:lang="en"><p><span class="h-card"><a href="https://icosahedron.website/@Trev" class="u-url mention">@<span>Trev</span></a></span> *hugs* ✨</p></content>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://icosahedron.website/users/Trev"/>
|
||||||
|
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
|
||||||
|
<mastodon:scope>public</mastodon:scope>
|
||||||
|
<link rel="alternate" type="text/html" href="https://mastodon.social/@emelie/101835823138262290"/>
|
||||||
|
<link rel="self" type="application/atom+xml" href="https://mastodon.social/users/emelie/updates/17809468.atom"/>
|
||||||
|
<thr:in-reply-to ref="https://icosahedron.website/users/Trev/statuses/101835812250051801" href="https://icosahedron.website/@Trev/101835812250051801"/>
|
||||||
|
<ostatus:conversation ref="tag:icosahedron.website,2019-03-29:objectId=12220882:objectType=Conversation"/>
|
||||||
|
</entry>
|
||||||
|
</feed>
|
|
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -271,7 +271,9 @@ test "it does not add XSS emoji" do
|
||||||
test "it returns the emoji used in the text" do
|
test "it returns the emoji used in the text" do
|
||||||
text = "I love :moominmamma:"
|
text = "I love :moominmamma:"
|
||||||
|
|
||||||
assert Formatter.get_emoji(text) == [{"moominmamma", "/finmoji/128px/moominmamma-128.png"}]
|
assert Formatter.get_emoji(text) == [
|
||||||
|
{"moominmamma", "/finmoji/128px/moominmamma-128.png", "Finmoji"}
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns a nice empty result when no emojis are present" do
|
test "it returns a nice empty result when no emojis are present" do
|
||||||
|
|
|
@ -47,16 +47,18 @@ test "it authenticates the auth_user if present and password is correct and rese
|
||||||
|> assign(:auth_user, user)
|
|> assign(:auth_user, user)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
with_mock User,
|
with_mocks([
|
||||||
reset_password: fn user, %{password: password, password_confirmation: password} ->
|
{:crypt, [], [crypt: fn _password, password_hash -> password_hash end]},
|
||||||
send(self(), :reset_password)
|
{User, [],
|
||||||
{:ok, user}
|
[
|
||||||
end do
|
reset_password: fn user, %{password: password, password_confirmation: password} ->
|
||||||
conn
|
{:ok, user}
|
||||||
|> LegacyAuthenticationPlug.call(%{})
|
end
|
||||||
|
]}
|
||||||
|
]) do
|
||||||
|
LegacyAuthenticationPlug.call(conn, %{})
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_received :reset_password
|
|
||||||
assert conn.assigns.user == user
|
assert conn.assigns.user == user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.RegistrationTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Registration
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
describe "generic changeset" do
|
||||||
|
test "requires :provider, :uid" do
|
||||||
|
registration = build(:registration, provider: nil, uid: nil)
|
||||||
|
|
||||||
|
cs = Registration.changeset(registration, %{})
|
||||||
|
refute cs.valid?
|
||||||
|
|
||||||
|
assert [
|
||||||
|
provider: {"can't be blank", [validation: :required]},
|
||||||
|
uid: {"can't be blank", [validation: :required]}
|
||||||
|
] == cs.errors
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ensures uniqueness of [:provider, :uid]" do
|
||||||
|
registration = insert(:registration)
|
||||||
|
registration2 = build(:registration, provider: registration.provider, uid: registration.uid)
|
||||||
|
|
||||||
|
cs = Registration.changeset(registration2, %{})
|
||||||
|
assert cs.valid?
|
||||||
|
|
||||||
|
assert {:error,
|
||||||
|
%Ecto.Changeset{
|
||||||
|
errors: [
|
||||||
|
uid:
|
||||||
|
{"has already been taken",
|
||||||
|
[constraint: :unique, constraint_name: "registrations_provider_uid_index"]}
|
||||||
|
]
|
||||||
|
}} = Repo.insert(cs)
|
||||||
|
|
||||||
|
# Note: multiple :uid values per [:user_id, :provider] are intentionally allowed
|
||||||
|
cs2 = Registration.changeset(registration2, %{uid: "available.uid"})
|
||||||
|
assert cs2.valid?
|
||||||
|
assert {:ok, _} = Repo.insert(cs2)
|
||||||
|
|
||||||
|
cs3 = Registration.changeset(registration2, %{provider: "provider2"})
|
||||||
|
assert cs3.valid?
|
||||||
|
assert {:ok, _} = Repo.insert(cs3)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows `nil` :user_id (user-unbound registration)" do
|
||||||
|
registration = build(:registration, user_id: nil)
|
||||||
|
cs = Registration.changeset(registration, %{})
|
||||||
|
assert cs.valid?
|
||||||
|
assert {:ok, _} = Repo.insert(cs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivityTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
setup context do
|
||||||
|
DataCase.ensure_local_uploader(context)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "creation" do
|
||||||
|
test "when daily user limit is exceeded" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, attrs)
|
||||||
|
assert changeset.errors == [scheduled_at: {"daily limit exceeded", []}]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when total user limit is exceeded" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
tomorrow =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: today})
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
assert changeset.errors == [scheduled_at: {"total limit exceeded", []}]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when scheduled_at is earlier than 5 minute from now" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(4), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: scheduled_at}
|
||||||
|
{:error, changeset} = ScheduledActivity.create(user, attrs)
|
||||||
|
assert changeset.errors == [scheduled_at: {"must be at least 5 minutes from now", []}]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.ScheduledActivityWorkerTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "creates a status from the scheduled activity" do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user, params: %{status: "hi"})
|
||||||
|
Pleroma.ScheduledActivityWorker.perform(:execute, scheduled_activity.id)
|
||||||
|
|
||||||
|
refute Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
|
activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id))
|
||||||
|
assert activity.data["object"]["content"] == "hi"
|
||||||
|
end
|
||||||
|
end
|
|
@ -216,7 +216,7 @@ def oauth_app_factory do
|
||||||
redirect_uris: "https://example.com/callback",
|
redirect_uris: "https://example.com/callback",
|
||||||
scopes: ["read", "write", "follow", "push"],
|
scopes: ["read", "write", "follow", "push"],
|
||||||
website: "https://example.com",
|
website: "https://example.com",
|
||||||
client_id: "aaabbb==",
|
client_id: Ecto.UUID.generate(),
|
||||||
client_secret: "aaa;/&bbb"
|
client_secret: "aaa;/&bbb"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -240,6 +240,16 @@ def oauth_token_factory do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def oauth_authorization_factory do
|
||||||
|
%Pleroma.Web.OAuth.Authorization{
|
||||||
|
token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false),
|
||||||
|
scopes: ["read", "write", "follow", "push"],
|
||||||
|
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10),
|
||||||
|
user: build(:user),
|
||||||
|
app: build(:oauth_app)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def push_subscription_factory do
|
def push_subscription_factory do
|
||||||
%Pleroma.Web.Push.Subscription{
|
%Pleroma.Web.Push.Subscription{
|
||||||
user: build(:user),
|
user: build(:user),
|
||||||
|
@ -257,4 +267,28 @@ def notification_factory do
|
||||||
user: build(:user)
|
user: build(:user)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def scheduled_activity_factory do
|
||||||
|
%Pleroma.ScheduledActivity{
|
||||||
|
user: build(:user),
|
||||||
|
scheduled_at: NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(60), :millisecond),
|
||||||
|
params: build(:note) |> Map.from_struct() |> Map.get(:data)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def registration_factory do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
%Pleroma.Registration{
|
||||||
|
user: user,
|
||||||
|
provider: "twitter",
|
||||||
|
uid: "171799000",
|
||||||
|
info: %{
|
||||||
|
"name" => "John Doe",
|
||||||
|
"email" => "john@doe.com",
|
||||||
|
"nickname" => "johndoe",
|
||||||
|
"description" => "My bio"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,6 +36,43 @@ def get("https://osada.macgirvin.com/channel/mike", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://mastodon.social/users/emelie/statuses/101849165031453009", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/httpoison_mock/status.emelie.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://mastodon.social/users/emelie", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/httpoison_mock/emelie.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(
|
||||||
|
"https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
_
|
||||||
|
) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/httpoison_mock/webfinger_emelie.json")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get("https://mastodon.social/users/emelie.atom", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/httpoison_mock/emelie.atom")
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
"https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
|
"https://osada.macgirvin.com/.well-known/webfinger?resource=acct:mike@osada.macgirvin.com",
|
||||||
_,
|
_,
|
||||||
|
|
|
@ -248,4 +248,14 @@ test "invite token is generated" do
|
||||||
assert message =~ "Generated"
|
assert message =~ "Generated"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "running delete_activities" do
|
||||||
|
test "activities are deleted" do
|
||||||
|
%{nickname: nickname} = insert(:user)
|
||||||
|
|
||||||
|
assert :ok == Mix.Tasks.Pleroma.User.run(["delete_activities", nickname])
|
||||||
|
assert_received {:mix_shell, :info, [message]}
|
||||||
|
assert message == "User #{nickname} statuses deleted."
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -122,7 +122,7 @@ test "follow takes a user and another user" do
|
||||||
|
|
||||||
{:ok, user} = User.follow(user, followed)
|
{:ok, user} = User.follow(user, followed)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
followed = User.get_by_ap_id(followed.ap_id)
|
followed = User.get_by_ap_id(followed.ap_id)
|
||||||
assert followed.info.follower_count == 1
|
assert followed.info.follower_count == 1
|
||||||
|
@ -178,7 +178,7 @@ test "unfollow takes a user and another user" do
|
||||||
|
|
||||||
{:ok, user, _activity} = User.unfollow(user, followed)
|
{:ok, user, _activity} = User.unfollow(user, followed)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert user.following == []
|
assert user.following == []
|
||||||
end
|
end
|
||||||
|
@ -188,7 +188,7 @@ test "unfollow doesn't unfollow yourself" do
|
||||||
|
|
||||||
{:error, _} = User.unfollow(user, user)
|
{:error, _} = User.unfollow(user, user)
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.following == [user.ap_id]
|
assert user.following == [user.ap_id]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -200,6 +200,13 @@ test "test if a user is following another user" do
|
||||||
refute User.following?(followed, user)
|
refute User.following?(followed, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "fetches correct profile for nickname beginning with number" do
|
||||||
|
# Use old-style integer ID to try to reproduce the problem
|
||||||
|
user = insert(:user, %{id: 1080})
|
||||||
|
userwithnumbers = insert(:user, %{nickname: "#{user.id}garbage"})
|
||||||
|
assert userwithnumbers == User.get_cached_by_nickname_or_id(userwithnumbers.nickname)
|
||||||
|
end
|
||||||
|
|
||||||
describe "user registration" do
|
describe "user registration" do
|
||||||
@full_user_data %{
|
@full_user_data %{
|
||||||
bio: "A guy",
|
bio: "A guy",
|
||||||
|
@ -679,7 +686,7 @@ test "blocks tear down cyclical follow relationships" do
|
||||||
assert User.following?(blocked, blocker)
|
assert User.following?(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = User.block(blocker, blocked)
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
blocked = Repo.get(User, blocked.id)
|
blocked = User.get_by_id(blocked.id)
|
||||||
|
|
||||||
assert User.blocks?(blocker, blocked)
|
assert User.blocks?(blocker, blocked)
|
||||||
|
|
||||||
|
@ -697,7 +704,7 @@ test "blocks tear down blocker->blocked follow relationships" do
|
||||||
refute User.following?(blocked, blocker)
|
refute User.following?(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = User.block(blocker, blocked)
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
blocked = Repo.get(User, blocked.id)
|
blocked = User.get_by_id(blocked.id)
|
||||||
|
|
||||||
assert User.blocks?(blocker, blocked)
|
assert User.blocks?(blocker, blocked)
|
||||||
|
|
||||||
|
@ -715,7 +722,7 @@ test "blocks tear down blocked->blocker follow relationships" do
|
||||||
assert User.following?(blocked, blocker)
|
assert User.following?(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = User.block(blocker, blocked)
|
{:ok, blocker} = User.block(blocker, blocked)
|
||||||
blocked = Repo.get(User, blocked.id)
|
blocked = User.get_by_id(blocked.id)
|
||||||
|
|
||||||
assert User.blocks?(blocker, blocked)
|
assert User.blocks?(blocker, blocked)
|
||||||
|
|
||||||
|
@ -792,6 +799,16 @@ test ".deactivate can de-activate then re-activate a user" do
|
||||||
assert false == user.info.deactivated
|
assert false == user.info.deactivated
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test ".delete_user_activities deletes all create activities" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"})
|
||||||
|
{:ok, _} = User.delete_user_activities(user)
|
||||||
|
|
||||||
|
# TODO: Remove favorites, repeats, delete activities.
|
||||||
|
refute Activity.get_by_id(activity.id)
|
||||||
|
end
|
||||||
|
|
||||||
test ".delete deactivates a user, all follow relationships and all create activities" do
|
test ".delete deactivates a user, all follow relationships and all create activities" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
followed = insert(:user)
|
followed = insert(:user)
|
||||||
|
@ -809,9 +826,9 @@ test ".delete deactivates a user, all follow relationships and all create activi
|
||||||
|
|
||||||
{:ok, _} = User.delete(user)
|
{:ok, _} = User.delete(user)
|
||||||
|
|
||||||
followed = Repo.get(User, followed.id)
|
followed = User.get_by_id(followed.id)
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert user.info.deactivated
|
assert user.info.deactivated
|
||||||
|
|
||||||
|
@ -820,7 +837,7 @@ test ".delete deactivates a user, all follow relationships and all create activi
|
||||||
|
|
||||||
# TODO: Remove favorites, repeats, delete activities.
|
# TODO: Remove favorites, repeats, delete activities.
|
||||||
|
|
||||||
refute Repo.get(Activity, activity.id)
|
refute Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_public_key_for_ap_id fetches a user that's not in the db" do
|
test "get_public_key_for_ap_id fetches a user that's not in the db" do
|
||||||
|
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Instances
|
alias Pleroma.Instances
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectView
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
alias Pleroma.Web.ActivityPub.UserView
|
alias Pleroma.Web.ActivityPub.UserView
|
||||||
|
@ -51,7 +50,7 @@ test "it returns a json representation of the user with accept application/json"
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|> get("/users/#{user.nickname}")
|
|> get("/users/#{user.nickname}")
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
@ -66,7 +65,7 @@ test "it returns a json representation of the user with accept application/activ
|
||||||
|> put_req_header("accept", "application/activity+json")
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|> get("/users/#{user.nickname}")
|
|> get("/users/#{user.nickname}")
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
@ -84,7 +83,7 @@ test "it returns a json representation of the user with accept application/ld+js
|
||||||
)
|
)
|
||||||
|> get("/users/#{user.nickname}")
|
|> get("/users/#{user.nickname}")
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
@ -543,7 +542,7 @@ test "it works for more than 10 users", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
Enum.each(1..15, fn _ ->
|
Enum.each(1..15, fn _ ->
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
User.follow(user, other_user)
|
User.follow(user, other_user)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -218,18 +218,18 @@ test "increases user note count only for public activities" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "1", "visibility" => "public"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "1", "visibility" => "public"})
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "2", "visibility" => "unlisted"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "unlisted"})
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "2", "visibility" => "private"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "private"})
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "3", "visibility" => "direct"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "3", "visibility" => "direct"})
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.note_count == 2
|
assert user.info.note_count == 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ test "doesn't return blocked activities" do
|
||||||
{:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
|
{:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
|
||||||
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
||||||
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
||||||
activity_three = Repo.get(Activity, activity_three.id)
|
activity_three = Activity.get_by_id(activity_three.id)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
|
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
|
||||||
|
@ -380,7 +380,7 @@ test "doesn't return muted activities" do
|
||||||
{:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
|
{:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
|
||||||
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
|
||||||
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
|
||||||
activity_three = Repo.get(Activity, activity_three.id)
|
activity_three = Activity.get_by_id(activity_three.id)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
|
ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
|
||||||
|
@ -559,7 +559,7 @@ test "unliking a previously liked object" do
|
||||||
{:ok, _, _, object} = ActivityPub.unlike(user, object)
|
{:ok, _, _, object} = ActivityPub.unlike(user, object)
|
||||||
assert object.data["like_count"] == 0
|
assert object.data["like_count"] == 0
|
||||||
|
|
||||||
assert Repo.get(Activity, like_activity.id) == nil
|
assert Activity.get_by_id(like_activity.id) == nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -610,7 +610,7 @@ test "unannouncing a previously announced object" do
|
||||||
assert unannounce_activity.data["actor"] == user.ap_id
|
assert unannounce_activity.data["actor"] == user.ap_id
|
||||||
assert unannounce_activity.data["context"] == announce_activity.data["context"]
|
assert unannounce_activity.data["context"] == announce_activity.data["context"]
|
||||||
|
|
||||||
assert Repo.get(Activity, announce_activity.id) == nil
|
assert Activity.get_by_id(announce_activity.id) == nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -635,16 +635,6 @@ test "works with base64 encoded images" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "fetch the latest Follow" do
|
|
||||||
test "fetches the latest Follow activity" do
|
|
||||||
%Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
|
|
||||||
follower = Repo.get_by(User, ap_id: activity.data["actor"])
|
|
||||||
followed = Repo.get_by(User, ap_id: activity.data["object"])
|
|
||||||
|
|
||||||
assert activity == Utils.fetch_latest_follow(follower, followed)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "fetching an object" do
|
describe "fetching an object" do
|
||||||
test "it fetches an object" do
|
test "it fetches an object" do
|
||||||
{:ok, object} =
|
{:ok, object} =
|
||||||
|
@ -749,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
|
||||||
|
@ -758,23 +748,23 @@ test "decrements user note count only for public activities" do
|
||||||
user = insert(:user, info: %{note_count: 10})
|
user = insert(:user, info: %{note_count: 10})
|
||||||
|
|
||||||
{:ok, a1} =
|
{:ok, a1} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "public"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "public"})
|
||||||
|
|
||||||
{:ok, a2} =
|
{:ok, a2} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "unlisted"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "unlisted"})
|
||||||
|
|
||||||
{:ok, a3} =
|
{:ok, a3} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "private"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "private"})
|
||||||
|
|
||||||
{:ok, a4} =
|
{:ok, a4} =
|
||||||
CommonAPI.post(Repo.get(User, user.id), %{"status" => "yeah", "visibility" => "direct"})
|
CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "direct"})
|
||||||
|
|
||||||
{:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
{:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
||||||
{:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
{:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
||||||
{:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
{:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
||||||
{:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
{:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.note_count == 10
|
assert user.info.note_count == 10
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -461,7 +461,7 @@ test "it works for incoming deletes" do
|
||||||
|
|
||||||
{:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
|
{:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
refute Repo.get(Activity, activity.id)
|
refute Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it fails for incoming deletes with spoofed origin" do
|
test "it fails for incoming deletes with spoofed origin" do
|
||||||
|
@ -481,7 +481,7 @@ test "it fails for incoming deletes with spoofed origin" do
|
||||||
|
|
||||||
:error = Transmogrifier.handle_incoming(data)
|
:error = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
assert Repo.get(Activity, activity.id)
|
assert Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming unannounces with an existing notice" do
|
test "it works for incoming unannounces with an existing notice" do
|
||||||
|
@ -639,7 +639,7 @@ test "it works for incoming accepts which were pre-accepted" do
|
||||||
|
|
||||||
assert activity.data["object"] == follow_activity.data["id"]
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == true
|
assert User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -661,7 +661,7 @@ test "it works for incoming accepts which were orphaned" do
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
||||||
assert activity.data["object"] == follow_activity.data["id"]
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == true
|
assert User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -681,7 +681,7 @@ test "it works for incoming accepts which are referenced by IRI only" do
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
{:ok, activity} = Transmogrifier.handle_incoming(accept_data)
|
||||||
assert activity.data["object"] == follow_activity.data["id"]
|
assert activity.data["object"] == follow_activity.data["id"]
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == true
|
assert User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -700,7 +700,7 @@ test "it fails for incoming accepts which cannot be correlated" do
|
||||||
|
|
||||||
:error = Transmogrifier.handle_incoming(accept_data)
|
:error = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
refute User.following?(follower, followed) == true
|
refute User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -719,7 +719,7 @@ test "it fails for incoming rejects which cannot be correlated" do
|
||||||
|
|
||||||
:error = Transmogrifier.handle_incoming(accept_data)
|
:error = Transmogrifier.handle_incoming(accept_data)
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
refute User.following?(follower, followed) == true
|
refute User.following?(follower, followed) == true
|
||||||
end
|
end
|
||||||
|
@ -744,7 +744,7 @@ test "it works for incoming rejects which are orphaned" do
|
||||||
{:ok, activity} = Transmogrifier.handle_incoming(reject_data)
|
{:ok, activity} = Transmogrifier.handle_incoming(reject_data)
|
||||||
refute activity.local
|
refute activity.local
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == false
|
assert User.following?(follower, followed) == false
|
||||||
end
|
end
|
||||||
|
@ -766,7 +766,7 @@ test "it works for incoming rejects which are referenced by IRI only" do
|
||||||
|
|
||||||
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
|
{:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
|
||||||
|
|
||||||
follower = Repo.get(User, follower.id)
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
assert User.following?(follower, followed) == false
|
assert User.following?(follower, followed) == false
|
||||||
end
|
end
|
||||||
|
@ -1020,7 +1020,7 @@ test "it upgrades a user to activitypub" do
|
||||||
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
|
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
|
||||||
assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
|
assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
|
||||||
|
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
assert user.info.note_count == 1
|
assert user.info.note_count == 1
|
||||||
|
|
||||||
{:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
|
{:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
|
||||||
|
@ -1028,13 +1028,10 @@ test "it upgrades a user to activitypub" do
|
||||||
assert user.info.note_count == 1
|
assert user.info.note_count == 1
|
||||||
assert user.follower_address == "https://niu.moe/users/rye/followers"
|
assert user.follower_address == "https://niu.moe/users/rye/followers"
|
||||||
|
|
||||||
# Wait for the background task
|
user = User.get_by_id(user.id)
|
||||||
:timer.sleep(1000)
|
|
||||||
|
|
||||||
user = Repo.get(User, 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 +1054,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
|
||||||
|
|
|
@ -1,10 +1,34 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.UtilsTest do
|
defmodule Pleroma.Web.ActivityPub.UtilsTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "fetch the latest Follow" do
|
||||||
|
test "fetches the latest Follow activity" do
|
||||||
|
%Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
|
||||||
|
follower = Repo.get_by(User, ap_id: activity.data["actor"])
|
||||||
|
followed = Repo.get_by(User, ap_id: activity.data["object"])
|
||||||
|
|
||||||
|
assert activity == Utils.fetch_latest_follow(follower, followed)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "fetch the latest Block" do
|
||||||
|
test "fetches the latest Block activity" do
|
||||||
|
blocker = insert(:user)
|
||||||
|
blocked = insert(:user)
|
||||||
|
{:ok, activity} = ActivityPub.block(blocker, blocked)
|
||||||
|
|
||||||
|
assert activity == Utils.fetch_latest_block(blocker, blocked)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "determine_explicit_mentions()" do
|
describe "determine_explicit_mentions()" do
|
||||||
test "works with an object that has mentions" do
|
test "works with an object that has mentions" do
|
||||||
object = %{
|
object = %{
|
||||||
|
@ -169,4 +193,16 @@ test "returns the what was collected if there are less pages than specified" do
|
||||||
assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1]
|
assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "make_json_ld_header/0" do
|
||||||
|
assert Utils.make_json_ld_header() == %{
|
||||||
|
"@context" => [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"http://localhost:4001/schemas/litepub-0.1.jsonld",
|
||||||
|
%{
|
||||||
|
"@language" => "und"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||||
use Pleroma.Web.ConnCase
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
@ -75,6 +74,50 @@ test "when the user doesn't exist", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "/api/pleroma/admin/user/follow" do
|
||||||
|
test "allows to force-follow another user" do
|
||||||
|
admin = insert(:user, info: %{is_admin: true})
|
||||||
|
user = insert(:user)
|
||||||
|
follower = insert(:user)
|
||||||
|
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, admin)
|
||||||
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> post("/api/pleroma/admin/user/follow", %{
|
||||||
|
"follower" => follower.nickname,
|
||||||
|
"followed" => user.nickname
|
||||||
|
})
|
||||||
|
|
||||||
|
user = User.get_by_id(user.id)
|
||||||
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
|
assert User.following?(follower, user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "/api/pleroma/admin/user/unfollow" do
|
||||||
|
test "allows to force-unfollow another user" do
|
||||||
|
admin = insert(:user, info: %{is_admin: true})
|
||||||
|
user = insert(:user)
|
||||||
|
follower = insert(:user)
|
||||||
|
|
||||||
|
User.follow(follower, user)
|
||||||
|
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, admin)
|
||||||
|
|> put_req_header("accept", "application/json")
|
||||||
|
|> post("/api/pleroma/admin/user/unfollow", %{
|
||||||
|
"follower" => follower.nickname,
|
||||||
|
"followed" => user.nickname
|
||||||
|
})
|
||||||
|
|
||||||
|
user = User.get_by_id(user.id)
|
||||||
|
follower = User.get_by_id(follower.id)
|
||||||
|
|
||||||
|
refute User.following?(follower, user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "PUT /api/pleroma/admin/users/tag" do
|
describe "PUT /api/pleroma/admin/users/tag" do
|
||||||
setup do
|
setup do
|
||||||
admin = insert(:user, info: %{is_admin: true})
|
admin = insert(:user, info: %{is_admin: true})
|
||||||
|
@ -101,13 +144,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 +180,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 +256,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 +268,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
|
||||||
|
|
|
@ -153,4 +153,40 @@ test "returns an existing mapping for an existing object" do
|
||||||
assert conversation_id == object.id
|
assert conversation_id == object.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "formats date to asctime" do
|
||||||
|
test "when date is in ISO 8601 format" do
|
||||||
|
date = DateTime.utc_now() |> DateTime.to_iso8601()
|
||||||
|
|
||||||
|
expected =
|
||||||
|
date
|
||||||
|
|> DateTime.from_iso8601()
|
||||||
|
|> elem(1)
|
||||||
|
|> Calendar.Strftime.strftime!("%a %b %d %H:%M:%S %z %Y")
|
||||||
|
|
||||||
|
assert Utils.date_to_asctime(date) == expected
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when date is a binary in wrong format" do
|
||||||
|
date = DateTime.utc_now()
|
||||||
|
|
||||||
|
expected = ""
|
||||||
|
|
||||||
|
assert Utils.date_to_asctime(date) == expected
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when date is a Unix timestamp" do
|
||||||
|
date = DateTime.utc_now() |> DateTime.to_unix()
|
||||||
|
|
||||||
|
expected = ""
|
||||||
|
|
||||||
|
assert Utils.date_to_asctime(date) == expected
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when date is nil" do
|
||||||
|
expected = ""
|
||||||
|
|
||||||
|
assert Utils.date_to_asctime(nil) == expected
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
@ -101,7 +102,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 +141,56 @@ 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
|
||||||
|
|
||||||
|
test "posting a fake status", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
real_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" =>
|
||||||
|
"\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
|
||||||
|
})
|
||||||
|
|
||||||
|
real_status = json_response(real_conn, 200)
|
||||||
|
|
||||||
|
assert real_status
|
||||||
|
assert Object.get_by_ap_id(real_status["uri"])
|
||||||
|
|
||||||
|
real_status =
|
||||||
|
real_status
|
||||||
|
|> Map.put("id", nil)
|
||||||
|
|> Map.put("url", nil)
|
||||||
|
|> Map.put("uri", nil)
|
||||||
|
|> Map.put("created_at", nil)
|
||||||
|
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
|
||||||
|
|
||||||
|
fake_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" =>
|
||||||
|
"\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
|
||||||
|
"preview" => true
|
||||||
|
})
|
||||||
|
|
||||||
|
fake_status = json_response(fake_conn, 200)
|
||||||
|
|
||||||
|
assert fake_status
|
||||||
|
refute Object.get_by_ap_id(fake_status["uri"])
|
||||||
|
|
||||||
|
fake_status =
|
||||||
|
fake_status
|
||||||
|
|> Map.put("id", nil)
|
||||||
|
|> Map.put("url", nil)
|
||||||
|
|> Map.put("uri", nil)
|
||||||
|
|> Map.put("created_at", nil)
|
||||||
|
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
|
||||||
|
|
||||||
|
assert real_status == fake_status
|
||||||
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 +205,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 +220,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"] == []
|
||||||
|
@ -340,7 +390,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
|
||||||
|
@ -356,7 +406,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
|
||||||
|
@ -455,7 +505,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
|
||||||
|
@ -469,7 +519,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
|
||||||
|
@ -492,8 +542,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
|
||||||
|
|
||||||
|
@ -1163,8 +1213,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
|
||||||
|
|
||||||
|
@ -1183,8 +1233,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
|
||||||
|
|
||||||
|
@ -1196,8 +1246,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
|
||||||
|
@ -1220,7 +1270,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()
|
||||||
|
@ -1230,8 +1280,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
|
||||||
|
@ -1516,7 +1566,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()
|
||||||
|
@ -1525,7 +1575,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()
|
||||||
|
@ -1547,7 +1597,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()
|
||||||
|
@ -1583,7 +1633,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()
|
||||||
|
@ -1940,7 +1990,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} =
|
||||||
|
@ -2316,4 +2366,323 @@ 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
|
||||||
|
|
||||||
|
describe "custom emoji" do
|
||||||
|
test "with tags", %{conn: conn} do
|
||||||
|
[emoji | _body] =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/custom_emojis")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert Map.has_key?(emoji, "shortcode")
|
||||||
|
assert Map.has_key?(emoji, "static_url")
|
||||||
|
assert Map.has_key?(emoji, "tags")
|
||||||
|
assert is_list(emoji["tags"])
|
||||||
|
assert Map.has_key?(emoji, "url")
|
||||||
|
assert Map.has_key?(emoji, "visible_in_picker")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "index/2 redirections" do
|
||||||
|
setup %{conn: conn} do
|
||||||
|
session_opts = [
|
||||||
|
store: :cookie,
|
||||||
|
key: "_test",
|
||||||
|
signing_salt: "cooldude"
|
||||||
|
]
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> Plug.Session.call(Plug.Session.init(session_opts))
|
||||||
|
|> fetch_session()
|
||||||
|
|
||||||
|
test_path = "/web/statuses/test"
|
||||||
|
%{conn: conn, path: test_path}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
|
||||||
|
conn = get(conn, path)
|
||||||
|
|
||||||
|
assert conn.status == 302
|
||||||
|
assert redirected_to(conn) == "/web/login"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
|
||||||
|
token = insert(:oauth_token)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, token.user)
|
||||||
|
|> put_session(:oauth_token, token.token)
|
||||||
|
|> get(path)
|
||||||
|
|
||||||
|
assert conn.status == 200
|
||||||
|
end
|
||||||
|
|
||||||
|
test "saves referer path to session", %{conn: conn, path: path} do
|
||||||
|
conn = get(conn, path)
|
||||||
|
return_to = Plug.Conn.get_session(conn, :return_to)
|
||||||
|
|
||||||
|
assert return_to == path
|
||||||
|
end
|
||||||
|
|
||||||
|
test "redirects to the saved path after log in", %{conn: conn, path: path} do
|
||||||
|
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
|
||||||
|
auth = insert(:oauth_authorization, app: app)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_session(:return_to, path)
|
||||||
|
|> get("/web/login", %{code: auth.token})
|
||||||
|
|
||||||
|
assert conn.status == 302
|
||||||
|
assert redirected_to(conn) == path
|
||||||
|
end
|
||||||
|
|
||||||
|
test "redirects to the getting-started page when referer is not present", %{conn: conn} do
|
||||||
|
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
|
||||||
|
auth = insert(:oauth_authorization, app: app)
|
||||||
|
|
||||||
|
conn = get(conn, "/web/login", %{code: auth.token})
|
||||||
|
|
||||||
|
assert conn.status == 302
|
||||||
|
assert redirected_to(conn) == "/web/getting-started"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "scheduled activities" do
|
||||||
|
test "creates a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
|
||||||
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
|
||||||
|
assert [] == Repo.all(Activity)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "creates a scheduled activity with a media attachment", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"media_ids" => [to_string(upload.id)],
|
||||||
|
"status" => "scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
|
||||||
|
assert %{"type" => "image"} = media_attachment
|
||||||
|
end
|
||||||
|
|
||||||
|
test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
|
||||||
|
%{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "not scheduled",
|
||||||
|
"scheduled_at" => scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"content" => "not scheduled"} = json_response(conn, 200)
|
||||||
|
assert [] == Repo.all(ScheduledActivity)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when daily user limit is exceeded", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
|
||||||
|
|
||||||
|
assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns error when total user limit is exceeded", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
today =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
tomorrow =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
attrs = %{params: %{}, scheduled_at: today}
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
||||||
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
|
||||||
|
|
||||||
|
assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows scheduled activities", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|
||||||
|
# min_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
|
||||||
|
# since_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
|
||||||
|
|
||||||
|
# max_id
|
||||||
|
conn_res =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
|
||||||
|
|
||||||
|
result = json_response(conn_res, 200)
|
||||||
|
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
|
||||||
|
assert scheduled_activity_id == scheduled_activity.id |> to_string()
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/scheduled_statuses/404")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "updates a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
new_scheduled_at =
|
||||||
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
|
||||||
|
scheduled_at: new_scheduled_at
|
||||||
|
})
|
||||||
|
|
||||||
|
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
|
||||||
|
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deletes a scheduled activity", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
scheduled_activity = insert(:scheduled_activity, user: user)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{} = json_response(res_conn, 200)
|
||||||
|
assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
|
||||||
|
|
||||||
|
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ test "Mention notification" do
|
||||||
mentioned_user = insert(:user)
|
mentioned_user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"})
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{mentioned_user.nickname}"})
|
||||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||||
user = Repo.get(User, user.id)
|
user = User.get_by_id(user.id)
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
id: to_string(notification.id),
|
id: to_string(notification.id),
|
||||||
|
@ -44,7 +44,7 @@ test "Favourite notification" do
|
||||||
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||||
{:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user)
|
{:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user)
|
||||||
{:ok, [notification]} = Notification.create_notifications(favorite_activity)
|
{:ok, [notification]} = Notification.create_notifications(favorite_activity)
|
||||||
create_activity = Repo.get(Activity, create_activity.id)
|
create_activity = Activity.get_by_id(create_activity.id)
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
id: to_string(notification.id),
|
id: to_string(notification.id),
|
||||||
|
@ -66,7 +66,7 @@ test "Reblog notification" do
|
||||||
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||||
{:ok, reblog_activity, _object} = CommonAPI.repeat(create_activity.id, another_user)
|
{:ok, reblog_activity, _object} = CommonAPI.repeat(create_activity.id, another_user)
|
||||||
{:ok, [notification]} = Notification.create_notifications(reblog_activity)
|
{:ok, [notification]} = Notification.create_notifications(reblog_activity)
|
||||||
reblog_activity = Repo.get(Activity, create_activity.id)
|
reblog_activity = Activity.get_by_id(create_activity.id)
|
||||||
|
|
||||||
expected = %{
|
expected = %{
|
||||||
id: to_string(notification.id),
|
id: to_string(notification.id),
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
alias Pleroma.ScheduledActivity
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.CommonAPI.Utils
|
||||||
|
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
test "A scheduled activity with a media attachment" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hi"})
|
||||||
|
|
||||||
|
scheduled_at =
|
||||||
|
NaiveDateTime.utc_now()
|
||||||
|
|> NaiveDateTime.add(:timer.minutes(10), :millisecond)
|
||||||
|
|> NaiveDateTime.to_iso8601()
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
attrs = %{
|
||||||
|
params: %{
|
||||||
|
"media_ids" => [upload.id],
|
||||||
|
"status" => "hi",
|
||||||
|
"sensitive" => true,
|
||||||
|
"spoiler_text" => "spoiler",
|
||||||
|
"visibility" => "unlisted",
|
||||||
|
"in_reply_to_id" => to_string(activity.id)
|
||||||
|
},
|
||||||
|
scheduled_at: scheduled_at
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, scheduled_activity} = ScheduledActivity.create(user, attrs)
|
||||||
|
result = ScheduledActivityView.render("show.json", %{scheduled_activity: scheduled_activity})
|
||||||
|
|
||||||
|
expected = %{
|
||||||
|
id: to_string(scheduled_activity.id),
|
||||||
|
media_attachments:
|
||||||
|
%{"media_ids" => [upload.id]}
|
||||||
|
|> Utils.attachments_from_ids()
|
||||||
|
|> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})),
|
||||||
|
params: %{
|
||||||
|
in_reply_to_id: to_string(activity.id),
|
||||||
|
media_ids: [upload.id],
|
||||||
|
poll: nil,
|
||||||
|
scheduled_at: nil,
|
||||||
|
sensitive: true,
|
||||||
|
spoiler_text: "spoiler",
|
||||||
|
text: "hi",
|
||||||
|
visibility: "unlisted"
|
||||||
|
},
|
||||||
|
scheduled_at: Utils.to_masto_date(scheduled_activity.scheduled_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert expected == result
|
||||||
|
end
|
||||||
|
end
|
|
@ -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)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue