Merge pull request #5237 from pixelfed/staging

Add Push Notifications
This commit is contained in:
daniel 2024-07-22 23:19:26 -06:00 committed by GitHub
commit 15a69f0688
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 458 additions and 269 deletions

View file

@ -7,7 +7,7 @@ jobs:
build: build:
docker: docker:
# Specify the version you desire here # Specify the version you desire here
- image: cimg/php:8.2.5 - image: cimg/php:8.3.8
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images

View file

@ -37,6 +37,7 @@ use Jenssegers\Agent\Agent;
use League\Fractal; use League\Fractal;
use League\Fractal\Serializer\ArraySerializer; use League\Fractal\Serializer\ArraySerializer;
use Mail; use Mail;
use NotificationChannels\Expo\ExpoPushToken;
class ApiV1Dot1Controller extends Controller class ApiV1Dot1Controller extends Controller
{ {
@ -1008,4 +1009,65 @@ class ApiV1Dot1Controller extends Controller
return $this->json($account, 200, $rateLimiting ? $limits : []); return $this->json($account, 200, $rateLimiting ? $limits : []);
} }
public function getExpoPushNotifications(Request $request)
{
abort_if(! $request->user() || ! $request->user()->token(), 403);
abort_unless($request->user()->tokenCan('push'), 403);
abort_unless(config('services.expo.access_token') && strlen(config('services.expo.access_token')) > 10, 404, 'Push notifications are not supported on this server.');
$user = $request->user();
$res = [
'expo_token' => (bool) $user->expo_token,
'notify_like' => (bool) $user->notify_like,
'notify_follow' => (bool) $user->notify_follow,
'notify_mention' => (bool) $user->notify_mention,
'notify_comment' => (bool) $user->notify_comment,
];
return $this->json($res);
}
public function disableExpoPushNotifications(Request $request)
{
abort_if(! $request->user() || ! $request->user()->token(), 403);
abort_unless($request->user()->tokenCan('push'), 403);
abort_unless(config('services.expo.access_token') && strlen(config('services.expo.access_token')) > 10, 404, 'Push notifications are not supported on this server.');
$request->user()->update([
'expo_token' => null,
]);
return $this->json(['expo_token' => null]);
}
public function updateExpoPushNotifications(Request $request)
{
abort_if(! $request->user() || ! $request->user()->token(), 403);
abort_unless($request->user()->tokenCan('push'), 403);
abort_unless(config('services.expo.access_token') && strlen(config('services.expo.access_token')) > 10, 404, 'Push notifications are not supported on this server.');
$this->validate($request, [
'expo_token' => ['required', ExpoPushToken::rule()],
'notify_like' => 'sometimes',
'notify_follow' => 'sometimes',
'notify_mention' => 'sometimes',
'notify_comment' => 'sometimes',
]);
$user = $request->user()->update([
'expo_token' => $request->input('expo_token'),
'notify_like' => $request->has('notify_like') && $request->boolean('notify_like'),
'notify_follow' => $request->has('notify_follow') && $request->boolean('notify_follow'),
'notify_mention' => $request->has('notify_mention') && $request->boolean('notify_mention'),
'notify_comment' => $request->has('notify_comment') && $request->boolean('notify_comment'),
]);
$res = [
'expo_token' => (bool) $request->user()->expo_token,
'notify_like' => (bool) $request->user()->notify_like,
'notify_follow' => (bool) $request->user()->notify_follow,
'notify_mention' => (bool) $request->user()->notify_mention,
'notify_comment' => (bool) $request->user()->notify_comment,
];
return $this->json($res);
}
} }

View file

@ -2,28 +2,35 @@
namespace App; namespace App;
use Laravel\Passport\HasApiTokens; use App\Services\AvatarService;
use Illuminate\Notifications\Notifiable; use App\Util\RateLimit\User as UserRateLimit;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Util\RateLimit\User as UserRateLimit; use Illuminate\Notifications\Notifiable;
use App\Services\AvatarService; use Laravel\Passport\HasApiTokens;
use NotificationChannels\Expo\ExpoPushToken;
use NotificationChannels\WebPush\HasPushSubscriptions;
class User extends Authenticatable class User extends Authenticatable
{ {
use Notifiable, SoftDeletes, HasApiTokens, UserRateLimit; use HasApiTokens, HasFactory, HasPushSubscriptions, Notifiable, SoftDeletes, UserRateLimit;
/** /**
* The attributes that should be mutated to dates. * The attributes that should be mutated to dates.
* *
* @var array * @var array
*/ */
protected $casts = [ protected function casts(): array
{
return [
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'2fa_setup_at' => 'datetime', '2fa_setup_at' => 'datetime',
'last_active_at' => 'datetime', 'last_active_at' => 'datetime',
'expo_token' => ExpoPushToken::class,
]; ];
}
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -38,7 +45,12 @@ class User extends Authenticatable
'app_register_ip', 'app_register_ip',
'email_verified_at', 'email_verified_at',
'last_active_at', 'last_active_at',
'register_source' 'register_source',
'expo_token',
'notify_like',
'notify_follow',
'notify_mention',
'notify_comment',
]; ];
/** /**
@ -50,7 +62,7 @@ class User extends Authenticatable
'email', 'password', 'is_admin', 'remember_token', 'email', 'password', 'is_admin', 'remember_token',
'email_verified_at', '2fa_enabled', '2fa_secret', 'email_verified_at', '2fa_enabled', '2fa_secret',
'2fa_backup_codes', '2fa_setup_at', 'deleted_at', '2fa_backup_codes', '2fa_setup_at', 'deleted_at',
'updated_at' 'updated_at',
]; ];
public function profile() public function profile()
@ -115,4 +127,8 @@ class User extends Authenticatable
return AvatarService::get($this->profile_id); return AvatarService::get($this->profile_id);
} }
public function routeNotificationForExpo(): ?ExpoPushToken
{
return $this->expo_token;
}
} }

View file

@ -19,6 +19,7 @@
"doctrine/dbal": "^3.0", "doctrine/dbal": "^3.0",
"intervention/image": "^2.4", "intervention/image": "^2.4",
"jenssegers/agent": "^2.6", "jenssegers/agent": "^2.6",
"laravel-notification-channels/expo": "~1.3.0|~2.0.0",
"laravel-notification-channels/webpush": "^8.0", "laravel-notification-channels/webpush": "^8.0",
"laravel/framework": "^11.0", "laravel/framework": "^11.0",
"laravel/helpers": "^1.1", "laravel/helpers": "^1.1",

567
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -36,7 +36,7 @@ return [
'network' => [ 'network' => [
'cached' => env('PF_NETWORK_TIMELINE') ? env('INSTANCE_NETWORK_TIMELINE_CACHED', false) : false, 'cached' => env('PF_NETWORK_TIMELINE') ? env('INSTANCE_NETWORK_TIMELINE_CACHED', false) : false,
'cache_dropoff' => env('INSTANCE_NETWORK_TIMELINE_CACHE_DROPOFF', 100), 'cache_dropoff' => env('INSTANCE_NETWORK_TIMELINE_CACHE_DROPOFF', 100),
'max_hours_old' => env('INSTANCE_NETWORK_TIMELINE_CACHE_MAX_HOUR_INGEST', 6), 'max_hours_old' => env('INSTANCE_NETWORK_TIMELINE_CACHE_MAX_HOUR_INGEST', 2160),
], ],
], ],

View file

@ -35,4 +35,7 @@ return [
'secret' => env('STRIPE_SECRET'), 'secret' => env('STRIPE_SECRET'),
], ],
'expo' => [
'access_token' => env('EXPO_ACCESS_TOKEN'),
],
]; ];

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('expo_token')->nullable();
$table->boolean('notify_like')->default(true);
$table->boolean('notify_follow')->default(true);
$table->boolean('notify_mention')->default(true);
$table->boolean('notify_comment')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('expo_token');
$table->dropColumn('notify_like');
$table->dropColumn('notify_follow');
$table->dropColumn('notify_mention');
$table->dropColumn('notify_comment');
});
}
};

View file

@ -194,6 +194,12 @@ Route::group(['prefix' => 'api'], function() use($middleware) {
Route::post('invite/admin/uc', 'AdminInviteController@apiUsernameCheck')->middleware('throttle:20,120'); Route::post('invite/admin/uc', 'AdminInviteController@apiUsernameCheck')->middleware('throttle:20,120');
Route::post('invite/admin/ec', 'AdminInviteController@apiEmailCheck')->middleware('throttle:10,1440'); Route::post('invite/admin/ec', 'AdminInviteController@apiEmailCheck')->middleware('throttle:10,1440');
}); });
Route::group(['prefix' => 'expo'], function() use($middleware) {
Route::get('push-notifications', 'Api\ApiV1Dot1Controller@getExpoPushNotifications')->middleware($middleware);
Route::post('push-notifications/update', 'Api\ApiV1Dot1Controller@updateExpoPushNotifications')->middleware($middleware);
Route::post('push-notifications/disable', 'Api\ApiV1Dot1Controller@disableExpoPushNotifications')->middleware($middleware);
});
}); });
Route::group(['prefix' => 'live'], function() use($middleware) { Route::group(['prefix' => 'live'], function() use($middleware) {