diff --git a/CHANGELOG.md b/CHANGELOG.md index 768af7f21..1c893644b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,8 @@ - Update ApiV1Controller, fix pagination header. Fixes #3354 ([4fe07e6f](https://github.com/pixelfed/pixelfed/commit/4fe07e6f)) - Update ApiV1Controller, add optional place_id parameter to POST /api/v1/statuses endpoint ([ef0d1f84](https://github.com/pixelfed/pixelfed/commit/ef0d1f84)) - Update SettingsController, fix double json encoding and cache settings for 7 days ([4514ab1d](https://github.com/pixelfed/pixelfed/commit/4514ab1d)) +- Update ApiV1Controller, fix mute/block entities ([364adb43](https://github.com/pixelfed/pixelfed/commit/364adb43)) +- Update atom feed, remove invalid entities ([e362ef9e](https://github.com/pixelfed/pixelfed/commit/e362ef9e)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.11.3 (2022-05-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.2...v0.11.3) diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index e6f70bb95..42b5e6121 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -888,6 +888,7 @@ class ApiV1Controller extends Controller ->whereUserId($user->profile_id) ->whereFilterableType('App\Profile') ->whereFilterType('block') + ->orderByDesc('id') ->simplePaginate($limit) ->pluck('filterable_id') ->map(function($id) { @@ -895,7 +896,8 @@ class ApiV1Controller extends Controller }) ->filter(function($account) { return $account && isset($account['id']); - }); + }) + ->values(); return $this->json($blocked); } @@ -1750,6 +1752,7 @@ class ApiV1Controller extends Controller $mutes = UserFilter::whereUserId($user->profile_id) ->whereFilterableType('App\Profile') ->whereFilterType('mute') + ->orderByDesc('id') ->simplePaginate($limit) ->pluck('filterable_id') ->map(function($id) { @@ -1757,7 +1760,8 @@ class ApiV1Controller extends Controller }) ->filter(function($account) { return $account && isset($account['id']); - }); + }) + ->values(); return $this->json($mutes); } diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php index 75bd2b3e9..7dff38cbc 100644 --- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php @@ -3,17 +3,23 @@ namespace App\Http\Controllers\Api; use Cache; +use DB; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use App\AccountLog; +use App\EmailVerification; use App\Status; use App\Report; use App\Profile; use App\Services\AccountService; use App\Services\StatusService; use App\Services\ProfileStatusService; +use Jenssegers\Agent\Agent; +use Mail; +use App\Mail\PasswordChange; class ApiV1Dot1Controller extends Controller { @@ -204,4 +210,185 @@ class ApiV1Dot1Controller extends Controller return $this->json($res); } + + /** + * POST /api/v1.1/accounts/change-password + * + * @return \App\Transformer\Api\AccountTransformer + */ + public function accountChangePassword(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $this->validate($request, [ + 'current_password' => 'bail|required|current_password', + 'new_password' => 'required|min:' . config('pixelfed.min_password_length', 8), + 'confirm_password' => 'required|same:new_password' + ],[ + 'current_password' => 'The password you entered is incorrect' + ]); + + $user->password = bcrypt($request->input('new_password')); + $user->save(); + + $log = new AccountLog; + $log->user_id = $user->id; + $log->item_id = $user->id; + $log->item_type = 'App\User'; + $log->action = 'account.edit.password'; + $log->message = 'Password changed'; + $log->link = null; + $log->ip_address = $request->ip(); + $log->user_agent = $request->userAgent(); + $log->save(); + + Mail::to($request->user())->send(new PasswordChange($user)); + + return $this->json(AccountService::get($user->profile_id)); + } + + /** + * GET /api/v1.1/accounts/login-activity + * + * @return array + */ + public function accountLoginActivity(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + $agent = new Agent(); + $currentIp = $request->ip(); + + $activity = AccountLog::whereUserId($user->id) + ->whereAction('auth.login') + ->orderBy('created_at', 'desc') + ->groupBy('ip_address') + ->limit(10) + ->get() + ->map(function($item) use($agent, $currentIp) { + $agent->setUserAgent($item->user_agent); + return [ + 'id' => $item->id, + 'action' => $item->action, + 'ip' => $item->ip_address, + 'ip_current' => $item->ip_address === $currentIp, + 'is_mobile' => $agent->isMobile(), + 'device' => $agent->device(), + 'browser' => $agent->browser(), + 'platform' => $agent->platform(), + 'created_at' => $item->created_at->format('c') + ]; + }); + + return $this->json($activity); + } + + /** + * GET /api/v1.1/accounts/two-factor + * + * @return array + */ + public function accountTwoFactor(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $res = [ + 'active' => (bool) $user->{'2fa_enabled'}, + 'setup_at' => $user->{'2fa_setup_at'} + ]; + return $this->json($res); + } + + /** + * GET /api/v1.1/accounts/emails-from-pixelfed + * + * @return array + */ + public function accountEmailsFromPixelfed(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $emailVerifications = EmailVerification::whereUserId($user->id) + ->orderByDesc('id') + ->where('created_at', '>', now()->subDays(14)) + ->limit(10) + ->get() + ->map(function($mail) { + return [ + 'type' => 'Email Verification', + 'created_at' => $mail->created_at->format('c') + ]; + }) + ->toArray(); + + $passwordResets = DB::table('password_resets') + ->whereEmail($user->email) + ->where('created_at', '>', now()->subDays(14)) + ->orderByDesc('created_at') + ->limit(10) + ->get() + ->map(function($mail) { + return [ + 'type' => 'Password Reset', + 'created_at' => now()->parse($mail->created_at)->format('c') + ]; + }) + ->toArray(); + + $passwordChanges = AccountLog::whereUserId($user->id) + ->whereAction('account.edit.password') + ->where('created_at', '>', now()->subDays(14)) + ->orderByDesc('created_at') + ->limit(10) + ->get() + ->map(function($mail) { + return [ + 'type' => 'Password Change', + 'created_at' => $mail->created_at + ]; + }) + ->toArray(); + + $res = [ + 'email_verifications' => $emailVerifications, + 'password_resets' => $passwordResets, + 'password_changes' => $passwordChanges + ]; + + return $this->json($res); + } + + + /** + * GET /api/v1.1/accounts/apps-and-applications + * + * @return array + */ + public function accountApps(Request $request) + { + $user = $request->user(); + abort_if(!$user, 403); + abort_if($user->status != null, 403); + + $res = $user->tokens->map(function($token, $key) { + return [ + 'id' => $key + 1, + 'did' => encrypt($token->id), + 'name' => $token->name, + 'scopes' => $token->scopes, + 'revoked' => $token->revoked, + 'created_at' => $token->created_at, + 'expires_at' => $token->expires_at + ]; + }); + + return $this->json($res); + } } diff --git a/app/Services/LikeService.php b/app/Services/LikeService.php index 0046f2f1a..63da4e47e 100644 --- a/app/Services/LikeService.php +++ b/app/Services/LikeService.php @@ -10,20 +10,53 @@ use App\Like; class LikeService { const CACHE_KEY = 'pf:services:likes:ids:'; + const CACHE_SET_KEY = 'pf:services:likes:set:'; public static function add($profileId, $statusId) { $key = self::CACHE_KEY . $profileId . ':' . $statusId; Cache::increment('pf:services:likes:count:'.$statusId); Cache::forget('pf:services:likes:liked_by:'.$statusId); + self::setAdd($profileId, $statusId); return Cache::put($key, true, 86400); } + public static function setAdd($profileId, $statusId) + { + if(self::setCount($profileId) > 400) { + if(config('database.redis.client') === 'phpredis') { + Redis::zpopmin(self::CACHE_SET_KEY . $id); + } + } + + return Redis::zadd(self::CACHE_SET_KEY . $profileId, $statusId, $statusId); + } + + public static function setCount($id) + { + return Redis::zcard(self::CACHE_SET_KEY . $id); + } + + public static function setRem($profileId, $val) + { + return Redis::zrem(self::CACHE_SET_KEY . $profileId, $val); + } + + public static function get($profileId, $start = 0, $stop = 10) + { + if($stop > 100) { + $stop = 100; + } + + return Redis::zrevrange(self::CACHE_SET_KEY . $profileId, $start, $stop); + } + public static function remove($profileId, $statusId) { $key = self::CACHE_KEY . $profileId . ':' . $statusId; Cache::decrement('pf:services:likes:count:'.$statusId); Cache::forget('pf:services:likes:liked_by:'.$statusId); + self::setRem($profileId, $statusId); return Cache::put($key, false, 86400); } diff --git a/resources/views/atom/user.blade.php b/resources/views/atom/user.blade.php index 219c11ce4..792dd7466 100644 --- a/resources/views/atom/user.blade.php +++ b/resources/views/atom/user.blade.php @@ -7,20 +7,14 @@ {{$profile['username']}} on Pixelfed {{$profile['note']}} {{$profile['created_at']}} - - {{$profile['url']}} {{$profile['url']}} {{$profile['url']}} - {{$profile['note']}} - - -@foreach($items as $item) - - {{ strip_tags($item['content']) }} +@foreach($items as $item) + {{ $item['content'] ? strip_tags($item['content']) : "No caption" }} {{ $item['url'] }} diff --git a/routes/api.php b/routes/api.php index cc147aff5..4ba95cdb8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -99,8 +99,16 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::group(['prefix' => 'v1.1'], function() use($middleware) { Route::post('report', 'Api\ApiV1Dot1Controller@report')->middleware($middleware); - Route::delete('accounts/avatar', 'Api\ApiV1Dot1Controller@deleteAvatar')->middleware($middleware); - Route::get('accounts/{id}/posts', 'Api\ApiV1Dot1Controller@accountPosts')->middleware($middleware); + + Route::group(['prefix' => 'accounts'], function () use($middleware) { + Route::delete('avatar', 'Api\ApiV1Dot1Controller@deleteAvatar')->middleware($middleware); + Route::get('{id}/posts', 'Api\ApiV1Dot1Controller@accountPosts')->middleware($middleware); + Route::post('change-password', 'Api\ApiV1Dot1Controller@accountChangePassword')->middleware($middleware); + Route::get('login-activity', 'Api\ApiV1Dot1Controller@accountLoginActivity')->middleware($middleware); + Route::get('two-factor', 'Api\ApiV1Dot1Controller@accountTwoFactor')->middleware($middleware); + Route::get('emails-from-pixelfed', 'Api\ApiV1Dot1Controller@accountEmailsFromPixelfed')->middleware($middleware); + Route::get('apps-and-applications', 'Api\ApiV1Dot1Controller@accountApps')->middleware($middleware); + }); Route::group(['prefix' => 'direct'], function () use($middleware) { Route::get('thread', 'DirectMessageController@thread')->middleware($middleware);