mirror of
https://github.com/pixelfed/pixelfed.git
synced 2025-02-03 02:10:45 +00:00
commit
6d528137ea
420 changed files with 45104 additions and 8665 deletions
|
@ -7,7 +7,7 @@ jobs:
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
# Specify the version you desire here
|
# Specify the version you desire here
|
||||||
- image: cimg/php:8.2.5
|
- image: cimg/php:8.3.8
|
||||||
|
|
||||||
# Specify service dependencies here if necessary
|
# Specify service dependencies here if necessary
|
||||||
# CircleCI maintains a library of pre-built images
|
# CircleCI maintains a library of pre-built images
|
||||||
|
|
|
@ -1034,7 +1034,7 @@ DOCKER_APP_HOST_CACHE_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH:?error}/pixelfed/ca
|
||||||
|
|
||||||
# Automatically run "One-time setup tasks" commands.
|
# Automatically run "One-time setup tasks" commands.
|
||||||
#
|
#
|
||||||
# If you are migrating to this docker-compose setup or have manually run the "One time seutp"
|
# If you are migrating to this docker-compose setup or have manually run the "One time setup"
|
||||||
# tasks (https://docs.pixelfed.org/running-pixelfed/installation/#setting-up-services)
|
# tasks (https://docs.pixelfed.org/running-pixelfed/installation/#setting-up-services)
|
||||||
# you can set this to "0" to prevent them from running.
|
# you can set this to "0" to prevent them from running.
|
||||||
#
|
#
|
||||||
|
@ -1131,6 +1131,13 @@ DOCKER_APP_HOST_CACHE_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH:?error}/pixelfed/ca
|
||||||
# @dottie/validate required,oneof=0 1 2
|
# @dottie/validate required,oneof=0 1 2
|
||||||
#DOCKER_APP_PHP_OPCACHE_REVALIDATE_FREQ="2"
|
#DOCKER_APP_PHP_OPCACHE_REVALIDATE_FREQ="2"
|
||||||
|
|
||||||
|
# When doing [docker compose build], should the frontend be built in the Dockerfile?
|
||||||
|
# If set to "0" the included pre-compiled frontend will be used.
|
||||||
|
#
|
||||||
|
# @default "0"
|
||||||
|
# @dottie/validate required,oneof=0 1
|
||||||
|
#DOCKER_APP_BUILD_FRONTEND="0"
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# docker redis
|
# docker redis
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
ignored:
|
ignored:
|
||||||
- DL3002 # warning: Last USER should not be root
|
- DL3002 # warning: Last USER should not be root
|
||||||
- DL3008 # warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
|
- DL3008 # warning: Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
|
||||||
|
- DL3029 # warning: Do not use --platform flag with FROM
|
||||||
- SC2046 # warning: Quote this to prevent word splitting.
|
- SC2046 # warning: Quote this to prevent word splitting.
|
||||||
- SC2086 # info: Double quote to prevent globbing and word splitting.
|
- SC2086 # info: Double quote to prevent globbing and word splitting.
|
||||||
|
|
83
CHANGELOG.md
83
CHANGELOG.md
|
@ -1,8 +1,89 @@
|
||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.0...dev)
|
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev)
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
|
## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Implement Admin Domain Blocks API (Mastodon API Compatible) [ThisIsMissEm](https://github.com/ThisIsMissEm) ([#5021](https://github.com/pixelfed/pixelfed/pull/5021))
|
||||||
|
- Authorize Interaction support (for handling remote interactions) ([4ca7c6c3](https://github.com/pixelfed/pixelfed/commit/4ca7c6c3))
|
||||||
|
- Contact Form Admin Responses ([52cc6090](https://github.com/pixelfed/pixelfed/commit/52cc6090))
|
||||||
|
- Profile Carousels ([8af77a3f](https://github.com/pixelfed/pixelfed/commit/8af77a3f))
|
||||||
|
- Moderated Profiles ([39f16321](https://github.com/pixelfed/pixelfed/commit/39f16321))
|
||||||
|
|
||||||
|
### Federation
|
||||||
|
- Add ActiveSharedInboxService, for efficient sharedInbox caching ([1a6a3397](https://github.com/pixelfed/pixelfed/commit/1a6a3397))
|
||||||
|
- Add MovePipeline queue jobs ([9904d05f](https://github.com/pixelfed/pixelfed/commit/9904d05f))
|
||||||
|
- Add ActivityPub Move validator ([909a6c72](https://github.com/pixelfed/pixelfed/commit/909a6c72))
|
||||||
|
- Add delay to move handler to allow for remote cache invalidation ([8a362c12](https://github.com/pixelfed/pixelfed/commit/8a362c12))
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- Update ApiV1Controller, add support for notification filter types ([f61159a1](https://github.com/pixelfed/pixelfed/commit/f61159a1))
|
||||||
|
- Update ApiV1Dot1Controller, fix mutual api ([a8bb97b2](https://github.com/pixelfed/pixelfed/commit/a8bb97b2))
|
||||||
|
- Update ApiV1Controller, fix /api/v1/favourits pagination ([72f68160](https://github.com/pixelfed/pixelfed/commit/72f68160))
|
||||||
|
- Update RegisterController, update username constraints, require atleast one alpha char ([dd6e3cc2](https://github.com/pixelfed/pixelfed/commit/dd6e3cc2))
|
||||||
|
- Update AdminUser, fix entity casting ([cb5620d4](https://github.com/pixelfed/pixelfed/commit/cb5620d4))
|
||||||
|
- Update instance config, update network cache feed max_hours_old falloff to 90 days instead of 6 hours to allow for less active instances to have more results ([c042d135](https://github.com/pixelfed/pixelfed/commit/c042d135))
|
||||||
|
- Update ApiV1Dot1Controller, add new single media status create endpoint ([b03f5cec](https://github.com/pixelfed/pixelfed/commit/b03f5cec))
|
||||||
|
- Update AdminSettings component, add link to Custom CSS settings ([958daac4](https://github.com/pixelfed/pixelfed/commit/958daac4))
|
||||||
|
- Update ApiV1Controller, fix v1/instance stats, force cast to int ([dcd95d68](https://github.com/pixelfed/pixelfed/commit/dcd95d68))
|
||||||
|
- Update BeagleService, disable discovery if AP is disabled ([6cd1cbb4](https://github.com/pixelfed/pixelfed/commit/6cd1cbb4))
|
||||||
|
- Update NodeinfoService, fix typo ([edad436d](https://github.com/pixelfed/pixelfed/commit/edad436d))
|
||||||
|
- Update ActivityPubFetchService, reduce cache ttl from 1 hour to 7.5 mins and add uncached fetchRequest method ([21da2b64](https://github.com/pixelfed/pixelfed/commit/21da2b64))
|
||||||
|
- Update UserAccountDelete command, increase sharedInbox ttl from 12h to 14d ([be02f48a](https://github.com/pixelfed/pixelfed/commit/be02f48a))
|
||||||
|
- Update HttpSignature, add signRaw method and improve error checking ([d4cf9181](https://github.com/pixelfed/pixelfed/commit/d4cf9181))
|
||||||
|
- Update AP helpers, add forceBanCheck param to validateUrl method ([42424028](https://github.com/pixelfed/pixelfed/commit/42424028))
|
||||||
|
- Update layout, add og:logo ([4cc576e1](https://github.com/pixelfed/pixelfed/commit/4cc576e1))
|
||||||
|
- Update ReblogService, fix cache sync issues ([3de8ceca](https://github.com/pixelfed/pixelfed/commit/3de8ceca))
|
||||||
|
- Update config, allow Beagle discover service to be disabled ([de4ce3c8](https://github.com/pixelfed/pixelfed/commit/de4ce3c8))
|
||||||
|
- Update ApiV1Dot1Controller, allow upto 5 similar push tokens ([7820b506](https://github.com/pixelfed/pixelfed/commit/7820b506))
|
||||||
|
- Update AdminReports, add missing click handler. Fixes #5332 ([fe48b8ad](https://github.com/pixelfed/pixelfed/commit/fe48b8ad))
|
||||||
|
- Improve media filtering by using OffscreenCanvas, if supported ([aea5392](https://github.com/pixelfed/pixelfed/commit/aea5392))
|
||||||
|
|
||||||
|
## [v0.12.3 (2024-07-01)](https://github.com/pixelfed/pixelfed/compare/v0.12.2...v0.12.3)
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- Fix migrations bug ([4d1180b1](https://github.com/pixelfed/pixelfed/commit/4d1180b1))
|
||||||
|
|
||||||
|
## [v0.12.2 (2024-07-01)](https://github.com/pixelfed/pixelfed/compare/v0.12.1...v0.12.2)
|
||||||
|
|
||||||
|
### Framework
|
||||||
|
- Updated to Laravel 11 (requires php 8.2+)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New api/v1/instance/peers API endpoint, disabled by default ([4aad1c22](https://github.com/pixelfed/pixelfed/commit/4aad1c22))
|
||||||
|
- Added disable_embeds setting, and fix cache invalidation in other settings ([c5e7e917](https://github.com/pixelfed/pixelfed/commit/c5e7e917))
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- Update DirectMessageController, add 72 hour delay for new accounts before they can send a DM ([61d105fd](https://github.com/pixelfed/pixelfed/commit/61d105fd))
|
||||||
|
- Update AdminCuratedRegisterController, increase message length from 1000 to 3000 ([9a5e3471](https://github.com/pixelfed/pixelfed/commit/9a5e3471))
|
||||||
|
- Update ApiV1Controller, add pe (pixelfed entity) support to /api/v1/statuses/{id}/context endpoint ([d645d6ca](https://github.com/pixelfed/pixelfed/commit/d645d6ca))
|
||||||
|
- Update Admin Curated Onboarding, add select-all/mass action operations ([b22cac94](https://github.com/pixelfed/pixelfed/commit/b22cac94))
|
||||||
|
- Update AdminCuratedRegisterController, fix existing account approval ([cbb96cfd](https://github.com/pixelfed/pixelfed/commit/cbb96cfd))
|
||||||
|
- Update ActivityPubFetchService, fix Friendica bug ([e4edc6f1](https://github.com/pixelfed/pixelfed/commit/e4edc6f1))
|
||||||
|
- Update ProfileController, fix atom feed cache ttl. Fixes #5093 ([921e2965](https://github.com/pixelfed/pixelfed/commit/921e2965))
|
||||||
|
- Update CollectionsController, add new self route ([bc2495c6](https://github.com/pixelfed/pixelfed/commit/bc2495c6))
|
||||||
|
- Update FederationController, add webfinger support for actor uri. Fixes #5068 ([24194f7d](https://github.com/pixelfed/pixelfed/commit/24194f7d))
|
||||||
|
- Update FetchNodeinfoPipeline, set last_fetched_at timestamp ([a7fce91e](https://github.com/pixelfed/pixelfed/commit/a7fce91e))
|
||||||
|
- Update task scheduler, add weekly instance scan to check nodeinfo for known instances ([dc6b9f46](https://github.com/pixelfed/pixelfed/commit/dc6b9f46))
|
||||||
|
- Update AP fetch service and domain service ([42915ff9](https://github.com/pixelfed/pixelfed/commit/42915ff9))
|
||||||
|
- Update ApiV1Controller, add settings to verify_credentials endpoint ([3f4e0b94](https://github.com/pixelfed/pixelfed/commit/3f4e0b94))
|
||||||
|
- Update ApiV1Controller, fix update_credentials boolean handling ([19c62aaa](https://github.com/pixelfed/pixelfed/commit/19c62aaa))
|
||||||
|
- Update ApiV1Controller, fix cache invalidation bug in update_credentials ([d56a4108](https://github.com/pixelfed/pixelfed/commit/d56a4108))
|
||||||
|
- Update ApiV1Controller, fix self relationship response ([28bc7aa4](https://github.com/pixelfed/pixelfed/commit/28bc7aa4))
|
||||||
|
- Update ApiController, add pe support to like/unlike endpoints ([679ef677](https://github.com/pixelfed/pixelfed/commit/679ef677))
|
||||||
|
- Update ApiV1Dot1Controller, fix username to id endpoint ([4d6cea9a](https://github.com/pixelfed/pixelfed/commit/4d6cea9a))
|
||||||
|
- Update StatusController, cache AP object ([a75b89b2](https://github.com/pixelfed/pixelfed/commit/a75b89b2))
|
||||||
|
- Update status embed, add support for album carousels ([f4898db9](https://github.com/pixelfed/pixelfed/commit/f4898db9))
|
||||||
|
- Update profile embeds, add support for albums ([4fd156c4](https://github.com/pixelfed/pixelfed/commit/4fd156c4))
|
||||||
|
- Update DirectMessageController, add timestamps to threads ([b24d2554](https://github.com/pixelfed/pixelfed/commit/b24d2554))
|
||||||
|
- Update DirectMessageController, add carousel entity to threads ([96f24f33](https://github.com/pixelfed/pixelfed/commit/96f24f33))
|
||||||
|
- Update and refactor total local post count logic, cache value and schedule updates twice daily to eliminate the perf issue on larger instances ([4f2b8ed2](https://github.com/pixelfed/pixelfed/commit/4f2b8ed2))
|
||||||
|
- Update Media model, fix broken thumbnail/gray thumbnail bug ([e33643c2](https://github.com/pixelfed/pixelfed/commit/e33643c2))
|
||||||
|
- Update StatusController, fix unlisted post guest/ap access bug ([83098428](https://github.com/pixelfed/pixelfed/commit/83098428))
|
||||||
|
- Update discover, add network trending using Beagle API ([2cae8b48](https://github.com/pixelfed/pixelfed/commit/2cae8b48))
|
||||||
|
|
||||||
## [v0.12.1 (2024-05-07)](https://github.com/pixelfed/pixelfed/compare/v0.12.0...v0.12.1)
|
## [v0.12.1 (2024-05-07)](https://github.com/pixelfed/pixelfed/compare/v0.12.0...v0.12.1)
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
|
75
Dockerfile
75
Dockerfile
|
@ -132,6 +132,10 @@ ENV DEBIAN_FRONTEND="noninteractive"
|
||||||
# Ensure we run all scripts through 'bash' rather than 'sh'
|
# Ensure we run all scripts through 'bash' rather than 'sh'
|
||||||
SHELL ["/bin/bash", "-c"]
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
|
# Set www-data to be RUNTIME_UID/RUNTIME_GID
|
||||||
|
RUN groupmod --gid ${RUNTIME_GID} www-data \
|
||||||
|
&& usermod --uid ${RUNTIME_UID} --gid ${RUNTIME_GID} www-data
|
||||||
|
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& mkdir -pv /var/www/ \
|
&& mkdir -pv /var/www/ \
|
||||||
&& chown -R ${RUNTIME_UID}:${RUNTIME_GID} /var/www
|
&& chown -R ${RUNTIME_UID}:${RUNTIME_GID} /var/www
|
||||||
|
@ -176,6 +180,56 @@ RUN --mount=type=cache,id=pixelfed-pear-${PHP_VERSION}-${PHP_DEBIAN_RELEASE}-${T
|
||||||
PHP_PECL_EXTENSIONS_EXTRA=${PHP_PECL_EXTENSIONS_EXTRA} \
|
PHP_PECL_EXTENSIONS_EXTRA=${PHP_PECL_EXTENSIONS_EXTRA} \
|
||||||
/docker/install/php-extensions.sh
|
/docker/install/php-extensions.sh
|
||||||
|
|
||||||
|
#######################################################
|
||||||
|
# Node: Build frontend
|
||||||
|
#######################################################
|
||||||
|
|
||||||
|
# NOTE: Since the nodejs build is CPU architecture agnostic,
|
||||||
|
# we only want to build once and cache it for other architectures.
|
||||||
|
# We force the (CPU) [--platform] here to be architecture
|
||||||
|
# of the "builder"/"server" and not the *target* CPU architecture
|
||||||
|
# (e.g.) building the ARM version of Pixelfed on AMD64.
|
||||||
|
FROM --platform=${BUILDARCH} node:lts AS frontend-build
|
||||||
|
|
||||||
|
ARG BUILDARCH
|
||||||
|
ARG BUILD_FRONTEND=0
|
||||||
|
ARG RUNTIME_UID
|
||||||
|
ARG RUNTIME_GID
|
||||||
|
|
||||||
|
ARG NODE_ENV=production
|
||||||
|
ENV NODE_ENV=$NODE_ENV
|
||||||
|
|
||||||
|
WORKDIR /var/www/
|
||||||
|
|
||||||
|
SHELL [ "/usr/bin/bash", "-c" ]
|
||||||
|
|
||||||
|
# Install NPM dependencies
|
||||||
|
RUN --mount=type=cache,id=pixelfed-node-${BUILDARCH},sharing=locked,target=/tmp/cache \
|
||||||
|
--mount=type=bind,source=package.json,target=/var/www/package.json \
|
||||||
|
--mount=type=bind,source=package-lock.json,target=/var/www/package-lock.json \
|
||||||
|
<<EOF
|
||||||
|
if [[ $BUILD_FRONTEND -eq 1 ]];
|
||||||
|
then
|
||||||
|
npm install --cache /tmp/cache --no-save --dev
|
||||||
|
else
|
||||||
|
echo "Skipping [npm install] as --build-arg [BUILD_FRONTEND] is not set to '1'"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Copy the frontend source into the image before building
|
||||||
|
COPY --chown=${RUNTIME_UID}:${RUNTIME_GID} . /var/www
|
||||||
|
|
||||||
|
# Build the frontend with "mix" (See package.json)
|
||||||
|
RUN \
|
||||||
|
<<EOF
|
||||||
|
if [[ $BUILD_FRONTEND -eq 1 ]];
|
||||||
|
then
|
||||||
|
npm run production
|
||||||
|
else
|
||||||
|
echo "Skipping [npm run production] as --build-arg [BUILD_FRONTEND] is not set to '1'"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
#######################################################
|
#######################################################
|
||||||
# PHP: composer and source code
|
# PHP: composer and source code
|
||||||
#######################################################
|
#######################################################
|
||||||
|
@ -203,17 +257,26 @@ COPY --link --from=composer-image /usr/bin/composer /usr/bin/composer
|
||||||
#! Changing user to runtime user
|
#! Changing user to runtime user
|
||||||
USER ${RUNTIME_UID}:${RUNTIME_GID}
|
USER ${RUNTIME_UID}:${RUNTIME_GID}
|
||||||
|
|
||||||
|
|
||||||
# Install composer dependencies
|
# Install composer dependencies
|
||||||
# NOTE: we skip the autoloader generation here since we don't have all files avaliable (yet)
|
# NOTE: we skip the autoloader generation here since we don't have all files avaliable (yet)
|
||||||
RUN --mount=type=cache,id=pixelfed-composer-${PHP_VERSION},sharing=locked,target=/cache/composer \
|
RUN --mount=type=cache,id=pixelfed-composer-${PHP_VERSION},sharing=locked,uid=${RUNTIME_UID},gid=${RUNTIME_GID},target=/cache/composer \
|
||||||
--mount=type=bind,source=composer.json,target=/var/www/composer.json \
|
--mount=type=bind,source=composer.json,target=/var/www/composer.json \
|
||||||
--mount=type=bind,source=composer.lock,target=/var/www/composer.lock \
|
--mount=type=bind,source=composer.lock,target=/var/www/composer.lock \
|
||||||
set -ex \
|
set -ex \
|
||||||
&& composer install --prefer-dist --no-autoloader --ignore-platform-reqs
|
&& composer install --prefer-dist --no-autoloader --ignore-platform-reqs --no-scripts
|
||||||
|
|
||||||
# Copy all other files over
|
# Copy all other files over
|
||||||
COPY --chown=${RUNTIME_UID}:${RUNTIME_GID} . /var/www/
|
COPY --chown=${RUNTIME_UID}:${RUNTIME_GID} . /var/www/
|
||||||
|
|
||||||
|
# Generate optimized autoloader now that we have all files around
|
||||||
|
RUN set -ex \
|
||||||
|
&& ENABLE_CONFIG_CACHE=false composer dump-autoload --optimize
|
||||||
|
|
||||||
|
# Now we can run the post-install scripts
|
||||||
|
RUN set -ex \
|
||||||
|
&& composer run-script post-update-cmd
|
||||||
|
|
||||||
#######################################################
|
#######################################################
|
||||||
# Runtime: base
|
# Runtime: base
|
||||||
#######################################################
|
#######################################################
|
||||||
|
@ -231,13 +294,7 @@ COPY --link --from=dottie-image /dottie /usr/local/bin/dottie
|
||||||
COPY --link --from=gomplate-image /usr/local/bin/gomplate /usr/local/bin/gomplate
|
COPY --link --from=gomplate-image /usr/local/bin/gomplate /usr/local/bin/gomplate
|
||||||
COPY --link --from=composer-image /usr/bin/composer /usr/bin/composer
|
COPY --link --from=composer-image /usr/bin/composer /usr/bin/composer
|
||||||
COPY --link --from=composer-and-src --chown=${RUNTIME_UID}:${RUNTIME_GID} /var/www /var/www
|
COPY --link --from=composer-and-src --chown=${RUNTIME_UID}:${RUNTIME_GID} /var/www /var/www
|
||||||
|
COPY --link --from=frontend-build --chown=${RUNTIME_UID}:${RUNTIME_GID} /var/www/public /var/www/public
|
||||||
#! Changing user to runtime user
|
|
||||||
USER ${RUNTIME_UID}:${RUNTIME_GID}
|
|
||||||
|
|
||||||
# Generate optimized autoloader now that we have all files around
|
|
||||||
RUN set -ex \
|
|
||||||
&& ENABLE_CONFIG_CACHE=false composer dump-autoload --optimize
|
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
|
|
|
@ -43,3 +43,10 @@ We would like to extend our thanks to the following sponsors for funding Pixelfe
|
||||||
- [NLnet Foundation](https://nlnet.nl) and [NGI0
|
- [NLnet Foundation](https://nlnet.nl) and [NGI0
|
||||||
Discovery](https://nlnet.nl/discovery/), part of the [Next Generation
|
Discovery](https://nlnet.nl/discovery/), part of the [Next Generation
|
||||||
Internet](https://ngi.eu) initiative.
|
Internet](https://ngi.eu) initiative.
|
||||||
|
|
||||||
|
<p>This project is supported by:</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://www.digitalocean.com/?utm_medium=opensource&utm_source=pixelfed">
|
||||||
|
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
52
app/Console/Commands/CaptchaToggleCommand.php
Normal file
52
app/Console/Commands/CaptchaToggleCommand.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use function Laravel\Prompts\info;
|
||||||
|
use function Laravel\Prompts\confirm;
|
||||||
|
use App\Services\ConfigCacheService;
|
||||||
|
|
||||||
|
class CaptchaToggleCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:captcha-toggle-command';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Command description';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$captchaEnabled = (bool) config_cache('captcha.enabled');
|
||||||
|
|
||||||
|
info($captchaEnabled ? 'Captcha is enabled' : 'Captcha is not enabled');
|
||||||
|
|
||||||
|
if(!$captchaEnabled) {
|
||||||
|
info('Enable the Captcha from the admin settings dashboard.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirmed = confirm(
|
||||||
|
label: 'Do you want to disable the captcha?',
|
||||||
|
default: false,
|
||||||
|
yes: 'Yes',
|
||||||
|
no: 'No',
|
||||||
|
hint: 'Select an option to proceed.'
|
||||||
|
);
|
||||||
|
|
||||||
|
if($confirmed) {
|
||||||
|
ConfigCacheService::put('captcha.enabled', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
app/Console/Commands/DeleteRemoteProfile.php
Normal file
51
app/Console/Commands/DeleteRemoteProfile.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
||||||
|
use App\Profile;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\confirm;
|
||||||
|
use function Laravel\Prompts\search;
|
||||||
|
|
||||||
|
class DeleteRemoteProfile extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:delete-remote-profile';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Delete remote profile';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$id = search(
|
||||||
|
'Search for the account',
|
||||||
|
fn (string $value) => strlen($value) > 2
|
||||||
|
? Profile::whereNotNull('domain')->where('username', 'like', $value.'%')->pluck('username', 'id')->all()
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
$profile = Profile::whereNotNull('domain')->find($id);
|
||||||
|
|
||||||
|
if (! $profile) {
|
||||||
|
$this->error('Could not find profile.');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirmed = confirm('Are you sure you want to delete '.$profile->username.'\'s account? This action cannot be reversed.');
|
||||||
|
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('adelete');
|
||||||
|
$this->info('Dispatched delete job, it may take a few minutes...');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
79
app/Console/Commands/InstanceUpdateTotalLocalPosts.php
Normal file
79
app/Console/Commands/InstanceUpdateTotalLocalPosts.php
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Services\ConfigCacheService;
|
||||||
|
use Cache;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Storage;
|
||||||
|
|
||||||
|
class InstanceUpdateTotalLocalPosts extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:instance-update-total-local-posts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Update the total number of local statuses/post count';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$cached = $this->checkForCache();
|
||||||
|
if (! $cached) {
|
||||||
|
$this->initCache();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$cache = $this->getCached();
|
||||||
|
if (! $cache || ! isset($cache['count'])) {
|
||||||
|
$this->error('Problem fetching cache');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->updateAndCache();
|
||||||
|
Cache::forget('api:nodeinfo');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkForCache()
|
||||||
|
{
|
||||||
|
return Storage::exists('total_local_posts.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function initCache()
|
||||||
|
{
|
||||||
|
$count = DB::table('statuses')->whereNull(['url', 'deleted_at'])->count();
|
||||||
|
$res = [
|
||||||
|
'count' => $count,
|
||||||
|
];
|
||||||
|
Storage::put('total_local_posts.json', json_encode($res, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
ConfigCacheService::put('instance.stats.total_local_posts', $res['count']);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCached()
|
||||||
|
{
|
||||||
|
return Storage::json('total_local_posts.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateAndCache()
|
||||||
|
{
|
||||||
|
$count = DB::table('statuses')->whereNull(['url', 'deleted_at'])->count();
|
||||||
|
$res = [
|
||||||
|
'count' => $count,
|
||||||
|
];
|
||||||
|
Storage::put('total_local_posts.json', json_encode($res, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
ConfigCacheService::put('instance.stats.total_local_posts', $res['count']);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
74
app/Console/Commands/PushGatewayRefresh.php
Normal file
74
app/Console/Commands/PushGatewayRefresh.php
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Services\NotificationAppGatewayService;
|
||||||
|
use App\Services\PushNotificationService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
|
||||||
|
class PushGatewayRefresh extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:push-gateway-refresh';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Refresh push notification gateway support';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->info('Checking Push Notification support...');
|
||||||
|
$this->line(' ');
|
||||||
|
|
||||||
|
$currentState = NotificationAppGatewayService::enabled();
|
||||||
|
|
||||||
|
if ($currentState) {
|
||||||
|
$this->info('Push Notification support is active!');
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
$this->error('Push notification support is NOT active');
|
||||||
|
|
||||||
|
$action = select(
|
||||||
|
label: 'Do you want to force re-check?',
|
||||||
|
options: ['Yes', 'No'],
|
||||||
|
required: true
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($action === 'Yes') {
|
||||||
|
$recheck = NotificationAppGatewayService::forceSupportRecheck();
|
||||||
|
if ($recheck) {
|
||||||
|
$this->info('Success! Push Notifications are now active!');
|
||||||
|
PushNotificationService::warmList('like');
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
$this->error('Error, please ensure you have a valid API key.');
|
||||||
|
$this->line(' ');
|
||||||
|
$this->line('For more info, visit https://docs.pixelfed.org/running-pixelfed/push-notifications.html');
|
||||||
|
$this->line(' ');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,14 +74,14 @@ class UserAccountDelete extends Command
|
||||||
$activity = $fractal->createData($resource)->toArray();
|
$activity = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
$audience = Instance::whereNotNull(['shared_inbox', 'nodeinfo_last_fetched'])
|
$audience = Instance::whereNotNull(['shared_inbox', 'nodeinfo_last_fetched'])
|
||||||
->where('nodeinfo_last_fetched', '>', now()->subHours(12))
|
->where('nodeinfo_last_fetched', '>', now()->subDays(14))
|
||||||
->distinct()
|
->distinct()
|
||||||
->pluck('shared_inbox');
|
->pluck('shared_inbox');
|
||||||
|
|
||||||
$payload = json_encode($activity);
|
$payload = json_encode($activity);
|
||||||
|
|
||||||
$client = new Client([
|
$client = new Client([
|
||||||
'timeout' => 10,
|
'timeout' => 5,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$version = config('pixelfed.version');
|
$version = config('pixelfed.version');
|
||||||
|
|
47
app/Console/Commands/WeeklyInstanceScan.php
Normal file
47
app/Console/Commands/WeeklyInstanceScan.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Instance;
|
||||||
|
use App\Jobs\InstancePipeline\FetchNodeinfoPipeline;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\progress;
|
||||||
|
|
||||||
|
class WeeklyInstanceScan extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:weekly-instance-scan';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Scan instance nodeinfo';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if ((bool) config_cache('federation.activitypub.enabled') == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$users = progress(
|
||||||
|
label: 'Updating instance stats...',
|
||||||
|
steps: Instance::all(),
|
||||||
|
callback: fn ($instance) => $this->updateInstanceStats($instance),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateInstanceStats($instance)
|
||||||
|
{
|
||||||
|
FetchNodeinfoPipeline::dispatch($instance)->onQueue('intbg');
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,6 @@ class Kernel extends ConsoleKernel
|
||||||
/**
|
/**
|
||||||
* Define the application's command schedule.
|
* Define the application's command schedule.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
@ -32,6 +31,7 @@ class Kernel extends ConsoleKernel
|
||||||
$schedule->command('gc:failedjobs')->dailyAt(3)->onOneServer();
|
$schedule->command('gc:failedjobs')->dailyAt(3)->onOneServer();
|
||||||
$schedule->command('gc:passwordreset')->dailyAt('09:41')->onOneServer();
|
$schedule->command('gc:passwordreset')->dailyAt('09:41')->onOneServer();
|
||||||
$schedule->command('gc:sessions')->twiceDaily(13, 23)->onOneServer();
|
$schedule->command('gc:sessions')->twiceDaily(13, 23)->onOneServer();
|
||||||
|
$schedule->command('app:weekly-instance-scan')->weeklyOn(2, '4:20')->onOneServer();
|
||||||
|
|
||||||
if ((bool) config_cache('pixelfed.cloud_storage') && (bool) config_cache('media.delete_local_after_cloud')) {
|
if ((bool) config_cache('pixelfed.cloud_storage') && (bool) config_cache('media.delete_local_after_cloud')) {
|
||||||
$schedule->command('media:s3gc')->hourlyAt(15);
|
$schedule->command('media:s3gc')->hourlyAt(15);
|
||||||
|
@ -51,6 +51,7 @@ class Kernel extends ConsoleKernel
|
||||||
$schedule->command('app:notification-epoch-update')->weeklyOn(1, '2:21')->onOneServer();
|
$schedule->command('app:notification-epoch-update')->weeklyOn(1, '2:21')->onOneServer();
|
||||||
$schedule->command('app:hashtag-cached-count-update')->hourlyAt(25)->onOneServer();
|
$schedule->command('app:hashtag-cached-count-update')->hourlyAt(25)->onOneServer();
|
||||||
$schedule->command('app:account-post-count-stat-update')->everySixHours(25)->onOneServer();
|
$schedule->command('app:account-post-count-stat-update')->everySixHours(25)->onOneServer();
|
||||||
|
$schedule->command('app:instance-update-total-local-posts')->twiceDailyAt(1, 13, 45)->onOneServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,9 +3,14 @@
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Contact extends Model
|
class Contact extends Model
|
||||||
{
|
{
|
||||||
|
protected $casts = [
|
||||||
|
'responded_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
|
@ -15,4 +20,14 @@ class Contact extends Model
|
||||||
{
|
{
|
||||||
return url('/i/admin/messages/show/'.$this->id);
|
return url('/i/admin/messages/show/'.$this->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function userResponseUrl()
|
||||||
|
{
|
||||||
|
return url('/i/contact-admin-response/'.$this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessageId()
|
||||||
|
{
|
||||||
|
return $this->id.'-'.(string) Str::uuid().'@'.strtolower(config('pixelfed.domain.app', 'example.org'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,9 @@ trait AdminDirectoryController
|
||||||
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
||||||
$res['curated_onboarding'] = (bool) config_cache('instance.curated_registration.enabled');
|
$res['curated_onboarding'] = (bool) config_cache('instance.curated_registration.enabled');
|
||||||
$res['open_registration'] = (bool) config_cache('pixelfed.open_registration');
|
$res['open_registration'] = (bool) config_cache('pixelfed.open_registration');
|
||||||
$res['oauth_enabled'] = (bool) config_cache('pixelfed.oauth_enabled') && file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
|
$res['oauth_enabled'] = (bool) config_cache('pixelfed.oauth_enabled') &&
|
||||||
|
(file_exists(storage_path('oauth-public.key')) || config_cache('passport.public_key')) &&
|
||||||
|
(file_exists(storage_path('oauth-private.key')) || config_cache('passport.private_key'));
|
||||||
|
|
||||||
$res['activitypub_enabled'] = (bool) config_cache('federation.activitypub.enabled');
|
$res['activitypub_enabled'] = (bool) config_cache('federation.activitypub.enabled');
|
||||||
|
|
||||||
|
|
49
app/Http/Controllers/Admin/AdminGroupsController.php
Normal file
49
app/Http/Controllers/Admin/AdminGroupsController.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupCategory;
|
||||||
|
use App\Models\GroupInteraction;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupReport;
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
trait AdminGroupsController
|
||||||
|
{
|
||||||
|
public function groupsHome(Request $request)
|
||||||
|
{
|
||||||
|
$stats = $this->groupAdminStats();
|
||||||
|
|
||||||
|
return view('admin.groups.home', compact('stats'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function groupAdminStats()
|
||||||
|
{
|
||||||
|
return Cache::remember('admin:groups:stats', 3, function () {
|
||||||
|
$res = [
|
||||||
|
'total' => Group::count(),
|
||||||
|
'local' => Group::whereLocal(true)->count(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$res['remote'] = $res['total'] - $res['local'];
|
||||||
|
$res['categories'] = GroupCategory::count();
|
||||||
|
$res['posts'] = GroupPost::count();
|
||||||
|
$res['members'] = GroupMember::count();
|
||||||
|
$res['interactions'] = GroupInteraction::count();
|
||||||
|
$res['reports'] = GroupReport::count();
|
||||||
|
|
||||||
|
$res['local_30d'] = Cache::remember('admin:groups:stats:local_30d', 43200, function () {
|
||||||
|
return Group::whereLocal(true)->where('created_at', '>', now()->subMonth())->count();
|
||||||
|
});
|
||||||
|
|
||||||
|
$res['remote_30d'] = Cache::remember('admin:groups:stats:remote_30d', 43200, function () {
|
||||||
|
return Group::whereLocal(false)->where('created_at', '>', now()->subMonth())->count();
|
||||||
|
});
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,18 +3,20 @@
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\AccountInterstitial;
|
use App\AccountInterstitial;
|
||||||
use App\Http\Resources\AdminReport;
|
use App\Http\Resources\Admin\AdminModeratedProfileResource;
|
||||||
use App\Http\Resources\AdminRemoteReport;
|
use App\Http\Resources\AdminRemoteReport;
|
||||||
|
use App\Http\Resources\AdminReport;
|
||||||
use App\Http\Resources\AdminSpamReport;
|
use App\Http\Resources\AdminSpamReport;
|
||||||
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
||||||
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
||||||
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
use App\Jobs\StoryPipeline\StoryDelete;
|
use App\Jobs\StoryPipeline\StoryDelete;
|
||||||
|
use App\Models\ModeratedProfile;
|
||||||
|
use App\Models\RemoteReport;
|
||||||
use App\Notification;
|
use App\Notification;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Report;
|
use App\Report;
|
||||||
use App\Models\RemoteReport;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\ModLogService;
|
use App\Services\ModLogService;
|
||||||
use App\Services\NetworkTimelineService;
|
use App\Services\NetworkTimelineService;
|
||||||
|
@ -24,12 +26,13 @@ use App\Services\StatusService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\Story;
|
use App\Story;
|
||||||
use App\User;
|
use App\User;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
use Cache;
|
use Cache;
|
||||||
use Storage;
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use Storage;
|
||||||
|
|
||||||
trait AdminReportController
|
trait AdminReportController
|
||||||
{
|
{
|
||||||
|
@ -201,10 +204,7 @@ trait AdminReportController
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render() {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -829,6 +829,16 @@ trait AdminReportController
|
||||||
$profile->cw = true;
|
$profile->cw = true;
|
||||||
$profile->save();
|
$profile->save();
|
||||||
|
|
||||||
|
if ($profile->remote_url) {
|
||||||
|
ModeratedProfile::updateOrCreate([
|
||||||
|
'profile_url' => $profile->remote_url,
|
||||||
|
'profile_id' => $profile->id,
|
||||||
|
], [
|
||||||
|
'is_nsfw' => true,
|
||||||
|
'domain' => $profile->domain,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Status::whereProfileId($profile->id)->cursor() as $status) {
|
foreach (Status::whereProfileId($profile->id)->cursor() as $status) {
|
||||||
$status->is_nsfw = true;
|
$status->is_nsfw = true;
|
||||||
$status->save();
|
$status->save();
|
||||||
|
@ -879,6 +889,16 @@ trait AdminReportController
|
||||||
$profile->unlisted = true;
|
$profile->unlisted = true;
|
||||||
$profile->save();
|
$profile->save();
|
||||||
|
|
||||||
|
if ($profile->remote_url) {
|
||||||
|
ModeratedProfile::updateOrCreate([
|
||||||
|
'profile_url' => $profile->remote_url,
|
||||||
|
'profile_id' => $profile->id,
|
||||||
|
], [
|
||||||
|
'is_unlisted' => true,
|
||||||
|
'domain' => $profile->domain,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Status::whereProfileId($profile->id)->whereScope('public')->cursor() as $status) {
|
foreach (Status::whereProfileId($profile->id)->whereScope('public')->cursor() as $status) {
|
||||||
$status->scope = 'unlisted';
|
$status->scope = 'unlisted';
|
||||||
$status->visibility = 'unlisted';
|
$status->visibility = 'unlisted';
|
||||||
|
@ -929,6 +949,16 @@ trait AdminReportController
|
||||||
$profile->unlisted = true;
|
$profile->unlisted = true;
|
||||||
$profile->save();
|
$profile->save();
|
||||||
|
|
||||||
|
if ($profile->remote_url) {
|
||||||
|
ModeratedProfile::updateOrCreate([
|
||||||
|
'profile_url' => $profile->remote_url,
|
||||||
|
'profile_id' => $profile->id,
|
||||||
|
], [
|
||||||
|
'is_unlisted' => true,
|
||||||
|
'domain' => $profile->domain,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Status::whereProfileId($profile->id)->cursor() as $status) {
|
foreach (Status::whereProfileId($profile->id)->cursor() as $status) {
|
||||||
$status->scope = 'private';
|
$status->scope = 'private';
|
||||||
$status->visibility = 'private';
|
$status->visibility = 'private';
|
||||||
|
@ -982,6 +1012,16 @@ trait AdminReportController
|
||||||
|
|
||||||
$ts = now()->addMonth();
|
$ts = now()->addMonth();
|
||||||
|
|
||||||
|
if ($profile->remote_url) {
|
||||||
|
ModeratedProfile::updateOrCreate([
|
||||||
|
'profile_url' => $profile->remote_url,
|
||||||
|
'profile_id' => $profile->id,
|
||||||
|
], [
|
||||||
|
'is_banned' => true,
|
||||||
|
'domain' => $profile->domain,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($profile->user_id) {
|
if ($profile->user_id) {
|
||||||
$user = $profile->user;
|
$user = $profile->user;
|
||||||
abort_if($user->is_admin, 403, 'You cannot delete admin accounts.');
|
abort_if($user->is_admin, 403, 'You cannot delete admin accounts.');
|
||||||
|
@ -1354,7 +1394,7 @@ trait AdminReportController
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required|exists:remote_reports,id',
|
'id' => 'required|exists:remote_reports,id',
|
||||||
'action' => 'required|in:mark-read,cw-posts,unlist-posts,delete-posts,private-posts,mark-all-read-by-domain,mark-all-read-by-username,cw-all-posts,private-all-posts,unlist-all-posts'
|
'action' => 'required|in:mark-read,cw-posts,unlist-posts,delete-posts,private-posts,mark-all-read-by-domain,mark-all-read-by-username,cw-all-posts,private-all-posts,unlist-all-posts',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$report = RemoteReport::findOrFail($request->input('id'));
|
$report = RemoteReport::findOrFail($request->input('id'));
|
||||||
|
@ -1504,7 +1544,7 @@ trait AdminReportController
|
||||||
->action('admin.report.moderate')
|
->action('admin.report.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => $request->input('action'),
|
'action' => $request->input('action'),
|
||||||
'duration_active' => now()->parse($report->created_at)->diffForHumans()
|
'duration_active' => now()->parse($report->created_at)->diffForHumans(),
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -1516,6 +1556,198 @@ trait AdminReportController
|
||||||
->update(['action_taken_at' => now()]);
|
->update(['action_taken_at' => now()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [200];
|
return [200];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getModeratedProfiles(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'search' => 'sometimes|string|min:3|max:120',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->filled('search')) {
|
||||||
|
$query = '%'.$request->input('search').'%';
|
||||||
|
$profiles = DB::table('moderated_profiles')
|
||||||
|
->join('profiles', 'moderated_profiles.profile_id', '=', 'profiles.id')
|
||||||
|
->where('profiles.username', 'LIKE', $query)
|
||||||
|
->select('moderated_profiles.*', 'profiles.username')
|
||||||
|
->orderByDesc('moderated_profiles.id')
|
||||||
|
->cursorPaginate(10);
|
||||||
|
|
||||||
|
return AdminModeratedProfileResource::collection($profiles);
|
||||||
|
}
|
||||||
|
$profiles = ModeratedProfile::orderByDesc('id')->cursorPaginate(10);
|
||||||
|
|
||||||
|
return AdminModeratedProfileResource::collection($profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getModeratedProfile(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'id' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profile = ModeratedProfile::findOrFail($request->input('id'));
|
||||||
|
|
||||||
|
return new AdminModeratedProfileResource($profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportModeratedProfiles(Request $request)
|
||||||
|
{
|
||||||
|
return response()->streamDownload(function () {
|
||||||
|
$profiles = ModeratedProfile::get();
|
||||||
|
$res = AdminModeratedProfileResource::collection($profiles);
|
||||||
|
echo json_encode([
|
||||||
|
'_pixelfed_export' => true,
|
||||||
|
'meta' => [
|
||||||
|
'ns' => 'https://pixelfed.org',
|
||||||
|
'origin' => config('pixelfed.domain.app'),
|
||||||
|
'date' => now()->format('c'),
|
||||||
|
'type' => 'moderated-profiles',
|
||||||
|
'version' => "1.0"
|
||||||
|
],
|
||||||
|
'data' => $res
|
||||||
|
], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}, 'data-export.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteModeratedProfile(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'id' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$profile = ModeratedProfile::findOrFail($request->input('id'));
|
||||||
|
|
||||||
|
ModLogService::boot()
|
||||||
|
->objectUid($profile->profile_id)
|
||||||
|
->objectId($profile->id)
|
||||||
|
->objectType('App\Models\ModeratedProfile::class')
|
||||||
|
->user(request()->user())
|
||||||
|
->action('admin.moderated-profiles.delete')
|
||||||
|
->metadata([
|
||||||
|
'profile_url' => $profile->profile_url,
|
||||||
|
'profile_id' => $profile->profile_id,
|
||||||
|
'domain' => $profile->domain,
|
||||||
|
'note' => $profile->note,
|
||||||
|
'is_banned' => $profile->is_banned,
|
||||||
|
'is_nsfw' => $profile->is_nsfw,
|
||||||
|
'is_unlisted' => $profile->is_unlisted,
|
||||||
|
'is_noautolink' => $profile->is_noautolink,
|
||||||
|
'is_nodms' => $profile->is_nodms,
|
||||||
|
'is_notrending' => $profile->is_notrending,
|
||||||
|
])
|
||||||
|
->accessLevel('admin')
|
||||||
|
->save();
|
||||||
|
|
||||||
|
$profile->delete();
|
||||||
|
|
||||||
|
return ['status' => 200, 'message' => 'Successfully deleted moderated profile!'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateModeratedProfile(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'id' => 'required|exists:moderated_profiles',
|
||||||
|
'note' => 'sometimes|nullable|string|max:500',
|
||||||
|
'is_banned' => 'required|boolean',
|
||||||
|
'is_noautolink' => 'required|boolean',
|
||||||
|
'is_nodms' => 'required|boolean',
|
||||||
|
'is_notrending' => 'required|boolean',
|
||||||
|
'is_nsfw' => 'required|boolean',
|
||||||
|
'is_unlisted' => 'required|boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$fields = [
|
||||||
|
'note',
|
||||||
|
'is_banned',
|
||||||
|
'is_noautolink',
|
||||||
|
'is_nodms',
|
||||||
|
'is_notrending',
|
||||||
|
'is_nsfw',
|
||||||
|
'is_unlisted',
|
||||||
|
];
|
||||||
|
|
||||||
|
$profile = ModeratedProfile::findOrFail($request->input('id'));
|
||||||
|
$profile->update($request->only($fields));
|
||||||
|
|
||||||
|
ModLogService::boot()
|
||||||
|
->objectUid($profile->profile_id)
|
||||||
|
->objectId($profile->id)
|
||||||
|
->objectType('App\Models\ModeratedProfile::class')
|
||||||
|
->user(request()->user())
|
||||||
|
->action('admin.moderated-profiles.update')
|
||||||
|
->metadata($request->only($fields))
|
||||||
|
->accessLevel('admin')
|
||||||
|
->save();
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createModeratedProfile(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'url' => 'required|url|starts_with:https://',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$url = $request->input('url');
|
||||||
|
$host = parse_url($url, PHP_URL_HOST);
|
||||||
|
|
||||||
|
abort_if($host === config('pixelfed.domain.app'), 400, 'You cannot add local users!');
|
||||||
|
|
||||||
|
$exists = ModeratedProfile::whereProfileUrl($url)->exists();
|
||||||
|
abort_if($exists, 400, 'Moderated profile already exists!');
|
||||||
|
|
||||||
|
$profile = Profile::whereRemoteUrl($url)->first();
|
||||||
|
|
||||||
|
if ($profile) {
|
||||||
|
$rec = ModeratedProfile::updateOrCreate([
|
||||||
|
'profile_id' => $profile->id,
|
||||||
|
], [
|
||||||
|
'profile_url' => $profile->remote_url,
|
||||||
|
'domain' => $profile->domain,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ModLogService::boot()
|
||||||
|
->objectUid($rec->profile_id)
|
||||||
|
->objectId($rec->id)
|
||||||
|
->objectType('App\Models\ModeratedProfile::class')
|
||||||
|
->user(request()->user())
|
||||||
|
->action('admin.moderated-profiles.create')
|
||||||
|
->metadata([
|
||||||
|
'profile_existed' => true,
|
||||||
|
])
|
||||||
|
->accessLevel('admin')
|
||||||
|
->save();
|
||||||
|
|
||||||
|
return $rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
$remoteSearch = Helpers::profileFetch($url);
|
||||||
|
|
||||||
|
if ($remoteSearch) {
|
||||||
|
$rec = ModeratedProfile::updateOrCreate([
|
||||||
|
'profile_id' => $remoteSearch->id,
|
||||||
|
], [
|
||||||
|
'profile_url' => $remoteSearch->remote_url,
|
||||||
|
'domain' => $remoteSearch->domain,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ModLogService::boot()
|
||||||
|
->objectUid($rec->profile_id)
|
||||||
|
->objectId($rec->id)
|
||||||
|
->objectType('App\Models\ModeratedProfile::class')
|
||||||
|
->user(request()->user())
|
||||||
|
->action('admin.moderated-profiles.create')
|
||||||
|
->metadata([
|
||||||
|
'profile_existed' => false,
|
||||||
|
])
|
||||||
|
->accessLevel('admin')
|
||||||
|
->save();
|
||||||
|
|
||||||
|
return $rec;
|
||||||
|
}
|
||||||
|
abort(400, 'Invalid account');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,7 +195,9 @@ trait AdminSettingsController
|
||||||
if ($key == 'mobile_apis' &&
|
if ($key == 'mobile_apis' &&
|
||||||
$active &&
|
$active &&
|
||||||
! file_exists(storage_path('oauth-public.key')) &&
|
! file_exists(storage_path('oauth-public.key')) &&
|
||||||
! file_exists(storage_path('oauth-private.key'))
|
! config_cache('passport.public_key') &&
|
||||||
|
! file_exists(storage_path('oauth-private.key')) &&
|
||||||
|
! config_cache('passport.private_key')
|
||||||
) {
|
) {
|
||||||
Artisan::call('passport:keys');
|
Artisan::call('passport:keys');
|
||||||
Artisan::call('route:cache');
|
Artisan::call('route:cache');
|
||||||
|
@ -531,6 +533,7 @@ trait AdminSettingsController
|
||||||
'registration_status' => 'required|in:open,filtered,closed',
|
'registration_status' => 'required|in:open,filtered,closed',
|
||||||
'cloud_storage' => 'required',
|
'cloud_storage' => 'required',
|
||||||
'activitypub_enabled' => 'required',
|
'activitypub_enabled' => 'required',
|
||||||
|
'authorized_fetch' => 'required',
|
||||||
'account_migration' => 'required',
|
'account_migration' => 'required',
|
||||||
'mobile_apis' => 'required',
|
'mobile_apis' => 'required',
|
||||||
'stories' => 'required',
|
'stories' => 'required',
|
||||||
|
@ -555,6 +558,7 @@ trait AdminSettingsController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ConfigCacheService::put('federation.activitypub.authorized_fetch', $request->boolean('authorized_fetch'));
|
||||||
ConfigCacheService::put('federation.activitypub.enabled', $request->boolean('activitypub_enabled'));
|
ConfigCacheService::put('federation.activitypub.enabled', $request->boolean('activitypub_enabled'));
|
||||||
ConfigCacheService::put('federation.migration', $request->boolean('account_migration'));
|
ConfigCacheService::put('federation.migration', $request->boolean('account_migration'));
|
||||||
ConfigCacheService::put('pixelfed.oauth_enabled', $request->boolean('mobile_apis'));
|
ConfigCacheService::put('pixelfed.oauth_enabled', $request->boolean('mobile_apis'));
|
||||||
|
|
|
@ -2,57 +2,47 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\{
|
use App\Contact;
|
||||||
AccountInterstitial,
|
use App\Http\Controllers\Admin\AdminAutospamController;
|
||||||
Contact,
|
use App\Http\Controllers\Admin\AdminDirectoryController;
|
||||||
Hashtag,
|
use App\Http\Controllers\Admin\AdminDiscoverController;
|
||||||
Instance,
|
use App\Http\Controllers\Admin\AdminHashtagsController;
|
||||||
Newsroom,
|
use App\Http\Controllers\Admin\AdminInstanceController;
|
||||||
OauthClient,
|
use App\Http\Controllers\Admin\AdminMediaController;
|
||||||
Profile,
|
use App\Http\Controllers\Admin\AdminReportController;
|
||||||
Report,
|
use App\Http\Controllers\Admin\AdminSettingsController;
|
||||||
Status,
|
use App\Http\Controllers\Admin\AdminUserController;
|
||||||
StatusHashtag,
|
use App\Instance;
|
||||||
Story,
|
use App\Mail\AdminMessageResponse;
|
||||||
User
|
use App\Models\CustomEmoji;
|
||||||
};
|
use App\Newsroom;
|
||||||
use DB, Cache, Storage;
|
use App\OauthClient;
|
||||||
use Carbon\Carbon;
|
use App\Profile;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use App\Http\Controllers\Admin\{
|
|
||||||
AdminAutospamController,
|
|
||||||
AdminDirectoryController,
|
|
||||||
AdminDiscoverController,
|
|
||||||
AdminHashtagsController,
|
|
||||||
AdminInstanceController,
|
|
||||||
AdminReportController,
|
|
||||||
// AdminGroupsController,
|
|
||||||
AdminMediaController,
|
|
||||||
AdminSettingsController,
|
|
||||||
// AdminStorageController,
|
|
||||||
AdminSupportController,
|
|
||||||
AdminUserController
|
|
||||||
};
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
use App\Services\AdminStatsService;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use App\Services\AdminStatsService;
|
||||||
|
use App\Services\ConfigCacheService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
use App\Services\StoryService;
|
use App\Services\StoryService;
|
||||||
use App\Models\CustomEmoji;
|
use App\Status;
|
||||||
|
use App\Story;
|
||||||
|
use App\User;
|
||||||
|
use Cache;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Mail;
|
||||||
|
use Storage;
|
||||||
|
|
||||||
class AdminController extends Controller
|
class AdminController extends Controller
|
||||||
{
|
{
|
||||||
use AdminReportController,
|
use AdminAutospamController,
|
||||||
AdminAutospamController,
|
|
||||||
AdminDirectoryController,
|
AdminDirectoryController,
|
||||||
AdminDiscoverController,
|
AdminDiscoverController,
|
||||||
AdminHashtagsController,
|
AdminHashtagsController,
|
||||||
// AdminGroupsController,
|
|
||||||
AdminMediaController,
|
|
||||||
AdminSettingsController,
|
|
||||||
AdminInstanceController,
|
AdminInstanceController,
|
||||||
// AdminStorageController,
|
AdminMediaController,
|
||||||
|
AdminReportController,
|
||||||
|
AdminSettingsController,
|
||||||
AdminUserController;
|
AdminUserController;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
@ -67,9 +57,27 @@ class AdminController extends Controller
|
||||||
return view('admin.home');
|
return view('admin.home');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function customCss()
|
||||||
|
{
|
||||||
|
return view('admin.settings.customcss');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveCustomCss(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'css' => 'sometimes|max:5000',
|
||||||
|
'show' => 'sometimes',
|
||||||
|
]);
|
||||||
|
ConfigCacheService::put('uikit.custom.css', $request->input('css'));
|
||||||
|
ConfigCacheService::put('uikit.show_custom.css', $request->boolean('show'));
|
||||||
|
|
||||||
|
return view('admin.settings.customcss');
|
||||||
|
}
|
||||||
|
|
||||||
public function stats()
|
public function stats()
|
||||||
{
|
{
|
||||||
$data = AdminStatsService::get();
|
$data = AdminStatsService::get();
|
||||||
|
|
||||||
return view('admin.stats', compact('data'));
|
return view('admin.stats', compact('data'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,25 +91,27 @@ class AdminController extends Controller
|
||||||
$users = User::orderByDesc('id')->cursorPaginate(10);
|
$users = User::orderByDesc('id')->cursorPaginate(10);
|
||||||
|
|
||||||
$res = [
|
$res = [
|
||||||
"next_page_url" => $users->nextPageUrl(),
|
'next_page_url' => $users->nextPageUrl(),
|
||||||
"data" => $users->map(function($user) {
|
'data' => $users->map(function ($user) {
|
||||||
$account = AccountService::get($user->profile_id, true);
|
$account = AccountService::get($user->profile_id, true);
|
||||||
if (! $account) {
|
if (! $account) {
|
||||||
return [
|
return [
|
||||||
"id" => $user->profile_id,
|
'id' => $user->profile_id,
|
||||||
"username" => $user->username,
|
'username' => $user->username,
|
||||||
"status" => "deleted",
|
'status' => 'deleted',
|
||||||
"avatar" => "/storage/avatars/default.jpg",
|
'avatar' => '/storage/avatars/default.jpg',
|
||||||
"created_at" => $user->created_at
|
'created_at' => $user->created_at,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$account['user_id'] = $user->id;
|
$account['user_id'] = $user->id;
|
||||||
|
|
||||||
return $account;
|
return $account;
|
||||||
})
|
})
|
||||||
->filter(function ($user) {
|
->filter(function ($user) {
|
||||||
return $user;
|
return $user;
|
||||||
})
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,14 +122,15 @@ class AdminController extends Controller
|
||||||
->cursorPaginate(10);
|
->cursorPaginate(10);
|
||||||
|
|
||||||
$res = [
|
$res = [
|
||||||
"next_page_url" => $posts->nextPageUrl(),
|
'next_page_url' => $posts->nextPageUrl(),
|
||||||
"data" => $posts->map(function($post) {
|
'data' => $posts->map(function ($post) {
|
||||||
$status = StatusService::get($post->id, false);
|
$status = StatusService::get($post->id, false);
|
||||||
if (! $status) {
|
if (! $status) {
|
||||||
return ["id" => $post->id, "created_at" => $post->created_at];
|
return ['id' => $post->id, 'created_at' => $post->created_at];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
|
@ -140,6 +151,7 @@ class AdminController extends Controller
|
||||||
return $s;
|
return $s;
|
||||||
})
|
})
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
return view('admin.statuses.home', compact('statuses', 'data'));
|
return view('admin.statuses.home', compact('statuses', 'data'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,8 +169,8 @@ class AdminController extends Controller
|
||||||
'filter' => [
|
'filter' => [
|
||||||
'nullable',
|
'nullable',
|
||||||
'string',
|
'string',
|
||||||
Rule::in(['all', 'local', 'remote'])
|
Rule::in(['all', 'local', 'remote']),
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
$search = $request->input('search');
|
$search = $request->input('search');
|
||||||
$filter = $request->input('filter');
|
$filter = $request->input('filter');
|
||||||
|
@ -174,6 +186,7 @@ class AdminController extends Controller
|
||||||
if ($filter == 'remote') {
|
if ($filter == 'remote') {
|
||||||
return $q->whereNotNull('domain');
|
return $q->whereNotNull('domain');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $q;
|
return $q;
|
||||||
})->orderByDesc('id')
|
})->orderByDesc('id')
|
||||||
->simplePaginate($limit);
|
->simplePaginate($limit);
|
||||||
|
@ -185,6 +198,7 @@ class AdminController extends Controller
|
||||||
{
|
{
|
||||||
$profile = Profile::findOrFail($id);
|
$profile = Profile::findOrFail($id);
|
||||||
$user = $profile->user;
|
$user = $profile->user;
|
||||||
|
|
||||||
return view('admin.profiles.edit', compact('profile', 'user'));
|
return view('admin.profiles.edit', compact('profile', 'user'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,39 +217,120 @@ class AdminController extends Controller
|
||||||
->orderByDesc('id')
|
->orderByDesc('id')
|
||||||
->paginate(10);
|
->paginate(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('admin.apps.home', compact('apps'));
|
return view('admin.apps.home', compact('apps'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function messagesHome(Request $request)
|
public function messagesHome(Request $request)
|
||||||
{
|
{
|
||||||
$messages = Contact::orderByDesc('id')->paginate(10);
|
$this->validate($request, [
|
||||||
return view('admin.messages.home', compact('messages'));
|
'sort' => 'sometimes|string|in:all,open,closed',
|
||||||
|
]);
|
||||||
|
$sort = $request->input('sort', 'open');
|
||||||
|
|
||||||
|
$messages = Contact::when($sort, function ($query, $sort) {
|
||||||
|
if ($sort === 'open') {
|
||||||
|
$query->whereNull('read_at');
|
||||||
|
}
|
||||||
|
if ($sort === 'closed') {
|
||||||
|
$query->whereNotNull('read_at');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->orderByDesc('id')
|
||||||
|
->paginate(10)
|
||||||
|
->withQueryString();
|
||||||
|
|
||||||
|
return view('admin.messages.home', compact('messages', 'sort'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function messagesShow(Request $request, $id)
|
public function messagesShow(Request $request, $id)
|
||||||
{
|
{
|
||||||
$message = Contact::findOrFail($id);
|
$message = Contact::findOrFail($id);
|
||||||
|
$user = User::whereNull('status')->find($message->user_id);
|
||||||
|
if(!$user) {
|
||||||
|
$message->read_at = now();
|
||||||
|
$message->save();
|
||||||
|
return redirect('/i/admin/messages/home')->with('status', 'Redirected from message sent from a deleted account');
|
||||||
|
}
|
||||||
|
|
||||||
return view('admin.messages.show', compact('message'));
|
return view('admin.messages.show', compact('message'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function messagesReply(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'message' => 'required|string|min:1|max:500',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(config('mail.default') === 'log') {
|
||||||
|
return redirect('/i/admin/messages/home')->with('error', 'Mail driver not configured, please setup before you can sent email.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = Contact::whereNull('responded_at')->findOrFail($id);
|
||||||
|
$user = User::whereNull('status')->find($message->user_id);
|
||||||
|
if(!$user) {
|
||||||
|
$message->read_at = now();
|
||||||
|
$message->save();
|
||||||
|
return redirect('/i/admin/messages/home')->with('status', 'Redirected from message sent from a deleted account');
|
||||||
|
}
|
||||||
|
$message->response = $request->input('message');
|
||||||
|
$message->read_at = now();
|
||||||
|
$message->responded_at = now();
|
||||||
|
$message->save();
|
||||||
|
|
||||||
|
Mail::to($message->user->email)->send(new AdminMessageResponse($message));
|
||||||
|
|
||||||
|
return redirect('/i/admin/messages/home')->with('status', 'Sent response to '.$message->user->username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messagesReplyPreview(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'message' => 'required|string|min:1|max:500',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(config('mail.default') === 'log') {
|
||||||
|
return redirect('/i/admin/messages/home')->with('error', 'Mail driver not configured, please setup before you can sent email.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = Contact::whereNull('read_at')->findOrFail($id);
|
||||||
|
$user = User::whereNull('status')->find($message->user_id);
|
||||||
|
if(!$user) {
|
||||||
|
$message->read_at = now();
|
||||||
|
$message->save();
|
||||||
|
return redirect('/i/admin/messages/home')->with('error', 'Redirected from message sent from a deleted account');
|
||||||
|
}
|
||||||
|
return new AdminMessageResponse($message);
|
||||||
|
}
|
||||||
|
|
||||||
public function messagesMarkRead(Request $request)
|
public function messagesMarkRead(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required|integer|min:1'
|
'id' => 'required|integer|min:1',
|
||||||
]);
|
]);
|
||||||
$id = $request->input('id');
|
$id = $request->input('id');
|
||||||
$message = Contact::findOrFail($id);
|
$message = Contact::findOrFail($id);
|
||||||
|
|
||||||
|
$user = User::whereNull('status')->find($message->user_id);
|
||||||
|
if(!$user) {
|
||||||
|
$message->read_at = now();
|
||||||
|
$message->save();
|
||||||
|
return redirect('/i/admin/messages/home')->with('error', 'Redirected from message sent from a deleted account');
|
||||||
|
}
|
||||||
if ($message->read_at) {
|
if ($message->read_at) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$message->read_at = now();
|
$message->read_at = now();
|
||||||
$message->save();
|
$message->save();
|
||||||
return;
|
$request->session()->flash('status', 'Marked response from '.$message->user->username.' as read!');
|
||||||
|
|
||||||
|
return ['status' => 200];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newsroomHome(Request $request)
|
public function newsroomHome(Request $request)
|
||||||
{
|
{
|
||||||
$newsroom = Newsroom::latest()->paginate(10);
|
$newsroom = Newsroom::latest()->paginate(10);
|
||||||
|
|
||||||
return view('admin.newsroom.home', compact('newsroom'));
|
return view('admin.newsroom.home', compact('newsroom'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,6 +342,7 @@ class AdminController extends Controller
|
||||||
public function newsroomEdit(Request $request, $id)
|
public function newsroomEdit(Request $request, $id)
|
||||||
{
|
{
|
||||||
$news = Newsroom::findOrFail($id);
|
$news = Newsroom::findOrFail($id);
|
||||||
|
|
||||||
return view('admin.newsroom.edit', compact('news'));
|
return view('admin.newsroom.edit', compact('news'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +350,7 @@ class AdminController extends Controller
|
||||||
{
|
{
|
||||||
$news = Newsroom::findOrFail($id);
|
$news = Newsroom::findOrFail($id);
|
||||||
$news->delete();
|
$news->delete();
|
||||||
|
|
||||||
return redirect('/i/admin/newsroom');
|
return redirect('/i/admin/newsroom');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +359,7 @@ class AdminController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'title' => 'required|string|min:1|max:100',
|
'title' => 'required|string|min:1|max:100',
|
||||||
'summary' => 'nullable|string|max:200',
|
'summary' => 'nullable|string|max:200',
|
||||||
'body' => 'nullable|string'
|
'body' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
$changed = false;
|
$changed = false;
|
||||||
$changedFields = [];
|
$changedFields = [];
|
||||||
|
@ -280,7 +377,7 @@ class AdminController extends Controller
|
||||||
'auth_only' => 'boolean',
|
'auth_only' => 'boolean',
|
||||||
'show_link' => 'boolean',
|
'show_link' => 'boolean',
|
||||||
'force_modal' => 'boolean',
|
'force_modal' => 'boolean',
|
||||||
'published' => 'published'
|
'published' => 'published',
|
||||||
];
|
];
|
||||||
foreach ($fields as $field => $type) {
|
foreach ($fields as $field => $type) {
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
@ -320,16 +417,16 @@ class AdminController extends Controller
|
||||||
$news->save();
|
$news->save();
|
||||||
}
|
}
|
||||||
$redirect = $news->published_at ? $news->permalink() : $news->editUrl();
|
$redirect = $news->published_at ? $news->permalink() : $news->editUrl();
|
||||||
|
|
||||||
return redirect($redirect);
|
return redirect($redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function newsroomStore(Request $request)
|
public function newsroomStore(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'title' => 'required|string|min:1|max:100',
|
'title' => 'required|string|min:1|max:100',
|
||||||
'summary' => 'nullable|string|max:200',
|
'summary' => 'nullable|string|max:200',
|
||||||
'body' => 'nullable|string'
|
'body' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
$changed = false;
|
$changed = false;
|
||||||
$changedFields = [];
|
$changedFields = [];
|
||||||
|
@ -337,7 +434,7 @@ class AdminController extends Controller
|
||||||
if (Newsroom::whereSlug($slug)->exists()) {
|
if (Newsroom::whereSlug($slug)->exists()) {
|
||||||
$slug = $slug.'-'.str_random(4);
|
$slug = $slug.'-'.str_random(4);
|
||||||
}
|
}
|
||||||
$news = new Newsroom();
|
$news = new Newsroom;
|
||||||
$fields = [
|
$fields = [
|
||||||
'title' => 'string',
|
'title' => 'string',
|
||||||
'summary' => 'string',
|
'summary' => 'string',
|
||||||
|
@ -347,7 +444,7 @@ class AdminController extends Controller
|
||||||
'auth_only' => 'boolean',
|
'auth_only' => 'boolean',
|
||||||
'show_link' => 'boolean',
|
'show_link' => 'boolean',
|
||||||
'force_modal' => 'boolean',
|
'force_modal' => 'boolean',
|
||||||
'published' => 'published'
|
'published' => 'published',
|
||||||
];
|
];
|
||||||
foreach ($fields as $field => $type) {
|
foreach ($fields as $field => $type) {
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
@ -387,6 +484,7 @@ class AdminController extends Controller
|
||||||
$news->save();
|
$news->save();
|
||||||
}
|
}
|
||||||
$redirect = $news->published_at ? $news->permalink() : $news->editUrl();
|
$redirect = $news->published_at ? $news->permalink() : $news->editUrl();
|
||||||
|
|
||||||
return redirect($redirect);
|
return redirect($redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +496,7 @@ class AdminController extends Controller
|
||||||
public function diagnosticsDecrypt(Request $request)
|
public function diagnosticsDecrypt(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'payload' => 'required'
|
'payload' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$key = 'exception_report:';
|
$key = 'exception_report:';
|
||||||
|
@ -409,7 +507,7 @@ class AdminController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = [
|
$res = [
|
||||||
'decrypted' => substr($decrypted, strlen($key))
|
'decrypted' => substr($decrypted, strlen($key)),
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
|
@ -419,6 +517,7 @@ class AdminController extends Controller
|
||||||
{
|
{
|
||||||
$stories = Story::with('profile')->latest()->paginate(10);
|
$stories = Story::with('profile')->latest()->paginate(10);
|
||||||
$stats = StoryService::adminStats();
|
$stats = StoryService::adminStats();
|
||||||
|
|
||||||
return view('admin.stories.home', compact('stories', 'stats'));
|
return view('admin.stories.home', compact('stories', 'stats'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,12 +527,13 @@ class AdminController extends Controller
|
||||||
return view('admin.custom-emoji.not-enabled');
|
return view('admin.custom-emoji.not-enabled');
|
||||||
}
|
}
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'sort' => 'sometimes|in:all,local,remote,duplicates,disabled,search'
|
'sort' => 'sometimes|in:all,local,remote,duplicates,disabled,search',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($request->has('cc')) {
|
if ($request->has('cc')) {
|
||||||
Cache::forget('pf:admin:custom_emoji:stats');
|
Cache::forget('pf:admin:custom_emoji:stats');
|
||||||
Cache::forget('pf:custom_emoji');
|
Cache::forget('pf:custom_emoji');
|
||||||
|
|
||||||
return redirect(route('admin.custom-emoji'));
|
return redirect(route('admin.custom-emoji'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,6 +570,7 @@ class AdminController extends Controller
|
||||||
$q = $q->groupBy('shortcode');
|
$q = $q->groupBy('shortcode');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $q;
|
return $q;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -503,12 +604,14 @@ class AdminController extends Controller
|
||||||
$emoji->save();
|
$emoji->save();
|
||||||
$key = CustomEmoji::CACHE_KEY.str_replace(':', '', $emoji->shortcode);
|
$key = CustomEmoji::CACHE_KEY.str_replace(':', '', $emoji->shortcode);
|
||||||
Cache::forget($key);
|
Cache::forget($key);
|
||||||
|
|
||||||
return redirect()->back();
|
return redirect()->back();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function customEmojiAdd(Request $request)
|
public function customEmojiAdd(Request $request)
|
||||||
{
|
{
|
||||||
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
||||||
|
|
||||||
return view('admin.custom-emoji.add');
|
return view('admin.custom-emoji.add');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,9 +628,9 @@ class AdminController extends Controller
|
||||||
Rule::unique('custom_emoji')->where(function ($query) use ($request) {
|
Rule::unique('custom_emoji')->where(function ($query) use ($request) {
|
||||||
return $query->whereDomain(config('pixelfed.domain.app'))
|
return $query->whereDomain(config('pixelfed.domain.app'))
|
||||||
->whereShortcode($request->input('shortcode'));
|
->whereShortcode($request->input('shortcode'));
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
'emoji' => 'required|file|mimes:jpg,png|max:' . (config('federation.custom_emoji.max_size') / 1000)
|
'emoji' => 'required|file|mimes:jpg,png|max:'.(config('federation.custom_emoji.max_size') / 1000),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$emoji = new CustomEmoji;
|
$emoji = new CustomEmoji;
|
||||||
|
@ -540,6 +643,7 @@ class AdminController extends Controller
|
||||||
$emoji->media_path = 'emoji/'.$fileName;
|
$emoji->media_path = 'emoji/'.$fileName;
|
||||||
$emoji->save();
|
$emoji->save();
|
||||||
Cache::forget('pf:custom_emoji');
|
Cache::forget('pf:custom_emoji');
|
||||||
|
|
||||||
return redirect(route('admin.custom-emoji'));
|
return redirect(route('admin.custom-emoji'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,6 +654,7 @@ class AdminController extends Controller
|
||||||
Storage::delete("public/{$emoji->media_path}");
|
Storage::delete("public/{$emoji->media_path}");
|
||||||
Cache::forget('pf:custom_emoji');
|
Cache::forget('pf:custom_emoji');
|
||||||
$emoji->delete();
|
$emoji->delete();
|
||||||
|
|
||||||
return redirect(route('admin.custom-emoji'));
|
return redirect(route('admin.custom-emoji'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,6 +663,7 @@ class AdminController extends Controller
|
||||||
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
||||||
$emoji = CustomEmoji::orderBy('id')->whereDisabled(false)->whereShortcode($id)->firstOrFail();
|
$emoji = CustomEmoji::orderBy('id')->whereDisabled(false)->whereShortcode($id)->firstOrFail();
|
||||||
$emojis = CustomEmoji::whereShortcode($id)->where('id', '!=', $emoji->id)->cursorPaginate(10);
|
$emojis = CustomEmoji::whereShortcode($id)->where('id', '!=', $emoji->id)->cursorPaginate(10);
|
||||||
|
|
||||||
return view('admin.custom-emoji.duplicates', compact('emoji', 'emojis'));
|
return view('admin.custom-emoji.duplicates', compact('emoji', 'emojis'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@ class AdminCuratedRegisterController extends Controller
|
||||||
public function apiMessageSendStore(Request $request, $id)
|
public function apiMessageSendStore(Request $request, $id)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'message' => 'required|string|min:5|max:1000',
|
'message' => 'required|string|min:5|max:3000',
|
||||||
]);
|
]);
|
||||||
$record = CuratedRegister::findOrFail($id);
|
$record = CuratedRegister::findOrFail($id);
|
||||||
abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
|
abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
|
||||||
|
@ -240,6 +240,11 @@ class AdminCuratedRegisterController extends Controller
|
||||||
$record->is_closed = true;
|
$record->is_closed = true;
|
||||||
$record->action_taken_at = now();
|
$record->action_taken_at = now();
|
||||||
$record->save();
|
$record->save();
|
||||||
|
|
||||||
|
if (User::withTrashed()->whereEmail($record->email)->exists()) {
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
$user = User::create([
|
$user = User::create([
|
||||||
'name' => $record->username,
|
'name' => $record->username,
|
||||||
'username' => $record->username,
|
'username' => $record->username,
|
||||||
|
|
|
@ -2,39 +2,34 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use App\AccountInterstitial;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Resources\AdminInstance;
|
||||||
|
use App\Http\Resources\AdminUser;
|
||||||
|
use App\Instance;
|
||||||
|
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
||||||
|
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
use Auth, Cache, DB;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use App\{
|
|
||||||
AccountInterstitial,
|
|
||||||
Instance,
|
|
||||||
Like,
|
|
||||||
Notification,
|
|
||||||
Media,
|
|
||||||
Profile,
|
|
||||||
Report,
|
|
||||||
Status,
|
|
||||||
User
|
|
||||||
};
|
|
||||||
use App\Models\Conversation;
|
use App\Models\Conversation;
|
||||||
use App\Models\RemoteReport;
|
use App\Models\RemoteReport;
|
||||||
|
use App\Notification;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Report;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\AdminStatsService;
|
use App\Services\AdminStatsService;
|
||||||
use App\Services\ConfigCacheService;
|
use App\Services\ConfigCacheService;
|
||||||
use App\Services\InstanceService;
|
use App\Services\InstanceService;
|
||||||
use App\Services\ModLogService;
|
use App\Services\ModLogService;
|
||||||
use App\Services\SnowflakeService;
|
|
||||||
use App\Services\StatusService;
|
|
||||||
use App\Services\PublicTimelineService;
|
|
||||||
use App\Services\NetworkTimelineService;
|
use App\Services\NetworkTimelineService;
|
||||||
use App\Services\NotificationService;
|
use App\Services\NotificationService;
|
||||||
use App\Http\Resources\AdminInstance;
|
use App\Services\PublicTimelineService;
|
||||||
use App\Http\Resources\AdminUser;
|
use App\Services\SnowflakeService;
|
||||||
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
use App\Services\StatusService;
|
||||||
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
use App\Status;
|
||||||
use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline;
|
use App\User;
|
||||||
|
use Cache;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class AdminApiController extends Controller
|
class AdminApiController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -59,6 +54,7 @@ class AdminApiController extends Controller
|
||||||
$res['autospam_count'] = AccountInterstitial::whereType('post.autospam')
|
$res['autospam_count'] = AccountInterstitial::whereType('post.autospam')
|
||||||
->whereNull('appeal_handled_at')
|
->whereNull('appeal_handled_at')
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +75,7 @@ class AdminApiController extends Controller
|
||||||
'type' => $report->type,
|
'type' => $report->type,
|
||||||
'item_id' => $report->item_id,
|
'item_id' => $report->item_id,
|
||||||
'item_type' => $report->item_type,
|
'item_type' => $report->item_type,
|
||||||
'created_at' => $report->created_at
|
'created_at' => $report->created_at,
|
||||||
];
|
];
|
||||||
if ($report->item_type === 'App\\Status') {
|
if ($report->item_type === 'App\\Status') {
|
||||||
$status = StatusService::get($report->item_id, false);
|
$status = StatusService::get($report->item_id, false);
|
||||||
|
@ -93,6 +89,7 @@ class AdminApiController extends Controller
|
||||||
$r['parent'] = StatusService::get($status['in_reply_to_id'], false);
|
$r['parent'] = StatusService::get($status['in_reply_to_id'], false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $r;
|
return $r;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -108,7 +105,7 @@ class AdminApiController extends Controller
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'action' => 'required|in:dismiss,approve,dismiss-all,approve-all,delete-post,delete-account',
|
'action' => 'required|in:dismiss,approve,dismiss-all,approve-all,delete-post,delete-account',
|
||||||
'id' => 'required'
|
'id' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$action = $request->input('action');
|
$action = $request->input('action');
|
||||||
|
@ -130,6 +127,7 @@ class AdminApiController extends Controller
|
||||||
Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$profile->id);
|
Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$profile->id);
|
||||||
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$profile->id);
|
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$profile->id);
|
||||||
Cache::forget('admin-dash:reports:spam-count');
|
Cache::forget('admin-dash:reports:spam-count');
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +146,7 @@ class AdminApiController extends Controller
|
||||||
PublicTimelineService::deleteByProfileId($profile->id);
|
PublicTimelineService::deleteByProfileId($profile->id);
|
||||||
StatusDelete::dispatch($appeal->status)->onQueue('high');
|
StatusDelete::dispatch($appeal->status)->onQueue('high');
|
||||||
Cache::forget('admin-dash:reports:spam-count');
|
Cache::forget('admin-dash:reports:spam-count');
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +166,7 @@ class AdminApiController extends Controller
|
||||||
PublicTimelineService::deleteByProfileId($profile->id);
|
PublicTimelineService::deleteByProfileId($profile->id);
|
||||||
DeleteAccountPipeline::dispatch($appeal->user)->onQueue('high');
|
DeleteAccountPipeline::dispatch($appeal->user)->onQueue('high');
|
||||||
Cache::forget('admin-dash:reports:spam-count');
|
Cache::forget('admin-dash:reports:spam-count');
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,6 +179,7 @@ class AdminApiController extends Controller
|
||||||
Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
|
||||||
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
|
||||||
Cache::forget('admin-dash:reports:spam-count');
|
Cache::forget('admin-dash:reports:spam-count');
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +207,7 @@ class AdminApiController extends Controller
|
||||||
Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
|
||||||
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
|
||||||
Cache::forget('admin-dash:reports:spam-count');
|
Cache::forget('admin-dash:reports:spam-count');
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,6 +241,7 @@ class AdminApiController extends Controller
|
||||||
Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:exemption_by_pid:'.$appeal->user->profile_id);
|
||||||
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
|
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$appeal->user->profile_id);
|
||||||
Cache::forget('admin-dash:reports:spam-count');
|
Cache::forget('admin-dash:reports:spam-count');
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +265,7 @@ class AdminApiController extends Controller
|
||||||
'message' => $report->message,
|
'message' => $report->message,
|
||||||
'object_id' => $report->object_id,
|
'object_id' => $report->object_id,
|
||||||
'object_type' => $report->object_type,
|
'object_type' => $report->object_type,
|
||||||
'created_at' => $report->created_at
|
'created_at' => $report->created_at,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($report->profile_id) {
|
if ($report->profile_id) {
|
||||||
|
@ -277,14 +280,18 @@ class AdminApiController extends Controller
|
||||||
|
|
||||||
$r['status'] = $status;
|
$r['status'] = $status;
|
||||||
|
|
||||||
if($status['in_reply_to_id']) {
|
if (isset($status['in_reply_to_id'])) {
|
||||||
$r['parent'] = StatusService::get($status['in_reply_to_id'], false);
|
$r['parent'] = StatusService::get($status['in_reply_to_id'], false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($report->object_type === 'App\\Profile') {
|
if ($report->object_type === 'App\\Profile') {
|
||||||
$r['account'] = AccountService::get($report->object_id, false);
|
$acct = AccountService::get($report->object_id, true);
|
||||||
|
if ($acct) {
|
||||||
|
$r['account'] = $acct;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $r;
|
return $r;
|
||||||
})
|
})
|
||||||
->filter()
|
->filter()
|
||||||
|
@ -302,7 +309,7 @@ class AdminApiController extends Controller
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'action' => 'required|string',
|
'action' => 'required|string',
|
||||||
'id' => 'required'
|
'id' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$action = $request->input('action');
|
$action = $request->input('action');
|
||||||
|
@ -311,7 +318,7 @@ class AdminApiController extends Controller
|
||||||
$actions = [
|
$actions = [
|
||||||
'ignore',
|
'ignore',
|
||||||
'cw',
|
'cw',
|
||||||
'unlist'
|
'unlist',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (! in_array($action, $actions)) {
|
if (! in_array($action, $actions)) {
|
||||||
|
@ -366,35 +373,36 @@ class AdminApiController extends Controller
|
||||||
[
|
[
|
||||||
'name' => 'ActivityPub Federation',
|
'name' => 'ActivityPub Federation',
|
||||||
'description' => 'Enable activitypub federation support, compatible with Pixelfed, Mastodon and other platforms.',
|
'description' => 'Enable activitypub federation support, compatible with Pixelfed, Mastodon and other platforms.',
|
||||||
'key' => 'federation.activitypub.enabled'
|
'key' => 'federation.activitypub.enabled',
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => 'Open Registration',
|
'name' => 'Open Registration',
|
||||||
'description' => 'Allow new account registrations.',
|
'description' => 'Allow new account registrations.',
|
||||||
'key' => 'pixelfed.open_registration'
|
'key' => 'pixelfed.open_registration',
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => 'Stories',
|
'name' => 'Stories',
|
||||||
'description' => 'Enable the ephemeral Stories feature.',
|
'description' => 'Enable the ephemeral Stories feature.',
|
||||||
'key' => 'instance.stories.enabled'
|
'key' => 'instance.stories.enabled',
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => 'Require Email Verification',
|
'name' => 'Require Email Verification',
|
||||||
'description' => 'Require new accounts to verify their email address.',
|
'description' => 'Require new accounts to verify their email address.',
|
||||||
'key' => 'pixelfed.enforce_email_verification'
|
'key' => 'pixelfed.enforce_email_verification',
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => 'AutoSpam Detection',
|
'name' => 'AutoSpam Detection',
|
||||||
'description' => 'Detect and remove spam from public timelines.',
|
'description' => 'Detect and remove spam from public timelines.',
|
||||||
'key' => 'pixelfed.bouncer.enabled'
|
'key' => 'pixelfed.bouncer.enabled',
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
->map(function ($s) {
|
->map(function ($s) {
|
||||||
$s['state'] = (bool) config_cache($s['key']);
|
$s['state'] = (bool) config_cache($s['key']);
|
||||||
|
|
||||||
return $s;
|
return $s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -410,7 +418,7 @@ class AdminApiController extends Controller
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'key' => 'required',
|
'key' => 'required',
|
||||||
'value' => 'required'
|
'value' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$allowedKeys = [
|
$allowedKeys = [
|
||||||
|
@ -431,35 +439,36 @@ class AdminApiController extends Controller
|
||||||
[
|
[
|
||||||
'name' => 'ActivityPub Federation',
|
'name' => 'ActivityPub Federation',
|
||||||
'description' => 'Enable activitypub federation support, compatible with Pixelfed, Mastodon and other platforms.',
|
'description' => 'Enable activitypub federation support, compatible with Pixelfed, Mastodon and other platforms.',
|
||||||
'key' => 'federation.activitypub.enabled'
|
'key' => 'federation.activitypub.enabled',
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => 'Open Registration',
|
'name' => 'Open Registration',
|
||||||
'description' => 'Allow new account registrations.',
|
'description' => 'Allow new account registrations.',
|
||||||
'key' => 'pixelfed.open_registration'
|
'key' => 'pixelfed.open_registration',
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => 'Stories',
|
'name' => 'Stories',
|
||||||
'description' => 'Enable the ephemeral Stories feature.',
|
'description' => 'Enable the ephemeral Stories feature.',
|
||||||
'key' => 'instance.stories.enabled'
|
'key' => 'instance.stories.enabled',
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => 'Require Email Verification',
|
'name' => 'Require Email Verification',
|
||||||
'description' => 'Require new accounts to verify their email address.',
|
'description' => 'Require new accounts to verify their email address.',
|
||||||
'key' => 'pixelfed.enforce_email_verification'
|
'key' => 'pixelfed.enforce_email_verification',
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'name' => 'AutoSpam Detection',
|
'name' => 'AutoSpam Detection',
|
||||||
'description' => 'Detect and remove spam from public timelines.',
|
'description' => 'Detect and remove spam from public timelines.',
|
||||||
'key' => 'pixelfed.bouncer.enabled'
|
'key' => 'pixelfed.bouncer.enabled',
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
->map(function ($s) {
|
->map(function ($s) {
|
||||||
$s['state'] = (bool) config_cache($s['key']);
|
$s['state'] = (bool) config_cache($s['key']);
|
||||||
|
|
||||||
return $s;
|
return $s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -482,6 +491,7 @@ class AdminApiController extends Controller
|
||||||
})
|
})
|
||||||
->orderBy('id', $sort)
|
->orderBy('id', $sort)
|
||||||
->cursorPaginate(10);
|
->cursorPaginate(10);
|
||||||
|
|
||||||
return AdminUser::collection($res);
|
return AdminUser::collection($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,6 +507,7 @@ class AdminApiController extends Controller
|
||||||
if ($request->has('refresh')) {
|
if ($request->has('refresh')) {
|
||||||
Cache::forget($key);
|
Cache::forget($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Cache::remember($key, 86400, function () use ($id) {
|
return Cache::remember($key, 86400, function () use ($id) {
|
||||||
$user = User::findOrFail($id);
|
$user = User::findOrFail($id);
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
|
@ -510,8 +521,8 @@ class AdminApiController extends Controller
|
||||||
'moderation' => [
|
'moderation' => [
|
||||||
'unlisted' => (bool) $profile->unlisted,
|
'unlisted' => (bool) $profile->unlisted,
|
||||||
'cw' => (bool) $profile->cw,
|
'cw' => (bool) $profile->cw,
|
||||||
'no_autolink' => (bool) $profile->no_autolink
|
'no_autolink' => (bool) $profile->no_autolink,
|
||||||
]
|
],
|
||||||
]]);
|
]]);
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
|
@ -528,7 +539,7 @@ class AdminApiController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required',
|
'id' => 'required',
|
||||||
'action' => 'required|in:unlisted,cw,no_autolink,refresh_stats,verify_email,delete',
|
'action' => 'required|in:unlisted,cw,no_autolink,refresh_stats,verify_email,delete',
|
||||||
'value' => 'sometimes'
|
'value' => 'sometimes',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$id = $request->input('id');
|
$id = $request->input('id');
|
||||||
|
@ -586,6 +597,7 @@ class AdminApiController extends Controller
|
||||||
AccountService::del($profile->id);
|
AccountService::del($profile->id);
|
||||||
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('high');
|
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'status' => 200,
|
'status' => 200,
|
||||||
'msg' => 'deleted',
|
'msg' => 'deleted',
|
||||||
|
@ -612,7 +624,7 @@ class AdminApiController extends Controller
|
||||||
->action('admin.user.moderate')
|
->action('admin.user.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => 'Manually verified email address',
|
'action' => 'Manually verified email address',
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -625,7 +637,7 @@ class AdminApiController extends Controller
|
||||||
->action('admin.user.moderate')
|
->action('admin.user.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => $action,
|
'action' => $action,
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -640,7 +652,7 @@ class AdminApiController extends Controller
|
||||||
->action('admin.user.moderate')
|
->action('admin.user.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => $action,
|
'action' => $action,
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -655,7 +667,7 @@ class AdminApiController extends Controller
|
||||||
->action('admin.user.moderate')
|
->action('admin.user.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => $action,
|
'action' => $action,
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -673,7 +685,7 @@ class AdminApiController extends Controller
|
||||||
->action('admin.user.moderate')
|
->action('admin.user.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => $action,
|
'action' => $action,
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -687,8 +699,8 @@ class AdminApiController extends Controller
|
||||||
'moderation' => [
|
'moderation' => [
|
||||||
'unlisted' => (bool) $profile->unlisted,
|
'unlisted' => (bool) $profile->unlisted,
|
||||||
'cw' => (bool) $profile->cw,
|
'cw' => (bool) $profile->cw,
|
||||||
'no_autolink' => (bool) $profile->no_autolink
|
'no_autolink' => (bool) $profile->no_autolink,
|
||||||
]
|
],
|
||||||
]]);
|
]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,7 +767,7 @@ class AdminApiController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required',
|
'id' => 'required',
|
||||||
'key' => 'required|in:unlisted,auto_cw,banned',
|
'key' => 'required|in:unlisted,auto_cw,banned',
|
||||||
'value' => 'required'
|
'value' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$id = $request->input('id');
|
$id = $request->input('id');
|
||||||
|
@ -816,28 +828,28 @@ class AdminApiController extends Controller
|
||||||
'date' => now()->subDays($day)->format('M j Y'),
|
'date' => now()->subDays($day)->format('M j Y'),
|
||||||
'label_full' => $label,
|
'label_full' => $label,
|
||||||
'label' => $labelShort,
|
'label' => $labelShort,
|
||||||
'count' => User::whereDate('created_at', now()->subDays($day))->count()
|
'count' => User::whereDate('created_at', now()->subDays($day))->count(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$res['posts']['days'][] = [
|
$res['posts']['days'][] = [
|
||||||
'date' => now()->subDays($day)->format('M j Y'),
|
'date' => now()->subDays($day)->format('M j Y'),
|
||||||
'label_full' => $label,
|
'label_full' => $label,
|
||||||
'label' => $labelShort,
|
'label' => $labelShort,
|
||||||
'count' => Status::whereNull('uri')->where('id', '>', $minStatusId)->whereDate('created_at', now()->subDays($day))->count()
|
'count' => Status::whereNull('uri')->where('id', '>', $minStatusId)->whereDate('created_at', now()->subDays($day))->count(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$res['instances']['days'][] = [
|
$res['instances']['days'][] = [
|
||||||
'date' => now()->subDays($day)->format('M j Y'),
|
'date' => now()->subDays($day)->format('M j Y'),
|
||||||
'label_full' => $label,
|
'label_full' => $label,
|
||||||
'label' => $labelShort,
|
'label' => $labelShort,
|
||||||
'count' => Instance::whereDate('created_at', now()->subDays($day))->count()
|
'count' => Instance::whereDate('created_at', now()->subDays($day))->count(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$res['users']['total'] = DB::table('users')->count();
|
$res['users']['total'] = DB::table('users')->count();
|
||||||
$res['users']['min'] = collect($res['users']['days'])->min('count');
|
$res['users']['min'] = collect($res['users']['days'])->min('count');
|
||||||
$res['users']['max'] = collect($res['users']['days'])->max('count');
|
$res['users']['max'] = collect($res['users']['days'])->max('count');
|
||||||
$res['users']['change'] = collect($res['users']['days'])->sum('count');;
|
$res['users']['change'] = collect($res['users']['days'])->sum('count');
|
||||||
$res['posts']['total'] = DB::table('statuses')->whereNull('uri')->count();
|
$res['posts']['total'] = DB::table('statuses')->whereNull('uri')->count();
|
||||||
$res['posts']['min'] = collect($res['posts']['days'])->min('count');
|
$res['posts']['min'] = collect($res['posts']['days'])->min('count');
|
||||||
$res['posts']['max'] = collect($res['posts']['days'])->max('count');
|
$res['posts']['max'] = collect($res['posts']['days'])->max('count');
|
||||||
|
|
38
app/Http/Controllers/Api/ApiController.php
Normal file
38
app/Http/Controllers/Api/ApiController.php
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class ApiController extends Controller {
|
||||||
|
public function json($res, $headers = [], $code = 200) {
|
||||||
|
return response()->json($res, $code, $this->filterHeaders($headers), JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function linksForCollection($paginator) {
|
||||||
|
$link = null;
|
||||||
|
|
||||||
|
if ($paginator->onFirstPage()) {
|
||||||
|
if ($paginator->hasMorePages()) {
|
||||||
|
$link = '<'.$paginator->nextPageUrl().'>; rel="prev"';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($paginator->previousPageUrl()) {
|
||||||
|
$link = '<'.$paginator->previousPageUrl().'>; rel="next"';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($paginator->hasMorePages()) {
|
||||||
|
$link .= ($link ? ', ' : '').'<'.$paginator->nextPageUrl().'>; rel="prev"';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $link;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function filterHeaders($headers) {
|
||||||
|
return array_filter($headers, function($v, $k) {
|
||||||
|
return $v != null;
|
||||||
|
}, ARRAY_FILTER_USE_BOTH);
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,15 +60,14 @@ use App\Services\SnowflakeService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
use App\Services\UserFilterService;
|
use App\Services\UserFilterService;
|
||||||
use App\Services\UserRoleService;
|
use App\Services\UserRoleService;
|
||||||
|
use App\Services\UserStorageService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\StatusHashtag;
|
use App\StatusHashtag;
|
||||||
use App\Transformer\Api\Mastodon\v1\AccountTransformer;
|
use App\Transformer\Api\Mastodon\v1\AccountTransformer;
|
||||||
use App\Transformer\Api\Mastodon\v1\MediaTransformer;
|
use App\Transformer\Api\Mastodon\v1\MediaTransformer;
|
||||||
use App\Transformer\Api\Mastodon\v1\NotificationTransformer;
|
use App\Transformer\Api\Mastodon\v1\NotificationTransformer;
|
||||||
use App\Transformer\Api\Mastodon\v1\StatusTransformer;
|
use App\Transformer\Api\Mastodon\v1\StatusTransformer;
|
||||||
use App\Transformer\Api\{
|
use App\Transformer\Api\RelationshipTransformer;
|
||||||
RelationshipTransformer,
|
|
||||||
};
|
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
use App\UserSetting;
|
use App\UserSetting;
|
||||||
|
@ -97,8 +96,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->fractal = new Fractal\Manager();
|
$this->fractal = new Fractal\Manager;
|
||||||
$this->fractal->setSerializer(new ArraySerializer());
|
$this->fractal->setSerializer(new ArraySerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function json($res, $code = 200, $headers = [])
|
public function json($res, $code = 200, $headers = [])
|
||||||
|
@ -193,6 +192,10 @@ class ApiV1Controller extends Controller
|
||||||
'fields' => [],
|
'fields' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($request->has(self::PF_API_ENTITY_KEY)) {
|
||||||
|
$res['settings'] = AccountService::getAccountSettings($user->profile_id);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +210,7 @@ class ApiV1Controller extends Controller
|
||||||
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||||
abort_unless($request->user()->tokenCan('read'), 403);
|
abort_unless($request->user()->tokenCan('read'), 403);
|
||||||
|
|
||||||
|
$withInstanceMeta = $request->has('_wim');
|
||||||
$res = $request->has(self::PF_API_ENTITY_KEY) ? AccountService::get($id, true) : AccountService::getMastodon($id, true);
|
$res = $request->has(self::PF_API_ENTITY_KEY) ? AccountService::get($id, true) : AccountService::getMastodon($id, true);
|
||||||
if (! $res) {
|
if (! $res) {
|
||||||
return response()->json(['error' => 'Record not found'], 404);
|
return response()->json(['error' => 'Record not found'], 404);
|
||||||
|
@ -326,7 +330,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('locked')) {
|
if ($request->has('locked')) {
|
||||||
$locked = $request->input('locked') == 'true';
|
$locked = $request->boolean('locked');
|
||||||
if ($profile->is_private != $locked) {
|
if ($profile->is_private != $locked) {
|
||||||
$profile->is_private = $locked;
|
$profile->is_private = $locked;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -334,7 +338,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('reduce_motion')) {
|
if ($request->has('reduce_motion')) {
|
||||||
$reduced = $request->input('reduce_motion');
|
$reduced = $request->boolean('reduce_motion');
|
||||||
if ($settings->reduce_motion != $reduced) {
|
if ($settings->reduce_motion != $reduced) {
|
||||||
$settings->reduce_motion = $reduced;
|
$settings->reduce_motion = $reduced;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -342,7 +346,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('high_contrast_mode')) {
|
if ($request->has('high_contrast_mode')) {
|
||||||
$contrast = $request->input('high_contrast_mode');
|
$contrast = $request->boolean('high_contrast_mode');
|
||||||
if ($settings->high_contrast_mode != $contrast) {
|
if ($settings->high_contrast_mode != $contrast) {
|
||||||
$settings->high_contrast_mode = $contrast;
|
$settings->high_contrast_mode = $contrast;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -350,7 +354,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('video_autoplay')) {
|
if ($request->has('video_autoplay')) {
|
||||||
$autoplay = $request->input('video_autoplay');
|
$autoplay = $request->boolean('video_autoplay');
|
||||||
if ($settings->video_autoplay != $autoplay) {
|
if ($settings->video_autoplay != $autoplay) {
|
||||||
$settings->video_autoplay = $autoplay;
|
$settings->video_autoplay = $autoplay;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -370,7 +374,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('media_descriptions')) {
|
if ($request->has('media_descriptions')) {
|
||||||
$md = $request->input('media_descriptions') == true;
|
$md = $request->boolean('media_descriptions');
|
||||||
if ($composeSettings['media_descriptions'] != $md) {
|
if ($composeSettings['media_descriptions'] != $md) {
|
||||||
$composeSettings['media_descriptions'] = $md;
|
$composeSettings['media_descriptions'] = $md;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -378,7 +382,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('crawlable')) {
|
if ($request->has('crawlable')) {
|
||||||
$crawlable = $request->input('crawlable');
|
$crawlable = $request->boolean('crawlable');
|
||||||
if ($settings->crawlable != $crawlable) {
|
if ($settings->crawlable != $crawlable) {
|
||||||
$settings->crawlable = $crawlable;
|
$settings->crawlable = $crawlable;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -386,7 +390,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('show_profile_follower_count')) {
|
if ($request->has('show_profile_follower_count')) {
|
||||||
$show_profile_follower_count = $request->input('show_profile_follower_count');
|
$show_profile_follower_count = $request->boolean('show_profile_follower_count');
|
||||||
if ($settings->show_profile_follower_count != $show_profile_follower_count) {
|
if ($settings->show_profile_follower_count != $show_profile_follower_count) {
|
||||||
$settings->show_profile_follower_count = $show_profile_follower_count;
|
$settings->show_profile_follower_count = $show_profile_follower_count;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -395,7 +399,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('show_profile_following_count')) {
|
if ($request->has('show_profile_following_count')) {
|
||||||
$show_profile_following_count = $request->input('show_profile_following_count');
|
$show_profile_following_count = $request->boolean('show_profile_following_count');
|
||||||
if ($settings->show_profile_following_count != $show_profile_following_count) {
|
if ($settings->show_profile_following_count != $show_profile_following_count) {
|
||||||
$settings->show_profile_following_count = $show_profile_following_count;
|
$settings->show_profile_following_count = $show_profile_following_count;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -404,7 +408,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('public_dm')) {
|
if ($request->has('public_dm')) {
|
||||||
$public_dm = $request->input('public_dm');
|
$public_dm = $request->boolean('public_dm');
|
||||||
if ($settings->public_dm != $public_dm) {
|
if ($settings->public_dm != $public_dm) {
|
||||||
$settings->public_dm = $public_dm;
|
$settings->public_dm = $public_dm;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -422,7 +426,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('disable_embeds')) {
|
if ($request->has('disable_embeds')) {
|
||||||
$disabledEmbeds = $request->input('disable_embeds');
|
$disabledEmbeds = $request->boolean('disable_embeds');
|
||||||
if ($other['disable_embeds'] != $disabledEmbeds) {
|
if ($other['disable_embeds'] != $disabledEmbeds) {
|
||||||
$other['disable_embeds'] = $disabledEmbeds;
|
$other['disable_embeds'] = $disabledEmbeds;
|
||||||
$changes = true;
|
$changes = true;
|
||||||
|
@ -441,8 +445,16 @@ class ApiV1Controller extends Controller
|
||||||
Cache::forget('profile:following_count:'.$profile->id);
|
Cache::forget('profile:following_count:'.$profile->id);
|
||||||
Cache::forget('profile:embed:'.$profile->id);
|
Cache::forget('profile:embed:'.$profile->id);
|
||||||
Cache::forget('profile:compose:settings:'.$user->id);
|
Cache::forget('profile:compose:settings:'.$user->id);
|
||||||
Cache::forget('profile:view:'.$user->username);
|
Cache::forget('profile:view:'.$profile->username);
|
||||||
|
Cache::forget('profile:atom:enabled:'.$profile->id);
|
||||||
|
Cache::forget('pfc:cached-user:wt:'.strtolower($profile->username));
|
||||||
|
Cache::forget('pfc:cached-user:wot:'.strtolower($profile->username));
|
||||||
|
Cache::forget('pf:acct:settings:hidden-followers:'.$profile->id);
|
||||||
|
Cache::forget('pf:acct:settings:hidden-following:'.$profile->id);
|
||||||
|
Cache::forget('pf:acct-trans:hideFollowing:'.$profile->id);
|
||||||
|
Cache::forget('pf:acct-trans:hideFollowers:'.$profile->id);
|
||||||
AccountService::del($user->profile_id);
|
AccountService::del($user->profile_id);
|
||||||
|
AccountService::forgetAccountSettings($profile->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($syncLicenses && $licenseChanged) {
|
if ($syncLicenses && $licenseChanged) {
|
||||||
|
@ -476,6 +488,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(! $account, 404);
|
abort_if(! $account, 404);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'limit' => 'sometimes|integer|min:1',
|
'limit' => 'sometimes|integer|min:1',
|
||||||
|
@ -577,6 +590,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(! $account, 404);
|
abort_if(! $account, 404);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'limit' => 'sometimes|integer|min:1',
|
'limit' => 'sometimes|integer|min:1',
|
||||||
|
@ -740,7 +754,15 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$dir = $min_id ? '>' : '<';
|
$dir = $min_id ? '>' : '<';
|
||||||
$id = $min_id ?? $max_id;
|
$id = $min_id ?? $max_id;
|
||||||
$res = Status::whereProfileId($profile['id'])
|
$res = Status::select(
|
||||||
|
'profile_id',
|
||||||
|
'in_reply_to_id',
|
||||||
|
'reblog_of_id',
|
||||||
|
'type',
|
||||||
|
'id',
|
||||||
|
'scope'
|
||||||
|
)
|
||||||
|
->whereProfileId($profile['id'])
|
||||||
->whereNull('in_reply_to_id')
|
->whereNull('in_reply_to_id')
|
||||||
->whereNull('reblog_of_id')
|
->whereNull('reblog_of_id')
|
||||||
->whereIn('type', $scope)
|
->whereIn('type', $scope)
|
||||||
|
@ -796,6 +818,8 @@ class ApiV1Controller extends Controller
|
||||||
->whereNull('status')
|
->whereNull('status')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($target && $target->moved_to_profile_id, 400, 'Cannot follow an account that has moved!');
|
||||||
|
|
||||||
if ($target && $target->domain) {
|
if ($target && $target->domain) {
|
||||||
$domain = $target->domain;
|
$domain = $target->domain;
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
|
@ -835,7 +859,7 @@ class ApiV1Controller extends Controller
|
||||||
'following_id' => $target->id,
|
'following_id' => $target->id,
|
||||||
]);
|
]);
|
||||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||||
(new FollowerController())->sendFollow($user->profile, $target);
|
(new FollowerController)->sendFollow($user->profile, $target);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$follower = Follower::firstOrCreate([
|
$follower = Follower::firstOrCreate([
|
||||||
|
@ -844,7 +868,7 @@ class ApiV1Controller extends Controller
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||||
(new FollowerController())->sendFollow($user->profile, $target);
|
(new FollowerController)->sendFollow($user->profile, $target);
|
||||||
}
|
}
|
||||||
FollowPipeline::dispatch($follower)->onQueue('high');
|
FollowPipeline::dispatch($follower)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
@ -903,7 +927,7 @@ class ApiV1Controller extends Controller
|
||||||
$followRequest->delete();
|
$followRequest->delete();
|
||||||
RelationshipService::refresh($target->id, $user->profile_id);
|
RelationshipService::refresh($target->id, $user->profile_id);
|
||||||
}
|
}
|
||||||
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer());
|
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -916,7 +940,7 @@ class ApiV1Controller extends Controller
|
||||||
UnfollowPipeline::dispatch($user->profile_id, $target->id)->onQueue('high');
|
UnfollowPipeline::dispatch($user->profile_id, $target->id)->onQueue('high');
|
||||||
|
|
||||||
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
||||||
(new FollowerController())->sendUndoFollow($user->profile, $target);
|
(new FollowerController)->sendUndoFollow($user->profile, $target);
|
||||||
}
|
}
|
||||||
|
|
||||||
RelationshipService::refresh($user->profile_id, $target->id);
|
RelationshipService::refresh($user->profile_id, $target->id);
|
||||||
|
@ -960,10 +984,22 @@ class ApiV1Controller extends Controller
|
||||||
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
$pid = $request->user()->profile_id ?? $request->user()->profile->id;
|
$pid = $request->user()->profile_id ?? $request->user()->profile->id;
|
||||||
$res = collect($ids)
|
$res = collect($ids)
|
||||||
->filter(function ($id) use ($pid) {
|
|
||||||
return intval($id) !== intval($pid);
|
|
||||||
})
|
|
||||||
->map(function ($id) use ($pid, $napi) {
|
->map(function ($id) use ($pid, $napi) {
|
||||||
|
if (intval($id) === intval($pid)) {
|
||||||
|
return [
|
||||||
|
'id' => $id,
|
||||||
|
'following' => false,
|
||||||
|
'followed_by' => false,
|
||||||
|
'blocking' => false,
|
||||||
|
'muting' => false,
|
||||||
|
'muting_notifications' => false,
|
||||||
|
'requested' => false,
|
||||||
|
'domain_blocking' => false,
|
||||||
|
'showing_reblogs' => false,
|
||||||
|
'endorsed' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return $napi ?
|
return $napi ?
|
||||||
RelationshipService::getWithDate($pid, $id) :
|
RelationshipService::getWithDate($pid, $id) :
|
||||||
RelationshipService::get($pid, $id);
|
RelationshipService::get($pid, $id);
|
||||||
|
@ -1098,6 +1134,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = Profile::findOrFail($id);
|
$profile = Profile::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($profile->moved_to_profile_id, 422, 'Cannot block an account that has migrated!');
|
||||||
|
|
||||||
if ($profile->user && $profile->user->is_admin == true) {
|
if ($profile->user && $profile->user->is_admin == true) {
|
||||||
abort(400, 'You cannot block an admin');
|
abort(400, 'You cannot block an admin');
|
||||||
}
|
}
|
||||||
|
@ -1164,7 +1202,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
UserFilterService::block($pid, $id);
|
UserFilterService::block($pid, $id);
|
||||||
RelationshipService::refresh($pid, $id);
|
RelationshipService::refresh($pid, $id);
|
||||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
|
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -1191,6 +1229,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = Profile::findOrFail($id);
|
$profile = Profile::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($profile->moved_to_profile_id, 422, 'Cannot unblock an account that has migrated!');
|
||||||
|
|
||||||
$filter = UserFilter::whereUserId($pid)
|
$filter = UserFilter::whereUserId($pid)
|
||||||
->whereFilterableId($profile->id)
|
->whereFilterableId($profile->id)
|
||||||
->whereFilterableType('App\Profile')
|
->whereFilterableType('App\Profile')
|
||||||
|
@ -1203,7 +1243,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
RelationshipService::refresh($pid, $id);
|
RelationshipService::refresh($pid, $id);
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
|
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -1301,12 +1341,17 @@ class ApiV1Controller extends Controller
|
||||||
if ($res->count()) {
|
if ($res->count()) {
|
||||||
$ids = $res->map(function ($status) {
|
$ids = $res->map(function ($status) {
|
||||||
return $status['like_id'];
|
return $status['like_id'];
|
||||||
});
|
})->filter();
|
||||||
$max = $ids->max();
|
|
||||||
$min = $ids->min();
|
$max = $ids->min() - 1;
|
||||||
|
$min = $ids->max();
|
||||||
|
|
||||||
$baseUrl = config('app.url').'/api/v1/favourites?limit='.$limit.'&';
|
$baseUrl = config('app.url').'/api/v1/favourites?limit='.$limit.'&';
|
||||||
|
if ($maxId) {
|
||||||
$link = '<'.$baseUrl.'max_id='.$max.'>; rel="next",<'.$baseUrl.'min_id='.$min.'>; rel="prev"';
|
$link = '<'.$baseUrl.'max_id='.$max.'>; rel="next",<'.$baseUrl.'min_id='.$min.'>; rel="prev"';
|
||||||
|
} else {
|
||||||
|
$link = '<'.$baseUrl.'max_id='.$max.'>; rel="next"';
|
||||||
|
}
|
||||||
|
|
||||||
return $this->json($res, 200, ['Link' => $link]);
|
return $this->json($res, 200, ['Link' => $link]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1328,10 +1373,13 @@ class ApiV1Controller extends Controller
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-like', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-like', $user->id), 403, 'Invalid permissions for this action');
|
||||||
|
|
||||||
$status = StatusService::getMastodon($id, false);
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
$status = $napi ? StatusService::get($id, false) : StatusService::getMastodon($id, false);
|
||||||
|
|
||||||
abort_unless($status, 404);
|
abort_unless($status, 404);
|
||||||
|
|
||||||
|
abort_if(isset($status['moved'], $status['moved']['id']), 422, 'Cannot like a post from an account that has migrated');
|
||||||
|
|
||||||
if ($status && isset($status['account'], $status['account']['acct']) && strpos($status['account']['acct'], '@') != -1) {
|
if ($status && isset($status['account'], $status['account']['acct']) && strpos($status['account']['acct'], '@') != -1) {
|
||||||
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
|
@ -1396,34 +1444,48 @@ class ApiV1Controller extends Controller
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-like', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-like', $user->id), 403, 'Invalid permissions for this action');
|
||||||
|
|
||||||
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
$status = $napi ? StatusService::get($id, false) : StatusService::getMastodon($id, false);
|
||||||
|
|
||||||
|
abort_unless($status && isset($status['account']), 404);
|
||||||
|
abort_if(isset($status['moved'], $status['moved']['id']), 422, 'Cannot unlike a post from an account that has migrated');
|
||||||
|
|
||||||
|
if ($status && isset($status['account'], $status['account']['acct']) && strpos($status['account']['acct'], '@') != -1) {
|
||||||
|
$domain = parse_url($status['account']['url'], PHP_URL_HOST);
|
||||||
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$spid = $status['account']['id'];
|
||||||
|
|
||||||
AccountService::setLastActive($user->id);
|
AccountService::setLastActive($user->id);
|
||||||
|
|
||||||
$status = Status::findOrFail($id);
|
if (intval($spid) !== intval($user->profile_id)) {
|
||||||
|
if ($status['visibility'] == 'private') {
|
||||||
if (intval($status->profile_id) !== intval($user->profile_id)) {
|
abort_if(! FollowerService::follows($user->profile_id, $spid), 403);
|
||||||
if ($status->scope == 'private') {
|
|
||||||
abort_if(! $status->profile->followedBy($user->profile), 403);
|
|
||||||
} else {
|
} else {
|
||||||
abort_if(! in_array($status->scope, ['public', 'unlisted']), 403);
|
abort_if(! in_array($status['visibility'], ['public', 'unlisted']), 403);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$like = Like::whereProfileId($user->profile_id)
|
$like = Like::whereProfileId($user->profile_id)
|
||||||
->whereStatusId($status->id)
|
->whereStatusId($status['id'])
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if ($like) {
|
if ($like) {
|
||||||
$like->forceDelete();
|
$like->forceDelete();
|
||||||
$status->likes_count = $status->likes_count > 1 ? $status->likes_count - 1 : 0;
|
$ogStatus = Status::find($status['id']);
|
||||||
$status->save();
|
if ($ogStatus) {
|
||||||
|
$ogStatus->likes_count = $ogStatus->likes_count > 1 ? $ogStatus->likes_count - 1 : 0;
|
||||||
|
$ogStatus->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusService::del($status->id);
|
StatusService::del($status['id']);
|
||||||
|
|
||||||
$res = StatusService::getMastodon($status->id, false);
|
$status['favourited'] = false;
|
||||||
$res['favourited'] = false;
|
$status['favourites_count'] = isset($ogStatus) ? $ogStatus->likes_count : $status['favourites_count'] - 1;
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($status);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1487,6 +1549,8 @@ class ApiV1Controller extends Controller
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$target = AccountService::getMastodon($id);
|
$target = AccountService::getMastodon($id);
|
||||||
|
|
||||||
|
abort_if(isset($target['moved'], $target['moved']['id']), 422, 'Cannot accept a request from an account that has migrated!');
|
||||||
|
|
||||||
if (! $target) {
|
if (! $target) {
|
||||||
return response()->json(['error' => 'Record not found'], 404);
|
return response()->json(['error' => 'Record not found'], 404);
|
||||||
}
|
}
|
||||||
|
@ -1503,7 +1567,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$follower = $followRequest->follower;
|
$follower = $followRequest->follower;
|
||||||
$follow = new Follower();
|
$follow = new Follower;
|
||||||
$follow->profile_id = $follower->id;
|
$follow->profile_id = $follower->id;
|
||||||
$follow->following_id = $pid;
|
$follow->following_id = $pid;
|
||||||
$follow->save();
|
$follow->save();
|
||||||
|
@ -1550,6 +1614,8 @@ class ApiV1Controller extends Controller
|
||||||
return response()->json(['error' => 'Record not found'], 404);
|
return response()->json(['error' => 'Record not found'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abort_if(isset($target['moved'], $target['moved']['id']), 422, 'Cannot reject a request from an account that has migrated!');
|
||||||
|
|
||||||
$followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first();
|
$followRequest = FollowRequest::whereFollowingId($pid)->whereFollowerId($id)->first();
|
||||||
|
|
||||||
if (! $followRequest) {
|
if (! $followRequest) {
|
||||||
|
@ -1608,11 +1674,11 @@ class ApiV1Controller extends Controller
|
||||||
null;
|
null;
|
||||||
});
|
});
|
||||||
|
|
||||||
$stats = Cache::remember('api:v1:instance-data:stats', 43200, function () {
|
$stats = Cache::remember('api:v1:instance-data:stats:v0', 43200, function () {
|
||||||
return [
|
return [
|
||||||
'user_count' => User::count(),
|
'user_count' => (int) User::count(),
|
||||||
'status_count' => Status::whereNull('uri')->count(),
|
'status_count' => (int) StatusService::totalLocalStatuses(),
|
||||||
'domain_count' => Instance::count(),
|
'domain_count' => (int) Instance::count(),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1754,12 +1820,16 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
|
|
||||||
if (config_cache('pixelfed.enforce_account_limit') == true) {
|
$accountSize = UserStorageService::get($user->id);
|
||||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
|
abort_if($accountSize === -1, 403, 'Invalid request.');
|
||||||
return Media::whereUserId($user->id)->sum('size') / 1000;
|
$photo = $request->file('file');
|
||||||
});
|
$fileSize = $photo->getSize();
|
||||||
|
$sizeInKbs = (int) ceil($fileSize / 1000);
|
||||||
|
$updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
|
||||||
|
|
||||||
|
if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
|
||||||
$limit = (int) config_cache('pixelfed.max_account_size');
|
$limit = (int) config_cache('pixelfed.max_account_size');
|
||||||
if ($size >= $limit) {
|
if ($updatedAccountSize >= $limit) {
|
||||||
abort(403, 'Account size limit reached.');
|
abort(403, 'Account size limit reached.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1767,8 +1837,6 @@ class ApiV1Controller extends Controller
|
||||||
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
|
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
|
||||||
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
|
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
|
||||||
|
|
||||||
$photo = $request->file('file');
|
|
||||||
|
|
||||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||||
if (in_array($photo->getMimeType(), $mimes) == false) {
|
if (in_array($photo->getMimeType(), $mimes) == false) {
|
||||||
abort(403, 'Invalid or unsupported mime type.');
|
abort(403, 'Invalid or unsupported mime type.');
|
||||||
|
@ -1802,7 +1870,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media;
|
||||||
$media->status_id = null;
|
$media->status_id = null;
|
||||||
$media->profile_id = $profile->id;
|
$media->profile_id = $profile->id;
|
||||||
$media->user_id = $user->id;
|
$media->user_id = $user->id;
|
||||||
|
@ -1810,7 +1878,7 @@ class ApiV1Controller extends Controller
|
||||||
$media->original_sha256 = $hash;
|
$media->original_sha256 = $hash;
|
||||||
$media->size = $photo->getSize();
|
$media->size = $photo->getSize();
|
||||||
$media->mime = $mime;
|
$media->mime = $mime;
|
||||||
$media->caption = $request->input('description');
|
$media->caption = $request->input('description') ?? "";
|
||||||
$media->filter_class = $filterClass;
|
$media->filter_class = $filterClass;
|
||||||
$media->filter_name = $filterName;
|
$media->filter_name = $filterName;
|
||||||
if ($license) {
|
if ($license) {
|
||||||
|
@ -1831,8 +1899,12 @@ class ApiV1Controller extends Controller
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user->storage_used = (int) $updatedAccountSize;
|
||||||
|
$user->storage_used_updated_at = now();
|
||||||
|
$user->save();
|
||||||
|
|
||||||
Cache::forget($limitKey);
|
Cache::forget($limitKey);
|
||||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
$res['preview_url'] = $media->url().'?v='.time();
|
$res['preview_url'] = $media->url().'?v='.time();
|
||||||
$res['url'] = $media->url().'?v='.time();
|
$res['url'] = $media->url().'?v='.time();
|
||||||
|
@ -1887,9 +1959,9 @@ class ApiV1Controller extends Controller
|
||||||
], 429);
|
], 429);
|
||||||
}
|
}
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||||
|
|
||||||
return $this->json($fractal->createData($resource)->toArray());
|
return $this->json($fractal->createData($resource)->toArray());
|
||||||
}
|
}
|
||||||
|
@ -1913,7 +1985,7 @@ class ApiV1Controller extends Controller
|
||||||
->whereNull('status_id')
|
->whereNull('status_id')
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -1971,12 +2043,16 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
|
|
||||||
if (config_cache('pixelfed.enforce_account_limit') == true) {
|
$accountSize = UserStorageService::get($user->id);
|
||||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
|
abort_if($accountSize === -1, 403, 'Invalid request.');
|
||||||
return Media::whereUserId($user->id)->sum('size') / 1000;
|
$photo = $request->file('file');
|
||||||
});
|
$fileSize = $photo->getSize();
|
||||||
|
$sizeInKbs = (int) ceil($fileSize / 1000);
|
||||||
|
$updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
|
||||||
|
|
||||||
|
if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
|
||||||
$limit = (int) config_cache('pixelfed.max_account_size');
|
$limit = (int) config_cache('pixelfed.max_account_size');
|
||||||
if ($size >= $limit) {
|
if ($updatedAccountSize >= $limit) {
|
||||||
abort(403, 'Account size limit reached.');
|
abort(403, 'Account size limit reached.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1984,8 +2060,6 @@ class ApiV1Controller extends Controller
|
||||||
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
|
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
|
||||||
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
|
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
|
||||||
|
|
||||||
$photo = $request->file('file');
|
|
||||||
|
|
||||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||||
if (in_array($photo->getMimeType(), $mimes) == false) {
|
if (in_array($photo->getMimeType(), $mimes) == false) {
|
||||||
abort(403, 'Invalid or unsupported mime type.');
|
abort(403, 'Invalid or unsupported mime type.');
|
||||||
|
@ -2024,7 +2098,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media;
|
||||||
$media->status_id = null;
|
$media->status_id = null;
|
||||||
$media->profile_id = $profile->id;
|
$media->profile_id = $profile->id;
|
||||||
$media->user_id = $user->id;
|
$media->user_id = $user->id;
|
||||||
|
@ -2032,7 +2106,7 @@ class ApiV1Controller extends Controller
|
||||||
$media->original_sha256 = $hash;
|
$media->original_sha256 = $hash;
|
||||||
$media->size = $photo->getSize();
|
$media->size = $photo->getSize();
|
||||||
$media->mime = $mime;
|
$media->mime = $mime;
|
||||||
$media->caption = $request->input('description');
|
$media->caption = $request->input('description') ?? "";
|
||||||
$media->filter_class = $filterClass;
|
$media->filter_class = $filterClass;
|
||||||
$media->filter_name = $filterName;
|
$media->filter_name = $filterName;
|
||||||
if ($license) {
|
if ($license) {
|
||||||
|
@ -2053,8 +2127,12 @@ class ApiV1Controller extends Controller
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user->storage_used = (int) $updatedAccountSize;
|
||||||
|
$user->storage_used_updated_at = now();
|
||||||
|
$user->save();
|
||||||
|
|
||||||
Cache::forget($limitKey);
|
Cache::forget($limitKey);
|
||||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
$res['preview_url'] = $media->url().'?v='.time();
|
$res['preview_url'] = $media->url().'?v='.time();
|
||||||
$res['url'] = null;
|
$res['url'] = null;
|
||||||
|
@ -2139,6 +2217,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$account = Profile::findOrFail($id);
|
$account = Profile::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($account->moved_to_profile_id, 422, 'Cannot mute an account that has migrated!');
|
||||||
|
|
||||||
if ($account && $account->domain) {
|
if ($account && $account->domain) {
|
||||||
$domain = $account->domain;
|
$domain = $account->domain;
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
|
@ -2172,7 +2252,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
RelationshipService::refresh($pid, $id);
|
RelationshipService::refresh($pid, $id);
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Item($account, new RelationshipTransformer());
|
$resource = new Fractal\Resource\Item($account, new RelationshipTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -2198,6 +2278,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = Profile::findOrFail($id);
|
$profile = Profile::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($profile->moved_to_profile_id, 422, 'Cannot unmute an account that has migrated!');
|
||||||
|
|
||||||
$filter = UserFilter::whereUserId($pid)
|
$filter = UserFilter::whereUserId($pid)
|
||||||
->whereFilterableId($profile->id)
|
->whereFilterableId($profile->id)
|
||||||
->whereFilterableType('App\Profile')
|
->whereFilterableType('App\Profile')
|
||||||
|
@ -2211,7 +2293,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
RelationshipService::refresh($pid, $id);
|
RelationshipService::refresh($pid, $id);
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
|
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
|
@ -2234,14 +2316,17 @@ class ApiV1Controller extends Controller
|
||||||
'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
||||||
'since_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
'since_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
||||||
'types[]' => 'sometimes|array',
|
'types[]' => 'sometimes|array',
|
||||||
|
'types[].*' => 'string|in:mention,reblog,follow,favourite',
|
||||||
'type' => 'sometimes|string|in:mention,reblog,follow,favourite',
|
'type' => 'sometimes|string|in:mention,reblog,follow,favourite',
|
||||||
'_pe' => 'sometimes',
|
'_pe' => 'sometimes',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$limit = $request->input('limit', 20);
|
$limit = $request->input('limit', 20);
|
||||||
|
$ogLimit = $request->input('limit', 20);
|
||||||
if ($limit > 40) {
|
if ($limit > 40) {
|
||||||
$limit = 40;
|
$limit = 40;
|
||||||
|
$ogLimit = 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
$since = $request->input('since_id');
|
$since = $request->input('since_id');
|
||||||
|
@ -2259,6 +2344,10 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$types = $request->input('types');
|
$types = $request->input('types');
|
||||||
|
|
||||||
|
if ($request->has('types')) {
|
||||||
|
$limit = 150;
|
||||||
|
}
|
||||||
|
|
||||||
$maxId = null;
|
$maxId = null;
|
||||||
$minId = null;
|
$minId = null;
|
||||||
AccountService::setLastActive($request->user()->id);
|
AccountService::setLastActive($request->user()->id);
|
||||||
|
@ -2281,7 +2370,12 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$baseUrl = config('app.url').'/api/v1/notifications?limit='.$limit.'&';
|
if ($request->has('types')) {
|
||||||
|
$typesParams = collect($types)->implode('&types[]=');
|
||||||
|
$baseUrl = config('app.url').'/api/v1/notifications?types[]='.$typesParams.'&limit='.$ogLimit.'&';
|
||||||
|
} else {
|
||||||
|
$baseUrl = config('app.url').'/api/v1/notifications?limit='.$ogLimit.'&';
|
||||||
|
}
|
||||||
|
|
||||||
if ($minId == $maxId) {
|
if ($minId == $maxId) {
|
||||||
$minId = null;
|
$minId = null;
|
||||||
|
@ -2323,7 +2417,16 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})->values();
|
})
|
||||||
|
->filter(function ($n) use ($types) {
|
||||||
|
if (! $types) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($n['type'], $types);
|
||||||
|
})
|
||||||
|
->take($ogLimit)
|
||||||
|
->values();
|
||||||
|
|
||||||
if ($maxId) {
|
if ($maxId) {
|
||||||
$link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"';
|
$link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"';
|
||||||
|
@ -3020,9 +3123,9 @@ class ApiV1Controller extends Controller
|
||||||
abort_unless($request->user()->tokenCan('read'), 403);
|
abort_unless($request->user()->tokenCan('read'), 403);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
AccountService::setLastActive($user->id);
|
|
||||||
$pid = $user->profile_id;
|
$pid = $user->profile_id;
|
||||||
$status = StatusService::getMastodon($id, false);
|
$status = StatusService::getMastodon($id, false);
|
||||||
|
$pe = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
|
||||||
if (! $status || ! isset($status['account'])) {
|
if (! $status || ! isset($status['account'])) {
|
||||||
return response('', 404);
|
return response('', 404);
|
||||||
|
@ -3049,7 +3152,9 @@ class ApiV1Controller extends Controller
|
||||||
$descendants = [];
|
$descendants = [];
|
||||||
|
|
||||||
if ($status['in_reply_to_id']) {
|
if ($status['in_reply_to_id']) {
|
||||||
$ancestors[] = StatusService::getMastodon($status['in_reply_to_id'], false);
|
$ancestors[] = $pe ?
|
||||||
|
StatusService::get($status['in_reply_to_id'], false) :
|
||||||
|
StatusService::getMastodon($status['in_reply_to_id'], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($status['replies_count']) {
|
if ($status['replies_count']) {
|
||||||
|
@ -3059,8 +3164,10 @@ class ApiV1Controller extends Controller
|
||||||
->where('in_reply_to_id', $id)
|
->where('in_reply_to_id', $id)
|
||||||
->limit(20)
|
->limit(20)
|
||||||
->pluck('id')
|
->pluck('id')
|
||||||
->map(function ($sid) {
|
->map(function ($sid) use ($pe) {
|
||||||
return StatusService::getMastodon($sid, false);
|
return $pe ?
|
||||||
|
StatusService::get($sid, false) :
|
||||||
|
StatusService::getMastodon($sid, false);
|
||||||
})
|
})
|
||||||
->filter(function ($post) use ($filters) {
|
->filter(function ($post) use ($filters) {
|
||||||
return $post && isset($post['account'], $post['account']['id']) && ! in_array($post['account']['id'], $filters);
|
return $post && isset($post['account'], $post['account']['id']) && ! in_array($post['account']['id'], $filters);
|
||||||
|
@ -3119,6 +3226,7 @@ class ApiV1Controller extends Controller
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$account = AccountService::get($status->profile_id, true);
|
$account = AccountService::get($status->profile_id, true);
|
||||||
abort_if(! $account, 404);
|
abort_if(! $account, 404);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||||
if ($account && strpos($account['acct'], '@') != -1) {
|
if ($account && strpos($account['acct'], '@') != -1) {
|
||||||
$domain = parse_url($account['url'], PHP_URL_HOST);
|
$domain = parse_url($account['url'], PHP_URL_HOST);
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
|
@ -3218,11 +3326,12 @@ class ApiV1Controller extends Controller
|
||||||
$pid = $user->profile_id;
|
$pid = $user->profile_id;
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$account = AccountService::get($status->profile_id, true);
|
$account = AccountService::get($status->profile_id, true);
|
||||||
|
abort_if(! $account, 404);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 404, 'Account moved');
|
||||||
if ($account && strpos($account['acct'], '@') != -1) {
|
if ($account && strpos($account['acct'], '@') != -1) {
|
||||||
$domain = parse_url($account['url'], PHP_URL_HOST);
|
$domain = parse_url($account['url'], PHP_URL_HOST);
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
}
|
}
|
||||||
abort_if(! $account, 404);
|
|
||||||
$author = intval($status->profile_id) === intval($pid) || $user->is_admin;
|
$author = intval($status->profile_id) === intval($pid) || $user->is_admin;
|
||||||
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||||
|
|
||||||
|
@ -3361,10 +3470,9 @@ class ApiV1Controller extends Controller
|
||||||
$limitKey = 'compose:rate-limit:store:'.$user->id;
|
$limitKey = 'compose:rate-limit:store:'.$user->id;
|
||||||
$limitTtl = now()->addMinutes(15);
|
$limitTtl = now()->addMinutes(15);
|
||||||
$limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
|
$limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
|
||||||
|
$minId = SnowflakeService::byDate(now()->subDays(1));
|
||||||
$dailyLimit = Status::whereProfileId($user->profile_id)
|
$dailyLimit = Status::whereProfileId($user->profile_id)
|
||||||
->whereNull('in_reply_to_id')
|
->where('id', '>', $minId)
|
||||||
->whereNull('reblog_of_id')
|
|
||||||
->where('created_at', '>', now()->subDays(1))
|
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
return $dailyLimit >= 1000;
|
return $dailyLimit >= 1000;
|
||||||
|
@ -3528,7 +3636,7 @@ class ApiV1Controller extends Controller
|
||||||
$status = Status::whereProfileId($request->user()->profile->id)
|
$status = Status::whereProfileId($request->user()->profile->id)
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Item($status, new StatusTransformer());
|
$resource = new Fractal\Resource\Item($status, new StatusTransformer);
|
||||||
|
|
||||||
Cache::forget('profile:status_count:'.$status->profile_id);
|
Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
StatusDelete::dispatch($status);
|
StatusDelete::dispatch($status);
|
||||||
|
@ -3555,6 +3663,8 @@ class ApiV1Controller extends Controller
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-share', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-share', $user->id), 403, 'Invalid permissions for this action');
|
||||||
AccountService::setLastActive($user->id);
|
AccountService::setLastActive($user->id);
|
||||||
$status = Status::whereScope('public')->findOrFail($id);
|
$status = Status::whereScope('public')->findOrFail($id);
|
||||||
|
$account = AccountService::get($status->profile_id);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot share a post from an account that has migrated');
|
||||||
if ($status && ($status->uri || $status->url || $status->object_url)) {
|
if ($status && ($status->uri || $status->url || $status->object_url)) {
|
||||||
$url = $status->uri ?? $status->url ?? $status->object_url;
|
$url = $status->uri ?? $status->url ?? $status->object_url;
|
||||||
$domain = parse_url($url, PHP_URL_HOST);
|
$domain = parse_url($url, PHP_URL_HOST);
|
||||||
|
@ -3607,6 +3717,8 @@ class ApiV1Controller extends Controller
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-share', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-share', $user->id), 403, 'Invalid permissions for this action');
|
||||||
AccountService::setLastActive($user->id);
|
AccountService::setLastActive($user->id);
|
||||||
$status = Status::whereScope('public')->findOrFail($id);
|
$status = Status::whereScope('public')->findOrFail($id);
|
||||||
|
$account = AccountService::get($status->profile_id);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot unshare a post from an account that has migrated');
|
||||||
|
|
||||||
if (intval($status->profile_id) !== intval($user->profile_id)) {
|
if (intval($status->profile_id) !== intval($user->profile_id)) {
|
||||||
if ($status->scope == 'private') {
|
if ($status->scope == 'private') {
|
||||||
|
@ -3713,7 +3825,6 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = StatusHashtag::whereHashtagId($tag->id)
|
$res = StatusHashtag::whereHashtagId($tag->id)
|
||||||
->whereIn('status_visibility', ['public', 'private', 'unlisted'])
|
|
||||||
->where('status_id', $dir, $id)
|
->where('status_id', $dir, $id)
|
||||||
->orderBy('status_id', 'desc')
|
->orderBy('status_id', 'desc')
|
||||||
->limit(100)
|
->limit(100)
|
||||||
|
@ -3730,11 +3841,11 @@ class ApiV1Controller extends Controller
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($i['visibility'] === 'private') {
|
// if ($i['visibility'] === 'private') {
|
||||||
if ((int) $i['account']['id'] !== $pid) {
|
// if ((int) $i['account']['id'] !== $pid) {
|
||||||
return FollowerService::follows($pid, $i['account']['id'], true);
|
// return FollowerService::follows($pid, $i['account']['id'], true);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if ($onlyMedia == true) {
|
if ($onlyMedia == true) {
|
||||||
if (! isset($i['media_attachments']) || ! count($i['media_attachments'])) {
|
if (! isset($i['media_attachments']) || ! count($i['media_attachments'])) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -3841,7 +3952,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
|
$account = AccountService::get($status->profile_id);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark a post from an account that has migrated');
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
||||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||||
|
@ -3957,8 +4069,8 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$status = StatusService::getMastodon($id, false);
|
$status = StatusService::getMastodon($id, false);
|
||||||
|
|
||||||
abort_if(! $status, 404);
|
abort_if(! $status, 404);
|
||||||
|
abort_if(isset($status['account'], $account['account']['moved']['id']), 404, 'Account moved');
|
||||||
|
|
||||||
if ($status['visibility'] == 'private') {
|
if ($status['visibility'] == 'private') {
|
||||||
if ($pid != $status['account']['id']) {
|
if ($pid != $status['account']['id']) {
|
||||||
|
@ -4047,11 +4159,11 @@ class ApiV1Controller extends Controller
|
||||||
{
|
{
|
||||||
abort_if(! $request->user(), 403);
|
abort_if(! $request->user(), 403);
|
||||||
|
|
||||||
$status = Status::findOrFail($id);
|
$status = StatusService::get($id, false, true);
|
||||||
$pid = $request->user()->profile_id;
|
abort_if(! $status, 404);
|
||||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
abort_if(! in_array($status['visibility'], ['public', 'unlisted', 'private']), 404);
|
||||||
|
|
||||||
return $this->json(StatusService::getState($status->id, $pid));
|
return $this->json(StatusService::getState($status['id'], $request->user()->profile_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4200,4 +4312,26 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
return $this->json([]);
|
return $this->json([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/v1/instance/peers
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function instancePeers(Request $request)
|
||||||
|
{
|
||||||
|
if ((bool) config('instance.show_peers') == false) {
|
||||||
|
return $this->json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json(
|
||||||
|
Cache::remember(InstanceService::CACHE_KEY_API_PEERS_LIST, now()->addHours(24), function () {
|
||||||
|
return Instance::whereNotNull('nodeinfo_last_fetched')
|
||||||
|
->whereBanned(false)
|
||||||
|
->where('nodeinfo_last_fetched', '>', now()->subDays(8))
|
||||||
|
->pluck('domain');
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,27 +5,39 @@ namespace App\Http\Controllers\Api;
|
||||||
use App\AccountLog;
|
use App\AccountLog;
|
||||||
use App\EmailVerification;
|
use App\EmailVerification;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Controllers\StatusController;
|
||||||
use App\Http\Resources\StatusStateless;
|
use App\Http\Resources\StatusStateless;
|
||||||
|
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||||
use App\Jobs\ReportPipeline\ReportNotifyAdminViaEmail;
|
use App\Jobs\ReportPipeline\ReportNotifyAdminViaEmail;
|
||||||
|
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||||
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
|
use App\Jobs\VideoPipeline\VideoThumbnail;
|
||||||
use App\Mail\ConfirmAppEmail;
|
use App\Mail\ConfirmAppEmail;
|
||||||
use App\Mail\PasswordChange;
|
use App\Mail\PasswordChange;
|
||||||
|
use App\Media;
|
||||||
use App\Place;
|
use App\Place;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Report;
|
use App\Report;
|
||||||
|
use App\Rules\ExpoPushTokenRule;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\BouncerService;
|
use App\Services\BouncerService;
|
||||||
use App\Services\EmailService;
|
use App\Services\EmailService;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
|
use App\Services\MediaBlocklistService;
|
||||||
|
use App\Services\MediaPathService;
|
||||||
use App\Services\NetworkTimelineService;
|
use App\Services\NetworkTimelineService;
|
||||||
|
use App\Services\NotificationAppGatewayService;
|
||||||
use App\Services\ProfileStatusService;
|
use App\Services\ProfileStatusService;
|
||||||
use App\Services\PublicTimelineService;
|
use App\Services\PublicTimelineService;
|
||||||
|
use App\Services\PushNotificationService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
use App\Services\UserStorageService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\StatusArchived;
|
use App\StatusArchived;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\UserSetting;
|
use App\UserSetting;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
use App\Util\Lexer\RestrictedNames;
|
use App\Util\Lexer\RestrictedNames;
|
||||||
use Cache;
|
use Cache;
|
||||||
use DB;
|
use DB;
|
||||||
|
@ -44,8 +56,8 @@ class ApiV1Dot1Controller extends Controller
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->fractal = new Fractal\Manager();
|
$this->fractal = new Fractal\Manager;
|
||||||
$this->fractal->setSerializer(new ArraySerializer());
|
$this->fractal->setSerializer(new ArraySerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function json($res, $code = 200, $headers = [])
|
public function json($res, $code = 200, $headers = [])
|
||||||
|
@ -307,7 +319,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
|
if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
|
||||||
abort_if(BouncerService::checkIp($request->ip()), 404);
|
abort_if(BouncerService::checkIp($request->ip()), 404);
|
||||||
}
|
}
|
||||||
$agent = new Agent();
|
$agent = new Agent;
|
||||||
$currentIp = $request->ip();
|
$currentIp = $request->ip();
|
||||||
|
|
||||||
$activity = AccountLog::whereUserId($user->id)
|
$activity = AccountLog::whereUserId($user->id)
|
||||||
|
@ -487,8 +499,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
abort_if(BouncerService::checkIp($request->ip()), 404);
|
abort_if(BouncerService::checkIp($request->ip()), 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rl = RateLimiter::attempt('pf:apiv1.1:iar:'.$request->ip(), config('pixelfed.app_registration_rate_limit_attempts', 3), function () {
|
$rl = RateLimiter::attempt('pf:apiv1.1:iar:'.$request->ip(), config('pixelfed.app_registration_rate_limit_attempts', 3), function () {}, config('pixelfed.app_registration_rate_limit_decay', 1800));
|
||||||
}, config('pixelfed.app_registration_rate_limit_decay', 1800));
|
|
||||||
abort_if(! $rl, 400, 'Too many requests');
|
abort_if(! $rl, 400, 'Too many requests');
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
|
@ -566,7 +577,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
|
|
||||||
$rtoken = Str::random(64);
|
$rtoken = Str::random(64);
|
||||||
|
|
||||||
$verify = new EmailVerification();
|
$verify = new EmailVerification;
|
||||||
$verify->user_id = $user->id;
|
$verify->user_id = $user->id;
|
||||||
$verify->email = $user->email;
|
$verify->email = $user->email;
|
||||||
$verify->user_token = $user->app_register_token;
|
$verify->user_token = $user->app_register_token;
|
||||||
|
@ -618,8 +629,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
abort_if(BouncerService::checkIp($request->ip()), 404);
|
abort_if(BouncerService::checkIp($request->ip()), 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.$request->ip(), config('pixelfed.app_registration_confirm_rate_limit_attempts', 20), function () {
|
$rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.$request->ip(), config('pixelfed.app_registration_confirm_rate_limit_attempts', 20), function () {}, config('pixelfed.app_registration_confirm_rate_limit_decay', 1800));
|
||||||
}, config('pixelfed.app_registration_confirm_rate_limit_decay', 1800));
|
|
||||||
abort_if(! $rl, 429, 'Too many requests');
|
abort_if(! $rl, 429, 'Too many requests');
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
|
@ -929,7 +939,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
public function getMutualAccounts(Request $request, $id)
|
public function getMutualAccounts(Request $request, $id)
|
||||||
{
|
{
|
||||||
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||||
abort_unless($request->user()->tokenCan('follows'), 403);
|
abort_unless($request->user()->tokenCan('follow'), 403);
|
||||||
|
|
||||||
$account = AccountService::get($id, true);
|
$account = AccountService::get($id, true);
|
||||||
if (! $account || ! isset($account['id'])) {
|
if (! $account || ! isset($account['id'])) {
|
||||||
|
@ -945,4 +955,420 @@ class ApiV1Dot1Controller extends Controller
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function accountUsernameToId(Request $request, $username)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user() || ! $request->user()->token() || ! $username, 403);
|
||||||
|
abort_unless($request->user()->tokenCan('read'), 403);
|
||||||
|
$username = trim($username);
|
||||||
|
$rateLimiting = (bool) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.enabled');
|
||||||
|
$ipRateLimiting = (bool) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_enabled');
|
||||||
|
if ($ipRateLimiting) {
|
||||||
|
$userLimit = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_limit');
|
||||||
|
$userDecay = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.ip_decay');
|
||||||
|
$userKey = 'pf:apiv1.1:acctU2ID:byIp:'.$request->ip();
|
||||||
|
|
||||||
|
if (RateLimiter::tooManyAttempts($userKey, $userLimit)) {
|
||||||
|
$limits = [
|
||||||
|
'X-Rate-Limit-Limit' => $userLimit,
|
||||||
|
'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit),
|
||||||
|
'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey),
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->json(['error' => 'Too many attempts!'], 429, $limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter::increment($userKey, $userDecay);
|
||||||
|
$limits = [
|
||||||
|
'X-Rate-Limit-Limit' => $userLimit,
|
||||||
|
'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit),
|
||||||
|
'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($rateLimiting) {
|
||||||
|
$userLimit = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.limit');
|
||||||
|
$userDecay = (int) config_cache('api.rate-limits.v1Dot1.accounts.usernameToId.decay');
|
||||||
|
$userKey = 'pf:apiv1.1:acctU2ID:byUid:'.$request->user()->id;
|
||||||
|
|
||||||
|
if (RateLimiter::tooManyAttempts($userKey, $userLimit)) {
|
||||||
|
$limits = [
|
||||||
|
'X-Rate-Limit-Limit' => $userLimit,
|
||||||
|
'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit),
|
||||||
|
'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey),
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->json(['error' => 'Too many attempts!'], 429, $limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter::increment($userKey, $userDecay);
|
||||||
|
$limits = [
|
||||||
|
'X-Rate-Limit-Limit' => $userLimit,
|
||||||
|
'X-Rate-Limit-Remaining' => RateLimiter::remaining($userKey, $userLimit),
|
||||||
|
'X-Rate-Limit-Reset' => RateLimiter::availableIn($userKey),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (str_ends_with($username, config_cache('pixelfed.domain.app'))) {
|
||||||
|
$pre = str_starts_with($username, '@') ? substr($username, 1) : $username;
|
||||||
|
$parts = explode('@', $pre);
|
||||||
|
$username = $parts[0];
|
||||||
|
}
|
||||||
|
$accountId = AccountService::usernameToId($username, true);
|
||||||
|
if (! $accountId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$account = AccountService::get($accountId);
|
||||||
|
|
||||||
|
return $this->json($account, 200, $rateLimiting ? $limits : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPushState(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless($request->hasHeader('X-PIXELFED-APP'), 404, 'Not found');
|
||||||
|
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||||
|
abort_unless($request->user()->tokenCan('push'), 403);
|
||||||
|
abort_unless(NotificationAppGatewayService::enabled(), 404, 'Push notifications are not supported on this server.');
|
||||||
|
$user = $request->user();
|
||||||
|
abort_if($user->status, 422, 'Cannot access this resource at this time');
|
||||||
|
$res = [
|
||||||
|
'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
|
||||||
|
'username' => (string) $user->username,
|
||||||
|
'profile_id' => (string) $user->profile_id,
|
||||||
|
'notify_enabled' => (bool) $user->notify_enabled,
|
||||||
|
'has_token' => (bool) $user->expo_token,
|
||||||
|
'notify_like' => (bool) $user->notify_like,
|
||||||
|
'notify_follow' => (bool) $user->notify_follow,
|
||||||
|
'notify_mention' => (bool) $user->notify_mention,
|
||||||
|
'notify_comment' => (bool) $user->notify_comment,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function disablePush(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless($request->hasHeader('X-PIXELFED-APP'), 404, 'Not found');
|
||||||
|
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||||
|
abort_unless($request->user()->tokenCan('push'), 403);
|
||||||
|
abort_unless(NotificationAppGatewayService::enabled(), 404, 'Push notifications are not supported on this server.');
|
||||||
|
abort_if($request->user()->status, 422, 'Cannot access this resource at this time');
|
||||||
|
|
||||||
|
$request->user()->update([
|
||||||
|
'notify_enabled' => false,
|
||||||
|
'expo_token' => null,
|
||||||
|
'notify_like' => false,
|
||||||
|
'notify_follow' => false,
|
||||||
|
'notify_mention' => false,
|
||||||
|
'notify_comment' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
PushNotificationService::removeMemberFromAll($request->user()->profile_id);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
|
||||||
|
'username' => (string) $user->username,
|
||||||
|
'profile_id' => (string) $user->profile_id,
|
||||||
|
'notify_enabled' => (bool) $user->notify_enabled,
|
||||||
|
'has_token' => (bool) $user->expo_token,
|
||||||
|
'notify_like' => (bool) $user->notify_like,
|
||||||
|
'notify_follow' => (bool) $user->notify_follow,
|
||||||
|
'notify_mention' => (bool) $user->notify_mention,
|
||||||
|
'notify_comment' => (bool) $user->notify_comment,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function comparePush(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless($request->hasHeader('X-PIXELFED-APP'), 404, 'Not found');
|
||||||
|
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||||
|
abort_unless($request->user()->tokenCan('push'), 403);
|
||||||
|
abort_unless(NotificationAppGatewayService::enabled(), 404, 'Push notifications are not supported on this server.');
|
||||||
|
abort_if($request->user()->status, 422, 'Cannot access this resource at this time');
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'expo_token' => ['required', 'string', new ExpoPushTokenRule],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (empty($user->expo_token)) {
|
||||||
|
return $this->json([
|
||||||
|
'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
|
||||||
|
'username' => (string) $user->username,
|
||||||
|
'profile_id' => (string) $user->profile_id,
|
||||||
|
'notify_enabled' => (bool) $user->notify_enabled,
|
||||||
|
'match' => false,
|
||||||
|
'has_existing' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $request->input('expo_token');
|
||||||
|
$knownToken = $user->expo_token;
|
||||||
|
$match = hash_equals($knownToken, $token);
|
||||||
|
|
||||||
|
return $this->json([
|
||||||
|
'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
|
||||||
|
'username' => (string) $user->username,
|
||||||
|
'profile_id' => (string) $user->profile_id,
|
||||||
|
'notify_enabled' => (bool) $user->notify_enabled,
|
||||||
|
'match' => $match,
|
||||||
|
'has_existing' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePush(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless($request->hasHeader('X-PIXELFED-APP'), 404, 'Not found');
|
||||||
|
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||||
|
abort_unless($request->user()->tokenCan('push'), 403);
|
||||||
|
abort_unless(NotificationAppGatewayService::enabled(), 404, 'Push notifications are not supported on this server.');
|
||||||
|
abort_if($request->user()->status, 422, 'Cannot access this resource at this time');
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'notify_enabled' => 'required',
|
||||||
|
'token' => ['required', 'string', new ExpoPushTokenRule],
|
||||||
|
'notify_like' => 'sometimes',
|
||||||
|
'notify_follow' => 'sometimes',
|
||||||
|
'notify_mention' => 'sometimes',
|
||||||
|
'notify_comment' => 'sometimes',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(! $pid, 422, 'An error occured');
|
||||||
|
$expoToken = $request->input('token');
|
||||||
|
|
||||||
|
$existing = User::where('profile_id', '!=', $pid)->whereExpoToken($expoToken)->count();
|
||||||
|
abort_if($existing && $existing > 5, 400, 'Push token is already used by another account');
|
||||||
|
|
||||||
|
$request->user()->update([
|
||||||
|
'notify_enabled' => $request->boolean('notify_enabled'),
|
||||||
|
'expo_token' => $expoToken,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->filled('notify_like')) {
|
||||||
|
$request->user()->update(['notify_like' => (bool) $request->boolean('notify_like')]);
|
||||||
|
$request->boolean('notify_like') == true ?
|
||||||
|
PushNotificationService::set('like', $pid) :
|
||||||
|
PushNotificationService::removeMember('like', $pid);
|
||||||
|
}
|
||||||
|
if ($request->filled('notify_follow')) {
|
||||||
|
$request->user()->update(['notify_follow' => (bool) $request->boolean('notify_follow')]);
|
||||||
|
$request->boolean('notify_follow') == true ?
|
||||||
|
PushNotificationService::set('follow', $pid) :
|
||||||
|
PushNotificationService::removeMember('follow', $pid);
|
||||||
|
}
|
||||||
|
if ($request->filled('notify_mention')) {
|
||||||
|
$request->user()->update(['notify_mention' => (bool) $request->boolean('notify_mention')]);
|
||||||
|
$request->boolean('notify_mention') == true ?
|
||||||
|
PushNotificationService::set('mention', $pid) :
|
||||||
|
PushNotificationService::removeMember('mention', $pid);
|
||||||
|
}
|
||||||
|
if ($request->filled('notify_comment')) {
|
||||||
|
$request->user()->update(['notify_comment' => (bool) $request->boolean('notify_comment')]);
|
||||||
|
$request->boolean('notify_comment') == true ?
|
||||||
|
PushNotificationService::set('comment', $pid) :
|
||||||
|
PushNotificationService::removeMember('comment', $pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->boolean('notify_enabled') == false) {
|
||||||
|
PushNotificationService::removeMemberFromAll($request->user()->profile_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
$res = [
|
||||||
|
'version' => PushNotificationService::PUSH_GATEWAY_VERSION,
|
||||||
|
'notify_enabled' => (bool) $user->notify_enabled,
|
||||||
|
'has_token' => (bool) $user->expo_token,
|
||||||
|
'notify_like' => (bool) $user->notify_like,
|
||||||
|
'notify_follow' => (bool) $user->notify_follow,
|
||||||
|
'notify_mention' => (bool) $user->notify_mention,
|
||||||
|
'notify_comment' => (bool) $user->notify_comment,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/v1.1/status/create
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return StatusTransformer
|
||||||
|
*/
|
||||||
|
public function statusCreate(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||||
|
abort_unless($request->user()->tokenCan('write'), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'status' => 'nullable|string|max:'.(int) config_cache('pixelfed.max_caption_length'),
|
||||||
|
'file' => [
|
||||||
|
'required',
|
||||||
|
'file',
|
||||||
|
'mimetypes:'.config_cache('pixelfed.media_types'),
|
||||||
|
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||||
|
function ($attribute, $value, $fail) {
|
||||||
|
if (is_array($value) && count($value) > 1) {
|
||||||
|
$fail('Only one file can be uploaded at a time.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'sensitive' => 'nullable',
|
||||||
|
'visibility' => 'string|in:private,unlisted,public',
|
||||||
|
'spoiler_text' => 'sometimes|max:140',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->hasHeader('idempotency-key')) {
|
||||||
|
$key = 'pf:api:v1:status:idempotency-key:'.$request->user()->id.':'.hash('sha1', $request->header('idempotency-key'));
|
||||||
|
$exists = Cache::has($key);
|
||||||
|
abort_if($exists, 400, 'Duplicate idempotency key.');
|
||||||
|
Cache::put($key, 1, 3600);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config('costar.enabled') == true) {
|
||||||
|
$blockedKeywords = config('costar.keyword.block');
|
||||||
|
if ($blockedKeywords !== null && $request->status) {
|
||||||
|
$keywords = config('costar.keyword.block');
|
||||||
|
foreach ($keywords as $kw) {
|
||||||
|
if (Str::contains($request->status, $kw) == true) {
|
||||||
|
abort(400, 'Invalid object. Contains banned keyword.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if ($user->has_roles) {
|
||||||
|
abort_if(! UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile = $user->profile;
|
||||||
|
|
||||||
|
$limitKey = 'compose:rate-limit:media-upload:'.$user->id;
|
||||||
|
$photo = $request->file('file');
|
||||||
|
$fileSize = $photo->getSize();
|
||||||
|
$sizeInKbs = (int) ceil($fileSize / 1000);
|
||||||
|
$accountSize = UserStorageService::get($user->id);
|
||||||
|
abort_if($accountSize === -1, 403, 'Invalid request.');
|
||||||
|
$updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
|
||||||
|
|
||||||
|
if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
|
||||||
|
$limit = (int) config_cache('pixelfed.max_account_size');
|
||||||
|
if ($updatedAccountSize >= $limit) {
|
||||||
|
abort(403, 'Account size limit reached.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||||
|
if (in_array($photo->getMimeType(), $mimes) == false) {
|
||||||
|
abort(403, 'Invalid or unsupported mime type.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$storagePath = MediaPathService::get($user, 2);
|
||||||
|
$path = $photo->storePublicly($storagePath);
|
||||||
|
$hash = \hash_file('sha256', $photo);
|
||||||
|
$license = null;
|
||||||
|
$mime = $photo->getMimeType();
|
||||||
|
|
||||||
|
$settings = UserSetting::whereUserId($user->id)->first();
|
||||||
|
|
||||||
|
if ($settings && ! empty($settings->compose_settings)) {
|
||||||
|
$compose = $settings->compose_settings;
|
||||||
|
|
||||||
|
if (isset($compose['default_license']) && $compose['default_license'] != 1) {
|
||||||
|
$license = $compose['default_license'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
||||||
|
|
||||||
|
$visibility = $profile->is_private ? 'private' : (
|
||||||
|
$profile->unlisted == true &&
|
||||||
|
$request->input('visibility', 'public') == 'public' ?
|
||||||
|
'unlisted' :
|
||||||
|
$request->input('visibility', 'public'));
|
||||||
|
|
||||||
|
if ($user->last_active_at == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = strip_tags($request->input('status'));
|
||||||
|
$rendered = Autolink::create()->autolink($content);
|
||||||
|
$cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false);
|
||||||
|
$spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null;
|
||||||
|
|
||||||
|
$status = new Status;
|
||||||
|
$status->caption = $content;
|
||||||
|
$status->rendered = $rendered;
|
||||||
|
$status->profile_id = $user->profile_id;
|
||||||
|
$status->is_nsfw = $cw;
|
||||||
|
$status->cw_summary = $spoilerText;
|
||||||
|
$status->scope = $visibility;
|
||||||
|
$status->visibility = $visibility;
|
||||||
|
$status->type = StatusController::mimeTypeCheck([$mime]);
|
||||||
|
$status->save();
|
||||||
|
|
||||||
|
if (! $status) {
|
||||||
|
abort(500, 'An error occured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$media = new Media;
|
||||||
|
$media->status_id = $status->id;
|
||||||
|
$media->profile_id = $profile->id;
|
||||||
|
$media->user_id = $user->id;
|
||||||
|
$media->media_path = $path;
|
||||||
|
$media->original_sha256 = $hash;
|
||||||
|
$media->size = $photo->getSize();
|
||||||
|
$media->mime = $mime;
|
||||||
|
$media->order = 1;
|
||||||
|
$media->caption = $request->input('description');
|
||||||
|
if ($license) {
|
||||||
|
$media->license = $license;
|
||||||
|
}
|
||||||
|
$media->save();
|
||||||
|
|
||||||
|
switch ($media->mime) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
case 'image/png':
|
||||||
|
ImageOptimize::dispatch($media)->onQueue('mmo');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'video/mp4':
|
||||||
|
VideoThumbnail::dispatch($media)->onQueue('mmo');
|
||||||
|
$preview_url = '/storage/no-preview.png';
|
||||||
|
$url = '/storage/no-preview.png';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->storage_used = (int) $updatedAccountSize;
|
||||||
|
$user->storage_used_updated_at = now();
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
NewStatusPipeline::dispatch($status);
|
||||||
|
|
||||||
|
Cache::forget('user:account:id:'.$user->id);
|
||||||
|
Cache::forget('_api:statuses:recent_9:'.$user->profile_id);
|
||||||
|
Cache::forget('profile:status_count:'.$user->profile_id);
|
||||||
|
Cache::forget($user->storageUsedKey());
|
||||||
|
Cache::forget('profile:embed:'.$status->profile_id);
|
||||||
|
Cache::forget($limitKey);
|
||||||
|
|
||||||
|
$res = StatusService::getMastodon($status->id, false);
|
||||||
|
$res['favourited'] = false;
|
||||||
|
$res['language'] = 'en';
|
||||||
|
$res['bookmarked'] = false;
|
||||||
|
$res['card'] = null;
|
||||||
|
|
||||||
|
return $this->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nagState(Request $request)
|
||||||
|
{
|
||||||
|
abort_unless((bool) config_cache('pixelfed.oauth_enabled'), 404);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'active' => NotificationAppGatewayService::enabled(),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,44 +2,32 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||||
|
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||||
|
use App\Jobs\VideoPipeline\VideoThumbnail;
|
||||||
use App\Media;
|
use App\Media;
|
||||||
use App\UserSetting;
|
|
||||||
use App\User;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\BouncerService;
|
|
||||||
use App\Services\InstanceService;
|
use App\Services\InstanceService;
|
||||||
use App\Services\MediaBlocklistService;
|
use App\Services\MediaBlocklistService;
|
||||||
use App\Services\MediaPathService;
|
use App\Services\MediaPathService;
|
||||||
use App\Services\SearchApiV2Service;
|
use App\Services\SearchApiV2Service;
|
||||||
|
use App\Services\UserRoleService;
|
||||||
|
use App\Services\UserStorageService;
|
||||||
|
use App\Transformer\Api\Mastodon\v1\MediaTransformer;
|
||||||
|
use App\User;
|
||||||
|
use App\UserSetting;
|
||||||
use App\Util\Media\Filter;
|
use App\Util\Media\Filter;
|
||||||
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
use App\Util\Site\Nodeinfo;
|
||||||
use App\Jobs\VideoPipeline\{
|
use Illuminate\Http\Request;
|
||||||
VideoOptimize,
|
use Illuminate\Support\Facades\Cache;
|
||||||
VideoPostProcess,
|
use Illuminate\Support\Facades\Storage;
|
||||||
VideoThumbnail
|
|
||||||
};
|
|
||||||
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
|
||||||
use App\Transformer\Api\Mastodon\v1\{
|
|
||||||
AccountTransformer,
|
|
||||||
MediaTransformer,
|
|
||||||
NotificationTransformer,
|
|
||||||
StatusTransformer,
|
|
||||||
};
|
|
||||||
use App\Transformer\Api\{
|
|
||||||
RelationshipTransformer,
|
|
||||||
};
|
|
||||||
use App\Util\Site\Nodeinfo;
|
|
||||||
use App\Services\UserRoleService;
|
|
||||||
|
|
||||||
class ApiV2Controller extends Controller
|
class ApiV2Controller extends Controller
|
||||||
{
|
{
|
||||||
const PF_API_ENTITY_KEY = "_pe";
|
const PF_API_ENTITY_KEY = '_pe';
|
||||||
|
|
||||||
public function json($res, $code = 200, $headers = [])
|
public function json($res, $code = 200, $headers = [])
|
||||||
{
|
{
|
||||||
|
@ -53,6 +41,7 @@ class ApiV2Controller extends Controller
|
||||||
return AccountService::getMastodon(config_cache('instance.admin.pid'), true);
|
return AccountService::getMastodon(config_cache('instance.admin.pid'), true);
|
||||||
}
|
}
|
||||||
$admin = User::whereIsAdmin(true)->first();
|
$admin = User::whereIsAdmin(true)->first();
|
||||||
|
|
||||||
return $admin && isset($admin->profile_id) ?
|
return $admin && isset($admin->profile_id) ?
|
||||||
AccountService::getMastodon($admin->profile_id, true) :
|
AccountService::getMastodon($admin->profile_id, true) :
|
||||||
null;
|
null;
|
||||||
|
@ -63,9 +52,10 @@ class ApiV2Controller extends Controller
|
||||||
collect(json_decode(config_cache('app.rules'), true))
|
collect(json_decode(config_cache('app.rules'), true))
|
||||||
->map(function ($rule, $key) {
|
->map(function ($rule, $key) {
|
||||||
$id = $key + 1;
|
$id = $key + 1;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => "{$id}",
|
'id' => "{$id}",
|
||||||
'text' => $rule
|
'text' => $rule,
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
->toArray() : [];
|
->toArray() : [];
|
||||||
|
@ -80,22 +70,22 @@ class ApiV2Controller extends Controller
|
||||||
'description' => config_cache('app.short_description'),
|
'description' => config_cache('app.short_description'),
|
||||||
'usage' => [
|
'usage' => [
|
||||||
'users' => [
|
'users' => [
|
||||||
'active_month' => (int) Nodeinfo::activeUsersMonthly()
|
'active_month' => (int) Nodeinfo::activeUsersMonthly(),
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'thumbnail' => [
|
'thumbnail' => [
|
||||||
'url' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
|
'url' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
|
||||||
'blurhash' => InstanceService::headerBlurhash(),
|
'blurhash' => InstanceService::headerBlurhash(),
|
||||||
'versions' => [
|
'versions' => [
|
||||||
'@1x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
|
'@1x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
|
||||||
'@2x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg'))
|
'@2x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'languages' => [config('app.locale')],
|
'languages' => [config('app.locale')],
|
||||||
'configuration' => [
|
'configuration' => [
|
||||||
'urls' => [
|
'urls' => [
|
||||||
'streaming' => null,
|
'streaming' => null,
|
||||||
'status' => null
|
'status' => null,
|
||||||
],
|
],
|
||||||
'vapid' => [
|
'vapid' => [
|
||||||
'public_key' => config('webpush.vapid.public_key'),
|
'public_key' => config('webpush.vapid.public_key'),
|
||||||
|
@ -106,7 +96,7 @@ class ApiV2Controller extends Controller
|
||||||
'statuses' => [
|
'statuses' => [
|
||||||
'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
|
'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
|
||||||
'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
|
'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
|
||||||
'characters_reserved_per_url' => 23
|
'characters_reserved_per_url' => 23,
|
||||||
],
|
],
|
||||||
'media_attachments' => [
|
'media_attachments' => [
|
||||||
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
|
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
|
||||||
|
@ -114,7 +104,7 @@ class ApiV2Controller extends Controller
|
||||||
'image_matrix_limit' => 3686400,
|
'image_matrix_limit' => 3686400,
|
||||||
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
||||||
'video_frame_rate_limit' => 240,
|
'video_frame_rate_limit' => 240,
|
||||||
'video_matrix_limit' => 3686400
|
'video_matrix_limit' => 3686400,
|
||||||
],
|
],
|
||||||
'polls' => [
|
'polls' => [
|
||||||
'max_options' => 0,
|
'max_options' => 0,
|
||||||
|
@ -134,14 +124,15 @@ class ApiV2Controller extends Controller
|
||||||
],
|
],
|
||||||
'contact' => [
|
'contact' => [
|
||||||
'email' => config('instance.email'),
|
'email' => config('instance.email'),
|
||||||
'account' => $contact
|
'account' => $contact,
|
||||||
],
|
],
|
||||||
'rules' => $rules
|
'rules' => $rules,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
$res['registrations']['enabled'] = (bool) config_cache('pixelfed.open_registration');
|
$res['registrations']['enabled'] = (bool) config_cache('pixelfed.open_registration');
|
||||||
$res['registrations']['approval_required'] = (bool) config_cache('instance.curated_registration.enabled');
|
$res['registrations']['approval_required'] = (bool) config_cache('instance.curated_registration.enabled');
|
||||||
|
|
||||||
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,18 +157,19 @@ class ApiV2Controller extends Controller
|
||||||
'resolve' => 'nullable',
|
'resolve' => 'nullable',
|
||||||
'limit' => 'nullable|integer|max:40',
|
'limit' => 'nullable|integer|max:40',
|
||||||
'offset' => 'nullable|integer',
|
'offset' => 'nullable|integer',
|
||||||
'following' => 'nullable'
|
'following' => 'nullable',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($request->user()->has_roles && ! UserRoleService::can('can-view-discover', $request->user()->id)) {
|
if ($request->user()->has_roles && ! UserRoleService::can('can-view-discover', $request->user()->id)) {
|
||||||
return [
|
return [
|
||||||
'accounts' => [],
|
'accounts' => [],
|
||||||
'hashtags' => [],
|
'hashtags' => [],
|
||||||
'statuses' => []
|
'statuses' => [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$mastodonMode = ! $request->has('_pe');
|
$mastodonMode = ! $request->has('_pe');
|
||||||
|
|
||||||
return $this->json(SearchApiV2Service::query($request, $mastodonMode));
|
return $this->json(SearchApiV2Service::query($request, $mastodonMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +185,7 @@ class ApiV2Controller extends Controller
|
||||||
'host' => config('broadcasting.connections.pusher.options.host'),
|
'host' => config('broadcasting.connections.pusher.options.host'),
|
||||||
'port' => config('broadcasting.connections.pusher.options.port'),
|
'port' => config('broadcasting.connections.pusher.options.port'),
|
||||||
'key' => config('broadcasting.connections.pusher.key'),
|
'key' => config('broadcasting.connections.pusher.key'),
|
||||||
'cluster' => config('broadcasting.connections.pusher.options.cluster')
|
'cluster' => config('broadcasting.connections.pusher.options.cluster'),
|
||||||
] : [];
|
] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +214,7 @@ class ApiV2Controller extends Controller
|
||||||
'filter_name' => 'nullable|string|max:24',
|
'filter_name' => 'nullable|string|max:24',
|
||||||
'filter_class' => 'nullable|alpha_dash|max:24',
|
'filter_class' => 'nullable|alpha_dash|max:24',
|
||||||
'description' => 'nullable|string|max:'.config_cache('pixelfed.max_altext_length'),
|
'description' => 'nullable|string|max:'.config_cache('pixelfed.max_altext_length'),
|
||||||
'replace_id' => 'sometimes'
|
'replace_id' => 'sometimes',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
@ -246,12 +238,16 @@ class ApiV2Controller extends Controller
|
||||||
|
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
|
|
||||||
if(config_cache('pixelfed.enforce_account_limit') == true) {
|
$accountSize = UserStorageService::get($user->id);
|
||||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
|
abort_if($accountSize === -1, 403, 'Invalid request.');
|
||||||
return Media::whereUserId($user->id)->sum('size') / 1000;
|
$photo = $request->file('file');
|
||||||
});
|
$fileSize = $photo->getSize();
|
||||||
|
$sizeInKbs = (int) ceil($fileSize / 1000);
|
||||||
|
$updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
|
||||||
|
|
||||||
|
if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
|
||||||
$limit = (int) config_cache('pixelfed.max_account_size');
|
$limit = (int) config_cache('pixelfed.max_account_size');
|
||||||
if ($size >= $limit) {
|
if ($updatedAccountSize >= $limit) {
|
||||||
abort(403, 'Account size limit reached.');
|
abort(403, 'Account size limit reached.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,8 +255,6 @@ class ApiV2Controller extends Controller
|
||||||
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
|
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
|
||||||
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
|
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
|
||||||
|
|
||||||
$photo = $request->file('file');
|
|
||||||
|
|
||||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||||
if (in_array($photo->getMimeType(), $mimes) == false) {
|
if (in_array($photo->getMimeType(), $mimes) == false) {
|
||||||
abort(403, 'Invalid or unsupported mime type.');
|
abort(403, 'Invalid or unsupported mime type.');
|
||||||
|
@ -327,6 +321,10 @@ class ApiV2Controller extends Controller
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user->storage_used = (int) $updatedAccountSize;
|
||||||
|
$user->storage_used_updated_at = now();
|
||||||
|
$user->save();
|
||||||
|
|
||||||
Cache::forget($limitKey);
|
Cache::forget($limitKey);
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager();
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
|
@ -334,6 +332,7 @@ class ApiV2Controller extends Controller
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
$res['preview_url'] = $media->url().'?v='.time();
|
$res['preview_url'] = $media->url().'?v='.time();
|
||||||
$res['url'] = null;
|
$res['url'] = null;
|
||||||
|
|
||||||
return $this->json($res, 202);
|
return $this->json($res, 202);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\{Profile, Status, User};
|
use App\{Profile, Instance, Status, User};
|
||||||
use Cache;
|
use Cache;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
|
||||||
class InstanceApiController extends Controller {
|
class InstanceApiController extends Controller {
|
||||||
|
|
||||||
|
@ -40,11 +41,8 @@ class InstanceApiController extends Controller {
|
||||||
'urls' => [],
|
'urls' => [],
|
||||||
'stats' => [
|
'stats' => [
|
||||||
'user_count' => User::count(),
|
'user_count' => User::count(),
|
||||||
'status_count' => Status::whereNull('uri')->count(),
|
'status_count' => StatusService::totalLocalStatuses(),
|
||||||
'domain_count' => Profile::whereNotNull('domain')
|
'domain_count' => Instance::count()
|
||||||
->groupBy('domain')
|
|
||||||
->pluck('domain')
|
|
||||||
->count()
|
|
||||||
],
|
],
|
||||||
'thumbnail' => '',
|
'thumbnail' => '',
|
||||||
'languages' => [],
|
'languages' => [],
|
||||||
|
|
147
app/Http/Controllers/Api/V1/Admin/DomainBlocksController.php
Normal file
147
app/Http/Controllers/Api/V1/Admin/DomainBlocksController.php
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\V1\Admin;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use App\Http\Controllers\Api\ApiController;
|
||||||
|
use App\Instance;
|
||||||
|
use App\Services\InstanceService;
|
||||||
|
use App\Http\Resources\MastoApi\Admin\DomainBlockResource;
|
||||||
|
|
||||||
|
class DomainBlocksController extends ApiController {
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->middleware(['auth:api', 'api.admin', 'scope:admin:read,admin:read:domain_blocks'])->only(['index', 'show']);
|
||||||
|
$this->middleware(['auth:api', 'api.admin', 'scope:admin:write,admin:write:domain_blocks'])->only(['create', 'update', 'delete']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request) {
|
||||||
|
$this->validate($request, [
|
||||||
|
'limit' => 'sometimes|integer|max:100|min:1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$limit = $request->input('limit', 100);
|
||||||
|
|
||||||
|
$res = Instance::moderated()
|
||||||
|
->orderBy('id')
|
||||||
|
->cursorPaginate($limit)
|
||||||
|
->withQueryString();
|
||||||
|
|
||||||
|
return $this->json(DomainBlockResource::collection($res), [
|
||||||
|
'Link' => $this->linksForCollection($res)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request, $id) {
|
||||||
|
$domain_block = Instance::moderated()->find($id);
|
||||||
|
|
||||||
|
if (!$domain_block) {
|
||||||
|
return $this->json([ 'error' => 'Record not found'], [], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json(new DomainBlockResource($domain_block));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request) {
|
||||||
|
$this->validate($request, [
|
||||||
|
'domain' => 'required|string|min:1|max:120',
|
||||||
|
'severity' => [
|
||||||
|
'sometimes',
|
||||||
|
Rule::in(['noop', 'silence', 'suspend'])
|
||||||
|
],
|
||||||
|
'reject_media' => 'sometimes|required|boolean',
|
||||||
|
'reject_reports' => 'sometimes|required|boolean',
|
||||||
|
'private_comment' => 'sometimes|string|min:1|max:1000',
|
||||||
|
'public_comment' => 'sometimes|string|min:1|max:1000',
|
||||||
|
'obfuscate' => 'sometimes|required|boolean'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$domain = $request->input('domain');
|
||||||
|
$severity = $request->input('severity', 'silence');
|
||||||
|
$private_comment = $request->input('private_comment');
|
||||||
|
|
||||||
|
abort_if(!strpos($domain, '.'), 400, 'Invalid domain');
|
||||||
|
abort_if(!filter_var($domain, FILTER_VALIDATE_DOMAIN), 400, 'Invalid domain');
|
||||||
|
|
||||||
|
// This is because Pixelfed can't currently support wildcard domain blocks
|
||||||
|
// We have to find something that could plausibly be an instance
|
||||||
|
$parts = explode('.', $domain);
|
||||||
|
if ($parts[0] == '*') {
|
||||||
|
// If we only have two parts, e.g., "*", "example", then we want to fail:
|
||||||
|
abort_if(count($parts) <= 2, 400, 'Invalid domain: This API does not support wildcard domain blocks yet');
|
||||||
|
|
||||||
|
// Otherwise we convert the *.foo.example to foo.example
|
||||||
|
$domain = implode('.', array_slice($parts, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double check we definitely haven't let anything through:
|
||||||
|
abort_if(str_contains($domain, '*'), 400, 'Invalid domain');
|
||||||
|
|
||||||
|
$existing_domain_block = Instance::moderated()->whereDomain($domain)->first();
|
||||||
|
|
||||||
|
if ($existing_domain_block) {
|
||||||
|
return $this->json([
|
||||||
|
'error' => 'A domain block already exists for this domain',
|
||||||
|
'existing_domain_block' => new DomainBlockResource($existing_domain_block)
|
||||||
|
], [], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain_block = Instance::updateOrCreate(
|
||||||
|
[ 'domain' => $domain ],
|
||||||
|
[ 'banned' => $severity === 'suspend', 'unlisted' => $severity === 'silence', 'notes' => [$private_comment]]
|
||||||
|
);
|
||||||
|
|
||||||
|
InstanceService::refresh();
|
||||||
|
|
||||||
|
return $this->json(new DomainBlockResource($domain_block));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
$this->validate($request, [
|
||||||
|
'severity' => [
|
||||||
|
'sometimes',
|
||||||
|
Rule::in(['noop', 'silence', 'suspend'])
|
||||||
|
],
|
||||||
|
'reject_media' => 'sometimes|required|boolean',
|
||||||
|
'reject_reports' => 'sometimes|required|boolean',
|
||||||
|
'private_comment' => 'sometimes|string|min:1|max:1000',
|
||||||
|
'public_comment' => 'sometimes|string|min:1|max:1000',
|
||||||
|
'obfuscate' => 'sometimes|required|boolean'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$severity = $request->input('severity', 'silence');
|
||||||
|
$private_comment = $request->input('private_comment');
|
||||||
|
|
||||||
|
$domain_block = Instance::moderated()->find($id);
|
||||||
|
|
||||||
|
if (!$domain_block) {
|
||||||
|
return $this->json([ 'error' => 'Record not found'], [], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain_block->banned = $severity === 'suspend';
|
||||||
|
$domain_block->unlisted = $severity === 'silence';
|
||||||
|
$domain_block->notes = [$private_comment];
|
||||||
|
$domain_block->save();
|
||||||
|
|
||||||
|
InstanceService::refresh();
|
||||||
|
|
||||||
|
return $this->json(new DomainBlockResource($domain_block));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Request $request, $id) {
|
||||||
|
$domain_block = Instance::moderated()->find($id);
|
||||||
|
|
||||||
|
if (!$domain_block) {
|
||||||
|
return $this->json([ 'error' => 'Record not found'], [], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain_block->banned = false;
|
||||||
|
$domain_block->unlisted = false;
|
||||||
|
$domain_block->save();
|
||||||
|
|
||||||
|
InstanceService::refresh();
|
||||||
|
|
||||||
|
return $this->json(null, [], 200);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,16 +3,16 @@
|
||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Services\BouncerService;
|
||||||
|
use App\Services\EmailService;
|
||||||
use App\User;
|
use App\User;
|
||||||
use Purify;
|
|
||||||
use App\Util\Lexer\RestrictedNames;
|
use App\Util\Lexer\RestrictedNames;
|
||||||
|
use Illuminate\Auth\Events\Registered;
|
||||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Auth\Events\Registered;
|
use Purify;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Services\EmailService;
|
|
||||||
use App\Services\BouncerService;
|
|
||||||
|
|
||||||
class RegisterController extends Controller
|
class RegisterController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -56,7 +56,6 @@ class RegisterController extends Controller
|
||||||
/**
|
/**
|
||||||
* Get a validator for an incoming registration request.
|
* Get a validator for an incoming registration request.
|
||||||
*
|
*
|
||||||
* @param array $data
|
|
||||||
*
|
*
|
||||||
* @return \Illuminate\Contracts\Validation\Validator
|
* @return \Illuminate\Contracts\Validation\Validator
|
||||||
*/
|
*/
|
||||||
|
@ -98,6 +97,10 @@ class RegisterController extends Controller
|
||||||
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! preg_match('/[a-zA-Z]/', $value)) {
|
||||||
|
return $fail('Username is invalid. Must contain at least one alphabetical character.');
|
||||||
|
}
|
||||||
|
|
||||||
$restricted = RestrictedNames::get();
|
$restricted = RestrictedNames::get();
|
||||||
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||||
return $fail('Username cannot be used.');
|
return $fail('Username cannot be used.');
|
||||||
|
@ -125,7 +128,7 @@ class RegisterController extends Controller
|
||||||
if ($value !== $this->getRegisterToken()) {
|
if ($value !== $this->getRegisterToken()) {
|
||||||
return $fail('Something went wrong');
|
return $fail('Something went wrong');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
$rules = [
|
$rules = [
|
||||||
|
@ -147,7 +150,6 @@ class RegisterController extends Controller
|
||||||
/**
|
/**
|
||||||
* Create a new user instance after a valid registration.
|
* Create a new user instance after a valid registration.
|
||||||
*
|
*
|
||||||
* @param array $data
|
|
||||||
*
|
*
|
||||||
* @return \App\User
|
* @return \App\User
|
||||||
*/
|
*/
|
||||||
|
@ -163,7 +165,7 @@ class RegisterController extends Controller
|
||||||
'username' => $data['username'],
|
'username' => $data['username'],
|
||||||
'email' => $data['email'],
|
'email' => $data['email'],
|
||||||
'password' => Hash::make($data['password']),
|
'password' => Hash::make($data['password']),
|
||||||
'app_register_ip' => request()->ip()
|
'app_register_ip' => request()->ip(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,11 +183,14 @@ class RegisterController extends Controller
|
||||||
$hasLimit = config('pixelfed.enforce_max_users');
|
$hasLimit = config('pixelfed.enforce_max_users');
|
||||||
if ($hasLimit) {
|
if ($hasLimit) {
|
||||||
$limit = config('pixelfed.max_users');
|
$limit = config('pixelfed.max_users');
|
||||||
$count = User::where(function($q){ return $q->whereNull('status')->orWhereNotIn('status', ['deleted','delete']); })->count();
|
$count = User::where(function ($q) {
|
||||||
|
return $q->whereNull('status')->orWhereNotIn('status', ['deleted', 'delete']);
|
||||||
|
})->count();
|
||||||
if ($limit <= $count) {
|
if ($limit <= $count) {
|
||||||
return redirect(route('help.instance-max-users-limit'));
|
return redirect(route('help.instance-max-users-limit'));
|
||||||
}
|
}
|
||||||
abort_if($limit <= $count, 404);
|
abort_if($limit <= $count, 404);
|
||||||
|
|
||||||
return view('auth.register');
|
return view('auth.register');
|
||||||
} else {
|
} else {
|
||||||
return view('auth.register');
|
return view('auth.register');
|
||||||
|
@ -202,7 +207,6 @@ class RegisterController extends Controller
|
||||||
/**
|
/**
|
||||||
* Handle a registration request for the application.
|
* Handle a registration request for the application.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function register(Request $request)
|
public function register(Request $request)
|
||||||
|
@ -215,7 +219,9 @@ class RegisterController extends Controller
|
||||||
|
|
||||||
$hasLimit = config('pixelfed.enforce_max_users');
|
$hasLimit = config('pixelfed.enforce_max_users');
|
||||||
if ($hasLimit) {
|
if ($hasLimit) {
|
||||||
$count = User::where(function($q){ return $q->whereNull('status')->orWhereNotIn('status', ['deleted','delete']); })->count();
|
$count = User::where(function ($q) {
|
||||||
|
return $q->whereNull('status')->orWhereNotIn('status', ['deleted', 'delete']);
|
||||||
|
})->count();
|
||||||
$limit = config('pixelfed.max_users');
|
$limit = config('pixelfed.max_users');
|
||||||
|
|
||||||
if ($limit && $limit <= $count) {
|
if ($limit && $limit <= $count) {
|
||||||
|
@ -223,7 +229,6 @@ class RegisterController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$this->validator($request->all())->validate();
|
$this->validator($request->all())->validate();
|
||||||
|
|
||||||
event(new Registered($user = $this->create($request->all())));
|
event(new Registered($user = $this->create($request->all())));
|
||||||
|
|
37
app/Http/Controllers/AuthorizeInteractionController.php
Normal file
37
app/Http/Controllers/AuthorizeInteractionController.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class AuthorizeInteractionController extends Controller
|
||||||
|
{
|
||||||
|
public function get(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'uri' => 'required|url',
|
||||||
|
]);
|
||||||
|
|
||||||
|
abort_unless((bool) config_cache('federation.activitypub.enabled'), 404);
|
||||||
|
|
||||||
|
$uri = Helpers::validateUrl($request->input('uri'), true);
|
||||||
|
abort_unless($uri, 404);
|
||||||
|
|
||||||
|
if (! $request->user()) {
|
||||||
|
return redirect('/login?next='.urlencode($uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = Helpers::statusFetch($uri);
|
||||||
|
if ($status && isset($status['id'])) {
|
||||||
|
return redirect('/i/web/post/'.$status['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile = Helpers::profileFetch($uri);
|
||||||
|
if ($profile && isset($profile['id'])) {
|
||||||
|
return redirect('/i/web/profile/'.$profile['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('/i/web');
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,12 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Bookmark;
|
use App\Bookmark;
|
||||||
use App\Status;
|
use App\Services\AccountService;
|
||||||
use Auth;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Services\BookmarkService;
|
use App\Services\BookmarkService;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use App\Services\UserRoleService;
|
use App\Services\UserRoleService;
|
||||||
|
use App\Status;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class BookmarkController extends Controller
|
class BookmarkController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,8 @@ class BookmarkController extends Controller
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$status = Status::findOrFail($request->input('item'));
|
$status = Status::findOrFail($request->input('item'));
|
||||||
|
$account = AccountService::get($status->profile_id);
|
||||||
|
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark or unbookmark a post from an account that has migrated');
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
||||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||||
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
abort_if(! in_array($status->scope, ['public', 'unlisted', 'private']), 404);
|
||||||
|
|
|
@ -2,25 +2,15 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use App\Collection;
|
||||||
use Auth;
|
use App\CollectionItem;
|
||||||
use App\{
|
|
||||||
Collection,
|
|
||||||
CollectionItem,
|
|
||||||
Profile,
|
|
||||||
Status
|
|
||||||
};
|
|
||||||
use League\Fractal;
|
|
||||||
use App\Transformer\Api\{
|
|
||||||
AccountTransformer,
|
|
||||||
StatusTransformer,
|
|
||||||
};
|
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\CollectionService;
|
use App\Services\CollectionService;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
use App\Status;
|
||||||
|
use Auth;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class CollectionController extends Controller
|
class CollectionController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -31,10 +21,11 @@ class CollectionController extends Controller
|
||||||
|
|
||||||
$collection = Collection::firstOrCreate([
|
$collection = Collection::firstOrCreate([
|
||||||
'profile_id' => $profile->id,
|
'profile_id' => $profile->id,
|
||||||
'published_at' => null
|
'published_at' => null,
|
||||||
]);
|
]);
|
||||||
$collection->visibility = 'draft';
|
$collection->visibility = 'draft';
|
||||||
$collection->save();
|
$collection->save();
|
||||||
|
|
||||||
return view('collection.create', compact('collection'));
|
return view('collection.create', compact('collection'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,12 +43,14 @@ class CollectionController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('collection.show', compact('collection'));
|
return view('collection.show', compact('collection'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
abort_if(! Auth::check(), 403);
|
abort_if(! Auth::check(), 403);
|
||||||
|
|
||||||
return $request->all();
|
return $request->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +60,7 @@ class CollectionController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'title' => 'nullable|max:50',
|
'title' => 'nullable|max:50',
|
||||||
'description' => 'nullable|max:500',
|
'description' => 'nullable|max:500',
|
||||||
'visibility' => 'nullable|string|in:public,private,draft'
|
'visibility' => 'nullable|string|in:public,private,draft',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
|
@ -78,6 +71,7 @@ class CollectionController extends Controller
|
||||||
$collection->save();
|
$collection->save();
|
||||||
|
|
||||||
CollectionService::deleteCollection($id);
|
CollectionService::deleteCollection($id);
|
||||||
|
|
||||||
return CollectionService::setCollection($collection->id, $collection);
|
return CollectionService::setCollection($collection->id, $collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +81,7 @@ class CollectionController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'title' => 'nullable|max:50',
|
'title' => 'nullable|max:50',
|
||||||
'description' => 'nullable|max:500',
|
'description' => 'nullable|max:500',
|
||||||
'visibility' => 'required|alpha|in:public,private,draft'
|
'visibility' => 'required|alpha|in:public,private,draft',
|
||||||
]);
|
]);
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
$collection = Collection::whereProfileId($profile->id)->findOrFail($id);
|
$collection = Collection::whereProfileId($profile->id)->findOrFail($id);
|
||||||
|
@ -99,6 +93,7 @@ class CollectionController extends Controller
|
||||||
$collection->visibility = $request->input('visibility');
|
$collection->visibility = $request->input('visibility');
|
||||||
$collection->published_at = now();
|
$collection->published_at = now();
|
||||||
$collection->save();
|
$collection->save();
|
||||||
|
|
||||||
return CollectionService::setCollection($collection->id, $collection);
|
return CollectionService::setCollection($collection->id, $collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +121,7 @@ class CollectionController extends Controller
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'collection_id' => 'required|int|min:1|exists:collections,id',
|
'collection_id' => 'required|int|min:1|exists:collections,id',
|
||||||
'post_id' => 'required|int|min:1'
|
'post_id' => 'required|int|min:1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$profileId = $request->user()->profile_id;
|
$profileId = $request->user()->profile_id;
|
||||||
|
@ -161,7 +156,7 @@ class CollectionController extends Controller
|
||||||
$item = CollectionItem::firstOrCreate([
|
$item = CollectionItem::firstOrCreate([
|
||||||
'collection_id' => $collection->id,
|
'collection_id' => $collection->id,
|
||||||
'object_type' => 'App\Status',
|
'object_type' => 'App\Status',
|
||||||
'object_id' => $status->id
|
'object_id' => $status->id,
|
||||||
], [
|
], [
|
||||||
'order' => $count,
|
'order' => $count,
|
||||||
]);
|
]);
|
||||||
|
@ -280,7 +275,7 @@ class CollectionController extends Controller
|
||||||
abort_if(! $request->user(), 403);
|
abort_if(! $request->user(), 403);
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'collection_id' => 'required|int|min:1|exists:collections,id',
|
'collection_id' => 'required|int|min:1|exists:collections,id',
|
||||||
'post_id' => 'required|int|min:1'
|
'post_id' => 'required|int|min:1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$profileId = $request->user()->profile_id;
|
$profileId = $request->user()->profile_id;
|
||||||
|
@ -319,4 +314,31 @@ class CollectionController extends Controller
|
||||||
|
|
||||||
return 200;
|
return 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSelfCollections(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$user = $request->user();
|
||||||
|
$pid = $user->profile_id;
|
||||||
|
|
||||||
|
$profile = AccountService::get($pid, true);
|
||||||
|
if (! $profile || ! isset($profile['id'])) {
|
||||||
|
return response()->json([], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collection::whereProfileId($pid)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->paginate(9)
|
||||||
|
->map(function ($collection) {
|
||||||
|
$c = CollectionService::getCollection($collection->id);
|
||||||
|
$c['items'] = collect(CollectionService::getItems($collection->id))
|
||||||
|
->map(function ($id) {
|
||||||
|
return StatusService::get($id, false);
|
||||||
|
})->filter()->values();
|
||||||
|
|
||||||
|
return $c;
|
||||||
|
})
|
||||||
|
->filter()
|
||||||
|
->values();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ use App\Services\MediaStorageService;
|
||||||
use App\Services\MediaTagService;
|
use App\Services\MediaTagService;
|
||||||
use App\Services\SnowflakeService;
|
use App\Services\SnowflakeService;
|
||||||
use App\Services\UserRoleService;
|
use App\Services\UserRoleService;
|
||||||
|
use App\Services\UserStorageService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\Transformer\Api\MediaTransformer;
|
use App\Transformer\Api\MediaTransformer;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
|
@ -70,7 +71,7 @@ class ComposeController extends Controller
|
||||||
'filter_class' => 'nullable|alpha_dash|max:24',
|
'filter_class' => 'nullable|alpha_dash|max:24',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = Auth::user();
|
$user = $request->user();
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
|
||||||
|
|
||||||
|
@ -84,21 +85,22 @@ class ComposeController extends Controller
|
||||||
|
|
||||||
abort_if($limitReached == true, 429);
|
abort_if($limitReached == true, 429);
|
||||||
|
|
||||||
if (config_cache('pixelfed.enforce_account_limit') == true) {
|
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
|
||||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
|
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
|
||||||
return Media::whereUserId($user->id)->sum('size') / 1000;
|
$accountSize = UserStorageService::get($user->id);
|
||||||
});
|
abort_if($accountSize === -1, 403, 'Invalid request.');
|
||||||
|
$photo = $request->file('file');
|
||||||
|
$fileSize = $photo->getSize();
|
||||||
|
$sizeInKbs = (int) ceil($fileSize / 1000);
|
||||||
|
$updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
|
||||||
|
|
||||||
|
if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
|
||||||
$limit = (int) config_cache('pixelfed.max_account_size');
|
$limit = (int) config_cache('pixelfed.max_account_size');
|
||||||
if ($size >= $limit) {
|
if ($updatedAccountSize >= $limit) {
|
||||||
abort(403, 'Account size limit reached.');
|
abort(403, 'Account size limit reached.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null;
|
|
||||||
$filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null;
|
|
||||||
|
|
||||||
$photo = $request->file('file');
|
|
||||||
|
|
||||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||||
|
|
||||||
abort_if(in_array($photo->getMimeType(), $mimes) == false, 400, 'Invalid media format');
|
abort_if(in_array($photo->getMimeType(), $mimes) == false, 400, 'Invalid media format');
|
||||||
|
@ -117,10 +119,11 @@ class ComposeController extends Controller
|
||||||
$media->media_path = $path;
|
$media->media_path = $path;
|
||||||
$media->original_sha256 = $hash;
|
$media->original_sha256 = $hash;
|
||||||
$media->size = $photo->getSize();
|
$media->size = $photo->getSize();
|
||||||
|
$media->caption = "";
|
||||||
$media->mime = $mime;
|
$media->mime = $mime;
|
||||||
$media->filter_class = $filterClass;
|
$media->filter_class = $filterClass;
|
||||||
$media->filter_name = $filterName;
|
$media->filter_name = $filterName;
|
||||||
$media->version = 3;
|
$media->version = '3';
|
||||||
$media->save();
|
$media->save();
|
||||||
|
|
||||||
$preview_url = $media->url().'?v='.time();
|
$preview_url = $media->url().'?v='.time();
|
||||||
|
@ -143,6 +146,10 @@ class ComposeController extends Controller
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user->storage_used = (int) $updatedAccountSize;
|
||||||
|
$user->storage_used_updated_at = now();
|
||||||
|
$user->save();
|
||||||
|
|
||||||
Cache::forget($limitKey);
|
Cache::forget($limitKey);
|
||||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
@ -198,6 +205,7 @@ class ComposeController extends Controller
|
||||||
];
|
];
|
||||||
ImageOptimize::dispatch($media)->onQueue('mmo');
|
ImageOptimize::dispatch($media)->onQueue('mmo');
|
||||||
Cache::forget($limitKey);
|
Cache::forget($limitKey);
|
||||||
|
UserStorageService::recalculateUpdateStorageUsed($request->user()->id);
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
@ -218,6 +226,8 @@ class ComposeController extends Controller
|
||||||
|
|
||||||
MediaStorageService::delete($media, true);
|
MediaStorageService::delete($media, true);
|
||||||
|
|
||||||
|
UserStorageService::recalculateUpdateStorageUsed($request->user()->id);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'msg' => 'Successfully deleted',
|
'msg' => 'Successfully deleted',
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
|
@ -494,17 +504,17 @@ class ComposeController extends Controller
|
||||||
|
|
||||||
$limitKey = 'compose:rate-limit:store:'.$user->id;
|
$limitKey = 'compose:rate-limit:store:'.$user->id;
|
||||||
$limitTtl = now()->addMinutes(15);
|
$limitTtl = now()->addMinutes(15);
|
||||||
$limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
|
// $limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
|
||||||
$dailyLimit = Status::whereProfileId($user->profile_id)
|
// $dailyLimit = Status::whereProfileId($user->profile_id)
|
||||||
->whereNull('in_reply_to_id')
|
// ->whereNull('in_reply_to_id')
|
||||||
->whereNull('reblog_of_id')
|
// ->whereNull('reblog_of_id')
|
||||||
->where('created_at', '>', now()->subDays(1))
|
// ->where('created_at', '>', now()->subDays(1))
|
||||||
->count();
|
// ->count();
|
||||||
|
|
||||||
return $dailyLimit >= 1000;
|
// return $dailyLimit >= 1000;
|
||||||
});
|
// });
|
||||||
|
|
||||||
abort_if($limitReached == true, 429);
|
// abort_if($limitReached == true, 429);
|
||||||
|
|
||||||
$license = in_array($request->input('license'), License::keys()) ? $request->input('license') : null;
|
$license = in_array($request->input('license'), License::keys()) ? $request->input('license') : null;
|
||||||
|
|
||||||
|
@ -626,7 +636,6 @@ class ComposeController extends Controller
|
||||||
Cache::forget('_api:statuses:recent_9:'.$profile->id);
|
Cache::forget('_api:statuses:recent_9:'.$profile->id);
|
||||||
Cache::forget('profile:status_count:'.$profile->id);
|
Cache::forget('profile:status_count:'.$profile->id);
|
||||||
Cache::forget('status:transformer:media:attachments:'.$status->id);
|
Cache::forget('status:transformer:media:attachments:'.$status->id);
|
||||||
Cache::forget($user->storageUsedKey());
|
|
||||||
Cache::forget('profile:embed:'.$status->profile_id);
|
Cache::forget('profile:embed:'.$status->profile_id);
|
||||||
Cache::forget($limitKey);
|
Cache::forget($limitKey);
|
||||||
|
|
||||||
|
|
|
@ -50,4 +50,15 @@ class ContactController extends Controller
|
||||||
|
|
||||||
return redirect()->back()->with('status', 'Success - Your message has been sent to admins.');
|
return redirect()->back()->with('status', 'Success - Your message has been sent to admins.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function showAdminResponse(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$uid = $request->user()->id;
|
||||||
|
$contact = Contact::whereUserId($uid)
|
||||||
|
->whereNotNull('response')
|
||||||
|
->whereNotNull('responded_at')
|
||||||
|
->findOrFail($id);
|
||||||
|
return view('site.contact.admin-response', compact('contact'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,31 +2,28 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Auth, Cache;
|
use App\DirectMessage;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\{
|
|
||||||
DirectMessage,
|
|
||||||
Media,
|
|
||||||
Notification,
|
|
||||||
Profile,
|
|
||||||
Status,
|
|
||||||
User,
|
|
||||||
UserFilter,
|
|
||||||
UserSetting
|
|
||||||
};
|
|
||||||
use App\Services\MediaPathService;
|
|
||||||
use App\Services\MediaBlocklistService;
|
|
||||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
|
||||||
use App\Jobs\StatusPipeline\StatusDelete;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use App\Util\ActivityPub\Helpers;
|
|
||||||
use App\Services\AccountService;
|
|
||||||
use App\Services\StatusService;
|
|
||||||
use App\Services\WebfingerService;
|
|
||||||
use App\Models\Conversation;
|
|
||||||
use App\Jobs\DirectPipeline\DirectDeletePipeline;
|
use App\Jobs\DirectPipeline\DirectDeletePipeline;
|
||||||
use App\Jobs\DirectPipeline\DirectDeliverPipeline;
|
use App\Jobs\DirectPipeline\DirectDeliverPipeline;
|
||||||
|
use App\Jobs\StatusPipeline\StatusDelete;
|
||||||
|
use App\Media;
|
||||||
|
use App\Models\Conversation;
|
||||||
|
use App\Notification;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\MediaBlocklistService;
|
||||||
|
use App\Services\MediaPathService;
|
||||||
|
use App\Services\MediaService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Services\UserFilterService;
|
||||||
use App\Services\UserRoleService;
|
use App\Services\UserRoleService;
|
||||||
|
use App\Services\UserStorageService;
|
||||||
|
use App\Services\WebfingerService;
|
||||||
|
use App\Status;
|
||||||
|
use App\UserFilter;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class DirectMessageController extends Controller
|
class DirectMessageController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -39,7 +36,7 @@ class DirectMessageController extends Controller
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'a' => 'nullable|string|in:inbox,sent,filtered',
|
'a' => 'nullable|string|in:inbox,sent,filtered',
|
||||||
'page' => 'nullable|integer|min:1|max:99'
|
'page' => 'nullable|integer|min:1|max:99',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
@ -76,7 +73,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->author->domain,
|
'domain' => $r->author->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
] : [
|
] : [
|
||||||
'id' => (string) $r->to_id,
|
'id' => (string) $r->to_id,
|
||||||
'name' => $r->recipient->name,
|
'name' => $r->recipient->name,
|
||||||
|
@ -87,7 +84,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->recipient->domain,
|
'domain' => $r->recipient->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
];
|
];
|
||||||
})->values();
|
})->values();
|
||||||
}
|
}
|
||||||
|
@ -116,7 +113,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->author->domain,
|
'domain' => $r->author->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
] : [
|
] : [
|
||||||
'id' => (string) $r->to_id,
|
'id' => (string) $r->to_id,
|
||||||
'name' => $r->recipient->name,
|
'name' => $r->recipient->name,
|
||||||
|
@ -127,7 +124,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->recipient->domain,
|
'domain' => $r->recipient->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -157,7 +154,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->author->domain,
|
'domain' => $r->author->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
] : [
|
] : [
|
||||||
'id' => (string) $r->to_id,
|
'id' => (string) $r->to_id,
|
||||||
'name' => $r->recipient->name,
|
'name' => $r->recipient->name,
|
||||||
|
@ -168,7 +165,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->recipient->domain,
|
'domain' => $r->recipient->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -198,7 +195,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->author->domain,
|
'domain' => $r->author->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
] : [
|
] : [
|
||||||
'id' => (string) $r->to_id,
|
'id' => (string) $r->to_id,
|
||||||
'name' => $r->recipient->name,
|
'name' => $r->recipient->name,
|
||||||
|
@ -209,7 +206,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->recipient->domain,
|
'domain' => $r->recipient->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -238,7 +235,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->author->domain,
|
'domain' => $r->author->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
] : [
|
] : [
|
||||||
'id' => (string) $r->to_id,
|
'id' => (string) $r->to_id,
|
||||||
'name' => $r->recipient->name,
|
'name' => $r->recipient->name,
|
||||||
|
@ -249,7 +246,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->recipient->domain,
|
'domain' => $r->recipient->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -279,7 +276,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->author->domain,
|
'domain' => $r->author->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
] : [
|
] : [
|
||||||
'id' => (string) $r->to_id,
|
'id' => (string) $r->to_id,
|
||||||
'name' => $r->recipient->name,
|
'name' => $r->recipient->name,
|
||||||
|
@ -290,7 +287,7 @@ class DirectMessageController extends Controller
|
||||||
'domain' => $r->recipient->domain,
|
'domain' => $r->recipient->domain,
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => $r->status->caption,
|
'lastMessage' => $r->status->caption,
|
||||||
'messages' => []
|
'messages' => [],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -304,11 +301,12 @@ class DirectMessageController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'to_id' => 'required',
|
'to_id' => 'required',
|
||||||
'message' => 'required|string|min:1|max:500',
|
'message' => 'required|string|min:1|max:500',
|
||||||
'type' => 'required|in:text,emoji'
|
'type' => 'required|in:text,emoji',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
|
||||||
|
abort_if($user->created_at->gt(now()->subHours(72)), 400, 'You need to wait a bit before you can DM another account');
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
$recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id'));
|
$recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id'));
|
||||||
|
|
||||||
|
@ -345,13 +343,13 @@ class DirectMessageController extends Controller
|
||||||
Conversation::updateOrInsert(
|
Conversation::updateOrInsert(
|
||||||
[
|
[
|
||||||
'to_id' => $recipient->id,
|
'to_id' => $recipient->id,
|
||||||
'from_id' => $profile->id
|
'from_id' => $profile->id,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => $dm->type,
|
'type' => $dm->type,
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'dm_id' => $dm->id,
|
'dm_id' => $dm->id,
|
||||||
'is_hidden' => $hidden
|
'is_hidden' => $hidden,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -361,7 +359,7 @@ class DirectMessageController extends Controller
|
||||||
$dm->meta = [
|
$dm->meta = [
|
||||||
'domain' => parse_url($msg, PHP_URL_HOST),
|
'domain' => parse_url($msg, PHP_URL_HOST),
|
||||||
'local' => parse_url($msg, PHP_URL_HOST) ==
|
'local' => parse_url($msg, PHP_URL_HOST) ==
|
||||||
parse_url(config('app.url'), PHP_URL_HOST)
|
parse_url(config('app.url'), PHP_URL_HOST),
|
||||||
];
|
];
|
||||||
$dm->save();
|
$dm->save();
|
||||||
}
|
}
|
||||||
|
@ -397,7 +395,7 @@ class DirectMessageController extends Controller
|
||||||
'media' => null,
|
'media' => null,
|
||||||
'timeAgo' => $dm->created_at->diffForHumans(null, null, true),
|
'timeAgo' => $dm->created_at->diffForHumans(null, null, true),
|
||||||
'seen' => $dm->read_at != null,
|
'seen' => $dm->read_at != null,
|
||||||
'meta' => $dm->meta
|
'meta' => $dm->meta,
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
|
@ -406,7 +404,7 @@ class DirectMessageController extends Controller
|
||||||
public function thread(Request $request)
|
public function thread(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'pid' => 'required'
|
'pid' => 'required',
|
||||||
]);
|
]);
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
|
||||||
|
@ -422,7 +420,7 @@ class DirectMessageController extends Controller
|
||||||
$res = DirectMessage::select('*')
|
$res = DirectMessage::select('*')
|
||||||
->where('id', '>', $min_id)
|
->where('id', '>', $min_id)
|
||||||
->where(function ($q) use ($pid, $uid) {
|
->where(function ($q) use ($pid, $uid) {
|
||||||
return $q->where([['from_id',$pid],['to_id',$uid]
|
return $q->where([['from_id', $pid], ['to_id', $uid],
|
||||||
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
||||||
})
|
})
|
||||||
->latest()
|
->latest()
|
||||||
|
@ -432,7 +430,7 @@ class DirectMessageController extends Controller
|
||||||
$res = DirectMessage::select('*')
|
$res = DirectMessage::select('*')
|
||||||
->where('id', '<', $max_id)
|
->where('id', '<', $max_id)
|
||||||
->where(function ($q) use ($pid, $uid) {
|
->where(function ($q) use ($pid, $uid) {
|
||||||
return $q->where([['from_id',$pid],['to_id',$uid]
|
return $q->where([['from_id', $pid], ['to_id', $uid],
|
||||||
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
||||||
})
|
})
|
||||||
->latest()
|
->latest()
|
||||||
|
@ -440,7 +438,7 @@ class DirectMessageController extends Controller
|
||||||
->get();
|
->get();
|
||||||
} else {
|
} else {
|
||||||
$res = DirectMessage::where(function ($q) use ($pid, $uid) {
|
$res = DirectMessage::where(function ($q) use ($pid, $uid) {
|
||||||
return $q->where([['from_id',$pid],['to_id',$uid]
|
return $q->where([['from_id', $pid], ['to_id', $uid],
|
||||||
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
||||||
})
|
})
|
||||||
->latest()
|
->latest()
|
||||||
|
@ -459,30 +457,32 @@ class DirectMessageController extends Controller
|
||||||
'type' => $s->type,
|
'type' => $s->type,
|
||||||
'text' => $s->status->caption,
|
'text' => $s->status->caption,
|
||||||
'media' => $s->status->firstMedia() ? $s->status->firstMedia()->url() : null,
|
'media' => $s->status->firstMedia() ? $s->status->firstMedia()->url() : null,
|
||||||
|
'carousel' => MediaService::get($s->status_id),
|
||||||
|
'created_at' => $s->created_at->format('c'),
|
||||||
'timeAgo' => $s->created_at->diffForHumans(null, null, true),
|
'timeAgo' => $s->created_at->diffForHumans(null, null, true),
|
||||||
'seen' => $s->read_at != null,
|
'seen' => $s->read_at != null,
|
||||||
'reportId' => (string) $s->status_id,
|
'reportId' => (string) $s->status_id,
|
||||||
'meta' => json_decode($s->meta,true)
|
'meta' => json_decode($s->meta, true),
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
|
$filters = UserFilterService::mutes($uid);
|
||||||
|
|
||||||
$w = [
|
$w = [
|
||||||
'id' => (string) $r->id,
|
'id' => (string) $r->id,
|
||||||
'name' => $r->name,
|
'name' => $r->name,
|
||||||
'username' => $r->username,
|
'username' => $r->username,
|
||||||
'avatar' => $r->avatarUrl(),
|
'avatar' => $r->avatarUrl(),
|
||||||
'url' => $r->url(),
|
'url' => $r->url(),
|
||||||
'muted' => UserFilter::whereUserId($uid)
|
'muted' => in_array($r->id, $filters),
|
||||||
->whereFilterableId($r->id)
|
|
||||||
->whereFilterableType('App\Profile')
|
|
||||||
->whereFilterType('dm.mute')
|
|
||||||
->first() ? true : false,
|
|
||||||
'isLocal' => (bool) ! $r->domain,
|
'isLocal' => (bool) ! $r->domain,
|
||||||
'domain' => $r->domain,
|
'domain' => $r->domain,
|
||||||
|
'created_at' => $r->created_at->format('c'),
|
||||||
|
'updated_at' => $r->updated_at->format('c'),
|
||||||
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
'timeAgo' => $r->created_at->diffForHumans(null, true, true),
|
||||||
'lastMessage' => '',
|
'lastMessage' => '',
|
||||||
'messages' => $res
|
'messages' => $res,
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($w, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
return response()->json($w, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
|
@ -491,7 +491,7 @@ class DirectMessageController extends Controller
|
||||||
public function delete(Request $request)
|
public function delete(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required'
|
'id' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$sid = $request->input('id');
|
$sid = $request->input('id');
|
||||||
|
@ -529,7 +529,7 @@ class DirectMessageController extends Controller
|
||||||
'updated_at' => $latest->updated_at,
|
'updated_at' => $latest->updated_at,
|
||||||
'status_id' => $latest->status_id,
|
'status_id' => $latest->status_id,
|
||||||
'type' => $latest->type,
|
'type' => $latest->type,
|
||||||
'is_hidden' => false
|
'is_hidden' => false,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Conversation::where(['to_id' => $dm->to_id, 'from_id' => $dm->from_id])
|
Conversation::where(['to_id' => $dm->to_id, 'from_id' => $dm->from_id])
|
||||||
|
@ -537,19 +537,19 @@ class DirectMessageController extends Controller
|
||||||
'updated_at' => $latest->updated_at,
|
'updated_at' => $latest->updated_at,
|
||||||
'status_id' => $latest->status_id,
|
'status_id' => $latest->status_id,
|
||||||
'type' => $latest->type,
|
'type' => $latest->type,
|
||||||
'is_hidden' => false
|
'is_hidden' => false,
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
Conversation::where([
|
Conversation::where([
|
||||||
'status_id' => $sid,
|
'status_id' => $sid,
|
||||||
'to_id' => $dm->from_id,
|
'to_id' => $dm->from_id,
|
||||||
'from_id' => $dm->to_id
|
'from_id' => $dm->to_id,
|
||||||
])->delete();
|
])->delete();
|
||||||
|
|
||||||
Conversation::where([
|
Conversation::where([
|
||||||
'status_id' => $sid,
|
'status_id' => $sid,
|
||||||
'from_id' => $dm->from_id,
|
'from_id' => $dm->from_id,
|
||||||
'to_id' => $dm->to_id
|
'to_id' => $dm->to_id,
|
||||||
])->delete();
|
])->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -557,6 +557,7 @@ class DirectMessageController extends Controller
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
|
|
||||||
$status->forceDeleteQuietly();
|
$status->forceDeleteQuietly();
|
||||||
|
|
||||||
return [200];
|
return [200];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,6 +569,7 @@ class DirectMessageController extends Controller
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$dm = DirectMessage::whereStatusId($id)->firstOrFail();
|
$dm = DirectMessage::whereStatusId($id)->firstOrFail();
|
||||||
abort_if($pid !== $dm->to_id && $pid !== $dm->from_id, 404);
|
abort_if($pid !== $dm->to_id && $pid !== $dm->from_id, 404);
|
||||||
|
|
||||||
return response()->json($dm, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
return response()->json($dm, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,7 +583,7 @@ class DirectMessageController extends Controller
|
||||||
'max:'.config_cache('pixelfed.max_photo_size'),
|
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
'to_id' => 'required'
|
'to_id' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
@ -600,16 +602,19 @@ class DirectMessageController extends Controller
|
||||||
$hidden = false;
|
$hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config_cache('pixelfed.enforce_account_limit') == true) {
|
$accountSize = UserStorageService::get($user->id);
|
||||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
|
abort_if($accountSize === -1, 403, 'Invalid request.');
|
||||||
return Media::whereUserId($user->id)->sum('size') / 1000;
|
$photo = $request->file('file');
|
||||||
});
|
$fileSize = $photo->getSize();
|
||||||
|
$sizeInKbs = (int) ceil($fileSize / 1000);
|
||||||
|
$updatedAccountSize = (int) $accountSize + (int) $sizeInKbs;
|
||||||
|
|
||||||
|
if ((bool) config_cache('pixelfed.enforce_account_limit') == true) {
|
||||||
$limit = (int) config_cache('pixelfed.max_account_size');
|
$limit = (int) config_cache('pixelfed.max_account_size');
|
||||||
if ($size >= $limit) {
|
if ($updatedAccountSize >= $limit) {
|
||||||
abort(403, 'Account size limit reached.');
|
abort(403, 'Account size limit reached.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$photo = $request->file('file');
|
|
||||||
|
|
||||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||||
if (in_array($photo->getMimeType(), $mimes) == false) {
|
if (in_array($photo->getMimeType(), $mimes) == false) {
|
||||||
|
@ -655,16 +660,20 @@ class DirectMessageController extends Controller
|
||||||
Conversation::updateOrInsert(
|
Conversation::updateOrInsert(
|
||||||
[
|
[
|
||||||
'to_id' => $recipient->id,
|
'to_id' => $recipient->id,
|
||||||
'from_id' => $profile->id
|
'from_id' => $profile->id,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => $dm->type,
|
'type' => $dm->type,
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'dm_id' => $dm->id,
|
'dm_id' => $dm->id,
|
||||||
'is_hidden' => $hidden
|
'is_hidden' => $hidden,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$user->storage_used = (int) $updatedAccountSize;
|
||||||
|
$user->storage_used_updated_at = now();
|
||||||
|
$user->save();
|
||||||
|
|
||||||
if ($recipient->domain) {
|
if ($recipient->domain) {
|
||||||
$this->remoteDeliver($dm);
|
$this->remoteDeliver($dm);
|
||||||
}
|
}
|
||||||
|
@ -673,7 +682,7 @@ class DirectMessageController extends Controller
|
||||||
'id' => $dm->id,
|
'id' => $dm->id,
|
||||||
'reportId' => (string) $dm->status_id,
|
'reportId' => (string) $dm->status_id,
|
||||||
'type' => $dm->type,
|
'type' => $dm->type,
|
||||||
'url' => $media->url()
|
'url' => $media->url(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -725,13 +734,14 @@ class DirectMessageController extends Controller
|
||||||
->get()
|
->get()
|
||||||
->map(function ($r) {
|
->map(function ($r) {
|
||||||
$acct = AccountService::get($r->id);
|
$acct = AccountService::get($r->id);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'local' => (bool) ! $r->domain,
|
'local' => (bool) ! $r->domain,
|
||||||
'id' => (string) $r->id,
|
'id' => (string) $r->id,
|
||||||
'name' => $r->username,
|
'name' => $r->username,
|
||||||
'privacy' => true,
|
'privacy' => true,
|
||||||
'avatar' => $r->avatarUrl(),
|
'avatar' => $r->avatarUrl(),
|
||||||
'account' => $acct
|
'account' => $acct,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -742,7 +752,7 @@ class DirectMessageController extends Controller
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'pid' => 'required',
|
'pid' => 'required',
|
||||||
'sid' => 'required'
|
'sid' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$pid = $request->input('pid');
|
$pid = $request->input('pid');
|
||||||
|
@ -767,7 +777,7 @@ class DirectMessageController extends Controller
|
||||||
public function mute(Request $request)
|
public function mute(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required'
|
'id' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
@ -780,7 +790,7 @@ class DirectMessageController extends Controller
|
||||||
'user_id' => $pid,
|
'user_id' => $pid,
|
||||||
'filterable_id' => $fid,
|
'filterable_id' => $fid,
|
||||||
'filterable_type' => 'App\Profile',
|
'filterable_type' => 'App\Profile',
|
||||||
'filter_type' => 'dm.mute'
|
'filter_type' => 'dm.mute',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -790,7 +800,7 @@ class DirectMessageController extends Controller
|
||||||
public function unmute(Request $request)
|
public function unmute(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required'
|
'id' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
@ -820,7 +830,7 @@ class DirectMessageController extends Controller
|
||||||
'type' => 'Mention',
|
'type' => 'Mention',
|
||||||
'href' => $dm->recipient->permalink(),
|
'href' => $dm->recipient->permalink(),
|
||||||
'name' => $dm->recipient->emailUrl(),
|
'name' => $dm->recipient->emailUrl(),
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$body = [
|
$body = [
|
||||||
|
@ -855,7 +865,7 @@ class DirectMessageController extends Controller
|
||||||
];
|
];
|
||||||
})->toArray(),
|
})->toArray(),
|
||||||
'tag' => $tags,
|
'tag' => $tags,
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
DirectDeliverPipeline::dispatch($profile, $url, $body)->onQueue('high');
|
DirectDeliverPipeline::dispatch($profile, $url, $body)->onQueue('high');
|
||||||
|
@ -872,14 +882,14 @@ class DirectMessageController extends Controller
|
||||||
],
|
],
|
||||||
'id' => $dm->status->permalink('#delete'),
|
'id' => $dm->status->permalink('#delete'),
|
||||||
'to' => [
|
'to' => [
|
||||||
'https://www.w3.org/ns/activitystreams#Public'
|
'https://www.w3.org/ns/activitystreams#Public',
|
||||||
],
|
],
|
||||||
'type' => 'Delete',
|
'type' => 'Delete',
|
||||||
'actor' => $dm->status->profile->permalink(),
|
'actor' => $dm->status->profile->permalink(),
|
||||||
'object' => [
|
'object' => [
|
||||||
'id' => $dm->status->url(),
|
'id' => $dm->status->url(),
|
||||||
'type' => 'Tombstone'
|
'type' => 'Tombstone',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
DirectDeletePipeline::dispatch($profile, $url, $body)->onQueue('high');
|
DirectDeletePipeline::dispatch($profile, $url, $body)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use App\Services\BookmarkService;
|
||||||
use App\Services\ConfigCacheService;
|
use App\Services\ConfigCacheService;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use App\Services\HashtagService;
|
use App\Services\HashtagService;
|
||||||
|
use App\Services\Internal\BeagleService;
|
||||||
use App\Services\LikeService;
|
use App\Services\LikeService;
|
||||||
use App\Services\ReblogService;
|
use App\Services\ReblogService;
|
||||||
use App\Services\SnowflakeService;
|
use App\Services\SnowflakeService;
|
||||||
|
@ -420,4 +421,11 @@ class DiscoverController extends Controller
|
||||||
|
|
||||||
return response()->json($ids, 200, [], JSON_UNESCAPED_SLASHES);
|
return response()->json($ids, 200, [], JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function discoverNetworkTrending(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
|
||||||
|
return BeagleService::getDiscoverPosts();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,8 +45,11 @@ class FederationController extends Controller
|
||||||
$resource = $request->input('resource');
|
$resource = $request->input('resource');
|
||||||
$domain = config('pixelfed.domain.app');
|
$domain = config('pixelfed.domain.app');
|
||||||
|
|
||||||
if (config('federation.activitypub.sharedInbox') &&
|
// Instance Actor
|
||||||
$resource == 'acct:'.$domain.'@'.$domain) {
|
if (
|
||||||
|
config('federation.activitypub.sharedInbox') &&
|
||||||
|
$resource == 'acct:'.$domain.'@'.$domain
|
||||||
|
) {
|
||||||
$res = [
|
$res = [
|
||||||
'subject' => 'acct:'.$domain.'@'.$domain,
|
'subject' => 'acct:'.$domain.'@'.$domain,
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
|
@ -63,11 +66,43 @@ class FederationController extends Controller
|
||||||
'type' => 'application/activity+json',
|
'type' => 'application/activity+json',
|
||||||
'href' => 'https://'.$domain.'/i/actor',
|
'href' => 'https://'.$domain.'/i/actor',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'rel' => 'http://ostatus.org/schema/1.0/subscribe',
|
||||||
|
'template' => 'https://'.$domain.'/authorize_interaction?uri={uri}',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($resource, 'https://')) {
|
||||||
|
if (str_starts_with($resource, 'https://'.$domain.'/users/')) {
|
||||||
|
$username = str_replace('https://'.$domain.'/users/', '', $resource);
|
||||||
|
if (strlen($username) > 15) {
|
||||||
|
return response('', 400);
|
||||||
|
}
|
||||||
|
$stripped = str_replace(['_', '.', '-'], '', $username);
|
||||||
|
if (! ctype_alnum($stripped)) {
|
||||||
|
return response('', 400);
|
||||||
|
}
|
||||||
|
$key = 'federation:webfinger:sha256:url-username:'.$username;
|
||||||
|
if ($cached = Cache::get($key)) {
|
||||||
|
return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
$profile = Profile::whereUsername($username)->first();
|
||||||
|
if (! $profile || $profile->status !== null || $profile->domain) {
|
||||||
|
return response('', 400);
|
||||||
|
}
|
||||||
|
$webfinger = (new Webfinger($profile))->generate();
|
||||||
|
Cache::put($key, $webfinger, 1209600);
|
||||||
|
|
||||||
|
return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES)
|
||||||
|
->header('Access-Control-Allow-Origin', '*');
|
||||||
|
} else {
|
||||||
|
return response('', 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
$hash = hash('sha256', $resource);
|
$hash = hash('sha256', $resource);
|
||||||
$key = 'federation:webfinger:sha256:'.$hash;
|
$key = 'federation:webfinger:sha256:'.$hash;
|
||||||
if ($cached = Cache::get($key)) {
|
if ($cached = Cache::get($key)) {
|
||||||
|
@ -81,8 +116,8 @@ class FederationController extends Controller
|
||||||
return response('', 400);
|
return response('', 400);
|
||||||
}
|
}
|
||||||
$username = $parsed['username'];
|
$username = $parsed['username'];
|
||||||
$profile = Profile::whereNull('domain')->whereUsername($username)->first();
|
$profile = Profile::whereUsername($username)->first();
|
||||||
if (! $profile || $profile->status !== null) {
|
if (! $profile || $profile->status !== null || $profile->domain) {
|
||||||
return response('', 400);
|
return response('', 400);
|
||||||
}
|
}
|
||||||
$webfinger = (new Webfinger($profile))->generate();
|
$webfinger = (new Webfinger($profile))->generate();
|
||||||
|
|
671
app/Http/Controllers/GroupController.php
Normal file
671
app/Http/Controllers/GroupController.php
Normal file
|
@ -0,0 +1,671 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Instance;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupBlock;
|
||||||
|
use App\Models\GroupCategory;
|
||||||
|
use App\Models\GroupInvitation;
|
||||||
|
use App\Models\GroupLike;
|
||||||
|
use App\Models\GroupLimit;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupReport;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Services\HashidService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Status;
|
||||||
|
use App\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Storage;
|
||||||
|
|
||||||
|
class GroupController extends GroupFederationController
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
abort_unless(config('groups.enabled'), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
|
||||||
|
return view('layouts.spa');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function home(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
|
||||||
|
return view('layouts.spa');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request, $id, $path = false)
|
||||||
|
{
|
||||||
|
$group = Group::find($id);
|
||||||
|
|
||||||
|
if (! $group || $group->status) {
|
||||||
|
return response()->view('groups.unavailable')->setStatusCode(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->wantsJson()) {
|
||||||
|
return $this->showGroupObject($group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('layouts.spa', compact('id', 'path'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showStatus(Request $request, $gid, $sid)
|
||||||
|
{
|
||||||
|
$group = Group::find($gid);
|
||||||
|
$pid = optional($request->user())->profile_id ?? false;
|
||||||
|
|
||||||
|
if (! $group || $group->status) {
|
||||||
|
return response()->view('groups.unavailable')->setStatusCode(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($group->is_private) {
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
abort_if(! $group->isMember($pid), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp = GroupPost::whereGroupId($gid)
|
||||||
|
->findOrFail($sid);
|
||||||
|
|
||||||
|
return view('layouts.spa', compact('group', 'gp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroup(Request $request, $id)
|
||||||
|
{
|
||||||
|
$group = Group::whereNull('status')->findOrFail($id);
|
||||||
|
$pid = optional($request->user())->profile_id ?? false;
|
||||||
|
|
||||||
|
$group = $this->toJson($group, $pid);
|
||||||
|
|
||||||
|
return response()->json($group, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showStatusLikes(Request $request, $id, $sid)
|
||||||
|
{
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$user = $request->user();
|
||||||
|
$pid = $user->profile_id;
|
||||||
|
abort_if(! $group->isMember($pid), 404);
|
||||||
|
$status = GroupPost::whereGroupId($id)->findOrFail($sid);
|
||||||
|
$likes = GroupLike::whereStatusId($sid)
|
||||||
|
->cursorPaginate(10)
|
||||||
|
->map(function ($l) use ($group) {
|
||||||
|
$account = AccountService::get($l->profile_id);
|
||||||
|
$account['url'] = "/groups/{$group->id}/user/{$account['id']}";
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
})
|
||||||
|
->filter(function ($l) {
|
||||||
|
return $l && isset($l['id']);
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
|
return $likes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupSettings(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(! $group->isMember($pid), 404);
|
||||||
|
abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
return view('groups.settings', compact('group'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function joinGroup(Request $request, $id)
|
||||||
|
{
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if($group->isMember($pid), 404);
|
||||||
|
|
||||||
|
if (! $request->user()->is_admin) {
|
||||||
|
abort_if(GroupService::getRejoinTimeout($group->id, $pid), 422, 'Cannot re-join this group for 24 hours after leaving or cancelling a request to join');
|
||||||
|
}
|
||||||
|
|
||||||
|
$member = new GroupMember;
|
||||||
|
$member->group_id = $group->id;
|
||||||
|
$member->profile_id = $pid;
|
||||||
|
$member->role = 'member';
|
||||||
|
$member->local_group = true;
|
||||||
|
$member->local_profile = true;
|
||||||
|
$member->join_request = $group->is_private;
|
||||||
|
$member->save();
|
||||||
|
|
||||||
|
GroupService::delSelf($group->id, $pid);
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:joined',
|
||||||
|
null,
|
||||||
|
GroupMember::class,
|
||||||
|
$member->id
|
||||||
|
);
|
||||||
|
|
||||||
|
$group = $this->toJson($group, $pid);
|
||||||
|
|
||||||
|
return $group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateGroup(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'description' => 'nullable|max:500',
|
||||||
|
'membership' => 'required|in:all,local,private',
|
||||||
|
'avatar' => 'nullable',
|
||||||
|
'header' => 'nullable',
|
||||||
|
'discoverable' => 'required',
|
||||||
|
'activitypub' => 'required',
|
||||||
|
'is_nsfw' => 'required',
|
||||||
|
'category' => 'required|string|in:'.implode(',', GroupService::categories()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::whereProfileId($pid)->findOrFail($id);
|
||||||
|
$member = GroupMember::whereGroupId($group->id)->whereProfileId($pid)->firstOrFail();
|
||||||
|
|
||||||
|
abort_if($member->role != 'founder', 403, 'Invalid group permission');
|
||||||
|
|
||||||
|
$metadata = $group->metadata;
|
||||||
|
$len = $group->is_private ? 12 : 4;
|
||||||
|
|
||||||
|
if ($request->hasFile('avatar')) {
|
||||||
|
$avatar = $request->file('avatar');
|
||||||
|
|
||||||
|
if ($avatar) {
|
||||||
|
if (isset($metadata['avatar']) &&
|
||||||
|
isset($metadata['avatar']['path']) &&
|
||||||
|
Storage::exists($metadata['avatar']['path'])
|
||||||
|
) {
|
||||||
|
Storage::delete($metadata['avatar']['path']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = 'avatar_'.strtolower(str_random($len)).'.'.$avatar->extension();
|
||||||
|
$path = $avatar->storePubliclyAs('public/g/'.$group->id.'/meta', $fileName);
|
||||||
|
$url = url(Storage::url($path));
|
||||||
|
$metadata['avatar'] = [
|
||||||
|
'path' => $path,
|
||||||
|
'url' => $url,
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('header')) {
|
||||||
|
$header = $request->file('header');
|
||||||
|
|
||||||
|
if ($header) {
|
||||||
|
if (isset($metadata['header']) &&
|
||||||
|
isset($metadata['header']['path']) &&
|
||||||
|
Storage::exists($metadata['header']['path'])
|
||||||
|
) {
|
||||||
|
Storage::delete($metadata['header']['path']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = 'header_'.strtolower(str_random($len)).'.'.$header->extension();
|
||||||
|
$path = $header->storePubliclyAs('public/g/'.$group->id.'/meta', $fileName);
|
||||||
|
$url = url(Storage::url($path));
|
||||||
|
$metadata['header'] = [
|
||||||
|
'path' => $path,
|
||||||
|
'url' => $url,
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cat = GroupService::categoryById($group->category_id);
|
||||||
|
if ($request->category !== $cat['name']) {
|
||||||
|
$group->category_id = GroupCategory::whereName($request->category)->first()->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$changes = null;
|
||||||
|
$group->description = e($request->input('description', null));
|
||||||
|
$group->is_private = $request->input('membership') == 'private';
|
||||||
|
$group->local_only = $request->input('membership') == 'local';
|
||||||
|
$group->activitypub = $request->input('activitypub') == 'true';
|
||||||
|
$group->discoverable = $request->input('discoverable') == 'true';
|
||||||
|
$group->is_nsfw = $request->input('is_nsfw') == 'true';
|
||||||
|
$group->metadata = $metadata;
|
||||||
|
if ($group->isDirty()) {
|
||||||
|
$changes = $group->getDirty();
|
||||||
|
}
|
||||||
|
$group->save();
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:settings:updated',
|
||||||
|
$changes
|
||||||
|
);
|
||||||
|
|
||||||
|
GroupService::del($group->id);
|
||||||
|
|
||||||
|
$res = $this->toJson($group, $pid);
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function toJson($group, $pid = false)
|
||||||
|
{
|
||||||
|
return GroupService::get($group->id, $pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupLeave(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($pid == $group->profile_id, 422, 'Cannot leave a group you created');
|
||||||
|
|
||||||
|
abort_if(! $group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
|
||||||
|
GroupMember::whereGroupId($group->id)->whereProfileId($pid)->delete();
|
||||||
|
GroupService::del($group->id);
|
||||||
|
GroupService::delSelf($group->id, $pid);
|
||||||
|
GroupService::setRejoinTimeout($group->id, $pid);
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancelJoinRequest(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
|
||||||
|
abort_if($pid == $group->profile_id, 422, 'Cannot leave a group you created');
|
||||||
|
abort_if($group->isMember($pid), 422, 'Cannot cancel approved join request, please leave group instead.');
|
||||||
|
|
||||||
|
GroupMember::whereGroupId($group->id)->whereProfileId($pid)->delete();
|
||||||
|
GroupService::del($group->id);
|
||||||
|
GroupService::delSelf($group->id, $pid);
|
||||||
|
GroupService::setRejoinTimeout($group->id, $pid);
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function metaBlockSearch(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(! $group->isMember($pid), 404);
|
||||||
|
abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$type = $request->input('type');
|
||||||
|
$item = $request->input('item');
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'instance':
|
||||||
|
$res = Instance::whereDomain($item)->first();
|
||||||
|
if ($res) {
|
||||||
|
abort_if(GroupBlock::whereGroupId($group->id)->whereInstanceId($res->id)->exists(), 400);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'user':
|
||||||
|
$res = Profile::whereUsername($item)->first();
|
||||||
|
if ($res) {
|
||||||
|
abort_if(GroupBlock::whereGroupId($group->id)->whereProfileId($res->id)->exists(), 400);
|
||||||
|
}
|
||||||
|
if ($res->user_id != null) {
|
||||||
|
abort_if(User::whereIsAdmin(true)->whereId($res->user_id)->exists(), 400);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json((bool) $res, ($res ? 200 : 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reportCreate(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(! $group->isMember($pid), 404);
|
||||||
|
|
||||||
|
$id = $request->input('id');
|
||||||
|
$type = $request->input('type');
|
||||||
|
$types = [
|
||||||
|
// original 3
|
||||||
|
'spam',
|
||||||
|
'sensitive',
|
||||||
|
'abusive',
|
||||||
|
|
||||||
|
// new
|
||||||
|
'underage',
|
||||||
|
'violence',
|
||||||
|
'copyright',
|
||||||
|
'impersonation',
|
||||||
|
'scam',
|
||||||
|
'terrorism',
|
||||||
|
];
|
||||||
|
|
||||||
|
$gp = GroupPost::whereGroupId($group->id)->find($id);
|
||||||
|
abort_if(! $gp, 422, 'Cannot report an invalid or deleted post');
|
||||||
|
abort_if(! in_array($type, $types), 422, 'Invalid report type');
|
||||||
|
abort_if($gp->profile_id === $pid, 422, 'Cannot report your own post');
|
||||||
|
abort_if(
|
||||||
|
GroupReport::whereGroupId($group->id)
|
||||||
|
->whereProfileId($pid)
|
||||||
|
->whereItemType(GroupPost::class)
|
||||||
|
->whereItemId($id)
|
||||||
|
->exists(),
|
||||||
|
422,
|
||||||
|
'You already reported this'
|
||||||
|
);
|
||||||
|
|
||||||
|
$report = new GroupReport();
|
||||||
|
$report->group_id = $group->id;
|
||||||
|
$report->profile_id = $pid;
|
||||||
|
$report->type = $type;
|
||||||
|
$report->item_type = GroupPost::class;
|
||||||
|
$report->item_id = $id;
|
||||||
|
$report->open = true;
|
||||||
|
$report->save();
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:report:create',
|
||||||
|
[
|
||||||
|
'type' => $type,
|
||||||
|
'report_id' => $report->id,
|
||||||
|
'status_id' => $gp->status_id,
|
||||||
|
'profile_id' => $gp->profile_id,
|
||||||
|
'username' => optional(AccountService::get($gp->profile_id))['acct'],
|
||||||
|
'gpid' => $gp->id,
|
||||||
|
'url' => $gp->url(),
|
||||||
|
],
|
||||||
|
GroupReport::class,
|
||||||
|
$report->id
|
||||||
|
);
|
||||||
|
|
||||||
|
return response([200]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reportAction(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(! $group->isMember($pid), 404);
|
||||||
|
abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'action' => 'required|in:cw,delete,ignore',
|
||||||
|
'id' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$action = $request->input('action');
|
||||||
|
$id = $request->input('id');
|
||||||
|
|
||||||
|
$report = GroupReport::whereGroupId($group->id)
|
||||||
|
->findOrFail($id);
|
||||||
|
$status = Status::findOrFail($report->item_id);
|
||||||
|
$gp = GroupPost::whereGroupId($group->id)
|
||||||
|
->whereStatusId($status->id)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'cw':
|
||||||
|
$status->is_nsfw = true;
|
||||||
|
$status->save();
|
||||||
|
StatusService::del($status->id);
|
||||||
|
|
||||||
|
GroupReport::whereGroupId($group->id)
|
||||||
|
->whereItemType($report->item_type)
|
||||||
|
->whereItemId($report->item_id)
|
||||||
|
->update(['open' => false]);
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:moderation:action',
|
||||||
|
[
|
||||||
|
'type' => 'cw',
|
||||||
|
'report_id' => $report->id,
|
||||||
|
'status_id' => $status->id,
|
||||||
|
'profile_id' => $status->profile_id,
|
||||||
|
'status_url' => $gp->url(),
|
||||||
|
],
|
||||||
|
GroupReport::class,
|
||||||
|
$report->id
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([200]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ignore':
|
||||||
|
GroupReport::whereGroupId($group->id)
|
||||||
|
->whereItemType($report->item_type)
|
||||||
|
->whereItemId($report->item_id)
|
||||||
|
->update(['open' => false]);
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:moderation:action',
|
||||||
|
[
|
||||||
|
'type' => 'ignore',
|
||||||
|
'report_id' => $report->id,
|
||||||
|
'status_id' => $status->id,
|
||||||
|
'profile_id' => $status->profile_id,
|
||||||
|
'status_url' => $gp->url(),
|
||||||
|
],
|
||||||
|
GroupReport::class,
|
||||||
|
$report->id
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([200]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMemberInteractionLimits(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(! $group->isMember($pid), 404);
|
||||||
|
abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$profile_id = $request->input('profile_id');
|
||||||
|
abort_if(! $group->isMember($profile_id), 404);
|
||||||
|
$limits = GroupService::getInteractionLimits($group->id, $profile_id);
|
||||||
|
|
||||||
|
return response()->json($limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateMemberInteractionLimits(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(! $group->isMember($pid), 404);
|
||||||
|
abort_if(! in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'profile_id' => 'required|exists:profiles,id',
|
||||||
|
'can_post' => 'required',
|
||||||
|
'can_comment' => 'required',
|
||||||
|
'can_like' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$member = $request->input('profile_id');
|
||||||
|
$can_post = $request->input('can_post');
|
||||||
|
$can_comment = $request->input('can_comment');
|
||||||
|
$can_like = $request->input('can_like');
|
||||||
|
$account = AccountService::get($member);
|
||||||
|
|
||||||
|
abort_if(! $account, 422, 'Invalid profile');
|
||||||
|
abort_if(! $group->isMember($member), 422, 'Invalid profile');
|
||||||
|
|
||||||
|
$limit = GroupLimit::firstOrCreate([
|
||||||
|
'profile_id' => $member,
|
||||||
|
'group_id' => $group->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($limit->wasRecentlyCreated) {
|
||||||
|
abort_if(GroupLimit::whereGroupId($group->id)->count() >= 25, 422, 'limit_reached');
|
||||||
|
}
|
||||||
|
|
||||||
|
$previousLimits = $limit->limits;
|
||||||
|
|
||||||
|
$limit->limits = [
|
||||||
|
'can_post' => $can_post,
|
||||||
|
'can_comment' => $can_comment,
|
||||||
|
'can_like' => $can_like,
|
||||||
|
];
|
||||||
|
$limit->save();
|
||||||
|
|
||||||
|
GroupService::clearInteractionLimits($group->id, $member);
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:member-limits:updated',
|
||||||
|
[
|
||||||
|
'profile_id' => $account['id'],
|
||||||
|
'username' => $account['username'],
|
||||||
|
'previousLimits' => $previousLimits,
|
||||||
|
'newLimits' => $limit->limits,
|
||||||
|
],
|
||||||
|
GroupLimit::class,
|
||||||
|
$limit->id
|
||||||
|
);
|
||||||
|
|
||||||
|
return $request->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showProfile(Request $request, $id, $pid)
|
||||||
|
{
|
||||||
|
$group = Group::find($id);
|
||||||
|
|
||||||
|
if (! $group || $group->status) {
|
||||||
|
return response()->view('groups.unavailable')->setStatusCode(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('layouts.spa');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showProfileByUsername(Request $request, $id, $pid)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
if (! $request->user()) {
|
||||||
|
return redirect("/{$pid}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$group = Group::find($id);
|
||||||
|
$cid = $request->user()->profile_id;
|
||||||
|
|
||||||
|
if (! $group || $group->status) {
|
||||||
|
return response()->view('groups.unavailable')->setStatusCode(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $group->isMember($cid)) {
|
||||||
|
return redirect("/{$pid}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$profile = Profile::whereUsername($pid)->first();
|
||||||
|
|
||||||
|
if (! $group->isMember($profile->id)) {
|
||||||
|
return redirect("/{$pid}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($profile) {
|
||||||
|
$url = url("/groups/{$id}/user/{$profile->id}");
|
||||||
|
|
||||||
|
return redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
abort(404, 'Invalid username');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupInviteLanding(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort(404, 'Not yet implemented');
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
|
||||||
|
return view('groups.invite', compact('group'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupShortLinkRedirect(Request $request, $hid)
|
||||||
|
{
|
||||||
|
$gid = HashidService::decode($hid);
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
|
||||||
|
return redirect($group->url());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupInviteClaim(Request $request, $id)
|
||||||
|
{
|
||||||
|
$group = GroupService::get($id);
|
||||||
|
abort_if(! $group || empty($group), 404);
|
||||||
|
|
||||||
|
return view('groups.invite-claim', compact('group'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupMemberInviteCheck(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
abort_if($group->isMember($pid), 422, 'Already a member');
|
||||||
|
|
||||||
|
$exists = GroupInvitation::whereGroupId($id)->whereToProfileId($pid)->exists();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'gid' => $id,
|
||||||
|
'can_join' => (bool) $exists,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupMemberInviteAccept(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
abort_if($group->isMember($pid), 422, 'Already a member');
|
||||||
|
|
||||||
|
abort_if(! GroupInvitation::whereGroupId($id)->whereToProfileId($pid)->exists(), 422);
|
||||||
|
|
||||||
|
$gm = new GroupMember;
|
||||||
|
$gm->group_id = $id;
|
||||||
|
$gm->profile_id = $pid;
|
||||||
|
$gm->role = 'member';
|
||||||
|
$gm->local_group = $group->local;
|
||||||
|
$gm->local_profile = true;
|
||||||
|
$gm->join_request = false;
|
||||||
|
$gm->save();
|
||||||
|
|
||||||
|
GroupInvitation::whereGroupId($id)->whereToProfileId($pid)->delete();
|
||||||
|
GroupService::del($id);
|
||||||
|
GroupService::delSelf($id, $pid);
|
||||||
|
|
||||||
|
return ['next_url' => $group->url()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupMemberInviteDecline(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(! $request->user(), 404);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
abort_if($group->isMember($pid), 422, 'Already a member');
|
||||||
|
|
||||||
|
return ['next_url' => '/'];
|
||||||
|
}
|
||||||
|
}
|
103
app/Http/Controllers/GroupFederationController.php
Normal file
103
app/Http/Controllers/GroupFederationController.php
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Status;
|
||||||
|
use App\Models\InstanceActor;
|
||||||
|
use App\Services\MediaService;
|
||||||
|
|
||||||
|
class GroupFederationController extends Controller
|
||||||
|
{
|
||||||
|
public function getGroupObject(Request $request, $id)
|
||||||
|
{
|
||||||
|
$group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id);
|
||||||
|
$res = $this->showGroupObject($group);
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showGroupObject($group)
|
||||||
|
{
|
||||||
|
return Cache::remember('ap:groups:object:' . $group->id, 3600, function() use($group) {
|
||||||
|
return [
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id' => $group->url(),
|
||||||
|
'inbox' => $group->permalink('/inbox'),
|
||||||
|
'name' => $group->name,
|
||||||
|
'outbox' => $group->permalink('/outbox'),
|
||||||
|
'summary' => $group->description,
|
||||||
|
'type' => 'Group',
|
||||||
|
'attributedTo' => [
|
||||||
|
'type' => 'Person',
|
||||||
|
'id' => $group->admin->permalink()
|
||||||
|
],
|
||||||
|
// 'endpoints' => [
|
||||||
|
// 'sharedInbox' => config('app.url') . '/f/inbox'
|
||||||
|
// ],
|
||||||
|
'preferredUsername' => 'gid_' . $group->id,
|
||||||
|
'publicKey' => [
|
||||||
|
'id' => $group->permalink('#main-key'),
|
||||||
|
'owner' => $group->permalink(),
|
||||||
|
'publicKeyPem' => InstanceActor::first()->public_key,
|
||||||
|
],
|
||||||
|
'url' => $group->permalink()
|
||||||
|
];
|
||||||
|
|
||||||
|
if($group->metadata && isset($group->metadata['avatar'])) {
|
||||||
|
$res['icon'] = [
|
||||||
|
'type' => 'Image',
|
||||||
|
'url' => $group->metadata['avatar']['url']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if($group->metadata && isset($group->metadata['header'])) {
|
||||||
|
$res['image'] = [
|
||||||
|
'type' => 'Image',
|
||||||
|
'url' => $group->metadata['header']['url']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
ksort($res);
|
||||||
|
return $res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatusObject(Request $request, $gid, $sid)
|
||||||
|
{
|
||||||
|
$group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($gid);
|
||||||
|
$gp = GroupPost::whereGroupId($gid)->findOrFail($sid);
|
||||||
|
$status = Status::findOrFail($gp->status_id);
|
||||||
|
// permission check
|
||||||
|
|
||||||
|
$res = [
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'id' => $gp->url(),
|
||||||
|
|
||||||
|
'type' => 'Note',
|
||||||
|
|
||||||
|
'summary' => null,
|
||||||
|
'content' => $status->rendered ?? $status->caption,
|
||||||
|
'inReplyTo' => null,
|
||||||
|
|
||||||
|
'published' => $status->created_at->toAtomString(),
|
||||||
|
'url' => $gp->url(),
|
||||||
|
'attributedTo' => $status->profile->permalink(),
|
||||||
|
'to' => [
|
||||||
|
'https://www.w3.org/ns/activitystreams#Public',
|
||||||
|
$group->permalink('/followers'),
|
||||||
|
],
|
||||||
|
'cc' => [],
|
||||||
|
'sensitive' => (bool) $status->is_nsfw,
|
||||||
|
'attachment' => MediaService::activitypub($status->id),
|
||||||
|
'target' => [
|
||||||
|
'type' => 'Collection',
|
||||||
|
'id' => $group->permalink('/wall'),
|
||||||
|
'attributedTo' => $group->permalink()
|
||||||
|
]
|
||||||
|
];
|
||||||
|
// ksort($res);
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
}
|
10
app/Http/Controllers/GroupPostController.php
Normal file
10
app/Http/Controllers/GroupPostController.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class GroupPostController extends Controller
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
83
app/Http/Controllers/Groups/CreateGroupsController.php
Normal file
83
app/Http/Controllers/Groups/CreateGroupsController.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
|
||||||
|
class CreateGroupsController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkCreatePermission(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$config = GroupService::config();
|
||||||
|
if($request->user()->is_admin) {
|
||||||
|
$allowed = true;
|
||||||
|
} else {
|
||||||
|
$max = $config['limits']['user']['create']['max'];
|
||||||
|
$allowed = Group::whereProfileId($pid)->count() <= $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['permission' => (bool) $allowed];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeGroup(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'name' => 'required',
|
||||||
|
'description' => 'nullable|max:500',
|
||||||
|
'membership' => 'required|in:public,private,local'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
|
$config = GroupService::config();
|
||||||
|
abort_if($config['limits']['user']['create']['new'] == false && $request->user()->is_admin == false, 422, 'Invalid operation');
|
||||||
|
$max = $config['limits']['user']['create']['max'];
|
||||||
|
// abort_if(Group::whereProfileId($pid)->count() <= $max, 422, 'Group limit reached');
|
||||||
|
|
||||||
|
$group = new Group;
|
||||||
|
$group->profile_id = $pid;
|
||||||
|
$group->name = $request->input('name');
|
||||||
|
$group->description = $request->input('description', null);
|
||||||
|
$group->is_private = $request->input('membership') == 'private';
|
||||||
|
$group->local_only = $request->input('membership') == 'local';
|
||||||
|
$group->metadata = $request->input('configuration');
|
||||||
|
$group->save();
|
||||||
|
|
||||||
|
GroupService::log($group->id, $pid, 'group:created');
|
||||||
|
|
||||||
|
$member = new GroupMember;
|
||||||
|
$member->group_id = $group->id;
|
||||||
|
$member->profile_id = $pid;
|
||||||
|
$member->role = 'founder';
|
||||||
|
$member->local_group = true;
|
||||||
|
$member->local_profile = true;
|
||||||
|
$member->save();
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:joined',
|
||||||
|
null,
|
||||||
|
GroupMember::class,
|
||||||
|
$member->id
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $group->id,
|
||||||
|
'url' => $group->url()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
353
app/Http/Controllers/Groups/GroupsAdminController.php
Normal file
353
app/Http/Controllers/Groups/GroupsAdminController.php
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Instance;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupBlock;
|
||||||
|
use App\Models\GroupCategory;
|
||||||
|
use App\Models\GroupInteraction;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Models\GroupReport;
|
||||||
|
use App\Services\Groups\GroupAccountService;
|
||||||
|
use App\Services\Groups\GroupPostService;
|
||||||
|
|
||||||
|
class GroupsAdminController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdminTabs(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
abort_if($pid !== $group->profile_id, 404);
|
||||||
|
|
||||||
|
$reqs = GroupMember::whereGroupId($group->id)->whereJoinRequest(true)->count();
|
||||||
|
$mods = GroupReport::whereGroupId($group->id)->whereOpen(true)->count();
|
||||||
|
$tabs = [
|
||||||
|
'moderation_count' => $mods > 99 ? '99+' : $mods,
|
||||||
|
'request_count' => $reqs > 99 ? '99+' : $reqs
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->json($tabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInteractionLogs(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$logs = GroupInteraction::whereGroupId($id)
|
||||||
|
->latest()
|
||||||
|
->paginate(10)
|
||||||
|
->map(function($log) use($group) {
|
||||||
|
return [
|
||||||
|
'id' => $log->id,
|
||||||
|
'profile' => GroupAccountService::get($group->id, $log->profile_id),
|
||||||
|
'type' => $log->type,
|
||||||
|
'metadata' => $log->metadata,
|
||||||
|
'created_at' => $log->created_at->format('c')
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($logs, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBlocks(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$blocks = [
|
||||||
|
'instances' => GroupBlock::whereGroupId($group->id)->whereNotNull('instance_id')->whereModerated(false)->latest()->take(3)->pluck('name'),
|
||||||
|
'users' => GroupBlock::whereGroupId($group->id)->whereNotNull('profile_id')->whereIsUser(true)->latest()->take(3)->pluck('name'),
|
||||||
|
'moderated' => GroupBlock::whereGroupId($group->id)->whereNotNull('instance_id')->whereModerated(true)->latest()->take(3)->pluck('name')
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->json($blocks, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportBlocks(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$blocks = [
|
||||||
|
'instances' => GroupBlock::whereGroupId($group->id)->whereNotNull('instance_id')->whereModerated(false)->latest()->pluck('name'),
|
||||||
|
'users' => GroupBlock::whereGroupId($group->id)->whereNotNull('profile_id')->whereIsUser(true)->latest()->pluck('name'),
|
||||||
|
'moderated' => GroupBlock::whereGroupId($group->id)->whereNotNull('instance_id')->whereModerated(true)->latest()->pluck('name')
|
||||||
|
];
|
||||||
|
|
||||||
|
$blocks['_created_at'] = now()->format('c');
|
||||||
|
$blocks['_version'] = '1.0.0';
|
||||||
|
ksort($blocks);
|
||||||
|
|
||||||
|
return response()->streamDownload(function() use($blocks) {
|
||||||
|
echo json_encode($blocks, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addBlock(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'item' => 'required',
|
||||||
|
'type' => 'required|in:instance,user,moderate'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$item = $request->input('item');
|
||||||
|
$type = $request->input('type');
|
||||||
|
|
||||||
|
switch($type) {
|
||||||
|
case 'instance':
|
||||||
|
$instance = Instance::whereDomain($item)->first();
|
||||||
|
abort_if(!$instance, 422, 'This domain either isn\'nt known or is invalid');
|
||||||
|
$gb = new GroupBlock;
|
||||||
|
$gb->group_id = $group->id;
|
||||||
|
$gb->admin_id = $pid;
|
||||||
|
$gb->instance_id = $instance->id;
|
||||||
|
$gb->name = $instance->domain;
|
||||||
|
$gb->is_user = false;
|
||||||
|
$gb->moderated = false;
|
||||||
|
$gb->save();
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:admin:block:instance',
|
||||||
|
[
|
||||||
|
'domain' => $instance->domain
|
||||||
|
],
|
||||||
|
GroupBlock::class,
|
||||||
|
$gb->id
|
||||||
|
);
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'user':
|
||||||
|
$profile = Profile::whereUsername($item)->first();
|
||||||
|
abort_if(!$profile, 422, 'This user either isn\'nt known or is invalid');
|
||||||
|
$gb = new GroupBlock;
|
||||||
|
$gb->group_id = $group->id;
|
||||||
|
$gb->admin_id = $pid;
|
||||||
|
$gb->profile_id = $profile->id;
|
||||||
|
$gb->name = $profile->username;
|
||||||
|
$gb->is_user = true;
|
||||||
|
$gb->moderated = false;
|
||||||
|
$gb->save();
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:admin:block:user',
|
||||||
|
[
|
||||||
|
'username' => $profile->username,
|
||||||
|
'domain' => $profile->domain
|
||||||
|
],
|
||||||
|
GroupBlock::class,
|
||||||
|
$gb->id
|
||||||
|
);
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'moderate':
|
||||||
|
$instance = Instance::whereDomain($item)->first();
|
||||||
|
abort_if(!$instance, 422, 'This domain either isn\'nt known or is invalid');
|
||||||
|
$gb = new GroupBlock;
|
||||||
|
$gb->group_id = $group->id;
|
||||||
|
$gb->admin_id = $pid;
|
||||||
|
$gb->instance_id = $instance->id;
|
||||||
|
$gb->name = $instance->domain;
|
||||||
|
$gb->is_user = false;
|
||||||
|
$gb->moderated = true;
|
||||||
|
$gb->save();
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:admin:moderate:instance',
|
||||||
|
[
|
||||||
|
'domain' => $instance->domain
|
||||||
|
],
|
||||||
|
GroupBlock::class,
|
||||||
|
$gb->id
|
||||||
|
);
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return response()->json([], 422, []);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function undoBlock(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'item' => 'required',
|
||||||
|
'type' => 'required|in:instance,user,moderate'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$item = $request->input('item');
|
||||||
|
$type = $request->input('type');
|
||||||
|
|
||||||
|
switch($type) {
|
||||||
|
case 'instance':
|
||||||
|
$instance = Instance::whereDomain($item)->first();
|
||||||
|
abort_if(!$instance, 422, 'This domain either isn\'nt known or is invalid');
|
||||||
|
|
||||||
|
$gb = GroupBlock::whereGroupId($group->id)
|
||||||
|
->whereInstanceId($instance->id)
|
||||||
|
->whereModerated(false)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
abort_if(!$gb, 422, 'Invalid group block');
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:admin:unblock:instance',
|
||||||
|
[
|
||||||
|
'domain' => $instance->domain
|
||||||
|
],
|
||||||
|
GroupBlock::class,
|
||||||
|
$gb->id
|
||||||
|
);
|
||||||
|
|
||||||
|
$gb->delete();
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'user':
|
||||||
|
$profile = Profile::whereUsername($item)->first();
|
||||||
|
abort_if(!$profile, 422, 'This user either isn\'nt known or is invalid');
|
||||||
|
|
||||||
|
$gb = GroupBlock::whereGroupId($group->id)
|
||||||
|
->whereProfileId($profile->id)
|
||||||
|
->whereIsUser(true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
abort_if(!$gb, 422, 'Invalid group block');
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:admin:unblock:user',
|
||||||
|
[
|
||||||
|
'username' => $profile->username,
|
||||||
|
'domain' => $profile->domain
|
||||||
|
],
|
||||||
|
GroupBlock::class,
|
||||||
|
$gb->id
|
||||||
|
);
|
||||||
|
|
||||||
|
$gb->delete();
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'moderate':
|
||||||
|
$instance = Instance::whereDomain($item)->first();
|
||||||
|
abort_if(!$instance, 422, 'This domain either isn\'nt known or is invalid');
|
||||||
|
|
||||||
|
$gb = GroupBlock::whereGroupId($group->id)
|
||||||
|
->whereInstanceId($instance->id)
|
||||||
|
->whereModerated(true)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
abort_if(!$gb, 422, 'Invalid group block');
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:admin:moderate:instance',
|
||||||
|
[
|
||||||
|
'domain' => $instance->domain
|
||||||
|
],
|
||||||
|
GroupBlock::class,
|
||||||
|
$gb->id
|
||||||
|
);
|
||||||
|
|
||||||
|
$gb->delete();
|
||||||
|
|
||||||
|
return [200];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return response()->json([], 422, []);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReportList(Request $request, $id)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$scope = $request->input('scope', 'open');
|
||||||
|
|
||||||
|
$list = GroupReport::selectRaw('id, profile_id, item_type, item_id, type, created_at, count(*) as total')
|
||||||
|
->whereGroupId($group->id)
|
||||||
|
->groupBy('item_id')
|
||||||
|
->when($scope == 'open', function($query, $scope) {
|
||||||
|
return $query->whereOpen(true);
|
||||||
|
})
|
||||||
|
->latest()
|
||||||
|
->simplePaginate(10)
|
||||||
|
->map(function($report) use($group) {
|
||||||
|
$res = [
|
||||||
|
'id' => (string) $report->id,
|
||||||
|
'profile' => GroupAccountService::get($group->id, $report->profile_id),
|
||||||
|
'type' => $report->type,
|
||||||
|
'created_at' => $report->created_at->format('c'),
|
||||||
|
'total_count' => $report->total
|
||||||
|
];
|
||||||
|
|
||||||
|
if($report->item_type === GroupPost::class) {
|
||||||
|
$res['status'] = GroupPostService::get($group->id, $report->item_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
});
|
||||||
|
return response()->json($list, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
84
app/Http/Controllers/Groups/GroupsApiController.php
Normal file
84
app/Http/Controllers/Groups/GroupsApiController.php
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupCategory;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Services\Groups\GroupAccountService;
|
||||||
|
|
||||||
|
class GroupsApiController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function toJson($group, $pid = false)
|
||||||
|
{
|
||||||
|
return GroupService::get($group->id, $pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfig(Request $request)
|
||||||
|
{
|
||||||
|
return GroupService::config();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupAccount(Request $request, $gid, $pid)
|
||||||
|
{
|
||||||
|
$res = GroupAccountService::get($gid, $pid);
|
||||||
|
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupCategories(Request $request)
|
||||||
|
{
|
||||||
|
$res = GroupService::categories();
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupsByCategory(Request $request)
|
||||||
|
{
|
||||||
|
$name = $request->input('name');
|
||||||
|
$category = GroupCategory::whereName($name)->firstOrFail();
|
||||||
|
$groups = Group::whereCategoryId($category->id)
|
||||||
|
->simplePaginate(6)
|
||||||
|
->map(function($group) {
|
||||||
|
return GroupService::get($group->id);
|
||||||
|
})
|
||||||
|
->filter(function($group) {
|
||||||
|
return $group;
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
return $groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRecommendedGroups(Request $request)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSelfGroups(Request $request)
|
||||||
|
{
|
||||||
|
$selfOnly = $request->input('self') == true;
|
||||||
|
$memberOnly = $request->input('member') == true;
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$res = GroupMember::whereProfileId($request->user()->profile_id)
|
||||||
|
->when($selfOnly, function($q, $selfOnly) {
|
||||||
|
return $q->whereRole('founder');
|
||||||
|
})
|
||||||
|
->when($memberOnly, function($q, $memberOnly) {
|
||||||
|
return $q->whereRole('member');
|
||||||
|
})
|
||||||
|
->simplePaginate(4)
|
||||||
|
->map(function($member) use($pid) {
|
||||||
|
$group = $member->group;
|
||||||
|
return $this->toJson($group, $pid);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($res);
|
||||||
|
}
|
||||||
|
}
|
361
app/Http/Controllers/Groups/GroupsCommentController.php
Normal file
361
app/Http/Controllers/Groups/GroupsCommentController.php
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Services\Groups\GroupCommentService;
|
||||||
|
use App\Services\Groups\GroupMediaService;
|
||||||
|
use App\Services\Groups\GroupPostService;
|
||||||
|
use App\Services\Groups\GroupsLikeService;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupLike;
|
||||||
|
use App\Models\GroupMedia;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupComment;
|
||||||
|
use Purify;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use App\Jobs\GroupsPipeline\ImageResizePipeline;
|
||||||
|
use App\Jobs\GroupsPipeline\ImageS3UploadPipeline;
|
||||||
|
use App\Jobs\GroupsPipeline\NewPostPipeline;
|
||||||
|
use App\Jobs\GroupsPipeline\NewCommentPipeline;
|
||||||
|
use App\Jobs\GroupsPipeline\DeleteCommentPipeline;
|
||||||
|
|
||||||
|
class GroupsCommentController extends Controller
|
||||||
|
{
|
||||||
|
public function getComments(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'sid' => 'required',
|
||||||
|
'cid' => 'sometimes',
|
||||||
|
'limit' => 'nullable|integer|min:3|max:10'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = optional($request->user())->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$sid = $request->input('sid');
|
||||||
|
$cid = $request->has('cid') && $request->input('cid') == 1;
|
||||||
|
$limit = $request->input('limit', 3);
|
||||||
|
$maxId = $request->input('max_id', 0);
|
||||||
|
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
|
||||||
|
abort_if($group->is_private && !$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
|
||||||
|
$status = $cid ? GroupComment::findOrFail($sid) : GroupPost::findOrFail($sid);
|
||||||
|
|
||||||
|
abort_if($status->group_id != $group->id, 400, 'Invalid group');
|
||||||
|
|
||||||
|
$replies = GroupComment::whereGroupId($group->id)
|
||||||
|
->whereStatusId($status->id)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->when($maxId, function($query, $maxId) {
|
||||||
|
return $query->where('id', '<', $maxId);
|
||||||
|
})
|
||||||
|
->take($limit)
|
||||||
|
->get()
|
||||||
|
->map(function($gp) use($pid) {
|
||||||
|
$status = GroupCommentService::get($gp['group_id'], $gp['id']);
|
||||||
|
$status['reply_count'] = $gp['reply_count'];
|
||||||
|
$status['url'] = $gp->url();
|
||||||
|
$status['favourited'] = (bool) GroupsLikeService::liked($pid, $gp['id']);
|
||||||
|
$status['account']['url'] = url("/groups/{$gp['group_id']}/user/{$gp['profile_id']}");
|
||||||
|
return $status;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $replies->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeComment(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required|exists:groups,id',
|
||||||
|
'sid' => 'required|exists:group_posts,id',
|
||||||
|
'cid' => 'sometimes',
|
||||||
|
'content' => 'required|string|min:1|max:1500'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$sid = $request->input('sid');
|
||||||
|
$cid = $request->input('cid');
|
||||||
|
$limit = $request->input('limit', 3);
|
||||||
|
$caption = e($request->input('content'));
|
||||||
|
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
abort_if(!GroupService::canComment($gid, $pid), 422, 'You cannot interact with this content at this time');
|
||||||
|
|
||||||
|
|
||||||
|
$parent = $cid == 1 ?
|
||||||
|
GroupComment::findOrFail($sid) :
|
||||||
|
GroupPost::whereGroupId($gid)->findOrFail($sid);
|
||||||
|
// $autolink = Purify::clean(Autolink::create()->autolink($caption));
|
||||||
|
// $autolink = str_replace('/discover/tags/', '/groups/' . $gid . '/topics/', $autolink);
|
||||||
|
|
||||||
|
$status = new GroupComment;
|
||||||
|
$status->group_id = $group->id;
|
||||||
|
$status->profile_id = $pid;
|
||||||
|
$status->status_id = $parent->id;
|
||||||
|
$status->caption = Purify::clean($caption);
|
||||||
|
$status->visibility = 'public';
|
||||||
|
$status->is_nsfw = false;
|
||||||
|
$status->local = true;
|
||||||
|
$status->save();
|
||||||
|
|
||||||
|
NewCommentPipeline::dispatch($parent, $status)->onQueue('groups');
|
||||||
|
// todo: perform in job
|
||||||
|
$parent->reply_count = $parent->reply_count ? $parent->reply_count + $parent->reply_count : 1;
|
||||||
|
$parent->save();
|
||||||
|
GroupPostService::del($parent->group_id, $parent->id);
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:comment:created',
|
||||||
|
[
|
||||||
|
'type' => 'group:post:comment',
|
||||||
|
'status_id' => $status->id
|
||||||
|
],
|
||||||
|
GroupPost::class,
|
||||||
|
$status->id
|
||||||
|
);
|
||||||
|
|
||||||
|
//GroupCommentPipeline::dispatch($parent, $status, $gp);
|
||||||
|
//NewStatusPipeline::dispatch($status, $gp);
|
||||||
|
//GroupPostService::del($group->id, GroupService::sidToGid($group->id, $parent->id));
|
||||||
|
|
||||||
|
// todo: perform in job
|
||||||
|
$s = GroupCommentService::get($status->group_id, $status->id);
|
||||||
|
|
||||||
|
$s['pf_type'] = 'text';
|
||||||
|
$s['visibility'] = 'public';
|
||||||
|
$s['url'] = $status->url();
|
||||||
|
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeCommentPhoto(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required|exists:groups,id',
|
||||||
|
'sid' => 'required|exists:group_posts,id',
|
||||||
|
'photo' => 'required|image'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$sid = $request->input('sid');
|
||||||
|
$limit = $request->input('limit', 3);
|
||||||
|
$caption = $request->input('content');
|
||||||
|
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
abort_if(!GroupService::canComment($gid, $pid), 422, 'You cannot interact with this content at this time');
|
||||||
|
$parent = GroupPost::whereGroupId($gid)->findOrFail($sid);
|
||||||
|
|
||||||
|
$status = new GroupComment;
|
||||||
|
$status->status_id = $parent->id;
|
||||||
|
$status->group_id = $group->id;
|
||||||
|
$status->profile_id = $pid;
|
||||||
|
$status->caption = Purify::clean($caption);
|
||||||
|
$status->visibility = 'draft';
|
||||||
|
$status->is_nsfw = false;
|
||||||
|
$status->save();
|
||||||
|
|
||||||
|
$photo = $request->file('photo');
|
||||||
|
$storagePath = GroupMediaService::path($group->id, $pid, $status->id);
|
||||||
|
$storagePath = 'public/g/' . $group->id . '/p/' . $parent->id;
|
||||||
|
$path = $photo->storePublicly($storagePath);
|
||||||
|
|
||||||
|
$media = new GroupMedia();
|
||||||
|
$media->group_id = $group->id;
|
||||||
|
$media->status_id = $status->id;
|
||||||
|
$media->profile_id = $request->user()->profile_id;
|
||||||
|
$media->media_path = $path;
|
||||||
|
$media->size = $photo->getSize();
|
||||||
|
$media->mime = $photo->getMimeType();
|
||||||
|
$media->save();
|
||||||
|
|
||||||
|
ImageResizePipeline::dispatchSync($media);
|
||||||
|
ImageS3UploadPipeline::dispatchSync($media);
|
||||||
|
|
||||||
|
// $gp = new GroupPost;
|
||||||
|
// $gp->group_id = $group->id;
|
||||||
|
// $gp->profile_id = $pid;
|
||||||
|
// $gp->type = 'reply:photo';
|
||||||
|
// $gp->status_id = $status->id;
|
||||||
|
// $gp->in_reply_to_id = $parent->id;
|
||||||
|
// $gp->save();
|
||||||
|
|
||||||
|
// GroupService::log(
|
||||||
|
// $group->id,
|
||||||
|
// $pid,
|
||||||
|
// 'group:comment:created',
|
||||||
|
// [
|
||||||
|
// 'type' => $gp->type,
|
||||||
|
// 'status_id' => $status->id
|
||||||
|
// ],
|
||||||
|
// GroupPost::class,
|
||||||
|
// $gp->id
|
||||||
|
// );
|
||||||
|
|
||||||
|
// todo: perform in job
|
||||||
|
// $parent->reply_count = Status::whereInReplyToId($parent->id)->count();
|
||||||
|
// $parent->save();
|
||||||
|
// StatusService::del($parent->id);
|
||||||
|
// GroupPostService::del($group->id, GroupService::sidToGid($group->id, $parent->id));
|
||||||
|
|
||||||
|
// delay response while background job optimizes media
|
||||||
|
// sleep(5);
|
||||||
|
|
||||||
|
// todo: perform in job
|
||||||
|
$s = GroupCommentService::get($status->group_id, $status->id);
|
||||||
|
|
||||||
|
// $s['pf_type'] = 'text';
|
||||||
|
// $s['visibility'] = 'public';
|
||||||
|
// $s['url'] = $gp->url();
|
||||||
|
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteComment(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'id' => 'required|integer|min:1',
|
||||||
|
'gid' => 'required|integer|min:1'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
|
||||||
|
$gp = GroupComment::whereGroupId($group->id)->findOrFail($request->input('id'));
|
||||||
|
abort_if($gp->profile_id != $pid && $group->profile_id != $pid, 403);
|
||||||
|
|
||||||
|
$parent = GroupPost::find($gp->status_id);
|
||||||
|
abort_if(!$parent, 422, 'Invalid parent');
|
||||||
|
|
||||||
|
DeleteCommentPipeline::dispatch($parent, $gp)->onQueue('groups');
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:status:deleted',
|
||||||
|
[
|
||||||
|
'type' => $gp->type,
|
||||||
|
'status_id' => $gp->id,
|
||||||
|
],
|
||||||
|
GroupComment::class,
|
||||||
|
$gp->id
|
||||||
|
);
|
||||||
|
$gp->delete();
|
||||||
|
|
||||||
|
if($request->wantsJson()) {
|
||||||
|
return response()->json(['Status successfully deleted.']);
|
||||||
|
} else {
|
||||||
|
return redirect('/groups/feed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function likePost(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'sid' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$sid = $request->input('sid');
|
||||||
|
|
||||||
|
$group = GroupService::get($gid);
|
||||||
|
abort_if(!$group || $gid != $group['id'], 422, 'Invalid group');
|
||||||
|
abort_if(!GroupService::canLike($gid, $pid), 422, 'You cannot interact with this content at this time');
|
||||||
|
abort_if(!GroupService::isMember($gid, $pid), 403, 'Not a member of group');
|
||||||
|
$gp = GroupCommentService::get($gid, $sid);
|
||||||
|
abort_if(!$gp, 422, 'Invalid status');
|
||||||
|
$count = $gp['favourites_count'] ?? 0;
|
||||||
|
|
||||||
|
$like = GroupLike::firstOrCreate([
|
||||||
|
'group_id' => $gid,
|
||||||
|
'profile_id' => $pid,
|
||||||
|
'comment_id' => $sid,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($like->wasRecentlyCreated) {
|
||||||
|
// update parent post like count
|
||||||
|
$parent = GroupComment::find($sid);
|
||||||
|
abort_if(!$parent || $parent->group_id != $gid, 422, 'Invalid status');
|
||||||
|
$parent->likes_count = $parent->likes_count + 1;
|
||||||
|
$parent->save();
|
||||||
|
GroupsLikeService::add($pid, $sid);
|
||||||
|
// invalidate cache
|
||||||
|
GroupCommentService::del($gid, $sid);
|
||||||
|
$count++;
|
||||||
|
GroupService::log(
|
||||||
|
$gid,
|
||||||
|
$pid,
|
||||||
|
'group:like',
|
||||||
|
null,
|
||||||
|
GroupLike::class,
|
||||||
|
$like->id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unlikePost(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'sid' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$sid = $request->input('sid');
|
||||||
|
|
||||||
|
$group = GroupService::get($gid);
|
||||||
|
abort_if(!$group || $gid != $group['id'], 422, 'Invalid group');
|
||||||
|
abort_if(!GroupService::canLike($gid, $pid), 422, 'You cannot interact with this content at this time');
|
||||||
|
abort_if(!GroupService::isMember($gid, $pid), 403, 'Not a member of group');
|
||||||
|
$gp = GroupCommentService::get($gid, $sid);
|
||||||
|
abort_if(!$gp, 422, 'Invalid status');
|
||||||
|
$count = $gp['favourites_count'] ?? 0;
|
||||||
|
|
||||||
|
$like = GroupLike::where([
|
||||||
|
'group_id' => $gid,
|
||||||
|
'profile_id' => $pid,
|
||||||
|
'comment_id' => $sid,
|
||||||
|
])->first();
|
||||||
|
|
||||||
|
if($like) {
|
||||||
|
$like->delete();
|
||||||
|
$parent = GroupComment::find($sid);
|
||||||
|
abort_if(!$parent || $parent->group_id != $gid, 422, 'Invalid status');
|
||||||
|
$parent->likes_count = $parent->likes_count - 1;
|
||||||
|
$parent->save();
|
||||||
|
GroupsLikeService::remove($pid, $sid);
|
||||||
|
// invalidate cache
|
||||||
|
GroupCommentService::del($gid, $sid);
|
||||||
|
$count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = ['code' => 200, 'msg' => 'Unliked post', 'count' => $count];
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
57
app/Http/Controllers/Groups/GroupsDiscoverController.php
Normal file
57
app/Http/Controllers/Groups/GroupsDiscoverController.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Models\GroupInvitation;
|
||||||
|
|
||||||
|
class GroupsDiscoverController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscoverPopular(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$groups = Group::orderByDesc('member_count')
|
||||||
|
->take(12)
|
||||||
|
->pluck('id')
|
||||||
|
->map(function($id) {
|
||||||
|
return GroupService::get($id);
|
||||||
|
})
|
||||||
|
->filter(function($id) {
|
||||||
|
return $id;
|
||||||
|
})
|
||||||
|
->take(6)
|
||||||
|
->values();
|
||||||
|
return $groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscoverNew(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$groups = Group::latest()
|
||||||
|
->take(12)
|
||||||
|
->pluck('id')
|
||||||
|
->map(function($id) {
|
||||||
|
return GroupService::get($id);
|
||||||
|
})
|
||||||
|
->filter(function($id) {
|
||||||
|
return $id;
|
||||||
|
})
|
||||||
|
->take(6)
|
||||||
|
->values();
|
||||||
|
return $groups;
|
||||||
|
}
|
||||||
|
}
|
188
app/Http/Controllers/Groups/GroupsFeedController.php
Normal file
188
app/Http/Controllers/Groups/GroupsFeedController.php
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Services\UserFilterService;
|
||||||
|
use App\Services\Groups\GroupFeedService;
|
||||||
|
use App\Services\Groups\GroupPostService;
|
||||||
|
use App\Services\RelationshipService;
|
||||||
|
use App\Services\Groups\GroupsLikeService;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupInvitation;
|
||||||
|
|
||||||
|
class GroupsFeedController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSelfFeed(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$limit = $request->input('limit', 5);
|
||||||
|
$page = $request->input('page');
|
||||||
|
$initial = $request->has('initial');
|
||||||
|
|
||||||
|
if($initial) {
|
||||||
|
$res = Cache::remember('groups:self:feed:' . $pid, 900, function() use($pid) {
|
||||||
|
return $this->getSelfFeedV0($pid, 5, null);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
abort_if($page && $page > 5, 422);
|
||||||
|
$res = $this->getSelfFeedV0($pid, $limit, $page);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSelfFeedV0($pid, $limit, $page)
|
||||||
|
{
|
||||||
|
return GroupPost::join('group_members', 'group_posts.group_id', 'group_members.group_id')
|
||||||
|
->select('group_posts.*', 'group_members.group_id', 'group_members.profile_id')
|
||||||
|
->where('group_members.profile_id', $pid)
|
||||||
|
->whereIn('group_posts.type', ['text', 'photo', 'video'])
|
||||||
|
->orderByDesc('group_posts.id')
|
||||||
|
->limit($limit)
|
||||||
|
// ->pluck('group_posts.status_id')
|
||||||
|
->simplePaginate($limit)
|
||||||
|
->map(function($gp) use($pid) {
|
||||||
|
$status = GroupPostService::get($gp['group_id'], $gp['id']);
|
||||||
|
|
||||||
|
if(!$status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status['favourited'] = (bool) GroupsLikeService::liked($pid, $gp['id']);
|
||||||
|
$status['favourites_count'] = GroupsLikeService::count($gp['id']);
|
||||||
|
$status['pf_type'] = $gp['type'];
|
||||||
|
$status['visibility'] = 'public';
|
||||||
|
$status['url'] = url("/groups/{$gp['group_id']}/p/{$gp['id']}");
|
||||||
|
$status['group'] = GroupService::get($gp['group_id']);
|
||||||
|
$status['account']['url'] = url("/groups/{$gp['group_id']}/user/{$status['account']['id']}");
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupProfileFeed(Request $request, $id, $pid)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$cid = $request->user()->profile_id;
|
||||||
|
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
|
||||||
|
$feed = GroupPost::whereGroupId($id)
|
||||||
|
->whereProfileId($pid)
|
||||||
|
->latest()
|
||||||
|
->paginate(3)
|
||||||
|
->map(function($gp) use($pid) {
|
||||||
|
$status = GroupPostService::get($gp['group_id'], $gp['id']);
|
||||||
|
if(!$status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$status['favourited'] = (bool) GroupsLikeService::liked($pid, $gp['id']);
|
||||||
|
$status['favourites_count'] = GroupsLikeService::count($gp['id']);
|
||||||
|
$status['pf_type'] = $gp['type'];
|
||||||
|
$status['visibility'] = 'public';
|
||||||
|
$status['url'] = $gp->url();
|
||||||
|
|
||||||
|
// if($gp['type'] == 'poll') {
|
||||||
|
// $status['poll'] = PollService::get($status['id']);
|
||||||
|
// }
|
||||||
|
|
||||||
|
$status['account']['url'] = "/groups/{$gp['group_id']}/user/{$status['account']['id']}";
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
})
|
||||||
|
->filter(function($status) {
|
||||||
|
return $status;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupFeed(Request $request, $id)
|
||||||
|
{
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$user = $request->user();
|
||||||
|
$pid = optional($user)->profile_id ?? false;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
$max = $request->input('max_id');
|
||||||
|
$limit = $request->limit ?? 3;
|
||||||
|
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
|
||||||
|
|
||||||
|
// $posts = GroupPost::whereGroupId($group->id)
|
||||||
|
// ->when($maxId, function($q, $maxId) {
|
||||||
|
// return $q->where('status_id', '<', $maxId);
|
||||||
|
// })
|
||||||
|
// ->whereNull('in_reply_to_id')
|
||||||
|
// ->orderByDesc('status_id')
|
||||||
|
// ->simplePaginate($limit)
|
||||||
|
// ->map(function($gp) use($pid) {
|
||||||
|
// $status = StatusService::get($gp['status_id'], false);
|
||||||
|
// if(!$status) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// $status['favourited'] = (bool) LikeService::liked($pid, $gp['status_id']);
|
||||||
|
// $status['favourites_count'] = LikeService::count($gp['status_id']);
|
||||||
|
// $status['pf_type'] = $gp['type'];
|
||||||
|
// $status['visibility'] = 'public';
|
||||||
|
// $status['url'] = $gp->url();
|
||||||
|
|
||||||
|
// if($gp['type'] == 'poll') {
|
||||||
|
// $status['poll'] = PollService::get($status['id']);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $status['account']['url'] = url("/groups/{$gp['group_id']}/user/{$status['account']['id']}");
|
||||||
|
|
||||||
|
// return $status;
|
||||||
|
// })->filter(function($status) {
|
||||||
|
// return $status;
|
||||||
|
// });
|
||||||
|
// return $posts;
|
||||||
|
|
||||||
|
Cache::remember('api:v1:timelines:public:cache_check', 10368000, function() use($id) {
|
||||||
|
if(GroupFeedService::count($id) == 0) {
|
||||||
|
GroupFeedService::warmCache($id, true, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($max) {
|
||||||
|
$feed = GroupFeedService::getRankedMaxId($id, $max, $limit);
|
||||||
|
} else {
|
||||||
|
$feed = GroupFeedService::get($id, 0, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = collect($feed)
|
||||||
|
->map(function($k) use($user, $id) {
|
||||||
|
$status = GroupPostService::get($id, $k);
|
||||||
|
if($status && $user) {
|
||||||
|
$pid = $user->profile_id;
|
||||||
|
$sid = $status['account']['id'];
|
||||||
|
$status['favourited'] = (bool) GroupsLikeService::liked($pid, $status['id']);
|
||||||
|
$status['favourites_count'] = GroupsLikeService::count($status['id']);
|
||||||
|
$status['relationship'] = $pid == $sid ? [] : RelationshipService::get($pid, $sid);
|
||||||
|
}
|
||||||
|
return $status;
|
||||||
|
})
|
||||||
|
->filter(function($s) use($filtered) {
|
||||||
|
return $s && in_array($s['account']['id'], $filtered) == false;
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
214
app/Http/Controllers/Groups/GroupsMemberController.php
Normal file
214
app/Http/Controllers/Groups/GroupsMemberController.php
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupCategory;
|
||||||
|
use App\Models\GroupHashtag;
|
||||||
|
use App\Models\GroupPostHashtag;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\FollowerService;
|
||||||
|
use App\Services\Groups\GroupAccountService;
|
||||||
|
use App\Services\Groups\GroupHashtagService;
|
||||||
|
use App\Jobs\GroupsPipeline\MemberJoinApprovedPipeline;
|
||||||
|
use App\Jobs\GroupsPipeline\MemberJoinRejectedPipeline;
|
||||||
|
|
||||||
|
class GroupsMemberController extends Controller
|
||||||
|
{
|
||||||
|
public function getGroupMembers(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'limit' => 'nullable|integer|min:3|max:10'
|
||||||
|
]);
|
||||||
|
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
|
||||||
|
$members = GroupMember::whereGroupId($gid)
|
||||||
|
->whereJoinRequest(false)
|
||||||
|
->simplePaginate(10)
|
||||||
|
->map(function($member) use($pid) {
|
||||||
|
$account = AccountService::get($member['profile_id']);
|
||||||
|
$account['role'] = $member['role'];
|
||||||
|
$account['joined'] = $member['created_at'];
|
||||||
|
$account['following'] = $pid != $member['profile_id'] ?
|
||||||
|
FollowerService::follows($pid, $member['profile_id']) :
|
||||||
|
null;
|
||||||
|
$account['url'] = url("/groups/{$member->group_id}/user/{$member['profile_id']}");
|
||||||
|
return $account;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($members->toArray(), 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupMemberJoinRequests(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$id = $request->input('gid');
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
return GroupMember::whereGroupId($group->id)
|
||||||
|
->whereJoinRequest(true)
|
||||||
|
->whereNull('rejected_at')
|
||||||
|
->paginate(10)
|
||||||
|
->map(function($member) {
|
||||||
|
return AccountService::get($member->profile_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleGroupMemberJoinRequest(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$id = $request->input('gid');
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
$mid = $request->input('pid');
|
||||||
|
abort_if($group->isMember($mid), 404);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'pid' => 'required',
|
||||||
|
'action' => 'required|in:approve,reject'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$action = $request->input('action');
|
||||||
|
|
||||||
|
$member = GroupMember::whereGroupId($group->id)
|
||||||
|
->whereProfileId($mid)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
if($action == 'approve') {
|
||||||
|
MemberJoinApprovedPipeline::dispatch($member)->onQueue('groups');
|
||||||
|
} else if ($action == 'reject') {
|
||||||
|
MemberJoinRejectedPipeline::dispatch($member)->onQueue('groups');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupMember(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'pid' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$member_id = $request->input('pid');
|
||||||
|
$member = GroupMember::whereGroupId($gid)
|
||||||
|
->whereProfileId($member_id)
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$account = GroupAccountService::get($group->id, $member['profile_id']);
|
||||||
|
$account['role'] = $member['role'];
|
||||||
|
$account['joined'] = $member['created_at'];
|
||||||
|
$account['following'] = $pid != $member['profile_id'] ?
|
||||||
|
FollowerService::follows($pid, $member['profile_id']) :
|
||||||
|
null;
|
||||||
|
$account['url'] = url("/groups/{$gid}/user/{$member_id}");
|
||||||
|
|
||||||
|
return response()->json($account, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupMemberCommonIntersections(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$cid = $request->user()->profile_id;
|
||||||
|
|
||||||
|
// $this->validate($request, [
|
||||||
|
// 'gid' => 'required',
|
||||||
|
// 'pid' => 'required'
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$pid = $request->input('pid');
|
||||||
|
|
||||||
|
if($pid === $cid) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
abort_if(!$group->isMember($cid), 404);
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
|
||||||
|
$self = GroupPostHashtag::selectRaw('group_post_hashtags.*, count(*) as countr')
|
||||||
|
->whereProfileId($cid)
|
||||||
|
->groupBy('hashtag_id')
|
||||||
|
->orderByDesc('countr')
|
||||||
|
->take(20)
|
||||||
|
->pluck('hashtag_id');
|
||||||
|
$user = GroupPostHashtag::selectRaw('group_post_hashtags.*, count(*) as countr')
|
||||||
|
->whereProfileId($pid)
|
||||||
|
->groupBy('hashtag_id')
|
||||||
|
->orderByDesc('countr')
|
||||||
|
->take(20)
|
||||||
|
->pluck('hashtag_id');
|
||||||
|
|
||||||
|
$topics = $self->intersect($user)
|
||||||
|
->values()
|
||||||
|
->shuffle()
|
||||||
|
->take(3)
|
||||||
|
->map(function($id) use($group) {
|
||||||
|
$tag = GroupHashtagService::get($id);
|
||||||
|
$tag['url'] = url("/groups/{$group->id}/topics/{$tag['slug']}?src=upt");
|
||||||
|
return $tag;
|
||||||
|
});
|
||||||
|
|
||||||
|
// $friends = DB::table('followers as u')
|
||||||
|
// ->join('followers as s', 'u.following_id', '=', 's.following_id')
|
||||||
|
// ->where('s.profile_id', $cid)
|
||||||
|
// ->where('u.profile_id', $pid)
|
||||||
|
// ->inRandomOrder()
|
||||||
|
// ->take(10)
|
||||||
|
// ->pluck('s.following_id')
|
||||||
|
// ->map(function($id) use($gid) {
|
||||||
|
// $res = AccountService::get($id);
|
||||||
|
// $res['url'] = url("/groups/{$gid}/user/{$id}");
|
||||||
|
// return $res;
|
||||||
|
// });
|
||||||
|
$mutualGroups = GroupService::mutualGroups($cid, $pid, [$gid]);
|
||||||
|
|
||||||
|
$mutualFriends = collect(FollowerService::mutualIds($cid, $pid))
|
||||||
|
->map(function($id) use($gid) {
|
||||||
|
$res = AccountService::get($id);
|
||||||
|
if(GroupService::isMember($gid, $id)) {
|
||||||
|
$res['url'] = url("/groups/{$gid}/user/{$id}");
|
||||||
|
} else if(!$res['local']) {
|
||||||
|
$res['url'] = url("/i/web/profile/_/{$id}");
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
|
});
|
||||||
|
$mutualFriendsCount = FollowerService::mutualCount($cid, $pid);
|
||||||
|
|
||||||
|
$res = [
|
||||||
|
'groups_count' => $mutualGroups['count'],
|
||||||
|
'groups' => $mutualGroups['groups'],
|
||||||
|
'topics' => $topics,
|
||||||
|
'friends_count' => $mutualFriendsCount,
|
||||||
|
'friends' => $mutualFriends,
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
}
|
31
app/Http/Controllers/Groups/GroupsMetaController.php
Normal file
31
app/Http/Controllers/Groups/GroupsMetaController.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Models\Group;
|
||||||
|
|
||||||
|
class GroupsMetaController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteGroup(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$id = $request->input('gid');
|
||||||
|
$group = Group::findOrFail($id);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(!in_array($group->selfRole($pid), ['founder', 'admin']), 404);
|
||||||
|
|
||||||
|
$group->status = "delete";
|
||||||
|
$group->save();
|
||||||
|
GroupService::del($group->id);
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Notification;
|
||||||
|
|
||||||
|
class GroupsNotificationsController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selfGlobalNotifications(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
|
$res = Notification::whereProfileId($pid)
|
||||||
|
->where('action', 'like', 'group%')
|
||||||
|
->latest()
|
||||||
|
->paginate(10)
|
||||||
|
->map(function($n) {
|
||||||
|
$res = [
|
||||||
|
'id' => $n->id,
|
||||||
|
'type' => $n->action,
|
||||||
|
'account' => AccountService::get($n->actor_id),
|
||||||
|
'object' => [
|
||||||
|
'id' => $n->item_id,
|
||||||
|
'type' => last(explode('\\', $n->item_type)),
|
||||||
|
],
|
||||||
|
'created_at' => $n->created_at->format('c')
|
||||||
|
];
|
||||||
|
|
||||||
|
if($res['object']['type'] == 'Status' || in_array($n->action, ['group:comment'])) {
|
||||||
|
$res['status'] = StatusService::get($n->item_id, false);
|
||||||
|
$res['group'] = GroupService::get($res['status']['gid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($res['object']['type'] == 'Group') {
|
||||||
|
$res['group'] = GroupService::get($n->item_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
}
|
420
app/Http/Controllers/Groups/GroupsPostController.php
Normal file
420
app/Http/Controllers/Groups/GroupsPostController.php
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Bus;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Services\Groups\GroupFeedService;
|
||||||
|
use App\Services\Groups\GroupPostService;
|
||||||
|
use App\Services\Groups\GroupMediaService;
|
||||||
|
use App\Services\Groups\GroupsLikeService;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupHashtag;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupLike;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Models\GroupInvitation;
|
||||||
|
use App\Models\GroupMedia;
|
||||||
|
use App\Jobs\GroupsPipeline\ImageResizePipeline;
|
||||||
|
use App\Jobs\GroupsPipeline\ImageS3UploadPipeline;
|
||||||
|
use App\Jobs\GroupsPipeline\NewPostPipeline;
|
||||||
|
|
||||||
|
class GroupsPostController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storePost(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'group_id' => 'required|exists:groups,id',
|
||||||
|
'caption' => 'sometimes|string|max:'.config_cache('pixelfed.max_caption_length', 500),
|
||||||
|
'pollOptions' => 'sometimes|array|min:1|max:4'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$group = Group::findOrFail($request->input('group_id'));
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$caption = $request->input('caption');
|
||||||
|
$type = $request->input('type', 'text');
|
||||||
|
|
||||||
|
abort_if(!GroupService::canPost($group->id, $pid), 422, 'You cannot create new posts at this time');
|
||||||
|
|
||||||
|
if($type == 'text') {
|
||||||
|
abort_if(strlen(e($caption)) == 0, 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp = new GroupPost;
|
||||||
|
$gp->group_id = $group->id;
|
||||||
|
$gp->profile_id = $pid;
|
||||||
|
$gp->caption = e($caption);
|
||||||
|
$gp->type = $type;
|
||||||
|
$gp->visibility = 'draft';
|
||||||
|
$gp->save();
|
||||||
|
|
||||||
|
$status = $gp;
|
||||||
|
|
||||||
|
NewPostPipeline::dispatchSync($gp);
|
||||||
|
|
||||||
|
// NewStatusPipeline::dispatch($status, $gp);
|
||||||
|
|
||||||
|
if($type == 'poll') {
|
||||||
|
// Polls not supported yet
|
||||||
|
// $poll = new Poll;
|
||||||
|
// $poll->status_id = $status->id;
|
||||||
|
// $poll->profile_id = $status->profile_id;
|
||||||
|
// $poll->poll_options = $request->input('pollOptions');
|
||||||
|
// $poll->expires_at = now()->addMinutes($request->input('expiry'));
|
||||||
|
// $poll->cached_tallies = collect($poll->poll_options)->map(function($o) {
|
||||||
|
// return 0;
|
||||||
|
// })->toArray();
|
||||||
|
// $poll->save();
|
||||||
|
// sleep(5);
|
||||||
|
}
|
||||||
|
if($type == 'photo') {
|
||||||
|
$photo = $request->file('photo');
|
||||||
|
$storagePath = GroupMediaService::path($group->id, $pid, $status->id);
|
||||||
|
// $storagePath = 'public/g/' . $group->id . '/p/' . $status->id;
|
||||||
|
$path = $photo->storePublicly($storagePath);
|
||||||
|
// $hash = \hash_file('sha256', $photo);
|
||||||
|
|
||||||
|
$media = new GroupMedia();
|
||||||
|
$media->group_id = $group->id;
|
||||||
|
$media->status_id = $status->id;
|
||||||
|
$media->profile_id = $request->user()->profile_id;
|
||||||
|
$media->media_path = $path;
|
||||||
|
$media->size = $photo->getSize();
|
||||||
|
$media->mime = $photo->getMimeType();
|
||||||
|
$media->save();
|
||||||
|
|
||||||
|
// Bus::chain([
|
||||||
|
// new ImageResizePipeline($media),
|
||||||
|
// new ImageS3UploadPipeline($media),
|
||||||
|
// ])->dispatch($media);
|
||||||
|
|
||||||
|
ImageResizePipeline::dispatchSync($media);
|
||||||
|
ImageS3UploadPipeline::dispatchSync($media);
|
||||||
|
// ImageOptimize::dispatch($media);
|
||||||
|
// delay response while background job optimizes media
|
||||||
|
// sleep(5);
|
||||||
|
}
|
||||||
|
if($type == 'video') {
|
||||||
|
$video = $request->file('video');
|
||||||
|
$storagePath = 'public/g/' . $group->id . '/p/' . $status->id;
|
||||||
|
$path = $video->storePublicly($storagePath);
|
||||||
|
$hash = \hash_file('sha256', $video);
|
||||||
|
|
||||||
|
$media = new Media();
|
||||||
|
$media->status_id = $status->id;
|
||||||
|
$media->profile_id = $request->user()->profile_id;
|
||||||
|
$media->user_id = $request->user()->id;
|
||||||
|
$media->media_path = $path;
|
||||||
|
$media->original_sha256 = $hash;
|
||||||
|
$media->size = $video->getSize();
|
||||||
|
$media->mime = $video->getMimeType();
|
||||||
|
$media->save();
|
||||||
|
|
||||||
|
VideoThumbnail::dispatch($media);
|
||||||
|
sleep(15);
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$group->id,
|
||||||
|
$pid,
|
||||||
|
'group:status:created',
|
||||||
|
[
|
||||||
|
'type' => $gp->type,
|
||||||
|
'status_id' => $status->id
|
||||||
|
],
|
||||||
|
GroupPost::class,
|
||||||
|
$gp->id
|
||||||
|
);
|
||||||
|
|
||||||
|
$s = GroupPostService::get($status->group_id, $status->id);
|
||||||
|
GroupFeedService::add($group->id, $gp->id);
|
||||||
|
Cache::forget('groups:self:feed:' . $pid);
|
||||||
|
|
||||||
|
$s['pf_type'] = $type;
|
||||||
|
$s['visibility'] = 'public';
|
||||||
|
$s['url'] = $gp->url();
|
||||||
|
|
||||||
|
if($type == 'poll') {
|
||||||
|
$s['poll'] = PollService::get($status->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$group->last_active_at = now();
|
||||||
|
$group->save();
|
||||||
|
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deletePost(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 403);
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'id' => 'required|integer|min:1',
|
||||||
|
'gid' => 'required|integer|min:1'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
|
||||||
|
$gp = GroupPost::whereGroupId($status->group_id)->findOrFail($request->input('id'));
|
||||||
|
abort_if($gp->profile_id != $pid && $group->profile_id != $pid, 403);
|
||||||
|
$cached = GroupPostService::get($status->group_id, $status->id);
|
||||||
|
|
||||||
|
if($cached) {
|
||||||
|
$cached = collect($cached)->filter(function($r, $k) {
|
||||||
|
return in_array($k, [
|
||||||
|
'id',
|
||||||
|
'sensitive',
|
||||||
|
'pf_type',
|
||||||
|
'media_attachments',
|
||||||
|
'content_text',
|
||||||
|
'created_at'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupService::log(
|
||||||
|
$status->group_id,
|
||||||
|
$request->user()->profile_id,
|
||||||
|
'group:status:deleted',
|
||||||
|
[
|
||||||
|
'type' => $gp->type,
|
||||||
|
'status_id' => $status->id,
|
||||||
|
'original' => $cached
|
||||||
|
],
|
||||||
|
GroupPost::class,
|
||||||
|
$gp->id
|
||||||
|
);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
// if($status->profile_id != $user->profile->id &&
|
||||||
|
// $user->is_admin == true &&
|
||||||
|
// $status->uri == null
|
||||||
|
// ) {
|
||||||
|
// $media = $status->media;
|
||||||
|
|
||||||
|
// $ai = new AccountInterstitial;
|
||||||
|
// $ai->user_id = $status->profile->user_id;
|
||||||
|
// $ai->type = 'post.removed';
|
||||||
|
// $ai->view = 'account.moderation.post.removed';
|
||||||
|
// $ai->item_type = 'App\Status';
|
||||||
|
// $ai->item_id = $status->id;
|
||||||
|
// $ai->has_media = (bool) $media->count();
|
||||||
|
// $ai->blurhash = $media->count() ? $media->first()->blurhash : null;
|
||||||
|
// $ai->meta = json_encode([
|
||||||
|
// 'caption' => $status->caption,
|
||||||
|
// 'created_at' => $status->created_at,
|
||||||
|
// 'type' => $status->type,
|
||||||
|
// 'url' => $status->url(),
|
||||||
|
// 'is_nsfw' => $status->is_nsfw,
|
||||||
|
// 'scope' => $status->scope,
|
||||||
|
// 'reblog' => $status->reblog_of_id,
|
||||||
|
// 'likes_count' => $status->likes_count,
|
||||||
|
// 'reblogs_count' => $status->reblogs_count,
|
||||||
|
// ]);
|
||||||
|
// $ai->save();
|
||||||
|
|
||||||
|
// $u = $status->profile->user;
|
||||||
|
// $u->has_interstitial = true;
|
||||||
|
// $u->save();
|
||||||
|
// }
|
||||||
|
|
||||||
|
if($status->in_reply_to_id) {
|
||||||
|
$parent = GroupPost::find($status->in_reply_to_id);
|
||||||
|
if($parent) {
|
||||||
|
$parent->reply_count = GroupPost::whereInReplyToId($parent->id)->count();
|
||||||
|
$parent->save();
|
||||||
|
GroupPostService::del($group->id, GroupService::sidToGid($group->id, $parent->id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupPostService::del($group->id, $gp->id);
|
||||||
|
GroupFeedService::del($group->id, $gp->id);
|
||||||
|
if ($status->profile_id == $user->profile->id || $user->is_admin == true) {
|
||||||
|
// Cache::forget('profile:status_count:'.$status->profile_id);
|
||||||
|
StatusDelete::dispatch($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($request->wantsJson()) {
|
||||||
|
return response()->json(['Status successfully deleted.']);
|
||||||
|
} else {
|
||||||
|
return redirect($user->url());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function likePost(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'sid' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$sid = $request->input('sid');
|
||||||
|
|
||||||
|
$group = GroupService::get($gid);
|
||||||
|
abort_if(!$group, 422, 'Invalid group');
|
||||||
|
abort_if(!GroupService::canLike($gid, $pid), 422, 'You cannot interact with this content at this time');
|
||||||
|
abort_if(!GroupService::isMember($gid, $pid), 403, 'Not a member of group');
|
||||||
|
$gp = GroupPostService::get($gid, $sid);
|
||||||
|
abort_if(!$gp, 422, 'Invalid status');
|
||||||
|
$count = $gp['favourites_count'] ?? 0;
|
||||||
|
|
||||||
|
$like = GroupLike::firstOrCreate([
|
||||||
|
'group_id' => $gid,
|
||||||
|
'profile_id' => $pid,
|
||||||
|
'status_id' => $sid,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if($like->wasRecentlyCreated) {
|
||||||
|
// update parent post like count
|
||||||
|
$parent = GroupPost::whereGroupId($gid)->find($sid);
|
||||||
|
abort_if(!$parent, 422, 'Invalid status');
|
||||||
|
$parent->likes_count = $parent->likes_count + 1;
|
||||||
|
$parent->save();
|
||||||
|
GroupsLikeService::add($pid, $sid);
|
||||||
|
// invalidate cache
|
||||||
|
GroupPostService::del($gid, $sid);
|
||||||
|
$count++;
|
||||||
|
GroupService::log(
|
||||||
|
$gid,
|
||||||
|
$pid,
|
||||||
|
'group:like',
|
||||||
|
null,
|
||||||
|
GroupLike::class,
|
||||||
|
$like->id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// if (GroupLike::whereGroupId($gid)->whereStatusId($sid)->whereProfileId($pid)->exists()) {
|
||||||
|
// $like = GroupLike::whereProfileId($pid)->whereStatusId($sid)->firstOrFail();
|
||||||
|
// // UnlikePipeline::dispatch($like);
|
||||||
|
// $count = $gp->likes_count - 1;
|
||||||
|
// $action = 'group:unlike';
|
||||||
|
// } else {
|
||||||
|
// $count = $gp->likes_count;
|
||||||
|
// $like = GroupLike::firstOrCreate([
|
||||||
|
// 'group_id' => $gid,
|
||||||
|
// 'profile_id' => $pid,
|
||||||
|
// 'status_id' => $sid
|
||||||
|
// ]);
|
||||||
|
// if($like->wasRecentlyCreated == true) {
|
||||||
|
// $count++;
|
||||||
|
// $gp->likes_count = $count;
|
||||||
|
// $like->save();
|
||||||
|
// $gp->save();
|
||||||
|
// // LikePipeline::dispatch($like);
|
||||||
|
// $action = 'group:like';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// Cache::forget('status:'.$status->id.':likedby:userid:'.$request->user()->id);
|
||||||
|
// StatusService::del($status->id);
|
||||||
|
|
||||||
|
$response = ['code' => 200, 'msg' => 'Like saved', 'count' => $count];
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function unlikePost(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'sid' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$sid = $request->input('sid');
|
||||||
|
|
||||||
|
$group = GroupService::get($gid);
|
||||||
|
abort_if(!$group, 422, 'Invalid group');
|
||||||
|
abort_if(!GroupService::canLike($gid, $pid), 422, 'You cannot interact with this content at this time');
|
||||||
|
abort_if(!GroupService::isMember($gid, $pid), 403, 'Not a member of group');
|
||||||
|
$gp = GroupPostService::get($gid, $sid);
|
||||||
|
abort_if(!$gp, 422, 'Invalid status');
|
||||||
|
$count = $gp['favourites_count'] ?? 0;
|
||||||
|
|
||||||
|
$like = GroupLike::where([
|
||||||
|
'group_id' => $gid,
|
||||||
|
'profile_id' => $pid,
|
||||||
|
'status_id' => $sid,
|
||||||
|
])->first();
|
||||||
|
|
||||||
|
if($like) {
|
||||||
|
$like->delete();
|
||||||
|
$parent = GroupPost::whereGroupId($gid)->find($sid);
|
||||||
|
abort_if(!$parent, 422, 'Invalid status');
|
||||||
|
$parent->likes_count = $parent->likes_count - 1;
|
||||||
|
$parent->save();
|
||||||
|
GroupsLikeService::remove($pid, $sid);
|
||||||
|
// invalidate cache
|
||||||
|
GroupPostService::del($gid, $sid);
|
||||||
|
$count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = ['code' => 200, 'msg' => 'Unliked post', 'count' => $count];
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGroupMedia(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'type' => 'required|in:photo,video'
|
||||||
|
]);
|
||||||
|
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$type = $request->input('type');
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
|
||||||
|
$media = GroupPost::whereGroupId($gid)
|
||||||
|
->whereType($type)
|
||||||
|
->latest()
|
||||||
|
->simplePaginate(20)
|
||||||
|
->map(function($gp) use($pid) {
|
||||||
|
$status = GroupPostService::get($gp['group_id'], $gp['id']);
|
||||||
|
if(!$status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$status['favourited'] = (bool) GroupsLikeService::liked($pid, $gp['id']);
|
||||||
|
$status['favourites_count'] = GroupsLikeService::count($gp['id']);
|
||||||
|
$status['pf_type'] = $gp['type'];
|
||||||
|
$status['visibility'] = 'public';
|
||||||
|
$status['url'] = $gp->url();
|
||||||
|
|
||||||
|
// if($gp['type'] == 'poll') {
|
||||||
|
// $status['poll'] = PollService::get($status['id']);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
})->filter(function($status) {
|
||||||
|
return $status;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($media->toArray(), 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
}
|
221
app/Http/Controllers/Groups/GroupsSearchController.php
Normal file
221
app/Http/Controllers/Groups/GroupsSearchController.php
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Models\GroupInvitation;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use App\Services\Groups\GroupActivityPubService;
|
||||||
|
|
||||||
|
class GroupsSearchController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function inviteFriendsToGroup(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$this->validate($request, [
|
||||||
|
'uids' => 'required',
|
||||||
|
'g' => 'required',
|
||||||
|
]);
|
||||||
|
$uid = $request->input('uids');
|
||||||
|
$gid = $request->input('g');
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
abort_if(
|
||||||
|
GroupInvitation::whereGroupId($group->id)
|
||||||
|
->whereFromProfileId($pid)
|
||||||
|
->count() >= 20,
|
||||||
|
422,
|
||||||
|
'Invite limit reached'
|
||||||
|
);
|
||||||
|
|
||||||
|
$profiles = collect($uid)
|
||||||
|
->map(function($u) {
|
||||||
|
return Profile::find($u);
|
||||||
|
})
|
||||||
|
->filter(function($u) use($pid) {
|
||||||
|
return $u &&
|
||||||
|
$u->id != $pid &&
|
||||||
|
isset($u->id) &&
|
||||||
|
Follower::whereFollowingId($pid)
|
||||||
|
->whereProfileId($u->id)
|
||||||
|
->exists();
|
||||||
|
})
|
||||||
|
->filter(function($u) use($group, $pid) {
|
||||||
|
return GroupInvitation::whereGroupId($group->id)
|
||||||
|
->whereFromProfileId($pid)
|
||||||
|
->whereToProfileId($u->id)
|
||||||
|
->exists() == false;
|
||||||
|
})
|
||||||
|
->each(function($u) use($gid, $pid) {
|
||||||
|
$gi = new GroupInvitation;
|
||||||
|
$gi->group_id = $gid;
|
||||||
|
$gi->from_profile_id = $pid;
|
||||||
|
$gi->to_profile_id = $u->id;
|
||||||
|
$gi->to_local = true;
|
||||||
|
$gi->from_local = $u->domain == null;
|
||||||
|
$gi->save();
|
||||||
|
// GroupMemberInvite::dispatch($gi);
|
||||||
|
});
|
||||||
|
return [200];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchFriendsToInvite(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$this->validate($request, [
|
||||||
|
'q' => 'required|min:2|max:40',
|
||||||
|
'g' => 'required',
|
||||||
|
'v' => 'required|in:0.2'
|
||||||
|
]);
|
||||||
|
$q = $request->input('q');
|
||||||
|
$gid = $request->input('g');
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
|
||||||
|
$res = Profile::where('username', 'like', "%{$q}%")
|
||||||
|
->whereNull('profiles.domain')
|
||||||
|
->join('followers', 'profiles.id', '=', 'followers.profile_id')
|
||||||
|
->where('followers.following_id', $pid)
|
||||||
|
->take(10)
|
||||||
|
->get()
|
||||||
|
->filter(function($p) use($group) {
|
||||||
|
return $group->isMember($p->profile_id) == false;
|
||||||
|
})
|
||||||
|
->filter(function($p) use($group, $pid) {
|
||||||
|
return GroupInvitation::whereGroupId($group->id)
|
||||||
|
->whereFromProfileId($pid)
|
||||||
|
->whereToProfileId($p->profile_id)
|
||||||
|
->exists() == false;
|
||||||
|
})
|
||||||
|
->map(function($gm) use ($gid) {
|
||||||
|
$a = AccountService::get($gm->profile_id);
|
||||||
|
return [
|
||||||
|
'id' => (string) $gm->profile_id,
|
||||||
|
'username' => $a['acct'],
|
||||||
|
'url' => url("/groups/{$gid}/user/{$a['id']}?rf=group_search")
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->values();
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchGlobalResults(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$this->validate($request, [
|
||||||
|
'q' => 'required|min:2|max:140',
|
||||||
|
'v' => 'required|in:0.2'
|
||||||
|
]);
|
||||||
|
$q = $request->input('q');
|
||||||
|
|
||||||
|
if(str_starts_with($q, 'https://')) {
|
||||||
|
$res = Helpers::getSignedFetch($q);
|
||||||
|
if($res && $res = json_decode($res, true)) {
|
||||||
|
|
||||||
|
}
|
||||||
|
if($res && isset($res['type']) && in_array($res['type'], ['Group', 'Note', 'Page'])) {
|
||||||
|
if($res['type'] === 'Group') {
|
||||||
|
return GroupActivityPubService::fetchGroup($q, true);
|
||||||
|
}
|
||||||
|
$resp = GroupActivityPubService::fetchGroupPost($q, true);
|
||||||
|
$resp['name'] = 'Group Post';
|
||||||
|
$resp['url'] = '/groups/' . $resp['group_id'] . '/p/' . $resp['id'];
|
||||||
|
return [$resp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Group::whereNull('status')
|
||||||
|
->where('name', 'like', '%' . $q . '%')
|
||||||
|
->orderBy('id')
|
||||||
|
->take(10)
|
||||||
|
->pluck('id')
|
||||||
|
->map(function($group) {
|
||||||
|
return GroupService::get($group);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchLocalAutocomplete(Request $request)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
$this->validate($request, [
|
||||||
|
'q' => 'required|min:2|max:40',
|
||||||
|
'g' => 'required',
|
||||||
|
'v' => 'required|in:0.2'
|
||||||
|
]);
|
||||||
|
$q = $request->input('q');
|
||||||
|
$gid = $request->input('g');
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
|
||||||
|
$res = GroupMember::whereGroupId($gid)
|
||||||
|
->join('profiles', 'group_members.profile_id', '=', 'profiles.id')
|
||||||
|
->where('profiles.username', 'like', "%{$q}%")
|
||||||
|
->take(10)
|
||||||
|
->get()
|
||||||
|
->map(function($gm) use ($gid) {
|
||||||
|
$a = AccountService::get($gm->profile_id);
|
||||||
|
return [
|
||||||
|
'username' => $a['username'],
|
||||||
|
'url' => url("/groups/{$gid}/user/{$a['id']}?rf=group_search")
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchAddRecent(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'q' => 'required|min:2|max:40',
|
||||||
|
'g' => 'required',
|
||||||
|
]);
|
||||||
|
$q = $request->input('q');
|
||||||
|
$gid = $request->input('g');
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
|
||||||
|
$key = 'groups:search:recent:'.$gid.':pid:'.$pid;
|
||||||
|
$ttl = now()->addDays(14);
|
||||||
|
$res = Cache::get($key);
|
||||||
|
if(!$res) {
|
||||||
|
$val = json_encode([$q]);
|
||||||
|
} else {
|
||||||
|
$ex = collect(json_decode($res))
|
||||||
|
->prepend($q)
|
||||||
|
->unique('value')
|
||||||
|
->slice(0, 3)
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
$val = json_encode($ex);
|
||||||
|
}
|
||||||
|
Cache::put($key, $val, $ttl);
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchGetRecent(Request $request)
|
||||||
|
{
|
||||||
|
$gid = $request->input('g');
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
abort_if(!$group->isMember($pid), 404);
|
||||||
|
$key = 'groups:search:recent:'.$gid.':pid:'.$pid;
|
||||||
|
return Cache::get($key);
|
||||||
|
}
|
||||||
|
}
|
133
app/Http/Controllers/Groups/GroupsTopicController.php
Normal file
133
app/Http/Controllers/Groups/GroupsTopicController.php
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Groups;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use App\Services\Groups\GroupPostService;
|
||||||
|
use App\Services\Groups\GroupsLikeService;
|
||||||
|
use App\Follower;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupHashtag;
|
||||||
|
use App\Models\GroupInvitation;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Models\GroupPostHashtag;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
|
||||||
|
class GroupsTopicController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupTopics(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
|
||||||
|
$posts = GroupPostHashtag::join('group_hashtags', 'group_hashtags.id', '=', 'group_post_hashtags.hashtag_id')
|
||||||
|
->selectRaw('group_hashtags.*, group_post_hashtags.*, count(group_post_hashtags.hashtag_id) as ht_count')
|
||||||
|
->where('group_post_hashtags.group_id', $gid)
|
||||||
|
->orderByDesc('ht_count')
|
||||||
|
->limit(10)
|
||||||
|
->pluck('group_post_hashtags.hashtag_id', 'ht_count')
|
||||||
|
->map(function($id, $key) use ($gid) {
|
||||||
|
$tag = GroupHashtag::find($id);
|
||||||
|
return [
|
||||||
|
'hid' => $id,
|
||||||
|
'name' => $tag->name,
|
||||||
|
'url' => url("/groups/{$gid}/topics/{$tag->slug}"),
|
||||||
|
'count' => $key
|
||||||
|
];
|
||||||
|
})->values();
|
||||||
|
|
||||||
|
return response()->json($posts, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupTopicTag(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'gid' => 'required',
|
||||||
|
'name' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$gid = $request->input('gid');
|
||||||
|
$limit = $request->input('limit', 3);
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
|
||||||
|
$name = $request->input('name');
|
||||||
|
$hashtag = GroupHashtag::whereName($name)->first();
|
||||||
|
|
||||||
|
if(!$hashtag) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// $posts = GroupPost::whereGroupId($gid)
|
||||||
|
// ->select('status_hashtags.*', 'group_posts.*')
|
||||||
|
// ->where('status_hashtags.hashtag_id', $hashtag->id)
|
||||||
|
// ->join('status_hashtags', 'group_posts.status_id', '=', 'status_hashtags.status_id')
|
||||||
|
// ->orderByDesc('group_posts.status_id')
|
||||||
|
// ->simplePaginate($limit)
|
||||||
|
// ->map(function($gp) use($pid) {
|
||||||
|
// $status = StatusService::get($gp['status_id'], false);
|
||||||
|
// if(!$status) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// $status['favourited'] = (bool) LikeService::liked($pid, $gp['status_id']);
|
||||||
|
// $status['favourites_count'] = LikeService::count($gp['status_id']);
|
||||||
|
// $status['pf_type'] = $gp['type'];
|
||||||
|
// $status['visibility'] = 'public';
|
||||||
|
// $status['url'] = $gp->url();
|
||||||
|
// return $status;
|
||||||
|
// });
|
||||||
|
|
||||||
|
$posts = GroupPostHashtag::whereGroupId($gid)
|
||||||
|
->whereHashtagId($hashtag->id)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->simplePaginate($limit)
|
||||||
|
->map(function($gp) use($pid) {
|
||||||
|
$status = GroupPostService::get($gp['group_id'], $gp['status_id']);
|
||||||
|
if(!$status) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$status['favourited'] = (bool) GroupsLikeService::liked($pid, $gp['status_id']);
|
||||||
|
$status['favourites_count'] = GroupsLikeService::count($gp['status_id']);
|
||||||
|
$status['pf_type'] = $status['pf_type'];
|
||||||
|
$status['visibility'] = 'public';
|
||||||
|
return $status;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($posts, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showTopicFeed(Request $request, $gid, $tag)
|
||||||
|
{
|
||||||
|
abort_if(!$request->user(), 404);
|
||||||
|
|
||||||
|
$pid = $request->user()->profile_id;
|
||||||
|
$group = Group::findOrFail($gid);
|
||||||
|
$gid = $group->id;
|
||||||
|
abort_if(!$group->isMember($pid), 403, 'Not a member of group.');
|
||||||
|
return view('groups.topic-feed', compact('gid', 'tag'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,13 +4,13 @@ namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\ConfigCache;
|
use App\Models\ConfigCache;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use App\Services\InstanceService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
use App\User;
|
||||||
|
use Cache;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Cache;
|
|
||||||
use Storage;
|
use Storage;
|
||||||
use App\Status;
|
|
||||||
use App\User;
|
|
||||||
|
|
||||||
class PixelfedDirectoryController extends Controller
|
class PixelfedDirectoryController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -90,7 +90,8 @@ class PixelfedDirectoryController extends Controller
|
||||||
|
|
||||||
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
|
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
|
||||||
if ($oauthEnabled) {
|
if ($oauthEnabled) {
|
||||||
$keys = file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
|
$keys = (file_exists(storage_path('oauth-public.key')) || config_cache('passport.public_key')) &&
|
||||||
|
(file_exists(storage_path('oauth-private.key')) || config_cache('passport.private_key'));
|
||||||
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,9 +141,7 @@ class PixelfedDirectoryController extends Controller
|
||||||
'stories' => (bool) config_cache('instance.stories.enabled'),
|
'stories' => (bool) config_cache('instance.stories.enabled'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$statusesCount = Cache::remember('api:nodeinfo:statuses', 21600, function() {
|
$statusesCount = InstanceService::totalLocalStatuses();
|
||||||
return Status::whereLocal(true)->count();
|
|
||||||
});
|
|
||||||
$usersCount = Cache::remember('api:nodeinfo:users', 43200, function () {
|
$usersCount = Cache::remember('api:nodeinfo:users', 43200, function () {
|
||||||
return User::count();
|
return User::count();
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ProfileController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect authed users to Metro 2.0
|
// redirect authed users to Metro 2.0
|
||||||
if ($request->user()) {
|
if ($request->user() && !$request->filled('carousel')) {
|
||||||
// unless they force static view
|
// unless they force static view
|
||||||
if (! $request->has('fs') || $request->input('fs') != '1') {
|
if (! $request->has('fs') || $request->input('fs') != '1') {
|
||||||
$pid = AccountService::usernameToId($username);
|
$pid = AccountService::usernameToId($username);
|
||||||
|
@ -64,6 +64,7 @@ class ProfileController extends Controller
|
||||||
|
|
||||||
protected function buildProfile(Request $request, $user)
|
protected function buildProfile(Request $request, $user)
|
||||||
{
|
{
|
||||||
|
$carousel = (bool) $request->filled('carousel');
|
||||||
$username = $user->username;
|
$username = $user->username;
|
||||||
$loggedIn = Auth::check();
|
$loggedIn = Auth::check();
|
||||||
$isPrivate = false;
|
$isPrivate = false;
|
||||||
|
@ -97,6 +98,9 @@ class ProfileController extends Controller
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if($carousel) {
|
||||||
|
return view('profile.show_carousel', compact('profile', 'settings'));
|
||||||
|
}
|
||||||
return view('profile.show', compact('profile', 'settings'));
|
return view('profile.show', compact('profile', 'settings'));
|
||||||
} else {
|
} else {
|
||||||
$key = 'profile:settings:'.$user->id;
|
$key = 'profile:settings:'.$user->id;
|
||||||
|
@ -135,7 +139,9 @@ class ProfileController extends Controller
|
||||||
'list' => $settings->show_profile_followers,
|
'list' => $settings->show_profile_followers,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
if($carousel) {
|
||||||
|
return view('profile.show_carousel', compact('profile', 'settings'));
|
||||||
|
}
|
||||||
return view('profile.show', compact('profile', 'settings'));
|
return view('profile.show', compact('profile', 'settings'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,7 +275,7 @@ class ProfileController extends Controller
|
||||||
|
|
||||||
abort_if($aiCheck, 404);
|
abort_if($aiCheck, 404);
|
||||||
|
|
||||||
$enabled = Cache::remember('profile:atom:enabled:'.$profile['id'], 84600, function () use ($profile) {
|
$enabled = Cache::remember('profile:atom:enabled:'.$profile['id'], 86400, function () use ($profile) {
|
||||||
$uid = User::whereProfileId($profile['id'])->first();
|
$uid = User::whereProfileId($profile['id'])->first();
|
||||||
if (! $uid) {
|
if (! $uid) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -361,7 +367,7 @@ class ProfileController extends Controller
|
||||||
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AccountService::canEmbed($profile->user_id) == false) {
|
if (AccountService::canEmbed($profile->id) == false) {
|
||||||
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,47 +2,28 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use App\Follower;
|
||||||
use App\{
|
use App\Profile;
|
||||||
Hashtag,
|
use App\Services\AccountService;
|
||||||
Follower,
|
use App\Services\BookmarkService;
|
||||||
Like,
|
use App\Services\FollowerService;
|
||||||
Media,
|
|
||||||
Notification,
|
|
||||||
Profile,
|
|
||||||
StatusHashtag,
|
|
||||||
Status,
|
|
||||||
StatusView,
|
|
||||||
UserFilter
|
|
||||||
};
|
|
||||||
use Auth, Cache, DB;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use League\Fractal;
|
|
||||||
use App\Transformer\Api\{
|
|
||||||
AccountTransformer,
|
|
||||||
RelationshipTransformer,
|
|
||||||
StatusTransformer,
|
|
||||||
StatusStatelessTransformer
|
|
||||||
};
|
|
||||||
use App\Services\{
|
|
||||||
AccountService,
|
|
||||||
BookmarkService,
|
|
||||||
FollowerService,
|
|
||||||
LikeService,
|
|
||||||
PublicTimelineService,
|
|
||||||
ProfileService,
|
|
||||||
NetworkTimelineService,
|
|
||||||
ReblogService,
|
|
||||||
RelationshipService,
|
|
||||||
StatusService,
|
|
||||||
SnowflakeService,
|
|
||||||
UserFilterService
|
|
||||||
};
|
|
||||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
|
||||||
use App\Services\InstanceService;
|
use App\Services\InstanceService;
|
||||||
|
use App\Services\LikeService;
|
||||||
|
use App\Services\NetworkTimelineService;
|
||||||
|
use App\Services\PublicTimelineService;
|
||||||
|
use App\Services\ReblogService;
|
||||||
|
use App\Services\RelationshipService;
|
||||||
|
use App\Services\SnowflakeService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Services\UserFilterService;
|
||||||
|
use App\Status;
|
||||||
|
use App\Transformer\Api\StatusStatelessTransformer;
|
||||||
|
use Auth;
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use League\Fractal;
|
||||||
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
|
||||||
class PublicApiController extends Controller
|
class PublicApiController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -118,6 +99,7 @@ class PublicApiController extends Controller
|
||||||
'bookmarked' => false,
|
'bookmarked' => false,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
$res = [
|
$res = [
|
||||||
|
@ -130,6 +112,7 @@ class PublicApiController extends Controller
|
||||||
'bookmarked' => (bool) $status->bookmarked(),
|
'bookmarked' => (bool) $status->bookmarked(),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +121,7 @@ class PublicApiController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'min_id' => 'nullable|integer|min:1',
|
'min_id' => 'nullable|integer|min:1',
|
||||||
'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
||||||
'limit' => 'nullable|integer|min:5|max:50'
|
'limit' => 'nullable|integer|min:5|max:50',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$limit = $request->limit ?? 10;
|
$limit = $request->limit ?? 10;
|
||||||
|
@ -184,6 +167,7 @@ class PublicApiController extends Controller
|
||||||
$resource = new Fractal\Resource\Collection($replies, new StatusStatelessTransformer(), 'data');
|
$resource = new Fractal\Resource\Collection($replies, new StatusStatelessTransformer(), 'data');
|
||||||
$resource->setPaginator(new IlluminatePaginatorAdapter($replies));
|
$resource->setPaginator(new IlluminatePaginatorAdapter($replies));
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,8 +186,8 @@ class PublicApiController extends Controller
|
||||||
if (! $user) {
|
if (! $user) {
|
||||||
abort(403);
|
abort(403);
|
||||||
} else {
|
} else {
|
||||||
$follows = $profile->followedBy($user->profile);
|
$follows = FollowerService::follows($profile->id, $user->profile_id);
|
||||||
if($follows == false && $profile->id !== $user->profile->id && $user->is_admin == false) {
|
if ($follows == false && $profile->id !== $user->profile_id && $user->is_admin == false) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,7 +213,7 @@ class PublicApiController extends Controller
|
||||||
'page' => 'nullable|integer|max:40',
|
'page' => 'nullable|integer|max:40',
|
||||||
'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'limit' => 'nullable|integer|max:30'
|
'limit' => 'nullable|integer|max:30',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (! $request->user()) {
|
if (! $request->user()) {
|
||||||
|
@ -274,6 +258,7 @@ class PublicApiController extends Controller
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function ($s) use ($filtered) {
|
->filter(function ($s) use ($filtered) {
|
||||||
|
@ -320,6 +305,7 @@ class PublicApiController extends Controller
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function ($s) use ($filtered) {
|
->filter(function ($s) use ($filtered) {
|
||||||
|
@ -354,6 +340,7 @@ class PublicApiController extends Controller
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $k);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $k);
|
||||||
$status['relationship'] = RelationshipService::get($user->profile_id, $status['account']['id']);
|
$status['relationship'] = RelationshipService::get($user->profile_id, $status['account']['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function ($s) use ($filtered) {
|
->filter(function ($s) use ($filtered) {
|
||||||
|
@ -378,7 +365,7 @@ class PublicApiController extends Controller
|
||||||
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'limit' => 'nullable|integer|max:40',
|
'limit' => 'nullable|integer|max:40',
|
||||||
'recent_feed' => 'nullable',
|
'recent_feed' => 'nullable',
|
||||||
'recent_min' => 'nullable|integer'
|
'recent_min' => 'nullable|integer',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$recentFeed = $request->input('recent_feed') == 'true';
|
$recentFeed = $request->input('recent_feed') == 'true';
|
||||||
|
@ -400,6 +387,7 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
$following = Cache::remember('profile:following:'.$pid, 1209600, function () use ($pid) {
|
$following = Cache::remember('profile:following:'.$pid, 1209600, function () use ($pid) {
|
||||||
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
||||||
|
|
||||||
return $following->push($pid)->toArray();
|
return $following->push($pid)->toArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -412,6 +400,7 @@ class PublicApiController extends Controller
|
||||||
if ($min || $max) {
|
if ($min || $max) {
|
||||||
$dir = $min ? '>' : '<';
|
$dir = $min ? '>' : '<';
|
||||||
$id = $min ?? $max;
|
$id = $min ?? $max;
|
||||||
|
|
||||||
return Status::select(
|
return Status::select(
|
||||||
'id',
|
'id',
|
||||||
'uri',
|
'uri',
|
||||||
|
@ -454,6 +443,7 @@ class PublicApiController extends Controller
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function ($s) use ($filtered) {
|
->filter(function ($s) use ($filtered) {
|
||||||
|
@ -503,6 +493,7 @@ class PublicApiController extends Controller
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function ($s) use ($filtered) {
|
->filter(function ($s) use ($filtered) {
|
||||||
|
@ -525,7 +516,7 @@ class PublicApiController extends Controller
|
||||||
'page' => 'nullable|integer|max:40',
|
'page' => 'nullable|integer|max:40',
|
||||||
'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'limit' => 'nullable|integer|max:30'
|
'limit' => 'nullable|integer|max:30',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$page = $request->input('page');
|
$page = $request->input('page');
|
||||||
|
@ -567,6 +558,7 @@ class PublicApiController extends Controller
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
});
|
});
|
||||||
$res = $timeline->toArray();
|
$res = $timeline->toArray();
|
||||||
|
@ -595,6 +587,7 @@ class PublicApiController extends Controller
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
$status['bookmarked'] = (bool) BookmarkService::get($user->profile_id, $s->id);
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $s->id);
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
});
|
});
|
||||||
$res = $timeline->toArray();
|
$res = $timeline->toArray();
|
||||||
|
@ -624,6 +617,7 @@ class PublicApiController extends Controller
|
||||||
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $k);
|
$status['reblogged'] = (bool) ReblogService::get($user->profile_id, $k);
|
||||||
$status['relationship'] = RelationshipService::get($user->profile_id, $status['account']['id']);
|
$status['relationship'] = RelationshipService::get($user->profile_id, $status['account']['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function ($s) use ($filtered) {
|
->filter(function ($s) use ($filtered) {
|
||||||
|
@ -646,7 +640,7 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required|array|min:1|max:20',
|
'id' => 'required|array|min:1|max:20',
|
||||||
'id.*' => 'required|integer'
|
'id.*' => 'required|integer',
|
||||||
]);
|
]);
|
||||||
$ids = collect($request->input('id'));
|
$ids = collect($request->input('id'));
|
||||||
$res = $ids->filter(function ($v) use ($pid) {
|
$res = $ids->filter(function ($v) use ($pid) {
|
||||||
|
@ -666,6 +660,7 @@ class PublicApiController extends Controller
|
||||||
$domain = parse_url($res['url'], PHP_URL_HOST);
|
$domain = parse_url($res['url'], PHP_URL_HOST);
|
||||||
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -678,7 +673,7 @@ class PublicApiController extends Controller
|
||||||
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'since_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'since_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'limit' => 'nullable|integer|min:1|max:24'
|
'limit' => 'nullable|integer|min:1|max:24',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
@ -707,17 +702,19 @@ class PublicApiController extends Controller
|
||||||
$pid = $user->profile_id;
|
$pid = $user->profile_id;
|
||||||
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) {
|
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) {
|
||||||
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
||||||
|
|
||||||
return $following->push($pid)->toArray();
|
return $following->push($pid)->toArray();
|
||||||
});
|
});
|
||||||
$visibility = true == in_array($profile['id'], $following) ? ['public', 'unlisted', 'private'] : [];
|
$visibility = in_array($profile['id'], $following) == true ? ['public', 'unlisted', 'private'] : [];
|
||||||
} else {
|
} else {
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$pid = $user->profile_id;
|
$pid = $user->profile_id;
|
||||||
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) {
|
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) {
|
||||||
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
||||||
|
|
||||||
return $following->push($pid)->toArray();
|
return $following->push($pid)->toArray();
|
||||||
});
|
});
|
||||||
$visibility = true == in_array($profile['id'], $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
|
$visibility = in_array($profile['id'], $following) == true ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
|
||||||
} else {
|
} else {
|
||||||
$visibility = ['public', 'unlisted'];
|
$visibility = ['public', 'unlisted'];
|
||||||
}
|
}
|
||||||
|
@ -742,6 +739,7 @@ class PublicApiController extends Controller
|
||||||
if ($user && $status) {
|
if ($user && $status) {
|
||||||
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function ($s) use ($onlyMedia) {
|
->filter(function ($s) use ($onlyMedia) {
|
||||||
|
@ -754,6 +752,7 @@ class PublicApiController extends Controller
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $s;
|
return $s;
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
|
|
|
@ -4,21 +4,17 @@ namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
use App\AccountLog;
|
use App\AccountLog;
|
||||||
use App\EmailVerification;
|
use App\EmailVerification;
|
||||||
|
use App\Mail\PasswordChange;
|
||||||
use App\Media;
|
use App\Media;
|
||||||
use App\Profile;
|
use App\Services\AccountService;
|
||||||
use App\User;
|
use App\Services\PronounService;
|
||||||
use App\UserFilter;
|
|
||||||
use App\Util\Lexer\Autolink;
|
use App\Util\Lexer\Autolink;
|
||||||
use App\Util\Lexer\PrettyNumber;
|
use App\Util\Lexer\PrettyNumber;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Cache;
|
use Cache;
|
||||||
use DB;
|
use Illuminate\Http\Request;
|
||||||
use Mail;
|
use Mail;
|
||||||
use Purify;
|
use Purify;
|
||||||
use App\Mail\PasswordChange;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Services\AccountService;
|
|
||||||
use App\Services\PronounService;
|
|
||||||
|
|
||||||
trait HomeSettings
|
trait HomeSettings
|
||||||
{
|
{
|
||||||
|
@ -44,7 +40,7 @@ trait HomeSettings
|
||||||
'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'),
|
'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'),
|
||||||
'website' => 'nullable|url',
|
'website' => 'nullable|url',
|
||||||
'language' => 'nullable|string|min:2|max:5',
|
'language' => 'nullable|string|min:2|max:5',
|
||||||
'pronouns' => 'nullable|array|max:4'
|
'pronouns' => 'nullable|array|max:4',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$changes = false;
|
$changes = false;
|
||||||
|
@ -102,7 +98,9 @@ trait HomeSettings
|
||||||
$user->save();
|
$user->save();
|
||||||
$profile->save();
|
$profile->save();
|
||||||
Cache::forget('user:account:id:'.$user->id);
|
Cache::forget('user:account:id:'.$user->id);
|
||||||
|
AccountService::forgetAccountSettings($profile->id);
|
||||||
AccountService::del($profile->id);
|
AccountService::del($profile->id);
|
||||||
|
|
||||||
return redirect('/settings/home')->with('status', 'Profile successfully updated!');
|
return redirect('/settings/home')->with('status', 'Profile successfully updated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +142,7 @@ trait HomeSettings
|
||||||
$log->save();
|
$log->save();
|
||||||
|
|
||||||
Mail::to($request->user())->send(new PasswordChange($user));
|
Mail::to($request->user())->send(new PasswordChange($user));
|
||||||
|
|
||||||
return redirect('/settings/home')->with('status', 'Password successfully updated!');
|
return redirect('/settings/home')->with('status', 'Password successfully updated!');
|
||||||
} else {
|
} else {
|
||||||
return redirect()->back()->with('error', 'There was an error with your request! Please try again.');
|
return redirect()->back()->with('error', 'There was an error with your request! Please try again.');
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
use App\Follower;
|
use App\Follower;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
|
use App\Services\AccountService;
|
||||||
use App\Services\RelationshipService;
|
use App\Services\RelationshipService;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
use Auth;
|
use Auth;
|
||||||
|
@ -19,7 +20,13 @@ trait PrivacySettings
|
||||||
$settings = $user->settings;
|
$settings = $user->settings;
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
$is_private = $profile->is_private;
|
$is_private = $profile->is_private;
|
||||||
|
$cachedSettings = AccountService::getAccountSettings($profile->id);
|
||||||
$settings['is_private'] = (bool) $is_private;
|
$settings['is_private'] = (bool) $is_private;
|
||||||
|
if ($cachedSettings && isset($cachedSettings['disable_embeds'])) {
|
||||||
|
$settings['disable_embeds'] = (bool) $cachedSettings['disable_embeds'];
|
||||||
|
} else {
|
||||||
|
$settings['disable_embeds'] = false;
|
||||||
|
}
|
||||||
|
|
||||||
return view('settings.privacy', compact('settings', 'profile'));
|
return view('settings.privacy', compact('settings', 'profile'));
|
||||||
}
|
}
|
||||||
|
@ -28,6 +35,7 @@ trait PrivacySettings
|
||||||
{
|
{
|
||||||
$settings = $request->user()->settings;
|
$settings = $request->user()->settings;
|
||||||
$profile = $request->user()->profile;
|
$profile = $request->user()->profile;
|
||||||
|
$other = $settings->other;
|
||||||
$fields = [
|
$fields = [
|
||||||
'is_private',
|
'is_private',
|
||||||
'crawlable',
|
'crawlable',
|
||||||
|
@ -42,6 +50,16 @@ trait PrivacySettings
|
||||||
$profile->is_suggestable = $request->input('is_suggestable') == 'on';
|
$profile->is_suggestable = $request->input('is_suggestable') == 'on';
|
||||||
$profile->save();
|
$profile->save();
|
||||||
|
|
||||||
|
if ($request->has('disable_embeds')) {
|
||||||
|
$other['disable_embeds'] = true;
|
||||||
|
$settings->other = $other;
|
||||||
|
$settings->save();
|
||||||
|
} else {
|
||||||
|
$other['disable_embeds'] = false;
|
||||||
|
$settings->other = $other;
|
||||||
|
$settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
$form = $request->input($field);
|
$form = $request->input($field);
|
||||||
if ($field == 'is_private') {
|
if ($field == 'is_private') {
|
||||||
|
@ -91,6 +109,7 @@ trait PrivacySettings
|
||||||
Cache::forget('pf:acct-trans:hideFollowers:'.$pid);
|
Cache::forget('pf:acct-trans:hideFollowers:'.$pid);
|
||||||
Cache::forget('pfc:cached-user:wt:'.strtolower($profile->username));
|
Cache::forget('pfc:cached-user:wt:'.strtolower($profile->username));
|
||||||
Cache::forget('pfc:cached-user:wot:'.strtolower($profile->username));
|
Cache::forget('pfc:cached-user:wot:'.strtolower($profile->username));
|
||||||
|
AccountService::forgetAccountSettings($profile->id);
|
||||||
|
|
||||||
return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
|
return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,34 +2,30 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\AccountLog;
|
use App\Http\Controllers\Settings\ExportSettings;
|
||||||
use App\Following;
|
use App\Http\Controllers\Settings\HomeSettings;
|
||||||
use App\ProfileSponsor;
|
use App\Http\Controllers\Settings\LabsSettings;
|
||||||
use App\Report;
|
use App\Http\Controllers\Settings\PrivacySettings;
|
||||||
use App\UserFilter;
|
use App\Http\Controllers\Settings\RelationshipSettings;
|
||||||
use App\UserSetting;
|
use App\Http\Controllers\Settings\SecuritySettings;
|
||||||
use Auth, Cookie, DB, Cache, Purify;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use App\Http\Controllers\Settings\{
|
|
||||||
ExportSettings,
|
|
||||||
LabsSettings,
|
|
||||||
HomeSettings,
|
|
||||||
PrivacySettings,
|
|
||||||
RelationshipSettings,
|
|
||||||
SecuritySettings
|
|
||||||
};
|
|
||||||
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
||||||
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
|
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
|
||||||
|
use App\ProfileSponsor;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use App\UserSetting;
|
||||||
|
use Auth;
|
||||||
|
use Cache;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Cookie;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class SettingsController extends Controller
|
class SettingsController extends Controller
|
||||||
{
|
{
|
||||||
use ExportSettings,
|
use ExportSettings,
|
||||||
LabsSettings,
|
|
||||||
HomeSettings,
|
HomeSettings,
|
||||||
|
LabsSettings,
|
||||||
PrivacySettings,
|
PrivacySettings,
|
||||||
RelationshipSettings,
|
RelationshipSettings,
|
||||||
SecuritySettings;
|
SecuritySettings;
|
||||||
|
@ -48,7 +44,8 @@ class SettingsController extends Controller
|
||||||
|
|
||||||
public function accessibilityStore(Request $request)
|
public function accessibilityStore(Request $request)
|
||||||
{
|
{
|
||||||
$settings = Auth::user()->settings;
|
$user = $request->user();
|
||||||
|
$settings = $user->settings;
|
||||||
$fields = [
|
$fields = [
|
||||||
'compose_media_descriptions',
|
'compose_media_descriptions',
|
||||||
'reduce_motion',
|
'reduce_motion',
|
||||||
|
@ -65,6 +62,7 @@ class SettingsController extends Controller
|
||||||
}
|
}
|
||||||
$settings->save();
|
$settings->save();
|
||||||
}
|
}
|
||||||
|
AccountService::forgetAccountSettings($user->profile_id);
|
||||||
|
|
||||||
return redirect(route('settings.accessibility'))->with('status', 'Settings successfully updated!');
|
return redirect(route('settings.accessibility'))->with('status', 'Settings successfully updated!');
|
||||||
}
|
}
|
||||||
|
@ -115,6 +113,7 @@ class SettingsController extends Controller
|
||||||
$profile->save();
|
$profile->save();
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
Cache::forget('profiles:private');
|
Cache::forget('profiles:private');
|
||||||
|
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +121,7 @@ class SettingsController extends Controller
|
||||||
{
|
{
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
abort_if($user->is_admin, 403);
|
abort_if($user->is_admin, 403);
|
||||||
|
|
||||||
return view('settings.remove.permanent');
|
return view('settings.remove.permanent');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,19 +147,21 @@ class SettingsController extends Controller
|
||||||
AccountService::del($profile->id);
|
AccountService::del($profile->id);
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
DeleteAccountPipeline::dispatch($user)->onQueue('low');
|
DeleteAccountPipeline::dispatch($user)->onQueue('low');
|
||||||
|
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function requestFullExport(Request $request)
|
public function requestFullExport(Request $request)
|
||||||
{
|
{
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
return view('settings.export.show');
|
return view('settings.export.show');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function metroDarkMode(Request $request)
|
public function metroDarkMode(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'mode' => 'required|string|in:light,dark'
|
'mode' => 'required|string|in:light,dark',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$mode = $request->input('mode');
|
$mode = $request->input('mode');
|
||||||
|
@ -178,10 +180,11 @@ class SettingsController extends Controller
|
||||||
$default = [
|
$default = [
|
||||||
'patreon' => null,
|
'patreon' => null,
|
||||||
'liberapay' => null,
|
'liberapay' => null,
|
||||||
'opencollective' => null
|
'opencollective' => null,
|
||||||
];
|
];
|
||||||
$sponsors = ProfileSponsor::whereProfileId(Auth::user()->profile->id)->first();
|
$sponsors = ProfileSponsor::whereProfileId(Auth::user()->profile->id)->first();
|
||||||
$sponsors = $sponsors ? json_decode($sponsors->sponsors, true) : $default;
|
$sponsors = $sponsors ? json_decode($sponsors->sponsors, true) : $default;
|
||||||
|
|
||||||
return view('settings.sponsor', compact('sponsors'));
|
return view('settings.sponsor', compact('sponsors'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +193,7 @@ class SettingsController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'patreon' => 'nullable|string',
|
'patreon' => 'nullable|string',
|
||||||
'liberapay' => 'nullable|string',
|
'liberapay' => 'nullable|string',
|
||||||
'opencollective' => 'nullable|string'
|
'opencollective' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$patreon = Str::startsWith($request->input('patreon'), 'https://') ?
|
$patreon = Str::startsWith($request->input('patreon'), 'https://') ?
|
||||||
|
@ -216,15 +219,16 @@ class SettingsController extends Controller
|
||||||
$res = [
|
$res = [
|
||||||
'patreon' => $patreon,
|
'patreon' => $patreon,
|
||||||
'liberapay' => $liberapay,
|
'liberapay' => $liberapay,
|
||||||
'opencollective' => $opencollective
|
'opencollective' => $opencollective,
|
||||||
];
|
];
|
||||||
|
|
||||||
$sponsors = ProfileSponsor::firstOrCreate([
|
$sponsors = ProfileSponsor::firstOrCreate([
|
||||||
'profile_id' => Auth::user()->profile_id ?? Auth::user()->profile->id
|
'profile_id' => Auth::user()->profile_id ?? Auth::user()->profile->id,
|
||||||
]);
|
]);
|
||||||
$sponsors->sponsors = json_encode($res);
|
$sponsors->sponsors = json_encode($res);
|
||||||
$sponsors->save();
|
$sponsors->save();
|
||||||
$sponsors = $res;
|
$sponsors = $res;
|
||||||
|
|
||||||
return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');
|
return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,20 +239,21 @@ class SettingsController extends Controller
|
||||||
$top = Redis::zscore('pf:tl:top', $pid) != false;
|
$top = Redis::zscore('pf:tl:top', $pid) != false;
|
||||||
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
|
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
|
||||||
$userSettings = UserSetting::firstOrCreate([
|
$userSettings = UserSetting::firstOrCreate([
|
||||||
'user_id' => $uid
|
'user_id' => $uid,
|
||||||
]);
|
]);
|
||||||
if (! $userSettings || ! $userSettings->other) {
|
if (! $userSettings || ! $userSettings->other) {
|
||||||
$userSettings = [
|
$userSettings = [
|
||||||
'enable_reblogs' => false,
|
'enable_reblogs' => false,
|
||||||
'photo_reblogs_only' => false
|
'photo_reblogs_only' => false,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$userSettings = array_merge([
|
$userSettings = array_merge([
|
||||||
'enable_reblogs' => false,
|
'enable_reblogs' => false,
|
||||||
'photo_reblogs_only' => false
|
'photo_reblogs_only' => false,
|
||||||
],
|
],
|
||||||
$userSettings->other);
|
$userSettings->other);
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('settings.timeline', compact('top', 'replies', 'userSettings'));
|
return view('settings.timeline', compact('top', 'replies', 'userSettings'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,12 +263,12 @@ class SettingsController extends Controller
|
||||||
$uid = $request->user()->id;
|
$uid = $request->user()->id;
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'enable_reblogs' => 'sometimes',
|
'enable_reblogs' => 'sometimes',
|
||||||
'photo_reblogs_only' => 'sometimes'
|
'photo_reblogs_only' => 'sometimes',
|
||||||
]);
|
]);
|
||||||
Redis::zrem('pf:tl:top', $pid);
|
Redis::zrem('pf:tl:top', $pid);
|
||||||
Redis::zrem('pf:tl:replies', $pid);
|
Redis::zrem('pf:tl:replies', $pid);
|
||||||
$userSettings = UserSetting::firstOrCreate([
|
$userSettings = UserSetting::firstOrCreate([
|
||||||
'user_id' => $uid
|
'user_id' => $uid,
|
||||||
]);
|
]);
|
||||||
if ($userSettings->other) {
|
if ($userSettings->other) {
|
||||||
$other = $userSettings->other;
|
$other = $userSettings->other;
|
||||||
|
@ -275,6 +280,7 @@ class SettingsController extends Controller
|
||||||
}
|
}
|
||||||
$userSettings->other = $other;
|
$userSettings->other = $other;
|
||||||
$userSettings->save();
|
$userSettings->save();
|
||||||
|
|
||||||
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
|
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,8 +291,9 @@ class SettingsController extends Controller
|
||||||
is_string($setting->compose_settings) ? json_decode($setting->compose_settings, true) : $setting->compose_settings
|
is_string($setting->compose_settings) ? json_decode($setting->compose_settings, true) : $setting->compose_settings
|
||||||
) : [
|
) : [
|
||||||
'default_license' => null,
|
'default_license' => null,
|
||||||
'media_descriptions' => false
|
'media_descriptions' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
return view('settings.media', compact('compose'));
|
return view('settings.media', compact('compose'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +302,7 @@ class SettingsController extends Controller
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'default' => 'required|int|min:1|max:16',
|
'default' => 'required|int|min:1|max:16',
|
||||||
'sync' => 'nullable',
|
'sync' => 'nullable',
|
||||||
'media_descriptions' => 'nullable'
|
'media_descriptions' => 'nullable',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$license = $request->input('default');
|
$license = $request->input('default');
|
||||||
|
@ -311,6 +318,7 @@ class SettingsController extends Controller
|
||||||
$key = 'pf:settings:mls_recently:'.$uid;
|
$key = 'pf:settings:mls_recently:'.$uid;
|
||||||
if (Cache::get($key) == 2) {
|
if (Cache::get($key) == 2) {
|
||||||
$msg = 'You can only sync licenses twice per 24 hours. Try again later.';
|
$msg = 'You can only sync licenses twice per 24 hours. Try again later.';
|
||||||
|
|
||||||
return redirect(route('settings'))
|
return redirect(route('settings'))
|
||||||
->with('error', $msg);
|
->with('error', $msg);
|
||||||
}
|
}
|
||||||
|
@ -336,11 +344,10 @@ class SettingsController extends Controller
|
||||||
$val = Cache::has($key) ? 2 : 1;
|
$val = Cache::has($key) ? 2 : 1;
|
||||||
Cache::put($key, $val, 86400);
|
Cache::put($key, $val, 86400);
|
||||||
MediaSyncLicensePipeline::dispatch($uid, $license);
|
MediaSyncLicensePipeline::dispatch($uid, $license);
|
||||||
|
|
||||||
return redirect(route('settings'))->with('status', 'Media licenses successfully synced! It may take a few minutes to take effect for every post.');
|
return redirect(route('settings'))->with('status', 'Media licenses successfully synced! It may take a few minutes to take effect for every post.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect(route('settings'))->with('status', 'Media settings successfully updated!');
|
return redirect(route('settings'))->with('status', 'Media settings successfully updated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace App\Http\Controllers;
|
||||||
use App\Page;
|
use App\Page;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Services\FollowerService;
|
use App\Services\FollowerService;
|
||||||
use App\Status;
|
use App\Services\StatusService;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
use App\Util\Localization\Localization;
|
use App\Util\Localization\Localization;
|
||||||
|
@ -60,7 +60,7 @@ class SiteController extends Controller
|
||||||
{
|
{
|
||||||
return Cache::remember('site.about_v2', now()->addMinutes(15), function () {
|
return Cache::remember('site.about_v2', now()->addMinutes(15), function () {
|
||||||
$user_count = number_format(User::count());
|
$user_count = number_format(User::count());
|
||||||
$post_count = number_format(Status::count());
|
$post_count = number_format(StatusService::totalLocalStatuses());
|
||||||
$rules = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : null;
|
$rules = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : null;
|
||||||
|
|
||||||
return view('site.about', compact('rules', 'user_count', 'post_count'))->render();
|
return view('site.about', compact('rules', 'user_count', 'post_count'))->render();
|
||||||
|
|
|
@ -35,8 +35,21 @@ class StatusController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
$status = StatusService::get($id, false);
|
||||||
|
|
||||||
|
abort_if(
|
||||||
|
! $status ||
|
||||||
|
! isset($status['account'], $status['account']['username']) ||
|
||||||
|
$status['account']['username'] != $username ||
|
||||||
|
isset($status['reblog']), 404);
|
||||||
|
|
||||||
|
abort_if(! in_array($status['visibility'], ['public', 'unlisted']) && ! $request->user(), 403, 'Invalid permission');
|
||||||
|
|
||||||
|
if ($request->wantsJson() && (bool) config_cache('federation.activitypub.enabled')) {
|
||||||
|
return $this->showActivityPub($request, $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||||
if ($user->status != null) {
|
if ($user->status != null) {
|
||||||
return ProfileController::accountCheck($user);
|
return ProfileController::accountCheck($user);
|
||||||
}
|
}
|
||||||
|
@ -71,18 +84,6 @@ class StatusController extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->user() && $request->user()->profile_id != $status->profile_id) {
|
|
||||||
StatusView::firstOrCreate([
|
|
||||||
'status_id' => $status->id,
|
|
||||||
'status_profile_id' => $status->profile_id,
|
|
||||||
'profile_id' => $request->user()->profile_id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->wantsJson() && (bool) config_cache('federation.activitypub.enabled')) {
|
|
||||||
return $this->showActivityPub($request, $status);
|
|
||||||
}
|
|
||||||
|
|
||||||
$template = $status->in_reply_to_id ? 'status.reply' : 'status.show';
|
$template = $status->in_reply_to_id ? 'status.reply' : 'status.show';
|
||||||
|
|
||||||
return view($template, compact('user', 'status'));
|
return view($template, compact('user', 'status'));
|
||||||
|
@ -120,7 +121,8 @@ class StatusController extends Controller
|
||||||
! $status ||
|
! $status ||
|
||||||
! isset($status['account'], $status['account']['id'], $status['local']) ||
|
! isset($status['account'], $status['account']['id'], $status['local']) ||
|
||||||
! $status['local'] ||
|
! $status['local'] ||
|
||||||
strtolower($status['account']['username']) !== strtolower($username)
|
strtolower($status['account']['username']) !== strtolower($username) ||
|
||||||
|
isset($status['account']['moved'], $status['account']['moved']['id'])
|
||||||
) {
|
) {
|
||||||
$content = view('status.embed-removed');
|
$content = view('status.embed-removed');
|
||||||
|
|
||||||
|
@ -135,6 +137,14 @@ class StatusController extends Controller
|
||||||
return response($content)->header('X-Frame-Options', 'ALLOWALL');
|
return response($content)->header('X-Frame-Options', 'ALLOWALL');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$embedCheck = AccountService::canEmbed($profile['id']);
|
||||||
|
|
||||||
|
if (! $embedCheck) {
|
||||||
|
$content = view('status.embed-removed');
|
||||||
|
|
||||||
|
return response($content)->header('X-Frame-Options', 'ALLOWALL');
|
||||||
|
}
|
||||||
|
|
||||||
$aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 3600, function () use ($profile) {
|
$aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 3600, function () use ($profile) {
|
||||||
$user = Profile::find($profile['id']);
|
$user = Profile::find($profile['id']);
|
||||||
if (! $user) {
|
if (! $user) {
|
||||||
|
@ -162,7 +172,7 @@ class StatusController extends Controller
|
||||||
intval($status['account']['id']) !== intval($profile['id']) ||
|
intval($status['account']['id']) !== intval($profile['id']) ||
|
||||||
$status['sensitive'] ||
|
$status['sensitive'] ||
|
||||||
$status['visibility'] !== 'public' ||
|
$status['visibility'] !== 'public' ||
|
||||||
$status['pf_type'] !== 'photo'
|
! in_array($status['pf_type'], ['photo', 'photo:album'])
|
||||||
) {
|
) {
|
||||||
$content = view('status.embed-removed');
|
$content = view('status.embed-removed');
|
||||||
|
|
||||||
|
@ -211,10 +221,7 @@ class StatusController extends Controller
|
||||||
return view('status.compose');
|
return view('status.compose');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request) {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(Request $request)
|
public function delete(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -298,6 +305,8 @@ class StatusController extends Controller
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
$status = Status::whereScope('public')
|
$status = Status::whereScope('public')
|
||||||
->findOrFail($request->input('item'));
|
->findOrFail($request->input('item'));
|
||||||
|
$statusAccount = AccountService::get($status->profile_id);
|
||||||
|
abort_if(! $statusAccount || isset($statusAccount['moved'], $statusAccount['moved']['id']), 422, 'Account moved');
|
||||||
|
|
||||||
$count = $status->reblogs_count;
|
$count = $status->reblogs_count;
|
||||||
|
|
||||||
|
@ -314,7 +323,7 @@ class StatusController extends Controller
|
||||||
$count--;
|
$count--;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$share = new Status();
|
$share = new Status;
|
||||||
$share->profile_id = $profile->id;
|
$share->profile_id = $profile->id;
|
||||||
$share->reblog_of_id = $status->id;
|
$share->reblog_of_id = $status->id;
|
||||||
$share->in_reply_to_profile_id = $status->profile_id;
|
$share->in_reply_to_profile_id = $status->profile_id;
|
||||||
|
@ -339,12 +348,17 @@ class StatusController extends Controller
|
||||||
|
|
||||||
public function showActivityPub(Request $request, $status)
|
public function showActivityPub(Request $request, $status)
|
||||||
{
|
{
|
||||||
$object = $status->type == 'poll' ? new Question() : new Note();
|
$key = 'pf:status:ap:v1:sid:'.$status['id'];
|
||||||
$fractal = new Fractal\Manager();
|
|
||||||
|
return Cache::remember($key, 3600, function () use ($status) {
|
||||||
|
$status = Status::findOrFail($status['id']);
|
||||||
|
$object = $status->type == 'poll' ? new Question : new Note;
|
||||||
|
$fractal = new Fractal\Manager;
|
||||||
$resource = new Fractal\Resource\Item($status, $object);
|
$resource = new Fractal\Resource\Item($status, $object);
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function edit(Request $request, $username, $id)
|
public function edit(Request $request, $username, $id)
|
||||||
|
|
|
@ -54,6 +54,7 @@ class Kernel extends HttpKernel
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $routeMiddleware = [
|
protected $routeMiddleware = [
|
||||||
|
'api.admin' => \App\Http\Middleware\Api\Admin::class,
|
||||||
'admin' => \App\Http\Middleware\Admin::class,
|
'admin' => \App\Http\Middleware\Admin::class,
|
||||||
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
|
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
|
||||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
|
@ -68,6 +69,8 @@ class Kernel extends HttpKernel
|
||||||
'twofactor' => \App\Http\Middleware\TwoFactorAuth::class,
|
'twofactor' => \App\Http\Middleware\TwoFactorAuth::class,
|
||||||
'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
|
'validemail' => \App\Http\Middleware\EmailVerificationCheck::class,
|
||||||
'interstitial' => \App\Http\Middleware\AccountInterstitial::class,
|
'interstitial' => \App\Http\Middleware\AccountInterstitial::class,
|
||||||
|
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
|
||||||
|
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
|
||||||
// 'restricted' => \App\Http\Middleware\RestrictedAccess::class,
|
// 'restricted' => \App\Http\Middleware\RestrictedAccess::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
26
app/Http/Middleware/Api/Admin.php
Normal file
26
app/Http/Middleware/Api/Admin.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware\Api;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
|
use Closure;
|
||||||
|
|
||||||
|
class Admin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
|
if (Auth::check() == false || Auth::user()->is_admin == false) {
|
||||||
|
return abort(403, "You must be an administrator to do that");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
45
app/Http/Resources/Admin/AdminModeratedProfileResource.php
Normal file
45
app/Http/Resources/Admin/AdminModeratedProfileResource.php
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\Admin;
|
||||||
|
|
||||||
|
use App\Profile;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class AdminModeratedProfileResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
$profileObj = [];
|
||||||
|
$profile = Profile::withTrashed()->find($this->profile_id);
|
||||||
|
if ($profile) {
|
||||||
|
$profileObj = [
|
||||||
|
'name' => $profile->name,
|
||||||
|
'username' => $profile->username,
|
||||||
|
'username_str' => explode('@', $profile->username)[1],
|
||||||
|
'remote_url' => $profile->remote_url,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'domain' => $this->domain,
|
||||||
|
'profile' => $profileObj,
|
||||||
|
'profile_id' => $this->profile_id,
|
||||||
|
'profile_url' => $this->profile_url,
|
||||||
|
'note' => $this->note,
|
||||||
|
'is_banned' => (bool) $this->is_banned,
|
||||||
|
'is_nsfw' => (bool) $this->is_nsfw,
|
||||||
|
'is_unlisted' => (bool) $this->is_unlisted,
|
||||||
|
'is_noautolink' => (bool) $this->is_noautolink,
|
||||||
|
'is_nodms' => (bool) $this->is_nodms,
|
||||||
|
'is_notrending' => (bool) $this->is_notrending,
|
||||||
|
'created_at' => now()->parse($this->created_at)->format('c'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
use App\Story;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
class AdminReport extends JsonResource
|
class AdminReport extends JsonResource
|
||||||
{
|
{
|
||||||
|
@ -33,6 +34,13 @@ class AdminReport extends JsonResource
|
||||||
$res['status'] = StatusService::get($this->object_id, false);
|
$res['status'] = StatusService::get($this->object_id, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->object_id && $this->object_type === 'App\Story') {
|
||||||
|
$story = Story::find($this->object_id);
|
||||||
|
if ($story) {
|
||||||
|
$res['story'] = $story->toAdminEntity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
class AdminUser extends JsonResource
|
class AdminUser extends JsonResource
|
||||||
{
|
{
|
||||||
|
@ -18,8 +18,8 @@ class AdminUser extends JsonResource
|
||||||
$account = AccountService::get($this->profile_id, true);
|
$account = AccountService::get($this->profile_id, true);
|
||||||
|
|
||||||
$res = [
|
$res = [
|
||||||
'id' => $this->id,
|
'id' => (string) $this->id,
|
||||||
'profile_id' => $this->profile_id,
|
'profile_id' => (string) $this->profile_id,
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'username' => $this->username,
|
'username' => $this->username,
|
||||||
'is_admin' => (bool) $this->is_admin,
|
'is_admin' => (bool) $this->is_admin,
|
||||||
|
@ -28,6 +28,7 @@ class AdminUser extends JsonResource
|
||||||
'two_factor_enabled' => (bool) $this->{'2fa_enabled'},
|
'two_factor_enabled' => (bool) $this->{'2fa_enabled'},
|
||||||
'register_source' => $this->register_source,
|
'register_source' => $this->register_source,
|
||||||
'app_register_ip' => $this->app_register_ip,
|
'app_register_ip' => $this->app_register_ip,
|
||||||
|
'has_interstitial' => (bool) $this->has_interstitial,
|
||||||
'last_active_at' => $this->last_active_at,
|
'last_active_at' => $this->last_active_at,
|
||||||
'created_at' => $this->created_at,
|
'created_at' => $this->created_at,
|
||||||
];
|
];
|
||||||
|
@ -35,10 +36,10 @@ class AdminUser extends JsonResource
|
||||||
if ($account) {
|
if ($account) {
|
||||||
$res['avatar'] = $account['avatar'];
|
$res['avatar'] = $account['avatar'];
|
||||||
$res['bio'] = $account['note_text'];
|
$res['bio'] = $account['note_text'];
|
||||||
$res['statuses_count'] = $account['statuses_count'];
|
$res['statuses_count'] = (int) $account['statuses_count'];
|
||||||
$res['following_count'] = $account['following_count'];
|
$res['following_count'] = (int) $account['following_count'];
|
||||||
$res['followers_count'] = $account['followers_count'];
|
$res['followers_count'] = (int) $account['followers_count'];
|
||||||
$res['is_private'] = $account['locked'];
|
$res['is_private'] = (bool) $account['locked'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
|
|
42
app/Http/Resources/MastoApi/Admin/DomainBlockResource.php
Normal file
42
app/Http/Resources/MastoApi/Admin/DomainBlockResource.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources\MastoApi\Admin;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class DomainBlockResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
$severity = 'noop';
|
||||||
|
if ($this->banned) {
|
||||||
|
$severity = 'suspend';
|
||||||
|
} else if ($this->unlisted) {
|
||||||
|
$severity = 'silence';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'domain' => $this->domain,
|
||||||
|
// This property is coming in Mastodon 4.3, although it'll only be
|
||||||
|
// useful if Pixelfed supports obfuscating domains:
|
||||||
|
'digest' => hash('sha256', $this->domain),
|
||||||
|
'severity' => $severity,
|
||||||
|
// Using the updated_at value as this is going to be the closest to
|
||||||
|
// when the domain was banned
|
||||||
|
'created_at' => $this->updated_at,
|
||||||
|
// We don't have data for these fields
|
||||||
|
'reject_media' => false,
|
||||||
|
'reject_reports' => false,
|
||||||
|
'private_comment' => $this->notes ? join('; ', $this->notes) : null,
|
||||||
|
'public_comment' => $this->limit_reason,
|
||||||
|
'obfuscate' => false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,13 @@ class Instance extends Model
|
||||||
'notes'
|
'notes'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// To get all moderated instances, we need to search where (banned OR unlisted)
|
||||||
|
public function scopeModerated($query): void {
|
||||||
|
$query->where(function ($query) {
|
||||||
|
$query->where('banned', true)->orWhere('unlisted', true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function profiles()
|
public function profiles()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Profile::class, 'domain', 'domain');
|
return $this->hasMany(Profile::class, 'domain', 'domain');
|
||||||
|
|
|
@ -3,7 +3,13 @@
|
||||||
namespace App\Jobs\FollowPipeline;
|
namespace App\Jobs\FollowPipeline;
|
||||||
|
|
||||||
use App\Follower;
|
use App\Follower;
|
||||||
|
use App\Jobs\PushNotificationPipeline\FollowPushNotifyPipeline;
|
||||||
use App\Notification;
|
use App\Notification;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\FollowerService;
|
||||||
|
use App\Services\NotificationAppGatewayService;
|
||||||
|
use App\Services\PushNotificationService;
|
||||||
|
use App\User;
|
||||||
use Cache;
|
use Cache;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
@ -11,9 +17,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Log;
|
use Log;
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use App\Services\AccountService;
|
|
||||||
use App\Services\FollowerService;
|
|
||||||
|
|
||||||
class FollowPipeline implements ShouldQueue
|
class FollowPipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -74,7 +77,7 @@ class FollowPipeline implements ShouldQueue
|
||||||
|
|
||||||
if ($target->user_id && $target->domain === null) {
|
if ($target->user_id && $target->domain === null) {
|
||||||
try {
|
try {
|
||||||
$notification = new Notification();
|
$notification = new Notification;
|
||||||
$notification->profile_id = $target->id;
|
$notification->profile_id = $target->id;
|
||||||
$notification->actor_id = $actor->id;
|
$notification->actor_id = $actor->id;
|
||||||
$notification->action = 'follow';
|
$notification->action = 'follow';
|
||||||
|
@ -84,6 +87,15 @@ class FollowPipeline implements ShouldQueue
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error($e);
|
Log::error($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (NotificationAppGatewayService::enabled()) {
|
||||||
|
if (PushNotificationService::check('follow', $target->id)) {
|
||||||
|
$user = User::whereProfileId($target->id)->first();
|
||||||
|
if ($user && $user->expo_token && $user->notify_enabled) {
|
||||||
|
FollowPushNotifyPipeline::dispatch($user->expo_token, $actor->username)->onQueue('pushnotify');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
99
app/Jobs/GroupPipeline/GroupCommentPipeline.php
Normal file
99
app/Jobs/GroupPipeline/GroupCommentPipeline.php
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
|
use App\Notification;
|
||||||
|
use App\Status;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use Cache;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use App\Services\MediaStorageService;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
|
||||||
|
class GroupCommentPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $status;
|
||||||
|
protected $comment;
|
||||||
|
protected $groupPost;
|
||||||
|
|
||||||
|
public function __construct(Status $status, Status $comment, $groupPost = null)
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
$this->comment = $comment;
|
||||||
|
$this->groupPost = $groupPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if($this->status->group_id == null || $this->comment->group_id == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->updateParentReplyCount();
|
||||||
|
$this->generateNotification();
|
||||||
|
|
||||||
|
if($this->groupPost) {
|
||||||
|
$this->updateChildReplyCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateParentReplyCount()
|
||||||
|
{
|
||||||
|
$parent = $this->status;
|
||||||
|
$parent->reply_count = Status::whereInReplyToId($parent->id)->count();
|
||||||
|
$parent->save();
|
||||||
|
StatusService::del($parent->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateChildReplyCount()
|
||||||
|
{
|
||||||
|
$gp = $this->groupPost;
|
||||||
|
if($gp->reply_child_id) {
|
||||||
|
$parent = GroupPost::whereStatusId($gp->reply_child_id)->first();
|
||||||
|
if($parent) {
|
||||||
|
$parent->reply_count++;
|
||||||
|
$parent->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateNotification()
|
||||||
|
{
|
||||||
|
$status = $this->status;
|
||||||
|
$comment = $this->comment;
|
||||||
|
|
||||||
|
$target = $status->profile;
|
||||||
|
$actor = $comment->profile;
|
||||||
|
|
||||||
|
if ($actor->id == $target->id || $status->comments_disabled == true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$notification = DB::transaction(function() use($target, $actor, $comment) {
|
||||||
|
$actorName = $actor->username;
|
||||||
|
$actorUrl = $actor->url();
|
||||||
|
$text = "{$actorName} commented on your group post.";
|
||||||
|
$html = "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> commented on your group post.";
|
||||||
|
$notification = new Notification();
|
||||||
|
$notification->profile_id = $target->id;
|
||||||
|
$notification->actor_id = $actor->id;
|
||||||
|
$notification->action = 'group:comment';
|
||||||
|
$notification->item_id = $comment->id;
|
||||||
|
$notification->item_type = "App\Status";
|
||||||
|
$notification->save();
|
||||||
|
return $notification;
|
||||||
|
});
|
||||||
|
|
||||||
|
NotificationService::setNotification($notification);
|
||||||
|
NotificationService::set($notification->profile_id, $notification->id);
|
||||||
|
}
|
||||||
|
}
|
57
app/Jobs/GroupPipeline/GroupMediaPipeline.php
Normal file
57
app/Jobs/GroupPipeline/GroupMediaPipeline.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
|
use App\Media;
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use App\Services\MediaStorageService;
|
||||||
|
|
||||||
|
class GroupMediaPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $media;
|
||||||
|
|
||||||
|
public function __construct(Media $media)
|
||||||
|
{
|
||||||
|
$this->media = $media;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
MediaStorageService::store($this->media);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function localToCloud($media)
|
||||||
|
{
|
||||||
|
$path = storage_path('app/'.$media->media_path);
|
||||||
|
$thumb = storage_path('app/'.$media->thumbnail_path);
|
||||||
|
|
||||||
|
$p = explode('/', $media->media_path);
|
||||||
|
$name = array_pop($p);
|
||||||
|
$pt = explode('/', $media->thumbnail_path);
|
||||||
|
$thumbname = array_pop($pt);
|
||||||
|
$storagePath = implode('/', $p);
|
||||||
|
|
||||||
|
$disk = Storage::disk(config('filesystems.cloud'));
|
||||||
|
$file = $disk->putFileAs($storagePath, new File($path), $name, 'public');
|
||||||
|
$url = $disk->url($file);
|
||||||
|
$thumbFile = $disk->putFileAs($storagePath, new File($thumb), $thumbname, 'public');
|
||||||
|
$thumbUrl = $disk->url($thumbFile);
|
||||||
|
$media->thumbnail_url = $thumbUrl;
|
||||||
|
$media->cdn_url = $url;
|
||||||
|
$media->optimized_url = $url;
|
||||||
|
$media->replicated_at = now();
|
||||||
|
$media->save();
|
||||||
|
if($media->status_id) {
|
||||||
|
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
54
app/Jobs/GroupPipeline/GroupMemberInvite.php
Normal file
54
app/Jobs/GroupPipeline/GroupMemberInvite.php
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\GroupInvitation;
|
||||||
|
use App\Notification;
|
||||||
|
use App\Profile;
|
||||||
|
|
||||||
|
class GroupMemberInvite implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $invite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupInvitation $invite)
|
||||||
|
{
|
||||||
|
$this->invite = $invite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$invite = $this->invite;
|
||||||
|
$actor = Profile::find($invite->from_profile_id);
|
||||||
|
$target = Profile::find($invite->to_profile_id);
|
||||||
|
|
||||||
|
if(!$actor || !$target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$notification = new Notification;
|
||||||
|
$notification->profile_id = $target->id;
|
||||||
|
$notification->actor_id = $actor->id;
|
||||||
|
$notification->action = 'group:invite';
|
||||||
|
$notification->item_id = $invite->group_id;
|
||||||
|
$notification->item_type = 'App\Models\Group';
|
||||||
|
$notification->save();
|
||||||
|
}
|
||||||
|
}
|
54
app/Jobs/GroupPipeline/JoinApproved.php
Normal file
54
app/Jobs/GroupPipeline/JoinApproved.php
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Notification;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
|
||||||
|
class JoinApproved implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $member;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupMember $member)
|
||||||
|
{
|
||||||
|
$this->member = $member;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$member = $this->member;
|
||||||
|
$member->approved_at = now();
|
||||||
|
$member->join_request = false;
|
||||||
|
$member->role = 'member';
|
||||||
|
$member->save();
|
||||||
|
|
||||||
|
$n = new Notification;
|
||||||
|
$n->profile_id = $member->profile_id;
|
||||||
|
$n->actor_id = $member->profile_id;
|
||||||
|
$n->item_id = $member->group_id;
|
||||||
|
$n->item_type = 'App\Models\Group';
|
||||||
|
$n->save();
|
||||||
|
|
||||||
|
GroupService::del($member->group_id);
|
||||||
|
GroupService::delSelf($member->group_id, $member->profile_id);
|
||||||
|
}
|
||||||
|
}
|
50
app/Jobs/GroupPipeline/JoinRejected.php
Normal file
50
app/Jobs/GroupPipeline/JoinRejected.php
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Notification;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
|
||||||
|
class JoinRejected implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $member;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupMember $member)
|
||||||
|
{
|
||||||
|
$this->member = $member;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$member = $this->member;
|
||||||
|
$member->rejected_at = now();
|
||||||
|
$member->save();
|
||||||
|
|
||||||
|
$n = new Notification;
|
||||||
|
$n->profile_id = $member->profile_id;
|
||||||
|
$n->actor_id = $member->profile_id;
|
||||||
|
$n->item_id = $member->group_id;
|
||||||
|
$n->item_type = 'App\Models\Group';
|
||||||
|
$n->action = 'group.join.rejected';
|
||||||
|
$n->save();
|
||||||
|
}
|
||||||
|
}
|
107
app/Jobs/GroupPipeline/LikePipeline.php
Normal file
107
app/Jobs/GroupPipeline/LikePipeline.php
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
|
use Cache, Log;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use App\{Like, Notification};
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use League\Fractal;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
use App\Transformer\ActivityPub\Verb\Like as LikeTransformer;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
|
||||||
|
class LikePipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $like;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
public $timeout = 5;
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Like $like)
|
||||||
|
{
|
||||||
|
$this->like = $like;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$like = $this->like;
|
||||||
|
|
||||||
|
$status = $this->like->status;
|
||||||
|
$actor = $this->like->actor;
|
||||||
|
|
||||||
|
if (!$status) {
|
||||||
|
// Ignore notifications to deleted statuses
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusService::refresh($status->id);
|
||||||
|
|
||||||
|
if($status->url && $actor->domain == null) {
|
||||||
|
return $this->remoteLikeDeliver();
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = Notification::whereProfileId($status->profile_id)
|
||||||
|
->whereActorId($actor->id)
|
||||||
|
->whereAction('group:like')
|
||||||
|
->whereItemId($status->id)
|
||||||
|
->whereItemType('App\Status')
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if ($actor->id === $status->profile_id || $exists !== 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$notification = new Notification();
|
||||||
|
$notification->profile_id = $status->profile_id;
|
||||||
|
$notification->actor_id = $actor->id;
|
||||||
|
$notification->action = 'group:like';
|
||||||
|
$notification->item_id = $status->id;
|
||||||
|
$notification->item_type = "App\Status";
|
||||||
|
$notification->save();
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remoteLikeDeliver()
|
||||||
|
{
|
||||||
|
$like = $this->like;
|
||||||
|
$status = $this->like->status;
|
||||||
|
$actor = $this->like->actor;
|
||||||
|
|
||||||
|
$fractal = new Fractal\Manager();
|
||||||
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
|
$resource = new Fractal\Resource\Item($like, new LikeTransformer());
|
||||||
|
$activity = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
|
$url = $status->profile->sharedInbox ?? $status->profile->inbox_url;
|
||||||
|
|
||||||
|
Helpers::sendSignedObject($actor, $url, $activity);
|
||||||
|
}
|
||||||
|
}
|
130
app/Jobs/GroupPipeline/NewStatusPipeline.php
Normal file
130
app/Jobs/GroupPipeline/NewStatusPipeline.php
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
|
use App\Notification;
|
||||||
|
use App\Hashtag;
|
||||||
|
use App\Mention;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Status;
|
||||||
|
use App\StatusHashtag;
|
||||||
|
use App\Models\GroupPostHashtag;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use Cache;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use App\Services\MediaStorageService;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use App\Util\Lexer\Extractor;
|
||||||
|
|
||||||
|
class NewStatusPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $status;
|
||||||
|
protected $gp;
|
||||||
|
protected $tags;
|
||||||
|
protected $mentions;
|
||||||
|
|
||||||
|
public function __construct(Status $status, GroupPost $gp)
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
$this->gp = $gp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$status = $this->status;
|
||||||
|
|
||||||
|
$autolink = Autolink::create()
|
||||||
|
->setAutolinkActiveUsersOnly(true)
|
||||||
|
->setBaseHashPath("/groups/{$status->group_id}/topics/")
|
||||||
|
->setBaseUserPath("/groups/{$status->group_id}/username/")
|
||||||
|
->autolink($status->caption);
|
||||||
|
|
||||||
|
$entities = Extractor::create()->extract($status->caption);
|
||||||
|
|
||||||
|
$autolink = str_replace('/discover/tags/', '/groups/' . $status->group_id . '/topics/', $autolink);
|
||||||
|
|
||||||
|
$status->rendered = nl2br($autolink);
|
||||||
|
$status->entities = null;
|
||||||
|
$status->save();
|
||||||
|
|
||||||
|
$this->tags = array_unique($entities['hashtags']);
|
||||||
|
$this->mentions = array_unique($entities['mentions']);
|
||||||
|
|
||||||
|
if(count($this->tags)) {
|
||||||
|
$this->storeHashtags();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(count($this->mentions)) {
|
||||||
|
$this->storeMentions($this->mentions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function storeHashtags()
|
||||||
|
{
|
||||||
|
$tags = $this->tags;
|
||||||
|
$status = $this->status;
|
||||||
|
$gp = $this->gp;
|
||||||
|
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
if(mb_strlen($tag) > 124) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::transaction(function () use ($status, $tag, $gp) {
|
||||||
|
$slug = str_slug($tag, '-', false);
|
||||||
|
$hashtag = Hashtag::firstOrCreate(
|
||||||
|
['name' => $tag, 'slug' => $slug]
|
||||||
|
);
|
||||||
|
GroupPostHashtag::firstOrCreate(
|
||||||
|
[
|
||||||
|
'group_id' => $status->group_id,
|
||||||
|
'group_post_id' => $gp->id,
|
||||||
|
'status_id' => $status->id,
|
||||||
|
'hashtag_id' => $hashtag->id,
|
||||||
|
'profile_id' => $status->profile_id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(count($this->mentions)) {
|
||||||
|
$this->storeMentions();
|
||||||
|
}
|
||||||
|
StatusService::del($status->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function storeMentions()
|
||||||
|
{
|
||||||
|
$mentions = $this->mentions;
|
||||||
|
$status = $this->status;
|
||||||
|
|
||||||
|
foreach ($mentions as $mention) {
|
||||||
|
$mentioned = Profile::whereUsername($mention)->first();
|
||||||
|
|
||||||
|
if (empty($mentioned) || !isset($mentioned->id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::transaction(function () use ($status, $mentioned) {
|
||||||
|
$m = new Mention();
|
||||||
|
$m->status_id = $status->id;
|
||||||
|
$m->profile_id = $mentioned->id;
|
||||||
|
$m->save();
|
||||||
|
|
||||||
|
MentionPipeline::dispatch($status, $m);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
StatusService::del($status->id);
|
||||||
|
}
|
||||||
|
}
|
109
app/Jobs/GroupPipeline/UnlikePipeline.php
Normal file
109
app/Jobs/GroupPipeline/UnlikePipeline.php
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
|
use Cache, Log;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use App\{Like, Notification};
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use League\Fractal;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
use App\Transformer\ActivityPub\Verb\UndoLike as LikeTransformer;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
|
||||||
|
class UnlikePipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $like;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
public $timeout = 5;
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Like $like)
|
||||||
|
{
|
||||||
|
$this->like = $like;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$like = $this->like;
|
||||||
|
|
||||||
|
$status = $this->like->status;
|
||||||
|
$actor = $this->like->actor;
|
||||||
|
|
||||||
|
if (!$status) {
|
||||||
|
// Ignore notifications to deleted statuses
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = $status->likes_count > 1 ? $status->likes_count : $status->likes()->count();
|
||||||
|
$status->likes_count = $count - 1;
|
||||||
|
$status->save();
|
||||||
|
|
||||||
|
StatusService::del($status->id);
|
||||||
|
|
||||||
|
if($actor->id !== $status->profile_id && $status->url && $actor->domain == null) {
|
||||||
|
$this->remoteLikeDeliver();
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = Notification::whereProfileId($status->profile_id)
|
||||||
|
->whereActorId($actor->id)
|
||||||
|
->whereAction('group:like')
|
||||||
|
->whereItemId($status->id)
|
||||||
|
->whereItemType('App\Status')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if($exists) {
|
||||||
|
$exists->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$like = Like::whereProfileId($actor->id)->whereStatusId($status->id)->first();
|
||||||
|
|
||||||
|
if(!$like) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$like->forceDelete();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remoteLikeDeliver()
|
||||||
|
{
|
||||||
|
$like = $this->like;
|
||||||
|
$status = $this->like->status;
|
||||||
|
$actor = $this->like->actor;
|
||||||
|
|
||||||
|
$fractal = new Fractal\Manager();
|
||||||
|
$fractal->setSerializer(new ArraySerializer());
|
||||||
|
$resource = new Fractal\Resource\Item($like, new LikeTransformer());
|
||||||
|
$activity = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
|
$url = $status->profile->sharedInbox ?? $status->profile->inbox_url;
|
||||||
|
|
||||||
|
Helpers::sendSignedObject($actor, $url, $activity);
|
||||||
|
}
|
||||||
|
}
|
58
app/Jobs/GroupsPipeline/DeleteCommentPipeline.php
Normal file
58
app/Jobs/GroupsPipeline/DeleteCommentPipeline.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupsPipeline;
|
||||||
|
|
||||||
|
use App\Util\Media\Image;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupComment;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupHashtag;
|
||||||
|
use App\Models\GroupPostHashtag;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use App\Util\Lexer\Extractor;
|
||||||
|
use DB;
|
||||||
|
|
||||||
|
class DeleteCommentPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $parent;
|
||||||
|
protected $status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct($parent, $status)
|
||||||
|
{
|
||||||
|
$this->parent = $parent;
|
||||||
|
$this->status = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$parent = $this->parent;
|
||||||
|
$parent->reply_count = GroupComment::whereStatusId($parent->id)->count();
|
||||||
|
$parent->save();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
89
app/Jobs/GroupsPipeline/ImageResizePipeline.php
Normal file
89
app/Jobs/GroupsPipeline/ImageResizePipeline.php
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupsPipeline;
|
||||||
|
|
||||||
|
use App\Models\GroupMedia;
|
||||||
|
use App\Util\Media\Image;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Log;
|
||||||
|
use Storage;
|
||||||
|
use Image as Intervention;
|
||||||
|
|
||||||
|
class ImageResizePipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $media;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupMedia $media)
|
||||||
|
{
|
||||||
|
$this->media = $media;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$media = $this->media;
|
||||||
|
|
||||||
|
if(!$media) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Storage::exists($media->media_path) || $media->skip_optimize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = $media->media_path;
|
||||||
|
$file = storage_path('app/' . $path);
|
||||||
|
$quality = config_cache('pixelfed.image_quality');
|
||||||
|
|
||||||
|
$orientations = [
|
||||||
|
'square' => [
|
||||||
|
'width' => 1080,
|
||||||
|
'height' => 1080,
|
||||||
|
],
|
||||||
|
'landscape' => [
|
||||||
|
'width' => 1920,
|
||||||
|
'height' => 1080,
|
||||||
|
],
|
||||||
|
'portrait' => [
|
||||||
|
'width' => 1080,
|
||||||
|
'height' => 1350,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$img = Intervention::make($file);
|
||||||
|
$img->orientate();
|
||||||
|
$width = $img->width();
|
||||||
|
$height = $img->height();
|
||||||
|
$aspect = $width / $height;
|
||||||
|
$orientation = $aspect === 1 ? 'square' : ($aspect > 1 ? 'landscape' : 'portrait');
|
||||||
|
$ratio = $orientations[$orientation];
|
||||||
|
$img->resize($ratio['width'], $ratio['height']);
|
||||||
|
$img->save($file, $quality);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::error($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
app/Jobs/GroupsPipeline/ImageS3DeletePipeline.php
Normal file
67
app/Jobs/GroupsPipeline/ImageS3DeletePipeline.php
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupsPipeline;
|
||||||
|
|
||||||
|
use App\Models\GroupMedia;
|
||||||
|
use App\Util\Media\Image;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Storage;
|
||||||
|
use Illuminate\Http\File;
|
||||||
|
use Exception;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Aws\S3\Exception\S3Exception;
|
||||||
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
|
use League\Flysystem\UnableToWriteFile;
|
||||||
|
|
||||||
|
class ImageS3DeletePipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $media;
|
||||||
|
static $attempts = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupMedia $media)
|
||||||
|
{
|
||||||
|
$this->media = $media;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$media = $this->media;
|
||||||
|
|
||||||
|
if(!$media || (bool) config_cache('pixelfed.cloud_storage') === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fs = Storage::disk(config('filesystems.cloud'));
|
||||||
|
|
||||||
|
if(!$fs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($fs->exists($media->media_path)) {
|
||||||
|
$fs->delete($media->media_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
app/Jobs/GroupsPipeline/ImageS3UploadPipeline.php
Normal file
107
app/Jobs/GroupsPipeline/ImageS3UploadPipeline.php
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupsPipeline;
|
||||||
|
|
||||||
|
use App\Models\GroupMedia;
|
||||||
|
use App\Util\Media\Image;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Storage;
|
||||||
|
use Illuminate\Http\File;
|
||||||
|
use Exception;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Aws\S3\Exception\S3Exception;
|
||||||
|
use GuzzleHttp\Exception\ConnectException;
|
||||||
|
use League\Flysystem\UnableToWriteFile;
|
||||||
|
|
||||||
|
class ImageS3UploadPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $media;
|
||||||
|
static $attempts = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupMedia $media)
|
||||||
|
{
|
||||||
|
$this->media = $media;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$media = $this->media;
|
||||||
|
|
||||||
|
if(!$media || (bool) config_cache('pixelfed.cloud_storage') === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = storage_path('app/' . $media->media_path);
|
||||||
|
|
||||||
|
$p = explode('/', $media->media_path);
|
||||||
|
$name = array_pop($p);
|
||||||
|
$storagePath = implode('/', $p);
|
||||||
|
|
||||||
|
$url = (bool) config_cache('pixelfed.cloud_storage') && (bool) config('media.storage.remote.resilient_mode') ?
|
||||||
|
self::handleResilientStore($storagePath, $path, $name) :
|
||||||
|
self::handleStore($storagePath, $path, $name);
|
||||||
|
|
||||||
|
if($url && strlen($url) && str_starts_with($url, 'https://')) {
|
||||||
|
$media->cdn_url = $url;
|
||||||
|
$media->processed_at = now();
|
||||||
|
$media->version = 11;
|
||||||
|
$media->save();
|
||||||
|
Storage::disk('local')->delete($media->media_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handleStore($storagePath, $path, $name)
|
||||||
|
{
|
||||||
|
return retry(3, function() use($storagePath, $path, $name) {
|
||||||
|
$baseDisk = (bool) config_cache('pixelfed.cloud_storage') ? config('filesystems.cloud') : 'local';
|
||||||
|
$disk = Storage::disk($baseDisk);
|
||||||
|
$file = $disk->putFileAs($storagePath, new File($path), $name, 'public');
|
||||||
|
return $disk->url($file);
|
||||||
|
}, random_int(100, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function handleResilientStore($storagePath, $path, $name)
|
||||||
|
{
|
||||||
|
$attempts = 0;
|
||||||
|
return retry(4, function() use($storagePath, $path, $name, $attempts) {
|
||||||
|
self::$attempts++;
|
||||||
|
usleep(100000);
|
||||||
|
$baseDisk = self::$attempts > 1 ? $this->getAltDriver() : config('filesystems.cloud');
|
||||||
|
try {
|
||||||
|
$disk = Storage::disk($baseDisk);
|
||||||
|
$file = $disk->putFileAs($storagePath, new File($path), $name, 'public');
|
||||||
|
} catch (S3Exception | ClientException | ConnectException | UnableToWriteFile | Exception $e) {}
|
||||||
|
return $disk->url($file);
|
||||||
|
}, function (int $attempt, Exception $exception) {
|
||||||
|
return $attempt * 200;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAltDriver()
|
||||||
|
{
|
||||||
|
return config('filesystems.cloud');
|
||||||
|
}
|
||||||
|
}
|
47
app/Jobs/GroupsPipeline/MemberJoinApprovedPipeline.php
Normal file
47
app/Jobs/GroupsPipeline/MemberJoinApprovedPipeline.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupsPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Notification;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
|
||||||
|
class MemberJoinApprovedPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $member;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupMember $member)
|
||||||
|
{
|
||||||
|
$this->member = $member;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$member = $this->member;
|
||||||
|
$member->approved_at = now();
|
||||||
|
$member->join_request = false;
|
||||||
|
$member->role = 'member';
|
||||||
|
$member->save();
|
||||||
|
|
||||||
|
GroupService::del($member->group_id);
|
||||||
|
GroupService::delSelf($member->group_id, $member->profile_id);
|
||||||
|
}
|
||||||
|
}
|
42
app/Jobs/GroupsPipeline/MemberJoinRejectedPipeline.php
Normal file
42
app/Jobs/GroupsPipeline/MemberJoinRejectedPipeline.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupsPipeline;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\GroupMember;
|
||||||
|
use App\Notification;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
|
||||||
|
class MemberJoinRejectedPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $member;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupMember $member)
|
||||||
|
{
|
||||||
|
$this->member = $member;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$member = $this->member;
|
||||||
|
$member->rejected_at = now();
|
||||||
|
$member->save();
|
||||||
|
}
|
||||||
|
}
|
115
app/Jobs/GroupsPipeline/NewCommentPipeline.php
Normal file
115
app/Jobs/GroupsPipeline/NewCommentPipeline.php
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupsPipeline;
|
||||||
|
|
||||||
|
use App\Util\Media\Image;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupComment;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupHashtag;
|
||||||
|
use App\Models\GroupPostHashtag;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use App\Util\Lexer\Extractor;
|
||||||
|
use DB;
|
||||||
|
|
||||||
|
class NewCommentPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $status;
|
||||||
|
protected $parent;
|
||||||
|
protected $entities;
|
||||||
|
protected $autolink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct($parent, GroupComment $status)
|
||||||
|
{
|
||||||
|
$this->parent = $parent;
|
||||||
|
$this->status = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$profile = $this->status->profile;
|
||||||
|
$status = $this->status;
|
||||||
|
|
||||||
|
$parent = $this->parent;
|
||||||
|
$parent->reply_count = GroupComment::whereStatusId($parent->id)->count();
|
||||||
|
$parent->save();
|
||||||
|
|
||||||
|
if ($profile->no_autolink == false) {
|
||||||
|
$this->parseEntities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseEntities()
|
||||||
|
{
|
||||||
|
$this->extractEntities();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extractEntities()
|
||||||
|
{
|
||||||
|
$this->entities = Extractor::create()->extract($this->status->caption);
|
||||||
|
$this->autolinkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autolinkStatus()
|
||||||
|
{
|
||||||
|
$this->autolink = Autolink::create()->autolink($this->status->caption);
|
||||||
|
$this->storeHashtags();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeHashtags()
|
||||||
|
{
|
||||||
|
$tags = array_unique($this->entities['hashtags']);
|
||||||
|
$status = $this->status;
|
||||||
|
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
if (mb_strlen($tag) > 124) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DB::transaction(function () use ($status, $tag) {
|
||||||
|
$hashtag = GroupHashtag::firstOrCreate([
|
||||||
|
'name' => $tag,
|
||||||
|
]);
|
||||||
|
|
||||||
|
GroupPostHashtag::firstOrCreate(
|
||||||
|
[
|
||||||
|
'status_id' => $status->id,
|
||||||
|
'group_id' => $status->group_id,
|
||||||
|
'hashtag_id' => $hashtag->id,
|
||||||
|
'profile_id' => $status->profile_id,
|
||||||
|
'status_visibility' => $status->visibility,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$this->storeMentions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeMentions()
|
||||||
|
{
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
}
|
108
app/Jobs/GroupsPipeline/NewPostPipeline.php
Normal file
108
app/Jobs/GroupsPipeline/NewPostPipeline.php
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\GroupsPipeline;
|
||||||
|
|
||||||
|
use App\Util\Media\Image;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use App\Models\Group;
|
||||||
|
use App\Models\GroupPost;
|
||||||
|
use App\Models\GroupHashtag;
|
||||||
|
use App\Models\GroupPostHashtag;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use App\Util\Lexer\Extractor;
|
||||||
|
use DB;
|
||||||
|
|
||||||
|
class NewPostPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $status;
|
||||||
|
protected $entities;
|
||||||
|
protected $autolink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the job if its models no longer exist.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(GroupPost $status)
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$profile = $this->status->profile;
|
||||||
|
$status = $this->status;
|
||||||
|
|
||||||
|
if ($profile->no_autolink == false) {
|
||||||
|
$this->parseEntities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parseEntities()
|
||||||
|
{
|
||||||
|
$this->extractEntities();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extractEntities()
|
||||||
|
{
|
||||||
|
$this->entities = Extractor::create()->extract($this->status->caption);
|
||||||
|
$this->autolinkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autolinkStatus()
|
||||||
|
{
|
||||||
|
$this->autolink = Autolink::create()->autolink($this->status->caption);
|
||||||
|
$this->storeHashtags();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeHashtags()
|
||||||
|
{
|
||||||
|
$tags = array_unique($this->entities['hashtags']);
|
||||||
|
$status = $this->status;
|
||||||
|
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
if (mb_strlen($tag) > 124) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DB::transaction(function () use ($status, $tag) {
|
||||||
|
$hashtag = GroupHashtag::firstOrCreate([
|
||||||
|
'name' => $tag,
|
||||||
|
]);
|
||||||
|
|
||||||
|
GroupPostHashtag::firstOrCreate(
|
||||||
|
[
|
||||||
|
'status_id' => $status->id,
|
||||||
|
'group_id' => $status->group_id,
|
||||||
|
'hashtag_id' => $hashtag->id,
|
||||||
|
'profile_id' => $status->profile_id,
|
||||||
|
'status_visibility' => $status->visibility,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$this->storeMentions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeMentions()
|
||||||
|
{
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,10 +72,12 @@ class FetchNodeinfoPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessin
|
||||||
$instance->software = strtolower(strip_tags($software));
|
$instance->software = strtolower(strip_tags($software));
|
||||||
$instance->user_count = Profile::whereDomain($instance->domain)->count();
|
$instance->user_count = Profile::whereDomain($instance->domain)->count();
|
||||||
$instance->nodeinfo_last_fetched = now();
|
$instance->nodeinfo_last_fetched = now();
|
||||||
|
$instance->last_crawled_at = now();
|
||||||
$instance->save();
|
$instance->save();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$instance->delivery_timeout = 1;
|
$instance->delivery_timeout = 1;
|
||||||
|
$instance->last_crawled_at = now();
|
||||||
$instance->delivery_next_after = now()->addHours(14);
|
$instance->delivery_next_after = now()->addHours(14);
|
||||||
$instance->save();
|
$instance->save();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,22 @@
|
||||||
|
|
||||||
namespace App\Jobs\LikePipeline;
|
namespace App\Jobs\LikePipeline;
|
||||||
|
|
||||||
use Cache, DB, Log;
|
use App\Jobs\PushNotificationPipeline\LikePushNotifyPipeline;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use App\Like;
|
||||||
use App\{Like, Notification};
|
use App\Notification;
|
||||||
|
use App\Services\NotificationAppGatewayService;
|
||||||
|
use App\Services\PushNotificationService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Transformer\ActivityPub\Verb\Like as LikeTransformer;
|
||||||
|
use App\User;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use App\Util\ActivityPub\Helpers;
|
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
use App\Transformer\ActivityPub\Verb\Like as LikeTransformer;
|
|
||||||
use App\Services\StatusService;
|
|
||||||
|
|
||||||
class LikePipeline implements ShouldQueue
|
class LikePipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -30,6 +33,7 @@ class LikePipeline implements ShouldQueue
|
||||||
public $deleteWhenMissingModels = true;
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
public $timeout = 5;
|
public $timeout = 5;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,9 +63,6 @@ class LikePipeline implements ShouldQueue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$status->likes_count = DB::table('likes')->whereStatusId($status->id)->count();
|
|
||||||
$status->save();
|
|
||||||
|
|
||||||
StatusService::refresh($status->id);
|
StatusService::refresh($status->id);
|
||||||
|
|
||||||
if ($status->url && $actor->domain == null) {
|
if ($status->url && $actor->domain == null) {
|
||||||
|
@ -75,13 +76,13 @@ class LikePipeline implements ShouldQueue
|
||||||
->whereItemType('App\Status')
|
->whereItemType('App\Status')
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
if ($actor->id === $status->profile_id || $exists !== 0) {
|
if ($actor->id === $status->profile_id || $exists) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($status->uri === null && $status->object_url === null && $status->url === null) {
|
if ($status->uri === null && $status->object_url === null && $status->url === null) {
|
||||||
try {
|
try {
|
||||||
$notification = new Notification();
|
$notification = new Notification;
|
||||||
$notification->profile_id = $status->profile_id;
|
$notification->profile_id = $status->profile_id;
|
||||||
$notification->actor_id = $actor->id;
|
$notification->actor_id = $actor->id;
|
||||||
$notification->action = 'like';
|
$notification->action = 'like';
|
||||||
|
@ -91,6 +92,15 @@ class LikePipeline implements ShouldQueue
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (NotificationAppGatewayService::enabled()) {
|
||||||
|
if (PushNotificationService::check('like', $status->profile_id)) {
|
||||||
|
$user = User::whereProfileId($status->profile_id)->first();
|
||||||
|
if ($user && $user->expo_token && $user->notify_enabled) {
|
||||||
|
LikePushNotifyPipeline::dispatchSync($user->expo_token, $actor->username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,9 +110,9 @@ class LikePipeline implements ShouldQueue
|
||||||
$status = $this->like->status;
|
$status = $this->like->status;
|
||||||
$actor = $this->like->actor;
|
$actor = $this->like->actor;
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$resource = new Fractal\Resource\Item($like, new LikeTransformer());
|
$resource = new Fractal\Resource\Item($like, new LikeTransformer);
|
||||||
$activity = $fractal->createData($resource)->toArray();
|
$activity = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
$url = $status->profile->sharedInbox ?? $status->profile->inbox_url;
|
$url = $status->profile->sharedInbox ?? $status->profile->inbox_url;
|
||||||
|
|
103
app/Jobs/MovePipeline/CleanupLegacyAccountMovePipeline.php
Normal file
103
app/Jobs/MovePipeline/CleanupLegacyAccountMovePipeline.php
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\MovePipeline;
|
||||||
|
|
||||||
|
use App\Follower;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\UserFilter;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use DateTime;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Queue\Middleware\ThrottlesExceptions;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
|
||||||
|
class CleanupLegacyAccountMovePipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $target;
|
||||||
|
|
||||||
|
public $activity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $tries = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of unhandled exceptions to allow before failing.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $maxExceptions = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($target, $activity)
|
||||||
|
{
|
||||||
|
$this->target = $target;
|
||||||
|
$this->activity = $activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new WithoutOverlapping('process-move-cleanup-legacy-followers:'.$this->target),
|
||||||
|
(new ThrottlesExceptions(2, 5 * 60))->backoff(5),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the time at which the job should timeout.
|
||||||
|
*/
|
||||||
|
public function retryUntil(): DateTime
|
||||||
|
{
|
||||||
|
return now()->addMinutes(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||||
|
throw new Exception('Activitypub not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = $this->target;
|
||||||
|
$actor = $this->activity;
|
||||||
|
|
||||||
|
$targetAccount = Helpers::profileFetch($target);
|
||||||
|
$actorAccount = Helpers::profileFetch($actor);
|
||||||
|
|
||||||
|
if (! $targetAccount || ! $actorAccount) {
|
||||||
|
throw new Exception('Invalid move accounts');
|
||||||
|
}
|
||||||
|
|
||||||
|
UserFilter::where('filterable_type', 'App\Profile')
|
||||||
|
->where('filterable_id', $actorAccount['id'])
|
||||||
|
->update(['filterable_id' => $targetAccount['id']]);
|
||||||
|
|
||||||
|
Follower::whereFollowingId($actorAccount['id'])->delete();
|
||||||
|
|
||||||
|
$oldProfile = Profile::find($actorAccount['id']);
|
||||||
|
|
||||||
|
if ($oldProfile) {
|
||||||
|
$oldProfile->moved_to_profile_id = $targetAccount['id'];
|
||||||
|
$oldProfile->save();
|
||||||
|
AccountService::del($oldProfile->id);
|
||||||
|
AccountService::del($targetAccount['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
app/Jobs/MovePipeline/MoveMigrateFollowersPipeline.php
Normal file
131
app/Jobs/MovePipeline/MoveMigrateFollowersPipeline.php
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\MovePipeline;
|
||||||
|
|
||||||
|
use App\Follower;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use DateTime;
|
||||||
|
use DB;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
|
||||||
|
class MoveMigrateFollowersPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $target;
|
||||||
|
|
||||||
|
public $activity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $tries = 15;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of unhandled exceptions to allow before failing.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $maxExceptions = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of seconds the job can run before timing out.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $timeout = 900;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($target, $activity)
|
||||||
|
{
|
||||||
|
$this->target = $target;
|
||||||
|
$this->activity = $activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new WithoutOverlapping('process-move-migrate-followers:'.$this->target),
|
||||||
|
(new ThrottlesExceptionsWithRedis(5, 2 * 60))->backoff(1),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the time at which the job should timeout.
|
||||||
|
*/
|
||||||
|
public function retryUntil(): DateTime
|
||||||
|
{
|
||||||
|
return now()->addMinutes(15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||||
|
throw new Exception('Activitypub not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = $this->target;
|
||||||
|
$actor = $this->activity;
|
||||||
|
|
||||||
|
$targetAccount = Helpers::profileFetch($target);
|
||||||
|
$actorAccount = Helpers::profileFetch($actor);
|
||||||
|
|
||||||
|
if (! $targetAccount || ! $actorAccount) {
|
||||||
|
throw new Exception('Invalid move accounts');
|
||||||
|
}
|
||||||
|
|
||||||
|
$activity = [
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'type' => 'Follow',
|
||||||
|
'actor' => null,
|
||||||
|
'object' => $target,
|
||||||
|
];
|
||||||
|
|
||||||
|
$version = config('pixelfed.version');
|
||||||
|
$appUrl = config('app.url');
|
||||||
|
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||||
|
$addlHeaders = [
|
||||||
|
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
|
'User-Agent' => $userAgent,
|
||||||
|
];
|
||||||
|
$targetInbox = $targetAccount['sharedInbox'] ?? $targetAccount['inbox_url'];
|
||||||
|
$targetPid = $targetAccount['id'];
|
||||||
|
|
||||||
|
DB::table('followers')
|
||||||
|
->join('profiles', 'followers.profile_id', '=', 'profiles.id')
|
||||||
|
->where('followers.following_id', $actorAccount['id'])
|
||||||
|
->whereNotNull('profiles.user_id')
|
||||||
|
->whereNull('profiles.deleted_at')
|
||||||
|
->select('profiles.id', 'profiles.user_id', 'profiles.username', 'profiles.private_key', 'profiles.status')
|
||||||
|
->chunkById(100, function ($followers) use ($targetInbox, $targetPid, $target) {
|
||||||
|
foreach ($followers as $follower) {
|
||||||
|
if (! $follower->private_key || ! $follower->username || ! $follower->user_id || $follower->status === 'delete') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Follower::updateOrCreate([
|
||||||
|
'profile_id' => $follower->id,
|
||||||
|
'following_id' => $targetPid,
|
||||||
|
]);
|
||||||
|
|
||||||
|
MoveSendFollowPipeline::dispatch($follower, $targetInbox, $targetPid, $target)->onQueue('follow');
|
||||||
|
}
|
||||||
|
}, 'id');
|
||||||
|
}
|
||||||
|
}
|
113
app/Jobs/MovePipeline/MoveSendFollowPipeline.php
Normal file
113
app/Jobs/MovePipeline/MoveSendFollowPipeline.php
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\MovePipeline;
|
||||||
|
|
||||||
|
use App\Util\ActivityPub\HttpSignature;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Queue\Middleware\ThrottlesExceptions;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
|
||||||
|
class MoveSendFollowPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $follower;
|
||||||
|
|
||||||
|
public $targetInbox;
|
||||||
|
|
||||||
|
public $targetPid;
|
||||||
|
|
||||||
|
public $target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $tries = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of unhandled exceptions to allow before failing.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $maxExceptions = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new WithoutOverlapping('move-send-follow:'.$this->follower->id.':target:'.$this->target),
|
||||||
|
(new ThrottlesExceptions(2, 5 * 60))->backoff(5),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($follower, $targetInbox, $targetPid, $target)
|
||||||
|
{
|
||||||
|
$this->follower = $follower;
|
||||||
|
$this->targetInbox = $targetInbox;
|
||||||
|
$this->targetPid = $targetPid;
|
||||||
|
$this->target = $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$follower = $this->follower;
|
||||||
|
$targetPid = $this->targetPid;
|
||||||
|
$targetInbox = $this->targetInbox;
|
||||||
|
$target = $this->target;
|
||||||
|
|
||||||
|
if (! $follower->username || ! $follower->private_key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$permalink = 'https://'.config('pixelfed.domain.app').'/users/'.$follower->username;
|
||||||
|
$version = config('pixelfed.version');
|
||||||
|
$appUrl = config('app.url');
|
||||||
|
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||||
|
$addlHeaders = [
|
||||||
|
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
|
'User-Agent' => $userAgent,
|
||||||
|
];
|
||||||
|
|
||||||
|
$activity = [
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'type' => 'Follow',
|
||||||
|
'actor' => $permalink,
|
||||||
|
'object' => $target,
|
||||||
|
];
|
||||||
|
|
||||||
|
$keyId = $permalink.'#main-key';
|
||||||
|
$payload = json_encode($activity);
|
||||||
|
$headers = HttpSignature::signRaw($follower->private_key, $keyId, $targetInbox, $activity, $addlHeaders);
|
||||||
|
|
||||||
|
$client = new Client([
|
||||||
|
'timeout' => config('federation.activitypub.delivery.timeout'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client->post($targetInbox, [
|
||||||
|
'curl' => [
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
CURLOPT_POSTFIELDS => $payload,
|
||||||
|
CURLOPT_HEADER => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
119
app/Jobs/MovePipeline/MoveSendUndoFollowPipeline.php
Normal file
119
app/Jobs/MovePipeline/MoveSendUndoFollowPipeline.php
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\MovePipeline;
|
||||||
|
|
||||||
|
use App\Util\ActivityPub\HttpSignature;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Queue\Middleware\ThrottlesExceptions;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
|
||||||
|
class MoveSendUndoFollowPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $follower;
|
||||||
|
|
||||||
|
public $targetInbox;
|
||||||
|
|
||||||
|
public $targetPid;
|
||||||
|
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $tries = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of unhandled exceptions to allow before failing.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $maxExceptions = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new WithoutOverlapping('move-send-unfollow:'.$this->follower->id.':actor:'.$this->actor),
|
||||||
|
(new ThrottlesExceptions(2, 5 * 60))->backoff(5),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($follower, $targetInbox, $targetPid, $actor)
|
||||||
|
{
|
||||||
|
$this->follower = $follower;
|
||||||
|
$this->targetInbox = $targetInbox;
|
||||||
|
$this->targetPid = $targetPid;
|
||||||
|
$this->actor = $actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$follower = $this->follower;
|
||||||
|
$targetPid = $this->targetPid;
|
||||||
|
$targetInbox = $this->targetInbox;
|
||||||
|
$actor = $this->actor;
|
||||||
|
|
||||||
|
if (! $follower->username || ! $follower->private_key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$permalink = 'https://'.config('pixelfed.domain.app').'/users/'.$follower->username;
|
||||||
|
$version = config('pixelfed.version');
|
||||||
|
$appUrl = config('app.url');
|
||||||
|
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||||
|
$addlHeaders = [
|
||||||
|
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
|
'User-Agent' => $userAgent,
|
||||||
|
];
|
||||||
|
|
||||||
|
$activity = [
|
||||||
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'type' => 'Undo',
|
||||||
|
'id' => $permalink.'#follow/'.$targetPid.'/undo',
|
||||||
|
'actor' => $permalink,
|
||||||
|
'object' => [
|
||||||
|
'type' => 'Follow',
|
||||||
|
'id' => $permalink.'#follows/'.$targetPid,
|
||||||
|
'object' => $actor,
|
||||||
|
'actor' => $permalink,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$keyId = $permalink.'#main-key';
|
||||||
|
$payload = json_encode($activity);
|
||||||
|
$headers = HttpSignature::signRaw($follower->private_key, $keyId, $targetInbox, $activity, $addlHeaders);
|
||||||
|
|
||||||
|
$client = new Client([
|
||||||
|
'timeout' => config('federation.activitypub.delivery.timeout'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client->post($targetInbox, [
|
||||||
|
'curl' => [
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
CURLOPT_POSTFIELDS => $payload,
|
||||||
|
CURLOPT_HEADER => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
} catch (ClientException $e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
156
app/Jobs/MovePipeline/ProcessMovePipeline.php
Normal file
156
app/Jobs/MovePipeline/ProcessMovePipeline.php
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\MovePipeline;
|
||||||
|
|
||||||
|
use App\Services\ActivityPubFetchService;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use DateTime;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class ProcessMovePipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $target;
|
||||||
|
|
||||||
|
public $activity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $tries = 15;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of unhandled exceptions to allow before failing.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $maxExceptions = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of seconds the job can run before timing out.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $timeout = 120;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($target, $activity)
|
||||||
|
{
|
||||||
|
$this->target = $target;
|
||||||
|
$this->activity = $activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new WithoutOverlapping('process-move:'.$this->target),
|
||||||
|
(new ThrottlesExceptionsWithRedis(5, 2 * 60))->backoff(1),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the time at which the job should timeout.
|
||||||
|
*/
|
||||||
|
public function retryUntil(): DateTime
|
||||||
|
{
|
||||||
|
return now()->addMinutes(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||||
|
throw new Exception('Activitypub not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
$validTarget = $this->checkTarget();
|
||||||
|
if (! $validTarget) {
|
||||||
|
throw new Exception('Invalid target');
|
||||||
|
}
|
||||||
|
|
||||||
|
$validActor = $this->checkActor();
|
||||||
|
if (! $validActor) {
|
||||||
|
throw new Exception('Invalid actor');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkTarget()
|
||||||
|
{
|
||||||
|
$fetchTargetUrl = $this->target.'?cb='.time();
|
||||||
|
$res = ActivityPubFetchService::fetchRequest($fetchTargetUrl, true);
|
||||||
|
|
||||||
|
if (! $res || ! isset($res['alsoKnownAs'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetRes = Helpers::profileFetch($this->target);
|
||||||
|
if (! $targetRes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($res['alsoKnownAs'])) {
|
||||||
|
return $this->lowerTrim($res['alsoKnownAs']) === $this->lowerTrim($this->activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($res['alsoKnownAs'])) {
|
||||||
|
$map = Arr::map($res['alsoKnownAs'], function ($value, $key) {
|
||||||
|
return trim(strtolower($value));
|
||||||
|
});
|
||||||
|
|
||||||
|
$res = in_array($this->activity, $map);
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkActor()
|
||||||
|
{
|
||||||
|
$fetchActivityUrl = $this->activity.'?cb='.time();
|
||||||
|
$res = ActivityPubFetchService::fetchRequest($fetchActivityUrl, true);
|
||||||
|
|
||||||
|
if (! $res || ! isset($res['movedTo']) || empty($res['movedTo'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$actorRes = Helpers::profileFetch($this->activity);
|
||||||
|
if (! $actorRes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($res['movedTo'])) {
|
||||||
|
$match = $this->lowerTrim($res['movedTo']) === $this->lowerTrim($this->target);
|
||||||
|
if (! $match) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $match;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function lowerTrim($str)
|
||||||
|
{
|
||||||
|
return trim(strtolower($str));
|
||||||
|
}
|
||||||
|
}
|
111
app/Jobs/MovePipeline/UnfollowLegacyAccountMovePipeline.php
Normal file
111
app/Jobs/MovePipeline/UnfollowLegacyAccountMovePipeline.php
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\MovePipeline;
|
||||||
|
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use DateTime;
|
||||||
|
use DB;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
use Illuminate\Queue\Middleware\ThrottlesExceptions;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
|
||||||
|
class UnfollowLegacyAccountMovePipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $target;
|
||||||
|
|
||||||
|
public $activity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $tries = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of unhandled exceptions to allow before failing.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $maxExceptions = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($target, $activity)
|
||||||
|
{
|
||||||
|
$this->target = $target;
|
||||||
|
$this->activity = $activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the middleware the job should pass through.
|
||||||
|
*
|
||||||
|
* @return array<int, object>
|
||||||
|
*/
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new WithoutOverlapping('process-move-undo-legacy-followers:'.$this->target),
|
||||||
|
(new ThrottlesExceptions(2, 5 * 60))->backoff(5),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the time at which the job should timeout.
|
||||||
|
*/
|
||||||
|
public function retryUntil(): DateTime
|
||||||
|
{
|
||||||
|
return now()->addMinutes(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||||
|
throw new Exception('Activitypub not enabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = $this->target;
|
||||||
|
$actor = $this->activity;
|
||||||
|
|
||||||
|
$targetAccount = Helpers::profileFetch($target);
|
||||||
|
$actorAccount = Helpers::profileFetch($actor);
|
||||||
|
|
||||||
|
if (! $targetAccount || ! $actorAccount) {
|
||||||
|
throw new Exception('Invalid move accounts');
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = config('pixelfed.version');
|
||||||
|
$appUrl = config('app.url');
|
||||||
|
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||||
|
$addlHeaders = [
|
||||||
|
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
|
'User-Agent' => $userAgent,
|
||||||
|
];
|
||||||
|
$targetInbox = $actorAccount['sharedInbox'] ?? $actorAccount['inbox_url'];
|
||||||
|
$targetPid = $actorAccount['id'];
|
||||||
|
|
||||||
|
DB::table('followers')
|
||||||
|
->join('profiles', 'followers.profile_id', '=', 'profiles.id')
|
||||||
|
->where('followers.following_id', $actorAccount['id'])
|
||||||
|
->whereNotNull('profiles.user_id')
|
||||||
|
->whereNull('profiles.deleted_at')
|
||||||
|
->select('profiles.id', 'profiles.user_id', 'profiles.username', 'profiles.private_key', 'profiles.status')
|
||||||
|
->chunkById(100, function ($followers) use ($actor, $targetInbox, $targetPid) {
|
||||||
|
foreach ($followers as $follower) {
|
||||||
|
if (! $follower->id || ! $follower->private_key || ! $follower->username || ! $follower->user_id || $follower->status === 'delete') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveSendUndoFollowPipeline::dispatch($follower, $targetInbox, $targetPid, $actor)->onQueue('move');
|
||||||
|
}
|
||||||
|
}, 'id');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\PushNotificationPipeline;
|
||||||
|
|
||||||
|
use App\Services\NotificationAppGatewayService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
|
||||||
|
class FollowPushNotifyPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $pushToken;
|
||||||
|
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($pushToken, $actor)
|
||||||
|
{
|
||||||
|
$this->pushToken = $pushToken;
|
||||||
|
$this->actor = $actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
NotificationAppGatewayService::send($this->pushToken, 'follow', $this->actor);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
app/Jobs/PushNotificationPipeline/LikePushNotifyPipeline.php
Normal file
38
app/Jobs/PushNotificationPipeline/LikePushNotifyPipeline.php
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\PushNotificationPipeline;
|
||||||
|
|
||||||
|
use App\Services\NotificationAppGatewayService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
|
||||||
|
class LikePushNotifyPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $pushToken;
|
||||||
|
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($pushToken, $actor)
|
||||||
|
{
|
||||||
|
$this->pushToken = $pushToken;
|
||||||
|
$this->actor = $actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
NotificationAppGatewayService::send($this->pushToken, 'like', $this->actor);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs\PushNotificationPipeline;
|
||||||
|
|
||||||
|
use App\Services\NotificationAppGatewayService;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Queue\Queueable;
|
||||||
|
|
||||||
|
class MentionPushNotifyPipeline implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public $pushToken;
|
||||||
|
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*/
|
||||||
|
public function __construct($pushToken, $actor)
|
||||||
|
{
|
||||||
|
$this->pushToken = $pushToken;
|
||||||
|
$this->actor = $actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
NotificationAppGatewayService::send($this->pushToken, 'mention', $this->actor);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,7 +109,7 @@ class RemoteStatusDelete implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
AccountStatService::decrementPostCount($status->profile_id);
|
// AccountStatService::decrementPostCount($status->profile_id);
|
||||||
return $this->unlinkRemoveMedia($status);
|
return $this->unlinkRemoveMedia($status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,11 +176,11 @@ class RemoteStatusDelete implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
StatusView::whereStatusId($status->id)->delete();
|
StatusView::whereStatusId($status->id)->delete();
|
||||||
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
||||||
|
|
||||||
$status->delete();
|
|
||||||
|
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
AccountService::del($status->profile_id);
|
AccountService::del($status->profile_id);
|
||||||
|
|
||||||
|
$status->forceDelete();
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
71
app/Mail/AdminMessageResponse.php
Normal file
71
app/Mail/AdminMessageResponse.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Contact;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Mail\Mailables\Content;
|
||||||
|
use Illuminate\Mail\Mailables\Envelope;
|
||||||
|
use Illuminate\Mail\Mailables\Headers;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class AdminMessageResponse extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public Contact $contact,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message headers.
|
||||||
|
*/
|
||||||
|
public function headers(): Headers
|
||||||
|
{
|
||||||
|
$mid = $this->contact->getMessageId();
|
||||||
|
|
||||||
|
return new Headers(
|
||||||
|
messageId: $mid,
|
||||||
|
text: [
|
||||||
|
'X-Entity-Ref-ID' => $mid,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: ucfirst(strtolower(config('pixelfed.domain.app'))).' Contact Form Response [Ticket #'.$this->contact->id.']',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
markdown: 'emails.contact.admin-response',
|
||||||
|
with: [
|
||||||
|
'url' => $this->contact->userResponseUrl(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
|
use App\Util\Media\License;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use App\Util\Media\License;
|
|
||||||
use Storage;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Storage;
|
||||||
|
|
||||||
class Media extends Model
|
class Media extends Model
|
||||||
{
|
{
|
||||||
|
@ -21,7 +21,7 @@ class Media extends Model
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'srcset' => 'array',
|
'srcset' => 'array',
|
||||||
'deleted_at' => 'datetime'
|
'deleted_at' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function status()
|
public function status()
|
||||||
|
@ -58,7 +58,7 @@ class Media extends Model
|
||||||
return url(Storage::url($this->thumbnail_path));
|
return url(Storage::url($this->thumbnail_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->remote_media && !$this->thumbnail_path && $this->cdn_url) {
|
if (! $this->thumbnail_path && $this->cdn_url) {
|
||||||
return $this->cdn_url;
|
return $this->cdn_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ class Media extends Model
|
||||||
if (! $this->mime) {
|
if (! $this->mime) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return explode('/', $this->mime)[0];
|
return explode('/', $this->mime)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +105,7 @@ class Media extends Model
|
||||||
$verb = 'Document';
|
$verb = 'Document';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $verb;
|
return $verb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +142,7 @@ class Media extends Model
|
||||||
return [
|
return [
|
||||||
'id' => $res['id'],
|
'id' => $res['id'],
|
||||||
'title' => $res['title'],
|
'title' => $res['title'],
|
||||||
'url' => $res['url']
|
'url' => $res['url'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
67
app/Models/Group.php
Normal file
67
app/Models/Group.php
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\HasSnowflakePrimary;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Services\GroupService;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class Group extends Model
|
||||||
|
{
|
||||||
|
use HasSnowflakePrimary, HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the IDs are auto-incrementing.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $incrementing = false;
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'metadata' => 'json'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function url()
|
||||||
|
{
|
||||||
|
return url("/groups/{$this->id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function permalink($suffix = null)
|
||||||
|
{
|
||||||
|
if(!$this->local) {
|
||||||
|
return $this->remote_url;
|
||||||
|
}
|
||||||
|
return $this->url() . $suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function members()
|
||||||
|
{
|
||||||
|
return $this->hasMany(GroupMember::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function admin()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Profile::class, 'profile_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isMember($id = false)
|
||||||
|
{
|
||||||
|
$id = $id ?? request()->user()->profile_id;
|
||||||
|
// return $this->members()->whereProfileId($id)->whereJoinRequest(false)->exists();
|
||||||
|
return GroupService::isMember($this->id, $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMembershipType()
|
||||||
|
{
|
||||||
|
return $this->is_private ? 'private' : ($this->is_local ? 'local' : 'all');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selfRole($id = false)
|
||||||
|
{
|
||||||
|
$id = $id ?? request()->user()->profile_id;
|
||||||
|
return optional($this->members()->whereProfileId($id)->first())->role ?? null;
|
||||||
|
}
|
||||||
|
}
|
11
app/Models/GroupActivityGraph.php
Normal file
11
app/Models/GroupActivityGraph.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class GroupActivityGraph extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
}
|
11
app/Models/GroupBlock.php
Normal file
11
app/Models/GroupBlock.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class GroupBlock extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue