Skip to content

Commit

Permalink
Add Post Edits/Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
dansup committed May 25, 2023
1 parent 0136560 commit 98cf8f3
Show file tree
Hide file tree
Showing 17 changed files with 1,281 additions and 9 deletions.
59 changes: 59 additions & 0 deletions app/Http/Controllers/StatusEditController.php
@@ -0,0 +1,59 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests\Status\StoreStatusEditRequest;
use App\Status;
use App\Models\StatusEdit;
use Purify;
use App\Services\Status\UpdateStatusService;
use App\Services\StatusService;
use App\Util\Lexer\Autolink;
use App\Jobs\StatusPipeline\StatusLocalUpdateActivityPubDeliverPipeline;

class StatusEditController extends Controller
{
public function __construct()
{
$this->middleware('auth');
abort_if(!config('exp.pue'), 404, 'Post editing is not enabled on this server.');
}

public function store(StoreStatusEditRequest $request, $id)
{
$validated = $request->validated();

$status = Status::findOrFail($id);
abort_if(StatusEdit::whereStatusId($status->id)->count() >= 10, 400, 'You cannot edit your post more than 10 times.');
$res = UpdateStatusService::call($status, $validated);

$status = Status::findOrFail($id);
StatusLocalUpdateActivityPubDeliverPipeline::dispatch($status)->delay(now()->addMinutes(1));
return $res;
}

public function history(Request $request, $id)
{
abort_if(!$request->user(), 403);
$status = Status::whereNull('reblog_of_id')->findOrFail($id);
abort_if(!in_array($status->scope, ['public', 'unlisted']), 403);
if(!$status->edits()->count()) {
return [];
}
$cached = StatusService::get($status->id, false);

$res = $status->edits->map(function($edit) use($cached) {
return [
'content' => Autolink::create()->autolink($edit->caption),
'spoiler_text' => $edit->spoiler_text,
'sensitive' => (bool) $edit->is_nsfw,
'created_at' => str_replace('+00:00', 'Z', $edit->created_at->format(DATE_RFC3339_EXTENDED)),
'account' => $cached['account'],
'media_attachments' => $cached['media_attachments'],
'emojis' => $cached['emojis'],
];
})->reverse()->values()->toArray();
return $res;
}
}
69 changes: 69 additions & 0 deletions app/Http/Requests/Status/StoreStatusEditRequest.php
@@ -0,0 +1,69 @@
<?php

namespace App\Http\Requests\Status;

use Illuminate\Foundation\Http\FormRequest;
use App\Media;
use App\Status;
use Closure;

class StoreStatusEditRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
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'));
return $status && $this->user()->profile_id === $status->profile_id;
}

/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'status' => 'sometimes|max:'.config('pixelfed.max_caption_length', 500),
'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);
},
],
'location' => 'sometimes|nullable',
'location.id' => 'sometimes|integer|min:1|max:128769',
'location.country' => 'required_with:location.id',
'location.name' => 'required_with:location.id',
];
}
}
@@ -0,0 +1,129 @@
<?php

namespace App\Jobs\StatusPipeline;

use Cache, Log;
use App\Status;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\Transformer\ActivityPub\Verb\UpdateNote;
use App\Util\ActivityPub\Helpers;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use App\Util\ActivityPub\HttpSignature;

class StatusLocalUpdateActivityPubDeliverPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

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(Status $status)
{
$this->status = $status;
}

/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$status = $this->status;
$profile = $status->profile;

// ignore group posts
// if($status->group_id != null) {
// return;
// }

if($status->local == false || $status->url || $status->uri) {
return;
}

$audience = $status->profile->getAudienceInbox();

if(empty($audience) || !in_array($status->scope, ['public', 'unlisted', 'private'])) {
// Return on profiles with no remote followers
return;
}

switch($status->type) {
case 'poll':
// Polls not yet supported
return;
break;

default:
$activitypubObject = new UpdateNote();
break;
}


$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($status, $activitypubObject);
$activity = $fractal->createData($resource)->toArray();

$payload = json_encode($activity);

$client = new Client([
'timeout' => config('federation.activitypub.delivery.timeout')
]);

$version = config('pixelfed.version');
$appUrl = config('app.url');
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";

$requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) {
foreach($audience as $url) {
$headers = HttpSignature::sign($profile, $url, $activity, [
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'User-Agent' => $userAgent,
]);
yield function() use ($client, $url, $headers, $payload) {
return $client->postAsync($url, [
'curl' => [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HEADER => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false
]
]);
};
}
};

$pool = new Pool($client, $requests($audience), [
'concurrency' => config('federation.activitypub.delivery.concurrency'),
'fulfilled' => function ($response, $index) {
},
'rejected' => function ($reason, $index) {
}
]);

$promise = $pool->promise();

$promise->wait();
}
}
19 changes: 19 additions & 0 deletions app/Models/StatusEdit.php
@@ -0,0 +1,19 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class StatusEdit extends Model
{
use HasFactory;

protected $casts = [
'ordered_media_attachment_ids' => 'array',
'media_descriptions' => 'array',
'poll_options' => 'array'
];

protected $guarded = [];
}

0 comments on commit 98cf8f3

Please sign in to comment.