diff --git a/CHANGELOG.md b/CHANGELOG.md index 1083ed8e1..bdfa38426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,15 @@ # Release Notes -## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.8...dev) +## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.9...dev) + +## [v0.11.9 (2023-08-06)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9) ### Added - Import from Instagram ([#4466](https://github.com/pixelfed/pixelfed/pull/4466)) ([cf3078c5](https://github.com/pixelfed/pixelfed/commit/cf3078c5)) - Sign-in with Mastodon ([#4545](https://github.com/pixelfed/pixelfed/pull/4545)) ([45b9404e](https://github.com/pixelfed/pixelfed/commit/45b9404e)) - Health check endpoint at /api/service/health-check ([ff58f970](https://github.com/pixelfed/pixelfed/commit/ff58f970)) - Reblogs in home feed ([#4563](https://github.com/pixelfed/pixelfed/pull/4563)) ([b86d47bf](https://github.com/pixelfed/pixelfed/commit/b86d47bf)) +- Account Migrations ([#4578](https://github.com/pixelfed/pixelfed/pull/4578)) ([a9220e4e](https://github.com/pixelfed/pixelfed/commit/a9220e4e)) ### Updates - Update Notifications.vue component, fix filtering logic to prevent endless spinner ([3df9b53f](https://github.com/pixelfed/pixelfed/commit/3df9b53f)) @@ -50,6 +53,11 @@ - Update Timeline component, improve reblog support ([29de91e5](https://github.com/pixelfed/pixelfed/commit/29de91e5)) - Update timeline settings, add photo reblogs only option ([e2705b9a](https://github.com/pixelfed/pixelfed/commit/e2705b9a)) - Update PostContent, add text cw warning ([911504fa](https://github.com/pixelfed/pixelfed/commit/911504fa)) +- Update ActivityPubFetchService, add validateUrl parameter to bypass url validation to fetch content from blocked instances ([3d1b6516](https://github.com/pixelfed/pixelfed/commit/3d1b6516)) +- Update RemoteStatusDelete pipeline ([71e92261](https://github.com/pixelfed/pixelfed/commit/71e92261)) +- Update RemoteStatusDelete pipeline ([fab8f25e](https://github.com/pixelfed/pixelfed/commit/fab8f25e)) +- Update RemoteStatusPipeline, fix reply check ([618b6727](https://github.com/pixelfed/pixelfed/commit/618b6727)) +- ([](https://github.com/pixelfed/pixelfed/commit/)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.11.8 (2023-05-29)](https://github.com/pixelfed/pixelfed/compare/v0.11.7...v0.11.8) diff --git a/app/Http/Controllers/ProfileAliasController.php b/app/Http/Controllers/ProfileAliasController.php new file mode 100644 index 000000000..024005a8e --- /dev/null +++ b/app/Http/Controllers/ProfileAliasController.php @@ -0,0 +1,64 @@ +middleware('auth'); + } + + public function index(Request $request) + { + $aliases = $request->user()->profile->aliases; + return view('settings.aliases.index', compact('aliases')); + } + + public function store(Request $request) + { + $this->validate($request, [ + 'acct' => 'required' + ]); + + $acct = $request->input('acct'); + + if($request->user()->profile->aliases->count() >= 3) { + return back()->with('error', 'You can only add 3 account aliases.'); + } + + $webfingerService = WebfingerService::lookup($acct); + if(!$webfingerService || !isset($webfingerService['url'])) { + return back()->with('error', 'Invalid account, cannot add alias at this time.'); + } + $alias = new ProfileAlias; + $alias->profile_id = $request->user()->profile_id; + $alias->acct = $acct; + $alias->uri = $webfingerService['url']; + $alias->save(); + + return back()->with('status', 'Successfully added alias!'); + } + + public function delete(Request $request) + { + $this->validate($request, [ + 'acct' => 'required', + 'id' => 'required|exists:profile_aliases' + ]); + + $alias = ProfileAlias::where('profile_id', $request->user()->profile_id) + ->where('acct', $request->input('acct')) + ->findOrFail($request->input('id')); + + $alias->delete(); + + return back()->with('status', 'Successfully deleted alias!'); + } +} diff --git a/app/Models/ProfileAlias.php b/app/Models/ProfileAlias.php new file mode 100644 index 000000000..b7a3bdc9c --- /dev/null +++ b/app/Models/ProfileAlias.php @@ -0,0 +1,17 @@ +belongsTo(Profile::class); + } +} diff --git a/app/Profile.php b/app/Profile.php index 69994bf5b..0ce1e7be0 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -7,6 +7,7 @@ use App\Util\Lexer\PrettyNumber; use App\HasSnowflakePrimary; use Illuminate\Database\Eloquent\{Model, SoftDeletes}; use App\Services\FollowerService; +use App\Models\ProfileAlias; class Profile extends Model { @@ -369,9 +370,13 @@ class Profile extends Model return $this->hasMany(Story::class); } - public function reported() { return $this->hasMany(Report::class, 'object_id'); } + + public function aliases() + { + return $this->hasMany(ProfileAlias::class); + } } diff --git a/app/Transformer/ActivityPub/ProfileTransformer.php b/app/Transformer/ActivityPub/ProfileTransformer.php index 29f53425c..1df7b6100 100644 --- a/app/Transformer/ActivityPub/ProfileTransformer.php +++ b/app/Transformer/ActivityPub/ProfileTransformer.php @@ -4,17 +4,26 @@ namespace App\Transformer\ActivityPub; use App\Profile; use League\Fractal; +use App\Services\AccountService; class ProfileTransformer extends Fractal\TransformerAbstract { public function transform(Profile $profile) { - return [ + $res = [ '@context' => [ 'https://w3id.org/security/v1', 'https://www.w3.org/ns/activitystreams', [ 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', + 'alsoKnownAs' => [ + '@id' => 'as:alsoKnownAs', + '@type' => '@id' + ], + 'movedTo' => [ + '@id' => 'as:movedTo', + '@type' => '@id' + ] ], ], 'id' => $profile->permalink(), @@ -42,5 +51,15 @@ class ProfileTransformer extends Fractal\TransformerAbstract 'sharedInbox' => config('app.url') . '/f/inbox' ] ]; + + if($profile->aliases->count()) { + $res['alsoKnownAs'] = $profile->aliases->map(fn($alias) => $alias->uri); + } + + if($profile->moved_to_profile_id) { + $res['movedTo'] = AccountService::get($profile->moved_to_profile_id)['url']; + } + + return $res; } } diff --git a/database/migrations/2023_08_07_021252_create_profile_aliases_table.php b/database/migrations/2023_08_07_021252_create_profile_aliases_table.php new file mode 100644 index 000000000..ed9ab6ada --- /dev/null +++ b/database/migrations/2023_08_07_021252_create_profile_aliases_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger('profile_id')->nullable()->index(); + $table->string('acct')->nullable(); + $table->string('uri')->nullable(); + $table->foreign('profile_id')->references('id')->on('profiles'); + $table->unique(['profile_id', 'acct'], 'profile_id_acct_unique'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('profile_aliases'); + } +}; diff --git a/database/migrations/2023_08_08_045430_add_moved_to_profile_id_to_profiles_table.php b/database/migrations/2023_08_08_045430_add_moved_to_profile_id_to_profiles_table.php new file mode 100644 index 000000000..a13bb1705 --- /dev/null +++ b/database/migrations/2023_08_08_045430_add_moved_to_profile_id_to_profiles_table.php @@ -0,0 +1,28 @@ +unsignedBigInteger('moved_to_profile_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('profiles', function (Blueprint $table) { + $table->dropColumn('moved_to_profile_id'); + }); + } +}; diff --git a/resources/views/settings/aliases/index.blade.php b/resources/views/settings/aliases/index.blade.php new file mode 100644 index 000000000..af79f654d --- /dev/null +++ b/resources/views/settings/aliases/index.blade.php @@ -0,0 +1,121 @@ +@extends('layouts.app') + +@section('content') +@if (session('status')) +
{{ $error }}
+ @endforeach +If you want to move from another account to this one, you can create an alias here first.
+This alias is needed before you can move your followers from the old account to this one. Don't worry, making this change is safe and can be undone. The process of moving the account starts from the old one.
+ +Your followers will be migrated to your new account, and in some instances your posts too! For more information on Aliases and Account Migration, visit the Help Center.
+Old Account
+oldUsername@example.org
+Old Account
+oldUsername2@example.net
+We support migration to and from Pixelfed, Mastodon and most other platforms that use the Mastodon Account Migration extension.
+Aliases
+No aliases found!
+Select up to 4 pronouns that will appear on your profile.
+ +To move from another account to this one, first you need to create an alias.
+Storage Usage
diff --git a/resources/views/site/help/your-profile.blade.php b/resources/views/site/help/your-profile.blade.php index 76f1e7ebd..22a105d33 100644 --- a/resources/views/site/help/your-profile.blade.php +++ b/resources/views/site/help/your-profile.blade.php @@ -141,6 +141,45 @@Migration
++ + + How can I migrate my account? + +
Navigate to the Account Aliases page in the Settings to begin.
++ + + How long does the migration take? + +
+ + + Why are my posts not migrated? + +
Delete Your Account
@endif -@endsection \ No newline at end of file +@endsection diff --git a/routes/web.php b/routes/web.php index bd4d978bf..bb091fce5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -549,7 +549,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('invites', 'UserInviteController@show')->name('settings.invites'); // Route::get('sponsor', 'SettingsController@sponsor')->name('settings.sponsor'); // Route::post('sponsor', 'SettingsController@sponsorStore'); - Route::prefix('import')->group(function() { + Route::group(['prefix' => 'import', 'middleware' => 'dangerzone'], function() { Route::get('/', 'SettingsController@dataImport')->name('settings.import'); Route::prefix('instagram')->group(function() { Route::get('/', 'ImportController@instagram')->name('settings.import.ig'); @@ -564,6 +564,12 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::post('timeline', 'SettingsController@updateTimelineSettings'); Route::get('media', 'SettingsController@mediaSettings')->name('settings.media'); Route::post('media', 'SettingsController@updateMediaSettings'); + + Route::group(['prefix' => 'account/aliases', 'middleware' => 'dangerzone'], function() { + Route::get('manage', 'ProfileAliasController@index'); + Route::post('manage', 'ProfileAliasController@store'); + Route::post('manage/delete', 'ProfileAliasController@delete'); + }); }); Route::group(['prefix' => 'site'], function () {