diff --git a/app/Http/Controllers/Api/BaseApiController.php b/app/Http/Controllers/Api/BaseApiController.php index 6fa1073f2..491287398 100644 --- a/app/Http/Controllers/Api/BaseApiController.php +++ b/app/Http/Controllers/Api/BaseApiController.php @@ -118,7 +118,7 @@ class BaseApiController extends Controller $since_id = $request->since_id ?? false; $only_media = $request->only_media ?? false; $user = Auth::user(); - $account = Profile::findOrFail($id); + $account = Profile::whereNull('status')->findOrFail($id); $statuses = $account->statuses()->getQuery(); if($only_media == true) { $statuses = $statuses @@ -150,15 +150,6 @@ class BaseApiController extends Controller return response()->json($res); } - public function followSuggestions(Request $request) - { - $followers = Auth::user()->profile->recommendFollowers(); - $resource = new Fractal\Resource\Collection($followers, new AccountTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - - return response()->json($res); - } - public function avatarUpdate(Request $request) { $this->validate($request, [ @@ -197,14 +188,9 @@ class BaseApiController extends Controller public function showTempMedia(Request $request, int $profileId, $mediaId) { - if (!$request->hasValidSignature()) { - abort(401); - } - $profile = Auth::user()->profile; - if($profile->id !== $profileId) { - abort(403); - } - $media = Media::whereProfileId($profile->id)->findOrFail($mediaId); + abort_if(!$request->hasValidSignature(), 404); + abort_if(Auth::user()->profile_id !== $profileId, 404); + $media = Media::whereProfileId(Auth::user()->profile_id)->findOrFail($mediaId); $path = storage_path('app/'.$media->media_path); return response()->file($path); } diff --git a/app/Http/Controllers/ApiController.php b/app/Http/Controllers/ApiController.php index dfe48426d..16f906161 100644 --- a/app/Http/Controllers/ApiController.php +++ b/app/Http/Controllers/ApiController.php @@ -10,6 +10,7 @@ use App\{ UserFilter }; use Auth, Cache, Redis; +use App\Util\Site\Config; use Illuminate\Http\Request; use App\Services\SuggestionService; @@ -23,34 +24,7 @@ class ApiController extends BaseApiController public function siteConfiguration(Request $request) { - $res = Cache::remember('api:site:configuration', now()->addMinutes(30), function() { - return [ - 'uploader' => [ - 'max_photo_size' => config('pixelfed.max_photo_size'), - 'max_caption_length' => config('pixelfed.max_caption_length'), - 'album_limit' => config('pixelfed.max_album_length'), - 'image_quality' => config('pixelfed.image_quality'), - - 'optimize_image' => config('pixelfed.optimize_image'), - 'optimize_video' => config('pixelfed.optimize_video'), - - 'media_types' => config('pixelfed.media_types'), - 'enforce_account_limit' => config('pixelfed.enforce_account_limit') - ], - - 'activitypub' => [ - 'enabled' => config('federation.activitypub.enabled'), - 'remote_follow' => config('federation.activitypub.remoteFollow') - ], - - 'ab' => [ - 'lc' => config('exp.lc'), - 'rec' => config('exp.rec'), - 'loops' => config('exp.loops') - ], - ]; - }); - return response()->json($res); + return response()->json(Config::get()); } public function userRecommendations(Request $request) diff --git a/app/Http/Controllers/CollectionController.php b/app/Http/Controllers/CollectionController.php index 884064759..c2c62350d 100644 --- a/app/Http/Controllers/CollectionController.php +++ b/app/Http/Controllers/CollectionController.php @@ -34,7 +34,7 @@ class CollectionController extends Controller public function show(Request $request, int $collection) { - $collection = Collection::whereNotNull('published_at')->findOrFail($collection); + $collection = Collection::with('profile')->whereNotNull('published_at')->findOrFail($collection); if($collection->profile->status != null) { abort(404); } @@ -100,7 +100,11 @@ class CollectionController extends Controller $collection->items()->delete(); $collection->delete(); - return 200; + if($request->wantsJson()) { + return 200; + } + + return redirect('/'); } public function storeId(Request $request) diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 870af7340..7ab41a286 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -245,4 +245,10 @@ class ProfileController extends Controller } return view('profile.following', compact('user', 'profile', 'following', 'owner', 'is_following', 'is_admin', 'settings')); } + + public function meRedirect() + { + abort_if(!Auth::check(), 404); + return redirect(Auth::user()->url()); + } } diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index bfff71c47..245871a89 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -272,6 +272,7 @@ class PublicApiController extends Controller 'created_at', 'updated_at' )->where('id', $dir, $id) + ->with('profile', 'hashtags', 'mentions') ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album']) ->whereLocal(true) ->whereNull('uri') @@ -300,6 +301,7 @@ class PublicApiController extends Controller 'created_at', 'updated_at' )->whereIn('type', ['photo', 'photo:album', 'video', 'video:album']) + ->with('profile', 'hashtags', 'mentions') ->whereLocal(true) ->whereNull('uri') ->whereNotIn('profile_id', $filtered) @@ -378,6 +380,7 @@ class PublicApiController extends Controller 'created_at', 'updated_at' )->whereIn('type', ['photo', 'photo:album', 'video', 'video:album']) + ->with('profile', 'hashtags', 'mentions') ->where('id', $dir, $id) ->whereIn('profile_id', $following) ->whereNotIn('profile_id', $filtered) @@ -405,6 +408,7 @@ class PublicApiController extends Controller 'created_at', 'updated_at' )->whereIn('type', ['photo', 'photo:album', 'video', 'video:album']) + ->with('profile', 'hashtags', 'mentions') ->whereIn('profile_id', $following) ->whereNotIn('profile_id', $filtered) ->whereNull('in_reply_to_id') diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index fc336c301..85fe256f9 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -52,6 +52,7 @@ class SearchController extends Controller 'entity' => [ 'id' => $item->id, 'following' => $item->followedBy(Auth::user()->profile), + 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), 'thumb' => $item->avatarUrl() ] ]]; diff --git a/app/Jobs/AvatarPipeline/ImportAvatar.php b/app/Jobs/AvatarPipeline/ImportAvatar.php deleted file mode 100644 index 19beb23a7..000000000 --- a/app/Jobs/AvatarPipeline/ImportAvatar.php +++ /dev/null @@ -1,95 +0,0 @@ -url = $url; - $this->profile = $profile; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $url = $this->url; - $profile = $this->profile; - - $basePath = $this->buildPath(); - } - - public function buildPath() - { - $baseDir = storage_path('app/public/avatars'); - if (!is_dir($baseDir)) { - mkdir($baseDir); - } - - $prefix = $this->profile->id; - $padded = str_pad($prefix, 12, 0, STR_PAD_LEFT); - $parts = str_split($padded, 3); - foreach ($parts as $k => $part) { - if ($k == 0) { - $prefix = storage_path('app/public/avatars/'.$parts[0]); - if (!is_dir($prefix)) { - mkdir($prefix); - } - } - if ($k == 1) { - $prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1]); - if (!is_dir($prefix)) { - mkdir($prefix); - } - } - if ($k == 2) { - $prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2]); - if (!is_dir($prefix)) { - mkdir($prefix); - } - } - if ($k == 3) { - $avatarpath = 'public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2].'/'.$parts[3]; - $prefix = storage_path('app/'.$avatarpath); - if (!is_dir($prefix)) { - mkdir($prefix); - } - } - } - $dir = storage_path('app/'.$avatarpath); - if (!is_dir($dir)) { - mkdir($dir); - } - $path = $avatarpath.'/avatar.svg'; - return storage_path('app/'.$path); - } -} diff --git a/app/Media.php b/app/Media.php index 499d95b61..8c3de944b 100644 --- a/app/Media.php +++ b/app/Media.php @@ -34,10 +34,10 @@ class Media extends Model $url = $this->remote_url; } else { $path = $this->media_path; - $url = $this->cdn_url ?? Storage::url($path); + $url = $this->cdn_url ?? config('app.url') . Storage::url($path); } - return url($url); + return $url; } public function thumbnailUrl() diff --git a/app/Profile.php b/app/Profile.php index e46bedeed..e01ad368b 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -140,7 +140,7 @@ class Profile extends Model $version = hash('sha256', $avatar->change_count); $path = "{$path}?v={$version}"; - return url(Storage::url($path)); + return config('app.url') . Storage::url($path); }); return $url; diff --git a/app/Transformer/ActivityPub/ProfileTransformer.php b/app/Transformer/ActivityPub/ProfileTransformer.php index 97617bdf0..8172e3773 100644 --- a/app/Transformer/ActivityPub/ProfileTransformer.php +++ b/app/Transformer/ActivityPub/ProfileTransformer.php @@ -15,6 +15,9 @@ class ProfileTransformer extends Fractal\TransformerAbstract 'https://w3id.org/security/v1', [ 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', + 'PropertyValue' => 'schema:PropertyValue', + 'schema' => 'http://schema.org#', + 'value' => 'schema:value' ], ], 'id' => $profile->permalink(), diff --git a/app/Transformer/ActivityPub/Verb/Announce.php b/app/Transformer/ActivityPub/Verb/Announce.php index 682885b77..30330c91f 100644 --- a/app/Transformer/ActivityPub/Verb/Announce.php +++ b/app/Transformer/ActivityPub/Verb/Announce.php @@ -7,20 +7,20 @@ use League\Fractal; class Announce extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $status->permalink(), - 'type' => 'Announce', - 'actor' => $status->profile->permalink(), - 'to' => ['https://www.w3.org/ns/activitystreams#Public'], - 'cc' => [ - $status->profile->permalink(), - $status->profile->follower_url ?? $status->profile->permalink('/followers') - ], - 'published' => $status->created_at->format(DATE_ISO8601), - 'object' => $status->parent()->url(), - ]; - } + public function transform(Status $status) + { + return [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $status->permalink(), + 'type' => 'Announce', + 'actor' => $status->profile->permalink(), + 'to' => ['https://www.w3.org/ns/activitystreams#Public'], + 'cc' => [ + $status->profile->permalink(), + $status->profile->follower_url ?? $status->profile->permalink('/followers') + ], + 'published' => $status->created_at->format(DATE_ISO8601), + 'object' => $status->parent()->url(), + ]; + } } \ No newline at end of file diff --git a/app/Transformer/Api/DirectMessageTransformer.php b/app/Transformer/Api/DirectMessageTransformer.php new file mode 100644 index 000000000..d17f08527 --- /dev/null +++ b/app/Transformer/Api/DirectMessageTransformer.php @@ -0,0 +1,23 @@ + $dm->id, + 'to_id' => $dm->to_id, + 'from_id' => $dm->from_id, + 'from_profile_ids' => $dm->from_profile_ids, + 'group_message' => $dm->group_message, + 'status_id' => $dm->status_id, + 'read_at' => $dm->read_at, + 'created_at' => $dm->created_at + ]; + } +} diff --git a/app/Transformer/Api/RelationshipTransformer.php b/app/Transformer/Api/RelationshipTransformer.php index 280d72392..89e1decd8 100644 --- a/app/Transformer/Api/RelationshipTransformer.php +++ b/app/Transformer/Api/RelationshipTransformer.php @@ -3,7 +3,10 @@ namespace App\Transformer\Api; use Auth; -use App\Profile; +use App\{ + FollowRequest, + Profile +}; use League\Fractal; class RelationshipTransformer extends Fractal\TransformerAbstract @@ -12,6 +15,12 @@ class RelationshipTransformer extends Fractal\TransformerAbstract { $auth = Auth::check(); $user = $auth ? Auth::user()->profile : false; + $requested = false; + if($user) { + $requested = FollowRequest::whereFollowerId($user->id) + ->whereFollowingId($profile->id) + ->exists(); + } return [ 'id' => (string) $profile->id, 'following' => $auth ? $user->follows($profile) : false, @@ -19,7 +28,7 @@ class RelationshipTransformer extends Fractal\TransformerAbstract 'blocking' => $auth ? $user->blockedIds()->contains($profile->id) : false, 'muting' => $auth ? $user->mutedIds()->contains($profile->id) : false, 'muting_notifications' => null, - 'requested' => null, + 'requested' => $requested, 'domain_blocking' => null, 'showing_reblogs' => null, 'endorsed' => false diff --git a/app/Util/Media/Image.php b/app/Util/Media/Image.php index e519b4714..feabbd260 100644 --- a/app/Util/Media/Image.php +++ b/app/Util/Media/Image.php @@ -110,14 +110,15 @@ class Image $orientation = $ratio['orientation']; try { - $img = Intervention::make($file)->orientate(); + $img = Intervention::make($file); + $metadata = $img->exif(); + $img->orientate(); if($thumbnail) { $img->resize($aspect['width'], $aspect['height'], function ($constraint) { $constraint->aspectRatio(); }); } else { if(config('media.exif.database', false) == true) { - $metadata = $img->exif(); $media->metadata = json_encode($metadata); } diff --git a/app/Util/Site/Config.php b/app/Util/Site/Config.php new file mode 100644 index 000000000..3e6c5351f --- /dev/null +++ b/app/Util/Site/Config.php @@ -0,0 +1,47 @@ +addMinutes(30), function() { + return [ + 'uploader' => [ + 'max_photo_size' => config('pixelfed.max_photo_size'), + 'max_caption_length' => config('pixelfed.max_caption_length'), + 'album_limit' => config('pixelfed.max_album_length'), + 'image_quality' => config('pixelfed.image_quality'), + + 'optimize_image' => config('pixelfed.optimize_image'), + 'optimize_video' => config('pixelfed.optimize_video'), + + 'media_types' => config('pixelfed.media_types'), + 'enforce_account_limit' => config('pixelfed.enforce_account_limit') + ], + + 'activitypub' => [ + 'enabled' => config('federation.activitypub.enabled'), + 'remote_follow' => config('federation.activitypub.remoteFollow') + ], + + 'ab' => [ + 'lc' => config('exp.lc'), + 'rec' => config('exp.rec'), + 'loops' => config('exp.loops') + ], + + 'site' => [ + 'domain' => config('pixelfed.domain.app'), + 'url' => config('app.url') + ] + ]; + }); + } + + public static function json() { + return json_encode(self::get(), JSON_FORCE_OBJECT); + } +} diff --git a/public/css/appdark.css b/public/css/appdark.css index d04397c95..18d3d2cb1 100644 Binary files a/public/css/appdark.css and b/public/css/appdark.css differ diff --git a/public/js/app.js b/public/js/app.js index cc5c1e1e7..867555714 100644 Binary files a/public/js/app.js and b/public/js/app.js differ diff --git a/public/js/collections.js b/public/js/collections.js index 3ba40ec9e..f7f5f605f 100644 Binary files a/public/js/collections.js and b/public/js/collections.js differ diff --git a/public/js/components.js b/public/js/components.js index 2cc54d9d3..317187b5b 100644 Binary files a/public/js/components.js and b/public/js/components.js differ diff --git a/public/js/compose.js b/public/js/compose.js index 4d9736738..98cb8a057 100644 Binary files a/public/js/compose.js and b/public/js/compose.js differ diff --git a/public/js/discover.js b/public/js/discover.js index 9bfa6330d..42f879f69 100644 Binary files a/public/js/discover.js and b/public/js/discover.js differ diff --git a/public/js/profile.js b/public/js/profile.js index 92660827a..1c121f664 100644 Binary files a/public/js/profile.js and b/public/js/profile.js differ diff --git a/public/js/timeline.js b/public/js/timeline.js index 1bb76747e..77260e4cd 100644 Binary files a/public/js/timeline.js and b/public/js/timeline.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index b56897802..2e4047772 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index ad0c293af..b16d71c3b 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -11,5 +11,11 @@ let token = document.head.querySelector('meta[name="csrf-token"]'); if (token) { window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; } else { - console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); + console.error('CSRF token not found.'); } + +window.App = window.App || {}; + +window.App.boot = function() { + new Vue({ el: '#content'}); +} \ No newline at end of file diff --git a/resources/assets/js/components.js b/resources/assets/js/components.js index a4dbf0613..026b496fe 100644 --- a/resources/assets/js/components.js +++ b/resources/assets/js/components.js @@ -3,7 +3,6 @@ import BootstrapVue from 'bootstrap-vue' import InfiniteLoading from 'vue-infinite-loading'; import Loading from 'vue-loading-overlay'; import VueTimeago from 'vue-timeago'; -//import {Howl, Howler} from 'howler'; Vue.use(BootstrapVue); Vue.use(InfiniteLoading); @@ -36,126 +35,8 @@ try { } window.filesize = require('filesize'); -// window.Plyr = require('plyr'); import swal from 'sweetalert'; -// require('./components/localstorage'); -// require('./components/commentform'); -//require('./components/searchform'); -// require('./components/bookmarkform'); -// require('./components/statusform'); -//require('./components/embed'); -//require('./components/notifications'); - -// import Echo from "laravel-echo" - -// window.io = require('socket.io-client'); - -// window.pixelfed.bootEcho = function() { -// window.Echo = new Echo({ -// broadcaster: 'socket.io', -// host: window.location.hostname + ':2096', -// auth: { -// headers: { -// Authorization: 'Bearer ' + token.content, -// }, -// }, -// }); -// } - -// Initialize Notification Helper -window.pixelfed.n = {}; - -// Vue.component( -// 'search-results', -// require('./components/SearchResults.vue').default -// ); - -// Vue.component( -// 'photo-presenter', -// require('./components/presenter/PhotoPresenter.vue').default -// ); - -// Vue.component( -// 'video-presenter', -// require('./components/presenter/VideoPresenter.vue').default -// ); - -// Vue.component( -// 'photo-album-presenter', -// require('./components/presenter/PhotoAlbumPresenter.vue').default -// ); - -// Vue.component( -// 'video-album-presenter', -// require('./components/presenter/VideoAlbumPresenter.vue').default -// ); - -// Vue.component( -// 'mixed-album-presenter', -// require('./components/presenter/MixedAlbumPresenter.vue').default -// ); - -// Vue.component( -// 'post-menu', -// require('./components/PostMenu.vue').default -// ); - - -// Vue.component( -// 'passport-clients', -// require('./components/passport/Clients.vue').default -// ); - -// Vue.component( -// 'passport-authorized-clients', -// require('./components/passport/AuthorizedClients.vue').default -// ); - -// Vue.component( -// 'passport-personal-access-tokens', -// require('./components/passport/PersonalAccessTokens.vue').default -// ); - - - -// Vue.component( -// 'follow-suggestions', -// require('./components/FollowSuggestions.vue').default -// ); - -// Vue.component( -// 'circle-panel', -// require('./components/CirclePanel.vue') -// ); - -// Vue.component( -// 'story-compose', -// require('./components/StoryCompose.vue').default -// ); - -//import 'promise-polyfill/src/polyfill'; - -// window.pixelfed.copyToClipboard = (str) => { -// const el = document.createElement('textarea'); -// el.value = str; -// el.setAttribute('readonly', ''); -// el.style.position = 'absolute'; -// el.style.left = '-9999px'; -// document.body.appendChild(el); -// const selected = -// document.getSelection().rangeCount > 0 -// ? document.getSelection().getRangeAt(0) -// : false; -// el.select(); -// document.execCommand('copy'); -// document.body.removeChild(el); -// if (selected) { -// document.getSelection().removeAllRanges(); -// document.getSelection().addRange(selected); -// } -// }; - $(document).ready(function() { $(function () { $('[data-toggle="tooltip"]').tooltip() diff --git a/resources/assets/js/components/CollectionComponent.vue b/resources/assets/js/components/CollectionComponent.vue index 53d9739b8..733601ba0 100644 --- a/resources/assets/js/components/CollectionComponent.vue +++ b/resources/assets/js/components/CollectionComponent.vue @@ -1,47 +1,121 @@ - + \ No newline at end of file diff --git a/resources/assets/js/components/ComposeModal.vue b/resources/assets/js/components/ComposeModal.vue index 17b8e7359..7599b1bbd 100644 --- a/resources/assets/js/components/ComposeModal.vue +++ b/resources/assets/js/components/ComposeModal.vue @@ -37,7 +37,12 @@

Add Photo

-

{{composeMessage()}}

+
+

{{composeMessage()}}

+

Accepted Formats: {{acceptedFormats()}}

+

Max File Size: {{maxSize()}}

+

Albums can contain up to {{config.uploader.album_limit}} photos or videos

+
@@ -173,19 +178,20 @@ {{composeText.length}} / {{config.uploader.max_caption_length}}
-
+ + Delete - ->
-
+ --> + @@ -218,11 +224,7 @@ export default { data() { return { - config: { - uploader: { - media_types: '', - } - }, + config: window.App.config, profile: {}, composeText: '', composeTextLength: 0, @@ -241,7 +243,6 @@ export default { }, beforeMount() { - this.fetchConfig(); this.fetchProfile(); }, @@ -290,20 +291,9 @@ export default { ['Willow','filter-willow'], ['X-Pro II','filter-xpro-ii'] ]; - }, methods: { - - fetchConfig() { - axios.get('/api/v2/config').then(res => { - this.config = res.data; - if(this.config.uploader.media_types.includes('video/mp4') == false) { - this.composeType = 'post' - } - }); - }, - fetchProfile() { axios.get('/api/v1/accounts/verify_credentials').then(res => { this.profile = res.data; @@ -311,7 +301,6 @@ export default { this.visibility = 'private'; } }).catch(err => { - console.log(err) }); }, @@ -464,6 +453,11 @@ export default { let data = res.data; window.location.href = data; }).catch(err => { + let res = err.response.data; + if(res.message == 'Too Many Attempts.') { + swal('You\'re posting too much!', 'We only allow 50 posts per hour or 100 per day. If you\'ve reached that limit, please try again later. If you think this is an error, please contact an administrator.', 'error'); + return; + } swal('Oops, something went wrong!', 'An unexpected error occurred.', 'error'); }); return; @@ -511,6 +505,18 @@ export default { createCollection() { window.location.href = '/i/collections/create'; + }, + + maxSize() { + let limit = this.config.uploader.max_photo_size; + return limit / 1000 + ' MB'; + }, + + acceptedFormats() { + let formats = this.config.uploader.media_types; + return formats.split(',').map(f => { + return ' ' + f.split('/')[1]; + }).toString(); } } } diff --git a/resources/assets/js/components/DiscoverComponent.vue b/resources/assets/js/components/DiscoverComponent.vue index 0fae3c89d..4da2e3fb6 100644 --- a/resources/assets/js/components/DiscoverComponent.vue +++ b/resources/assets/js/components/DiscoverComponent.vue @@ -1,122 +1,105 @@ + \ No newline at end of file diff --git a/resources/assets/js/components/Profile.vue b/resources/assets/js/components/Profile.vue index deccf4e76..53a86f37c 100644 --- a/resources/assets/js/components/Profile.vue +++ b/resources/assets/js/components/Profile.vue @@ -1,5 +1,19 @@