Merge remote-tracking branch 'pleroma/develop' into next
This commit is contained in:
commit
64f48c4239
158 changed files with 1195 additions and 121 deletions
|
@ -1,4 +1,4 @@
|
|||
image: elixir:1.9.4
|
||||
image: git.pleroma.social:5050/pleroma/pleroma/ci-base
|
||||
|
||||
variables: &global_variables
|
||||
POSTGRES_DB: pleroma_test
|
||||
|
@ -26,12 +26,7 @@ stages:
|
|||
before_script:
|
||||
- echo $MIX_ENV
|
||||
- rm -rf _build/*/lib/pleroma
|
||||
- apt-get update && apt-get install -y cmake
|
||||
- mix local.hex --force
|
||||
- mix local.rebar --force
|
||||
- mix deps.get
|
||||
- apt-get -qq update
|
||||
- apt-get install -y libmagic-dev
|
||||
|
||||
after_script:
|
||||
- rm -rf _build/*/lib/pleroma
|
||||
|
@ -88,7 +83,6 @@ unit-testing:
|
|||
alias: postgres
|
||||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
|
||||
script:
|
||||
- apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
|
||||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
- mix coveralls --preload-modules
|
||||
|
@ -96,6 +90,7 @@ unit-testing:
|
|||
unit-testing-erratic:
|
||||
stage: test
|
||||
retry: 2
|
||||
allow_failure: true
|
||||
only:
|
||||
changes:
|
||||
- "**/*.ex"
|
||||
|
@ -146,7 +141,6 @@ unit-testing-erratic:
|
|||
# <<: *global_variables
|
||||
# RUM_ENABLED: "true"
|
||||
# script:
|
||||
# - apt-get update && apt-get install -y libimage-exiftool-perl ffmpeg
|
||||
# - mix ecto.create
|
||||
# - mix ecto.migrate
|
||||
# - "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
||||
|
@ -161,6 +155,10 @@ lint:
|
|||
- "**/*.exs"
|
||||
- "mix.lock"
|
||||
cache: *testing_cache_policy
|
||||
before_script:
|
||||
- mix local.hex --force
|
||||
- mix local.rebar --force
|
||||
- mix deps.get
|
||||
script:
|
||||
- mix format --check-formatted
|
||||
|
||||
|
@ -184,8 +182,13 @@ cycles:
|
|||
- "**/*.exs"
|
||||
- "mix.lock"
|
||||
cache: {}
|
||||
script:
|
||||
before_script:
|
||||
- mix local.hex --force
|
||||
- mix local.rebar --force
|
||||
- mix deps.get
|
||||
- apt-get update
|
||||
- apt-get install cmake libmagic-dev -y
|
||||
script:
|
||||
- mix compile
|
||||
- mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != "No cycles found")}'
|
||||
|
||||
|
|
28
CHANGELOG.md
28
CHANGELOG.md
|
@ -16,16 +16,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Added
|
||||
- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object
|
||||
- Experimental support for Finch. Put `config :tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch}` in your secrets file to use it. Reverse Proxy will still use Hackney.
|
||||
- `ForceMentionsInPostContent` MRF policy
|
||||
- AdminAPI: allow moderators to manage reports, users, invites, and custom emojis
|
||||
- AdminAPI: restrict moderators to access sensitive data: change user credentials, get password reset token, read private statuses and chats, etc
|
||||
- PleromaAPI: Add remote follow API endpoint at `POST /api/v1/pleroma/remote_interaction`
|
||||
- MastoAPI: Add `GET /api/v1/accounts/lookup`
|
||||
- MastoAPI: Profile Directory support
|
||||
- MastoAPI: Support v2 Suggestions (handpicked accounts only)
|
||||
- Ability to log slow Ecto queries by configuring `:pleroma, :telemetry, :slow_queries_logging`
|
||||
- Added Phoenix LiveDashboard at `/phoenix/live_dashboard`
|
||||
- Added `/manifest.json` for progressive web apps.
|
||||
|
||||
### Fixed
|
||||
- Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies
|
||||
- Handle Reject for already-accepted Follows properly
|
||||
- Display OpenGraph data on alternative notice routes.
|
||||
- Fix replies count for remote replies
|
||||
- Fixed hashtags disappearing from the end of lines when Markdown is enabled
|
||||
- ChatAPI: Add link headers
|
||||
- Limited number of search results to 40 to prevent DoS attacks
|
||||
- ActivityPub: fixed federation of attachment dimensions
|
||||
- Fixed benchmarks
|
||||
- Elixir 1.13 support
|
||||
- Fixed crash when pinned_objects is nil
|
||||
- Fixed slow timelines when there are a lot of deactivated users
|
||||
- Fixed account deletion API
|
||||
|
||||
### Removed
|
||||
|
||||
## 2.4.2 - 2022-01-10
|
||||
|
||||
### Fixed
|
||||
- Federation issues caused by HTTP pool checkout timeouts
|
||||
- Compatibility with Elixir 1.13
|
||||
|
||||
### Upgrade notes
|
||||
|
||||
1. Restart Pleroma
|
||||
|
||||
## 2.4.1 - 2021-08-29
|
||||
|
||||
### Changed
|
||||
|
|
7
ci/Dockerfile
Normal file
7
ci/Dockerfile
Normal file
|
@ -0,0 +1,7 @@
|
|||
FROM elixir:1.9.4
|
||||
|
||||
RUN apt-get update &&\
|
||||
apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
|
||||
mix local.hex --force &&\
|
||||
mix local.rebar --force
|
||||
|
1
ci/build_and_push.sh
Executable file
1
ci/build_and_push.sh
Executable file
|
@ -0,0 +1 @@
|
|||
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:latest --push .
|
|
@ -258,7 +258,8 @@
|
|||
show_reactions: true,
|
||||
password_reset_token_validity: 60 * 60 * 24,
|
||||
profile_directory: true,
|
||||
privileged_staff: false
|
||||
privileged_staff: false,
|
||||
max_endorsed_users: 20
|
||||
|
||||
config :pleroma, :welcome,
|
||||
direct_message: [
|
||||
|
|
|
@ -742,6 +742,16 @@
|
|||
3
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: :max_endorsed_users,
|
||||
type: :integer,
|
||||
description: "The maximum number of recommended accounts. 0 will disable the feature.",
|
||||
suggestions: [
|
||||
0,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: :autofollowed_nicknames,
|
||||
type: {:list, :string},
|
||||
|
|
|
@ -116,3 +116,9 @@ Feel free to contact us to be added to this list!
|
|||
- Contact: [@r@freesoftwareextremist.com](https://freesoftwareextremist.com/users/r)
|
||||
- Features: Does not requires JavaScript
|
||||
- Features: MastoAPI
|
||||
|
||||
### Glitch-lily
|
||||
- Source Code: <https://lily.kazv.moe/infra/glitch-lily>
|
||||
- Contact: [@tusooa@kazv.moe](https://kazv.moe/users/tusooa)
|
||||
- Features: MastoAPI
|
||||
- Based on [glitch-soc](https://github.com/glitch-soc/mastodon) frontend
|
||||
|
|
|
@ -125,6 +125,8 @@ To add configuration to your config file, you can copy it from the base config.
|
|||
* `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
|
||||
* `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
|
||||
* `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed.
|
||||
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
|
||||
* `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content.
|
||||
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
||||
|
||||
|
|
|
@ -377,12 +377,6 @@ Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer feat
|
|||
|
||||
- `GET /api/v1/identity_proofs`: Returns an empty array, `[]`
|
||||
|
||||
### Endorsements
|
||||
|
||||
*Added in Mastodon 2.5.0*
|
||||
|
||||
- `GET /api/v1/endorsements`: Returns an empty array, `[]`
|
||||
|
||||
### Featured tags
|
||||
|
||||
*Added in Mastodon 3.0.0*
|
||||
|
|
|
@ -37,7 +37,7 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap
|
|||
```
|
||||
* Note: Same data as Mastodon API’s `/api/v1/custom_emojis` but in a different format
|
||||
|
||||
## `/api/v1/pleroma/follow_import`
|
||||
## `/api/pleroma/follow_import`
|
||||
### Imports your follows, for example from a Mastodon CSV file.
|
||||
* Method: `POST`
|
||||
* Authentication: required
|
||||
|
@ -46,7 +46,7 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap
|
|||
* Response: HTTP 200 on success, 500 on error
|
||||
* Note: Users that can't be followed are silently skipped.
|
||||
|
||||
## `/api/v1/pleroma/blocks_import`
|
||||
## `/api/pleroma/blocks_import`
|
||||
### Imports your blocks.
|
||||
* Method: `POST`
|
||||
* Authentication: required
|
||||
|
@ -54,7 +54,7 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap
|
|||
* `list`: STRING or FILE containing a whitespace-separated list of accounts to block
|
||||
* Response: HTTP 200 on success, 500 on error
|
||||
|
||||
## `/api/v1/pleroma/mutes_import`
|
||||
## `/api/pleroma/mutes_import`
|
||||
### Imports your mutes.
|
||||
* Method: `POST`
|
||||
* Authentication: required
|
||||
|
@ -70,7 +70,7 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap
|
|||
* Response: Provider specific JSON, the only guaranteed parameter is `type`
|
||||
* Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint", "seconds_valid": 300}`
|
||||
|
||||
## `/api/v1/pleroma/delete_account`
|
||||
## `/api/pleroma/delete_account`
|
||||
### Delete an account
|
||||
* Method `POST`
|
||||
* Authentication: required
|
||||
|
@ -79,7 +79,7 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap
|
|||
* Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
|
||||
* Example response: `{"error": "Invalid password."}`
|
||||
|
||||
## `/api/v1/pleroma/disable_account`
|
||||
## `/api/pleroma/disable_account`
|
||||
### Disable an account
|
||||
* Method `POST`
|
||||
* Authentication: required
|
||||
|
@ -88,21 +88,22 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap
|
|||
* Response: JSON. Returns `{"status": "success"}` if the account was successfully disabled, `{"error": "[error message]"}` otherwise
|
||||
* Example response: `{"error": "Invalid password."}`
|
||||
|
||||
## `/api/v1/pleroma/accounts/mfa`
|
||||
## `/api/pleroma/accounts/mfa`
|
||||
#### Gets current MFA settings
|
||||
* method: `GET`
|
||||
* Authentication: required
|
||||
* OAuth scope: `read:security`
|
||||
* Response: JSON. Returns `{"enabled": "false", "totp": false }`
|
||||
* Response: JSON. Returns `{"settings": {"enabled": "false", "totp": false }}`
|
||||
* Note: `enabled` is whether multi-factor auth is enabled for the user in general, while `totp` is one type of MFA.
|
||||
|
||||
## `/api/v1/pleroma/accounts/mfa/setup/totp`
|
||||
## `/api/pleroma/accounts/mfa/setup/totp`
|
||||
#### Pre-setup the MFA/TOTP method
|
||||
* method: `GET`
|
||||
* Authentication: required
|
||||
* OAuth scope: `write:security`
|
||||
* Response: JSON. Returns `{"key": [secret_key], "provisioning_uri": "[qr code uri]" }` when successful, otherwise returns HTTP 422 `{"error": "error_msg"}`
|
||||
|
||||
## `/api/v1/pleroma/accounts/mfa/confirm/totp`
|
||||
## `/api/pleroma/accounts/mfa/confirm/totp`
|
||||
#### Confirms & enables MFA/TOTP support for user account.
|
||||
* method: `POST`
|
||||
* Authentication: required
|
||||
|
@ -113,7 +114,7 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap
|
|||
* Response: JSON. Returns `{}` if the enable was successful, HTTP 422 `{"error": "[error message]"}` otherwise
|
||||
|
||||
|
||||
## `/api/v1/pleroma/accounts/mfa/totp`
|
||||
## `/api/pleroma/accounts/mfa/totp`
|
||||
#### Disables MFA/TOTP method for user account.
|
||||
* method: `DELETE`
|
||||
* Authentication: required
|
||||
|
@ -123,7 +124,7 @@ The `/api/v1/pleroma/*` path is backwards compatible with `/api/pleroma/*` (`/ap
|
|||
* Response: JSON. Returns `{}` if the disable was successful, HTTP 422 `{"error": "[error message]"}` otherwise
|
||||
* Example response: `{"error": "Invalid password."}`
|
||||
|
||||
## `/api/v1/pleroma/accounts/mfa/backup_codes`
|
||||
## `/api/pleroma/accounts/mfa/backup_codes`
|
||||
#### Generstes backup codes MFA for user account.
|
||||
* method: `GET`
|
||||
* Authentication: required
|
||||
|
@ -331,7 +332,7 @@ See [Admin-API](admin_api.md)
|
|||
}
|
||||
```
|
||||
|
||||
## `/api/v1/pleroma/change_email`
|
||||
## `/api/pleroma/change_email`
|
||||
### Change account email
|
||||
* Method `POST`
|
||||
* Authentication: required
|
||||
|
@ -659,3 +660,38 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
|
|||
"url": "https://example.com/media/backups/archive-foobar-20200910T161803-QUhx6VYDRQ2wfV0SdA2Pfj_2CLM_ATUlw-D5l5TJf4Q.zip"
|
||||
}]
|
||||
```
|
||||
|
||||
## `GET /api/oauth_tokens`
|
||||
### Retrieve a list of active sessions for the user
|
||||
* Method: `GET`
|
||||
* Authentication: required
|
||||
* Params: none
|
||||
* Response: JSON
|
||||
* Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"app_name": "Pleroma FE",
|
||||
"id": 9275,
|
||||
"valid_until": "2121-11-24T15:51:08.234234"
|
||||
},
|
||||
{
|
||||
"app_name": "Patron",
|
||||
"id": 8805,
|
||||
"valid_until": "2121-10-26T18:09:59.857150"
|
||||
},
|
||||
{
|
||||
"app_name": "Soapbox FE",
|
||||
"id": 9727,
|
||||
"valid_until": "2121-12-25T16:52:39.692877"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## `DELETE /api/oauth_tokens/:id`
|
||||
### Revoke a user session by its ID
|
||||
* Method: `DELETE`
|
||||
* Authentication: required
|
||||
* Params: none
|
||||
* Response: HTTP 200 on success, 500 on error
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
reblog_mute: 3,
|
||||
notification_mute: 4,
|
||||
inverse_subscription: 5,
|
||||
suggestion_dismiss: 6
|
||||
suggestion_dismiss: 6,
|
||||
endorsement: 7
|
||||
)
|
||||
|
||||
defenum(Pleroma.FollowingRelationship.State,
|
||||
|
|
|
@ -34,32 +34,34 @@ def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
|
|||
|
||||
def mention_handler("@" <> nickname, buffer, opts, acc) do
|
||||
case User.get_cached_by_nickname(nickname) do
|
||||
%User{id: id} = user ->
|
||||
user_url = user.uri || user.ap_id
|
||||
nickname_text = get_nickname_text(nickname, opts)
|
||||
|
||||
link =
|
||||
Phoenix.HTML.Tag.content_tag(
|
||||
:span,
|
||||
Phoenix.HTML.Tag.content_tag(
|
||||
:a,
|
||||
["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
|
||||
"data-user": id,
|
||||
class: "u-url mention",
|
||||
href: user_url,
|
||||
rel: "ugc"
|
||||
),
|
||||
class: "h-card"
|
||||
)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
|
||||
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
|
||||
%User{} = user ->
|
||||
{mention_from_user(user, opts),
|
||||
%{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
|
||||
|
||||
_ ->
|
||||
{buffer, acc}
|
||||
end
|
||||
end
|
||||
|
||||
def mention_from_user(%User{id: id} = user, opts \\ %{mentions_format: :full}) do
|
||||
user_url = user.uri || user.ap_id
|
||||
nickname_text = get_nickname_text(user.nickname, opts)
|
||||
|
||||
Phoenix.HTML.Tag.content_tag(
|
||||
:span,
|
||||
Phoenix.HTML.Tag.content_tag(
|
||||
:a,
|
||||
["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
|
||||
"data-user": id,
|
||||
class: "u-url mention",
|
||||
href: user_url,
|
||||
rel: "ugc"
|
||||
),
|
||||
class: "h-card"
|
||||
)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
end
|
||||
|
||||
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
|
||||
tag = String.downcase(tag)
|
||||
url = "#{Pleroma.Web.Endpoint.url()}/tag/#{tag}"
|
||||
|
|
|
@ -78,6 +78,10 @@ defmodule Pleroma.User do
|
|||
inverse_subscription: [
|
||||
subscribee_subscriptions: :subscriber_users,
|
||||
subscriber_subscriptions: :subscribee_users
|
||||
],
|
||||
endorsement: [
|
||||
endorser_endorsements: :endorsed_users,
|
||||
endorsee_endorsements: :endorser_users
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -171,25 +175,25 @@ defmodule Pleroma.User do
|
|||
{incoming_relation, incoming_relation_source}
|
||||
]} <- @user_relationships_config do
|
||||
# Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
|
||||
# :notification_muter_mutes, :subscribee_subscriptions
|
||||
# :notification_muter_mutes, :subscribee_subscriptions, :endorser_endorsements
|
||||
has_many(outgoing_relation, UserRelationship,
|
||||
foreign_key: :source_id,
|
||||
where: [relationship_type: relationship_type]
|
||||
)
|
||||
|
||||
# Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
|
||||
# :notification_mutee_mutes, :subscriber_subscriptions
|
||||
# :notification_mutee_mutes, :subscriber_subscriptions, :endorsee_endorsements
|
||||
has_many(incoming_relation, UserRelationship,
|
||||
foreign_key: :target_id,
|
||||
where: [relationship_type: relationship_type]
|
||||
)
|
||||
|
||||
# Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
|
||||
# :notification_muted_users, :subscriber_users
|
||||
# :notification_muted_users, :subscriber_users, :endorsed_users
|
||||
has_many(outgoing_relation_target, through: [outgoing_relation, :target])
|
||||
|
||||
# Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
|
||||
# :notification_muter_users, :subscribee_users
|
||||
# :notification_muter_users, :subscribee_users, :endorser_users
|
||||
has_many(incoming_relation_source, through: [incoming_relation, :source])
|
||||
end
|
||||
|
||||
|
@ -217,7 +221,7 @@ defmodule Pleroma.User do
|
|||
@user_relationships_config do
|
||||
# `def blocked_users_relation/2`, `def muted_users_relation/2`,
|
||||
# `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
|
||||
# `def subscriber_users/2`
|
||||
# `def subscriber_users/2`, `def endorsed_users_relation/2`
|
||||
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
|
||||
target_users_query = assoc(user, unquote(outgoing_relation_target))
|
||||
|
||||
|
@ -230,7 +234,7 @@ def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated?
|
|||
end
|
||||
|
||||
# `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
|
||||
# `def notification_muted_users/2`, `def subscriber_users/2`
|
||||
# `def notification_muted_users/2`, `def subscriber_users/2`, `def endorsed_users/2`
|
||||
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
|
||||
__MODULE__
|
||||
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
||||
|
@ -241,7 +245,8 @@ def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
|
|||
end
|
||||
|
||||
# `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
|
||||
# `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`
|
||||
# `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`,
|
||||
# `def endorsed_users_ap_ids/2`
|
||||
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
|
||||
__MODULE__
|
||||
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
|
||||
|
@ -1054,6 +1059,10 @@ def get_by_ap_id(ap_id) do
|
|||
Repo.get_by(User, ap_id: ap_id)
|
||||
end
|
||||
|
||||
def get_by_uri(uri) do
|
||||
Repo.get_by(User, uri: uri)
|
||||
end
|
||||
|
||||
def get_all_by_ap_id(ap_ids) do
|
||||
from(u in __MODULE__,
|
||||
where: u.ap_id in ^ap_ids
|
||||
|
@ -1520,6 +1529,40 @@ def unblock(%User{} = blocker, %{ap_id: ap_id}) do
|
|||
unblock(blocker, get_cached_by_ap_id(ap_id))
|
||||
end
|
||||
|
||||
def endorse(%User{} = endorser, %User{} = target) do
|
||||
with max_endorsed_users <- Pleroma.Config.get([:instance, :max_endorsed_users], 0),
|
||||
endorsed_users <-
|
||||
User.endorsed_users_relation(endorser)
|
||||
|> Repo.aggregate(:count, :id) do
|
||||
cond do
|
||||
endorsed_users >= max_endorsed_users ->
|
||||
{:error, "You have already pinned the maximum number of users"}
|
||||
|
||||
not following?(endorser, target) ->
|
||||
{:error, "Could not endorse: You are not following #{target.nickname}"}
|
||||
|
||||
true ->
|
||||
UserRelationship.create_endorsement(endorser, target)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def endorse(%User{} = endorser, %{ap_id: ap_id}) do
|
||||
with %User{} = endorsed <- get_cached_by_ap_id(ap_id) do
|
||||
endorse(endorser, endorsed)
|
||||
end
|
||||
end
|
||||
|
||||
def unendorse(%User{} = unendorser, %User{} = target) do
|
||||
UserRelationship.delete_endorsement(unendorser, target)
|
||||
end
|
||||
|
||||
def unendorse(%User{} = unendorser, %{ap_id: ap_id}) do
|
||||
with %User{} = user <- get_cached_by_ap_id(ap_id) do
|
||||
unendorse(unendorser, user)
|
||||
end
|
||||
end
|
||||
|
||||
def mutes?(nil, _), do: false
|
||||
def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
|
||||
|
||||
|
@ -1565,6 +1608,10 @@ def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
|
|||
end
|
||||
end
|
||||
|
||||
def endorses?(%User{} = user, %User{} = target) do
|
||||
UserRelationship.endorsement_exists?(user, target)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
|
||||
E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
|
||||
|
|
|
@ -24,17 +24,20 @@ defmodule Pleroma.UserRelationship do
|
|||
|
||||
for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
|
||||
# `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
|
||||
# `def create_notification_mute/2`, `def create_inverse_subscription/2`
|
||||
# `def create_notification_mute/2`, `def create_inverse_subscription/2`,
|
||||
# `def endorsement/2`
|
||||
def unquote(:"create_#{relationship_type}")(source, target),
|
||||
do: create(unquote(relationship_type), source, target)
|
||||
|
||||
# `def delete_block/2`, `def delete_mute/2`, `def delete_reblog_mute/2`,
|
||||
# `def delete_notification_mute/2`, `def delete_inverse_subscription/2`
|
||||
# `def delete_notification_mute/2`, `def delete_inverse_subscription/2`,
|
||||
# `def delete_endorsement/2`
|
||||
def unquote(:"delete_#{relationship_type}")(source, target),
|
||||
do: delete(unquote(relationship_type), source, target)
|
||||
|
||||
# `def block_exists?/2`, `def mute_exists?/2`, `def reblog_mute_exists?/2`,
|
||||
# `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2`
|
||||
# `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2`,
|
||||
# `def inverse_endorsement?/2`
|
||||
def unquote(:"#{relationship_type}_exists?")(source, target),
|
||||
do: exists?(unquote(relationship_type), source, target)
|
||||
end
|
||||
|
|
|
@ -1666,7 +1666,10 @@ def pin_data_from_featured_collection(%{
|
|||
"orderedItems" => objects
|
||||
})
|
||||
when type in ["OrderedCollection", "Collection"] do
|
||||
Map.new(objects, fn %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()} end)
|
||||
Map.new(objects, fn
|
||||
%{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()}
|
||||
object_ap_id when is_binary(object_ap_id) -> {object_ap_id, NaiveDateTime.utc_now()}
|
||||
end)
|
||||
end
|
||||
|
||||
def fetch_and_prepare_featured_from_ap_id(nil) do
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.User
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||
|
||||
defp do_extract({:a, attrs, _}, acc) do
|
||||
if Enum.find(attrs, fn {name, value} ->
|
||||
name == "class" && value in ["mention", "u-url mention", "mention u-url"]
|
||||
end) do
|
||||
href = Enum.find(attrs, fn {name, _} -> name == "href" end) |> elem(1)
|
||||
acc ++ [href]
|
||||
else
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
defp do_extract({_, _, children}, acc) do
|
||||
do_extract(children, acc)
|
||||
end
|
||||
|
||||
defp do_extract(nodes, acc) when is_list(nodes) do
|
||||
Enum.reduce(nodes, acc, fn node, acc -> do_extract(node, acc) end)
|
||||
end
|
||||
|
||||
defp do_extract(_, acc), do: acc
|
||||
|
||||
defp extract_mention_uris_from_content(content) do
|
||||
{:ok, tree} = :fast_html.decode(content, format: [:html_atoms])
|
||||
do_extract(tree, [])
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create", "object" => %{"type" => "Note", "tag" => tag}} = object) do
|
||||
# image-only posts from pleroma apparently reach this MRF without the content field
|
||||
content = object["object"]["content"] || ""
|
||||
|
||||
mention_users =
|
||||
tag
|
||||
|> Enum.filter(fn tag -> tag["type"] == "Mention" end)
|
||||
|> Enum.map(& &1["href"])
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Enum.map(fn ap_id_or_uri ->
|
||||
case User.get_or_fetch_by_ap_id(ap_id_or_uri) do
|
||||
{:ok, user} -> {ap_id_or_uri, user}
|
||||
_ -> {ap_id_or_uri, User.get_by_uri(ap_id_or_uri)}
|
||||
end
|
||||
end)
|
||||
|> Enum.reject(fn {_, user} -> user == nil end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
explicitly_mentioned_uris = extract_mention_uris_from_content(content)
|
||||
|
||||
added_mentions =
|
||||
Enum.reduce(mention_users, "", fn {uri, user}, acc ->
|
||||
unless uri in explicitly_mentioned_uris do
|
||||
acc <> Formatter.mention_from_user(user)
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
content =
|
||||
if added_mentions != "",
|
||||
do: added_mentions <> " " <> content,
|
||||
else: content
|
||||
|
||||
{:ok, put_in(object["object"]["content"], content)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(object), do: {:ok, object}
|
||||
|
||||
@impl true
|
||||
def describe, do: {:ok, %{}}
|
||||
end
|
|
@ -334,6 +334,42 @@ def unblock_operation do
|
|||
}
|
||||
end
|
||||
|
||||
def endorse_operation do
|
||||
%Operation{
|
||||
tags: ["Account actions"],
|
||||
summary: "Endorse",
|
||||
operationId: "AccountController.endorse",
|
||||
security: [%{"oAuth" => ["follow", "write:accounts"]}],
|
||||
description: "Addds the given account to endorsed accounts list.",
|
||||
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
|
||||
responses: %{
|
||||
200 => Operation.response("Relationship", "application/json", AccountRelationship),
|
||||
400 =>
|
||||
Operation.response("Bad Request", "application/json", %Schema{
|
||||
allOf: [ApiError],
|
||||
title: "Unprocessable Entity",
|
||||
example: %{
|
||||
"error" => "You have already pinned the maximum number of users"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def unendorse_operation do
|
||||
%Operation{
|
||||
tags: ["Account actions"],
|
||||
summary: "Unendorse",
|
||||
operationId: "AccountController.unendorse",
|
||||
security: [%{"oAuth" => ["follow", "write:accounts"]}],
|
||||
description: "Removes the given account from endorsed accounts list.",
|
||||
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
|
||||
responses: %{
|
||||
200 => Operation.response("Relationship", "application/json", AccountRelationship)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def note_operation do
|
||||
%Operation{
|
||||
tags: ["Account actions"],
|
||||
|
@ -400,15 +436,35 @@ def blocks_operation do
|
|||
}
|
||||
end
|
||||
|
||||
def lookup_operation do
|
||||
%Operation{
|
||||
tags: ["Account lookup"],
|
||||
summary: "Find a user by nickname",
|
||||
operationId: "AccountController.lookup",
|
||||
parameters: [
|
||||
Operation.parameter(
|
||||
:acct,
|
||||
:query,
|
||||
:string,
|
||||
"User nickname"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Account", "application/json", Account),
|
||||
404 => Operation.response("Error", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def endorsements_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve account information"],
|
||||
summary: "Endorsements",
|
||||
operationId: "AccountController.endorsements",
|
||||
description: "Not implemented",
|
||||
description: "Returns endorsed accounts",
|
||||
security: [%{"oAuth" => ["read:accounts"]}],
|
||||
responses: %{
|
||||
200 => empty_array_response()
|
||||
200 => Operation.response("Array of Accounts", "application/json", array_of_accounts())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
|
|||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.App
|
||||
|
||||
@spec open_api_operation(atom) :: Operation.t()
|
||||
def open_api_operation(action) do
|
||||
|
@ -22,7 +23,7 @@ def create_operation do
|
|||
operationId: "AppController.create",
|
||||
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("App", "application/json", create_response()),
|
||||
200 => Operation.response("App", "application/json", App),
|
||||
422 =>
|
||||
Operation.response(
|
||||
"Unprocessable Entity",
|
||||
|
@ -119,30 +120,4 @@ defp create_request do
|
|||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp create_response do
|
||||
%Schema{
|
||||
title: "AppCreateResponse",
|
||||
description: "Response schema for an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
name: %Schema{type: :string},
|
||||
client_id: %Schema{type: :string},
|
||||
client_secret: %Schema{type: :string},
|
||||
redirect_uri: %Schema{type: :string},
|
||||
vapid_key: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"id" => "123",
|
||||
"name" => "My App",
|
||||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||
"vapid_key" =>
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias Pleroma.Web.ApiSpec.AccountOperation
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
|
||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||
|
@ -62,6 +63,25 @@ def favourites_operation do
|
|||
}
|
||||
end
|
||||
|
||||
def endorsements_operation do
|
||||
%Operation{
|
||||
tags: ["Retrieve account information"],
|
||||
summary: "Endorsements",
|
||||
description: "Returns endorsed accounts",
|
||||
operationId: "PleromaAPI.AccountController.endorsements",
|
||||
parameters: [with_relationships_param(), id_param()],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response(
|
||||
"Array of Accounts",
|
||||
"application/json",
|
||||
AccountOperation.array_of_accounts()
|
||||
),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def subscribe_operation do
|
||||
%Operation{
|
||||
tags: ["Account actions"],
|
||||
|
|
31
lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex
Normal file
31
lib/pleroma/web/api_spec/operations/pleroma_app_operation.ex
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.PleromaAppOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Schemas.App
|
||||
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
@spec index_operation() :: Operation.t()
|
||||
def index_operation do
|
||||
%Operation{
|
||||
tags: ["Applications"],
|
||||
summary: "List applications",
|
||||
description: "List the OAuth applications for the current user",
|
||||
operationId: "AppController.index",
|
||||
responses: %{
|
||||
200 => Operation.response("Array of App", "application/json", array_of_apps())
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp array_of_apps do
|
||||
%Schema{type: :array, items: App, example: [App.schema().example]}
|
||||
end
|
||||
end
|
33
lib/pleroma/web/api_spec/schemas/app.ex
Normal file
33
lib/pleroma/web/api_spec/schemas/app.ex
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.Schemas.App do
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "App",
|
||||
description: "Response schema for an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
name: %Schema{type: :string},
|
||||
client_id: %Schema{type: :string},
|
||||
client_secret: %Schema{type: :string},
|
||||
redirect_uri: %Schema{type: :string},
|
||||
vapid_key: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"id" => "123",
|
||||
"name" => "My App",
|
||||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||
"vapid_key" =>
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
})
|
||||
end
|
|
@ -117,7 +117,8 @@ def follow(follower, followed) do
|
|||
def unfollow(follower, unfollowed) do
|
||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
|
||||
{:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
|
||||
{:ok, _subscription} <- User.unsubscribe(follower, unfollowed),
|
||||
{:ok, _endorsement} <- User.unendorse(follower, unfollowed) do
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(:skip_auth when action == :create)
|
||||
plug(:skip_auth when action in [:create, :lookup])
|
||||
|
||||
plug(:skip_public_check when action in [:show, :statuses])
|
||||
|
||||
|
@ -57,7 +57,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["write:accounts"]}
|
||||
when action in [:update_credentials, :note]
|
||||
when action in [:update_credentials, :note, :endorse, :unendorse]
|
||||
)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
|
||||
|
@ -84,7 +84,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
|
||||
|
||||
@relationship_actions [:follow, :unfollow]
|
||||
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock note)a
|
||||
@needs_account ~W(
|
||||
followers following lists follow unfollow mute unmute block unblock note endorse unendorse
|
||||
)a
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
|
@ -451,6 +453,24 @@ def note(
|
|||
end
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/accounts/:id/pin"
|
||||
def endorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
|
||||
with {:ok, _user_relationships} <- User.endorse(endorser, endorsed) do
|
||||
render(conn, "relationship.json", user: endorser, target: endorsed)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :bad_request, %{error: message})
|
||||
end
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/accounts/:id/unpin"
|
||||
def unendorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
|
||||
with {:ok, _user_relationships} <- User.unendorse(endorser, endorsed) do
|
||||
render(conn, "relationship.json", user: endorser, target: endorsed)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
end
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/follows"
|
||||
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
|
||||
case User.get_cached_by_nickname(uri) do
|
||||
|
@ -493,8 +513,33 @@ def blocks(%{assigns: %{user: user}} = conn, params) do
|
|||
|> render("index.json", users: users, for: user, as: :user)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/accounts/lookup"
|
||||
def lookup(conn, %{acct: nickname} = _params) do
|
||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
||||
render(conn, "show.json",
|
||||
user: user,
|
||||
skip_visibility_check: true
|
||||
)
|
||||
else
|
||||
error -> user_visibility_error(conn, error)
|
||||
end
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/endorsements"
|
||||
def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)
|
||||
def endorsements(%{assigns: %{user: user}} = conn, params) do
|
||||
users =
|
||||
user
|
||||
|> User.endorsed_users_relation(_restrict_deactivated = true)
|
||||
|> Pleroma.Repo.all()
|
||||
|
||||
conn
|
||||
|> render("index.json",
|
||||
users: users,
|
||||
for: user,
|
||||
as: :user,
|
||||
embed_relationships: embed_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/identity_proofs"
|
||||
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
|
||||
|
|
|
@ -10,7 +10,9 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
|||
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
@ -26,11 +28,13 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
|||
@doc "POST /api/v1/apps"
|
||||
def create(%{body_params: params} = conn, _params) do
|
||||
scopes = Scopes.fetch_scopes(params, ["read"])
|
||||
user_id = get_user_id(conn)
|
||||
|
||||
app_attrs =
|
||||
params
|
||||
|> Map.take([:client_name, :redirect_uris, :website])
|
||||
|> Map.put(:scopes, scopes)
|
||||
|> Maps.put_if_present(:user_id, user_id)
|
||||
|
||||
with cs <- App.register_changeset(%App{}, app_attrs),
|
||||
{:ok, app} <- Repo.insert(cs) do
|
||||
|
@ -38,6 +42,9 @@ def create(%{body_params: params} = conn, _params) do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_user_id(%{assigns: %{user: %User{id: user_id}}}), do: user_id
|
||||
defp get_user_id(_conn), do: nil
|
||||
|
||||
@doc """
|
||||
GET /api/v1/apps/verify_credentials
|
||||
Gets compact non-secret representation of the app. Supports app tokens and user tokens.
|
||||
|
|
|
@ -160,11 +160,18 @@ def render(
|
|||
target,
|
||||
&User.muting_reblogs?(&1, &2)
|
||||
),
|
||||
endorsed: false,
|
||||
note:
|
||||
UserNote.show(
|
||||
reading_user,
|
||||
target
|
||||
),
|
||||
endorsed:
|
||||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
:endorsement,
|
||||
target,
|
||||
reading_user,
|
||||
&User.endorses?(&2, &1)
|
||||
)
|
||||
}
|
||||
end
|
||||
|
|
|
@ -68,6 +68,9 @@ def features do
|
|||
"shareable_emoji_packs",
|
||||
"multifetch",
|
||||
"pleroma:api/v1/notifications:include_types_filter",
|
||||
if Config.get([:activitypub, :blockers_visible]) do
|
||||
"blockers_visible"
|
||||
end,
|
||||
if Config.get([:media_proxy, :enabled]) do
|
||||
"media_proxy"
|
||||
end,
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
|
@ -19,6 +20,8 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
field(:client_secret, :string)
|
||||
field(:trusted, :boolean, default: false)
|
||||
|
||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||
|
||||
has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all)
|
||||
has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all)
|
||||
|
||||
|
@ -27,7 +30,7 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
|
||||
@spec changeset(t(), map()) :: Ecto.Changeset.t()
|
||||
def changeset(struct, params) do
|
||||
cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
|
||||
cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted, :user_id])
|
||||
end
|
||||
|
||||
@spec register_changeset(t(), map()) :: Ecto.Changeset.t()
|
||||
|
@ -129,6 +132,12 @@ def search(params) do
|
|||
{:ok, Repo.all(query), count}
|
||||
end
|
||||
|
||||
@spec get_user_apps(User.t()) :: {:ok, [t()], non_neg_integer()}
|
||||
def get_user_apps(%User{id: user_id}) do
|
||||
from(a in __MODULE__, where: a.user_id == ^user_id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def destroy(id) do
|
||||
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
|
||||
|
|
|
@ -6,7 +6,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper,
|
||||
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
|
||||
only: [
|
||||
json_response: 3,
|
||||
add_link_headers: 2,
|
||||
embed_relationships?: 1,
|
||||
assign_account_by_id: 2
|
||||
]
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
@ -40,9 +45,18 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
|||
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
|
||||
)
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
|
||||
when action == :endorsements
|
||||
)
|
||||
|
||||
plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
|
||||
|
||||
plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
|
||||
plug(
|
||||
:assign_account_by_id
|
||||
when action in [:favourites, :endorsements, :subscribe, :unsubscribe]
|
||||
)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
|
||||
|
||||
|
@ -90,6 +104,22 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
|||
)
|
||||
end
|
||||
|
||||
@doc "GET /api/v1/pleroma/accounts/:id/endorsements"
|
||||
def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do
|
||||
users =
|
||||
user
|
||||
|> User.endorsed_users_relation(_restrict_deactivated = true)
|
||||
|> Pleroma.Repo.all()
|
||||
|
||||
conn
|
||||
|> render("index.json",
|
||||
for: for_user,
|
||||
users: users,
|
||||
as: :user,
|
||||
embed_relationships: embed_relationships?(params)
|
||||
)
|
||||
end
|
||||
|
||||
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
|
||||
def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
|
||||
with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
|
||||
|
|
23
lib/pleroma/web/pleroma_api/controllers/app_controller.ex
Normal file
23
lib/pleroma/web/pleroma_api/controllers/app_controller.ex
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.AppController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["follow", "read"]} when action in [:index])
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAppOperation
|
||||
|
||||
@doc "GET /api/v1/pleroma/apps"
|
||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||
with apps <- App.get_user_apps(user) do
|
||||
render(conn, "index.json", %{apps: apps})
|
||||
end
|
||||
end
|
||||
end
|
11
lib/pleroma/web/pleroma_api/views/app_view.ex
Normal file
11
lib/pleroma/web/pleroma_api/views/app_view.ex
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.AppView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("index.json", %{apps: apps}) do
|
||||
render_many(apps, Pleroma.Web.MastodonAPI.AppView, "show.json")
|
||||
end
|
||||
end
|
|
@ -399,6 +399,7 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
||||
pipe_through(:api)
|
||||
|
||||
get("/apps", AppController, :index)
|
||||
get("/statuses/:id/reactions/:emoji", EmojiReactionController, :index)
|
||||
get("/statuses/:id/reactions", EmojiReactionController, :index)
|
||||
end
|
||||
|
@ -443,6 +444,7 @@ defmodule Pleroma.Web.Router do
|
|||
scope [] do
|
||||
pipe_through(:api)
|
||||
get("/accounts/:id/favourites", AccountController, :favourites)
|
||||
get("/accounts/:id/endorsements", AccountController, :endorsements)
|
||||
end
|
||||
|
||||
scope [] do
|
||||
|
@ -489,6 +491,8 @@ defmodule Pleroma.Web.Router do
|
|||
post("/accounts/:id/mute", AccountController, :mute)
|
||||
post("/accounts/:id/unmute", AccountController, :unmute)
|
||||
post("/accounts/:id/note", AccountController, :note)
|
||||
post("/accounts/:id/pin", AccountController, :endorse)
|
||||
post("/accounts/:id/unpin", AccountController, :unendorse)
|
||||
|
||||
get("/conversations", ConversationController, :index)
|
||||
post("/conversations/:id/read", ConversationController, :mark_as_read)
|
||||
|
@ -588,6 +592,8 @@ defmodule Pleroma.Web.Router do
|
|||
get("/accounts/search", SearchController, :account_search)
|
||||
get("/search", SearchController, :search)
|
||||
|
||||
get("/accounts/lookup", AccountController, :lookup)
|
||||
|
||||
get("/accounts/:id/statuses", AccountController, :statuses)
|
||||
get("/accounts/:id/followers", AccountController, :followers)
|
||||
get("/accounts/:id/following", AccountController, :following)
|
||||
|
|
9
mix.exs
9
mix.exs
|
@ -150,8 +150,7 @@ defp deps do
|
|||
git: "https://github.com/msantos/crypt.git",
|
||||
ref: "f75cd55325e33cbea198fb41fe41871392f8fb76"},
|
||||
{:cors_plug, "~> 2.0"},
|
||||
{:web_push_encryption,
|
||||
git: "https://github.com/lanodan/elixir-web-push-encryption.git", branch: "bugfix/otp-24"},
|
||||
{:web_push_encryption, "~> 0.3.1"},
|
||||
{:swoosh, "~> 1.0"},
|
||||
{:phoenix_swoosh, "~> 0.3"},
|
||||
{:gen_smtp, "~> 0.13"},
|
||||
|
@ -159,7 +158,7 @@ defp deps do
|
|||
{:floki, "~> 0.27"},
|
||||
{:timex, "~> 3.6"},
|
||||
{:ueberauth, "~> 0.4"},
|
||||
{:linkify, "~> 0.5.1"},
|
||||
{:linkify, "~> 0.5.2"},
|
||||
{:http_signatures, "~> 0.1.1"},
|
||||
{:telemetry, "~> 0.3"},
|
||||
{:poolboy, "~> 1.5"},
|
||||
|
@ -184,9 +183,7 @@ defp deps do
|
|||
{:ex_const, "~> 0.2"},
|
||||
{:plug_static_index_html, "~> 1.0.0"},
|
||||
{:flake_id, "~> 0.1.0"},
|
||||
{:concurrent_limiter,
|
||||
git: "https://gitlab.com/soapbox-pub/elixir-libraries/concurrent_limiter.git",
|
||||
ref: "d81be41024569330f296fc472e24198d7499ba78"},
|
||||
{:concurrent_limiter, "~> 0.1.1"},
|
||||
{:remote_ip,
|
||||
git: "https://git.pleroma.social/pleroma/remote_ip.git",
|
||||
ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"},
|
||||
|
|
6
mix.lock
6
mix.lock
|
@ -14,7 +14,7 @@
|
|||
"certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"},
|
||||
"concurrent_limiter": {:git, "https://gitlab.com/soapbox-pub/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]},
|
||||
"concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"},
|
||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
||||
"cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"},
|
||||
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
||||
|
@ -71,7 +71,7 @@
|
|||
"jose": {:hex, :jose, "1.11.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"},
|
||||
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
|
||||
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
|
||||
"linkify": {:hex, :linkify, "0.5.1", "6dc415cbc948b2f6ecec7cb226aab7ba9d3a1815bb501ae33e042334d707ecee", [:mix], [], "hexpm", "a3128c7e22fada4aa7214009501d8131e1fa3faf2f0a68b33dba379dc84ff944"},
|
||||
"linkify": {:hex, :linkify, "0.5.2", "fb66be139fdf1656ecb31f78a93592724d1b78d960a1b3598bd661013ea0e3c7", [:mix], [], "hexpm", "8d71ac690218d8952c90cbeb63cb8cc33738bb230d8a56d487d9447f2a5eab86"},
|
||||
"majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"},
|
||||
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
|
||||
|
@ -137,6 +137,6 @@
|
|||
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
|
||||
"web_push_encryption": {:git, "https://github.com/lanodan/elixir-web-push-encryption.git", "026a043037a89db4da8f07560bc8f9c68bcf0cc0", [branch: "bugfix/otp-24"]},
|
||||
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
|
||||
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []},
|
||||
}
|
||||
|
|
11
priv/repo/migrations/20210818023112_add_user_id_to_apps.exs
Normal file
11
priv/repo/migrations/20210818023112_add_user_id_to_apps.exs
Normal file
|
@ -0,0 +1,11 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddUserIdToApps do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:apps) do
|
||||
add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
|
||||
end
|
||||
|
||||
create_if_not_exists(index(:apps, [:user_id]))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
defmodule Pleroma.Repo.Migrations.UserRelationshipsTargetIdRelationshipTypeIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create_if_not_exists(index(:user_relationships, [:target_id, :relationship_type]))
|
||||
end
|
||||
end
|
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.9a4c5ede37b2f0230836.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.fb9ee54b02db0c974e51.js></script><script type=text/javascript src=/static/js/app.ce97bd1883ee9dd7b809.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1,user-scalable=no"><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link href=/static/css/app.7d2d223f75c3a14b0991.css rel=stylesheet></head><body class=hidden><noscript>To use Pleroma, please enable JavaScript.</noscript><div id=app></div><script type=text/javascript src=/static/js/vendors~app.cea10ab53f3aa19fc30e.js></script><script type=text/javascript src=/static/js/app.6c972d84b60f601b01f8.js></script></body></html>
|
Binary file not shown.
1
priv/static/static/css/app.7d2d223f75c3a14b0991.css.map
Normal file
1
priv/static/static/css/app.7d2d223f75c3a14b0991.css.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
priv/static/static/js/10.02ffbc25214f297f720f.js
Normal file
BIN
priv/static/static/js/10.02ffbc25214f297f720f.js
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/12.5ca41e245bb40263bc7f.js
Normal file
BIN
priv/static/static/js/12.5ca41e245bb40263bc7f.js
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/14.4e05e7c284119777ecc5.js
Normal file
BIN
priv/static/static/js/14.4e05e7c284119777ecc5.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/14.4e05e7c284119777ecc5.js.map
Normal file
BIN
priv/static/static/js/14.4e05e7c284119777ecc5.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/15.23f179cc3adc903bb537.js.map
Normal file
BIN
priv/static/static/js/15.23f179cc3adc903bb537.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/16.43dd2c64dcb160dd96a6.js.map
Normal file
BIN
priv/static/static/js/16.43dd2c64dcb160dd96a6.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/17.d1deeeb81b7cab98b068.js.map
Normal file
BIN
priv/static/static/js/17.d1deeeb81b7cab98b068.js.map
Normal file
Binary file not shown.
BIN
priv/static/static/js/18.a4d5b399e228a6a45a7b.js
Normal file
BIN
priv/static/static/js/18.a4d5b399e228a6a45a7b.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/18.a4d5b399e228a6a45a7b.js.map
Normal file
BIN
priv/static/static/js/18.a4d5b399e228a6a45a7b.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/19.e513835c3274271258fa.js.map
Normal file
BIN
priv/static/static/js/19.e513835c3274271258fa.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/2.fec2056b00b4fa3921ba.js
Normal file
BIN
priv/static/static/js/2.fec2056b00b4fa3921ba.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/2.fec2056b00b4fa3921ba.js.map
Normal file
BIN
priv/static/static/js/2.fec2056b00b4fa3921ba.js.map
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/20.683b112f4dcea887f707.js.map
Normal file
BIN
priv/static/static/js/20.683b112f4dcea887f707.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/21.b2844ccdcfc3c8191e8e.js.map
Normal file
BIN
priv/static/static/js/21.b2844ccdcfc3c8191e8e.js.map
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/22.68c0a771d79e3383f5e8.js.map
Normal file
BIN
priv/static/static/js/22.68c0a771d79e3383f5e8.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/23.0b6cdf4c9dc52c4291c0.js.map
Normal file
BIN
priv/static/static/js/23.0b6cdf4c9dc52c4291c0.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/24.5cfb87799bd882b933dd.js.map
Normal file
BIN
priv/static/static/js/24.5cfb87799bd882b933dd.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/25.8185e4d775cea9fe47e1.js
Normal file
BIN
priv/static/static/js/25.8185e4d775cea9fe47e1.js
Normal file
Binary file not shown.
BIN
priv/static/static/js/25.8185e4d775cea9fe47e1.js.map
Normal file
BIN
priv/static/static/js/25.8185e4d775cea9fe47e1.js.map
Normal file
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/26.34ec129dd8f860ce4a8e.js.map
Normal file
BIN
priv/static/static/js/26.34ec129dd8f860ce4a8e.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/27.0f4a5145681cfb5a896e.js.map
Normal file
BIN
priv/static/static/js/27.0f4a5145681cfb5a896e.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
priv/static/static/js/28.75c01cd71372c39d5af8.js.map
Normal file
BIN
priv/static/static/js/28.75c01cd71372c39d5af8.js.map
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue