mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-17 20:11:27 +00:00
Merge branch 'staging' into feat/implement-admin-domain-blocks-api
This commit is contained in:
commit
1dbcdee289
435 changed files with 46242 additions and 11397 deletions
|
@ -7,7 +7,7 @@ jobs:
|
|||
build:
|
||||
docker:
|
||||
# Specify the version you desire here
|
||||
- image: cimg/php:8.2.5
|
||||
- image: cimg/php:8.3.8
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
|
@ -21,7 +21,12 @@ jobs:
|
|||
steps:
|
||||
- checkout
|
||||
|
||||
- run: sudo apt update && sudo apt install zlib1g-dev libsqlite3-dev
|
||||
- run:
|
||||
name: "Create Environment file and generate app key"
|
||||
command: |
|
||||
mv .env.testing .env
|
||||
|
||||
- run: sudo apt install zlib1g-dev libsqlite3-dev
|
||||
|
||||
# Download and cache dependencies
|
||||
|
||||
|
@ -36,18 +41,17 @@ jobs:
|
|||
- run: composer install -n --prefer-dist
|
||||
|
||||
- save_cache:
|
||||
key: composer-v2-{{ checksum "composer.lock" }}
|
||||
key: v2-dependencies-{{ checksum "composer.json" }}
|
||||
paths:
|
||||
- vendor
|
||||
|
||||
- run: cp .env.testing .env
|
||||
- run: php artisan config:cache
|
||||
- run: php artisan route:clear
|
||||
- run: php artisan storage:link
|
||||
- run: php artisan key:generate
|
||||
|
||||
# run tests with phpunit or codecept
|
||||
- run: ./vendor/bin/phpunit
|
||||
- run: php artisan test
|
||||
- store_test_results:
|
||||
path: tests/_output
|
||||
- store_artifacts:
|
||||
|
|
26
.env.docker
26
.env.docker
|
@ -60,6 +60,15 @@ ADMIN_DOMAIN="${APP_DOMAIN}"
|
|||
# @dottie/validate required,boolean
|
||||
#APP_DEBUG="false"
|
||||
|
||||
# Disable config cache
|
||||
#
|
||||
# If disabled, settings must be managed by .env variables.
|
||||
#
|
||||
# @default "false"
|
||||
# @see https://docs.pixelfed.org/technical-documentation/config/#config_cache
|
||||
# @dottie/validate required,boolean
|
||||
ENABLE_CONFIG_CACHE="true"
|
||||
|
||||
# Enable/disable new local account registrations.
|
||||
#
|
||||
# @default "true"
|
||||
|
@ -1025,7 +1034,7 @@ DOCKER_APP_HOST_CACHE_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH:?error}/pixelfed/ca
|
|||
|
||||
# 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)
|
||||
# you can set this to "0" to prevent them from running.
|
||||
#
|
||||
|
@ -1122,6 +1131,13 @@ DOCKER_APP_HOST_CACHE_PATH="${DOCKER_ALL_HOST_DATA_ROOT_PATH:?error}/pixelfed/ca
|
|||
# @dottie/validate required,oneof=0 1 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
|
||||
################################################################################
|
||||
|
@ -1202,6 +1218,14 @@ DOCKER_DB_HOST_PORT="${DB_PORT:?error}"
|
|||
# @dottie/validate required,number
|
||||
DOCKER_DB_CONTAINER_PORT="${DB_PORT:?error}"
|
||||
|
||||
# root password for the database. By default uses DB_PASSWORD
|
||||
# but can be changed in situations where you are migrating
|
||||
# to the included docker-compose and have a different password
|
||||
# set already
|
||||
#
|
||||
# @dottie/validate required
|
||||
DOCKER_DB_ROOT_PASSWORD="${DB_PASSWORD:?error}"
|
||||
|
||||
# How often Docker health check should run for [db] service
|
||||
# @dottie/validate required
|
||||
DOCKER_DB_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL:?error}"
|
||||
|
|
79
.env.example
Normal file
79
.env.example
Normal file
|
@ -0,0 +1,79 @@
|
|||
APP_NAME="Pixelfed"
|
||||
APP_ENV="production"
|
||||
APP_KEY=
|
||||
APP_DEBUG="false"
|
||||
|
||||
# Instance Configuration
|
||||
OPEN_REGISTRATION="false"
|
||||
ENFORCE_EMAIL_VERIFICATION="false"
|
||||
PF_MAX_USERS="1000"
|
||||
OAUTH_ENABLED="true"
|
||||
ENABLE_CONFIG_CACHE=true
|
||||
|
||||
# Media Configuration
|
||||
PF_OPTIMIZE_IMAGES="true"
|
||||
IMAGE_QUALITY="80"
|
||||
MAX_PHOTO_SIZE="15000"
|
||||
MAX_CAPTION_LENGTH="500"
|
||||
MAX_ALBUM_LENGTH="4"
|
||||
|
||||
# Instance URL Configuration
|
||||
APP_URL="http://localhost"
|
||||
APP_DOMAIN="localhost"
|
||||
ADMIN_DOMAIN="localhost"
|
||||
SESSION_DOMAIN="localhost"
|
||||
TRUST_PROXIES="*"
|
||||
|
||||
# Database Configuration
|
||||
DB_CONNECTION="mysql"
|
||||
DB_HOST="127.0.0.1"
|
||||
DB_PORT="3306"
|
||||
DB_DATABASE="pixelfed"
|
||||
DB_USERNAME="pixelfed"
|
||||
DB_PASSWORD="pixelfed"
|
||||
|
||||
# Redis Configuration
|
||||
REDIS_CLIENT="predis"
|
||||
REDIS_SCHEME="tcp"
|
||||
REDIS_HOST="127.0.0.1"
|
||||
REDIS_PASSWORD="null"
|
||||
REDIS_PORT="6379"
|
||||
|
||||
# Laravel Configuration
|
||||
SESSION_DRIVER="database"
|
||||
CACHE_DRIVER="redis"
|
||||
QUEUE_DRIVER="redis"
|
||||
BROADCAST_DRIVER="log"
|
||||
LOG_CHANNEL="stack"
|
||||
HORIZON_PREFIX="horizon-"
|
||||
|
||||
# ActivityPub Configuration
|
||||
ACTIVITY_PUB="false"
|
||||
AP_REMOTE_FOLLOW="false"
|
||||
AP_INBOX="false"
|
||||
AP_OUTBOX="false"
|
||||
AP_SHAREDINBOX="false"
|
||||
|
||||
# Experimental Configuration
|
||||
EXP_EMC="true"
|
||||
|
||||
## Mail Configuration (Post-Installer)
|
||||
MAIL_DRIVER=log
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="pixelfed@example.com"
|
||||
MAIL_FROM_NAME="Pixelfed"
|
||||
|
||||
## S3 Configuration (Post-Installer)
|
||||
PF_ENABLE_CLOUD=false
|
||||
FILESYSTEM_CLOUD=s3
|
||||
#AWS_ACCESS_KEY_ID=
|
||||
#AWS_SECRET_ACCESS_KEY=
|
||||
#AWS_DEFAULT_REGION=
|
||||
#AWS_BUCKET=<BucketName>
|
||||
#AWS_URL=
|
||||
#AWS_ENDPOINT=
|
||||
#AWS_USE_PATH_STYLE_ENDPOINT=false
|
|
@ -1,5 +1,6 @@
|
|||
ignored:
|
||||
- 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>`
|
||||
- DL3029 # warning: Do not use --platform flag with FROM
|
||||
- SC2046 # warning: Quote this to prevent word splitting.
|
||||
- SC2086 # info: Double quote to prevent globbing and word splitting.
|
||||
|
|
115
CHANGELOG.md
115
CHANGELOG.md
|
@ -1,6 +1,66 @@
|
|||
# Release Notes
|
||||
|
||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.13...dev)
|
||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev)
|
||||
|
||||
### 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))
|
||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||
|
||||
## [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)
|
||||
|
||||
### Updates
|
||||
- Update ApiV1Dot1Controller, fix in app registration bug that prevents proper auth flow due to missing oauth scopes ([cbf996c9](https://github.com/pixelfed/pixelfed/commit/cbf996c9))
|
||||
- Update ConfigCacheService, fix database race condition and fallback to file config and enable by default ([60a62b59](https://github.com/pixelfed/pixelfed/commit/60a62b59))
|
||||
|
||||
## [v0.12.0 (2024-04-29)](https://github.com/pixelfed/pixelfed/compare/v0.11.13...v0.12.0)
|
||||
|
||||
### Updates
|
||||
|
||||
|
@ -19,7 +79,58 @@
|
|||
- Update SiteController, add curatedOnboarding method that gracefully falls back to open registration when applicable ([95199843](https://github.com/pixelfed/pixelfed/commit/95199843))
|
||||
- Update AP transformers, add DeleteActor activity ([bcce1df6](https://github.com/pixelfed/pixelfed/commit/bcce1df6))
|
||||
- Update commands, add user account delete cli command to federate account deletion ([4aa0e25f](https://github.com/pixelfed/pixelfed/commit/4aa0e25f))
|
||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||
- Update web-api popular accounts route to its own method to remove the breaking oauth scope bug ([a4bc5ce3](https://github.com/pixelfed/pixelfed/commit/a4bc5ce3))
|
||||
- Update config cache ([5e4d4eff](https://github.com/pixelfed/pixelfed/commit/5e4d4eff))
|
||||
- Update Config, use config_cache ([7785a2da](https://github.com/pixelfed/pixelfed/commit/7785a2da))
|
||||
- Update ApiV1Dot1Controller, use config_cache for in-app registration ([b0cb4456](https://github.com/pixelfed/pixelfed/commit/b0cb4456))
|
||||
- Update captcha, use config_cache helper ([8a89e3c9](https://github.com/pixelfed/pixelfed/commit/8a89e3c9))
|
||||
- Update custom emoji, add config_cache support ([481314cd](https://github.com/pixelfed/pixelfed/commit/481314cd))
|
||||
- Update ProfileController, fix permalink redirect bug ([75081e60](https://github.com/pixelfed/pixelfed/commit/75081e60))
|
||||
- Update admin css, use font-display:swap for nucleo icons ([8a0c456e](https://github.com/pixelfed/pixelfed/commit/8a0c456e))
|
||||
- Update PixelfedDirectoryController, fix boolean cast bug ([f08aab22](https://github.com/pixelfed/pixelfed/commit/f08aab22))
|
||||
- Update PixelfedDirectoryController, use cached stats ([f2f2a809](https://github.com/pixelfed/pixelfed/commit/f2f2a809))
|
||||
- Update AdminDirectoryController, fix type casting ([ad506e90](https://github.com/pixelfed/pixelfed/commit/ad506e90))
|
||||
- Update image pipeline, use config_cache ([a72188a7](https://github.com/pixelfed/pixelfed/commit/a72188a7))
|
||||
- Update cloud storage, use config_cache ([665581d8](https://github.com/pixelfed/pixelfed/commit/665581d8))
|
||||
- Update pixelfed.max_album_length, use config_cache ([fecbe189](https://github.com/pixelfed/pixelfed/commit/fecbe189))
|
||||
- Update media_types, use config_cache ([d670de17](https://github.com/pixelfed/pixelfed/commit/d670de17))
|
||||
- Update landing settings, use config_cache ([40478f25](https://github.com/pixelfed/pixelfed/commit/40478f25))
|
||||
- Update activitypub setting, use config_cache ([5071aaf4](https://github.com/pixelfed/pixelfed/commit/5071aaf4))
|
||||
- Update oauth setting, use config_cache ([ce228f7f](https://github.com/pixelfed/pixelfed/commit/ce228f7f))
|
||||
- Update stories config, use config_cache ([d1adb109](https://github.com/pixelfed/pixelfed/commit/d1adb109))
|
||||
- Update ig import, use config_cache ([da0e0ffa](https://github.com/pixelfed/pixelfed/commit/da0e0ffa))
|
||||
- Update autospam config, use config_cache ([a76cb5f4](https://github.com/pixelfed/pixelfed/commit/a76cb5f4))
|
||||
- Update app.name config, use config_cache ([911446c0](https://github.com/pixelfed/pixelfed/commit/911446c0))
|
||||
- Update UserObserver, fix type casting ([949e9979](https://github.com/pixelfed/pixelfed/commit/949e9979))
|
||||
- Update user_filters, use config_cache ([6ce513f8](https://github.com/pixelfed/pixelfed/commit/6ce513f8))
|
||||
- Update filesystems config, add to config_cache ([087b2791](https://github.com/pixelfed/pixelfed/commit/087b2791))
|
||||
- Update web-admin routes, add setting api routes ([828a456f](https://github.com/pixelfed/pixelfed/commit/828a456f))
|
||||
- Update hashtag component ([cee979ed](https://github.com/pixelfed/pixelfed/commit/cee979ed))
|
||||
- Update AdminReadMore component, add .prevent to click action ([704e7b12](https://github.com/pixelfed/pixelfed/commit/704e7b12))
|
||||
- Update admin dashboard, add admin settings partials ([eb487123](https://github.com/pixelfed/pixelfed/commit/eb487123))
|
||||
- Update admin settings, refactor to vue component ([674e560f](https://github.com/pixelfed/pixelfed/commit/674e560f))
|
||||
- Update ConfigCacheService, encrypt keys at rest ([3628b462](https://github.com/pixelfed/pixelfed/commit/3628b462))
|
||||
- Update RemoteFollowImportRecent, use MediaPathService ([5162c070](https://github.com/pixelfed/pixelfed/commit/5162c070))
|
||||
- Update AdminSettingsController, add user filter max limit settings ([ac1f0748](https://github.com/pixelfed/pixelfed/commit/ac1f0748))
|
||||
- Update AdminSettingsController, add AdminSettingsService ([dcc5f416](https://github.com/pixelfed/pixelfed/commit/dcc5f416))
|
||||
- Update AdminSettings component, fix user settings ([aba1e13d](https://github.com/pixelfed/pixelfed/commit/aba1e13d))
|
||||
- Update AdminInstances component ([ec2fdd61](https://github.com/pixelfed/pixelfed/commit/ec2fdd61))
|
||||
- Update AdminSettings, add max_account_size support ([2dcbc1d5](https://github.com/pixelfed/pixelfed/commit/2dcbc1d5))
|
||||
- Update AdminSettings, use better validation for user integer settings ([d946afcc](https://github.com/pixelfed/pixelfed/commit/d946afcc))
|
||||
- Update spa sass, fix timestamp dark mode bug ([4147f7c5](https://github.com/pixelfed/pixelfed/commit/4147f7c5))
|
||||
- Update relationships view, fix unfollow hashtag bug. Fixes #5008 ([8c693640](https://github.com/pixelfed/pixelfed/commit/8c693640))
|
||||
- Update PrivacySettings controller, refresh RelationshipService when unmute/unblocking ([b7322b68](https://github.com/pixelfed/pixelfed/commit/b7322b68))
|
||||
- Update ApiV1Controller, improve refresh relations logic when (un)muting or (un)blocking ([b8e96a5f](https://github.com/pixelfed/pixelfed/commit/b8e96a5f))
|
||||
- Update context menu, add mute/block/unfollow actions and update relationship store accordingly ([81d1e0fd](https://github.com/pixelfed/pixelfed/commit/81d1e0fd))
|
||||
- Update docker env, fix config_cache. Fixes #5033 ([858fcbf6](https://github.com/pixelfed/pixelfed/commit/858fcbf6))
|
||||
- Update UnfollowPipeline, fix follower count cache bug ([6bdf73de](https://github.com/pixelfed/pixelfed/commit/6bdf73de))
|
||||
- Update VideoPresenter component, add webkit-playsinline attribute to video element to prevent the full screen video player ([ad032916](https://github.com/pixelfed/pixelfed/commit/ad032916))
|
||||
- Update VideoPlayer component, add playsinline attribute to video element ([8af23607](https://github.com/pixelfed/pixelfed/commit/8af23607))
|
||||
- Update StatusController, refactor status embeds ([9a7acc12](https://github.com/pixelfed/pixelfed/commit/9a7acc12))
|
||||
- Update ProfileController, refactor profile embeds ([8b8b1ffc](https://github.com/pixelfed/pixelfed/commit/8b8b1ffc))
|
||||
- Update profile embed view, fix height bug ([65166570](https://github.com/pixelfed/pixelfed/commit/65166570))
|
||||
- Update CustomEmojiService, only return local emoji ([7f8bba44](https://github.com/pixelfed/pixelfed/commit/7f8bba44))
|
||||
- Update Like model, increase max likes per day from 500 to 1500 ([4223119f](https://github.com/pixelfed/pixelfed/commit/4223119f))
|
||||
|
||||
## [v0.11.13 (2024-03-05)](https://github.com/pixelfed/pixelfed/compare/v0.11.12...v0.11.13)
|
||||
|
||||
|
|
56
Dockerfile
56
Dockerfile
|
@ -132,6 +132,10 @@ ENV DEBIAN_FRONTEND="noninteractive"
|
|||
# Ensure we run all scripts through 'bash' rather than 'sh'
|
||||
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 \
|
||||
&& mkdir -pv /var/www/ \
|
||||
&& chown -R ${RUNTIME_UID}:${RUNTIME_GID} /var/www
|
||||
|
@ -176,6 +180,55 @@ RUN --mount=type=cache,id=pixelfed-pear-${PHP_VERSION}-${PHP_DEBIAN_RELEASE}-${T
|
|||
PHP_PECL_EXTENSIONS_EXTRA=${PHP_PECL_EXTENSIONS_EXTRA} \
|
||||
/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 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
|
||||
#######################################################
|
||||
|
@ -231,13 +284,14 @@ 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=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=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 \
|
||||
&& composer dump-autoload --optimize
|
||||
&& ENABLE_CONFIG_CACHE=false composer dump-autoload --optimize
|
||||
|
||||
USER root
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ class AvatarStorage extends Command
|
|||
|
||||
$this->line(' ');
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage')) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage')) {
|
||||
$this->info('✅ - Cloud storage configured');
|
||||
$this->line(' ');
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class AvatarStorage extends Command
|
|||
$this->line(' ');
|
||||
}
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud')) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud')) {
|
||||
$disk = Storage::disk(config_cache('filesystems.cloud'));
|
||||
$exists = $disk->exists('cache/avatars/default.jpg');
|
||||
$state = $exists ? '✅' : '❌';
|
||||
|
@ -100,7 +100,7 @@ class AvatarStorage extends Command
|
|||
$this->info($msg);
|
||||
}
|
||||
|
||||
$options = config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud') ?
|
||||
$options = (bool) config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud') ?
|
||||
[
|
||||
'Cancel',
|
||||
'Upload default avatar to cloud',
|
||||
|
@ -164,7 +164,7 @@ class AvatarStorage extends Command
|
|||
|
||||
protected function uploadAvatarsToCloud()
|
||||
{
|
||||
if(!config_cache('pixelfed.cloud_storage') || !config('instance.avatar.local_to_cloud')) {
|
||||
if(!(bool) config_cache('pixelfed.cloud_storage') || !config('instance.avatar.local_to_cloud')) {
|
||||
$this->error('Enable cloud storage and avatar cloud storage to perform this action');
|
||||
return;
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ class AvatarStorage extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') == false && config_cache('federation.avatars.store_local') == false) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage') == false && config_cache('federation.avatars.store_local') == false) {
|
||||
$this->error('You have cloud storage disabled and local avatar storage disabled, we cannot refetch avatars.');
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class AvatarStorageDeepClean extends Command
|
|||
$this->line(' ');
|
||||
|
||||
$storage = [
|
||||
'cloud' => boolval(config_cache('pixelfed.cloud_storage')),
|
||||
'cloud' => (bool) config_cache('pixelfed.cloud_storage'),
|
||||
'local' => boolval(config_cache('federation.avatars.store_local'))
|
||||
];
|
||||
|
||||
|
|
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,12 +35,16 @@ class CloudMediaMigrate extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$enabled = config('pixelfed.cloud_storage');
|
||||
$enabled = (bool) config_cache('pixelfed.cloud_storage');
|
||||
if(!$enabled) {
|
||||
$this->error('Cloud storage not enabled. Exiting...');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!$this->confirm('Are you sure you want to proceed?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$limit = $this->option('limit');
|
||||
$hugeMode = $this->option('huge');
|
||||
|
||||
|
|
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;
|
||||
}
|
||||
}
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Media;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Services\MediaService;
|
||||
use App\Services\StatusService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class FetchMissingMediaMimeType extends Command
|
||||
{
|
||||
|
@ -29,20 +29,20 @@ class FetchMissingMediaMimeType extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
foreach(Media::whereNotNull(['remote_url', 'status_id'])->whereNull('mime')->lazyByIdDesc(50, 'id') as $media) {
|
||||
foreach (Media::whereNotNull(['remote_url', 'status_id'])->whereNull('mime')->lazyByIdDesc(50, 'id') as $media) {
|
||||
$res = Http::retry(2, 100, throw: false)->head($media->remote_url);
|
||||
|
||||
if(!$res->successful()) {
|
||||
if (! $res->successful()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!in_array($res->header('content-type'), explode(',',config('pixelfed.media_types')))) {
|
||||
if (! in_array($res->header('content-type'), explode(',', config_cache('pixelfed.media_types')))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$media->mime = $res->header('content-type');
|
||||
|
||||
if($res->hasHeader('content-length')) {
|
||||
if ($res->hasHeader('content-length')) {
|
||||
$media->size = $res->header('content-length');
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ class FetchMissingMediaMimeType extends Command
|
|||
|
||||
MediaService::del($media->status_id);
|
||||
StatusService::del($media->status_id);
|
||||
$this->info('mid:'.$media->id . ' (' . $res->header('content-type') . ':' . $res->header('content-length') . ' bytes)');
|
||||
$this->info('mid:'.$media->id.' ('.$res->header('content-type').':'.$res->header('content-length').' bytes)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ class FixMediaDriver extends Command
|
|||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') == false) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage') == false) {
|
||||
$this->error('Cloud storage not enabled, exiting...');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
|
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']);
|
||||
|
||||
}
|
||||
}
|
|
@ -47,7 +47,7 @@ class MediaCloudUrlRewrite extends Command implements PromptsForMissingInput
|
|||
|
||||
protected function preflightCheck()
|
||||
{
|
||||
if(config_cache('pixelfed.cloud_storage') != true) {
|
||||
if(!(bool) config_cache('pixelfed.cloud_storage')) {
|
||||
$this->info('Error: Cloud storage is not enabled!');
|
||||
$this->error('Aborting...');
|
||||
exit;
|
||||
|
|
|
@ -45,7 +45,7 @@ class MediaS3GarbageCollector extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$enabled = in_array(config_cache('pixelfed.cloud_storage'), ['1', true, 'true']);
|
||||
$enabled = (bool) config_cache('pixelfed.cloud_storage');
|
||||
if(!$enabled) {
|
||||
$this->error('Cloud storage not enabled. Exiting...');
|
||||
return;
|
||||
|
|
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.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
@ -32,8 +31,9 @@ class Kernel extends ConsoleKernel
|
|||
$schedule->command('gc:failedjobs')->dailyAt(3)->onOneServer();
|
||||
$schedule->command('gc:passwordreset')->dailyAt('09:41')->onOneServer();
|
||||
$schedule->command('gc:sessions')->twiceDaily(13, 23)->onOneServer();
|
||||
$schedule->command('app:weekly-instance-scan')->weeklyOn(2, '4:20')->onOneServer();
|
||||
|
||||
if (in_array(config_cache('pixelfed.cloud_storage'), ['1', true, 'true']) && config('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);
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,7 @@ class Kernel extends ConsoleKernel
|
|||
$schedule->command('app:notification-epoch-update')->weeklyOn(1, '2:21')->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:instance-update-total-local-posts')->twiceDailyAt(1, 13, 45)->onOneServer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +61,7 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ class AccountController extends Controller
|
|||
|
||||
$pid = $request->user()->profile_id;
|
||||
$count = UserFilterService::muteCount($pid);
|
||||
$maxLimit = intval(config('instance.user_filters.max_user_mutes'));
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_user_mutes');
|
||||
abort_if($count >= $maxLimit, 422, self::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts');
|
||||
if($count == 0) {
|
||||
$filterCount = UserFilter::whereUserId($pid)->count();
|
||||
|
@ -260,7 +260,7 @@ class AccountController extends Controller
|
|||
]);
|
||||
$pid = $request->user()->profile_id;
|
||||
$count = UserFilterService::blockCount($pid);
|
||||
$maxLimit = intval(config('instance.user_filters.max_user_blocks'));
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_user_blocks');
|
||||
abort_if($count >= $maxLimit, 422, self::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts');
|
||||
if($count == 0) {
|
||||
$filterCount = UserFilter::whereUserId($pid)->whereFilterType('block')->count();
|
||||
|
|
|
@ -2,30 +2,20 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use DB, Cache;
|
||||
use App\{
|
||||
DiscoverCategory,
|
||||
DiscoverCategoryHashtag,
|
||||
Hashtag,
|
||||
Media,
|
||||
Profile,
|
||||
Status,
|
||||
StatusHashtag,
|
||||
User
|
||||
};
|
||||
use App\Http\Controllers\PixelfedDirectoryController;
|
||||
use App\Models\ConfigCache;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\ConfigCacheService;
|
||||
use App\Services\StatusService;
|
||||
use Carbon\Carbon;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
use Cache;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use League\ISO3166\ISO3166;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Http\Controllers\PixelfedDirectoryController;
|
||||
use Illuminate\Support\Str;
|
||||
use League\ISO3166\ISO3166;
|
||||
|
||||
trait AdminDirectoryController
|
||||
{
|
||||
|
@ -41,37 +31,37 @@ trait AdminDirectoryController
|
|||
$res['countries'] = collect((new ISO3166)->all())->pluck('name');
|
||||
$res['admins'] = User::whereIsAdmin(true)
|
||||
->where('2fa_enabled', true)
|
||||
->get()->map(function($user) {
|
||||
return [
|
||||
'uid' => (string) $user->id,
|
||||
'pid' => (string) $user->profile_id,
|
||||
'username' => $user->username,
|
||||
'created_at' => $user->created_at
|
||||
];
|
||||
});
|
||||
->get()->map(function ($user) {
|
||||
return [
|
||||
'uid' => (string) $user->id,
|
||||
'pid' => (string) $user->profile_id,
|
||||
'username' => $user->username,
|
||||
'created_at' => $user->created_at,
|
||||
];
|
||||
});
|
||||
$config = ConfigCache::whereK('pixelfed.directory')->first();
|
||||
if($config) {
|
||||
if ($config) {
|
||||
$data = $config->v ? json_decode($config->v, true) : [];
|
||||
$res = array_merge($res, $data);
|
||||
}
|
||||
|
||||
if(empty($res['summary'])) {
|
||||
if (empty($res['summary'])) {
|
||||
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
||||
$res['summary'] = $summary ? $summary[0] : null;
|
||||
}
|
||||
|
||||
if(isset($res['banner_image']) && !empty($res['banner_image'])) {
|
||||
if (isset($res['banner_image']) && ! empty($res['banner_image'])) {
|
||||
$res['banner_image'] = url(Storage::url($res['banner_image']));
|
||||
}
|
||||
|
||||
if(isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function($id) {
|
||||
if (isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function ($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
->filter(function ($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
||||
|
@ -84,22 +74,22 @@ trait AdminDirectoryController
|
|||
$res['feature_config'] = [
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
'optimize_image' => (bool) config_cache('pixelfed.optimize_image'),
|
||||
'max_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => config_cache('pixelfed.max_altext_length'),
|
||||
'enforce_account_limit' => config_cache('pixelfed.enforce_account_limit'),
|
||||
'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit'),
|
||||
'max_account_size' => config_cache('pixelfed.max_account_size'),
|
||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
||||
'account_deletion' => config_cache('pixelfed.account_deletion'),
|
||||
'account_deletion' => (bool) config_cache('pixelfed.account_deletion'),
|
||||
];
|
||||
|
||||
if(config_cache('pixelfed.directory.testimonials')) {
|
||||
$testimonials = collect(json_decode(config_cache('pixelfed.directory.testimonials'),true))
|
||||
->map(function($t) {
|
||||
if (config_cache('pixelfed.directory.testimonials')) {
|
||||
$testimonials = collect(json_decode(config_cache('pixelfed.directory.testimonials'), true))
|
||||
->map(function ($t) {
|
||||
return [
|
||||
'profile' => AccountService::get($t['profile_id']),
|
||||
'body' => $t['body']
|
||||
'body' => $t['body'],
|
||||
];
|
||||
});
|
||||
$res['testimonials'] = $testimonials;
|
||||
|
@ -108,8 +98,8 @@ trait AdminDirectoryController
|
|||
$validator = Validator::make($res['feature_config'], [
|
||||
'media_types' => [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
if (!in_array('image/jpeg', $value->toArray()) || !in_array('image/png', $value->toArray())) {
|
||||
function ($attribute, $value, $fail) {
|
||||
if (! in_array('image/jpeg', $value->toArray()) || ! in_array('image/png', $value->toArray())) {
|
||||
$fail('You must enable image/jpeg and image/png support.');
|
||||
}
|
||||
},
|
||||
|
@ -120,7 +110,7 @@ trait AdminDirectoryController
|
|||
'max_account_size' => 'required_if:enforce_account_limit,true|integer|min:1000000',
|
||||
'max_album_length' => 'required|integer|min:4|max:20',
|
||||
'account_deletion' => 'required|accepted',
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000'
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000',
|
||||
]);
|
||||
|
||||
$res['requirements_validator'] = $validator->errors();
|
||||
|
@ -146,11 +136,11 @@ trait AdminDirectoryController
|
|||
foreach (new \DirectoryIterator($path) as $io) {
|
||||
$name = $io->getFilename();
|
||||
$skip = ['vendor'];
|
||||
if($io->isDot() || in_array($name, $skip)) {
|
||||
if ($io->isDot() || in_array($name, $skip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if($io->isDir()) {
|
||||
if ($io->isDir()) {
|
||||
$langs->push(['code' => $name, 'name' => locale_get_display_name($name)]);
|
||||
}
|
||||
}
|
||||
|
@ -159,25 +149,26 @@ trait AdminDirectoryController
|
|||
$res['primary_locale'] = config('app.locale');
|
||||
|
||||
$submissionState = Http::withoutVerifying()
|
||||
->post('https://pixelfed.org/api/v1/directory/check-submission', [
|
||||
'domain' => config('pixelfed.domain.app')
|
||||
]);
|
||||
->post('https://pixelfed.org/api/v1/directory/check-submission', [
|
||||
'domain' => config('pixelfed.domain.app'),
|
||||
]);
|
||||
|
||||
$res['submission_state'] = $submissionState->json();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function validVal($res, $val, $count = false, $minLen = false)
|
||||
{
|
||||
if(!isset($res[$val])) {
|
||||
if (! isset($res[$val])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($count) {
|
||||
if ($count) {
|
||||
return count($res[$val]) >= $count;
|
||||
}
|
||||
|
||||
if($minLen) {
|
||||
if ($minLen) {
|
||||
return strlen($res[$val]) >= $minLen;
|
||||
}
|
||||
|
||||
|
@ -194,11 +185,11 @@ trait AdminDirectoryController
|
|||
'favourite_posts' => 'array|max:12',
|
||||
'favourite_posts.*' => 'distinct',
|
||||
'privacy_pledge' => 'sometimes',
|
||||
'banner_image' => 'sometimes|mimes:jpg,png|dimensions:width=1920,height:1080|max:5000'
|
||||
'banner_image' => 'sometimes|mimes:jpg,png|dimensions:width=1920,height:1080|max:5000',
|
||||
]);
|
||||
|
||||
$config = ConfigCache::firstOrNew([
|
||||
'k' => 'pixelfed.directory'
|
||||
'k' => 'pixelfed.directory',
|
||||
]);
|
||||
|
||||
$res = $config->v ? json_decode($config->v, true) : [];
|
||||
|
@ -208,26 +199,27 @@ trait AdminDirectoryController
|
|||
$res['contact_email'] = $request->input('contact_email');
|
||||
$res['privacy_pledge'] = (bool) $request->input('privacy_pledge');
|
||||
|
||||
if($request->filled('location')) {
|
||||
if ($request->filled('location')) {
|
||||
$exists = (new ISO3166)->name($request->location);
|
||||
if($exists) {
|
||||
if ($exists) {
|
||||
$res['location'] = $request->input('location');
|
||||
}
|
||||
}
|
||||
|
||||
if($request->hasFile('banner_image')) {
|
||||
if ($request->hasFile('banner_image')) {
|
||||
collect(Storage::files('public/headers'))
|
||||
->filter(function($name) {
|
||||
$protected = [
|
||||
'public/headers/.gitignore',
|
||||
'public/headers/default.jpg',
|
||||
'public/headers/missing.png'
|
||||
];
|
||||
return !in_array($name, $protected);
|
||||
})
|
||||
->each(function($name) {
|
||||
Storage::delete($name);
|
||||
});
|
||||
->filter(function ($name) {
|
||||
$protected = [
|
||||
'public/headers/.gitignore',
|
||||
'public/headers/default.jpg',
|
||||
'public/headers/missing.png',
|
||||
];
|
||||
|
||||
return ! in_array($name, $protected);
|
||||
})
|
||||
->each(function ($name) {
|
||||
Storage::delete($name);
|
||||
});
|
||||
$path = $request->file('banner_image')->storePublicly('public/headers');
|
||||
$res['banner_image'] = $path;
|
||||
ConfigCacheService::put('app.banner_image', url(Storage::url($path)));
|
||||
|
@ -240,9 +232,10 @@ trait AdminDirectoryController
|
|||
|
||||
ConfigCacheService::put('pixelfed.directory', $config->v);
|
||||
$updated = json_decode($config->v, true);
|
||||
if(isset($updated['banner_image'])) {
|
||||
if (isset($updated['banner_image'])) {
|
||||
$updated['banner_image'] = url(Storage::url($updated['banner_image']));
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
|
@ -253,7 +246,7 @@ trait AdminDirectoryController
|
|||
'open_registration' => (bool) config_cache('pixelfed.open_registration'),
|
||||
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
|
||||
'activitypub_enabled' => config_cache('federation.activitypub.enabled'),
|
||||
'oauth_enabled' => config_cache('pixelfed.oauth_enabled'),
|
||||
'oauth_enabled' => (bool) config_cache('pixelfed.oauth_enabled'),
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
|
@ -273,8 +266,8 @@ trait AdminDirectoryController
|
|||
'oauth_enabled' => 'required|accepted',
|
||||
'media_types' => [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
if (!in_array('image/jpeg', $value->toArray()) || !in_array('image/png', $value->toArray())) {
|
||||
function ($attribute, $value, $fail) {
|
||||
if (! in_array('image/jpeg', $value->toArray()) || ! in_array('image/png', $value->toArray())) {
|
||||
$fail('You must enable image/jpeg and image/png support.');
|
||||
}
|
||||
},
|
||||
|
@ -285,10 +278,10 @@ trait AdminDirectoryController
|
|||
'max_account_size' => 'required_if:enforce_account_limit,true|integer|min:1000000',
|
||||
'max_album_length' => 'required|integer|min:4|max:20',
|
||||
'account_deletion' => 'required|accepted',
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000'
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000',
|
||||
]);
|
||||
|
||||
if(!$validator->validate()) {
|
||||
if (! $validator->validate()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
|
@ -297,6 +290,7 @@ trait AdminDirectoryController
|
|||
|
||||
$data = (new PixelfedDirectoryController())->buildListing();
|
||||
$res = Http::withoutVerifying()->post('https://pixelfed.org/api/v1/directory/submission', $data);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
|
@ -304,7 +298,7 @@ trait AdminDirectoryController
|
|||
{
|
||||
$bannerImage = ConfigCache::whereK('app.banner_image')->first();
|
||||
$directory = ConfigCache::whereK('pixelfed.directory')->first();
|
||||
if(!$bannerImage && !$directory || empty($directory->v)) {
|
||||
if (! $bannerImage && ! $directory || empty($directory->v)) {
|
||||
return;
|
||||
}
|
||||
$directoryArr = json_decode($directory->v, true);
|
||||
|
@ -312,12 +306,12 @@ trait AdminDirectoryController
|
|||
$protected = [
|
||||
'public/headers/.gitignore',
|
||||
'public/headers/default.jpg',
|
||||
'public/headers/missing.png'
|
||||
'public/headers/missing.png',
|
||||
];
|
||||
if(!$path || in_array($path, $protected)) {
|
||||
if (! $path || in_array($path, $protected)) {
|
||||
return;
|
||||
}
|
||||
if(Storage::exists($directoryArr['banner_image'])) {
|
||||
if (Storage::exists($directoryArr['banner_image'])) {
|
||||
Storage::delete($directoryArr['banner_image']);
|
||||
}
|
||||
|
||||
|
@ -328,12 +322,13 @@ trait AdminDirectoryController
|
|||
$bannerImage->save();
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
ConfigCacheService::put('pixelfed.directory', $directory);
|
||||
|
||||
return $bannerImage->v;
|
||||
}
|
||||
|
||||
public function directoryGetPopularPosts(Request $request)
|
||||
{
|
||||
$ids = Cache::remember('admin:api:popular_posts', 86400, function() {
|
||||
$ids = Cache::remember('admin:api:popular_posts', 86400, function () {
|
||||
return Status::whereLocal(true)
|
||||
->whereScope('public')
|
||||
->whereType('photo')
|
||||
|
@ -343,21 +338,21 @@ trait AdminDirectoryController
|
|||
->pluck('id');
|
||||
});
|
||||
|
||||
$res = $ids->map(function($id) {
|
||||
$res = $ids->map(function ($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
->filter(function ($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function directoryGetAddPostByIdSearch(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|integer'
|
||||
'q' => 'required|integer',
|
||||
]);
|
||||
|
||||
$id = $request->input('q');
|
||||
|
@ -380,11 +375,12 @@ trait AdminDirectoryController
|
|||
$profile_id = $request->input('profile_id');
|
||||
$testimonials = ConfigCache::whereK('pixelfed.directory.testimonials')->firstOrFail();
|
||||
$existing = collect(json_decode($testimonials->v, true))
|
||||
->filter(function($t) use($profile_id) {
|
||||
->filter(function ($t) use ($profile_id) {
|
||||
return $t['profile_id'] !== $profile_id;
|
||||
})
|
||||
->values();
|
||||
ConfigCacheService::put('pixelfed.directory.testimonials', $existing);
|
||||
|
||||
return $existing;
|
||||
}
|
||||
|
||||
|
@ -392,13 +388,13 @@ trait AdminDirectoryController
|
|||
{
|
||||
$this->validate($request, [
|
||||
'username' => 'required',
|
||||
'body' => 'required|string|min:5|max:500'
|
||||
'body' => 'required|string|min:5|max:500',
|
||||
]);
|
||||
|
||||
$user = User::whereUsername($request->input('username'))->whereNull('status')->firstOrFail();
|
||||
|
||||
$configCache = ConfigCache::firstOrCreate([
|
||||
'k' => 'pixelfed.directory.testimonials'
|
||||
'k' => 'pixelfed.directory.testimonials',
|
||||
]);
|
||||
|
||||
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
||||
|
@ -409,7 +405,7 @@ trait AdminDirectoryController
|
|||
$testimonials->push([
|
||||
'profile_id' => (string) $user->profile_id,
|
||||
'username' => $request->input('username'),
|
||||
'body' => $request->input('body')
|
||||
'body' => $request->input('body'),
|
||||
]);
|
||||
|
||||
$configCache->v = json_encode($testimonials->toArray());
|
||||
|
@ -417,8 +413,9 @@ trait AdminDirectoryController
|
|||
ConfigCacheService::put('pixelfed.directory.testimonials', $configCache->v);
|
||||
$res = [
|
||||
'profile' => AccountService::get($user->profile_id),
|
||||
'body' => $request->input('body')
|
||||
'body' => $request->input('body'),
|
||||
];
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
@ -426,7 +423,7 @@ trait AdminDirectoryController
|
|||
{
|
||||
$this->validate($request, [
|
||||
'profile_id' => 'required',
|
||||
'body' => 'required|string|min:5|max:500'
|
||||
'body' => 'required|string|min:5|max:500',
|
||||
]);
|
||||
|
||||
$profile_id = $request->input('profile_id');
|
||||
|
@ -434,18 +431,19 @@ trait AdminDirectoryController
|
|||
$user = User::whereProfileId($profile_id)->firstOrFail();
|
||||
|
||||
$configCache = ConfigCache::firstOrCreate([
|
||||
'k' => 'pixelfed.directory.testimonials'
|
||||
'k' => 'pixelfed.directory.testimonials',
|
||||
]);
|
||||
|
||||
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
||||
|
||||
$updated = $testimonials->map(function($t) use($profile_id, $body) {
|
||||
if($t['profile_id'] == $profile_id) {
|
||||
$updated = $testimonials->map(function ($t) use ($profile_id, $body) {
|
||||
if ($t['profile_id'] == $profile_id) {
|
||||
$t['body'] = $body;
|
||||
}
|
||||
|
||||
return $t;
|
||||
})
|
||||
->values();
|
||||
->values();
|
||||
|
||||
$configCache->v = json_encode($updated);
|
||||
$configCache->save();
|
||||
|
|
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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,7 +7,9 @@ use App\Models\InstanceActor;
|
|||
use App\Page;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\AdminSettingsService;
|
||||
use App\Services\ConfigCacheService;
|
||||
use App\Services\FilesystemService;
|
||||
use App\User;
|
||||
use App\Util\Site\Config;
|
||||
use Artisan;
|
||||
|
@ -71,6 +73,7 @@ trait AdminSettingsController
|
|||
'admin_account_id' => 'nullable',
|
||||
'regs' => 'required|in:open,filtered,closed',
|
||||
'account_migration' => 'nullable',
|
||||
'rule_delete' => 'sometimes',
|
||||
]);
|
||||
|
||||
$orb = false;
|
||||
|
@ -310,4 +313,573 @@ trait AdminSettingsController
|
|||
|
||||
return view('admin.settings.system', compact('sys'));
|
||||
}
|
||||
|
||||
public function settingsApiFetch(Request $request)
|
||||
{
|
||||
$cloud_storage = ConfigCacheService::get('pixelfed.cloud_storage');
|
||||
$cloud_disk = config('filesystems.cloud');
|
||||
$cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret'));
|
||||
$types = explode(',', ConfigCacheService::get('pixelfed.media_types'));
|
||||
$rules = ConfigCacheService::get('app.rules') ? json_decode(ConfigCacheService::get('app.rules'), true) : [];
|
||||
$jpeg = in_array('image/jpg', $types) || in_array('image/jpeg', $types);
|
||||
$png = in_array('image/png', $types);
|
||||
$gif = in_array('image/gif', $types);
|
||||
$mp4 = in_array('video/mp4', $types);
|
||||
$webp = in_array('image/webp', $types);
|
||||
|
||||
$availableAdmins = User::whereIsAdmin(true)->get();
|
||||
$currentAdmin = config_cache('instance.admin.pid') ? AccountService::get(config_cache('instance.admin.pid'), true) : null;
|
||||
$openReg = (bool) config_cache('pixelfed.open_registration');
|
||||
$curOnboarding = (bool) config_cache('instance.curated_registration.enabled');
|
||||
$regState = $openReg ? 'open' : ($curOnboarding ? 'filtered' : 'closed');
|
||||
$accountMigration = (bool) config_cache('federation.migration');
|
||||
$autoFollow = config_cache('account.autofollow_usernames');
|
||||
if (strlen($autoFollow) > 3) {
|
||||
$autoFollow = explode(',', $autoFollow);
|
||||
}
|
||||
|
||||
$res = AdminSettingsService::getAll();
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function settingsApiRulesAdd(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'rule' => 'required|string|min:5|max:1000',
|
||||
]);
|
||||
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
$val = $request->input('rule');
|
||||
if (! $rules) {
|
||||
ConfigCacheService::put('app.rules', json_encode([$val]));
|
||||
} else {
|
||||
$json = json_decode($rules, true);
|
||||
$count = count($json);
|
||||
if ($count >= 30) {
|
||||
return response()->json(['message' => 'Max rules limit reached, you can set up to 30 rules at a time.'], 400);
|
||||
}
|
||||
$json[] = $val;
|
||||
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
|
||||
}
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return [$val];
|
||||
}
|
||||
|
||||
public function settingsApiRulesDelete(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'rule' => 'required|string',
|
||||
]);
|
||||
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
$val = $request->input('rule');
|
||||
|
||||
if (! $rules) {
|
||||
return [];
|
||||
} else {
|
||||
$json = json_decode($rules, true);
|
||||
$idx = array_search($val, $json);
|
||||
if ($idx !== false) {
|
||||
unset($json[$idx]);
|
||||
$json = array_values($json);
|
||||
}
|
||||
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
|
||||
}
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return response()->json($json);
|
||||
}
|
||||
|
||||
public function settingsApiRulesDeleteAll(Request $request)
|
||||
{
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
|
||||
if (! $rules) {
|
||||
return [];
|
||||
} else {
|
||||
ConfigCacheService::put('app.rules', json_encode([]));
|
||||
}
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
public function settingsApiAutofollowDelete(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'username' => 'required|string',
|
||||
]);
|
||||
|
||||
$username = $request->input('username');
|
||||
$names = [];
|
||||
$existing = config_cache('account.autofollow_usernames');
|
||||
if ($existing) {
|
||||
$names = explode(',', $existing);
|
||||
}
|
||||
|
||||
if (in_array($username, $names)) {
|
||||
$key = array_search($username, $names);
|
||||
if ($key !== false) {
|
||||
unset($names[$key]);
|
||||
}
|
||||
}
|
||||
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
|
||||
|
||||
return response()->json(['accounts' => array_values($names)]);
|
||||
}
|
||||
|
||||
public function settingsApiAutofollowAdd(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'username' => 'required|string',
|
||||
]);
|
||||
|
||||
$username = $request->input('username');
|
||||
$names = [];
|
||||
$existing = config_cache('account.autofollow_usernames');
|
||||
if ($existing) {
|
||||
$names = explode(',', $existing);
|
||||
}
|
||||
|
||||
if ($existing && count($names)) {
|
||||
if (count($names) >= 5) {
|
||||
return response()->json(['message' => 'You can only add up to 5 accounts to be autofollowed.'], 400);
|
||||
}
|
||||
if (in_array(strtolower($username), array_map('strtolower', $names))) {
|
||||
return response()->json(['message' => 'User already exists, please try again.'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
$p = User::whereUsername($username)->whereNull('status')->first();
|
||||
if (! $p || in_array($p->username, $names)) {
|
||||
abort(404);
|
||||
}
|
||||
array_push($names, $p->username);
|
||||
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
|
||||
|
||||
return response()->json(['accounts' => array_values($names)]);
|
||||
}
|
||||
|
||||
public function settingsApiUpdateType(Request $request, $type)
|
||||
{
|
||||
abort_unless(in_array($type, [
|
||||
'posts',
|
||||
'platform',
|
||||
'home',
|
||||
'landing',
|
||||
'branding',
|
||||
'media',
|
||||
'users',
|
||||
'storage',
|
||||
]), 400);
|
||||
|
||||
switch ($type) {
|
||||
case 'home':
|
||||
return $this->settingsApiUpdateHomeType($request);
|
||||
break;
|
||||
|
||||
case 'landing':
|
||||
return $this->settingsApiUpdateLandingType($request);
|
||||
break;
|
||||
|
||||
case 'posts':
|
||||
return $this->settingsApiUpdatePostsType($request);
|
||||
break;
|
||||
|
||||
case 'platform':
|
||||
return $this->settingsApiUpdatePlatformType($request);
|
||||
break;
|
||||
|
||||
case 'branding':
|
||||
return $this->settingsApiUpdateBrandingType($request);
|
||||
break;
|
||||
|
||||
case 'media':
|
||||
return $this->settingsApiUpdateMediaType($request);
|
||||
break;
|
||||
|
||||
case 'users':
|
||||
return $this->settingsApiUpdateUsersType($request);
|
||||
break;
|
||||
|
||||
case 'storage':
|
||||
return $this->settingsApiUpdateStorageType($request);
|
||||
break;
|
||||
|
||||
default:
|
||||
abort(404);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function settingsApiUpdateHomeType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'registration_status' => 'required|in:open,filtered,closed',
|
||||
'cloud_storage' => 'required',
|
||||
'activitypub_enabled' => 'required',
|
||||
'account_migration' => 'required',
|
||||
'mobile_apis' => 'required',
|
||||
'stories' => 'required',
|
||||
'instagram_import' => 'required',
|
||||
'autospam_enabled' => 'required',
|
||||
]);
|
||||
|
||||
$regStatus = $request->input('registration_status');
|
||||
ConfigCacheService::put('pixelfed.open_registration', $regStatus === 'open');
|
||||
ConfigCacheService::put('instance.curated_registration.enabled', $regStatus === 'filtered');
|
||||
$cloudStorage = $request->boolean('cloud_storage');
|
||||
if ($cloudStorage !== (bool) config_cache('pixelfed.cloud_storage')) {
|
||||
if (! $cloudStorage) {
|
||||
ConfigCacheService::put('pixelfed.cloud_storage', false);
|
||||
} else {
|
||||
$cloud_disk = config('filesystems.cloud');
|
||||
$cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret'));
|
||||
if (! $cloud_ready) {
|
||||
return redirect()->back()->withErrors(['cloud_storage' => 'Must configure cloud storage before enabling!']);
|
||||
} else {
|
||||
ConfigCacheService::put('pixelfed.cloud_storage', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigCacheService::put('federation.activitypub.enabled', $request->boolean('activitypub_enabled'));
|
||||
ConfigCacheService::put('federation.migration', $request->boolean('account_migration'));
|
||||
ConfigCacheService::put('pixelfed.oauth_enabled', $request->boolean('mobile_apis'));
|
||||
ConfigCacheService::put('instance.stories.enabled', $request->boolean('stories'));
|
||||
ConfigCacheService::put('pixelfed.import.instagram.enabled', $request->boolean('instagram_import'));
|
||||
ConfigCacheService::put('pixelfed.bouncer.enabled', $request->boolean('autospam_enabled'));
|
||||
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Config::refresh();
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function settingsApiUpdateLandingType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'current_admin' => 'required',
|
||||
'show_directory' => 'required',
|
||||
'show_explore' => 'required',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('instance.admin.pid', $request->input('current_admin'));
|
||||
ConfigCacheService::put('instance.landing.show_directory', $request->boolean('show_directory'));
|
||||
ConfigCacheService::put('instance.landing.show_explore', $request->boolean('show_explore'));
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Config::refresh();
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function settingsApiUpdateMediaType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'image_quality' => 'required|integer|min:1|max:100',
|
||||
'max_album_length' => 'required|integer|min:1|max:20',
|
||||
'max_photo_size' => 'required|integer|min:100|max:50000',
|
||||
'media_types' => 'required',
|
||||
'optimize_image' => 'required',
|
||||
'optimize_video' => 'required',
|
||||
]);
|
||||
|
||||
$mediaTypes = $request->input('media_types');
|
||||
$mediaArray = explode(',', $mediaTypes);
|
||||
foreach ($mediaArray as $mediaType) {
|
||||
if (! in_array($mediaType, ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4'])) {
|
||||
return redirect()->back()->withErrors(['media_types' => 'Invalid media type']);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigCacheService::put('pixelfed.media_types', $request->input('media_types'));
|
||||
ConfigCacheService::put('pixelfed.image_quality', $request->input('image_quality'));
|
||||
ConfigCacheService::put('pixelfed.max_album_length', $request->input('max_album_length'));
|
||||
ConfigCacheService::put('pixelfed.max_photo_size', $request->input('max_photo_size'));
|
||||
ConfigCacheService::put('pixelfed.optimize_image', $request->boolean('optimize_image'));
|
||||
ConfigCacheService::put('pixelfed.optimize_video', $request->boolean('optimize_video'));
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Config::refresh();
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function settingsApiUpdateBrandingType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required',
|
||||
'short_description' => 'required',
|
||||
'long_description' => 'required',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('app.name', $request->input('name'));
|
||||
ConfigCacheService::put('app.short_description', $request->input('short_description'));
|
||||
ConfigCacheService::put('app.description', $request->input('long_description'));
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Config::refresh();
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function settingsApiUpdatePostsType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'max_caption_length' => 'required|integer|min:5|max:10000',
|
||||
'max_altext_length' => 'required|integer|min:5|max:40000',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('pixelfed.max_caption_length', $request->input('max_caption_length'));
|
||||
ConfigCacheService::put('pixelfed.max_altext_length', $request->input('max_altext_length'));
|
||||
$res = [
|
||||
'max_caption_length' => $request->input('max_caption_length'),
|
||||
'max_altext_length' => $request->input('max_altext_length'),
|
||||
];
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function settingsApiUpdatePlatformType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'allow_app_registration' => 'required',
|
||||
'app_registration_rate_limit_attempts' => 'required|integer|min:1',
|
||||
'app_registration_rate_limit_decay' => 'required|integer|min:1',
|
||||
'app_registration_confirm_rate_limit_attempts' => 'required|integer|min:1',
|
||||
'app_registration_confirm_rate_limit_decay' => 'required|integer|min:1',
|
||||
'allow_post_embeds' => 'required',
|
||||
'allow_profile_embeds' => 'required',
|
||||
'captcha_enabled' => 'required',
|
||||
'captcha_on_login' => 'required_if_accepted:captcha_enabled',
|
||||
'captcha_on_register' => 'required_if_accepted:captcha_enabled',
|
||||
'captcha_secret' => 'required_if_accepted:captcha_enabled',
|
||||
'captcha_sitekey' => 'required_if_accepted:captcha_enabled',
|
||||
'custom_emoji_enabled' => 'required',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('pixelfed.allow_app_registration', $request->boolean('allow_app_registration'));
|
||||
ConfigCacheService::put('pixelfed.app_registration_rate_limit_attempts', $request->input('app_registration_rate_limit_attempts'));
|
||||
ConfigCacheService::put('pixelfed.app_registration_rate_limit_decay', $request->input('app_registration_rate_limit_decay'));
|
||||
ConfigCacheService::put('pixelfed.app_registration_confirm_rate_limit_attempts', $request->input('app_registration_confirm_rate_limit_attempts'));
|
||||
ConfigCacheService::put('pixelfed.app_registration_confirm_rate_limit_decay', $request->input('app_registration_confirm_rate_limit_decay'));
|
||||
ConfigCacheService::put('instance.embed.post', $request->boolean('allow_post_embeds'));
|
||||
ConfigCacheService::put('instance.embed.profile', $request->boolean('allow_profile_embeds'));
|
||||
ConfigCacheService::put('federation.custom_emoji.enabled', $request->boolean('custom_emoji_enabled'));
|
||||
$captcha = $request->boolean('captcha_enabled');
|
||||
if ($captcha) {
|
||||
$secret = $request->input('captcha_secret');
|
||||
$sitekey = $request->input('captcha_sitekey');
|
||||
if (config_cache('captcha.secret') != $secret && strpos($secret, '*') === false) {
|
||||
ConfigCacheService::put('captcha.secret', $secret);
|
||||
}
|
||||
if (config_cache('captcha.sitekey') != $sitekey && strpos($sitekey, '*') === false) {
|
||||
ConfigCacheService::put('captcha.sitekey', $sitekey);
|
||||
}
|
||||
ConfigCacheService::put('captcha.active.login', $request->boolean('captcha_on_login'));
|
||||
ConfigCacheService::put('captcha.active.register', $request->boolean('captcha_on_register'));
|
||||
ConfigCacheService::put('captcha.triggers.login.enabled', $request->boolean('captcha_on_login'));
|
||||
ConfigCacheService::put('captcha.enabled', true);
|
||||
} else {
|
||||
ConfigCacheService::put('captcha.enabled', false);
|
||||
}
|
||||
$res = [
|
||||
'allow_app_registration' => $request->boolean('allow_app_registration'),
|
||||
'app_registration_rate_limit_attempts' => $request->input('app_registration_rate_limit_attempts'),
|
||||
'app_registration_rate_limit_decay' => $request->input('app_registration_rate_limit_decay'),
|
||||
'app_registration_confirm_rate_limit_attempts' => $request->input('app_registration_confirm_rate_limit_attempts'),
|
||||
'app_registration_confirm_rate_limit_decay' => $request->input('app_registration_confirm_rate_limit_decay'),
|
||||
'allow_post_embeds' => $request->boolean('allow_post_embeds'),
|
||||
'allow_profile_embeds' => $request->boolean('allow_profile_embeds'),
|
||||
'captcha_enabled' => $request->boolean('captcha_enabled'),
|
||||
'captcha_on_login' => $request->boolean('captcha_on_login'),
|
||||
'captcha_on_register' => $request->boolean('captcha_on_register'),
|
||||
'captcha_secret' => $request->input('captcha_secret'),
|
||||
'captcha_sitekey' => $request->input('captcha_sitekey'),
|
||||
'custom_emoji_enabled' => $request->boolean('custom_emoji_enabled'),
|
||||
];
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function settingsApiUpdateUsersType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'require_email_verification' => 'required',
|
||||
'enforce_account_limit' => 'required',
|
||||
'max_account_size' => 'required|integer|min:50000',
|
||||
'admin_autofollow' => 'required',
|
||||
'admin_autofollow_accounts' => 'sometimes',
|
||||
'max_user_blocks' => 'required|integer|min:0|max:5000',
|
||||
'max_user_mutes' => 'required|integer|min:0|max:5000',
|
||||
'max_domain_blocks' => 'required|integer|min:0|max:5000',
|
||||
]);
|
||||
|
||||
$adminAutofollow = $request->boolean('admin_autofollow');
|
||||
$adminAutofollowAccounts = $request->input('admin_autofollow_accounts');
|
||||
if ($adminAutofollow) {
|
||||
if ($request->filled('admin_autofollow_accounts')) {
|
||||
$names = [];
|
||||
$existing = config_cache('account.autofollow_usernames');
|
||||
if ($existing) {
|
||||
$names = explode(',', $existing);
|
||||
foreach (array_map('strtolower', $adminAutofollowAccounts) as $afc) {
|
||||
if (in_array(strtolower($afc), array_map('strtolower', $names))) {
|
||||
continue;
|
||||
}
|
||||
$names[] = $afc;
|
||||
}
|
||||
} else {
|
||||
$names = $adminAutofollowAccounts;
|
||||
}
|
||||
if (! $names || count($names) == 0) {
|
||||
return response()->json(['message' => 'You need to assign autofollow accounts before you can enable it.'], 400);
|
||||
}
|
||||
if (count($names) > 5) {
|
||||
return response()->json(['message' => 'You can only add up to 5 accounts to be autofollowed.'.json_encode($names)], 400);
|
||||
}
|
||||
$autofollows = User::whereIn('username', $names)->whereNull('status')->pluck('username');
|
||||
$adminAutofollowAccounts = $autofollows->implode(',');
|
||||
ConfigCacheService::put('account.autofollow_usernames', $adminAutofollowAccounts);
|
||||
} else {
|
||||
return response()->json(['message' => 'You need to assign autofollow accounts before you can enable it.'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigCacheService::put('pixelfed.enforce_email_verification', $request->boolean('require_email_verification'));
|
||||
ConfigCacheService::put('pixelfed.enforce_account_limit', $request->boolean('enforce_account_limit'));
|
||||
ConfigCacheService::put('pixelfed.max_account_size', $request->input('max_account_size'));
|
||||
ConfigCacheService::put('account.autofollow', $request->boolean('admin_autofollow'));
|
||||
ConfigCacheService::put('instance.user_filters.max_user_blocks', (int) $request->input('max_user_blocks'));
|
||||
ConfigCacheService::put('instance.user_filters.max_user_mutes', (int) $request->input('max_user_mutes'));
|
||||
ConfigCacheService::put('instance.user_filters.max_domain_blocks', (int) $request->input('max_domain_blocks'));
|
||||
$res = [
|
||||
'require_email_verification' => $request->boolean('require_email_verification'),
|
||||
'enforce_account_limit' => $request->boolean('enforce_account_limit'),
|
||||
'admin_autofollow' => $request->boolean('admin_autofollow'),
|
||||
'admin_autofollow_accounts' => $adminAutofollowAccounts,
|
||||
'max_user_blocks' => $request->input('max_user_blocks'),
|
||||
'max_user_mutes' => $request->input('max_user_mutes'),
|
||||
'max_domain_blocks' => $request->input('max_domain_blocks'),
|
||||
];
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function settingsApiUpdateStorageType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'primary_disk' => 'required|in:local,cloud',
|
||||
'update_disk' => 'sometimes',
|
||||
'disk_config' => 'required_if_accepted:update_disk',
|
||||
'disk_config.driver' => 'required|in:s3,spaces',
|
||||
'disk_config.key' => 'required',
|
||||
'disk_config.secret' => 'required',
|
||||
'disk_config.region' => 'required',
|
||||
'disk_config.bucket' => 'required',
|
||||
'disk_config.visibility' => 'required',
|
||||
'disk_config.endpoint' => 'required',
|
||||
'disk_config.url' => 'nullable',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('pixelfed.cloud_storage', $request->input('primary_disk') === 'cloud');
|
||||
$res = [
|
||||
'primary_disk' => $request->input('primary_disk'),
|
||||
];
|
||||
if ($request->has('update_disk')) {
|
||||
$res['disk_config'] = $request->input('disk_config');
|
||||
$changes = [];
|
||||
$dkey = $request->input('disk_config.driver') === 's3' ? 'filesystems.disks.s3.' : 'filesystems.disks.spaces.';
|
||||
$key = $request->input('disk_config.key');
|
||||
$ckey = null;
|
||||
$secret = $request->input('disk_config.secret');
|
||||
$csecret = null;
|
||||
$region = $request->input('disk_config.region');
|
||||
$bucket = $request->input('disk_config.bucket');
|
||||
$visibility = $request->input('disk_config.visibility');
|
||||
$url = $request->input('disk_config.url');
|
||||
$endpoint = $request->input('disk_config.endpoint');
|
||||
if (strpos($key, '*') === false && $key != config_cache($dkey.'key')) {
|
||||
array_push($changes, 'key');
|
||||
} else {
|
||||
$ckey = config_cache($dkey.'key');
|
||||
}
|
||||
if (strpos($secret, '*') === false && $secret != config_cache($dkey.'secret')) {
|
||||
array_push($changes, 'secret');
|
||||
} else {
|
||||
$csecret = config_cache($dkey.'secret');
|
||||
}
|
||||
if ($region != config_cache($dkey.'region')) {
|
||||
array_push($changes, 'region');
|
||||
}
|
||||
if ($bucket != config_cache($dkey.'bucket')) {
|
||||
array_push($changes, 'bucket');
|
||||
}
|
||||
if ($visibility != config_cache($dkey.'visibility')) {
|
||||
array_push($changes, 'visibility');
|
||||
}
|
||||
if ($url != config_cache($dkey.'url')) {
|
||||
array_push($changes, 'url');
|
||||
}
|
||||
if ($endpoint != config_cache($dkey.'endpoint')) {
|
||||
array_push($changes, 'endpoint');
|
||||
}
|
||||
|
||||
if ($changes && count($changes)) {
|
||||
$isValid = FilesystemService::getVerifyCredentials(
|
||||
$ckey ?? $key,
|
||||
$csecret ?? $secret,
|
||||
$region,
|
||||
$bucket,
|
||||
$endpoint,
|
||||
);
|
||||
if (! $isValid) {
|
||||
return response()->json(['error' => true, 's3_vce' => true, 'message' => "<div class='border border-danger text-danger p-3 font-weight-bold rounded-lg'>The S3/Spaces credentials you provided are invalid, or the bucket does not have the proper permissions.</div><br/>Please check all fields and try again.<br/><br/><strong>Any cloud storage configuration changes you made have NOT been saved due to invalid credentials.</strong>"], 400);
|
||||
}
|
||||
}
|
||||
$res['changes'] = json_encode($changes);
|
||||
}
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -174,7 +174,7 @@ class AdminCuratedRegisterController extends Controller
|
|||
public function apiMessageSendStore(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'message' => 'required|string|min:5|max:1000',
|
||||
'message' => 'required|string|min:5|max:3000',
|
||||
]);
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
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->action_taken_at = now();
|
||||
$record->save();
|
||||
|
||||
if (User::withTrashed()->whereEmail($record->email)->exists()) {
|
||||
return [200];
|
||||
}
|
||||
|
||||
$user = User::create([
|
||||
'name' => $record->username,
|
||||
'username' => $record->username,
|
||||
|
|
|
@ -2,45 +2,40 @@
|
|||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\AccountInterstitial;
|
||||
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 Auth, Cache, DB;
|
||||
use Carbon\Carbon;
|
||||
use App\{
|
||||
AccountInterstitial,
|
||||
Instance,
|
||||
Like,
|
||||
Notification,
|
||||
Media,
|
||||
Profile,
|
||||
Report,
|
||||
Status,
|
||||
User
|
||||
};
|
||||
use App\Models\Conversation;
|
||||
use App\Models\RemoteReport;
|
||||
use App\Notification;
|
||||
use App\Profile;
|
||||
use App\Report;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\AdminStatsService;
|
||||
use App\Services\ConfigCacheService;
|
||||
use App\Services\InstanceService;
|
||||
use App\Services\ModLogService;
|
||||
use App\Services\SnowflakeService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\PublicTimelineService;
|
||||
use App\Services\NetworkTimelineService;
|
||||
use App\Services\NotificationService;
|
||||
use App\Http\Resources\AdminInstance;
|
||||
use App\Http\Resources\AdminUser;
|
||||
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
||||
use App\Jobs\DeletePipeline\DeleteRemoteProfilePipeline;
|
||||
use App\Jobs\DeletePipeline\DeleteRemoteStatusPipeline;
|
||||
use App\Services\PublicTimelineService;
|
||||
use App\Services\SnowflakeService;
|
||||
use App\Services\StatusService;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
use Cache;
|
||||
use DB;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdminApiController extends Controller
|
||||
{
|
||||
public function supported(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
@ -50,7 +45,7 @@ class AdminApiController extends Controller
|
|||
|
||||
public function getStats(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
@ -59,12 +54,13 @@ class AdminApiController extends Controller
|
|||
$res['autospam_count'] = AccountInterstitial::whereType('post.autospam')
|
||||
->whereNull('appeal_handled_at')
|
||||
->count();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function autospam(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
@ -73,26 +69,27 @@ class AdminApiController extends Controller
|
|||
->whereNull('appeal_handled_at')
|
||||
->latest()
|
||||
->simplePaginate(6)
|
||||
->map(function($report) {
|
||||
->map(function ($report) {
|
||||
$r = [
|
||||
'id' => $report->id,
|
||||
'type' => $report->type,
|
||||
'item_id' => $report->item_id,
|
||||
'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);
|
||||
if(!$status) {
|
||||
if (! $status) {
|
||||
return;
|
||||
}
|
||||
|
||||
$r['status'] = $status;
|
||||
|
||||
if($status['in_reply_to_id']) {
|
||||
if ($status['in_reply_to_id']) {
|
||||
$r['parent'] = StatusService::get($status['in_reply_to_id'], false);
|
||||
}
|
||||
}
|
||||
|
||||
return $r;
|
||||
});
|
||||
|
||||
|
@ -101,14 +98,14 @@ class AdminApiController extends Controller
|
|||
|
||||
public function autospamHandle(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:write'), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'action' => 'required|in:dismiss,approve,dismiss-all,approve-all,delete-post,delete-account',
|
||||
'id' => 'required'
|
||||
'id' => 'required',
|
||||
]);
|
||||
|
||||
$action = $request->input('action');
|
||||
|
@ -122,18 +119,19 @@ class AdminApiController extends Controller
|
|||
$user = $appeal->user;
|
||||
$profile = $user->profile;
|
||||
|
||||
if($action == 'dismiss') {
|
||||
if ($action == 'dismiss') {
|
||||
$appeal->is_spam = true;
|
||||
$appeal->appeal_handled_at = $now;
|
||||
$appeal->save();
|
||||
|
||||
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:exemption_by_pid:'.$profile->id);
|
||||
Cache::forget('pf:bouncer_v0:recent_by_pid:'.$profile->id);
|
||||
Cache::forget('admin-dash:reports:spam-count');
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
if($action == 'delete-post') {
|
||||
if ($action == 'delete-post') {
|
||||
$appeal->appeal_handled_at = now();
|
||||
$appeal->is_spam = true;
|
||||
$appeal->save();
|
||||
|
@ -148,10 +146,11 @@ class AdminApiController extends Controller
|
|||
PublicTimelineService::deleteByProfileId($profile->id);
|
||||
StatusDelete::dispatch($appeal->status)->onQueue('high');
|
||||
Cache::forget('admin-dash:reports:spam-count');
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
if($action == 'delete-account') {
|
||||
if ($action == 'delete-account') {
|
||||
abort_if($user->is_admin, 400, 'Cannot delete an admin account.');
|
||||
$appeal->appeal_handled_at = now();
|
||||
$appeal->is_spam = true;
|
||||
|
@ -167,22 +166,24 @@ class AdminApiController extends Controller
|
|||
PublicTimelineService::deleteByProfileId($profile->id);
|
||||
DeleteAccountPipeline::dispatch($appeal->user)->onQueue('high');
|
||||
Cache::forget('admin-dash:reports:spam-count');
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
if($action == 'dismiss-all') {
|
||||
if ($action == 'dismiss-all') {
|
||||
AccountInterstitial::whereType('post.autospam')
|
||||
->whereItemType('App\Status')
|
||||
->whereNull('appeal_handled_at')
|
||||
->whereUserId($appeal->user_id)
|
||||
->update(['appeal_handled_at' => $now, 'is_spam' => true]);
|
||||
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:exemption_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');
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
if($action == 'approve') {
|
||||
if ($action == 'approve') {
|
||||
$status = $appeal->status;
|
||||
$status->is_nsfw = $meta->is_nsfw;
|
||||
$status->scope = 'public';
|
||||
|
@ -198,29 +199,30 @@ class AdminApiController extends Controller
|
|||
Notification::whereAction('autospam.warning')
|
||||
->whereProfileId($appeal->user->profile_id)
|
||||
->get()
|
||||
->each(function($n) use($appeal) {
|
||||
->each(function ($n) use ($appeal) {
|
||||
NotificationService::del($appeal->user->profile_id, $n->id);
|
||||
$n->forceDelete();
|
||||
});
|
||||
|
||||
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:exemption_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');
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
if($action == 'approve-all') {
|
||||
if ($action == 'approve-all') {
|
||||
AccountInterstitial::whereType('post.autospam')
|
||||
->whereItemType('App\Status')
|
||||
->whereNull('appeal_handled_at')
|
||||
->whereUserId($appeal->user_id)
|
||||
->get()
|
||||
->each(function($report) use($meta) {
|
||||
->each(function ($report) use ($meta) {
|
||||
$report->is_spam = false;
|
||||
$report->appeal_handled_at = now();
|
||||
$report->save();
|
||||
$status = Status::find($report->item_id);
|
||||
if($status) {
|
||||
if ($status) {
|
||||
$status->is_nsfw = $meta->is_nsfw;
|
||||
$status->scope = 'public';
|
||||
$status->visibility = 'public';
|
||||
|
@ -231,14 +233,15 @@ class AdminApiController extends Controller
|
|||
Notification::whereAction('autospam.warning')
|
||||
->whereProfileId($report->user->profile_id)
|
||||
->get()
|
||||
->each(function($n) use($report) {
|
||||
->each(function ($n) use ($report) {
|
||||
NotificationService::del($report->user->profile_id, $n->id);
|
||||
$n->forceDelete();
|
||||
});
|
||||
});
|
||||
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:exemption_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');
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
@ -247,44 +250,48 @@ class AdminApiController extends Controller
|
|||
|
||||
public function modReports(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
||||
$reports = Report::whereNull('admin_seen')
|
||||
->orderBy('created_at','desc')
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(6)
|
||||
->map(function($report) {
|
||||
->map(function ($report) {
|
||||
$r = [
|
||||
'id' => $report->id,
|
||||
'type' => $report->type,
|
||||
'message' => $report->message,
|
||||
'object_id' => $report->object_id,
|
||||
'object_type' => $report->object_type,
|
||||
'created_at' => $report->created_at
|
||||
'created_at' => $report->created_at,
|
||||
];
|
||||
|
||||
if($report->profile_id) {
|
||||
if ($report->profile_id) {
|
||||
$r['reported_by_account'] = AccountService::get($report->profile_id, true);
|
||||
}
|
||||
|
||||
if($report->object_type === 'App\\Status') {
|
||||
if ($report->object_type === 'App\\Status') {
|
||||
$status = StatusService::get($report->object_id, false);
|
||||
if(!$status) {
|
||||
if (! $status) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
if($report->object_type === 'App\\Profile') {
|
||||
$r['account'] = AccountService::get($report->object_id, false);
|
||||
if ($report->object_type === 'App\\Profile') {
|
||||
$acct = AccountService::get($report->object_id, true);
|
||||
if ($acct) {
|
||||
$r['account'] = $acct;
|
||||
}
|
||||
}
|
||||
|
||||
return $r;
|
||||
})
|
||||
->filter()
|
||||
|
@ -295,14 +302,14 @@ class AdminApiController extends Controller
|
|||
|
||||
public function modReportHandle(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:write'), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'action' => 'required|string',
|
||||
'id' => 'required'
|
||||
'action' => 'required|string',
|
||||
'id' => 'required',
|
||||
]);
|
||||
|
||||
$action = $request->input('action');
|
||||
|
@ -311,10 +318,10 @@ class AdminApiController extends Controller
|
|||
$actions = [
|
||||
'ignore',
|
||||
'cw',
|
||||
'unlist'
|
||||
'unlist',
|
||||
];
|
||||
|
||||
if (!in_array($action, $actions)) {
|
||||
if (! in_array($action, $actions)) {
|
||||
return abort(403);
|
||||
}
|
||||
|
||||
|
@ -355,7 +362,7 @@ class AdminApiController extends Controller
|
|||
|
||||
public function getConfiguration(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
@ -366,42 +373,43 @@ class AdminApiController extends Controller
|
|||
[
|
||||
'name' => 'ActivityPub Federation',
|
||||
'description' => 'Enable activitypub federation support, compatible with Pixelfed, Mastodon and other platforms.',
|
||||
'key' => 'federation.activitypub.enabled'
|
||||
'key' => 'federation.activitypub.enabled',
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'Open Registration',
|
||||
'description' => 'Allow new account registrations.',
|
||||
'key' => 'pixelfed.open_registration'
|
||||
'key' => 'pixelfed.open_registration',
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'Stories',
|
||||
'description' => 'Enable the ephemeral Stories feature.',
|
||||
'key' => 'instance.stories.enabled'
|
||||
'key' => 'instance.stories.enabled',
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'Require Email Verification',
|
||||
'description' => 'Require new accounts to verify their email address.',
|
||||
'key' => 'pixelfed.enforce_email_verification'
|
||||
'key' => 'pixelfed.enforce_email_verification',
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'AutoSpam Detection',
|
||||
'description' => 'Detect and remove spam from public timelines.',
|
||||
'key' => 'pixelfed.bouncer.enabled'
|
||||
'key' => 'pixelfed.bouncer.enabled',
|
||||
],
|
||||
])
|
||||
->map(function($s) {
|
||||
$s['state'] = (bool) config_cache($s['key']);
|
||||
return $s;
|
||||
});
|
||||
->map(function ($s) {
|
||||
$s['state'] = (bool) config_cache($s['key']);
|
||||
|
||||
return $s;
|
||||
});
|
||||
}
|
||||
|
||||
public function updateConfiguration(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:write'), 404);
|
||||
|
@ -410,7 +418,7 @@ class AdminApiController extends Controller
|
|||
|
||||
$this->validate($request, [
|
||||
'key' => 'required',
|
||||
'value' => 'required'
|
||||
'value' => 'required',
|
||||
]);
|
||||
|
||||
$allowedKeys = [
|
||||
|
@ -423,50 +431,51 @@ class AdminApiController extends Controller
|
|||
|
||||
$key = $request->input('key');
|
||||
$value = (bool) filter_var($request->input('value'), FILTER_VALIDATE_BOOLEAN);
|
||||
abort_if(!in_array($key, $allowedKeys), 400, 'Invalid cache key.');
|
||||
abort_if(! in_array($key, $allowedKeys), 400, 'Invalid cache key.');
|
||||
|
||||
ConfigCacheService::put($key, $value);
|
||||
|
||||
return collect([
|
||||
return collect([
|
||||
[
|
||||
'name' => 'ActivityPub Federation',
|
||||
'description' => 'Enable activitypub federation support, compatible with Pixelfed, Mastodon and other platforms.',
|
||||
'key' => 'federation.activitypub.enabled'
|
||||
'key' => 'federation.activitypub.enabled',
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'Open Registration',
|
||||
'description' => 'Allow new account registrations.',
|
||||
'key' => 'pixelfed.open_registration'
|
||||
'key' => 'pixelfed.open_registration',
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'Stories',
|
||||
'description' => 'Enable the ephemeral Stories feature.',
|
||||
'key' => 'instance.stories.enabled'
|
||||
'key' => 'instance.stories.enabled',
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'Require Email Verification',
|
||||
'description' => 'Require new accounts to verify their email address.',
|
||||
'key' => 'pixelfed.enforce_email_verification'
|
||||
'key' => 'pixelfed.enforce_email_verification',
|
||||
],
|
||||
|
||||
[
|
||||
'name' => 'AutoSpam Detection',
|
||||
'description' => 'Detect and remove spam from public timelines.',
|
||||
'key' => 'pixelfed.bouncer.enabled'
|
||||
'key' => 'pixelfed.bouncer.enabled',
|
||||
],
|
||||
])
|
||||
->map(function($s) {
|
||||
$s['state'] = (bool) config_cache($s['key']);
|
||||
return $s;
|
||||
});
|
||||
->map(function ($s) {
|
||||
$s['state'] = (bool) config_cache($s['key']);
|
||||
|
||||
return $s;
|
||||
});
|
||||
}
|
||||
|
||||
public function getUsers(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
@ -477,27 +486,29 @@ class AdminApiController extends Controller
|
|||
$q = $request->input('q');
|
||||
$sort = $request->input('sort', 'desc') === 'asc' ? 'asc' : 'desc';
|
||||
$res = User::whereNull('status')
|
||||
->when($q, function($query, $q) {
|
||||
return $query->where('username', 'like', '%' . $q . '%');
|
||||
->when($q, function ($query, $q) {
|
||||
return $query->where('username', 'like', '%'.$q.'%');
|
||||
})
|
||||
->orderBy('id', $sort)
|
||||
->cursorPaginate(10);
|
||||
|
||||
return AdminUser::collection($res);
|
||||
}
|
||||
|
||||
public function getUser(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
||||
$id = $request->input('user_id');
|
||||
$key = 'pf-admin-api:getUser:byId:' . $id;
|
||||
if($request->has('refresh')) {
|
||||
$key = 'pf-admin-api:getUser:byId:'.$id;
|
||||
if ($request->has('refresh')) {
|
||||
Cache::forget($key);
|
||||
}
|
||||
return Cache::remember($key, 86400, function() use($id) {
|
||||
|
||||
return Cache::remember($key, 86400, function () use ($id) {
|
||||
$user = User::findOrFail($id);
|
||||
$profile = $user->profile;
|
||||
$account = AccountService::get($user->profile_id, true);
|
||||
|
@ -510,8 +521,8 @@ class AdminApiController extends Controller
|
|||
'moderation' => [
|
||||
'unlisted' => (bool) $profile->unlisted,
|
||||
'cw' => (bool) $profile->cw,
|
||||
'no_autolink' => (bool) $profile->no_autolink
|
||||
]
|
||||
'no_autolink' => (bool) $profile->no_autolink,
|
||||
],
|
||||
]]);
|
||||
|
||||
return $res;
|
||||
|
@ -520,7 +531,7 @@ class AdminApiController extends Controller
|
|||
|
||||
public function userAdminAction(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:write'), 404);
|
||||
|
@ -528,7 +539,7 @@ class AdminApiController extends Controller
|
|||
$this->validate($request, [
|
||||
'id' => 'required',
|
||||
'action' => 'required|in:unlisted,cw,no_autolink,refresh_stats,verify_email,delete',
|
||||
'value' => 'sometimes'
|
||||
'value' => 'sometimes',
|
||||
]);
|
||||
|
||||
$id = $request->input('id');
|
||||
|
@ -538,8 +549,8 @@ class AdminApiController extends Controller
|
|||
|
||||
abort_if($user->is_admin == true && $action !== 'refresh_stats', 400, 'Cannot moderate admin accounts');
|
||||
|
||||
if($action === 'delete') {
|
||||
if(config('pixelfed.account_deletion') == false) {
|
||||
if ($action === 'delete') {
|
||||
if (config('pixelfed.account_deletion') == false) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
|
@ -567,7 +578,7 @@ class AdminApiController extends Controller
|
|||
PublicTimelineService::deleteByProfileId($profile->id);
|
||||
NetworkTimelineService::deleteByProfileId($profile->id);
|
||||
|
||||
if($profile->user_id) {
|
||||
if ($profile->user_id) {
|
||||
DB::table('oauth_access_tokens')->whereUserId($user->id)->delete();
|
||||
DB::table('oauth_auth_codes')->whereUserId($user->id)->delete();
|
||||
$user->email = $user->id;
|
||||
|
@ -586,11 +597,12 @@ class AdminApiController extends Controller
|
|||
AccountService::del($profile->id);
|
||||
DeleteRemoteProfilePipeline::dispatch($profile)->onQueue('high');
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 200,
|
||||
'msg' => 'deleted',
|
||||
];
|
||||
} else if($action === 'refresh_stats') {
|
||||
} elseif ($action === 'refresh_stats') {
|
||||
$profile->following_count = DB::table('followers')->whereProfileId($user->profile_id)->count();
|
||||
$profile->followers_count = DB::table('followers')->whereFollowingId($user->profile_id)->count();
|
||||
$statusCount = Status::whereProfileId($user->profile_id)
|
||||
|
@ -600,7 +612,7 @@ class AdminApiController extends Controller
|
|||
->count();
|
||||
$profile->status_count = $statusCount;
|
||||
$profile->save();
|
||||
} else if($action === 'verify_email') {
|
||||
} elseif ($action === 'verify_email') {
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
|
||||
|
@ -612,11 +624,11 @@ class AdminApiController extends Controller
|
|||
->action('admin.user.moderate')
|
||||
->metadata([
|
||||
'action' => 'Manually verified email address',
|
||||
'message' => 'Success!'
|
||||
'message' => 'Success!',
|
||||
])
|
||||
->accessLevel('admin')
|
||||
->save();
|
||||
} else if($action === 'unlisted') {
|
||||
} elseif ($action === 'unlisted') {
|
||||
ModLogService::boot()
|
||||
->objectUid($profile->id)
|
||||
->objectId($profile->id)
|
||||
|
@ -625,13 +637,13 @@ class AdminApiController extends Controller
|
|||
->action('admin.user.moderate')
|
||||
->metadata([
|
||||
'action' => $action,
|
||||
'message' => 'Success!'
|
||||
'message' => 'Success!',
|
||||
])
|
||||
->accessLevel('admin')
|
||||
->save();
|
||||
$profile->unlisted = !$profile->unlisted;
|
||||
$profile->unlisted = ! $profile->unlisted;
|
||||
$profile->save();
|
||||
} else if($action === 'cw') {
|
||||
} elseif ($action === 'cw') {
|
||||
ModLogService::boot()
|
||||
->objectUid($profile->id)
|
||||
->objectId($profile->id)
|
||||
|
@ -640,13 +652,13 @@ class AdminApiController extends Controller
|
|||
->action('admin.user.moderate')
|
||||
->metadata([
|
||||
'action' => $action,
|
||||
'message' => 'Success!'
|
||||
'message' => 'Success!',
|
||||
])
|
||||
->accessLevel('admin')
|
||||
->save();
|
||||
$profile->cw = !$profile->cw;
|
||||
$profile->cw = ! $profile->cw;
|
||||
$profile->save();
|
||||
} else if($action === 'no_autolink') {
|
||||
} elseif ($action === 'no_autolink') {
|
||||
ModLogService::boot()
|
||||
->objectUid($profile->id)
|
||||
->objectId($profile->id)
|
||||
|
@ -655,11 +667,11 @@ class AdminApiController extends Controller
|
|||
->action('admin.user.moderate')
|
||||
->metadata([
|
||||
'action' => $action,
|
||||
'message' => 'Success!'
|
||||
'message' => 'Success!',
|
||||
])
|
||||
->accessLevel('admin')
|
||||
->save();
|
||||
$profile->no_autolink = !$profile->no_autolink;
|
||||
$profile->no_autolink = ! $profile->no_autolink;
|
||||
$profile->save();
|
||||
} else {
|
||||
$profile->{$action} = filter_var($request->input('value'), FILTER_VALIDATE_BOOLEAN);
|
||||
|
@ -673,7 +685,7 @@ class AdminApiController extends Controller
|
|||
->action('admin.user.moderate')
|
||||
->metadata([
|
||||
'action' => $action,
|
||||
'message' => 'Success!'
|
||||
'message' => 'Success!',
|
||||
])
|
||||
->accessLevel('admin')
|
||||
->save();
|
||||
|
@ -687,14 +699,14 @@ class AdminApiController extends Controller
|
|||
'moderation' => [
|
||||
'unlisted' => (bool) $profile->unlisted,
|
||||
'cw' => (bool) $profile->cw,
|
||||
'no_autolink' => (bool) $profile->no_autolink
|
||||
]
|
||||
'no_autolink' => (bool) $profile->no_autolink,
|
||||
],
|
||||
]]);
|
||||
}
|
||||
|
||||
public function instances(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:write'), 404);
|
||||
|
@ -711,19 +723,19 @@ class AdminApiController extends Controller
|
|||
$sortBy = $request->input('sort_by', 'id');
|
||||
$filter = $request->input('filter');
|
||||
|
||||
$res = Instance::when($q, function($query, $q) {
|
||||
return $query->where('domain', 'like', '%' . $q . '%');
|
||||
})
|
||||
->when($filter, function($query, $filter) {
|
||||
if($filter === 'all') {
|
||||
$res = Instance::when($q, function ($query, $q) {
|
||||
return $query->where('domain', 'like', '%'.$q.'%');
|
||||
})
|
||||
->when($filter, function ($query, $filter) {
|
||||
if ($filter === 'all') {
|
||||
return $query;
|
||||
} else {
|
||||
return $query->where($filter, true);
|
||||
}
|
||||
})
|
||||
->when($sortBy, function($query, $sortBy) use($sort) {
|
||||
->when($sortBy, function ($query, $sortBy) use ($sort) {
|
||||
return $query->orderBy($sortBy, $sort);
|
||||
}, function($query) {
|
||||
}, function ($query) {
|
||||
return $query->orderBy('id', 'desc');
|
||||
})
|
||||
->cursorPaginate(10)
|
||||
|
@ -734,7 +746,7 @@ class AdminApiController extends Controller
|
|||
|
||||
public function getInstance(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
@ -747,7 +759,7 @@ class AdminApiController extends Controller
|
|||
|
||||
public function moderateInstance(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:write'), 404);
|
||||
|
@ -755,7 +767,7 @@ class AdminApiController extends Controller
|
|||
$this->validate($request, [
|
||||
'id' => 'required',
|
||||
'key' => 'required|in:unlisted,auto_cw,banned',
|
||||
'value' => 'required'
|
||||
'value' => 'required',
|
||||
]);
|
||||
|
||||
$id = $request->input('id');
|
||||
|
@ -773,7 +785,7 @@ class AdminApiController extends Controller
|
|||
|
||||
public function refreshInstanceStats(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin == 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:write'), 404);
|
||||
|
@ -793,51 +805,51 @@ class AdminApiController extends Controller
|
|||
|
||||
public function getAllStats(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 404);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 404);
|
||||
|
||||
abort_unless($request->user()->is_admin === 1, 404);
|
||||
abort_unless($request->user()->tokenCan('admin:read'), 404);
|
||||
|
||||
if($request->has('refresh')) {
|
||||
if ($request->has('refresh')) {
|
||||
Cache::forget('admin-api:instance-all-stats-v1');
|
||||
}
|
||||
|
||||
return Cache::remember('admin-api:instance-all-stats-v1', 1209600, function() {
|
||||
return Cache::remember('admin-api:instance-all-stats-v1', 1209600, function () {
|
||||
$days = range(1, 7);
|
||||
$res = [
|
||||
'cached_at' => now()->format('c'),
|
||||
];
|
||||
$minStatusId = SnowflakeService::byDate(now()->subDays(7));
|
||||
|
||||
foreach($days as $day) {
|
||||
foreach ($days as $day) {
|
||||
$label = now()->subDays($day)->format('D');
|
||||
$labelShort = substr($label, 0, 1);
|
||||
$res['users']['days'][] = [
|
||||
'date' => now()->subDays($day)->format('M j Y'),
|
||||
'label_full' => $label,
|
||||
'label' => $labelShort,
|
||||
'count' => User::whereDate('created_at', now()->subDays($day))->count()
|
||||
'count' => User::whereDate('created_at', now()->subDays($day))->count(),
|
||||
];
|
||||
|
||||
$res['posts']['days'][] = [
|
||||
'date' => now()->subDays($day)->format('M j Y'),
|
||||
'label_full' => $label,
|
||||
'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'][] = [
|
||||
'date' => now()->subDays($day)->format('M j Y'),
|
||||
'label_full' => $label,
|
||||
'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']['min'] = collect($res['users']['days'])->min('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']['min'] = collect($res['posts']['days'])->min('count');
|
||||
$res['posts']['max'] = collect($res['posts']['days'])->max('count');
|
||||
|
|
|
@ -60,6 +60,7 @@ use App\Services\SnowflakeService;
|
|||
use App\Services\StatusService;
|
||||
use App\Services\UserFilterService;
|
||||
use App\Services\UserRoleService;
|
||||
use App\Services\UserStorageService;
|
||||
use App\Status;
|
||||
use App\StatusHashtag;
|
||||
use App\Transformer\Api\Mastodon\v1\AccountTransformer;
|
||||
|
@ -131,7 +132,7 @@ class ApiV1Controller extends Controller
|
|||
*/
|
||||
public function apps(Request $request)
|
||||
{
|
||||
abort_if(! config_cache('pixelfed.oauth_enabled'), 404);
|
||||
abort_if(! (bool) config_cache('pixelfed.oauth_enabled'), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'client_name' => 'required',
|
||||
|
@ -193,6 +194,10 @@ class ApiV1Controller extends Controller
|
|||
'fields' => [],
|
||||
];
|
||||
|
||||
if ($request->has(self::PF_API_ENTITY_KEY)) {
|
||||
$res['settings'] = AccountService::getAccountSettings($user->profile_id);
|
||||
}
|
||||
|
||||
return $this->json($res);
|
||||
}
|
||||
|
||||
|
@ -207,6 +212,7 @@ class ApiV1Controller extends Controller
|
|||
abort_if(! $request->user() || ! $request->user()->token(), 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);
|
||||
if (! $res) {
|
||||
return response()->json(['error' => 'Record not found'], 404);
|
||||
|
@ -326,7 +332,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if ($request->has('locked')) {
|
||||
$locked = $request->input('locked') == 'true';
|
||||
$locked = $request->boolean('locked');
|
||||
if ($profile->is_private != $locked) {
|
||||
$profile->is_private = $locked;
|
||||
$changes = true;
|
||||
|
@ -334,7 +340,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if ($request->has('reduce_motion')) {
|
||||
$reduced = $request->input('reduce_motion');
|
||||
$reduced = $request->boolean('reduce_motion');
|
||||
if ($settings->reduce_motion != $reduced) {
|
||||
$settings->reduce_motion = $reduced;
|
||||
$changes = true;
|
||||
|
@ -342,7 +348,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if ($request->has('high_contrast_mode')) {
|
||||
$contrast = $request->input('high_contrast_mode');
|
||||
$contrast = $request->boolean('high_contrast_mode');
|
||||
if ($settings->high_contrast_mode != $contrast) {
|
||||
$settings->high_contrast_mode = $contrast;
|
||||
$changes = true;
|
||||
|
@ -350,7 +356,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if ($request->has('video_autoplay')) {
|
||||
$autoplay = $request->input('video_autoplay');
|
||||
$autoplay = $request->boolean('video_autoplay');
|
||||
if ($settings->video_autoplay != $autoplay) {
|
||||
$settings->video_autoplay = $autoplay;
|
||||
$changes = true;
|
||||
|
@ -370,7 +376,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if ($request->has('media_descriptions')) {
|
||||
$md = $request->input('media_descriptions') == true;
|
||||
$md = $request->boolean('media_descriptions');
|
||||
if ($composeSettings['media_descriptions'] != $md) {
|
||||
$composeSettings['media_descriptions'] = $md;
|
||||
$changes = true;
|
||||
|
@ -378,7 +384,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if ($request->has('crawlable')) {
|
||||
$crawlable = $request->input('crawlable');
|
||||
$crawlable = $request->boolean('crawlable');
|
||||
if ($settings->crawlable != $crawlable) {
|
||||
$settings->crawlable = $crawlable;
|
||||
$changes = true;
|
||||
|
@ -386,7 +392,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
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) {
|
||||
$settings->show_profile_follower_count = $show_profile_follower_count;
|
||||
$changes = true;
|
||||
|
@ -395,7 +401,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
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) {
|
||||
$settings->show_profile_following_count = $show_profile_following_count;
|
||||
$changes = true;
|
||||
|
@ -404,7 +410,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if ($request->has('public_dm')) {
|
||||
$public_dm = $request->input('public_dm');
|
||||
$public_dm = $request->boolean('public_dm');
|
||||
if ($settings->public_dm != $public_dm) {
|
||||
$settings->public_dm = $public_dm;
|
||||
$changes = true;
|
||||
|
@ -422,7 +428,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
if ($request->has('disable_embeds')) {
|
||||
$disabledEmbeds = $request->input('disable_embeds');
|
||||
$disabledEmbeds = $request->boolean('disable_embeds');
|
||||
if ($other['disable_embeds'] != $disabledEmbeds) {
|
||||
$other['disable_embeds'] = $disabledEmbeds;
|
||||
$changes = true;
|
||||
|
@ -441,8 +447,16 @@ class ApiV1Controller extends Controller
|
|||
Cache::forget('profile:following_count:'.$profile->id);
|
||||
Cache::forget('profile:embed:'.$profile->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::forgetAccountSettings($profile->id);
|
||||
}
|
||||
|
||||
if ($syncLicenses && $licenseChanged) {
|
||||
|
@ -740,7 +754,15 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$dir = $min_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('reblog_of_id')
|
||||
->whereIn('type', $scope)
|
||||
|
@ -960,10 +982,22 @@ class ApiV1Controller extends Controller
|
|||
$napi = $request->has(self::PF_API_ENTITY_KEY);
|
||||
$pid = $request->user()->profile_id ?? $request->user()->profile->id;
|
||||
$res = collect($ids)
|
||||
->filter(function ($id) use ($pid) {
|
||||
return intval($id) !== intval($pid);
|
||||
})
|
||||
->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 ?
|
||||
RelationshipService::getWithDate($pid, $id) :
|
||||
RelationshipService::get($pid, $id);
|
||||
|
@ -1103,7 +1137,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
$count = UserFilterService::blockCount($pid);
|
||||
$maxLimit = intval(config('instance.user_filters.max_user_blocks'));
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_user_blocks');
|
||||
if ($count == 0) {
|
||||
$filterCount = UserFilter::whereUserId($pid)
|
||||
->whereFilterType('block')
|
||||
|
@ -1200,8 +1234,8 @@ class ApiV1Controller extends Controller
|
|||
if ($filter) {
|
||||
$filter->delete();
|
||||
UserFilterService::unblock($pid, $profile->id);
|
||||
RelationshipService::refresh($pid, $id);
|
||||
}
|
||||
RelationshipService::refresh($pid, $id);
|
||||
|
||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
@ -1301,12 +1335,17 @@ class ApiV1Controller extends Controller
|
|||
if ($res->count()) {
|
||||
$ids = $res->map(function ($status) {
|
||||
return $status['like_id'];
|
||||
});
|
||||
$max = $ids->max();
|
||||
$min = $ids->min();
|
||||
})->filter();
|
||||
|
||||
$max = $ids->min() - 1;
|
||||
$min = $ids->max();
|
||||
|
||||
$baseUrl = config('app.url').'/api/v1/favourites?limit='.$limit.'&';
|
||||
$link = '<'.$baseUrl.'max_id='.$max.'>; rel="next",<'.$baseUrl.'min_id='.$min.'>; rel="prev"';
|
||||
if ($maxId) {
|
||||
$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]);
|
||||
} else {
|
||||
|
@ -1328,7 +1367,8 @@ class ApiV1Controller extends Controller
|
|||
$user = $request->user();
|
||||
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);
|
||||
|
||||
|
@ -1396,34 +1436,47 @@ class ApiV1Controller extends Controller
|
|||
$user = $request->user();
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
$status = Status::findOrFail($id);
|
||||
|
||||
if (intval($status->profile_id) !== intval($user->profile_id)) {
|
||||
if ($status->scope == 'private') {
|
||||
abort_if(! $status->profile->followedBy($user->profile), 403);
|
||||
if (intval($spid) !== intval($user->profile_id)) {
|
||||
if ($status['visibility'] == 'private') {
|
||||
abort_if(! FollowerService::follows($user->profile_id, $spid), 403);
|
||||
} 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)
|
||||
->whereStatusId($status->id)
|
||||
->whereStatusId($status['id'])
|
||||
->first();
|
||||
|
||||
if ($like) {
|
||||
$like->forceDelete();
|
||||
$status->likes_count = $status->likes_count > 1 ? $status->likes_count - 1 : 0;
|
||||
$status->save();
|
||||
$ogStatus = Status::find($status['id']);
|
||||
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);
|
||||
$res['favourited'] = false;
|
||||
$status['favourited'] = false;
|
||||
$status['favourites_count'] = isset($ogStatus) ? $ogStatus->likes_count : $status['favourites_count'] - 1;
|
||||
|
||||
return $this->json($res);
|
||||
return $this->json($status);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1611,7 +1664,7 @@ class ApiV1Controller extends Controller
|
|||
$stats = Cache::remember('api:v1:instance-data:stats', 43200, function () {
|
||||
return [
|
||||
'user_count' => User::count(),
|
||||
'status_count' => Status::whereNull('uri')->count(),
|
||||
'status_count' => StatusService::totalLocalStatuses(),
|
||||
'domain_count' => Instance::count(),
|
||||
];
|
||||
});
|
||||
|
@ -1632,7 +1685,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
return [
|
||||
'uri' => config('pixelfed.domain.app'),
|
||||
'title' => config('app.name'),
|
||||
'title' => config_cache('app.name'),
|
||||
'short_description' => config_cache('app.short_description'),
|
||||
'description' => config_cache('app.description'),
|
||||
'email' => config('instance.email'),
|
||||
|
@ -1651,7 +1704,7 @@ class ApiV1Controller extends Controller
|
|||
'media_attachments' => [
|
||||
'image_matrix_limit' => 16777216,
|
||||
'image_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
||||
'supported_mime_types' => explode(',', config('pixelfed.media_types')),
|
||||
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
|
||||
'video_frame_rate_limit' => 120,
|
||||
'video_matrix_limit' => 2304000,
|
||||
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
||||
|
@ -1665,7 +1718,7 @@ class ApiV1Controller extends Controller
|
|||
'statuses' => [
|
||||
'characters_reserved_per_url' => 23,
|
||||
'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
|
||||
'max_media_attachments' => (int) config('pixelfed.max_album_length'),
|
||||
'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -1754,12 +1807,16 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$profile = $user->profile;
|
||||
|
||||
if (config_cache('pixelfed.enforce_account_limit') == true) {
|
||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
|
||||
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');
|
||||
if ($size >= $limit) {
|
||||
if ($updatedAccountSize >= $limit) {
|
||||
abort(403, 'Account size limit reached.');
|
||||
}
|
||||
}
|
||||
|
@ -1767,8 +1824,6 @@ class ApiV1Controller extends Controller
|
|||
$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'));
|
||||
if (in_array($photo->getMimeType(), $mimes) == false) {
|
||||
abort(403, 'Invalid or unsupported mime type.');
|
||||
|
@ -1831,6 +1886,10 @@ class ApiV1Controller extends Controller
|
|||
break;
|
||||
}
|
||||
|
||||
$user->storage_used = (int) $updatedAccountSize;
|
||||
$user->storage_used_updated_at = now();
|
||||
$user->save();
|
||||
|
||||
Cache::forget($limitKey);
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
@ -1971,12 +2030,16 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$profile = $user->profile;
|
||||
|
||||
if (config_cache('pixelfed.enforce_account_limit') == true) {
|
||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
|
||||
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');
|
||||
if ($size >= $limit) {
|
||||
if ($updatedAccountSize >= $limit) {
|
||||
abort(403, 'Account size limit reached.');
|
||||
}
|
||||
}
|
||||
|
@ -1984,8 +2047,6 @@ class ApiV1Controller extends Controller
|
|||
$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'));
|
||||
if (in_array($photo->getMimeType(), $mimes) == false) {
|
||||
abort(403, 'Invalid or unsupported mime type.');
|
||||
|
@ -2053,6 +2114,10 @@ class ApiV1Controller extends Controller
|
|||
break;
|
||||
}
|
||||
|
||||
$user->storage_used = (int) $updatedAccountSize;
|
||||
$user->storage_used_updated_at = now();
|
||||
$user->save();
|
||||
|
||||
Cache::forget($limitKey);
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
@ -2145,7 +2210,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
$count = UserFilterService::muteCount($pid);
|
||||
$maxLimit = intval(config('instance.user_filters.max_user_mutes'));
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_user_mutes');
|
||||
if ($count == 0) {
|
||||
$filterCount = UserFilter::whereUserId($pid)
|
||||
->whereFilterType('mute')
|
||||
|
@ -2207,9 +2272,10 @@ class ApiV1Controller extends Controller
|
|||
if ($filter) {
|
||||
$filter->delete();
|
||||
UserFilterService::unmute($pid, $profile->id);
|
||||
RelationshipService::refresh($pid, $id);
|
||||
}
|
||||
|
||||
RelationshipService::refresh($pid, $id);
|
||||
|
||||
$resource = new Fractal\Resource\Item($profile, new RelationshipTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
|
@ -2233,14 +2299,17 @@ class ApiV1Controller extends Controller
|
|||
'max_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
||||
'since_id' => 'nullable|integer|min:1|max:'.PHP_INT_MAX,
|
||||
'types[]' => 'sometimes|array',
|
||||
'types[].*' => 'string|in:mention,reblog,follow,favourite',
|
||||
'type' => 'sometimes|string|in:mention,reblog,follow,favourite',
|
||||
'_pe' => 'sometimes',
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
$limit = $request->input('limit', 20);
|
||||
$ogLimit = $request->input('limit', 20);
|
||||
if ($limit > 40) {
|
||||
$limit = 40;
|
||||
$ogLimit = 40;
|
||||
}
|
||||
|
||||
$since = $request->input('since_id');
|
||||
|
@ -2258,6 +2327,10 @@ class ApiV1Controller extends Controller
|
|||
|
||||
$types = $request->input('types');
|
||||
|
||||
if ($request->has('types')) {
|
||||
$limit = 150;
|
||||
}
|
||||
|
||||
$maxId = null;
|
||||
$minId = null;
|
||||
AccountService::setLastActive($request->user()->id);
|
||||
|
@ -2280,7 +2353,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) {
|
||||
$minId = null;
|
||||
|
@ -2322,7 +2400,16 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
return true;
|
||||
})->values();
|
||||
})
|
||||
->filter(function ($n) use ($types) {
|
||||
if (! $types) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($n['type'], $types);
|
||||
})
|
||||
->take($ogLimit)
|
||||
->values();
|
||||
|
||||
if ($maxId) {
|
||||
$link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"';
|
||||
|
@ -3019,9 +3106,9 @@ class ApiV1Controller extends Controller
|
|||
abort_unless($request->user()->tokenCan('read'), 403);
|
||||
|
||||
$user = $request->user();
|
||||
AccountService::setLastActive($user->id);
|
||||
$pid = $user->profile_id;
|
||||
$status = StatusService::getMastodon($id, false);
|
||||
$pe = $request->has(self::PF_API_ENTITY_KEY);
|
||||
|
||||
if (! $status || ! isset($status['account'])) {
|
||||
return response('', 404);
|
||||
|
@ -3048,7 +3135,9 @@ class ApiV1Controller extends Controller
|
|||
$descendants = [];
|
||||
|
||||
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']) {
|
||||
|
@ -3058,8 +3147,10 @@ class ApiV1Controller extends Controller
|
|||
->where('in_reply_to_id', $id)
|
||||
->limit(20)
|
||||
->pluck('id')
|
||||
->map(function ($sid) {
|
||||
return StatusService::getMastodon($sid, false);
|
||||
->map(function ($sid) use ($pe) {
|
||||
return $pe ?
|
||||
StatusService::get($sid, false) :
|
||||
StatusService::getMastodon($sid, false);
|
||||
})
|
||||
->filter(function ($post) use ($filters) {
|
||||
return $post && isset($post['account'], $post['account']['id']) && ! in_array($post['account']['id'], $filters);
|
||||
|
@ -3308,9 +3399,9 @@ class ApiV1Controller extends Controller
|
|||
abort_unless($request->user()->tokenCan('write'), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'status' => 'nullable|string|max:' . config_cache('pixelfed.max_caption_length'),
|
||||
'status' => 'nullable|string|max:'.(int) config_cache('pixelfed.max_caption_length'),
|
||||
'in_reply_to_id' => 'nullable',
|
||||
'media_ids' => 'sometimes|array|max:'.config_cache('pixelfed.max_album_length'),
|
||||
'media_ids' => 'sometimes|array|max:'.(int) config_cache('pixelfed.max_album_length'),
|
||||
'sensitive' => 'nullable',
|
||||
'visibility' => 'string|in:private,unlisted,public',
|
||||
'spoiler_text' => 'sometimes|max:140',
|
||||
|
@ -3360,10 +3451,9 @@ class ApiV1Controller extends Controller
|
|||
$limitKey = 'compose:rate-limit:store:'.$user->id;
|
||||
$limitTtl = now()->addMinutes(15);
|
||||
$limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
|
||||
$minId = SnowflakeService::byDate(now()->subDays(1));
|
||||
$dailyLimit = Status::whereProfileId($user->profile_id)
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id')
|
||||
->where('created_at', '>', now()->subDays(1))
|
||||
->where('id', '>', $minId)
|
||||
->count();
|
||||
|
||||
return $dailyLimit >= 1000;
|
||||
|
@ -3436,7 +3526,7 @@ class ApiV1Controller extends Controller
|
|||
$mimes = [];
|
||||
|
||||
foreach ($ids as $k => $v) {
|
||||
if ($k + 1 > config_cache('pixelfed.max_album_length')) {
|
||||
if ($k + 1 > (int) config_cache('pixelfed.max_album_length')) {
|
||||
continue;
|
||||
}
|
||||
$m = Media::whereUserId($user->id)->whereNull('status_id')->findOrFail($v);
|
||||
|
@ -3712,7 +3802,6 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
$res = StatusHashtag::whereHashtagId($tag->id)
|
||||
->whereIn('status_visibility', ['public', 'private', 'unlisted'])
|
||||
->where('status_id', $dir, $id)
|
||||
->orderBy('status_id', 'desc')
|
||||
->limit(100)
|
||||
|
@ -3729,11 +3818,11 @@ class ApiV1Controller extends Controller
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if ($i['visibility'] === 'private') {
|
||||
if ((int) $i['account']['id'] !== $pid) {
|
||||
return FollowerService::follows($pid, $i['account']['id'], true);
|
||||
}
|
||||
}
|
||||
// if ($i['visibility'] === 'private') {
|
||||
// if ((int) $i['account']['id'] !== $pid) {
|
||||
// return FollowerService::follows($pid, $i['account']['id'], true);
|
||||
// }
|
||||
// }
|
||||
if ($onlyMedia == true) {
|
||||
if (! isset($i['media_attachments']) || ! count($i['media_attachments'])) {
|
||||
return false;
|
||||
|
@ -4199,4 +4288,26 @@ class ApiV1Controller extends Controller
|
|||
|
||||
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');
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,44 +2,32 @@
|
|||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
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\UserSetting;
|
||||
use App\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\BouncerService;
|
||||
use App\Services\InstanceService;
|
||||
use App\Services\MediaBlocklistService;
|
||||
use App\Services\MediaPathService;
|
||||
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\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||
use App\Jobs\VideoPipeline\{
|
||||
VideoOptimize,
|
||||
VideoPostProcess,
|
||||
VideoThumbnail
|
||||
};
|
||||
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||
use App\Util\Site\Nodeinfo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use League\Fractal;
|
||||
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
|
||||
{
|
||||
const PF_API_ENTITY_KEY = "_pe";
|
||||
const PF_API_ENTITY_KEY = '_pe';
|
||||
|
||||
public function json($res, $code = 200, $headers = [])
|
||||
{
|
||||
|
@ -49,10 +37,11 @@ class ApiV2Controller extends Controller
|
|||
public function instance(Request $request)
|
||||
{
|
||||
$contact = Cache::remember('api:v1:instance-data:contact', 604800, function () {
|
||||
if(config_cache('instance.admin.pid')) {
|
||||
if (config_cache('instance.admin.pid')) {
|
||||
return AccountService::getMastodon(config_cache('instance.admin.pid'), true);
|
||||
}
|
||||
$admin = User::whereIsAdmin(true)->first();
|
||||
|
||||
return $admin && isset($admin->profile_id) ?
|
||||
AccountService::getMastodon($admin->profile_id, true) :
|
||||
null;
|
||||
|
@ -61,41 +50,42 @@ class ApiV2Controller extends Controller
|
|||
$rules = Cache::remember('api:v1:instance-data:rules', 604800, function () {
|
||||
return config_cache('app.rules') ?
|
||||
collect(json_decode(config_cache('app.rules'), true))
|
||||
->map(function($rule, $key) {
|
||||
$id = $key + 1;
|
||||
return [
|
||||
'id' => "{$id}",
|
||||
'text' => $rule
|
||||
];
|
||||
})
|
||||
->toArray() : [];
|
||||
->map(function ($rule, $key) {
|
||||
$id = $key + 1;
|
||||
|
||||
return [
|
||||
'id' => "{$id}",
|
||||
'text' => $rule,
|
||||
];
|
||||
})
|
||||
->toArray() : [];
|
||||
});
|
||||
|
||||
$res = Cache::remember('api:v2:instance-data-response-v2', 1800, function () use($contact, $rules) {
|
||||
$res = Cache::remember('api:v2:instance-data-response-v2', 1800, function () use ($contact, $rules) {
|
||||
return [
|
||||
'domain' => config('pixelfed.domain.app'),
|
||||
'title' => config_cache('app.name'),
|
||||
'version' => '3.5.3 (compatible; Pixelfed ' . config('pixelfed.version') .')',
|
||||
'version' => '3.5.3 (compatible; Pixelfed '.config('pixelfed.version').')',
|
||||
'source_url' => 'https://github.com/pixelfed/pixelfed',
|
||||
'description' => config_cache('app.short_description'),
|
||||
'usage' => [
|
||||
'users' => [
|
||||
'active_month' => (int) Nodeinfo::activeUsersMonthly()
|
||||
]
|
||||
'active_month' => (int) Nodeinfo::activeUsersMonthly(),
|
||||
],
|
||||
],
|
||||
'thumbnail' => [
|
||||
'url' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
|
||||
'blurhash' => InstanceService::headerBlurhash(),
|
||||
'versions' => [
|
||||
'@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')],
|
||||
'configuration' => [
|
||||
'urls' => [
|
||||
'streaming' => null,
|
||||
'status' => null
|
||||
'status' => null,
|
||||
],
|
||||
'vapid' => [
|
||||
'public_key' => config('webpush.vapid.public_key'),
|
||||
|
@ -106,7 +96,7 @@ class ApiV2Controller extends Controller
|
|||
'statuses' => [
|
||||
'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
|
||||
'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
|
||||
'characters_reserved_per_url' => 23
|
||||
'characters_reserved_per_url' => 23,
|
||||
],
|
||||
'media_attachments' => [
|
||||
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
|
||||
|
@ -114,7 +104,7 @@ class ApiV2Controller extends Controller
|
|||
'image_matrix_limit' => 3686400,
|
||||
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
||||
'video_frame_rate_limit' => 240,
|
||||
'video_matrix_limit' => 3686400
|
||||
'video_matrix_limit' => 3686400,
|
||||
],
|
||||
'polls' => [
|
||||
'max_options' => 0,
|
||||
|
@ -134,14 +124,15 @@ class ApiV2Controller extends Controller
|
|||
],
|
||||
'contact' => [
|
||||
'email' => config('instance.email'),
|
||||
'account' => $contact
|
||||
'account' => $contact,
|
||||
],
|
||||
'rules' => $rules
|
||||
'rules' => $rules,
|
||||
];
|
||||
});
|
||||
|
||||
$res['registrations']['enabled'] = (bool) config_cache('pixelfed.open_registration');
|
||||
$res['registrations']['approval_required'] = (bool) config_cache('instance.curated_registration.enabled');
|
||||
|
||||
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
|
@ -153,7 +144,7 @@ class ApiV2Controller extends Controller
|
|||
*/
|
||||
public function search(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 403);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||
abort_unless($request->user()->tokenCan('read'), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
|
@ -166,18 +157,19 @@ class ApiV2Controller extends Controller
|
|||
'resolve' => 'nullable',
|
||||
'limit' => 'nullable|integer|max:40',
|
||||
'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 [
|
||||
'accounts' => [],
|
||||
'hashtags' => [],
|
||||
'statuses' => []
|
||||
'statuses' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$mastodonMode = !$request->has('_pe');
|
||||
$mastodonMode = ! $request->has('_pe');
|
||||
|
||||
return $this->json(SearchApiV2Service::query($request, $mastodonMode));
|
||||
}
|
||||
|
||||
|
@ -193,7 +185,7 @@ class ApiV2Controller extends Controller
|
|||
'host' => config('broadcasting.connections.pusher.options.host'),
|
||||
'port' => config('broadcasting.connections.pusher.options.port'),
|
||||
'key' => config('broadcasting.connections.pusher.key'),
|
||||
'cluster' => config('broadcasting.connections.pusher.options.cluster')
|
||||
'cluster' => config('broadcasting.connections.pusher.options.cluster'),
|
||||
] : [];
|
||||
}
|
||||
|
||||
|
@ -205,39 +197,39 @@ class ApiV2Controller extends Controller
|
|||
*/
|
||||
public function mediaUploadV2(Request $request)
|
||||
{
|
||||
abort_if(!$request->user() || !$request->user()->token(), 403);
|
||||
abort_if(! $request->user() || ! $request->user()->token(), 403);
|
||||
abort_unless($request->user()->tokenCan('write'), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'file.*' => [
|
||||
'required_without:file',
|
||||
'mimetypes:' . config_cache('pixelfed.media_types'),
|
||||
'max:' . config_cache('pixelfed.max_photo_size'),
|
||||
'mimetypes:'.config_cache('pixelfed.media_types'),
|
||||
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||
],
|
||||
'file' => [
|
||||
'required_without:file.*',
|
||||
'mimetypes:' . config_cache('pixelfed.media_types'),
|
||||
'max:' . config_cache('pixelfed.max_photo_size'),
|
||||
'mimetypes:'.config_cache('pixelfed.media_types'),
|
||||
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||
],
|
||||
'filter_name' => 'nullable|string|max:24',
|
||||
'filter_class' => 'nullable|alpha_dash|max:24',
|
||||
'description' => 'nullable|string|max:' . config_cache('pixelfed.max_altext_length'),
|
||||
'replace_id' => 'sometimes'
|
||||
'filter_name' => 'nullable|string|max:24',
|
||||
'filter_class' => 'nullable|alpha_dash|max:24',
|
||||
'description' => 'nullable|string|max:'.config_cache('pixelfed.max_altext_length'),
|
||||
'replace_id' => 'sometimes',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if($user->last_active_at == null) {
|
||||
if ($user->last_active_at == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if(empty($request->file('file'))) {
|
||||
if (empty($request->file('file'))) {
|
||||
return response('', 422);
|
||||
}
|
||||
|
||||
$limitKey = 'compose:rate-limit:media-upload:' . $user->id;
|
||||
$limitKey = 'compose:rate-limit:media-upload:'.$user->id;
|
||||
$limitTtl = now()->addMinutes(15);
|
||||
$limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
|
||||
$limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
|
||||
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
|
||||
|
||||
return $dailyLimit >= 1250;
|
||||
|
@ -246,23 +238,25 @@ class ApiV2Controller extends Controller
|
|||
|
||||
$profile = $user->profile;
|
||||
|
||||
if(config_cache('pixelfed.enforce_account_limit') == true) {
|
||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
|
||||
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');
|
||||
if ($size >= $limit) {
|
||||
abort(403, 'Account size limit reached.');
|
||||
if ($updatedAccountSize >= $limit) {
|
||||
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'));
|
||||
if(in_array($photo->getMimeType(), $mimes) == false) {
|
||||
if (in_array($photo->getMimeType(), $mimes) == false) {
|
||||
abort(403, 'Invalid or unsupported mime type.');
|
||||
}
|
||||
|
||||
|
@ -274,24 +268,24 @@ class ApiV2Controller extends Controller
|
|||
|
||||
$settings = UserSetting::whereUserId($user->id)->first();
|
||||
|
||||
if($settings && !empty($settings->compose_settings)) {
|
||||
if ($settings && ! empty($settings->compose_settings)) {
|
||||
$compose = $settings->compose_settings;
|
||||
|
||||
if(isset($compose['default_license']) && $compose['default_license'] != 1) {
|
||||
if (isset($compose['default_license']) && $compose['default_license'] != 1) {
|
||||
$license = $compose['default_license'];
|
||||
}
|
||||
}
|
||||
|
||||
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
||||
|
||||
if($request->has('replace_id')) {
|
||||
if ($request->has('replace_id')) {
|
||||
$rpid = $request->input('replace_id');
|
||||
$removeMedia = Media::whereNull('status_id')
|
||||
->whereUserId($user->id)
|
||||
->whereProfileId($profile->id)
|
||||
->where('created_at', '>', now()->subHours(2))
|
||||
->find($rpid);
|
||||
if($removeMedia) {
|
||||
if ($removeMedia) {
|
||||
MediaDeletePipeline::dispatch($removeMedia)
|
||||
->onQueue('mmo')
|
||||
->delay(now()->addMinutes(15));
|
||||
|
@ -309,7 +303,7 @@ class ApiV2Controller extends Controller
|
|||
$media->caption = $request->input('description');
|
||||
$media->filter_class = $filterClass;
|
||||
$media->filter_name = $filterName;
|
||||
if($license) {
|
||||
if ($license) {
|
||||
$media->license = $license;
|
||||
}
|
||||
$media->save();
|
||||
|
@ -327,13 +321,18 @@ class ApiV2Controller extends Controller
|
|||
break;
|
||||
}
|
||||
|
||||
$user->storage_used = (int) $updatedAccountSize;
|
||||
$user->storage_used_updated_at = now();
|
||||
$user->save();
|
||||
|
||||
Cache::forget($limitKey);
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||
$res = $fractal->createData($resource)->toArray();
|
||||
$res['preview_url'] = $media->url(). '?v=' . time();
|
||||
$res['preview_url'] = $media->url().'?v='.time();
|
||||
$res['url'] = null;
|
||||
|
||||
return $this->json($res, 202);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@ namespace App\Http\Controllers\Api;
|
|||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\{Profile, Status, User};
|
||||
use App\{Profile, Instance, Status, User};
|
||||
use Cache;
|
||||
use App\Services\StatusService;
|
||||
|
||||
class InstanceApiController extends Controller {
|
||||
|
||||
|
@ -40,11 +41,8 @@ class InstanceApiController extends Controller {
|
|||
'urls' => [],
|
||||
'stats' => [
|
||||
'user_count' => User::count(),
|
||||
'status_count' => Status::whereNull('uri')->count(),
|
||||
'domain_count' => Profile::whereNotNull('domain')
|
||||
->groupBy('domain')
|
||||
->pluck('domain')
|
||||
->count()
|
||||
'status_count' => StatusService::totalLocalStatuses(),
|
||||
'domain_count' => Instance::count()
|
||||
],
|
||||
'thumbnail' => '',
|
||||
'languages' => [],
|
||||
|
|
|
@ -72,7 +72,7 @@ class DomainBlockController extends Controller
|
|||
abort_if(config_cache('pixelfed.domain.app') == $domain, 400, 'Cannot ban your own server');
|
||||
|
||||
$existingCount = UserDomainBlock::whereProfileId($pid)->count();
|
||||
$maxLimit = config('instance.user_filters.max_domain_blocks');
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_domain_blocks');
|
||||
$errorMsg = __('profile.block.domain.max', ['max' => $maxLimit]);
|
||||
|
||||
abort_if($existingCount >= $maxLimit, 400, $errorMsg);
|
||||
|
|
|
@ -62,7 +62,7 @@ class ForgotPasswordController extends Controller
|
|||
|
||||
usleep(random_int(100000, 3000000));
|
||||
|
||||
if(config('captcha.enabled')) {
|
||||
if((bool) config_cache('captcha.enabled')) {
|
||||
$rules = [
|
||||
'email' => 'required|email',
|
||||
'h-captcha-response' => 'required|captcha'
|
||||
|
|
|
@ -74,10 +74,10 @@ class LoginController extends Controller
|
|||
$messages = [];
|
||||
|
||||
if(
|
||||
config('captcha.enabled') ||
|
||||
config('captcha.active.login') ||
|
||||
(bool) config_cache('captcha.enabled') &&
|
||||
(bool) config_cache('captcha.active.login') ||
|
||||
(
|
||||
config('captcha.triggers.login.enabled') &&
|
||||
(bool) config_cache('captcha.triggers.login.enabled') &&
|
||||
request()->session()->has('login_attempts') &&
|
||||
request()->session()->get('login_attempts') >= config('captcha.triggers.login.attempts')
|
||||
)
|
||||
|
|
|
@ -3,234 +3,239 @@
|
|||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\BouncerService;
|
||||
use App\Services\EmailService;
|
||||
use App\User;
|
||||
use Purify;
|
||||
use App\Util\Lexer\RestrictedNames;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\EmailService;
|
||||
use App\Services\BouncerService;
|
||||
use Purify;
|
||||
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles the registration of new users as well as their
|
||||
| validation and creation. By default this controller uses a trait to
|
||||
| provide this functionality without requiring any additional code.
|
||||
|
|
||||
*/
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles the registration of new users as well as their
|
||||
| validation and creation. By default this controller uses a trait to
|
||||
| provide this functionality without requiring any additional code.
|
||||
|
|
||||
*/
|
||||
|
||||
use RegistersUsers;
|
||||
use RegistersUsers;
|
||||
|
||||
/**
|
||||
* Where to redirect users after registration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/i/web';
|
||||
/**
|
||||
* Where to redirect users after registration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/i/web';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest');
|
||||
}
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
public function getRegisterToken()
|
||||
{
|
||||
return \Cache::remember('pf:register:rt', 900, function() {
|
||||
return str_random(40);
|
||||
});
|
||||
}
|
||||
public function getRegisterToken()
|
||||
{
|
||||
return \Cache::remember('pf:register:rt', 900, function () {
|
||||
return str_random(40);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a validator for an incoming registration request.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return \Illuminate\Contracts\Validation\Validator
|
||||
*/
|
||||
public function validator(array $data)
|
||||
{
|
||||
if(config('database.default') == 'pgsql') {
|
||||
$data['username'] = strtolower($data['username']);
|
||||
$data['email'] = strtolower($data['email']);
|
||||
}
|
||||
/**
|
||||
* Get a validator for an incoming registration request.
|
||||
*
|
||||
*
|
||||
* @return \Illuminate\Contracts\Validation\Validator
|
||||
*/
|
||||
public function validator(array $data)
|
||||
{
|
||||
if (config('database.default') == 'pgsql') {
|
||||
$data['username'] = strtolower($data['username']);
|
||||
$data['email'] = strtolower($data['email']);
|
||||
}
|
||||
|
||||
$usernameRules = [
|
||||
'required',
|
||||
'min:2',
|
||||
'max:15',
|
||||
'unique:users',
|
||||
function ($attribute, $value, $fail) {
|
||||
$dash = substr_count($value, '-');
|
||||
$underscore = substr_count($value, '_');
|
||||
$period = substr_count($value, '.');
|
||||
$usernameRules = [
|
||||
'required',
|
||||
'min:2',
|
||||
'max:15',
|
||||
'unique:users',
|
||||
function ($attribute, $value, $fail) {
|
||||
$dash = substr_count($value, '-');
|
||||
$underscore = substr_count($value, '_');
|
||||
$period = substr_count($value, '.');
|
||||
|
||||
if(ends_with($value, ['.php', '.js', '.css'])) {
|
||||
return $fail('Username is invalid.');
|
||||
}
|
||||
if (ends_with($value, ['.php', '.js', '.css'])) {
|
||||
return $fail('Username is invalid.');
|
||||
}
|
||||
|
||||
if(($dash + $underscore + $period) > 1) {
|
||||
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||
}
|
||||
if (($dash + $underscore + $period) > 1) {
|
||||
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[0])) {
|
||||
return $fail('Username is invalid. Must start with a letter or number.');
|
||||
}
|
||||
if (! ctype_alnum($value[0])) {
|
||||
return $fail('Username is invalid. Must start with a letter or number.');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[strlen($value) - 1])) {
|
||||
return $fail('Username is invalid. Must end with a letter or number.');
|
||||
}
|
||||
if (! ctype_alnum($value[strlen($value) - 1])) {
|
||||
return $fail('Username is invalid. Must end with a letter or number.');
|
||||
}
|
||||
|
||||
$val = str_replace(['_', '.', '-'], '', $value);
|
||||
if(!ctype_alnum($val)) {
|
||||
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||
}
|
||||
$val = str_replace(['_', '.', '-'], '', $value);
|
||||
if (! ctype_alnum($val)) {
|
||||
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||
}
|
||||
|
||||
$restricted = RestrictedNames::get();
|
||||
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||
return $fail('Username cannot be used.');
|
||||
}
|
||||
},
|
||||
];
|
||||
if (! preg_match('/[a-zA-Z]/', $value)) {
|
||||
return $fail('Username is invalid. Must contain at least one alphabetical character.');
|
||||
}
|
||||
|
||||
$emailRules = [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
'unique:users',
|
||||
function ($attribute, $value, $fail) {
|
||||
$banned = EmailService::isBanned($value);
|
||||
if($banned) {
|
||||
return $fail('Email is invalid.');
|
||||
}
|
||||
},
|
||||
];
|
||||
$restricted = RestrictedNames::get();
|
||||
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||
return $fail('Username cannot be used.');
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
$rt = [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
if($value !== $this->getRegisterToken()) {
|
||||
return $fail('Something went wrong');
|
||||
}
|
||||
}
|
||||
];
|
||||
$emailRules = [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
'unique:users',
|
||||
function ($attribute, $value, $fail) {
|
||||
$banned = EmailService::isBanned($value);
|
||||
if ($banned) {
|
||||
return $fail('Email is invalid.');
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
$rules = [
|
||||
'agecheck' => 'required|accepted',
|
||||
'rt' => $rt,
|
||||
'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
|
||||
'username' => $usernameRules,
|
||||
'email' => $emailRules,
|
||||
'password' => 'required|string|min:'.config('pixelfed.min_password_length').'|confirmed',
|
||||
];
|
||||
$rt = [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
if ($value !== $this->getRegisterToken()) {
|
||||
return $fail('Something went wrong');
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
if(config('captcha.enabled') || config('captcha.active.register')) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
}
|
||||
$rules = [
|
||||
'agecheck' => 'required|accepted',
|
||||
'rt' => $rt,
|
||||
'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
|
||||
'username' => $usernameRules,
|
||||
'email' => $emailRules,
|
||||
'password' => 'required|string|min:'.config('pixelfed.min_password_length').'|confirmed',
|
||||
];
|
||||
|
||||
return Validator::make($data, $rules);
|
||||
}
|
||||
if ((bool) config_cache('captcha.enabled') && (bool) config_cache('captcha.active.register')) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user instance after a valid registration.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return \App\User
|
||||
*/
|
||||
public function create(array $data)
|
||||
{
|
||||
if(config('database.default') == 'pgsql') {
|
||||
$data['username'] = strtolower($data['username']);
|
||||
$data['email'] = strtolower($data['email']);
|
||||
}
|
||||
return Validator::make($data, $rules);
|
||||
}
|
||||
|
||||
return User::create([
|
||||
'name' => Purify::clean($data['name']),
|
||||
'username' => $data['username'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'app_register_ip' => request()->ip()
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Create a new user instance after a valid registration.
|
||||
*
|
||||
*
|
||||
* @return \App\User
|
||||
*/
|
||||
public function create(array $data)
|
||||
{
|
||||
if (config('database.default') == 'pgsql') {
|
||||
$data['username'] = strtolower($data['username']);
|
||||
$data['email'] = strtolower($data['email']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application registration form.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function showRegistrationForm()
|
||||
{
|
||||
if((bool) config_cache('pixelfed.open_registration')) {
|
||||
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
|
||||
abort_if(BouncerService::checkIp(request()->ip()), 404);
|
||||
}
|
||||
$hasLimit = config('pixelfed.enforce_max_users');
|
||||
if($hasLimit) {
|
||||
$limit = config('pixelfed.max_users');
|
||||
$count = User::where(function($q){ return $q->whereNull('status')->orWhereNotIn('status', ['deleted','delete']); })->count();
|
||||
if($limit <= $count) {
|
||||
return redirect(route('help.instance-max-users-limit'));
|
||||
}
|
||||
abort_if($limit <= $count, 404);
|
||||
return view('auth.register');
|
||||
} else {
|
||||
return view('auth.register');
|
||||
}
|
||||
} else {
|
||||
if((bool) config_cache('instance.curated_registration.enabled') && config('instance.curated_registration.state.fallback_on_closed_reg')) {
|
||||
return redirect('/auth/sign_up');
|
||||
} else {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
return User::create([
|
||||
'name' => Purify::clean($data['name']),
|
||||
'username' => $data['username'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'app_register_ip' => request()->ip(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a registration request for the application.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function register(Request $request)
|
||||
{
|
||||
abort_if(config_cache('pixelfed.open_registration') == false, 400);
|
||||
/**
|
||||
* Show the application registration form.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function showRegistrationForm()
|
||||
{
|
||||
if ((bool) config_cache('pixelfed.open_registration')) {
|
||||
if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
|
||||
abort_if(BouncerService::checkIp(request()->ip()), 404);
|
||||
}
|
||||
$hasLimit = config('pixelfed.enforce_max_users');
|
||||
if ($hasLimit) {
|
||||
$limit = config('pixelfed.max_users');
|
||||
$count = User::where(function ($q) {
|
||||
return $q->whereNull('status')->orWhereNotIn('status', ['deleted', 'delete']);
|
||||
})->count();
|
||||
if ($limit <= $count) {
|
||||
return redirect(route('help.instance-max-users-limit'));
|
||||
}
|
||||
abort_if($limit <= $count, 404);
|
||||
|
||||
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
|
||||
abort_if(BouncerService::checkIp($request->ip()), 404);
|
||||
}
|
||||
return view('auth.register');
|
||||
} else {
|
||||
return view('auth.register');
|
||||
}
|
||||
} else {
|
||||
if ((bool) config_cache('instance.curated_registration.enabled') && config('instance.curated_registration.state.fallback_on_closed_reg')) {
|
||||
return redirect('/auth/sign_up');
|
||||
} else {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hasLimit = config('pixelfed.enforce_max_users');
|
||||
if($hasLimit) {
|
||||
$count = User::where(function($q){ return $q->whereNull('status')->orWhereNotIn('status', ['deleted','delete']); })->count();
|
||||
$limit = config('pixelfed.max_users');
|
||||
/**
|
||||
* Handle a registration request for the application.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function register(Request $request)
|
||||
{
|
||||
abort_if(config_cache('pixelfed.open_registration') == false, 400);
|
||||
|
||||
if($limit && $limit <= $count) {
|
||||
return redirect(route('help.instance-max-users-limit'));
|
||||
}
|
||||
}
|
||||
if (config('pixelfed.bouncer.cloud_ips.ban_signups')) {
|
||||
abort_if(BouncerService::checkIp($request->ip()), 404);
|
||||
}
|
||||
|
||||
$hasLimit = config('pixelfed.enforce_max_users');
|
||||
if ($hasLimit) {
|
||||
$count = User::where(function ($q) {
|
||||
return $q->whereNull('status')->orWhereNotIn('status', ['deleted', 'delete']);
|
||||
})->count();
|
||||
$limit = config('pixelfed.max_users');
|
||||
|
||||
$this->validator($request->all())->validate();
|
||||
if ($limit && $limit <= $count) {
|
||||
return redirect(route('help.instance-max-users-limit'));
|
||||
}
|
||||
}
|
||||
|
||||
event(new Registered($user = $this->create($request->all())));
|
||||
$this->validator($request->all())->validate();
|
||||
|
||||
$this->guard()->login($user);
|
||||
event(new Registered($user = $this->create($request->all())));
|
||||
|
||||
return $this->registered($request, $user)
|
||||
?: redirect($this->redirectPath());
|
||||
}
|
||||
$this->guard()->login($user);
|
||||
|
||||
return $this->registered($request, $user)
|
||||
?: redirect($this->redirectPath());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class ResetPasswordController extends Controller
|
|||
{
|
||||
usleep(random_int(100000, 3000000));
|
||||
|
||||
if(config('captcha.enabled')) {
|
||||
if((bool) config_cache('captcha.enabled')) {
|
||||
return [
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
|
|
|
@ -2,72 +2,65 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Auth;
|
||||
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\Collection;
|
||||
use App\CollectionItem;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\CollectionService;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\StatusService;
|
||||
use App\Status;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CollectionController extends Controller
|
||||
{
|
||||
public function create(Request $request)
|
||||
{
|
||||
abort_if(!Auth::check(), 403);
|
||||
abort_if(! Auth::check(), 403);
|
||||
$profile = Auth::user()->profile;
|
||||
|
||||
$collection = Collection::firstOrCreate([
|
||||
'profile_id' => $profile->id,
|
||||
'published_at' => null
|
||||
'published_at' => null,
|
||||
]);
|
||||
$collection->visibility = 'draft';
|
||||
$collection->save();
|
||||
|
||||
return view('collection.create', compact('collection'));
|
||||
}
|
||||
|
||||
public function show(Request $request, int $id)
|
||||
{
|
||||
$user = $request->user();
|
||||
$collection = CollectionService::getCollection($id);
|
||||
abort_if(!$collection, 404);
|
||||
if($collection['published_at'] == null || $collection['visibility'] != 'public') {
|
||||
abort_if(!$user, 404);
|
||||
if($user->profile_id != $collection['pid']) {
|
||||
if(!$user->is_admin) {
|
||||
abort_if($collection['visibility'] != 'private', 404);
|
||||
abort_if(!FollowerService::follows($user->profile_id, $collection['pid']), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
return view('collection.show', compact('collection'));
|
||||
$collection = CollectionService::getCollection($id);
|
||||
abort_if(! $collection, 404);
|
||||
if ($collection['published_at'] == null || $collection['visibility'] != 'public') {
|
||||
abort_if(! $user, 404);
|
||||
if ($user->profile_id != $collection['pid']) {
|
||||
if (! $user->is_admin) {
|
||||
abort_if($collection['visibility'] != 'private', 404);
|
||||
abort_if(! FollowerService::follows($user->profile_id, $collection['pid']), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view('collection.show', compact('collection'));
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
abort_if(!Auth::check(), 403);
|
||||
return $request->all();
|
||||
abort_if(! Auth::check(), 403);
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function store(Request $request, $id)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
abort_if(! $request->user(), 403);
|
||||
$this->validate($request, [
|
||||
'title' => 'nullable|max:50',
|
||||
'description' => 'nullable|max:500',
|
||||
'visibility' => 'nullable|string|in:public,private,draft'
|
||||
'title' => 'nullable|max:50',
|
||||
'description' => 'nullable|max:500',
|
||||
'visibility' => 'nullable|string|in:public,private,draft',
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
|
@ -78,20 +71,21 @@ class CollectionController extends Controller
|
|||
$collection->save();
|
||||
|
||||
CollectionService::deleteCollection($id);
|
||||
|
||||
return CollectionService::setCollection($collection->id, $collection);
|
||||
}
|
||||
|
||||
public function publish(Request $request, int $id)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
abort_if(! $request->user(), 403);
|
||||
$this->validate($request, [
|
||||
'title' => 'nullable|max:50',
|
||||
'description' => 'nullable|max:500',
|
||||
'visibility' => 'required|alpha|in:public,private,draft'
|
||||
'title' => 'nullable|max:50',
|
||||
'description' => 'nullable|max:500',
|
||||
'visibility' => 'required|alpha|in:public,private,draft',
|
||||
]);
|
||||
$profile = Auth::user()->profile;
|
||||
$profile = Auth::user()->profile;
|
||||
$collection = Collection::whereProfileId($profile->id)->findOrFail($id);
|
||||
if($collection->items()->count() == 0) {
|
||||
if ($collection->items()->count() == 0) {
|
||||
abort(404);
|
||||
}
|
||||
$collection->title = strip_tags($request->input('title'));
|
||||
|
@ -99,12 +93,13 @@ class CollectionController extends Controller
|
|||
$collection->visibility = $request->input('visibility');
|
||||
$collection->published_at = now();
|
||||
$collection->save();
|
||||
|
||||
return CollectionService::setCollection($collection->id, $collection);
|
||||
}
|
||||
|
||||
public function delete(Request $request, int $id)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
abort_if(! $request->user(), 403);
|
||||
$user = $request->user();
|
||||
|
||||
$collection = Collection::whereProfileId($user->profile_id)->findOrFail($id);
|
||||
|
@ -113,7 +108,7 @@ class CollectionController extends Controller
|
|||
|
||||
CollectionService::deleteCollection($id);
|
||||
|
||||
if($request->wantsJson()) {
|
||||
if ($request->wantsJson()) {
|
||||
return 200;
|
||||
}
|
||||
|
||||
|
@ -122,13 +117,13 @@ class CollectionController extends Controller
|
|||
|
||||
public function storeId(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
abort_if(! $request->user(), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'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;
|
||||
$collectionId = $request->input('collection_id');
|
||||
$postId = $request->input('post_id');
|
||||
|
@ -136,20 +131,20 @@ class CollectionController extends Controller
|
|||
$collection = Collection::whereProfileId($profileId)->findOrFail($collectionId);
|
||||
$count = $collection->items()->count();
|
||||
|
||||
if($count) {
|
||||
if ($count) {
|
||||
CollectionItem::whereCollectionId($collection->id)
|
||||
->get()
|
||||
->filter(function($col) {
|
||||
->filter(function ($col) {
|
||||
return StatusService::get($col->object_id, false) == null;
|
||||
})
|
||||
->each(function($col) use($collectionId) {
|
||||
->each(function ($col) use ($collectionId) {
|
||||
CollectionService::removeItem($collectionId, $col->object_id);
|
||||
$col->delete();
|
||||
});
|
||||
}
|
||||
|
||||
$max = config('pixelfed.max_collection_length');
|
||||
if($count >= $max) {
|
||||
if ($count >= $max) {
|
||||
abort(400, 'You can only add '.$max.' posts per collection');
|
||||
}
|
||||
|
||||
|
@ -160,10 +155,10 @@ class CollectionController extends Controller
|
|||
|
||||
$item = CollectionItem::firstOrCreate([
|
||||
'collection_id' => $collection->id,
|
||||
'object_type' => 'App\Status',
|
||||
'object_id' => $status->id
|
||||
],[
|
||||
'order' => $count,
|
||||
'object_type' => 'App\Status',
|
||||
'object_id' => $status->id,
|
||||
], [
|
||||
'order' => $count,
|
||||
]);
|
||||
|
||||
CollectionService::deleteCollection($collection->id);
|
||||
|
@ -177,112 +172,112 @@ class CollectionController extends Controller
|
|||
|
||||
public function getCollection(Request $request, $id)
|
||||
{
|
||||
$user = $request->user();
|
||||
$collection = CollectionService::getCollection($id);
|
||||
$user = $request->user();
|
||||
$collection = CollectionService::getCollection($id);
|
||||
|
||||
if(!$collection) {
|
||||
if (! $collection) {
|
||||
return response()->json([], 404);
|
||||
}
|
||||
|
||||
if($collection['published_at'] == null || $collection['visibility'] != 'public') {
|
||||
abort_unless($user, 404);
|
||||
if($user->profile_id != $collection['pid']) {
|
||||
if(!$user->is_admin) {
|
||||
abort_if($collection['visibility'] != 'private', 404);
|
||||
abort_if(!FollowerService::follows($user->profile_id, $collection['pid']), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($collection['published_at'] == null || $collection['visibility'] != 'public') {
|
||||
abort_unless($user, 404);
|
||||
if ($user->profile_id != $collection['pid']) {
|
||||
if (! $user->is_admin) {
|
||||
abort_if($collection['visibility'] != 'private', 404);
|
||||
abort_if(! FollowerService::follows($user->profile_id, $collection['pid']), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function getItems(Request $request, int $id)
|
||||
{
|
||||
$user = $request->user();
|
||||
$collection = CollectionService::getCollection($id);
|
||||
$user = $request->user();
|
||||
$collection = CollectionService::getCollection($id);
|
||||
|
||||
if(!$collection) {
|
||||
if (! $collection) {
|
||||
return response()->json([], 404);
|
||||
}
|
||||
|
||||
if($collection['published_at'] == null || $collection['visibility'] != 'public') {
|
||||
abort_unless($user, 404);
|
||||
if($user->profile_id != $collection['pid']) {
|
||||
if(!$user->is_admin) {
|
||||
abort_if($collection['visibility'] != 'private', 404);
|
||||
abort_if(!FollowerService::follows($user->profile_id, $collection['pid']), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($collection['published_at'] == null || $collection['visibility'] != 'public') {
|
||||
abort_unless($user, 404);
|
||||
if ($user->profile_id != $collection['pid']) {
|
||||
if (! $user->is_admin) {
|
||||
abort_if($collection['visibility'] != 'private', 404);
|
||||
abort_if(! FollowerService::follows($user->profile_id, $collection['pid']), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
$page = $request->input('page') ?? 1;
|
||||
$start = $page == 1 ? 0 : ($page * 10 - 10);
|
||||
$end = $start + 10;
|
||||
$items = CollectionService::getItems($id, $start, $end);
|
||||
|
||||
return collect($items)
|
||||
->map(function($id) {
|
||||
->map(function ($id) {
|
||||
return StatusService::get($id, false);
|
||||
})
|
||||
->filter(function($item) {
|
||||
return $item && ($item['visibility'] == 'public' || $item['visibility'] == 'unlisted') && isset($item['account'], $item['media_attachments']);
|
||||
})
|
||||
->values();
|
||||
})
|
||||
->filter(function ($item) {
|
||||
return $item && ($item['visibility'] == 'public' || $item['visibility'] == 'unlisted') && isset($item['account'], $item['media_attachments']);
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
public function getUserCollections(Request $request, int $id)
|
||||
{
|
||||
$user = $request->user();
|
||||
$pid = $user ? $user->profile_id : null;
|
||||
$follows = false;
|
||||
$visibility = ['public'];
|
||||
$user = $request->user();
|
||||
$pid = $user ? $user->profile_id : null;
|
||||
$follows = false;
|
||||
$visibility = ['public'];
|
||||
|
||||
$profile = AccountService::get($id, true);
|
||||
if(!$profile || !isset($profile['id'])) {
|
||||
if (! $profile || ! isset($profile['id'])) {
|
||||
return response()->json([], 404);
|
||||
}
|
||||
|
||||
if($pid) {
|
||||
$follows = FollowerService::follows($pid, $profile['id']);
|
||||
if ($pid) {
|
||||
$follows = FollowerService::follows($pid, $profile['id']);
|
||||
}
|
||||
|
||||
if($profile['locked']) {
|
||||
abort_if(!$pid, 404);
|
||||
if(!$user->is_admin) {
|
||||
abort_if($profile['id'] != $pid && $follows == false, 404);
|
||||
if ($profile['locked']) {
|
||||
abort_if(! $pid, 404);
|
||||
if (! $user->is_admin) {
|
||||
abort_if($profile['id'] != $pid && $follows == false, 404);
|
||||
}
|
||||
}
|
||||
|
||||
$owner = $pid ? $pid == $profile['id'] : false;
|
||||
|
||||
if($follows) {
|
||||
$visibility = ['public', 'private'];
|
||||
if ($follows) {
|
||||
$visibility = ['public', 'private'];
|
||||
}
|
||||
|
||||
if($pid && $pid == $profile['id']) {
|
||||
$visibility = ['public', 'private', 'draft'];
|
||||
if ($pid && $pid == $profile['id']) {
|
||||
$visibility = ['public', 'private', 'draft'];
|
||||
}
|
||||
|
||||
return Collection::whereProfileId($profile['id'])
|
||||
->whereIn('visibility', $visibility)
|
||||
->when(!$owner, function($q, $owner) {
|
||||
return $q->whereNotNull('published_at');
|
||||
})
|
||||
->whereIn('visibility', $visibility)
|
||||
->when(! $owner, function ($q, $owner) {
|
||||
return $q->whereNotNull('published_at');
|
||||
})
|
||||
->orderByDesc('id')
|
||||
->paginate(9)
|
||||
->map(function($collection) {
|
||||
return CollectionService::getCollection($collection->id);
|
||||
});
|
||||
->map(function ($collection) {
|
||||
return CollectionService::getCollection($collection->id);
|
||||
});
|
||||
}
|
||||
|
||||
public function deleteId(Request $request)
|
||||
{
|
||||
abort_if(!$request->user(), 403);
|
||||
abort_if(! $request->user(), 403);
|
||||
$this->validate($request, [
|
||||
'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;
|
||||
$collectionId = $request->input('collection_id');
|
||||
$postId = $request->input('post_id');
|
||||
|
@ -290,7 +285,7 @@ class CollectionController extends Controller
|
|||
$collection = Collection::whereProfileId($profileId)->findOrFail($collectionId);
|
||||
$count = $collection->items()->count();
|
||||
|
||||
if($count == 1) {
|
||||
if ($count == 1) {
|
||||
abort(400, 'You cannot delete the only post of a collection!');
|
||||
}
|
||||
|
||||
|
@ -308,7 +303,7 @@ class CollectionController extends Controller
|
|||
CollectionItem::whereCollectionId($collection->id)
|
||||
->orderBy('created_at')
|
||||
->get()
|
||||
->each(function($item, $index) {
|
||||
->each(function ($item, $index) {
|
||||
$item->order = $index;
|
||||
$item->save();
|
||||
});
|
||||
|
@ -319,4 +314,31 @@ class CollectionController extends Controller
|
|||
|
||||
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\SnowflakeService;
|
||||
use App\Services\UserRoleService;
|
||||
use App\Services\UserStorageService;
|
||||
use App\Status;
|
||||
use App\Transformer\Api\MediaTransformer;
|
||||
use App\UserFilter;
|
||||
|
@ -70,7 +71,7 @@ class ComposeController extends Controller
|
|||
'filter_class' => 'nullable|alpha_dash|max:24',
|
||||
]);
|
||||
|
||||
$user = Auth::user();
|
||||
$user = $request->user();
|
||||
$profile = $user->profile;
|
||||
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);
|
||||
|
||||
if (config_cache('pixelfed.enforce_account_limit') == true) {
|
||||
$size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
|
||||
return Media::whereUserId($user->id)->sum('size') / 1000;
|
||||
});
|
||||
$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;
|
||||
$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');
|
||||
if ($size >= $limit) {
|
||||
if ($updatedAccountSize >= $limit) {
|
||||
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'));
|
||||
|
||||
abort_if(in_array($photo->getMimeType(), $mimes) == false, 400, 'Invalid media format');
|
||||
|
@ -143,6 +145,10 @@ class ComposeController extends Controller
|
|||
break;
|
||||
}
|
||||
|
||||
$user->storage_used = (int) $updatedAccountSize;
|
||||
$user->storage_used_updated_at = now();
|
||||
$user->save();
|
||||
|
||||
Cache::forget($limitKey);
|
||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
@ -198,6 +204,7 @@ class ComposeController extends Controller
|
|||
];
|
||||
ImageOptimize::dispatch($media)->onQueue('mmo');
|
||||
Cache::forget($limitKey);
|
||||
UserStorageService::recalculateUpdateStorageUsed($request->user()->id);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
@ -218,6 +225,8 @@ class ComposeController extends Controller
|
|||
|
||||
MediaStorageService::delete($media, true);
|
||||
|
||||
UserStorageService::recalculateUpdateStorageUsed($request->user()->id);
|
||||
|
||||
return response()->json([
|
||||
'msg' => 'Successfully deleted',
|
||||
'code' => 200,
|
||||
|
@ -494,17 +503,17 @@ class ComposeController extends Controller
|
|||
|
||||
$limitKey = 'compose:rate-limit:store:'.$user->id;
|
||||
$limitTtl = now()->addMinutes(15);
|
||||
$limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
|
||||
$dailyLimit = Status::whereProfileId($user->profile_id)
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id')
|
||||
->where('created_at', '>', now()->subDays(1))
|
||||
->count();
|
||||
// $limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
|
||||
// $dailyLimit = Status::whereProfileId($user->profile_id)
|
||||
// ->whereNull('in_reply_to_id')
|
||||
// ->whereNull('reblog_of_id')
|
||||
// ->where('created_at', '>', now()->subDays(1))
|
||||
// ->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;
|
||||
|
||||
|
@ -626,7 +635,6 @@ class ComposeController extends Controller
|
|||
Cache::forget('_api:statuses:recent_9:'.$profile->id);
|
||||
Cache::forget('profile:status_count:'.$profile->id);
|
||||
Cache::forget('status:transformer:media:attachments:'.$status->id);
|
||||
Cache::forget($user->storageUsedKey());
|
||||
Cache::forget('profile:embed:'.$status->profile_id);
|
||||
Cache::forget($limitKey);
|
||||
|
||||
|
@ -741,7 +749,7 @@ class ComposeController extends Controller
|
|||
case 'image/jpeg':
|
||||
case 'image/png':
|
||||
case 'video/mp4':
|
||||
$finished = config_cache('pixelfed.cloud_storage') ? (bool) $media->cdn_url : (bool) $media->processed_at;
|
||||
$finished = (bool) config_cache('pixelfed.cloud_storage') ? (bool) $media->cdn_url : (bool) $media->processed_at;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,6 +11,7 @@ use App\Services\BookmarkService;
|
|||
use App\Services\ConfigCacheService;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\HashtagService;
|
||||
use App\Services\Internal\BeagleService;
|
||||
use App\Services\LikeService;
|
||||
use App\Services\ReblogService;
|
||||
use App\Services\SnowflakeService;
|
||||
|
@ -420,4 +421,11 @@ class DiscoverController extends Controller
|
|||
|
||||
return response()->json($ids, 200, [], JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function discoverNetworkTrending(Request $request)
|
||||
{
|
||||
abort_if(! $request->user(), 404);
|
||||
|
||||
return BeagleService::getDiscoverPosts();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,57 +2,42 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\InboxPipeline\{
|
||||
DeleteWorker,
|
||||
InboxWorker,
|
||||
InboxValidator
|
||||
};
|
||||
use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline;
|
||||
use App\{
|
||||
AccountLog,
|
||||
Like,
|
||||
Profile,
|
||||
Status,
|
||||
User
|
||||
};
|
||||
use App\Util\Lexer\Nickname;
|
||||
use App\Util\Webfinger\Webfinger;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use League\Fractal;
|
||||
use App\Util\Site\Nodeinfo;
|
||||
use App\Util\ActivityPub\{
|
||||
Helpers,
|
||||
HttpSignature,
|
||||
Outbox
|
||||
};
|
||||
use Zttp\Zttp;
|
||||
use App\Services\InstanceService;
|
||||
use App\Jobs\InboxPipeline\DeleteWorker;
|
||||
use App\Jobs\InboxPipeline\InboxValidator;
|
||||
use App\Jobs\InboxPipeline\InboxWorker;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\InstanceService;
|
||||
use App\Status;
|
||||
use App\Util\Lexer\Nickname;
|
||||
use App\Util\Site\Nodeinfo;
|
||||
use App\Util\Webfinger\Webfinger;
|
||||
use Cache;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FederationController extends Controller
|
||||
{
|
||||
public function nodeinfoWellKnown()
|
||||
{
|
||||
abort_if(!config('federation.nodeinfo.enabled'), 404);
|
||||
abort_if(! config('federation.nodeinfo.enabled'), 404);
|
||||
|
||||
return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES)
|
||||
->header('Access-Control-Allow-Origin','*');
|
||||
->header('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
public function nodeinfo()
|
||||
{
|
||||
abort_if(!config('federation.nodeinfo.enabled'), 404);
|
||||
abort_if(! config('federation.nodeinfo.enabled'), 404);
|
||||
|
||||
return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES)
|
||||
->header('Access-Control-Allow-Origin','*');
|
||||
->header('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
public function webfinger(Request $request)
|
||||
{
|
||||
if (!config('federation.webfinger.enabled') ||
|
||||
!$request->has('resource') ||
|
||||
!$request->filled('resource')
|
||||
if (! config('federation.webfinger.enabled') ||
|
||||
! $request->has('resource') ||
|
||||
! $request->filled('resource')
|
||||
) {
|
||||
return response('', 400);
|
||||
}
|
||||
|
@ -60,55 +45,87 @@ class FederationController extends Controller
|
|||
$resource = $request->input('resource');
|
||||
$domain = config('pixelfed.domain.app');
|
||||
|
||||
if(config('federation.activitypub.sharedInbox') &&
|
||||
$resource == 'acct:' . $domain . '@' . $domain) {
|
||||
// Instance Actor
|
||||
if (
|
||||
config('federation.activitypub.sharedInbox') &&
|
||||
$resource == 'acct:'.$domain.'@'.$domain
|
||||
) {
|
||||
$res = [
|
||||
'subject' => 'acct:' . $domain . '@' . $domain,
|
||||
'subject' => 'acct:'.$domain.'@'.$domain,
|
||||
'aliases' => [
|
||||
'https://' . $domain . '/i/actor'
|
||||
'https://'.$domain.'/i/actor',
|
||||
],
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'http://webfinger.net/rel/profile-page',
|
||||
'type' => 'text/html',
|
||||
'href' => 'https://' . $domain . '/site/kb/instance-actor'
|
||||
'href' => 'https://'.$domain.'/site/kb/instance-actor',
|
||||
],
|
||||
[
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => 'https://' . $domain . '/i/actor'
|
||||
]
|
||||
]
|
||||
'href' => 'https://'.$domain.'/i/actor',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
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);
|
||||
$key = 'federation:webfinger:sha256:' . $hash;
|
||||
if($cached = Cache::get($key)) {
|
||||
$key = 'federation:webfinger:sha256:'.$hash;
|
||||
if ($cached = Cache::get($key)) {
|
||||
return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
if(strpos($resource, $domain) == false) {
|
||||
if (strpos($resource, $domain) == false) {
|
||||
return response('', 400);
|
||||
}
|
||||
$parsed = Nickname::normalizeProfileUrl($resource);
|
||||
if(empty($parsed) || $parsed['domain'] !== $domain) {
|
||||
if (empty($parsed) || $parsed['domain'] !== $domain) {
|
||||
return response('', 400);
|
||||
}
|
||||
$username = $parsed['username'];
|
||||
$profile = Profile::whereNull('domain')->whereUsername($username)->first();
|
||||
if(!$profile || $profile->status !== null) {
|
||||
$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','*');
|
||||
->header('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
public function hostMeta(Request $request)
|
||||
{
|
||||
abort_if(!config('federation.webfinger.enabled'), 404);
|
||||
abort_if(! config('federation.webfinger.enabled'), 404);
|
||||
|
||||
$path = route('well-known.webfinger');
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" type="application/xrd+xml" template="'.$path.'?resource={uri}"/></XRD>';
|
||||
|
@ -118,19 +135,19 @@ class FederationController extends Controller
|
|||
|
||||
public function userOutbox(Request $request, $username)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
|
||||
if(!$request->wantsJson()) {
|
||||
return redirect('/' . $username);
|
||||
if (! $request->wantsJson()) {
|
||||
return redirect('/'.$username);
|
||||
}
|
||||
|
||||
$id = AccountService::usernameToId($username);
|
||||
abort_if(!$id, 404);
|
||||
abort_if(! $id, 404);
|
||||
$account = AccountService::get($id);
|
||||
abort_if(!$account || !isset($account['statuses_count']), 404);
|
||||
abort_if(! $account || ! isset($account['statuses_count']), 404);
|
||||
$res = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => 'https://' . config('pixelfed.domain.app') . '/users/' . $username . '/outbox',
|
||||
'id' => 'https://'.config('pixelfed.domain.app').'/users/'.$username.'/outbox',
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $account['statuses_count'] ?? 0,
|
||||
];
|
||||
|
@ -140,135 +157,145 @@ class FederationController extends Controller
|
|||
|
||||
public function userInbox(Request $request, $username)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(!config('federation.activitypub.inbox'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! config('federation.activitypub.inbox'), 404);
|
||||
|
||||
$headers = $request->headers->all();
|
||||
$payload = $request->getContent();
|
||||
if(!$payload || empty($payload)) {
|
||||
if (! $payload || empty($payload)) {
|
||||
return;
|
||||
}
|
||||
$obj = json_decode($payload, true, 8);
|
||||
if(!isset($obj['id'])) {
|
||||
if (! isset($obj['id'])) {
|
||||
return;
|
||||
}
|
||||
$domain = parse_url($obj['id'], PHP_URL_HOST);
|
||||
if(in_array($domain, InstanceService::getBannedDomains())) {
|
||||
if (in_array($domain, InstanceService::getBannedDomains())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||
if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||
if($obj['object']['type'] === 'Person') {
|
||||
if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||
if (isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||
if (isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||
if ($obj['object']['type'] === 'Person') {
|
||||
if (Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($obj['object']['type'] === 'Tombstone') {
|
||||
if(Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||
if ($obj['object']['type'] === 'Tombstone') {
|
||||
if (Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($obj['object']['type'] === 'Story') {
|
||||
if ($obj['object']['type'] === 'Story') {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('story');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) {
|
||||
} elseif (isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) {
|
||||
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('follow');
|
||||
} else {
|
||||
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high');
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function sharedInbox(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(!config('federation.activitypub.sharedInbox'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! config('federation.activitypub.sharedInbox'), 404);
|
||||
|
||||
$headers = $request->headers->all();
|
||||
$payload = $request->getContent();
|
||||
|
||||
if(!$payload || empty($payload)) {
|
||||
if (! $payload || empty($payload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$obj = json_decode($payload, true, 8);
|
||||
if(!isset($obj['id'])) {
|
||||
if (! isset($obj['id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domain = parse_url($obj['id'], PHP_URL_HOST);
|
||||
if(in_array($domain, InstanceService::getBannedDomains())) {
|
||||
if (in_array($domain, InstanceService::getBannedDomains())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||
if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||
if($obj['object']['type'] === 'Person') {
|
||||
if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||
if (isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||
if (isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||
if ($obj['object']['type'] === 'Person') {
|
||||
if (Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($obj['object']['type'] === 'Tombstone') {
|
||||
if(Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||
if ($obj['object']['type'] === 'Tombstone') {
|
||||
if (Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($obj['object']['type'] === 'Story') {
|
||||
if ($obj['object']['type'] === 'Story') {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('story');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) {
|
||||
} elseif (isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) {
|
||||
dispatch(new InboxWorker($headers, $payload))->onQueue('follow');
|
||||
} else {
|
||||
dispatch(new InboxWorker($headers, $payload))->onQueue('shared');
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function userFollowing(Request $request, $username)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
|
||||
$id = AccountService::usernameToId($username);
|
||||
abort_if(!$id, 404);
|
||||
abort_if(! $id, 404);
|
||||
$account = AccountService::get($id);
|
||||
abort_if(!$account || !isset($account['following_count']), 404);
|
||||
abort_if(! $account || ! isset($account['following_count']), 404);
|
||||
$obj = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => $request->getUri(),
|
||||
'type' => 'OrderedCollection',
|
||||
'id' => $request->getUri(),
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $account['following_count'] ?? 0,
|
||||
];
|
||||
|
||||
return response()->json($obj)->header('Content-Type', 'application/activity+json');
|
||||
}
|
||||
|
||||
public function userFollowers(Request $request, $username)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
$id = AccountService::usernameToId($username);
|
||||
abort_if(!$id, 404);
|
||||
abort_if(! $id, 404);
|
||||
$account = AccountService::get($id);
|
||||
abort_if(!$account || !isset($account['followers_count']), 404);
|
||||
abort_if(! $account || ! isset($account['followers_count']), 404);
|
||||
$obj = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => $request->getUri(),
|
||||
'type' => 'OrderedCollection',
|
||||
'id' => $request->getUri(),
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $account['followers_count'] ?? 0,
|
||||
];
|
||||
|
||||
return response()->json($obj)->header('Content-Type', 'application/activity+json');
|
||||
}
|
||||
}
|
||||
|
|
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'));
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ trait Instagram
|
|||
{
|
||||
public function instagram()
|
||||
{
|
||||
if(config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
return view('settings.import.instagram.home');
|
||||
|
@ -25,6 +25,9 @@ trait Instagram
|
|||
|
||||
public function instagramStart(Request $request)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$completed = ImportJob::whereProfileId(Auth::user()->profile->id)
|
||||
->whereService('instagram')
|
||||
->whereNotNull('completed_at')
|
||||
|
@ -38,6 +41,9 @@ trait Instagram
|
|||
|
||||
protected function instagramRedirectOrNew()
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
$exists = ImportJob::whereProfileId($profile->id)
|
||||
->whereService('instagram')
|
||||
|
@ -61,6 +67,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepOne(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
$job = ImportJob::whereProfileId($profile->id)
|
||||
->whereNull('completed_at')
|
||||
|
@ -72,6 +81,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepOneStore(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$max = 'max:' . config('pixelfed.import.instagram.limits.size');
|
||||
$this->validate($request, [
|
||||
'media.*' => 'required|mimes:bin,jpeg,png,gif|'.$max,
|
||||
|
@ -114,6 +126,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepTwo(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
$job = ImportJob::whereProfileId($profile->id)
|
||||
->whereNull('completed_at')
|
||||
|
@ -125,6 +140,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepTwoStore(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$this->validate($request, [
|
||||
'media' => 'required|file|max:1000'
|
||||
]);
|
||||
|
@ -150,6 +168,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepThree(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
$job = ImportJob::whereProfileId($profile->id)
|
||||
->whereService('instagram')
|
||||
|
@ -162,6 +183,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepThreeStore(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
|
||||
try {
|
||||
|
|
|
@ -2,44 +2,43 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Http\Resources\DirectoryProfile;
|
||||
use App\Profile;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LandingController extends Controller
|
||||
{
|
||||
public function directoryRedirect(Request $request)
|
||||
{
|
||||
if($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
if ($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
abort_if(config_cache('instance.landing.show_directory') == false, 404);
|
||||
abort_if((bool) config_cache('instance.landing.show_directory') == false, 404);
|
||||
|
||||
return view('site.index');
|
||||
return view('site.index');
|
||||
}
|
||||
|
||||
public function exploreRedirect(Request $request)
|
||||
{
|
||||
if($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
if ($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
abort_if(config_cache('instance.landing.show_explore') == false, 404);
|
||||
abort_if((bool) config_cache('instance.landing.show_explore') == false, 404);
|
||||
|
||||
return view('site.index');
|
||||
return view('site.index');
|
||||
}
|
||||
|
||||
public function getDirectoryApi(Request $request)
|
||||
{
|
||||
abort_if(config_cache('instance.landing.show_directory') == false, 404);
|
||||
abort_if((bool) config_cache('instance.landing.show_directory') == false, 404);
|
||||
|
||||
return DirectoryProfile::collection(
|
||||
Profile::whereNull('domain')
|
||||
->whereIsSuggestable(true)
|
||||
->orderByDesc('updated_at')
|
||||
->cursorPaginate(20)
|
||||
);
|
||||
return DirectoryProfile::collection(
|
||||
Profile::whereNull('domain')
|
||||
->whereIsSuggestable(true)
|
||||
->orderByDesc('updated_at')
|
||||
->cursorPaginate(20)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,30 +2,31 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Media;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MediaController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
//return view('settings.drive.index');
|
||||
}
|
||||
public function index(Request $request)
|
||||
{
|
||||
//return view('settings.drive.index');
|
||||
abort(404);
|
||||
}
|
||||
|
||||
public function composeUpdate(Request $request, $id)
|
||||
{
|
||||
public function composeUpdate(Request $request, $id)
|
||||
{
|
||||
abort(400, 'Endpoint deprecated');
|
||||
}
|
||||
}
|
||||
|
||||
public function fallbackRedirect(Request $request, $pid, $mhash, $uhash, $f)
|
||||
{
|
||||
abort_if(!config_cache('pixelfed.cloud_storage'), 404);
|
||||
$path = 'public/m/_v2/' . $pid . '/' . $mhash . '/' . $uhash . '/' . $f;
|
||||
$media = Media::whereProfileId($pid)
|
||||
->whereMediaPath($path)
|
||||
->whereNotNull('cdn_url')
|
||||
->firstOrFail();
|
||||
public function fallbackRedirect(Request $request, $pid, $mhash, $uhash, $f)
|
||||
{
|
||||
abort_if(! (bool) config_cache('pixelfed.cloud_storage'), 404);
|
||||
$path = 'public/m/_v2/'.$pid.'/'.$mhash.'/'.$uhash.'/'.$f;
|
||||
$media = Media::whereProfileId($pid)
|
||||
->whereMediaPath($path)
|
||||
->whereNotNull('cdn_url')
|
||||
->firstOrFail();
|
||||
|
||||
return redirect()->away($media->cdn_url);
|
||||
}
|
||||
return redirect()->away($media->cdn_url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,37 +2,41 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\ConfigCache;
|
||||
use Storage;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\InstanceService;
|
||||
use App\Services\StatusService;
|
||||
use App\User;
|
||||
use Cache;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Storage;
|
||||
|
||||
class PixelfedDirectoryController extends Controller
|
||||
{
|
||||
public function get(Request $request)
|
||||
{
|
||||
if(!$request->filled('sk')) {
|
||||
if (! $request->filled('sk')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if(!config_cache('pixelfed.directory.submission-key')) {
|
||||
if (! config_cache('pixelfed.directory.submission-key')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if(!hash_equals(config_cache('pixelfed.directory.submission-key'), $request->input('sk'))) {
|
||||
if (! hash_equals(config_cache('pixelfed.directory.submission-key'), $request->input('sk'))) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$res = $this->buildListing();
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function buildListing()
|
||||
{
|
||||
$res = config_cache('pixelfed.directory');
|
||||
if($res) {
|
||||
if ($res) {
|
||||
$res = is_string($res) ? json_decode($res, true) : $res;
|
||||
}
|
||||
|
||||
|
@ -41,40 +45,40 @@ class PixelfedDirectoryController extends Controller
|
|||
$res['_ts'] = config_cache('pixelfed.directory.submission-ts');
|
||||
$res['version'] = config_cache('pixelfed.version');
|
||||
|
||||
if(empty($res['summary'])) {
|
||||
if (empty($res['summary'])) {
|
||||
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
||||
$res['summary'] = $summary ? $summary[0] : null;
|
||||
}
|
||||
|
||||
if(isset($res['admin'])) {
|
||||
if (isset($res['admin'])) {
|
||||
$res['admin'] = AccountService::get($res['admin'], true);
|
||||
}
|
||||
|
||||
if(isset($res['banner_image']) && !empty($res['banner_image'])) {
|
||||
if (isset($res['banner_image']) && ! empty($res['banner_image'])) {
|
||||
$res['banner_image'] = url(Storage::url($res['banner_image']));
|
||||
}
|
||||
|
||||
if(isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function($id) {
|
||||
if (isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function ($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->map(function($post) {
|
||||
return [
|
||||
'avatar' => $post['account']['avatar'],
|
||||
'display_name' => $post['account']['display_name'],
|
||||
'username' => $post['account']['username'],
|
||||
'media' => $post['media_attachments'][0]['url'],
|
||||
'url' => $post['url']
|
||||
];
|
||||
})
|
||||
->values();
|
||||
->filter(function ($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->map(function ($post) {
|
||||
return [
|
||||
'avatar' => $post['account']['avatar'],
|
||||
'display_name' => $post['account']['display_name'],
|
||||
'username' => $post['account']['username'],
|
||||
'media' => $post['media_attachments'][0]['url'],
|
||||
'url' => $post['url'],
|
||||
];
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
$guidelines = ConfigCache::whereK('app.rules')->first();
|
||||
if($guidelines) {
|
||||
if ($guidelines) {
|
||||
$res['community_guidelines'] = json_decode($guidelines->v, true);
|
||||
}
|
||||
|
||||
|
@ -85,27 +89,27 @@ class PixelfedDirectoryController extends Controller
|
|||
$res['curated_onboarding'] = $curatedOnboarding;
|
||||
|
||||
$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'));
|
||||
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
||||
}
|
||||
|
||||
$activityPubEnabled = ConfigCache::whereK('federation.activitypub.enabled')->first();
|
||||
if($activityPubEnabled) {
|
||||
if ($activityPubEnabled) {
|
||||
$res['activitypub_enabled'] = (bool) $activityPubEnabled;
|
||||
}
|
||||
|
||||
$res['feature_config'] = [
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
'optimize_image' => (bool) config_cache('pixelfed.optimize_image'),
|
||||
'max_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => config_cache('pixelfed.max_altext_length'),
|
||||
'enforce_account_limit' => config_cache('pixelfed.enforce_account_limit'),
|
||||
'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit'),
|
||||
'max_account_size' => config_cache('pixelfed.max_account_size'),
|
||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
||||
'account_deletion' => config_cache('pixelfed.account_deletion'),
|
||||
'account_deletion' => (bool) config_cache('pixelfed.account_deletion'),
|
||||
];
|
||||
|
||||
$res['is_eligible'] = $this->validVal($res, 'admin') &&
|
||||
|
@ -115,29 +119,34 @@ class PixelfedDirectoryController extends Controller
|
|||
$this->validVal($res, 'privacy_pledge') &&
|
||||
$this->validVal($res, 'location');
|
||||
|
||||
if(config_cache('pixelfed.directory.testimonials')) {
|
||||
if (config_cache('pixelfed.directory.testimonials')) {
|
||||
$res['testimonials'] = collect(json_decode(config_cache('pixelfed.directory.testimonials'), true))
|
||||
->map(function($testimonial) {
|
||||
->map(function ($testimonial) {
|
||||
$profile = AccountService::get($testimonial['profile_id']);
|
||||
|
||||
return [
|
||||
'profile' => [
|
||||
'username' => $profile['username'],
|
||||
'display_name' => $profile['display_name'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'created_at' => $profile['created_at']
|
||||
'created_at' => $profile['created_at'],
|
||||
],
|
||||
'body' => $testimonial['body']
|
||||
'body' => $testimonial['body'],
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
$res['features_enabled'] = [
|
||||
'stories' => (bool) config_cache('instance.stories.enabled')
|
||||
'stories' => (bool) config_cache('instance.stories.enabled'),
|
||||
];
|
||||
|
||||
$statusesCount = InstanceService::totalLocalStatuses();
|
||||
$usersCount = Cache::remember('api:nodeinfo:users', 43200, function () {
|
||||
return User::count();
|
||||
});
|
||||
$res['stats'] = [
|
||||
'user_count' => \App\User::count(),
|
||||
'post_count' => \App\Status::whereNull('uri')->count(),
|
||||
'user_count' => (int) $usersCount,
|
||||
'post_count' => (int) $statusesCount,
|
||||
];
|
||||
|
||||
$res['primary_locale'] = config('app.locale');
|
||||
|
@ -150,19 +159,18 @@ class PixelfedDirectoryController extends Controller
|
|||
|
||||
protected function validVal($res, $val, $count = false, $minLen = false)
|
||||
{
|
||||
if(!isset($res[$val])) {
|
||||
if (! isset($res[$val])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($count) {
|
||||
if ($count) {
|
||||
return count($res[$val]) >= $count;
|
||||
}
|
||||
|
||||
if($minLen) {
|
||||
if ($minLen) {
|
||||
return strlen($res[$val]) >= $minLen;
|
||||
}
|
||||
|
||||
return $res[$val];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -172,6 +172,8 @@ class ProfileController extends Controller
|
|||
|
||||
$user = $this->getCachedUser($username);
|
||||
|
||||
abort_if(! $user, 404);
|
||||
|
||||
return redirect($user->url());
|
||||
}
|
||||
|
||||
|
@ -252,7 +254,7 @@ class ProfileController extends Controller
|
|||
|
||||
abort_if(! $profile || $profile['locked'] || ! $profile['local'], 404);
|
||||
|
||||
$aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 86400, function () use ($profile) {
|
||||
$aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 3600, function () use ($profile) {
|
||||
$uid = User::whereProfileId($profile['id'])->first();
|
||||
if (! $uid) {
|
||||
return true;
|
||||
|
@ -267,7 +269,7 @@ class ProfileController extends Controller
|
|||
|
||||
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();
|
||||
if (! $uid) {
|
||||
return false;
|
||||
|
@ -346,7 +348,7 @@ class ProfileController extends Controller
|
|||
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
||||
}
|
||||
|
||||
$aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 86400, function () use ($profile) {
|
||||
$aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 3600, function () use ($profile) {
|
||||
$exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
|
||||
if ($exists) {
|
||||
return true;
|
||||
|
@ -359,7 +361,7 @@ class ProfileController extends Controller
|
|||
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']);
|
||||
}
|
||||
|
||||
|
@ -371,7 +373,7 @@ class ProfileController extends Controller
|
|||
|
||||
public function stories(Request $request, $username)
|
||||
{
|
||||
abort_if(! config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||
$pid = $profile->id;
|
||||
$authed = Auth::user()->profile_id;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,22 +2,20 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\Account\RemoteAuthService;
|
||||
use App\Models\RemoteAuth;
|
||||
use App\Profile;
|
||||
use App\Instance;
|
||||
use App\User;
|
||||
use Purify;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use App\Util\Lexer\RestrictedNames;
|
||||
use App\Services\Account\RemoteAuthService;
|
||||
use App\Services\EmailService;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\User;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Util\Lexer\RestrictedNames;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use Purify;
|
||||
|
||||
class RemoteAuthController extends Controller
|
||||
{
|
||||
|
@ -30,9 +28,10 @@ class RemoteAuthController extends Controller
|
|||
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||
config('remote-auth.mastodon.enabled')
|
||||
), 404);
|
||||
if($request->user()) {
|
||||
if ($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
return view('auth.remote.start');
|
||||
}
|
||||
|
||||
|
@ -51,25 +50,27 @@ class RemoteAuthController extends Controller
|
|||
config('remote-auth.mastodon.enabled')
|
||||
), 404);
|
||||
|
||||
if(config('remote-auth.mastodon.domains.only_custom')) {
|
||||
if (config('remote-auth.mastodon.domains.only_custom')) {
|
||||
$res = config('remote-auth.mastodon.domains.custom');
|
||||
if(!$res || !strlen($res)) {
|
||||
if (! $res || ! strlen($res)) {
|
||||
return [];
|
||||
}
|
||||
$res = explode(',', $res);
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
if( config('remote-auth.mastodon.domains.custom') &&
|
||||
!config('remote-auth.mastodon.domains.only_default') &&
|
||||
if (config('remote-auth.mastodon.domains.custom') &&
|
||||
! config('remote-auth.mastodon.domains.only_default') &&
|
||||
strlen(config('remote-auth.mastodon.domains.custom')) > 3 &&
|
||||
strpos(config('remote-auth.mastodon.domains.custom'), '.') > -1
|
||||
) {
|
||||
$res = config('remote-auth.mastodon.domains.custom');
|
||||
if(!$res || !strlen($res)) {
|
||||
if (! $res || ! strlen($res)) {
|
||||
return [];
|
||||
}
|
||||
$res = explode(',', $res);
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
|
@ -93,57 +94,62 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$domain = $request->input('domain');
|
||||
|
||||
if(str_starts_with(strtolower($domain), 'http')) {
|
||||
if (str_starts_with(strtolower($domain), 'http')) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'incompatible_domain'
|
||||
'action' => 'incompatible_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
$validateInstance = Helpers::validateUrl('https://' . $domain . '/?block-check=' . time());
|
||||
$validateInstance = Helpers::validateUrl('https://'.$domain.'/?block-check='.time());
|
||||
|
||||
if(!$validateInstance) {
|
||||
$res = [
|
||||
if (! $validateInstance) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'blocked_domain'
|
||||
'action' => 'blocked_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
$compatible = RemoteAuthService::isDomainCompatible($domain);
|
||||
|
||||
if(!$compatible) {
|
||||
if (! $compatible) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'incompatible_domain'
|
||||
'action' => 'incompatible_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
if(config('remote-auth.mastodon.domains.only_default')) {
|
||||
if (config('remote-auth.mastodon.domains.only_default')) {
|
||||
$defaultDomains = explode(',', config('remote-auth.mastodon.domains.default'));
|
||||
if(!in_array($domain, $defaultDomains)) {
|
||||
if (! in_array($domain, $defaultDomains)) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'incompatible_domain'
|
||||
'action' => 'incompatible_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
|
||||
if(config('remote-auth.mastodon.domains.only_custom') && config('remote-auth.mastodon.domains.custom')) {
|
||||
if (config('remote-auth.mastodon.domains.only_custom') && config('remote-auth.mastodon.domains.custom')) {
|
||||
$customDomains = explode(',', config('remote-auth.mastodon.domains.custom'));
|
||||
if(!in_array($domain, $customDomains)) {
|
||||
if (! in_array($domain, $customDomains)) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'incompatible_domain'
|
||||
'action' => 'incompatible_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
|
@ -163,13 +169,13 @@ class RemoteAuthController extends Controller
|
|||
'state' => $state,
|
||||
]);
|
||||
|
||||
$request->session()->put('oauth_redirect_to', 'https://' . $domain . '/oauth/authorize?' . $query);
|
||||
$request->session()->put('oauth_redirect_to', 'https://'.$domain.'/oauth/authorize?'.$query);
|
||||
|
||||
$dsh = Str::random(17);
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => true,
|
||||
'dsh' => $dsh
|
||||
'dsh' => $dsh,
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
|
@ -185,7 +191,7 @@ class RemoteAuthController extends Controller
|
|||
config('remote-auth.mastodon.enabled')
|
||||
), 404);
|
||||
|
||||
if(!$request->filled('d') || !$request->filled('dsh') || !$request->session()->exists('oauth_redirect_to')) {
|
||||
if (! $request->filled('d') || ! $request->filled('dsh') || ! $request->session()->exists('oauth_redirect_to')) {
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
|
@ -204,7 +210,7 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$domain = $request->session()->get('oauth_domain');
|
||||
|
||||
if($request->filled('code')) {
|
||||
if ($request->filled('code')) {
|
||||
$code = $request->input('code');
|
||||
$state = $request->session()->pull('state');
|
||||
|
||||
|
@ -216,12 +222,14 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$res = RemoteAuthService::getToken($domain, $code);
|
||||
|
||||
if(!$res || !isset($res['access_token'])) {
|
||||
if (! $res || ! isset($res['access_token'])) {
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
$request->session()->put('oauth_remote_session_token', $res['access_token']);
|
||||
|
||||
return redirect('/auth/mastodon/getting-started');
|
||||
}
|
||||
|
||||
|
@ -237,9 +245,10 @@ class RemoteAuthController extends Controller
|
|||
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||
config('remote-auth.mastodon.enabled')
|
||||
), 404);
|
||||
if($request->user()) {
|
||||
if ($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
return view('auth.remote.onboarding');
|
||||
}
|
||||
|
||||
|
@ -261,36 +270,36 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
||||
|
||||
abort_if(!$res || !isset($res['acct']), 403, 'Invalid credentials');
|
||||
abort_if(! $res || ! isset($res['acct']), 403, 'Invalid credentials');
|
||||
|
||||
$webfinger = strtolower('@' . $res['acct'] . '@' . $domain);
|
||||
$webfinger = strtolower('@'.$res['acct'].'@'.$domain);
|
||||
$request->session()->put('oauth_masto_webfinger', $webfinger);
|
||||
|
||||
if(config('remote-auth.mastodon.max_uses.enabled')) {
|
||||
if (config('remote-auth.mastodon.max_uses.enabled')) {
|
||||
$limit = config('remote-auth.mastodon.max_uses.limit');
|
||||
$uses = RemoteAuthService::lookupWebfingerUses($webfinger);
|
||||
if($uses >= $limit) {
|
||||
if ($uses >= $limit) {
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'msg' => 'Success!',
|
||||
'action' => 'max_uses_reached'
|
||||
'action' => 'max_uses_reached',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$exists = RemoteAuth::whereDomain($domain)->where('webfinger', $webfinger)->whereNotNull('user_id')->first();
|
||||
if($exists && $exists->user_id) {
|
||||
if ($exists && $exists->user_id) {
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'msg' => 'Success!',
|
||||
'action' => 'redirect_existing_user'
|
||||
'action' => 'redirect_existing_user',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'msg' => 'Success!',
|
||||
'action' => 'onboard'
|
||||
'action' => 'onboard',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -311,7 +320,7 @@ class RemoteAuthController extends Controller
|
|||
$token = $request->session()->get('oauth_remote_session_token');
|
||||
|
||||
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
||||
$res['_webfinger'] = strtolower('@' . $res['acct'] . '@' . $domain);
|
||||
$res['_webfinger'] = strtolower('@'.$res['acct'].'@'.$domain);
|
||||
$res['_domain'] = strtolower($domain);
|
||||
$request->session()->put('oauth_remasto_id', $res['id']);
|
||||
|
||||
|
@ -324,7 +333,7 @@ class RemoteAuthController extends Controller
|
|||
'bearer_token' => $token,
|
||||
'verify_credentials' => $res,
|
||||
'last_verify_credentials_at' => now(),
|
||||
'last_successful_login_at' => now()
|
||||
'last_successful_login_at' => now(),
|
||||
]);
|
||||
|
||||
$request->session()->put('oauth_masto_raid', $ra->id);
|
||||
|
@ -355,24 +364,24 @@ class RemoteAuthController extends Controller
|
|||
$underscore = substr_count($value, '_');
|
||||
$period = substr_count($value, '.');
|
||||
|
||||
if(ends_with($value, ['.php', '.js', '.css'])) {
|
||||
if (ends_with($value, ['.php', '.js', '.css'])) {
|
||||
return $fail('Username is invalid.');
|
||||
}
|
||||
|
||||
if(($dash + $underscore + $period) > 1) {
|
||||
if (($dash + $underscore + $period) > 1) {
|
||||
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[0])) {
|
||||
if (! ctype_alnum($value[0])) {
|
||||
return $fail('Username is invalid. Must start with a letter or number.');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[strlen($value) - 1])) {
|
||||
if (! ctype_alnum($value[strlen($value) - 1])) {
|
||||
return $fail('Username is invalid. Must end with a letter or number.');
|
||||
}
|
||||
|
||||
$val = str_replace(['_', '.', '-'], '', $value);
|
||||
if(!ctype_alnum($val)) {
|
||||
if (! ctype_alnum($val)) {
|
||||
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||
}
|
||||
|
||||
|
@ -380,8 +389,8 @@ class RemoteAuthController extends Controller
|
|||
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||
return $fail('Username cannot be used.');
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
]);
|
||||
$username = strtolower($request->input('username'));
|
||||
|
||||
|
@ -390,7 +399,7 @@ class RemoteAuthController extends Controller
|
|||
return response()->json([
|
||||
'code' => 200,
|
||||
'username' => $username,
|
||||
'exists' => $exists
|
||||
'exists' => $exists,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -411,7 +420,7 @@ class RemoteAuthController extends Controller
|
|||
'email' => [
|
||||
'required',
|
||||
'email:strict,filter_unicode,dns,spoof',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$email = $request->input('email');
|
||||
|
@ -422,7 +431,7 @@ class RemoteAuthController extends Controller
|
|||
'code' => 200,
|
||||
'email' => $email,
|
||||
'exists' => $exists,
|
||||
'banned' => $banned
|
||||
'banned' => $banned,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -445,18 +454,18 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$res = RemoteAuthService::getFollowing($domain, $token, $id);
|
||||
|
||||
if(!$res) {
|
||||
if (! $res) {
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'following' => []
|
||||
'following' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
$res = collect($res)->filter(fn($acct) => Helpers::validateUrl($acct['url']))->values()->toArray();
|
||||
$res = collect($res)->filter(fn ($acct) => Helpers::validateUrl($acct['url']))->values()->toArray();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'following' => $res
|
||||
'following' => $res,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -487,24 +496,24 @@ class RemoteAuthController extends Controller
|
|||
$underscore = substr_count($value, '_');
|
||||
$period = substr_count($value, '.');
|
||||
|
||||
if(ends_with($value, ['.php', '.js', '.css'])) {
|
||||
if (ends_with($value, ['.php', '.js', '.css'])) {
|
||||
return $fail('Username is invalid.');
|
||||
}
|
||||
|
||||
if(($dash + $underscore + $period) > 1) {
|
||||
if (($dash + $underscore + $period) > 1) {
|
||||
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[0])) {
|
||||
if (! ctype_alnum($value[0])) {
|
||||
return $fail('Username is invalid. Must start with a letter or number.');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[strlen($value) - 1])) {
|
||||
if (! ctype_alnum($value[strlen($value) - 1])) {
|
||||
return $fail('Username is invalid. Must end with a letter or number.');
|
||||
}
|
||||
|
||||
$val = str_replace(['_', '.', '-'], '', $value);
|
||||
if(!ctype_alnum($val)) {
|
||||
if (! ctype_alnum($val)) {
|
||||
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||
}
|
||||
|
||||
|
@ -512,10 +521,10 @@ class RemoteAuthController extends Controller
|
|||
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||
return $fail('Username cannot be used.');
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
'name' => 'nullable|max:30'
|
||||
'name' => 'nullable|max:30',
|
||||
]);
|
||||
|
||||
$email = $request->input('email');
|
||||
|
@ -527,7 +536,7 @@ class RemoteAuthController extends Controller
|
|||
'name' => $name,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'email' => $email
|
||||
'email' => $email,
|
||||
]);
|
||||
|
||||
$raid = $request->session()->pull('oauth_masto_raid');
|
||||
|
@ -541,7 +550,7 @@ class RemoteAuthController extends Controller
|
|||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Success',
|
||||
'token' => $token
|
||||
'token' => $token,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -585,7 +594,7 @@ class RemoteAuthController extends Controller
|
|||
abort_unless($request->session()->exists('oauth_remasto_id'), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'account' => 'required|url'
|
||||
'account' => 'required|url',
|
||||
]);
|
||||
|
||||
$account = $request->input('account');
|
||||
|
@ -594,10 +603,10 @@ class RemoteAuthController extends Controller
|
|||
$host = strtolower(config('pixelfed.domain.app'));
|
||||
$domain = strtolower(parse_url($account, PHP_URL_HOST));
|
||||
|
||||
if($domain == $host) {
|
||||
if ($domain == $host) {
|
||||
$username = Str::of($account)->explode('/')->last();
|
||||
$user = User::where('username', $username)->first();
|
||||
if($user) {
|
||||
if ($user) {
|
||||
return ['id' => (string) $user->profile_id];
|
||||
} else {
|
||||
return [];
|
||||
|
@ -605,7 +614,7 @@ class RemoteAuthController extends Controller
|
|||
} else {
|
||||
try {
|
||||
$profile = Helpers::profileFetch($account);
|
||||
if($profile) {
|
||||
if ($profile) {
|
||||
return ['id' => (string) $profile->id];
|
||||
} else {
|
||||
return [];
|
||||
|
@ -635,13 +644,13 @@ class RemoteAuthController extends Controller
|
|||
$user = $request->user();
|
||||
$profile = $user->profile;
|
||||
|
||||
abort_if(!$profile->avatar, 404, 'Missing avatar');
|
||||
abort_if(! $profile->avatar, 404, 'Missing avatar');
|
||||
|
||||
$avatar = $profile->avatar;
|
||||
$avatar->remote_url = $request->input('avatar_url');
|
||||
$avatar->save();
|
||||
|
||||
MediaStorageService::avatar($avatar, config_cache('pixelfed.cloud_storage') == false);
|
||||
MediaStorageService::avatar($avatar, (bool) config_cache('pixelfed.cloud_storage') == false);
|
||||
|
||||
return [200];
|
||||
}
|
||||
|
@ -657,7 +666,7 @@ class RemoteAuthController extends Controller
|
|||
), 404);
|
||||
abort_unless($request->user(), 404);
|
||||
|
||||
$currentWebfinger = '@' . $request->user()->username . '@' . config('pixelfed.domain.app');
|
||||
$currentWebfinger = '@'.$request->user()->username.'@'.config('pixelfed.domain.app');
|
||||
$ra = RemoteAuth::where('user_id', $request->user()->id)->firstOrFail();
|
||||
RemoteAuthService::submitToBeagle(
|
||||
$ra->webfinger,
|
||||
|
@ -691,19 +700,20 @@ class RemoteAuthController extends Controller
|
|||
$user = User::findOrFail($ra->user_id);
|
||||
abort_if($user->is_admin || $user->status != null, 422, 'Invalid auth action');
|
||||
Auth::loginUsingId($ra->user_id);
|
||||
|
||||
return [200];
|
||||
}
|
||||
|
||||
protected function createUser($data)
|
||||
{
|
||||
event(new Registered($user = User::create([
|
||||
'name' => Purify::clean($data['name']),
|
||||
'name' => Purify::clean($data['name']),
|
||||
'username' => $data['username'],
|
||||
'email' => $data['email'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'email_verified_at' => config('remote-auth.mastodon.contraints.skip_email_verification') ? now() : null,
|
||||
'app_register_ip' => request()->ip(),
|
||||
'register_source' => 'mastodon'
|
||||
'register_source' => 'mastodon',
|
||||
])));
|
||||
|
||||
$this->guarder()->login($user);
|
||||
|
|
|
@ -2,368 +2,367 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use App\Hashtag;
|
||||
use App\Place;
|
||||
use App\Profile;
|
||||
use App\Services\WebfingerService;
|
||||
use App\Status;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Transformer\Api\{
|
||||
AccountTransformer,
|
||||
HashtagTransformer,
|
||||
StatusTransformer,
|
||||
};
|
||||
use App\Services\WebfingerService;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
public $tokens = [];
|
||||
public $term = '';
|
||||
public $hash = '';
|
||||
public $cacheKey = 'api:search:tag:';
|
||||
public $tokens = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
public $term = '';
|
||||
|
||||
public function searchAPI(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|string|min:3|max:120',
|
||||
'src' => 'required|string|in:metro',
|
||||
'v' => 'required|integer|in:2',
|
||||
'scope' => 'required|in:all,hashtag,profile,remote,webfinger'
|
||||
]);
|
||||
public $hash = '';
|
||||
|
||||
$scope = $request->input('scope') ?? 'all';
|
||||
$this->term = e(urldecode($request->input('q')));
|
||||
$this->hash = hash('sha256', $this->term);
|
||||
public $cacheKey = 'api:search:tag:';
|
||||
|
||||
switch ($scope) {
|
||||
case 'all':
|
||||
$this->getHashtags();
|
||||
$this->getPosts();
|
||||
$this->getProfiles();
|
||||
// $this->getPlaces();
|
||||
break;
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
case 'hashtag':
|
||||
$this->getHashtags();
|
||||
break;
|
||||
public function searchAPI(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|string|min:3|max:120',
|
||||
'src' => 'required|string|in:metro',
|
||||
'v' => 'required|integer|in:2',
|
||||
'scope' => 'required|in:all,hashtag,profile,remote,webfinger',
|
||||
]);
|
||||
|
||||
case 'profile':
|
||||
$this->getProfiles();
|
||||
break;
|
||||
$scope = $request->input('scope') ?? 'all';
|
||||
$this->term = e(urldecode($request->input('q')));
|
||||
$this->hash = hash('sha256', $this->term);
|
||||
|
||||
case 'webfinger':
|
||||
$this->webfingerSearch();
|
||||
break;
|
||||
switch ($scope) {
|
||||
case 'all':
|
||||
$this->getHashtags();
|
||||
$this->getPosts();
|
||||
$this->getProfiles();
|
||||
// $this->getPlaces();
|
||||
break;
|
||||
|
||||
case 'remote':
|
||||
$this->remoteLookupSearch();
|
||||
break;
|
||||
case 'hashtag':
|
||||
$this->getHashtags();
|
||||
break;
|
||||
|
||||
case 'place':
|
||||
$this->getPlaces();
|
||||
break;
|
||||
case 'profile':
|
||||
$this->getProfiles();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case 'webfinger':
|
||||
$this->webfingerSearch();
|
||||
break;
|
||||
|
||||
return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
case 'remote':
|
||||
$this->remoteLookupSearch();
|
||||
break;
|
||||
|
||||
protected function getPosts()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$hash = hash('sha256', $tag);
|
||||
if( Helpers::validateUrl($tag) != false &&
|
||||
Helpers::validateLocalUrl($tag) != true &&
|
||||
config_cache('federation.activitypub.enabled') == true &&
|
||||
config('federation.activitypub.remoteFollow') == true
|
||||
) {
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
if( isset($remote['type']) &&
|
||||
$remote['type'] == 'Note') {
|
||||
$item = Helpers::statusFetch($tag);
|
||||
$this->tokens['posts'] = [[
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'status',
|
||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'tokens' => [$item->caption],
|
||||
'name' => $item->caption,
|
||||
'thumb' => $item->thumb(),
|
||||
]];
|
||||
}
|
||||
} else {
|
||||
$posts = Status::select('id', 'profile_id', 'caption', 'created_at')
|
||||
->whereHas('media')
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id')
|
||||
->whereProfileId(Auth::user()->profile_id)
|
||||
->where('caption', 'like', '%'.$tag.'%')
|
||||
->latest()
|
||||
->limit(10)
|
||||
->get();
|
||||
case 'place':
|
||||
$this->getPlaces();
|
||||
break;
|
||||
|
||||
if($posts->count() > 0) {
|
||||
$posts = $posts->map(function($item, $key) {
|
||||
return [
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'status',
|
||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'tokens' => [$item->caption],
|
||||
'name' => $item->caption,
|
||||
'thumb' => $item->thumb(),
|
||||
'filter' => $item->firstMedia()->filter_class
|
||||
];
|
||||
});
|
||||
$this->tokens['posts'] = $posts;
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
protected function getHashtags()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$key = $this->cacheKey . 'hashtags:' . $this->hash;
|
||||
$ttl = now()->addMinutes(1);
|
||||
$tokens = Cache::remember($key, $ttl, function() use($tag) {
|
||||
$htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag;
|
||||
$hashtags = Hashtag::select('id', 'name', 'slug')
|
||||
->where('slug', 'like', '%'.$htag.'%')
|
||||
->whereHas('posts')
|
||||
->limit(20)
|
||||
->get();
|
||||
if($hashtags->count() > 0) {
|
||||
$tags = $hashtags->map(function ($item, $key) {
|
||||
return [
|
||||
'count' => $item->posts()->count(),
|
||||
'url' => $item->url(),
|
||||
'type' => 'hashtag',
|
||||
'value' => $item->name,
|
||||
'tokens' => '',
|
||||
'name' => null,
|
||||
];
|
||||
});
|
||||
return $tags;
|
||||
}
|
||||
});
|
||||
$this->tokens['hashtags'] = $tokens;
|
||||
}
|
||||
return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
protected function getPlaces()
|
||||
{
|
||||
$tag = $this->term;
|
||||
// $key = $this->cacheKey . 'places:' . $this->hash;
|
||||
// $ttl = now()->addHours(12);
|
||||
// $tokens = Cache::remember($key, $ttl, function() use($tag) {
|
||||
$htag = Str::contains($tag, ',') == true ? explode(',', $tag) : [$tag];
|
||||
$hashtags = Place::select('id', 'name', 'slug', 'country')
|
||||
->where('name', 'like', '%'.$htag[0].'%')
|
||||
->paginate(20);
|
||||
$tags = [];
|
||||
if($hashtags->count() > 0) {
|
||||
$tags = $hashtags->map(function ($item, $key) {
|
||||
return [
|
||||
'count' => null,
|
||||
'url' => $item->url(),
|
||||
'type' => 'place',
|
||||
'value' => $item->name . ', ' . $item->country,
|
||||
'tokens' => '',
|
||||
'name' => null,
|
||||
'city' => $item->name,
|
||||
'country' => $item->country
|
||||
];
|
||||
});
|
||||
// return $tags;
|
||||
}
|
||||
// });
|
||||
$this->tokens['places'] = $tags;
|
||||
$this->tokens['placesPagination'] = [
|
||||
'total' => $hashtags->total(),
|
||||
'current_page' => $hashtags->currentPage(),
|
||||
'last_page' => $hashtags->lastPage()
|
||||
];
|
||||
}
|
||||
protected function getPosts()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$hash = hash('sha256', $tag);
|
||||
if (Helpers::validateUrl($tag) != false &&
|
||||
Helpers::validateLocalUrl($tag) != true &&
|
||||
(bool) config_cache('federation.activitypub.enabled') == true &&
|
||||
config('federation.activitypub.remoteFollow') == true
|
||||
) {
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
if (isset($remote['type']) &&
|
||||
in_array($remote['type'], ['Note', 'Question'])
|
||||
) {
|
||||
$item = Helpers::statusFetch($tag);
|
||||
$this->tokens['posts'] = [[
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'status',
|
||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'tokens' => [$item->caption],
|
||||
'name' => $item->caption,
|
||||
'thumb' => $item->thumb(),
|
||||
]];
|
||||
}
|
||||
} else {
|
||||
$posts = Status::select('id', 'profile_id', 'caption', 'created_at')
|
||||
->whereHas('media')
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id')
|
||||
->whereProfileId(Auth::user()->profile_id)
|
||||
->where('caption', 'like', '%'.$tag.'%')
|
||||
->latest()
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
protected function getProfiles()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$remoteKey = $this->cacheKey . 'profiles:remote:' . $this->hash;
|
||||
$key = $this->cacheKey . 'profiles:' . $this->hash;
|
||||
$remoteTtl = now()->addMinutes(15);
|
||||
$ttl = now()->addHours(2);
|
||||
if( Helpers::validateUrl($tag) != false &&
|
||||
Helpers::validateLocalUrl($tag) != true &&
|
||||
config_cache('federation.activitypub.enabled') == true &&
|
||||
config('federation.activitypub.remoteFollow') == true
|
||||
) {
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
if( isset($remote['type']) &&
|
||||
$remote['type'] == 'Person'
|
||||
) {
|
||||
$this->tokens['profiles'] = Cache::remember($remoteKey, $remoteTtl, function() use($tag) {
|
||||
$item = Helpers::profileFirstOrNew($tag);
|
||||
$tokens = [[
|
||||
'count' => 1,
|
||||
'url' => $item->url(),
|
||||
'type' => 'profile',
|
||||
'value' => $item->username,
|
||||
'tokens' => [$item->username],
|
||||
'name' => $item->name,
|
||||
'entity' => [
|
||||
'id' => (string) $item->id,
|
||||
'following' => $item->followedBy(Auth::user()->profile),
|
||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
||||
'thumb' => $item->avatarUrl(),
|
||||
'local' => (bool) !$item->domain,
|
||||
'post_count' => $item->statuses()->count()
|
||||
]
|
||||
]];
|
||||
return $tokens;
|
||||
});
|
||||
}
|
||||
}
|
||||
if ($posts->count() > 0) {
|
||||
$posts = $posts->map(function ($item, $key) {
|
||||
return [
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'status',
|
||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'tokens' => [$item->caption],
|
||||
'name' => $item->caption,
|
||||
'thumb' => $item->thumb(),
|
||||
'filter' => $item->firstMedia()->filter_class,
|
||||
];
|
||||
});
|
||||
$this->tokens['posts'] = $posts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
$this->tokens['profiles'] = Cache::remember($key, $ttl, function() use($tag) {
|
||||
if(Str::startsWith($tag, '@')) {
|
||||
$tag = substr($tag, 1);
|
||||
}
|
||||
$users = Profile::select('status', 'domain', 'username', 'name', 'id')
|
||||
->whereNull('status')
|
||||
->where('username', 'like', '%'.$tag.'%')
|
||||
->limit(20)
|
||||
->orderBy('domain')
|
||||
->get();
|
||||
protected function getHashtags()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$key = $this->cacheKey.'hashtags:'.$this->hash;
|
||||
$ttl = now()->addMinutes(1);
|
||||
$tokens = Cache::remember($key, $ttl, function () use ($tag) {
|
||||
$htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag;
|
||||
$hashtags = Hashtag::select('id', 'name', 'slug')
|
||||
->where('slug', 'like', '%'.$htag.'%')
|
||||
->whereHas('posts')
|
||||
->limit(20)
|
||||
->get();
|
||||
if ($hashtags->count() > 0) {
|
||||
$tags = $hashtags->map(function ($item, $key) {
|
||||
return [
|
||||
'count' => $item->posts()->count(),
|
||||
'url' => $item->url(),
|
||||
'type' => 'hashtag',
|
||||
'value' => $item->name,
|
||||
'tokens' => '',
|
||||
'name' => null,
|
||||
];
|
||||
});
|
||||
|
||||
if($users->count() > 0) {
|
||||
return $users->map(function ($item, $key) {
|
||||
return [
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'profile',
|
||||
'value' => $item->username,
|
||||
'tokens' => [$item->username],
|
||||
'name' => $item->name,
|
||||
'avatar' => $item->avatarUrl(),
|
||||
'id' => (string) $item->id,
|
||||
'entity' => [
|
||||
'id' => (string) $item->id,
|
||||
'following' => $item->followedBy(Auth::user()->profile),
|
||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
||||
'thumb' => $item->avatarUrl(),
|
||||
'local' => (bool) !$item->domain,
|
||||
'post_count' => $item->statuses()->count()
|
||||
]
|
||||
];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
});
|
||||
$this->tokens['hashtags'] = $tokens;
|
||||
}
|
||||
|
||||
public function results(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|string|min:1',
|
||||
]);
|
||||
protected function getPlaces()
|
||||
{
|
||||
$tag = $this->term;
|
||||
// $key = $this->cacheKey . 'places:' . $this->hash;
|
||||
// $ttl = now()->addHours(12);
|
||||
// $tokens = Cache::remember($key, $ttl, function() use($tag) {
|
||||
$htag = Str::contains($tag, ',') == true ? explode(',', $tag) : [$tag];
|
||||
$hashtags = Place::select('id', 'name', 'slug', 'country')
|
||||
->where('name', 'like', '%'.$htag[0].'%')
|
||||
->paginate(20);
|
||||
$tags = [];
|
||||
if ($hashtags->count() > 0) {
|
||||
$tags = $hashtags->map(function ($item, $key) {
|
||||
return [
|
||||
'count' => null,
|
||||
'url' => $item->url(),
|
||||
'type' => 'place',
|
||||
'value' => $item->name.', '.$item->country,
|
||||
'tokens' => '',
|
||||
'name' => null,
|
||||
'city' => $item->name,
|
||||
'country' => $item->country,
|
||||
];
|
||||
});
|
||||
// return $tags;
|
||||
}
|
||||
// });
|
||||
$this->tokens['places'] = $tags;
|
||||
$this->tokens['placesPagination'] = [
|
||||
'total' => $hashtags->total(),
|
||||
'current_page' => $hashtags->currentPage(),
|
||||
'last_page' => $hashtags->lastPage(),
|
||||
];
|
||||
}
|
||||
|
||||
return view('search.results');
|
||||
}
|
||||
protected function getProfiles()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$remoteKey = $this->cacheKey.'profiles:remote:'.$this->hash;
|
||||
$key = $this->cacheKey.'profiles:'.$this->hash;
|
||||
$remoteTtl = now()->addMinutes(15);
|
||||
$ttl = now()->addHours(2);
|
||||
if (Helpers::validateUrl($tag) != false &&
|
||||
Helpers::validateLocalUrl($tag) != true &&
|
||||
(bool) config_cache('federation.activitypub.enabled') == true &&
|
||||
config('federation.activitypub.remoteFollow') == true
|
||||
) {
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
if (isset($remote['type']) &&
|
||||
$remote['type'] == 'Person'
|
||||
) {
|
||||
$this->tokens['profiles'] = Cache::remember($remoteKey, $remoteTtl, function () use ($tag) {
|
||||
$item = Helpers::profileFirstOrNew($tag);
|
||||
$tokens = [[
|
||||
'count' => 1,
|
||||
'url' => $item->url(),
|
||||
'type' => 'profile',
|
||||
'value' => $item->username,
|
||||
'tokens' => [$item->username],
|
||||
'name' => $item->name,
|
||||
'entity' => [
|
||||
'id' => (string) $item->id,
|
||||
'following' => $item->followedBy(Auth::user()->profile),
|
||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
||||
'thumb' => $item->avatarUrl(),
|
||||
'local' => (bool) ! $item->domain,
|
||||
'post_count' => $item->statuses()->count(),
|
||||
],
|
||||
]];
|
||||
|
||||
protected function webfingerSearch()
|
||||
{
|
||||
$wfs = WebfingerService::lookup($this->term);
|
||||
return $tokens;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$this->tokens['profiles'] = Cache::remember($key, $ttl, function () use ($tag) {
|
||||
if (Str::startsWith($tag, '@')) {
|
||||
$tag = substr($tag, 1);
|
||||
}
|
||||
$users = Profile::select('status', 'domain', 'username', 'name', 'id')
|
||||
->whereNull('status')
|
||||
->where('username', 'like', '%'.$tag.'%')
|
||||
->limit(20)
|
||||
->orderBy('domain')
|
||||
->get();
|
||||
|
||||
if(empty($wfs)) {
|
||||
return;
|
||||
}
|
||||
if ($users->count() > 0) {
|
||||
return $users->map(function ($item, $key) {
|
||||
return [
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'profile',
|
||||
'value' => $item->username,
|
||||
'tokens' => [$item->username],
|
||||
'name' => $item->name,
|
||||
'avatar' => $item->avatarUrl(),
|
||||
'id' => (string) $item->id,
|
||||
'entity' => [
|
||||
'id' => (string) $item->id,
|
||||
'following' => $item->followedBy(Auth::user()->profile),
|
||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
||||
'thumb' => $item->avatarUrl(),
|
||||
'local' => (bool) ! $item->domain,
|
||||
'post_count' => $item->statuses()->count(),
|
||||
],
|
||||
];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$this->tokens['profiles'] = [
|
||||
[
|
||||
'count' => 1,
|
||||
'url' => $wfs['url'],
|
||||
'type' => 'profile',
|
||||
'value' => $wfs['username'],
|
||||
'tokens' => [$wfs['username']],
|
||||
'name' => $wfs['display_name'],
|
||||
'entity' => [
|
||||
'id' => (string) $wfs['id'],
|
||||
'following' => null,
|
||||
'follow_request' => null,
|
||||
'thumb' => $wfs['avatar'],
|
||||
'local' => (bool) $wfs['local']
|
||||
]
|
||||
]
|
||||
];
|
||||
return;
|
||||
}
|
||||
public function results(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|string|min:1',
|
||||
]);
|
||||
|
||||
protected function remotePostLookup()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$hash = hash('sha256', $tag);
|
||||
$local = Helpers::validateLocalUrl($tag);
|
||||
$valid = Helpers::validateUrl($tag);
|
||||
return view('search.results');
|
||||
}
|
||||
|
||||
if($valid == false || $local == true) {
|
||||
return;
|
||||
}
|
||||
protected function webfingerSearch()
|
||||
{
|
||||
$wfs = WebfingerService::lookup($this->term);
|
||||
|
||||
if(Status::whereUri($tag)->whereLocal(false)->exists()) {
|
||||
$item = Status::whereUri($tag)->first();
|
||||
$media = $item->firstMedia();
|
||||
$url = null;
|
||||
if($media) {
|
||||
$url = $media->remote_url;
|
||||
}
|
||||
$this->tokens['posts'] = [[
|
||||
'count' => 0,
|
||||
'url' => "/i/web/post/_/$item->profile_id/$item->id",
|
||||
'type' => 'status',
|
||||
'username' => $item->profile->username,
|
||||
'caption' => $item->rendered ?? $item->caption,
|
||||
'thumb' => $url,
|
||||
'timestamp' => $item->created_at->diffForHumans()
|
||||
]];
|
||||
}
|
||||
if (empty($wfs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
$this->tokens['profiles'] = [
|
||||
[
|
||||
'count' => 1,
|
||||
'url' => $wfs['url'],
|
||||
'type' => 'profile',
|
||||
'value' => $wfs['username'],
|
||||
'tokens' => [$wfs['username']],
|
||||
'name' => $wfs['display_name'],
|
||||
'entity' => [
|
||||
'id' => (string) $wfs['id'],
|
||||
'following' => null,
|
||||
'follow_request' => null,
|
||||
'thumb' => $wfs['avatar'],
|
||||
'local' => (bool) $wfs['local'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if(isset($remote['type']) && $remote['type'] == 'Note') {
|
||||
$item = Helpers::statusFetch($tag);
|
||||
$media = $item->firstMedia();
|
||||
$url = null;
|
||||
if($media) {
|
||||
$url = $media->remote_url;
|
||||
}
|
||||
$this->tokens['posts'] = [[
|
||||
'count' => 0,
|
||||
'url' => "/i/web/post/_/$item->profile_id/$item->id",
|
||||
'type' => 'status',
|
||||
'username' => $item->profile->username,
|
||||
'caption' => $item->rendered ?? $item->caption,
|
||||
'thumb' => $url,
|
||||
'timestamp' => $item->created_at->diffForHumans()
|
||||
]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function remoteLookupSearch()
|
||||
{
|
||||
if(!Helpers::validateUrl($this->term)) {
|
||||
return;
|
||||
}
|
||||
$this->getProfiles();
|
||||
$this->remotePostLookup();
|
||||
}
|
||||
protected function remotePostLookup()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$hash = hash('sha256', $tag);
|
||||
$local = Helpers::validateLocalUrl($tag);
|
||||
$valid = Helpers::validateUrl($tag);
|
||||
|
||||
if ($valid == false || $local == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Status::whereUri($tag)->whereLocal(false)->exists()) {
|
||||
$item = Status::whereUri($tag)->first();
|
||||
$media = $item->firstMedia();
|
||||
$url = null;
|
||||
if ($media) {
|
||||
$url = $media->remote_url;
|
||||
}
|
||||
$this->tokens['posts'] = [[
|
||||
'count' => 0,
|
||||
'url' => "/i/web/post/_/$item->profile_id/$item->id",
|
||||
'type' => 'status',
|
||||
'username' => $item->profile->username,
|
||||
'caption' => $item->rendered ?? $item->caption,
|
||||
'thumb' => $url,
|
||||
'timestamp' => $item->created_at->diffForHumans(),
|
||||
]];
|
||||
}
|
||||
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
|
||||
if (isset($remote['type']) && $remote['type'] == 'Note') {
|
||||
$item = Helpers::statusFetch($tag);
|
||||
$media = $item->firstMedia();
|
||||
$url = null;
|
||||
if ($media) {
|
||||
$url = $media->remote_url;
|
||||
}
|
||||
$this->tokens['posts'] = [[
|
||||
'count' => 0,
|
||||
'url' => "/i/web/post/_/$item->profile_id/$item->id",
|
||||
'type' => 'status',
|
||||
'username' => $item->profile->username,
|
||||
'caption' => $item->rendered ?? $item->caption,
|
||||
'thumb' => $url,
|
||||
'timestamp' => $item->created_at->diffForHumans(),
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
protected function remoteLookupSearch()
|
||||
{
|
||||
if (! Helpers::validateUrl($this->term)) {
|
||||
return;
|
||||
}
|
||||
$this->getProfiles();
|
||||
$this->remotePostLookup();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,21 +4,17 @@ namespace App\Http\Controllers\Settings;
|
|||
|
||||
use App\AccountLog;
|
||||
use App\EmailVerification;
|
||||
use App\Mail\PasswordChange;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\User;
|
||||
use App\UserFilter;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\PronounService;
|
||||
use App\Util\Lexer\Autolink;
|
||||
use App\Util\Lexer\PrettyNumber;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use DB;
|
||||
use Illuminate\Http\Request;
|
||||
use Mail;
|
||||
use Purify;
|
||||
use App\Mail\PasswordChange;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\PronounService;
|
||||
|
||||
trait HomeSettings
|
||||
{
|
||||
|
@ -40,11 +36,11 @@ trait HomeSettings
|
|||
public function homeUpdate(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
|
||||
'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'),
|
||||
'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'),
|
||||
'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'),
|
||||
'website' => 'nullable|url',
|
||||
'language' => 'nullable|string|min:2|max:5',
|
||||
'pronouns' => 'nullable|array|max:4'
|
||||
'pronouns' => 'nullable|array|max:4',
|
||||
]);
|
||||
|
||||
$changes = false;
|
||||
|
@ -57,14 +53,14 @@ trait HomeSettings
|
|||
$pronouns = $request->input('pronouns');
|
||||
$existingPronouns = PronounService::get($profile->id);
|
||||
$layout = $request->input('profile_layout');
|
||||
if($layout) {
|
||||
$layout = !in_array($layout, ['metro', 'moment']) ? 'metro' : $layout;
|
||||
if ($layout) {
|
||||
$layout = ! in_array($layout, ['metro', 'moment']) ? 'metro' : $layout;
|
||||
}
|
||||
|
||||
$enforceEmailVerification = config_cache('pixelfed.enforce_email_verification');
|
||||
|
||||
// Only allow email to be updated if not yet verified
|
||||
if (!$enforceEmailVerification || !$changes && $user->email_verified_at) {
|
||||
if (! $enforceEmailVerification || ! $changes && $user->email_verified_at) {
|
||||
if ($profile->name != $name) {
|
||||
$changes = true;
|
||||
$user->name = $name;
|
||||
|
@ -81,7 +77,7 @@ trait HomeSettings
|
|||
$profile->bio = Autolink::create()->autolink($bio);
|
||||
}
|
||||
|
||||
if($user->language != $language &&
|
||||
if ($user->language != $language &&
|
||||
in_array($language, \App\Util\Localization\Localization::languages())
|
||||
) {
|
||||
$changes = true;
|
||||
|
@ -89,8 +85,8 @@ trait HomeSettings
|
|||
session()->put('locale', $language);
|
||||
}
|
||||
|
||||
if($existingPronouns != $pronouns) {
|
||||
if($pronouns && in_array('Select Pronoun(s)', $pronouns)) {
|
||||
if ($existingPronouns != $pronouns) {
|
||||
if ($pronouns && in_array('Select Pronoun(s)', $pronouns)) {
|
||||
PronounService::clear($profile->id);
|
||||
} else {
|
||||
PronounService::put($profile->id, $pronouns);
|
||||
|
@ -102,7 +98,9 @@ trait HomeSettings
|
|||
$user->save();
|
||||
$profile->save();
|
||||
Cache::forget('user:account:id:'.$user->id);
|
||||
AccountService::forgetAccountSettings($profile->id);
|
||||
AccountService::del($profile->id);
|
||||
|
||||
return redirect('/settings/home')->with('status', 'Profile successfully updated!');
|
||||
}
|
||||
|
||||
|
@ -117,10 +115,10 @@ trait HomeSettings
|
|||
public function passwordUpdate(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'current' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'password_confirmation' => 'required|string',
|
||||
]);
|
||||
'current' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'password_confirmation' => 'required|string',
|
||||
]);
|
||||
|
||||
$current = $request->input('current');
|
||||
$new = $request->input('password');
|
||||
|
@ -144,6 +142,7 @@ trait HomeSettings
|
|||
$log->save();
|
||||
|
||||
Mail::to($request->user())->send(new PasswordChange($user));
|
||||
|
||||
return redirect('/settings/home')->with('status', 'Password successfully updated!');
|
||||
} else {
|
||||
return redirect()->back()->with('error', 'There was an error with your request! Please try again.');
|
||||
|
@ -159,7 +158,7 @@ trait HomeSettings
|
|||
public function emailUpdate(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'email' => 'required|email|unique:users,email',
|
||||
]);
|
||||
$changes = false;
|
||||
$email = $request->input('email');
|
||||
|
|
|
@ -2,30 +2,31 @@
|
|||
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\AccountLog;
|
||||
use App\EmailVerification;
|
||||
use App\Instance;
|
||||
use App\Follower;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\User;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\RelationshipService;
|
||||
use App\UserFilter;
|
||||
use App\Util\Lexer\PrettyNumber;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Auth, Cache, DB;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use DB;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\UserDomainBlock;
|
||||
|
||||
trait PrivacySettings
|
||||
{
|
||||
|
||||
public function privacy()
|
||||
{
|
||||
$user = Auth::user();
|
||||
$settings = $user->settings;
|
||||
$profile = $user->profile;
|
||||
$is_private = $profile->is_private;
|
||||
$cachedSettings = AccountService::getAccountSettings($profile->id);
|
||||
$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'));
|
||||
}
|
||||
|
@ -34,20 +35,31 @@ trait PrivacySettings
|
|||
{
|
||||
$settings = $request->user()->settings;
|
||||
$profile = $request->user()->profile;
|
||||
$other = $settings->other;
|
||||
$fields = [
|
||||
'is_private',
|
||||
'crawlable',
|
||||
'public_dm',
|
||||
'show_profile_follower_count',
|
||||
'show_profile_following_count',
|
||||
'indexable',
|
||||
'show_atom',
|
||||
'is_private',
|
||||
'crawlable',
|
||||
'public_dm',
|
||||
'show_profile_follower_count',
|
||||
'show_profile_following_count',
|
||||
'indexable',
|
||||
'show_atom',
|
||||
];
|
||||
|
||||
$profile->indexable = $request->input('indexable') == 'on';
|
||||
$profile->is_suggestable = $request->input('is_suggestable') == 'on';
|
||||
$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) {
|
||||
$form = $request->input($field);
|
||||
if ($field == 'is_private') {
|
||||
|
@ -67,7 +79,7 @@ trait PrivacySettings
|
|||
} else {
|
||||
$settings->{$field} = true;
|
||||
}
|
||||
} elseif ($field == 'public_dm') {
|
||||
} elseif ($field == 'public_dm') {
|
||||
if ($form == 'on') {
|
||||
$settings->{$field} = true;
|
||||
} else {
|
||||
|
@ -85,33 +97,36 @@ trait PrivacySettings
|
|||
$settings->save();
|
||||
}
|
||||
$pid = $profile->id;
|
||||
Cache::forget('profile:settings:' . $pid);
|
||||
Cache::forget('user:account:id:' . $profile->user_id);
|
||||
Cache::forget('profile:follower_count:' . $pid);
|
||||
Cache::forget('profile:following_count:' . $pid);
|
||||
Cache::forget('profile:atom:enabled:' . $pid);
|
||||
Cache::forget('profile:embed:' . $pid);
|
||||
Cache::forget('pf:acct:settings:hidden-followers:' . $pid);
|
||||
Cache::forget('pf:acct:settings:hidden-following:' . $pid);
|
||||
Cache::forget('pf:acct-trans:hideFollowing:' . $pid);
|
||||
Cache::forget('pf:acct-trans:hideFollowers:' . $pid);
|
||||
Cache::forget('pfc:cached-user:wt:' . strtolower($profile->username));
|
||||
Cache::forget('pfc:cached-user:wot:' . strtolower($profile->username));
|
||||
Cache::forget('profile:settings:'.$pid);
|
||||
Cache::forget('user:account:id:'.$profile->user_id);
|
||||
Cache::forget('profile:follower_count:'.$pid);
|
||||
Cache::forget('profile:following_count:'.$pid);
|
||||
Cache::forget('profile:atom:enabled:'.$pid);
|
||||
Cache::forget('profile:embed:'.$pid);
|
||||
Cache::forget('pf:acct:settings:hidden-followers:'.$pid);
|
||||
Cache::forget('pf:acct:settings:hidden-following:'.$pid);
|
||||
Cache::forget('pf:acct-trans:hideFollowing:'.$pid);
|
||||
Cache::forget('pf:acct-trans:hideFollowers:'.$pid);
|
||||
Cache::forget('pfc:cached-user:wt:'.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!');
|
||||
}
|
||||
|
||||
public function mutedUsers()
|
||||
{
|
||||
{
|
||||
$pid = Auth::user()->profile->id;
|
||||
$ids = (new UserFilter())->mutedUserIds($pid);
|
||||
$users = Profile::whereIn('id', $ids)->simplePaginate(15);
|
||||
|
||||
return view('settings.privacy.muted', compact('users'));
|
||||
}
|
||||
|
||||
public function mutedUsersUpdate(Request $request)
|
||||
{
|
||||
{
|
||||
$this->validate($request, [
|
||||
'profile_id' => 'required|integer|min:1'
|
||||
'profile_id' => 'required|integer|min:1',
|
||||
]);
|
||||
$fid = $request->input('profile_id');
|
||||
$pid = Auth::user()->profile->id;
|
||||
|
@ -123,6 +138,8 @@ trait PrivacySettings
|
|||
->firstOrFail();
|
||||
$filter->delete();
|
||||
});
|
||||
RelationshipService::refresh($pid, $fid);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
@ -131,14 +148,14 @@ trait PrivacySettings
|
|||
$pid = Auth::user()->profile->id;
|
||||
$ids = (new UserFilter())->blockedUserIds($pid);
|
||||
$users = Profile::whereIn('id', $ids)->simplePaginate(15);
|
||||
|
||||
return view('settings.privacy.blocked', compact('users'));
|
||||
}
|
||||
|
||||
|
||||
public function blockedUsersUpdate(Request $request)
|
||||
{
|
||||
{
|
||||
$this->validate($request, [
|
||||
'profile_id' => 'required|integer|min:1'
|
||||
'profile_id' => 'required|integer|min:1',
|
||||
]);
|
||||
$fid = $request->input('profile_id');
|
||||
$pid = Auth::user()->profile->id;
|
||||
|
@ -150,6 +167,8 @@ trait PrivacySettings
|
|||
->firstOrFail();
|
||||
$filter->delete();
|
||||
});
|
||||
RelationshipService::refresh($pid, $fid);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
|
@ -194,7 +213,7 @@ trait PrivacySettings
|
|||
$profile = Auth::user()->profile;
|
||||
$settings = Auth::user()->settings;
|
||||
|
||||
if($mode !== 'keep-all') {
|
||||
if ($mode !== 'keep-all') {
|
||||
switch ($mode) {
|
||||
case 'mutual-only':
|
||||
$following = $profile->following()->pluck('profiles.id');
|
||||
|
@ -209,9 +228,9 @@ trait PrivacySettings
|
|||
case 'remove-all':
|
||||
Follower::whereFollowingId($profile->id)->delete();
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
# code...
|
||||
// code...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -221,6 +240,7 @@ trait PrivacySettings
|
|||
$settings->save();
|
||||
$profile->save();
|
||||
Cache::forget('profiles:private');
|
||||
|
||||
return [200];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,270 +2,275 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\AccountLog;
|
||||
use App\Following;
|
||||
use App\ProfileSponsor;
|
||||
use App\Report;
|
||||
use App\UserFilter;
|
||||
use App\UserSetting;
|
||||
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\Http\Controllers\Settings\ExportSettings;
|
||||
use App\Http\Controllers\Settings\HomeSettings;
|
||||
use App\Http\Controllers\Settings\LabsSettings;
|
||||
use App\Http\Controllers\Settings\PrivacySettings;
|
||||
use App\Http\Controllers\Settings\RelationshipSettings;
|
||||
use App\Http\Controllers\Settings\SecuritySettings;
|
||||
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
|
||||
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
|
||||
use App\ProfileSponsor;
|
||||
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
|
||||
{
|
||||
use ExportSettings,
|
||||
LabsSettings,
|
||||
HomeSettings,
|
||||
PrivacySettings,
|
||||
RelationshipSettings,
|
||||
SecuritySettings;
|
||||
use ExportSettings,
|
||||
HomeSettings,
|
||||
LabsSettings,
|
||||
PrivacySettings,
|
||||
RelationshipSettings,
|
||||
SecuritySettings;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function accessibility()
|
||||
{
|
||||
$settings = Auth::user()->settings;
|
||||
public function accessibility()
|
||||
{
|
||||
$settings = Auth::user()->settings;
|
||||
|
||||
return view('settings.accessibility', compact('settings'));
|
||||
}
|
||||
return view('settings.accessibility', compact('settings'));
|
||||
}
|
||||
|
||||
public function accessibilityStore(Request $request)
|
||||
{
|
||||
$settings = Auth::user()->settings;
|
||||
$fields = [
|
||||
'compose_media_descriptions',
|
||||
'reduce_motion',
|
||||
'optimize_screen_reader',
|
||||
'high_contrast_mode',
|
||||
'video_autoplay',
|
||||
];
|
||||
foreach ($fields as $field) {
|
||||
$form = $request->input($field);
|
||||
if ($form == 'on') {
|
||||
$settings->{$field} = true;
|
||||
} else {
|
||||
$settings->{$field} = false;
|
||||
}
|
||||
$settings->save();
|
||||
}
|
||||
public function accessibilityStore(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
$settings = $user->settings;
|
||||
$fields = [
|
||||
'compose_media_descriptions',
|
||||
'reduce_motion',
|
||||
'optimize_screen_reader',
|
||||
'high_contrast_mode',
|
||||
'video_autoplay',
|
||||
];
|
||||
foreach ($fields as $field) {
|
||||
$form = $request->input($field);
|
||||
if ($form == 'on') {
|
||||
$settings->{$field} = true;
|
||||
} else {
|
||||
$settings->{$field} = false;
|
||||
}
|
||||
$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!');
|
||||
}
|
||||
|
||||
public function notifications()
|
||||
{
|
||||
return view('settings.notifications');
|
||||
}
|
||||
public function notifications()
|
||||
{
|
||||
return view('settings.notifications');
|
||||
}
|
||||
|
||||
public function applications()
|
||||
{
|
||||
return view('settings.applications');
|
||||
}
|
||||
public function applications()
|
||||
{
|
||||
return view('settings.applications');
|
||||
}
|
||||
|
||||
public function dataImport()
|
||||
{
|
||||
return view('settings.import.home');
|
||||
}
|
||||
public function dataImport()
|
||||
{
|
||||
return view('settings.import.home');
|
||||
}
|
||||
|
||||
public function dataImportInstagram()
|
||||
{
|
||||
abort(404);
|
||||
}
|
||||
public function dataImportInstagram()
|
||||
{
|
||||
abort(404);
|
||||
}
|
||||
|
||||
public function developers()
|
||||
{
|
||||
return view('settings.developers');
|
||||
}
|
||||
public function developers()
|
||||
{
|
||||
return view('settings.developers');
|
||||
}
|
||||
|
||||
public function removeAccountTemporary(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
abort_if(!config('pixelfed.account_deletion'), 403);
|
||||
abort_if($user->is_admin, 403);
|
||||
public function removeAccountTemporary(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
abort_if(! config('pixelfed.account_deletion'), 403);
|
||||
abort_if($user->is_admin, 403);
|
||||
|
||||
return view('settings.remove.temporary');
|
||||
}
|
||||
return view('settings.remove.temporary');
|
||||
}
|
||||
|
||||
public function removeAccountTemporarySubmit(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
abort_if(!config('pixelfed.account_deletion'), 403);
|
||||
abort_if($user->is_admin, 403);
|
||||
$profile = $user->profile;
|
||||
$user->status = 'disabled';
|
||||
$profile->status = 'disabled';
|
||||
$user->save();
|
||||
$profile->save();
|
||||
Auth::logout();
|
||||
Cache::forget('profiles:private');
|
||||
return redirect('/');
|
||||
}
|
||||
public function removeAccountTemporarySubmit(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
abort_if(! config('pixelfed.account_deletion'), 403);
|
||||
abort_if($user->is_admin, 403);
|
||||
$profile = $user->profile;
|
||||
$user->status = 'disabled';
|
||||
$profile->status = 'disabled';
|
||||
$user->save();
|
||||
$profile->save();
|
||||
Auth::logout();
|
||||
Cache::forget('profiles:private');
|
||||
|
||||
public function removeAccountPermanent(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
abort_if($user->is_admin, 403);
|
||||
return view('settings.remove.permanent');
|
||||
}
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
public function removeAccountPermanentSubmit(Request $request)
|
||||
{
|
||||
if(config('pixelfed.account_deletion') == false) {
|
||||
abort(404);
|
||||
}
|
||||
$user = Auth::user();
|
||||
abort_if(!config('pixelfed.account_deletion'), 403);
|
||||
abort_if($user->is_admin, 403);
|
||||
$profile = $user->profile;
|
||||
$ts = Carbon::now()->addMonth();
|
||||
$user->email = $user->id;
|
||||
$user->password = '';
|
||||
$user->status = 'delete';
|
||||
$profile->status = 'delete';
|
||||
$user->delete_after = $ts;
|
||||
$profile->delete_after = $ts;
|
||||
$user->save();
|
||||
$profile->save();
|
||||
Cache::forget('profiles:private');
|
||||
AccountService::del($profile->id);
|
||||
Auth::logout();
|
||||
DeleteAccountPipeline::dispatch($user)->onQueue('low');
|
||||
return redirect('/');
|
||||
}
|
||||
public function removeAccountPermanent(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
abort_if($user->is_admin, 403);
|
||||
|
||||
public function requestFullExport(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
return view('settings.export.show');
|
||||
}
|
||||
return view('settings.remove.permanent');
|
||||
}
|
||||
|
||||
public function metroDarkMode(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'mode' => 'required|string|in:light,dark'
|
||||
]);
|
||||
public function removeAccountPermanentSubmit(Request $request)
|
||||
{
|
||||
if (config('pixelfed.account_deletion') == false) {
|
||||
abort(404);
|
||||
}
|
||||
$user = Auth::user();
|
||||
abort_if(! config('pixelfed.account_deletion'), 403);
|
||||
abort_if($user->is_admin, 403);
|
||||
$profile = $user->profile;
|
||||
$ts = Carbon::now()->addMonth();
|
||||
$user->email = $user->id;
|
||||
$user->password = '';
|
||||
$user->status = 'delete';
|
||||
$profile->status = 'delete';
|
||||
$user->delete_after = $ts;
|
||||
$profile->delete_after = $ts;
|
||||
$user->save();
|
||||
$profile->save();
|
||||
Cache::forget('profiles:private');
|
||||
AccountService::del($profile->id);
|
||||
Auth::logout();
|
||||
DeleteAccountPipeline::dispatch($user)->onQueue('low');
|
||||
|
||||
$mode = $request->input('mode');
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
if($mode == 'dark') {
|
||||
$cookie = Cookie::make('dark-mode', 'true', 43800);
|
||||
} else {
|
||||
$cookie = Cookie::forget('dark-mode');
|
||||
}
|
||||
public function requestFullExport(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
return response()->json([200])->cookie($cookie);
|
||||
}
|
||||
return view('settings.export.show');
|
||||
}
|
||||
|
||||
public function sponsor()
|
||||
{
|
||||
$default = [
|
||||
'patreon' => null,
|
||||
'liberapay' => null,
|
||||
'opencollective' => null
|
||||
];
|
||||
$sponsors = ProfileSponsor::whereProfileId(Auth::user()->profile->id)->first();
|
||||
$sponsors = $sponsors ? json_decode($sponsors->sponsors, true) : $default;
|
||||
return view('settings.sponsor', compact('sponsors'));
|
||||
}
|
||||
|
||||
public function sponsorStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'patreon' => 'nullable|string',
|
||||
'liberapay' => 'nullable|string',
|
||||
'opencollective' => 'nullable|string'
|
||||
]);
|
||||
|
||||
$patreon = Str::startsWith($request->input('patreon'), 'https://') ?
|
||||
substr($request->input('patreon'), 8) :
|
||||
$request->input('patreon');
|
||||
|
||||
$liberapay = Str::startsWith($request->input('liberapay'), 'https://') ?
|
||||
substr($request->input('liberapay'), 8) :
|
||||
$request->input('liberapay');
|
||||
|
||||
$opencollective = Str::startsWith($request->input('opencollective'), 'https://') ?
|
||||
substr($request->input('opencollective'), 8) :
|
||||
$request->input('opencollective');
|
||||
|
||||
$patreon = Str::startsWith($patreon, 'patreon.com/') ? e($patreon) : null;
|
||||
$liberapay = Str::startsWith($liberapay, 'liberapay.com/') ? e($liberapay) : null;
|
||||
$opencollective = Str::startsWith($opencollective, 'opencollective.com/') ? e($opencollective) : null;
|
||||
|
||||
if(empty($patreon) && empty($liberapay) && empty($opencollective)) {
|
||||
return redirect(route('settings'))->with('error', 'An error occured. Please try again later.');
|
||||
}
|
||||
|
||||
$res = [
|
||||
'patreon' => $patreon,
|
||||
'liberapay' => $liberapay,
|
||||
'opencollective' => $opencollective
|
||||
];
|
||||
|
||||
$sponsors = ProfileSponsor::firstOrCreate([
|
||||
'profile_id' => Auth::user()->profile_id ?? Auth::user()->profile->id
|
||||
]);
|
||||
$sponsors->sponsors = json_encode($res);
|
||||
$sponsors->save();
|
||||
$sponsors = $res;
|
||||
return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');
|
||||
}
|
||||
|
||||
public function timelineSettings(Request $request)
|
||||
{
|
||||
$uid = $request->user()->id;
|
||||
$pid = $request->user()->profile_id;
|
||||
$top = Redis::zscore('pf:tl:top', $pid) != false;
|
||||
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
|
||||
$userSettings = UserSetting::firstOrCreate([
|
||||
'user_id' => $uid
|
||||
public function metroDarkMode(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'mode' => 'required|string|in:light,dark',
|
||||
]);
|
||||
if(!$userSettings || !$userSettings->other) {
|
||||
|
||||
$mode = $request->input('mode');
|
||||
|
||||
if ($mode == 'dark') {
|
||||
$cookie = Cookie::make('dark-mode', 'true', 43800);
|
||||
} else {
|
||||
$cookie = Cookie::forget('dark-mode');
|
||||
}
|
||||
|
||||
return response()->json([200])->cookie($cookie);
|
||||
}
|
||||
|
||||
public function sponsor()
|
||||
{
|
||||
$default = [
|
||||
'patreon' => null,
|
||||
'liberapay' => null,
|
||||
'opencollective' => null,
|
||||
];
|
||||
$sponsors = ProfileSponsor::whereProfileId(Auth::user()->profile->id)->first();
|
||||
$sponsors = $sponsors ? json_decode($sponsors->sponsors, true) : $default;
|
||||
|
||||
return view('settings.sponsor', compact('sponsors'));
|
||||
}
|
||||
|
||||
public function sponsorStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'patreon' => 'nullable|string',
|
||||
'liberapay' => 'nullable|string',
|
||||
'opencollective' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$patreon = Str::startsWith($request->input('patreon'), 'https://') ?
|
||||
substr($request->input('patreon'), 8) :
|
||||
$request->input('patreon');
|
||||
|
||||
$liberapay = Str::startsWith($request->input('liberapay'), 'https://') ?
|
||||
substr($request->input('liberapay'), 8) :
|
||||
$request->input('liberapay');
|
||||
|
||||
$opencollective = Str::startsWith($request->input('opencollective'), 'https://') ?
|
||||
substr($request->input('opencollective'), 8) :
|
||||
$request->input('opencollective');
|
||||
|
||||
$patreon = Str::startsWith($patreon, 'patreon.com/') ? e($patreon) : null;
|
||||
$liberapay = Str::startsWith($liberapay, 'liberapay.com/') ? e($liberapay) : null;
|
||||
$opencollective = Str::startsWith($opencollective, 'opencollective.com/') ? e($opencollective) : null;
|
||||
|
||||
if (empty($patreon) && empty($liberapay) && empty($opencollective)) {
|
||||
return redirect(route('settings'))->with('error', 'An error occured. Please try again later.');
|
||||
}
|
||||
|
||||
$res = [
|
||||
'patreon' => $patreon,
|
||||
'liberapay' => $liberapay,
|
||||
'opencollective' => $opencollective,
|
||||
];
|
||||
|
||||
$sponsors = ProfileSponsor::firstOrCreate([
|
||||
'profile_id' => Auth::user()->profile_id ?? Auth::user()->profile->id,
|
||||
]);
|
||||
$sponsors->sponsors = json_encode($res);
|
||||
$sponsors->save();
|
||||
$sponsors = $res;
|
||||
|
||||
return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');
|
||||
}
|
||||
|
||||
public function timelineSettings(Request $request)
|
||||
{
|
||||
$uid = $request->user()->id;
|
||||
$pid = $request->user()->profile_id;
|
||||
$top = Redis::zscore('pf:tl:top', $pid) != false;
|
||||
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
|
||||
$userSettings = UserSetting::firstOrCreate([
|
||||
'user_id' => $uid,
|
||||
]);
|
||||
if (! $userSettings || ! $userSettings->other) {
|
||||
$userSettings = [
|
||||
'enable_reblogs' => false,
|
||||
'photo_reblogs_only' => false
|
||||
'photo_reblogs_only' => false,
|
||||
];
|
||||
} else {
|
||||
$userSettings = array_merge([
|
||||
'enable_reblogs' => false,
|
||||
'photo_reblogs_only' => false
|
||||
'photo_reblogs_only' => false,
|
||||
],
|
||||
$userSettings->other);
|
||||
$userSettings->other);
|
||||
}
|
||||
return view('settings.timeline', compact('top', 'replies', 'userSettings'));
|
||||
}
|
||||
|
||||
public function updateTimelineSettings(Request $request)
|
||||
{
|
||||
return view('settings.timeline', compact('top', 'replies', 'userSettings'));
|
||||
}
|
||||
|
||||
public function updateTimelineSettings(Request $request)
|
||||
{
|
||||
$pid = $request->user()->profile_id;
|
||||
$uid = $request->user()->id;
|
||||
$uid = $request->user()->id;
|
||||
$this->validate($request, [
|
||||
'enable_reblogs' => 'sometimes',
|
||||
'photo_reblogs_only' => 'sometimes'
|
||||
'photo_reblogs_only' => 'sometimes',
|
||||
]);
|
||||
Redis::zrem('pf:tl:top', $pid);
|
||||
Redis::zrem('pf:tl:replies', $pid);
|
||||
Redis::zrem('pf:tl:top', $pid);
|
||||
Redis::zrem('pf:tl:replies', $pid);
|
||||
$userSettings = UserSetting::firstOrCreate([
|
||||
'user_id' => $uid
|
||||
'user_id' => $uid,
|
||||
]);
|
||||
if($userSettings->other) {
|
||||
if ($userSettings->other) {
|
||||
$other = $userSettings->other;
|
||||
$other['enable_reblogs'] = $request->has('enable_reblogs');
|
||||
$other['photo_reblogs_only'] = $request->has('photo_reblogs_only');
|
||||
|
@ -275,72 +280,74 @@ class SettingsController extends Controller
|
|||
}
|
||||
$userSettings->other = $other;
|
||||
$userSettings->save();
|
||||
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
|
||||
}
|
||||
|
||||
public function mediaSettings(Request $request)
|
||||
{
|
||||
$setting = UserSetting::whereUserId($request->user()->id)->firstOrFail();
|
||||
$compose = $setting->compose_settings ? (
|
||||
is_string($setting->compose_settings) ? json_decode($setting->compose_settings, true) : $setting->compose_settings
|
||||
) : [
|
||||
'default_license' => null,
|
||||
'media_descriptions' => false
|
||||
];
|
||||
return view('settings.media', compact('compose'));
|
||||
}
|
||||
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
|
||||
}
|
||||
|
||||
public function updateMediaSettings(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'default' => 'required|int|min:1|max:16',
|
||||
'sync' => 'nullable',
|
||||
'media_descriptions' => 'nullable'
|
||||
]);
|
||||
public function mediaSettings(Request $request)
|
||||
{
|
||||
$setting = UserSetting::whereUserId($request->user()->id)->firstOrFail();
|
||||
$compose = $setting->compose_settings ? (
|
||||
is_string($setting->compose_settings) ? json_decode($setting->compose_settings, true) : $setting->compose_settings
|
||||
) : [
|
||||
'default_license' => null,
|
||||
'media_descriptions' => false,
|
||||
];
|
||||
|
||||
$license = $request->input('default');
|
||||
$sync = $request->input('sync') == 'on';
|
||||
$media_descriptions = $request->input('media_descriptions') == 'on';
|
||||
$uid = $request->user()->id;
|
||||
return view('settings.media', compact('compose'));
|
||||
}
|
||||
|
||||
$setting = UserSetting::whereUserId($uid)->firstOrFail();
|
||||
$compose = is_string($setting->compose_settings) ? json_decode($setting->compose_settings, true) : $setting->compose_settings;
|
||||
$changed = false;
|
||||
public function updateMediaSettings(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'default' => 'required|int|min:1|max:16',
|
||||
'sync' => 'nullable',
|
||||
'media_descriptions' => 'nullable',
|
||||
]);
|
||||
|
||||
if($sync) {
|
||||
$key = 'pf:settings:mls_recently:'.$uid;
|
||||
if(Cache::get($key) == 2) {
|
||||
$msg = 'You can only sync licenses twice per 24 hours. Try again later.';
|
||||
return redirect(route('settings'))
|
||||
->with('error', $msg);
|
||||
}
|
||||
}
|
||||
$license = $request->input('default');
|
||||
$sync = $request->input('sync') == 'on';
|
||||
$media_descriptions = $request->input('media_descriptions') == 'on';
|
||||
$uid = $request->user()->id;
|
||||
|
||||
if(!isset($compose['default_license']) || $compose['default_license'] !== $license) {
|
||||
$compose['default_license'] = (int) $license;
|
||||
$changed = true;
|
||||
}
|
||||
$setting = UserSetting::whereUserId($uid)->firstOrFail();
|
||||
$compose = is_string($setting->compose_settings) ? json_decode($setting->compose_settings, true) : $setting->compose_settings;
|
||||
$changed = false;
|
||||
|
||||
if(!isset($compose['media_descriptions']) || $compose['media_descriptions'] !== $media_descriptions) {
|
||||
$compose['media_descriptions'] = $media_descriptions;
|
||||
$changed = true;
|
||||
}
|
||||
if ($sync) {
|
||||
$key = 'pf:settings:mls_recently:'.$uid;
|
||||
if (Cache::get($key) == 2) {
|
||||
$msg = 'You can only sync licenses twice per 24 hours. Try again later.';
|
||||
|
||||
if($changed) {
|
||||
$setting->compose_settings = $compose;
|
||||
$setting->save();
|
||||
Cache::forget('profile:compose:settings:' . $request->user()->id);
|
||||
}
|
||||
return redirect(route('settings'))
|
||||
->with('error', $msg);
|
||||
}
|
||||
}
|
||||
|
||||
if($sync) {
|
||||
$val = Cache::has($key) ? 2 : 1;
|
||||
Cache::put($key, $val, 86400);
|
||||
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.');
|
||||
}
|
||||
if (! isset($compose['default_license']) || $compose['default_license'] !== $license) {
|
||||
$compose['default_license'] = (int) $license;
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
return redirect(route('settings'))->with('status', 'Media settings successfully updated!');
|
||||
}
|
||||
if (! isset($compose['media_descriptions']) || $compose['media_descriptions'] !== $media_descriptions) {
|
||||
$compose['media_descriptions'] = $media_descriptions;
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
$setting->compose_settings = $compose;
|
||||
$setting->save();
|
||||
Cache::forget('profile:compose:settings:'.$request->user()->id);
|
||||
}
|
||||
|
||||
if ($sync) {
|
||||
$val = Cache::has($key) ? 2 : 1;
|
||||
Cache::put($key, $val, 86400);
|
||||
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 settings successfully updated!');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace App\Http\Controllers;
|
|||
use App\Page;
|
||||
use App\Profile;
|
||||
use App\Services\FollowerService;
|
||||
use App\Status;
|
||||
use App\Services\StatusService;
|
||||
use App\User;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Util\Localization\Localization;
|
||||
|
@ -60,7 +60,7 @@ class SiteController extends Controller
|
|||
{
|
||||
return Cache::remember('site.about_v2', now()->addMinutes(15), function () {
|
||||
$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;
|
||||
|
||||
return view('site.about', compact('rules', 'user_count', 'post_count'))->render();
|
||||
|
|
|
@ -8,6 +8,7 @@ use App\Jobs\SharePipeline\UndoSharePipeline;
|
|||
use App\Jobs\StatusPipeline\RemoteStatusDelete;
|
||||
use App\Jobs\StatusPipeline\StatusDelete;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\HashidService;
|
||||
use App\Services\ReblogService;
|
||||
use App\Services\StatusService;
|
||||
|
@ -34,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) {
|
||||
return ProfileController::accountCheck($user);
|
||||
}
|
||||
|
@ -70,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() && config_cache('federation.activitypub.enabled')) {
|
||||
return $this->showActivityPub($request, $status);
|
||||
}
|
||||
|
||||
$template = $status->in_reply_to_id ? 'status.reply' : 'status.show';
|
||||
|
||||
return view($template, compact('user', 'status'));
|
||||
|
@ -113,19 +115,41 @@ class StatusController extends Controller
|
|||
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
||||
}
|
||||
|
||||
$profile = Profile::whereNull(['domain', 'status'])
|
||||
->whereIsPrivate(false)
|
||||
->whereUsername($username)
|
||||
->first();
|
||||
$status = StatusService::get($id);
|
||||
|
||||
if (! $profile) {
|
||||
if (
|
||||
! $status ||
|
||||
! isset($status['account'], $status['account']['id'], $status['local']) ||
|
||||
! $status['local'] ||
|
||||
strtolower($status['account']['username']) !== strtolower($username)
|
||||
) {
|
||||
$content = view('status.embed-removed');
|
||||
|
||||
return response($content, 404)->header('X-Frame-Options', 'ALLOWALL');
|
||||
}
|
||||
|
||||
$profile = AccountService::get($status['account']['id'], true);
|
||||
|
||||
if (! $profile || $profile['locked'] || ! $profile['local']) {
|
||||
$content = view('status.embed-removed');
|
||||
|
||||
return response($content)->header('X-Frame-Options', 'ALLOWALL');
|
||||
}
|
||||
|
||||
$aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 86400, function () use ($profile) {
|
||||
$exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
|
||||
$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) {
|
||||
$user = Profile::find($profile['id']);
|
||||
if (! $user) {
|
||||
return true;
|
||||
}
|
||||
$exists = AccountInterstitial::whereUserId($user->user_id)->where('is_spam', 1)->count();
|
||||
if ($exists) {
|
||||
return true;
|
||||
}
|
||||
|
@ -138,17 +162,22 @@ class StatusController extends Controller
|
|||
|
||||
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
|
||||
}
|
||||
$status = Status::whereProfileId($profile->id)
|
||||
->whereNull('uri')
|
||||
->whereScope('public')
|
||||
->whereIsNsfw(false)
|
||||
->whereIn('type', ['photo', 'video', 'photo:album'])
|
||||
->find($id);
|
||||
if (! $status) {
|
||||
|
||||
$status = StatusService::get($id);
|
||||
|
||||
if (
|
||||
! $status ||
|
||||
! isset($status['account'], $status['account']['id']) ||
|
||||
intval($status['account']['id']) !== intval($profile['id']) ||
|
||||
$status['sensitive'] ||
|
||||
$status['visibility'] !== 'public' ||
|
||||
! in_array($status['pf_type'], ['photo', 'photo:album'])
|
||||
) {
|
||||
$content = view('status.embed-removed');
|
||||
|
||||
return response($content)->header('X-Frame-Options', 'ALLOWALL');
|
||||
}
|
||||
|
||||
$showLikes = $request->filled('likes') && $request->likes == true;
|
||||
$showCaption = $request->filled('caption') && $request->caption !== false;
|
||||
$layout = $request->filled('layout') && $request->layout == 'compact' ? 'compact' : 'full';
|
||||
|
@ -319,12 +348,17 @@ class StatusController extends Controller
|
|||
|
||||
public function showActivityPub(Request $request, $status)
|
||||
{
|
||||
$object = $status->type == 'poll' ? new Question() : new Note();
|
||||
$fractal = new Fractal\Manager();
|
||||
$resource = new Fractal\Resource\Item($status, $object);
|
||||
$res = $fractal->createData($resource)->toArray();
|
||||
$key = 'pf:status:ap:v1:sid:'.$status['id'];
|
||||
|
||||
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
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);
|
||||
$res = $fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
});
|
||||
}
|
||||
|
||||
public function edit(Request $request, $username, $id)
|
||||
|
|
|
@ -2,54 +2,56 @@
|
|||
|
||||
namespace App\Http\Controllers\Stories;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Models\Conversation;
|
||||
use App\DirectMessage;
|
||||
use App\Notification;
|
||||
use App\Story;
|
||||
use App\Status;
|
||||
use App\StoryView;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\StoryView as StoryViewResource;
|
||||
use App\Jobs\StoryPipeline\StoryDelete;
|
||||
use App\Jobs\StoryPipeline\StoryFanout;
|
||||
use App\Jobs\StoryPipeline\StoryReplyDeliver;
|
||||
use App\Jobs\StoryPipeline\StoryViewDeliver;
|
||||
use App\Models\Conversation;
|
||||
use App\Notification;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\MediaPathService;
|
||||
use App\Services\StoryService;
|
||||
use App\Http\Resources\StoryView as StoryViewResource;
|
||||
use App\Status;
|
||||
use App\Story;
|
||||
use App\StoryView;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StoryApiV1Controller extends Controller
|
||||
{
|
||||
const RECENT_KEY = 'pf:stories:recent-by-id:';
|
||||
|
||||
const RECENT_TTL = 300;
|
||||
|
||||
public function carousel(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
if(config('database.default') == 'pgsql') {
|
||||
$s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) {
|
||||
if (config('database.default') == 'pgsql') {
|
||||
$s = Cache::remember(self::RECENT_KEY.$pid, self::RECENT_TTL, function () use ($pid) {
|
||||
return Story::select('stories.*', 'followers.following_id')
|
||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
->where('stories.active', true)
|
||||
->map(function($s) {
|
||||
$r = new \StdClass;
|
||||
->map(function ($s) {
|
||||
$r = new \StdClass;
|
||||
$r->id = $s->id;
|
||||
$r->profile_id = $s->profile_id;
|
||||
$r->type = $s->type;
|
||||
$r->path = $s->path;
|
||||
|
||||
return $r;
|
||||
})
|
||||
->unique('profile_id');
|
||||
});
|
||||
} else {
|
||||
$s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) {
|
||||
$s = Cache::remember(self::RECENT_KEY.$pid, self::RECENT_TTL, function () use ($pid) {
|
||||
return Story::select('stories.*', 'followers.following_id')
|
||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
|
@ -59,9 +61,9 @@ class StoryApiV1Controller extends Controller
|
|||
});
|
||||
}
|
||||
|
||||
$nodes = $s->map(function($s) use($pid) {
|
||||
$nodes = $s->map(function ($s) use ($pid) {
|
||||
$profile = AccountService::get($s->profile_id, true);
|
||||
if(!$profile || !isset($profile['id'])) {
|
||||
if (! $profile || ! isset($profile['id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -72,50 +74,51 @@ class StoryApiV1Controller extends Controller
|
|||
'src' => url(Storage::url($s->path)),
|
||||
'duration' => $s->duration ?? 3,
|
||||
'seen' => StoryService::hasSeen($pid, $s->id),
|
||||
'created_at' => $s->created_at->format('c')
|
||||
'created_at' => $s->created_at->format('c'),
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->groupBy('pid')
|
||||
->map(function($item) use($pid) {
|
||||
$profile = AccountService::get($item[0]['pid'], true);
|
||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||
url("/i/rs/{$profile['id']}");
|
||||
return [
|
||||
'id' => 'pfs:' . $profile['id'],
|
||||
'user' => [
|
||||
'id' => (string) $profile['id'],
|
||||
'username' => $profile['username'],
|
||||
'username_acct' => $profile['acct'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'local' => $profile['local'],
|
||||
'is_author' => $profile['id'] == $pid
|
||||
],
|
||||
'nodes' => $item,
|
||||
'url' => $url,
|
||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||
];
|
||||
})
|
||||
->sortBy('seen')
|
||||
->values();
|
||||
->filter()
|
||||
->groupBy('pid')
|
||||
->map(function ($item) use ($pid) {
|
||||
$profile = AccountService::get($item[0]['pid'], true);
|
||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||
url("/i/rs/{$profile['id']}");
|
||||
|
||||
return [
|
||||
'id' => 'pfs:'.$profile['id'],
|
||||
'user' => [
|
||||
'id' => (string) $profile['id'],
|
||||
'username' => $profile['username'],
|
||||
'username_acct' => $profile['acct'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'local' => $profile['local'],
|
||||
'is_author' => $profile['id'] == $pid,
|
||||
],
|
||||
'nodes' => $item,
|
||||
'url' => $url,
|
||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||
];
|
||||
})
|
||||
->sortBy('seen')
|
||||
->values();
|
||||
|
||||
$res = [
|
||||
'self' => [],
|
||||
'nodes' => $nodes,
|
||||
];
|
||||
|
||||
if(Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||
if (Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||
$selfStories = Story::whereProfileId($pid)
|
||||
->whereActive(true)
|
||||
->get()
|
||||
->map(function($s) use($pid) {
|
||||
->map(function ($s) {
|
||||
return [
|
||||
'id' => (string) $s->id,
|
||||
'type' => $s->type,
|
||||
'src' => url(Storage::url($s->path)),
|
||||
'duration' => $s->duration,
|
||||
'seen' => true,
|
||||
'created_at' => $s->created_at->format('c')
|
||||
'created_at' => $s->created_at->format('c'),
|
||||
];
|
||||
})
|
||||
->sortBy('id')
|
||||
|
@ -127,38 +130,40 @@ class StoryApiV1Controller extends Controller
|
|||
'username' => $selfProfile['acct'],
|
||||
'avatar' => $selfProfile['avatar'],
|
||||
'local' => $selfProfile['local'],
|
||||
'is_author' => true
|
||||
'is_author' => true,
|
||||
],
|
||||
|
||||
'nodes' => $selfStories,
|
||||
];
|
||||
}
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function selfCarousel(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
if(config('database.default') == 'pgsql') {
|
||||
$s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) {
|
||||
if (config('database.default') == 'pgsql') {
|
||||
$s = Cache::remember(self::RECENT_KEY.$pid, self::RECENT_TTL, function () use ($pid) {
|
||||
return Story::select('stories.*', 'followers.following_id')
|
||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
->where('stories.active', true)
|
||||
->map(function($s) {
|
||||
$r = new \StdClass;
|
||||
->map(function ($s) {
|
||||
$r = new \StdClass;
|
||||
$r->id = $s->id;
|
||||
$r->profile_id = $s->profile_id;
|
||||
$r->type = $s->type;
|
||||
$r->path = $s->path;
|
||||
|
||||
return $r;
|
||||
})
|
||||
->unique('profile_id');
|
||||
});
|
||||
} else {
|
||||
$s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) {
|
||||
$s = Cache::remember(self::RECENT_KEY.$pid, self::RECENT_TTL, function () use ($pid) {
|
||||
return Story::select('stories.*', 'followers.following_id')
|
||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
|
@ -168,9 +173,9 @@ class StoryApiV1Controller extends Controller
|
|||
});
|
||||
}
|
||||
|
||||
$nodes = $s->map(function($s) use($pid) {
|
||||
$nodes = $s->map(function ($s) use ($pid) {
|
||||
$profile = AccountService::get($s->profile_id, true);
|
||||
if(!$profile || !isset($profile['id'])) {
|
||||
if (! $profile || ! isset($profile['id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -181,32 +186,33 @@ class StoryApiV1Controller extends Controller
|
|||
'src' => url(Storage::url($s->path)),
|
||||
'duration' => $s->duration ?? 3,
|
||||
'seen' => StoryService::hasSeen($pid, $s->id),
|
||||
'created_at' => $s->created_at->format('c')
|
||||
'created_at' => $s->created_at->format('c'),
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->groupBy('pid')
|
||||
->map(function($item) use($pid) {
|
||||
$profile = AccountService::get($item[0]['pid'], true);
|
||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||
url("/i/rs/{$profile['id']}");
|
||||
return [
|
||||
'id' => 'pfs:' . $profile['id'],
|
||||
'user' => [
|
||||
'id' => (string) $profile['id'],
|
||||
'username' => $profile['username'],
|
||||
'username_acct' => $profile['acct'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'local' => $profile['local'],
|
||||
'is_author' => $profile['id'] == $pid
|
||||
],
|
||||
'nodes' => $item,
|
||||
'url' => $url,
|
||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||
];
|
||||
})
|
||||
->sortBy('seen')
|
||||
->values();
|
||||
->filter()
|
||||
->groupBy('pid')
|
||||
->map(function ($item) use ($pid) {
|
||||
$profile = AccountService::get($item[0]['pid'], true);
|
||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||
url("/i/rs/{$profile['id']}");
|
||||
|
||||
return [
|
||||
'id' => 'pfs:'.$profile['id'],
|
||||
'user' => [
|
||||
'id' => (string) $profile['id'],
|
||||
'username' => $profile['username'],
|
||||
'username_acct' => $profile['acct'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'local' => $profile['local'],
|
||||
'is_author' => $profile['id'] == $pid,
|
||||
],
|
||||
'nodes' => $item,
|
||||
'url' => $url,
|
||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||
];
|
||||
})
|
||||
->sortBy('seen')
|
||||
->values();
|
||||
|
||||
$selfProfile = AccountService::get($pid, true);
|
||||
$res = [
|
||||
|
@ -216,7 +222,7 @@ class StoryApiV1Controller extends Controller
|
|||
'username' => $selfProfile['acct'],
|
||||
'avatar' => $selfProfile['avatar'],
|
||||
'local' => $selfProfile['local'],
|
||||
'is_author' => true
|
||||
'is_author' => true,
|
||||
],
|
||||
|
||||
'nodes' => [],
|
||||
|
@ -224,40 +230,41 @@ class StoryApiV1Controller extends Controller
|
|||
'nodes' => $nodes,
|
||||
];
|
||||
|
||||
if(Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||
if (Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||
$selfStories = Story::whereProfileId($pid)
|
||||
->whereActive(true)
|
||||
->get()
|
||||
->map(function($s) use($pid) {
|
||||
->map(function ($s) {
|
||||
return [
|
||||
'id' => (string) $s->id,
|
||||
'type' => $s->type,
|
||||
'src' => url(Storage::url($s->path)),
|
||||
'duration' => $s->duration,
|
||||
'seen' => true,
|
||||
'created_at' => $s->created_at->format('c')
|
||||
'created_at' => $s->created_at->format('c'),
|
||||
];
|
||||
})
|
||||
->sortBy('id')
|
||||
->values();
|
||||
$res['self']['nodes'] = $selfStories;
|
||||
}
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function add(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'file' => function() {
|
||||
'file' => function () {
|
||||
return [
|
||||
'required',
|
||||
'mimetypes:image/jpeg,image/png,video/mp4',
|
||||
'max:' . config_cache('pixelfed.max_photo_size'),
|
||||
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||
];
|
||||
},
|
||||
'duration' => 'sometimes|integer|min:0|max:30'
|
||||
'duration' => 'sometimes|integer|min:0|max:30',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
@ -267,7 +274,7 @@ class StoryApiV1Controller extends Controller
|
|||
->where('expires_at', '>', now())
|
||||
->count();
|
||||
|
||||
if($count >= Story::MAX_PER_DAY) {
|
||||
if ($count >= Story::MAX_PER_DAY) {
|
||||
abort(418, 'You have reached your limit for new Stories today.');
|
||||
}
|
||||
|
||||
|
@ -277,7 +284,7 @@ class StoryApiV1Controller extends Controller
|
|||
$story = new Story();
|
||||
$story->duration = $request->input('duration', 3);
|
||||
$story->profile_id = $user->profile_id;
|
||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' :'photo';
|
||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo';
|
||||
$story->mime = $photo->getMimeType();
|
||||
$story->path = $path;
|
||||
$story->local = true;
|
||||
|
@ -290,10 +297,10 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
$res = [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully added',
|
||||
'msg' => 'Successfully added',
|
||||
'media_id' => (string) $story->id,
|
||||
'media_url' => url(Storage::url($url)) . '?v=' . time(),
|
||||
'media_type' => $story->type
|
||||
'media_url' => url(Storage::url($url)).'?v='.time(),
|
||||
'media_type' => $story->type,
|
||||
];
|
||||
|
||||
return $res;
|
||||
|
@ -301,13 +308,13 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
public function publish(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'media_id' => 'required',
|
||||
'duration' => 'required|integer|min:0|max:30',
|
||||
'can_reply' => 'required|boolean',
|
||||
'can_react' => 'required|boolean'
|
||||
'can_react' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$id = $request->input('media_id');
|
||||
|
@ -327,13 +334,13 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully published',
|
||||
'msg' => 'Successfully published',
|
||||
];
|
||||
}
|
||||
|
||||
public function delete(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
|
@ -346,16 +353,16 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully deleted'
|
||||
'msg' => 'Successfully deleted',
|
||||
];
|
||||
}
|
||||
|
||||
public function viewed(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'id' => 'required|min:1',
|
||||
'id' => 'required|min:1',
|
||||
]);
|
||||
$id = $request->input('id');
|
||||
|
||||
|
@ -367,44 +374,45 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
$profile = $story->profile;
|
||||
|
||||
if($story->profile_id == $authed->id) {
|
||||
if ($story->profile_id == $authed->id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$publicOnly = (bool) $profile->followedBy($authed);
|
||||
abort_if(!$publicOnly, 403);
|
||||
abort_if(! $publicOnly, 403);
|
||||
|
||||
$v = StoryView::firstOrCreate([
|
||||
'story_id' => $id,
|
||||
'profile_id' => $authed->id
|
||||
'profile_id' => $authed->id,
|
||||
]);
|
||||
|
||||
if($v->wasRecentlyCreated) {
|
||||
if ($v->wasRecentlyCreated) {
|
||||
Story::findOrFail($story->id)->increment('view_count');
|
||||
|
||||
if($story->local == false) {
|
||||
if ($story->local == false) {
|
||||
StoryViewDeliver::dispatch($story, $authed)->onQueue('story');
|
||||
}
|
||||
}
|
||||
|
||||
Cache::forget('stories:recent:by_id:' . $authed->id);
|
||||
Cache::forget('stories:recent:by_id:'.$authed->id);
|
||||
StoryService::addSeen($authed->id, $story->id);
|
||||
|
||||
return ['code' => 200];
|
||||
}
|
||||
|
||||
public function comment(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$this->validate($request, [
|
||||
'sid' => 'required',
|
||||
'caption' => 'required|string'
|
||||
'caption' => 'required|string',
|
||||
]);
|
||||
$pid = $request->user()->profile_id;
|
||||
$text = $request->input('caption');
|
||||
|
||||
$story = Story::findOrFail($request->input('sid'));
|
||||
|
||||
abort_if(!$story->can_reply, 422);
|
||||
abort_if(! $story->can_reply, 422);
|
||||
|
||||
$status = new Status;
|
||||
$status->type = 'story:reply';
|
||||
|
@ -415,7 +423,7 @@ class StoryApiV1Controller extends Controller
|
|||
$status->visibility = 'direct';
|
||||
$status->in_reply_to_profile_id = $story->profile_id;
|
||||
$status->entities = json_encode([
|
||||
'story_id' => $story->id
|
||||
'story_id' => $story->id,
|
||||
]);
|
||||
$status->save();
|
||||
|
||||
|
@ -429,24 +437,24 @@ class StoryApiV1Controller extends Controller
|
|||
'story_actor_username' => $request->user()->username,
|
||||
'story_id' => $story->id,
|
||||
'story_media_url' => url(Storage::url($story->path)),
|
||||
'caption' => $text
|
||||
'caption' => $text,
|
||||
]);
|
||||
$dm->save();
|
||||
|
||||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $story->profile_id,
|
||||
'from_id' => $pid
|
||||
'from_id' => $pid,
|
||||
],
|
||||
[
|
||||
'type' => 'story:comment',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => false
|
||||
'is_hidden' => false,
|
||||
]
|
||||
);
|
||||
|
||||
if($story->local) {
|
||||
if ($story->local) {
|
||||
$n = new Notification;
|
||||
$n->profile_id = $dm->to_id;
|
||||
$n->actor_id = $dm->from_id;
|
||||
|
@ -460,33 +468,35 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Sent!'
|
||||
'msg' => 'Sent!',
|
||||
];
|
||||
}
|
||||
|
||||
protected function storeMedia($photo, $user)
|
||||
{
|
||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||
if(in_array($photo->getMimeType(), [
|
||||
if (in_array($photo->getMimeType(), [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'video/mp4'
|
||||
'video/mp4',
|
||||
]) == false) {
|
||||
abort(400, 'Invalid media type');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$storagePath = MediaPathService::story($user->profile);
|
||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension());
|
||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)).'_'.Str::random(random_int(32, 35)).'_'.Str::random(random_int(1, 14)).'.'.$photo->extension());
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function viewers(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'sid' => 'required|string|min:1|max:50'
|
||||
'sid' => 'required|string|min:1|max:50',
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
|
|
|
@ -2,59 +2,52 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\Report;
|
||||
use App\DirectMessage;
|
||||
use App\Notification;
|
||||
use App\Status;
|
||||
use App\Story;
|
||||
use App\StoryView;
|
||||
use App\Models\Poll;
|
||||
use App\Models\PollVote;
|
||||
use App\Services\ProfileService;
|
||||
use App\Services\StoryService;
|
||||
use Cache, Storage;
|
||||
use Image as Intervention;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\MediaPathService;
|
||||
use FFMpeg;
|
||||
use FFMpeg\Coordinate\Dimension;
|
||||
use FFMpeg\Format\Video\X264;
|
||||
use App\Jobs\StoryPipeline\StoryDelete;
|
||||
use App\Jobs\StoryPipeline\StoryFanout;
|
||||
use App\Jobs\StoryPipeline\StoryReactionDeliver;
|
||||
use App\Jobs\StoryPipeline\StoryReplyDeliver;
|
||||
use App\Jobs\StoryPipeline\StoryFanout;
|
||||
use App\Jobs\StoryPipeline\StoryDelete;
|
||||
use ImageOptimizer;
|
||||
use App\Models\Conversation;
|
||||
use App\Models\Poll;
|
||||
use App\Models\PollVote;
|
||||
use App\Notification;
|
||||
use App\Report;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\MediaPathService;
|
||||
use App\Services\StoryService;
|
||||
use App\Services\UserRoleService;
|
||||
use App\Status;
|
||||
use App\Story;
|
||||
use FFMpeg;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Image as Intervention;
|
||||
use Storage;
|
||||
|
||||
class StoryComposeController extends Controller
|
||||
{
|
||||
public function apiV1Add(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'file' => function() {
|
||||
'file' => function () {
|
||||
return [
|
||||
'required',
|
||||
'mimetypes:image/jpeg,image/png,video/mp4',
|
||||
'max:' . config_cache('pixelfed.max_photo_size'),
|
||||
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$count = Story::whereProfileId($user->profile_id)
|
||||
->whereActive(true)
|
||||
->where('expires_at', '>', now())
|
||||
->count();
|
||||
|
||||
if($count >= Story::MAX_PER_DAY) {
|
||||
if ($count >= Story::MAX_PER_DAY) {
|
||||
abort(418, 'You have reached your limit for new Stories today.');
|
||||
}
|
||||
|
||||
|
@ -64,7 +57,7 @@ class StoryComposeController extends Controller
|
|||
$story = new Story();
|
||||
$story->duration = 3;
|
||||
$story->profile_id = $user->profile_id;
|
||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' :'photo';
|
||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo';
|
||||
$story->mime = $photo->getMimeType();
|
||||
$story->path = $path;
|
||||
$story->local = true;
|
||||
|
@ -77,21 +70,22 @@ class StoryComposeController extends Controller
|
|||
|
||||
$res = [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully added',
|
||||
'msg' => 'Successfully added',
|
||||
'media_id' => (string) $story->id,
|
||||
'media_url' => url(Storage::url($url)) . '?v=' . time(),
|
||||
'media_type' => $story->type
|
||||
'media_url' => url(Storage::url($url)).'?v='.time(),
|
||||
'media_type' => $story->type,
|
||||
];
|
||||
|
||||
if($story->type === 'video') {
|
||||
if ($story->type === 'video') {
|
||||
$video = FFMpeg::open($path);
|
||||
$duration = $video->getDurationInSeconds();
|
||||
$res['media_duration'] = $duration;
|
||||
if($duration > 500) {
|
||||
if ($duration > 500) {
|
||||
Storage::delete($story->path);
|
||||
$story->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Video duration cannot exceed 60 seconds'
|
||||
'message' => 'Video duration cannot exceed 60 seconds',
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
@ -102,37 +96,39 @@ class StoryComposeController extends Controller
|
|||
protected function storePhoto($photo, $user)
|
||||
{
|
||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||
if(in_array($photo->getMimeType(), [
|
||||
if (in_array($photo->getMimeType(), [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'video/mp4'
|
||||
'video/mp4',
|
||||
]) == false) {
|
||||
abort(400, 'Invalid media type');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$storagePath = MediaPathService::story($user->profile);
|
||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension());
|
||||
if(in_array($photo->getMimeType(), ['image/jpeg','image/png'])) {
|
||||
$fpath = storage_path('app/' . $path);
|
||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)).'_'.Str::random(random_int(32, 35)).'_'.Str::random(random_int(1, 14)).'.'.$photo->extension());
|
||||
if (in_array($photo->getMimeType(), ['image/jpeg', 'image/png'])) {
|
||||
$fpath = storage_path('app/'.$path);
|
||||
$img = Intervention::make($fpath);
|
||||
$img->orientate();
|
||||
$img->save($fpath, config_cache('pixelfed.image_quality'));
|
||||
$img->destroy();
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function cropPhoto(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'media_id' => 'required|integer|min:1',
|
||||
'width' => 'required',
|
||||
'height' => 'required',
|
||||
'x' => 'required',
|
||||
'y' => 'required'
|
||||
'y' => 'required',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
@ -144,13 +140,13 @@ class StoryComposeController extends Controller
|
|||
|
||||
$story = Story::whereProfileId($user->profile_id)->findOrFail($id);
|
||||
|
||||
$path = storage_path('app/' . $story->path);
|
||||
$path = storage_path('app/'.$story->path);
|
||||
|
||||
if(!is_file($path)) {
|
||||
if (! is_file($path)) {
|
||||
abort(400, 'Invalid or missing media.');
|
||||
}
|
||||
|
||||
if($story->type === 'photo') {
|
||||
if ($story->type === 'photo') {
|
||||
$img = Intervention::make($path);
|
||||
$img->crop($width, $height, $x, $y);
|
||||
$img->resize(1080, 1920, function ($constraint) {
|
||||
|
@ -161,24 +157,24 @@ class StoryComposeController extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully cropped',
|
||||
'msg' => 'Successfully cropped',
|
||||
];
|
||||
}
|
||||
|
||||
public function publishStory(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'media_id' => 'required',
|
||||
'duration' => 'required|integer|min:3|max:120',
|
||||
'can_reply' => 'required|boolean',
|
||||
'can_react' => 'required|boolean'
|
||||
'can_react' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$id = $request->input('media_id');
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$story = Story::whereProfileId($user->profile_id)
|
||||
->findOrFail($id);
|
||||
|
||||
|
@ -194,13 +190,13 @@ class StoryComposeController extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully published',
|
||||
'msg' => 'Successfully published',
|
||||
];
|
||||
}
|
||||
|
||||
public function apiV1Delete(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
|
@ -213,40 +209,40 @@ class StoryComposeController extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully deleted'
|
||||
'msg' => 'Successfully deleted',
|
||||
];
|
||||
}
|
||||
|
||||
public function compose(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
|
||||
return view('stories.compose');
|
||||
}
|
||||
|
||||
public function createPoll(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!config_cache('instance.polls.enabled'), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
abort_if(! config_cache('instance.polls.enabled'), 404);
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function publishStoryPoll(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'question' => 'required|string|min:6|max:140',
|
||||
'options' => 'required|array|min:2|max:4',
|
||||
'can_reply' => 'required|boolean',
|
||||
'can_react' => 'required|boolean'
|
||||
'can_react' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
$count = Story::whereProfileId($pid)
|
||||
|
@ -254,7 +250,7 @@ class StoryComposeController extends Controller
|
|||
->where('expires_at', '>', now())
|
||||
->count();
|
||||
|
||||
if($count >= Story::MAX_PER_DAY) {
|
||||
if ($count >= Story::MAX_PER_DAY) {
|
||||
abort(418, 'You have reached your limit for new Stories today.');
|
||||
}
|
||||
|
||||
|
@ -262,7 +258,7 @@ class StoryComposeController extends Controller
|
|||
$story->type = 'poll';
|
||||
$story->story = json_encode([
|
||||
'question' => $request->input('question'),
|
||||
'options' => $request->input('options')
|
||||
'options' => $request->input('options'),
|
||||
]);
|
||||
$story->public = false;
|
||||
$story->local = true;
|
||||
|
@ -278,7 +274,7 @@ class StoryComposeController extends Controller
|
|||
$poll->profile_id = $pid;
|
||||
$poll->poll_options = $request->input('options');
|
||||
$poll->expires_at = $story->expires_at;
|
||||
$poll->cached_tallies = collect($poll->poll_options)->map(function($o) {
|
||||
$poll->cached_tallies = collect($poll->poll_options)->map(function ($o) {
|
||||
return 0;
|
||||
})->toArray();
|
||||
$poll->save();
|
||||
|
@ -290,23 +286,23 @@ class StoryComposeController extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully published',
|
||||
'msg' => 'Successfully published',
|
||||
];
|
||||
}
|
||||
|
||||
public function storyPollVote(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'sid' => 'required',
|
||||
'ci' => 'required|integer|min:0|max:3'
|
||||
'ci' => 'required|integer|min:0|max:3',
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
$ci = $request->input('ci');
|
||||
$story = Story::findOrFail($request->input('sid'));
|
||||
abort_if(!FollowerService::follows($pid, $story->profile_id), 403);
|
||||
abort_if(! FollowerService::follows($pid, $story->profile_id), 403);
|
||||
$poll = Poll::whereStoryId($story->id)->firstOrFail();
|
||||
|
||||
$vote = new PollVote;
|
||||
|
@ -318,7 +314,7 @@ class StoryComposeController extends Controller
|
|||
$vote->save();
|
||||
|
||||
$poll->votes_count = $poll->votes_count + 1;
|
||||
$poll->cached_tallies = collect($poll->getTallies())->map(function($tally, $key) use($ci) {
|
||||
$poll->cached_tallies = collect($poll->getTallies())->map(function ($tally, $key) use ($ci) {
|
||||
return $ci == $key ? $tally + 1 : $tally;
|
||||
})->toArray();
|
||||
$poll->save();
|
||||
|
@ -328,15 +324,15 @@ class StoryComposeController extends Controller
|
|||
|
||||
public function storeReport(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'type' => 'required|alpha_dash',
|
||||
'id' => 'required|integer|min:1',
|
||||
'type' => 'required|alpha_dash',
|
||||
'id' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
$sid = $request->input('id');
|
||||
|
@ -353,24 +349,24 @@ class StoryComposeController extends Controller
|
|||
'copyright',
|
||||
'impersonation',
|
||||
'scam',
|
||||
'terrorism'
|
||||
'terrorism',
|
||||
];
|
||||
|
||||
abort_if(!in_array($type, $types), 422, 'Invalid story report type');
|
||||
abort_if(! in_array($type, $types), 422, 'Invalid story report type');
|
||||
|
||||
$story = Story::findOrFail($sid);
|
||||
|
||||
abort_if($story->profile_id == $pid, 422, 'Cannot report your own story');
|
||||
abort_if(!FollowerService::follows($pid, $story->profile_id), 422, 'Cannot report a story from an account you do not follow');
|
||||
abort_if(! FollowerService::follows($pid, $story->profile_id), 422, 'Cannot report a story from an account you do not follow');
|
||||
|
||||
if( Report::whereProfileId($pid)
|
||||
if (Report::whereProfileId($pid)
|
||||
->whereObjectType('App\Story')
|
||||
->whereObjectId($story->id)
|
||||
->exists()
|
||||
) {
|
||||
return response()->json(['error' => [
|
||||
'code' => 409,
|
||||
'message' => 'Cannot report the same story again'
|
||||
'message' => 'Cannot report the same story again',
|
||||
]], 409);
|
||||
}
|
||||
|
||||
|
@ -389,18 +385,18 @@ class StoryComposeController extends Controller
|
|||
|
||||
public function react(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$this->validate($request, [
|
||||
'sid' => 'required',
|
||||
'reaction' => 'required|string'
|
||||
'reaction' => 'required|string',
|
||||
]);
|
||||
$pid = $request->user()->profile_id;
|
||||
$text = $request->input('reaction');
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$story = Story::findOrFail($request->input('sid'));
|
||||
|
||||
abort_if(!$story->can_react, 422);
|
||||
abort_if(! $story->can_react, 422);
|
||||
abort_if(StoryService::reactCounter($story->id, $pid) >= 5, 422, 'You have already reacted to this story');
|
||||
|
||||
$status = new Status;
|
||||
|
@ -413,7 +409,7 @@ class StoryComposeController extends Controller
|
|||
$status->in_reply_to_profile_id = $story->profile_id;
|
||||
$status->entities = json_encode([
|
||||
'story_id' => $story->id,
|
||||
'reaction' => $text
|
||||
'reaction' => $text,
|
||||
]);
|
||||
$status->save();
|
||||
|
||||
|
@ -427,24 +423,24 @@ class StoryComposeController extends Controller
|
|||
'story_actor_username' => $request->user()->username,
|
||||
'story_id' => $story->id,
|
||||
'story_media_url' => url(Storage::url($story->path)),
|
||||
'reaction' => $text
|
||||
'reaction' => $text,
|
||||
]);
|
||||
$dm->save();
|
||||
|
||||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $story->profile_id,
|
||||
'from_id' => $pid
|
||||
'from_id' => $pid,
|
||||
],
|
||||
[
|
||||
'type' => 'story:react',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => false
|
||||
'is_hidden' => false,
|
||||
]
|
||||
);
|
||||
|
||||
if($story->local) {
|
||||
if ($story->local) {
|
||||
// generate notification
|
||||
$n = new Notification;
|
||||
$n->profile_id = $dm->to_id;
|
||||
|
@ -464,18 +460,18 @@ class StoryComposeController extends Controller
|
|||
|
||||
public function comment(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$this->validate($request, [
|
||||
'sid' => 'required',
|
||||
'caption' => 'required|string'
|
||||
'caption' => 'required|string',
|
||||
]);
|
||||
$pid = $request->user()->profile_id;
|
||||
$text = $request->input('caption');
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$story = Story::findOrFail($request->input('sid'));
|
||||
|
||||
abort_if(!$story->can_reply, 422);
|
||||
abort_if(! $story->can_reply, 422);
|
||||
|
||||
$status = new Status;
|
||||
$status->type = 'story:reply';
|
||||
|
@ -486,7 +482,7 @@ class StoryComposeController extends Controller
|
|||
$status->visibility = 'direct';
|
||||
$status->in_reply_to_profile_id = $story->profile_id;
|
||||
$status->entities = json_encode([
|
||||
'story_id' => $story->id
|
||||
'story_id' => $story->id,
|
||||
]);
|
||||
$status->save();
|
||||
|
||||
|
@ -500,24 +496,24 @@ class StoryComposeController extends Controller
|
|||
'story_actor_username' => $request->user()->username,
|
||||
'story_id' => $story->id,
|
||||
'story_media_url' => url(Storage::url($story->path)),
|
||||
'caption' => $text
|
||||
'caption' => $text,
|
||||
]);
|
||||
$dm->save();
|
||||
|
||||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $story->profile_id,
|
||||
'from_id' => $pid
|
||||
'from_id' => $pid,
|
||||
],
|
||||
[
|
||||
'type' => 'story:comment',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => false
|
||||
'is_hidden' => false,
|
||||
]
|
||||
);
|
||||
|
||||
if($story->local) {
|
||||
if ($story->local) {
|
||||
// generate notification
|
||||
$n = new Notification;
|
||||
$n->profile_id = $dm->to_id;
|
||||
|
|
|
@ -34,7 +34,7 @@ class StoryController extends StoryComposeController
|
|||
{
|
||||
public function recent(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
$user = $request->user();
|
||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||
return [];
|
||||
|
@ -117,7 +117,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function profile(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||
|
@ -176,7 +176,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function viewed(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'id' => 'required|min:1',
|
||||
|
@ -221,7 +221,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function exists(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
$user = $request->user();
|
||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||
return response()->json(false);
|
||||
|
@ -233,7 +233,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function iRedirect(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
abort_if(!$user, 404);
|
||||
|
@ -243,7 +243,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function viewers(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'sid' => 'required|string'
|
||||
|
@ -274,7 +274,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function remoteStory(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$profile = Profile::findOrFail($id);
|
||||
if($profile->user_id != null || $profile->domain == null) {
|
||||
|
@ -286,7 +286,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function pollResults(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'sid' => 'required|string'
|
||||
|
@ -304,7 +304,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function getActivityObject(Request $request, $username, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled'), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled'), 404);
|
||||
|
||||
if(!$request->wantsJson()) {
|
||||
return redirect('/stories/' . $username);
|
||||
|
|
|
@ -34,7 +34,7 @@ class UserEmailForgotController extends Controller
|
|||
'username.exists' => 'This username is no longer active or does not exist!'
|
||||
];
|
||||
|
||||
if(config('captcha.enabled') || config('captcha.active.login') || config('captcha.active.register')) {
|
||||
if((bool) config_cache('captcha.enabled')) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
$messages['h-captcha-response.required'] = 'You need to complete the captcha!';
|
||||
}
|
||||
|
|
|
@ -14,12 +14,12 @@ class Kernel extends HttpKernel
|
|||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace App\Http\Requests\Status;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use App\Media;
|
||||
use App\Status;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreStatusEditRequest extends FormRequest
|
||||
{
|
||||
|
@ -14,24 +14,25 @@ class StoreStatusEditRequest extends FormRequest
|
|||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$profile = $this->user()->profile;
|
||||
if($profile->status != null) {
|
||||
return false;
|
||||
}
|
||||
if($profile->unlisted == true && $profile->cw == true) {
|
||||
return false;
|
||||
}
|
||||
$types = [
|
||||
"photo",
|
||||
"photo:album",
|
||||
"photo:video:album",
|
||||
"reply",
|
||||
"text",
|
||||
"video",
|
||||
"video:album"
|
||||
];
|
||||
$scopes = ['public', 'unlisted', 'private'];
|
||||
$status = Status::whereNull('reblog_of_id')->whereIn('type', $types)->whereIn('scope', $scopes)->find($this->route('id'));
|
||||
$profile = $this->user()->profile;
|
||||
if ($profile->status != null) {
|
||||
return false;
|
||||
}
|
||||
if ($profile->unlisted == true && $profile->cw == true) {
|
||||
return false;
|
||||
}
|
||||
$types = [
|
||||
'photo',
|
||||
'photo:album',
|
||||
'photo:video:album',
|
||||
'reply',
|
||||
'text',
|
||||
'video',
|
||||
'video:album',
|
||||
];
|
||||
$scopes = ['public', 'unlisted', 'private'];
|
||||
$status = Status::whereNull('reblog_of_id')->whereIn('type', $types)->whereIn('scope', $scopes)->find($this->route('id'));
|
||||
|
||||
return $status && $this->user()->profile_id === $status->profile_id;
|
||||
}
|
||||
|
||||
|
@ -47,18 +48,18 @@ class StoreStatusEditRequest extends FormRequest
|
|||
'spoiler_text' => 'nullable|string|max:140',
|
||||
'sensitive' => 'sometimes|boolean',
|
||||
'media_ids' => [
|
||||
'nullable',
|
||||
'required_without:status',
|
||||
'array',
|
||||
'max:' . config('pixelfed.max_album_length'),
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
Media::whereProfileId($this->user()->profile_id)
|
||||
->where(function($query) {
|
||||
return $query->whereNull('status_id')
|
||||
->orWhere('status_id', '=', $this->route('id'));
|
||||
})
|
||||
->findOrFail($value);
|
||||
},
|
||||
'nullable',
|
||||
'required_without:status',
|
||||
'array',
|
||||
'max:'.(int) config_cache('pixelfed.max_album_length'),
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
Media::whereProfileId($this->user()->profile_id)
|
||||
->where(function ($query) {
|
||||
return $query->whereNull('status_id')
|
||||
->orWhere('status_id', '=', $this->route('id'));
|
||||
})
|
||||
->findOrFail($value);
|
||||
},
|
||||
],
|
||||
'location' => 'sometimes|nullable',
|
||||
'location.id' => 'sometimes|integer|min:1|max:128769',
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use App\Services\AccountService;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class AdminUser extends JsonResource
|
||||
{
|
||||
|
@ -18,8 +18,8 @@ class AdminUser extends JsonResource
|
|||
$account = AccountService::get($this->profile_id, true);
|
||||
|
||||
$res = [
|
||||
'id' => $this->id,
|
||||
'profile_id' => $this->profile_id,
|
||||
'id' => (string) $this->id,
|
||||
'profile_id' => (string) $this->profile_id,
|
||||
'name' => $this->name,
|
||||
'username' => $this->username,
|
||||
'is_admin' => (bool) $this->is_admin,
|
||||
|
@ -28,17 +28,18 @@ class AdminUser extends JsonResource
|
|||
'two_factor_enabled' => (bool) $this->{'2fa_enabled'},
|
||||
'register_source' => $this->register_source,
|
||||
'app_register_ip' => $this->app_register_ip,
|
||||
'has_interstitial' => (bool) $this->has_interstitial,
|
||||
'last_active_at' => $this->last_active_at,
|
||||
'created_at' => $this->created_at,
|
||||
];
|
||||
|
||||
if($account) {
|
||||
if ($account) {
|
||||
$res['avatar'] = $account['avatar'];
|
||||
$res['bio'] = $account['note_text'];
|
||||
$res['statuses_count'] = $account['statuses_count'];
|
||||
$res['following_count'] = $account['following_count'];
|
||||
$res['followers_count'] = $account['followers_count'];
|
||||
$res['is_private'] = $account['locked'];
|
||||
$res['statuses_count'] = (int) $account['statuses_count'];
|
||||
$res['following_count'] = (int) $account['following_count'];
|
||||
$res['followers_count'] = (int) $account['followers_count'];
|
||||
$res['is_private'] = (bool) $account['locked'];
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace App\Jobs\AvatarPipeline;
|
||||
|
||||
use Cache;
|
||||
use App\Avatar;
|
||||
use App\Profile;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
@ -17,88 +17,88 @@ use Storage;
|
|||
|
||||
class AvatarOptimize implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $profile;
|
||||
protected $current;
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile, $current)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->current = $current;
|
||||
}
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$avatar = $this->profile->avatar;
|
||||
$file = storage_path("app/$avatar->media_path");
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile, $current)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->current = $current;
|
||||
}
|
||||
|
||||
try {
|
||||
$img = Intervention::make($file)->orientate();
|
||||
$img->fit(200, 200, function ($constraint) {
|
||||
$constraint->upsize();
|
||||
});
|
||||
$quality = config_cache('pixelfed.image_quality');
|
||||
$img->save($file, $quality);
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$avatar = $this->profile->avatar;
|
||||
$file = storage_path("app/$avatar->media_path");
|
||||
|
||||
$avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail();
|
||||
$avatar->change_count = ++$avatar->change_count;
|
||||
$avatar->last_processed_at = Carbon::now();
|
||||
$avatar->save();
|
||||
Cache::forget('avatar:' . $avatar->profile_id);
|
||||
$this->deleteOldAvatar($avatar->media_path, $this->current);
|
||||
try {
|
||||
$img = Intervention::make($file)->orientate();
|
||||
$img->fit(200, 200, function ($constraint) {
|
||||
$constraint->upsize();
|
||||
});
|
||||
$quality = config_cache('pixelfed.image_quality');
|
||||
$img->save($file, $quality);
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud')) {
|
||||
$this->uploadToCloud($avatar);
|
||||
} else {
|
||||
$avatar->cdn_url = null;
|
||||
$avatar->save();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
$avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail();
|
||||
$avatar->change_count = ++$avatar->change_count;
|
||||
$avatar->last_processed_at = Carbon::now();
|
||||
$avatar->save();
|
||||
Cache::forget('avatar:'.$avatar->profile_id);
|
||||
$this->deleteOldAvatar($avatar->media_path, $this->current);
|
||||
|
||||
protected function deleteOldAvatar($new, $current)
|
||||
{
|
||||
if ( storage_path('app/'.$new) == $current ||
|
||||
Str::endsWith($current, 'avatars/default.png') ||
|
||||
Str::endsWith($current, 'avatars/default.jpg'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (is_file($current)) {
|
||||
@unlink($current);
|
||||
}
|
||||
}
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') && (bool) config_cache('instance.avatar.local_to_cloud')) {
|
||||
$this->uploadToCloud($avatar);
|
||||
} else {
|
||||
$avatar->cdn_url = null;
|
||||
$avatar->save();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
protected function uploadToCloud($avatar)
|
||||
{
|
||||
$base = 'cache/avatars/' . $avatar->profile_id;
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
$disk->deleteDirectory($base);
|
||||
$path = $base . '/' . 'avatar_' . strtolower(Str::random(random_int(3,6))) . $avatar->change_count . '.' . pathinfo($avatar->media_path, PATHINFO_EXTENSION);
|
||||
$url = $disk->put($path, Storage::get($avatar->media_path));
|
||||
$avatar->media_path = $path;
|
||||
$avatar->cdn_url = $disk->url($path);
|
||||
$avatar->save();
|
||||
Storage::delete($avatar->media_path);
|
||||
Cache::forget('avatar:' . $avatar->profile_id);
|
||||
}
|
||||
protected function deleteOldAvatar($new, $current)
|
||||
{
|
||||
if (storage_path('app/'.$new) == $current ||
|
||||
Str::endsWith($current, 'avatars/default.png') ||
|
||||
Str::endsWith($current, 'avatars/default.jpg')) {
|
||||
return;
|
||||
}
|
||||
if (is_file($current)) {
|
||||
@unlink($current);
|
||||
}
|
||||
}
|
||||
|
||||
protected function uploadToCloud($avatar)
|
||||
{
|
||||
$base = 'cache/avatars/'.$avatar->profile_id;
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
$disk->deleteDirectory($base);
|
||||
$path = $base.'/'.'avatar_'.strtolower(Str::random(random_int(3, 6))).$avatar->change_count.'.'.pathinfo($avatar->media_path, PATHINFO_EXTENSION);
|
||||
$url = $disk->put($path, Storage::get($avatar->media_path));
|
||||
$avatar->media_path = $path;
|
||||
$avatar->cdn_url = $disk->url($path);
|
||||
$avatar->save();
|
||||
Storage::delete($avatar->media_path);
|
||||
Cache::forget('avatar:'.$avatar->profile_id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,112 +4,107 @@ namespace App\Jobs\AvatarPipeline;
|
|||
|
||||
use App\Avatar;
|
||||
use App\Profile;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
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 Illuminate\Support\Str;
|
||||
use Zttp\Zttp;
|
||||
use App\Http\Controllers\AvatarController;
|
||||
use Storage;
|
||||
use Log;
|
||||
use Illuminate\Http\File;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\Services\ActivityPubFetchService;
|
||||
|
||||
class RemoteAvatarFetch implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $profile;
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
public $timeout = 300;
|
||||
public $maxExceptions = 1;
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
}
|
||||
public $timeout = 300;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
public $maxExceptions = 1;
|
||||
|
||||
if(boolval(config_cache('pixelfed.cloud_storage')) == false && boolval(config_cache('federation.avatars.store_local')) == false) {
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
}
|
||||
|
||||
if($profile->domain == null || $profile->private_key) {
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
|
||||
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == false && (bool) config_cache('federation.avatars.store_local') == false) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!$avatar) {
|
||||
$avatar = new Avatar;
|
||||
$avatar->profile_id = $profile->id;
|
||||
$avatar->save();
|
||||
}
|
||||
if ($profile->domain == null || $profile->private_key) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if($avatar->media_path == null && $avatar->remote_url == null) {
|
||||
$avatar->media_path = 'public/avatars/default.jpg';
|
||||
$avatar->is_remote = true;
|
||||
$avatar->save();
|
||||
}
|
||||
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||
|
||||
$person = Helpers::fetchFromUrl($profile->remote_url);
|
||||
if (! $avatar) {
|
||||
$avatar = new Avatar;
|
||||
$avatar->profile_id = $profile->id;
|
||||
$avatar->save();
|
||||
}
|
||||
|
||||
if(!$person || !isset($person['@context'])) {
|
||||
return 1;
|
||||
}
|
||||
if ($avatar->media_path == null && $avatar->remote_url == null) {
|
||||
$avatar->media_path = 'public/avatars/default.jpg';
|
||||
$avatar->is_remote = true;
|
||||
$avatar->save();
|
||||
}
|
||||
|
||||
if( !isset($person['icon']) ||
|
||||
!isset($person['icon']['type']) ||
|
||||
!isset($person['icon']['url'])
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
$person = Helpers::fetchFromUrl($profile->remote_url);
|
||||
|
||||
if($person['icon']['type'] !== 'Image') {
|
||||
return 1;
|
||||
}
|
||||
if (! $person || ! isset($person['@context'])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!Helpers::validateUrl($person['icon']['url'])) {
|
||||
return 1;
|
||||
}
|
||||
if (! isset($person['icon']) ||
|
||||
! isset($person['icon']['type']) ||
|
||||
! isset($person['icon']['url'])
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$icon = $person['icon'];
|
||||
if ($person['icon']['type'] !== 'Image') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$avatar->remote_url = $icon['url'];
|
||||
$avatar->save();
|
||||
if (! Helpers::validateUrl($person['icon']['url'])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true);
|
||||
$icon = $person['icon'];
|
||||
|
||||
return 1;
|
||||
}
|
||||
$avatar->remote_url = $icon['url'];
|
||||
$avatar->save();
|
||||
|
||||
MediaStorageService::avatar($avatar, (bool) config_cache('pixelfed.cloud_storage') == false, true);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,93 +4,88 @@ namespace App\Jobs\AvatarPipeline;
|
|||
|
||||
use App\Avatar;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\MediaStorageService;
|
||||
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 App\Util\ActivityPub\Helpers;
|
||||
use Illuminate\Support\Str;
|
||||
use Zttp\Zttp;
|
||||
use App\Http\Controllers\AvatarController;
|
||||
use Cache;
|
||||
use Storage;
|
||||
use Log;
|
||||
use Illuminate\Http\File;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\Services\ActivityPubFetchService;
|
||||
|
||||
class RemoteAvatarFetchFromUrl implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $profile;
|
||||
protected $url;
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
public $timeout = 300;
|
||||
public $maxExceptions = 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(Profile $profile, $url)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->url = $url;
|
||||
}
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
public $timeout = 300;
|
||||
|
||||
Cache::forget('avatar:' . $profile->id);
|
||||
AccountService::del($profile->id);
|
||||
public $maxExceptions = 1;
|
||||
|
||||
if(boolval(config_cache('pixelfed.cloud_storage')) == false && boolval(config_cache('federation.avatars.store_local')) == false) {
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile, $url)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
if($profile->domain == null || $profile->private_key) {
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
|
||||
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||
Cache::forget('avatar:'.$profile->id);
|
||||
AccountService::del($profile->id);
|
||||
|
||||
if(!$avatar) {
|
||||
$avatar = new Avatar;
|
||||
$avatar->profile_id = $profile->id;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->remote_url = $this->url;
|
||||
$avatar->save();
|
||||
} else {
|
||||
$avatar->remote_url = $this->url;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->save();
|
||||
}
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == false && (bool) config_cache('federation.avatars.store_local') == false) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true);
|
||||
if ($profile->domain == null || $profile->private_key) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||
|
||||
if (! $avatar) {
|
||||
$avatar = new Avatar;
|
||||
$avatar->profile_id = $profile->id;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->remote_url = $this->url;
|
||||
$avatar->save();
|
||||
} else {
|
||||
$avatar->remote_url = $this->url;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->save();
|
||||
}
|
||||
|
||||
MediaStorageService::avatar($avatar, (bool) config_cache('pixelfed.cloud_storage') == false, true);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,114 +4,115 @@ namespace App\Jobs\FollowPipeline;
|
|||
|
||||
use App\Follower;
|
||||
use App\FollowRequest;
|
||||
use App\Jobs\HomeFeedPipeline\FeedUnfollowPipeline;
|
||||
use App\Notification;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\NotificationService;
|
||||
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 Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\NotificationService;
|
||||
use App\Jobs\HomeFeedPipeline\FeedUnfollowPipeline;
|
||||
|
||||
class UnfollowPipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $actor;
|
||||
protected $target;
|
||||
protected $actor;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($actor, $target)
|
||||
{
|
||||
$this->actor = $actor;
|
||||
$this->target = $target;
|
||||
}
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$actor = $this->actor;
|
||||
$target = $this->target;
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($actor, $target)
|
||||
{
|
||||
$this->actor = $actor;
|
||||
$this->target = $target;
|
||||
}
|
||||
|
||||
$actorProfile = Profile::find($actor);
|
||||
if(!$actorProfile) {
|
||||
return;
|
||||
}
|
||||
$targetProfile = Profile::find($target);
|
||||
if(!$targetProfile) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$actor = $this->actor;
|
||||
$target = $this->target;
|
||||
|
||||
FeedUnfollowPipeline::dispatch($actor, $target)->onQueue('follow');
|
||||
$actorProfile = Profile::find($actor);
|
||||
if (! $actorProfile) {
|
||||
return;
|
||||
}
|
||||
$targetProfile = Profile::find($target);
|
||||
if (! $targetProfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
FollowerService::remove($actor, $target);
|
||||
FeedUnfollowPipeline::dispatch($actor, $target)->onQueue('follow');
|
||||
|
||||
$actorProfileSync = Cache::get(FollowerService::FOLLOWING_SYNC_KEY . $actor);
|
||||
if(!$actorProfileSync) {
|
||||
FollowServiceWarmCache::dispatch($actor)->onQueue('low');
|
||||
} else {
|
||||
if($actorProfile->following_count) {
|
||||
$actorProfile->decrement('following_count');
|
||||
} else {
|
||||
$count = Follower::whereProfileId($actor)->count();
|
||||
$actorProfile->following_count = $count;
|
||||
$actorProfile->save();
|
||||
}
|
||||
Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $actor, 1, 604800);
|
||||
AccountService::del($actor);
|
||||
}
|
||||
FollowerService::remove($actor, $target);
|
||||
|
||||
$targetProfileSync = Cache::get(FollowerService::FOLLOWERS_SYNC_KEY . $target);
|
||||
if(!$targetProfileSync) {
|
||||
FollowServiceWarmCache::dispatch($target)->onQueue('low');
|
||||
} else {
|
||||
if($targetProfile->followers_count) {
|
||||
$targetProfile->decrement('followers_count');
|
||||
} else {
|
||||
$count = Follower::whereFollowingId($target)->count();
|
||||
$targetProfile->followers_count = $count;
|
||||
$targetProfile->save();
|
||||
}
|
||||
Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $target, 1, 604800);
|
||||
AccountService::del($target);
|
||||
}
|
||||
$actorProfileSync = Cache::get(FollowerService::FOLLOWING_SYNC_KEY.$actor);
|
||||
if (! $actorProfileSync) {
|
||||
FollowServiceWarmCache::dispatch($actor)->onQueue('low');
|
||||
} else {
|
||||
if ($actorProfile->following_count) {
|
||||
$actorProfile->decrement('following_count');
|
||||
} else {
|
||||
$count = Follower::whereProfileId($actor)->count();
|
||||
$actorProfile->following_count = $count;
|
||||
$actorProfile->save();
|
||||
}
|
||||
Cache::put(FollowerService::FOLLOWING_SYNC_KEY.$actor, 1, 604800);
|
||||
AccountService::del($actor);
|
||||
}
|
||||
|
||||
if($targetProfile->domain == null) {
|
||||
Notification::withTrashed()
|
||||
->whereProfileId($target)
|
||||
->whereAction('follow')
|
||||
->whereActorId($actor)
|
||||
->whereItemId($target)
|
||||
->whereItemType('App\Profile')
|
||||
->get()
|
||||
->each(function($n) {
|
||||
NotificationService::del($n->profile_id, $n->id);
|
||||
$n->forceDelete();
|
||||
});
|
||||
}
|
||||
$targetProfileSync = Cache::get(FollowerService::FOLLOWERS_SYNC_KEY.$target);
|
||||
if (! $targetProfileSync) {
|
||||
FollowServiceWarmCache::dispatch($target)->onQueue('low');
|
||||
} else {
|
||||
if ($targetProfile->followers_count) {
|
||||
$targetProfile->decrement('followers_count');
|
||||
} else {
|
||||
$count = Follower::whereFollowingId($target)->count();
|
||||
$targetProfile->followers_count = $count;
|
||||
$targetProfile->save();
|
||||
}
|
||||
Cache::put(FollowerService::FOLLOWERS_SYNC_KEY.$target, 1, 604800);
|
||||
AccountService::del($target);
|
||||
}
|
||||
|
||||
if($actorProfile->domain == null && config('instance.timeline.home.cached')) {
|
||||
Cache::forget('pf:timelines:home:' . $actor);
|
||||
}
|
||||
if ($targetProfile->domain == null) {
|
||||
Notification::withTrashed()
|
||||
->whereProfileId($target)
|
||||
->whereAction('follow')
|
||||
->whereActorId($actor)
|
||||
->whereItemId($target)
|
||||
->whereItemType('App\Profile')
|
||||
->get()
|
||||
->each(function ($n) {
|
||||
NotificationService::del($n->profile_id, $n->id);
|
||||
$n->forceDelete();
|
||||
});
|
||||
}
|
||||
|
||||
FollowRequest::whereFollowingId($target)
|
||||
->whereFollowerId($actor)
|
||||
->delete();
|
||||
if ($actorProfile->domain == null && config('instance.timeline.home.cached')) {
|
||||
Cache::forget('pf:timelines:home:'.$actor);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
FollowRequest::whereFollowingId($target)
|
||||
->whereFollowerId($actor)
|
||||
->delete();
|
||||
|
||||
AccountService::del($target);
|
||||
AccountService::del($actor);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
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
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ class ImageOptimize implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if(config('pixelfed.optimize_image') == false) {
|
||||
if((bool) config_cache('pixelfed.optimize_image') == false) {
|
||||
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
||||
return;
|
||||
} else {
|
||||
|
|
|
@ -51,7 +51,7 @@ class ImageResize implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if(!config('pixelfed.optimize_image')) {
|
||||
if((bool) config_cache('pixelfed.optimize_image') === false) {
|
||||
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ class ImageUpdate implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if(config('pixelfed.optimize_image')) {
|
||||
if((bool) config_cache('pixelfed.optimize_image')) {
|
||||
if (in_array($media->mime, $this->protectedMimes) == true) {
|
||||
ImageOptimizer::optimize($thumb);
|
||||
if(!$media->skip_optimize) {
|
||||
|
|
|
@ -72,10 +72,12 @@ class FetchNodeinfoPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessin
|
|||
$instance->software = strtolower(strip_tags($software));
|
||||
$instance->user_count = Profile::whereDomain($instance->domain)->count();
|
||||
$instance->nodeinfo_last_fetched = now();
|
||||
$instance->last_crawled_at = now();
|
||||
$instance->save();
|
||||
}
|
||||
} else {
|
||||
$instance->delivery_timeout = 1;
|
||||
$instance->last_crawled_at = now();
|
||||
$instance->delivery_next_after = now()->addHours(14);
|
||||
$instance->save();
|
||||
}
|
||||
|
|
|
@ -3,27 +3,30 @@
|
|||
namespace App\Jobs\MediaPipeline;
|
||||
|
||||
use App\Media;
|
||||
use App\Services\Media\MediaHlsService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Services\Media\MediaHlsService;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
class MediaDeletePipeline implements ShouldBeUniqueUntilProcessing, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $media;
|
||||
protected $media;
|
||||
|
||||
public $timeout = 300;
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
public $maxExceptions = 1;
|
||||
|
||||
public $failOnTimeout = true;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
|
@ -38,7 +41,7 @@ class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
|||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'media:purge-job:id-' . $this->media->id;
|
||||
return 'media:purge-job:id-'.$this->media->id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,58 +54,58 @@ class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
|||
return [(new WithoutOverlapping("media:purge-job:id-{$this->media->id}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
public function __construct(Media $media)
|
||||
{
|
||||
$this->media = $media;
|
||||
}
|
||||
public function __construct(Media $media)
|
||||
{
|
||||
$this->media = $media;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$media = $this->media;
|
||||
$path = $media->media_path;
|
||||
$thumb = $media->thumbnail_path;
|
||||
public function handle()
|
||||
{
|
||||
$media = $this->media;
|
||||
$path = $media->media_path;
|
||||
$thumb = $media->thumbnail_path;
|
||||
|
||||
if(!$path) {
|
||||
return 1;
|
||||
}
|
||||
if (! $path) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$e = explode('/', $path);
|
||||
array_pop($e);
|
||||
$i = implode('/', $e);
|
||||
$e = explode('/', $path);
|
||||
array_pop($e);
|
||||
$i = implode('/', $e);
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == true) {
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
|
||||
if($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
if ($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
|
||||
if($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
}
|
||||
if ($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
}
|
||||
|
||||
$disk = Storage::disk(config('filesystems.local'));
|
||||
$disk = Storage::disk(config('filesystems.local'));
|
||||
|
||||
if($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
if ($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
|
||||
if($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
if ($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
|
||||
if($media->hls_path != null) {
|
||||
if ($media->hls_path != null) {
|
||||
$files = MediaHlsService::allFiles($media);
|
||||
if($files && count($files)) {
|
||||
foreach($files as $file) {
|
||||
if ($files && count($files)) {
|
||||
foreach ($files as $file) {
|
||||
$disk->delete($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$media->delete();
|
||||
$media->delete();
|
||||
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,68 +8,69 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MediaFixLocalFilesystemCleanupPipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 1800;
|
||||
public $tries = 5;
|
||||
public $maxExceptions = 1;
|
||||
public $timeout = 1800;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if(config_cache('pixelfed.cloud_storage') == false) {
|
||||
// Only run if cloud storage is enabled
|
||||
return;
|
||||
}
|
||||
public $tries = 5;
|
||||
|
||||
$disk = Storage::disk('local');
|
||||
$cloud = Storage::disk(config('filesystems.cloud'));
|
||||
public $maxExceptions = 1;
|
||||
|
||||
Media::whereNotNull(['status_id', 'cdn_url', 'replicated_at'])
|
||||
->chunk(20, function ($medias) use($disk, $cloud) {
|
||||
foreach($medias as $media) {
|
||||
if(!str_starts_with($media->media_path, 'public')) {
|
||||
continue;
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == false) {
|
||||
// Only run if cloud storage is enabled
|
||||
return;
|
||||
}
|
||||
|
||||
if($disk->exists($media->media_path) && $cloud->exists($media->media_path)) {
|
||||
$disk->delete($media->media_path);
|
||||
}
|
||||
$disk = Storage::disk('local');
|
||||
$cloud = Storage::disk(config('filesystems.cloud'));
|
||||
|
||||
if($media->thumbnail_path) {
|
||||
if($disk->exists($media->thumbnail_path)) {
|
||||
$disk->delete($media->thumbnail_path);
|
||||
}
|
||||
}
|
||||
Media::whereNotNull(['status_id', 'cdn_url', 'replicated_at'])
|
||||
->chunk(20, function ($medias) use ($disk, $cloud) {
|
||||
foreach ($medias as $media) {
|
||||
if (! str_starts_with($media->media_path, 'public')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paths = explode('/', $media->media_path);
|
||||
if(count($paths) === 7) {
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
if ($disk->exists($media->media_path) && $cloud->exists($media->media_path)) {
|
||||
$disk->delete($media->media_path);
|
||||
}
|
||||
|
||||
if(count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
if ($media->thumbnail_path) {
|
||||
if ($disk->exists($media->thumbnail_path)) {
|
||||
$disk->delete($media->thumbnail_path);
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
$paths = explode('/', $media->media_path);
|
||||
if (count($paths) === 7) {
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
|
||||
if(count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
if (count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
|
||||
if(count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
|
||||
if (count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue