Merge pull request #735 from pixelfed/frontend-ui-refactor

Frontend ui refactor
This commit is contained in:
daniel 2019-01-03 20:05:48 -07:00 committed by GitHub
commit 1b16e5d696
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 4095 additions and 5707 deletions

View file

@ -17,4 +17,14 @@ class FollowRequest extends Model
{
return $this->belongsTo(Profile::class, 'following_id', 'id');
}
public function actor()
{
return $this->belongsTo(Profile::class, 'follower_id', 'id');
}
public function target()
{
return $this->belongsTo(Profile::class, 'following_id', 'id');
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\{Profile, Status, User};
use Cache;
class InstanceApiController extends Controller {
protected function getData()
{
$contact = Cache::remember('api:v1:instance:contact', 1440, function() {
$admin = User::whereIsAdmin(true)->first()->profile;
return [
'id' => $admin->id,
'username' => $admin->username,
'acct' => $admin->username,
'display_name' => e($admin->name),
'locked' => (bool) $admin->is_private,
'bot' => false,
'created_at' => $admin->created_at->format('c'),
'note' => e($admin->bio),
'url' => $admin->url(),
'avatar' => $admin->avatarUrl(),
'avatar_static' => $admin->avatarUrl(),
'header' => null,
'header_static' => null,
'moved' => null,
'fields' => null,
'bot' => null,
];
});
$res = [
'uri' => config('pixelfed.domain.app'),
'title' => config('app.name'),
'description' => '',
'version' => config('pixelfed.version'),
'urls' => [],
'stats' => [
'user_count' => User::count(),
'status_count' => Status::whereNull('uri')->count(),
'domain_count' => Profile::whereNotNull('domain')
->groupBy('domain')
->pluck('domain')
->count()
],
'thumbnail' => '',
'languages' => [],
'contact_account' => $contact
];
return $res;
}
public function instance()
{
$res = Cache::remember('api:v1:instance', 60, function() {
return json_encode($this->getData());
});
return response($res)->header('Content-Type', 'application/json');
}
}

View file

@ -2,13 +2,18 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use App\Comment;
use App\Jobs\CommentPipeline\CommentPipeline;
use App\Jobs\StatusPipeline\NewStatusPipeline;
use App\Profile;
use App\Status;
use Auth;
use Illuminate\Http\Request;
use League\Fractal;
use App\Transformer\Api\StatusTransformer;
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
class CommentController extends Controller
{
@ -57,7 +62,19 @@ class CommentController extends Controller
CommentPipeline::dispatch($status, $reply);
if ($request->ajax()) {
$response = ['code' => 200, 'msg' => 'Comment saved', 'username' => $profile->username, 'url' => $reply->url(), 'profile' => $profile->url(), 'comment' => $reply->caption];
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$entity = new Fractal\Resource\Item($reply, new StatusTransformer());
$entity = $fractal->createData($entity)->toArray();
$response = [
'code' => 200,
'msg' => 'Comment saved',
'username' => $profile->username,
'url' => $reply->url(),
'profile' => $profile->url(),
'comment' => $reply->caption,
'entity' => $entity,
];
} else {
$response = redirect($status->url());
}

View file

@ -15,6 +15,7 @@ use Illuminate\Http\Request;
use League\Fractal;
use App\Util\ActivityPub\Helpers;
use App\Util\ActivityPub\HttpSignature;
use \Zttp\Zttp;
class FederationController extends Controller
{

View file

@ -149,11 +149,17 @@ class SettingsController extends Controller
public function removeAccountPermanent(Request $request)
{
if(config('pixelfed.account_deletion') == false) {
abort(404);
}
return view('settings.remove.permanent');
}
public function removeAccountPermanentSubmit(Request $request)
{
if(config('pixelfed.account_deletion') == false) {
abort(404);
}
$user = Auth::user();
if($user->is_admin == true) {
return abort(400, 'You cannot delete an admin account.');

View file

@ -19,7 +19,7 @@ class StatusController extends Controller
{
public function show(Request $request, $username, int $id)
{
$user = Profile::whereUsername($username)->firstOrFail();
$user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
if($user->status != null) {
return ProfileController::accountCheck($user);

View file

@ -0,0 +1,57 @@
<?php
namespace App\Jobs\FollowPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Cache, Log, Redis;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use App\FollowRequest;
use App\Util\ActivityPub\Helpers;
use App\Transformer\ActivityPub\Verb\Follow;
class FollowActivityPubDeliver implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $followRequest;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(FollowRequest $followRequest)
{
$this->followRequest = $followRequest;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$follow = $this->followRequest;
$actor = $follow->actor;
$target = $follow->target;
if($target->domain == null || $target->inbox_url == null) {
return;
}
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($follow, new Follow());
$activity = $fractal->createData($resource)->toArray();
$url = $target->sharedInbox ?? $target->inbox_url;
Helpers::sendSignedObject($actor, $url, $activity);
}
}

View file

@ -37,7 +37,10 @@ class NewStatusPipeline implements ShouldQueue
$status = $this->status;
StatusEntityLexer::dispatch($status);
StatusActivityPubDeliver::dispatch($status);
if(config('pixelfed.activitypub_enabled') == true) {
StatusActivityPubDeliver::dispatch($status);
}
// Cache::forever('post.'.$status->id, $status);
// $redis = Redis::connection();

View file

@ -3,6 +3,7 @@
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
@ -24,6 +25,10 @@ class AuthServiceProvider extends ServiceProvider
{
$this->registerPolicies();
//
// Passport::routes();
// Passport::tokensExpireIn(now()->addDays(15));
// Passport::refreshTokensExpireIn(now()->addDays(30));
}
}

View file

@ -7,7 +7,7 @@ use League\Fractal;
class Follow extends Fractal\TransformerAbstract
{
public function transform(Follower $follower)
public function transform($follower)
{
return [
'@context' => 'https://www.w3.org/ns/activitystreams',

View file

@ -7,27 +7,27 @@ use League\Fractal;
class AccountTransformer extends Fractal\TransformerAbstract
{
public function transform(Profile $profile)
{
return [
'id' => $profile->id,
'username' => $profile->username,
'acct' => $profile->username,
'display_name' => $profile->name,
'locked' => (bool) $profile->is_private,
'created_at' => $profile->created_at->format('c'),
'followers_count' => $profile->followerCount(),
'following_count' => $profile->followingCount(),
'statuses_count' => $profile->statusCount(),
'note' => $profile->bio,
'url' => $profile->url(),
'avatar' => $profile->avatarUrl(),
'avatar_static' => $profile->avatarUrl(),
'header' => null,
'header_static' => null,
'moved' => null,
'fields' => null,
'bot' => null,
];
}
public function transform(Profile $profile)
{
return [
'id' => $profile->id,
'username' => $profile->username,
'acct' => $profile->username,
'display_name' => $profile->name,
'locked' => (bool) $profile->is_private,
'created_at' => $profile->created_at->format('c'),
'followers_count' => $profile->followerCount(),
'following_count' => $profile->followingCount(),
'statuses_count' => $profile->statusCount(),
'note' => $profile->bio,
'url' => $profile->url(),
'avatar' => $profile->avatarUrl(),
'avatar_static' => $profile->avatarUrl(),
'header' => null,
'header_static' => null,
'moved' => null,
'fields' => null,
'bot' => null,
];
}
}

View file

@ -6,11 +6,11 @@ use League\Fractal;
class ApplicationTransformer extends Fractal\TransformerAbstract
{
public function transform()
{
return [
'name' => '',
'website' => null,
];
}
public function transform()
{
return [
'name' => '',
'website' => null,
];
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Transformer\Api;
use League\Fractal;
class AttachmentTransformer extends Fractal\TransformerAbstract
{
public function transform(Media $media)
{
return [
'id' => $media->id,
'type' => $media->activityVerb(),
'url' => $media->url(),
'remote_url' => null,
'preview_url' => $media->thumbnailUrl(),
'text_url' => null,
'meta' => null,
'description' => $media->caption,
'license' => $media->license,
'is_nsfw' => $media->is_nsfw,
'orientation' => $media->orientation,
'filter_name' => $media->filter_name,
'filter_class' => $media->filter_class,
'mime' => $media->mime,
];
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Transformer\Api;
use League\Fractal;
class ContextTransformer extends Fractal\TransformerAbstract
{
public function transform()
{
return [
'ancestors' => [],
'descendants' => []
];
}
}

View file

@ -6,13 +6,13 @@ use League\Fractal;
class EmojiTransformer extends Fractal\TransformerAbstract
{
public function transform($emoji)
{
return [
'shortcode' => '',
'static_url' => '',
'url' => '',
'visible_in_picker' => false
];
}
public function transform($emoji)
{
return [
'shortcode' => '',
'static_url' => '',
'url' => '',
'visible_in_picker' => false
];
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Transformer\Api;
use League\Fractal;
class FilterTransformer extends Fractal\TransformerAbstract
{
public function transform()
{
return [
'id' => (string) '',
'phrase' => (string) '',
'context' => [],
'expires_at' => null,
'irreversible' => (bool) false,
'whole_word' => (bool) false
];
}
}

View file

@ -7,11 +7,11 @@ use League\Fractal;
class HashtagTransformer extends Fractal\TransformerAbstract
{
public function transform(Hashtag $hashtag)
{
return [
'name' => $hashtag->name,
'url' => $hashtag->url(),
];
}
public function transform(Hashtag $hashtag)
{
return [
'name' => $hashtag->name,
'url' => $hashtag->url(),
];
}
}

View file

@ -10,20 +10,20 @@ class MediaTransformer extends Fractal\TransformerAbstract
public function transform(Media $media)
{
return [
'id' => $media->id,
'type' => $media->activityVerb(),
'url' => $media->url(),
'remote_url' => null,
'preview_url' => $media->thumbnailUrl(),
'text_url' => null,
'meta' => null,
'description' => $media->caption,
'license' => $media->license,
'is_nsfw' => $media->is_nsfw,
'orientation' => $media->orientation,
'filter_name' => $media->filter_name,
'filter_class' => $media->filter_class,
'mime' => $media->mime,
'id' => $media->id,
'type' => $media->activityVerb(),
'url' => $media->url(),
'remote_url' => null,
'preview_url' => $media->thumbnailUrl(),
'text_url' => null,
'meta' => null,
'description' => $media->caption,
'license' => $media->license,
'is_nsfw' => $media->is_nsfw,
'orientation' => $media->orientation,
'filter_name' => $media->filter_name,
'filter_class' => $media->filter_class,
'mime' => $media->mime,
];
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Transformer\Api;
use League\Fractal;
class ResultsTransformer extends Fractal\TransformerAbstract
{
protected $defaultIncludes = [
'account',
'mentions',
'media_attachments',
'tags',
];
public function transform()
{
return [
'accounts' => [],
'statuses' => [],
'hashtags' => []
];
}
}

View file

@ -2,7 +2,7 @@
namespace App\Util\ActivityPub;
use Cache, DB, Log, Redis, Validator;
use Cache, DB, Log, Purify, Redis, Validator;
use App\{
Activity,
Follower,
@ -16,6 +16,10 @@ use Carbon\Carbon;
use App\Util\ActivityPub\Helpers;
use App\Jobs\LikePipeline\LikePipeline;
use App\Util\ActivityPub\Validator\{
Follow
};
class Inbox
{
protected $headers;
@ -35,30 +39,6 @@ class Inbox
$this->handleVerb();
}
public function authenticatePayload()
{
try {
$signature = Helpers::validateSignature($this->headers, $this->payload);
$payload = Helpers::validateObject($this->payload);
if($signature == false) {
return;
}
} catch (Exception $e) {
return;
}
$this->payloadLogger();
}
public function payloadLogger()
{
$logger = new Activity;
$logger->data = json_encode($this->payload);
$logger->save();
$this->logger = $logger;
Log::info('AP:inbox:activity:new:'.$this->logger->id);
$this->handleVerb();
}
public function handleVerb()
{
$verb = $this->payload['type'];
@ -76,6 +56,7 @@ class Inbox
break;
case 'Accept':
if(Accept::validate($this->payload) == false) { return; }
$this->handleAcceptActivity();
break;
@ -171,7 +152,8 @@ class Inbox
$caption = str_limit(strip_tags($activity['content']), config('pixelfed.max_caption_length'));
$status = new Status;
$status->profile_id = $actor->id;
$status->caption = $caption;
$status->caption = strip_tags($caption);
$status->rendered = Purify::clean($caption);
$status->visibility = $status->scope = 'public';
$status->uri = $url;
$status->url = $url;
@ -275,13 +257,10 @@ class Inbox
$obj = $this->payload['object'];
if(is_string($obj) && Helpers::validateUrl($obj)) {
// actor object detected
// todo delete actor
} else if (is_array($obj) && isset($obj['type']) && $obj['type'] == 'Tombstone') {
// tombstone detected
$status = Status::whereUri($obj['id'])->first();
if($status == null) {
return;
}
$status = Status::whereUri($obj['id'])->firstOrFail();
$status->forceDelete();
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Util\ActivityPub\Validator;
use Validator;
use Illuminate\Validation\Rule;
class Announce {
public static function validate($payload)
{
$valid = Validator::make($payload, [
'@context' => 'required',
'id' => 'required|string',
'type' => [
'required',
Rule::in(['Announce'])
],
'actor' => 'required|url|active_url',
'published' => 'required|date',
'to' => 'required',
'cc' => 'required',
'object' => 'required|url|active_url'
])->passes();
return $valid;
}
}

View file

@ -177,6 +177,28 @@ return [
*/
'image_quality' => (int) env('IMAGE_QUALITY', 80),
/*
|--------------------------------------------------------------------------
| Account deletion
|--------------------------------------------------------------------------
|
| Enable account deletion.
|
*/
'account_deletion' => env('ACCOUNT_DELETION', true),
/*
|--------------------------------------------------------------------------
| Account deletion after X days
|--------------------------------------------------------------------------
|
| Set account deletion queue after X days, set to false to delete accounts
| immediately.
|
*/
'account_delete_after' => env('ACCOUNT_DELETE_AFTER', false),
'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),
'enforce_account_limit' => env('LIMIT_ACCOUNT_SIZE', true),
'ap_inbox' => env('ACTIVITYPUB_INBOX', false),

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class UpdateProfilesTableUseTextForBio extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('profiles', function (Blueprint $table) {
$table->text('bio')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('profiles', function (Blueprint $table) {
$table->string('bio')->nullable()->change();
});
}
}

9047
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -15,9 +15,11 @@
"bootstrap": "^4.2.1",
"cross-env": "^5.2.0",
"jquery": "^3.2",
"laravel-mix": "^2.1.14",
"lodash": "^4.17.11",
"popper.js": "^1.14.6",
"resolve-url-loader": "^2.3.1",
"sass": "^1.15.2",
"sass-loader": "^7.1.0",
"vue": "^2.5.21",
"vue-template-compiler": "^2.5.21"
},
@ -27,9 +29,12 @@
"filesize": "^3.6.1",
"infinite-scroll": "^3.0.4",
"laravel-echo": "^1.5.2",
"laravel-mix": "^4.0.12",
"node-sass": "^4.11.0",
"opencollective": "^1.0.3",
"opencollective-postinstall": "^2.0.1",
"plyr": "^3.4.7",
"promise-polyfill": "8.1.0",
"pusher-js": "^4.2.2",
"readmore-js": "^2.2.1",
"socket.io-client": "^2.2.0",

View file

@ -1,4 +1,4 @@
<style>
<style scoped>
span {
font-size: 14px;
}
@ -92,7 +92,7 @@ export default {
axios.get(url)
.then(response => {
let self = this;
this.results = response.data.data;
this.results = _.reverse(response.data.data);
this.pagination = response.data.meta.pagination;
if(this.results.length > 0) {
$('.load-more-link').removeClass('d-none');

View file

@ -1,4 +1,4 @@
<style>
<style scoped>
#l-modal .modal-body,
#s-modal .modal-body {
max-height: 70vh;

View file

@ -2,11 +2,6 @@
<div class="container" style="">
<div class="row">
<div class="col-md-8 col-lg-8 pt-2 px-0 my-3 timeline order-2 order-md-1">
<div class="loader text-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
<div class="card mb-4 status-card card-md-rounded-0" :data-status-id="status.id" v-for="(status, index) in feed" :key="status.id">
<div class="card-header d-inline-flex align-items-center bg-white">
@ -97,6 +92,10 @@
</form>
</div>
</div>
<infinite-loading @infinite="infiniteTimeline">
<div slot="no-more" class="font-weight-bold text-light">No more posts to load</div>
<div slot="no-results" class="font-weight-bold text-light">No posts found</div>
</infinite-loading>
</div>
<div class="col-md-4 col-lg-4 pt-2 my-3 order-1 order-md-2">
@ -185,7 +184,7 @@
<div class="container pb-5">
<p class="mb-0 text-uppercase font-weight-bold text-muted small">
<a href="/site/about" class="text-dark pr-2">About Us</a>
<a href="/site/help" class="text-dark pr-2">Support</a>
<a href="/site/help" class="text-dark pr-2">Help</a>
<a href="/site/open-source" class="text-dark pr-2">Open Source</a>
<a href="/site/language" class="text-dark pr-2">Language</a>
<a href="/site/terms" class="text-dark pr-2">Terms</a>
@ -202,7 +201,7 @@
</div>
</template>
<style type="text/css">
<style type="text/css" scoped>
.postPresenterContainer {
display: flex;
align-items: center;
@ -241,7 +240,6 @@
},
updated() {
this.scroll();
},
methods: {
@ -279,6 +277,32 @@
});
},
infiniteTimeline($state) {
let homeTimeline = '/api/v1/timelines/home';
let localTimeline = '/api/v1/timelines/public';
let apiUrl = this.scope == '/' ? homeTimeline : localTimeline;
axios.get(apiUrl, {
params: {
page: this.page,
},
}).then(res => {
if (res.data.length) {
$('.timeline .loader').addClass('d-none');
let data = res.data;
this.feed.push(...data);
let ids = data.map(status => status.id);
this.min_id = Math.min(...ids);
if(this.page == 1) {
this.max_id = Math.max(...ids);
}
this.page += 1;
$state.loaded();
} else {
$state.complete();
}
});
},
fetchNotifications() {
axios.get('/api/v1/notifications')
.then(res => {
@ -288,16 +312,6 @@
});
},
scroll() {
window.onscroll = () => {
let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight == document.documentElement.offsetHeight;
if (bottomOfWindow) {
this.fetchTimelineApi();
}
};
},
reportUrl(status) {
let type = status.in_reply_to ? 'comment' : 'post';
let id = status.id;

View file

@ -140,7 +140,7 @@
</div>
</div>
</p>
{{-- <hr>
<hr>
<p class="h5 text-muted font-weight-light" id="delete-your-account">Delete Your Account</p>
<p>
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#del-collapse1" role="button" aria-expanded="false" aria-controls="del-collapse1">
@ -159,6 +159,7 @@
</div>
</div>
</p>
@if(config('pixelfed.account_deletion'))
<p>
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#del-collapse2" role="button" aria-expanded="false" aria-controls="del-collapse2">
<i class="fas fa-chevron-down mr-2"></i>
@ -166,9 +167,15 @@
</a>
<div class="collapse" id="del-collapse2">
<div>
@if(config('pixelfed.account_delete_after') == false)
<div class="bg-light p-3 mb-4">
<p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be permanently removed. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
<p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be <b>permanently removed</b>. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
</div>
@else
<div class="bg-light p-3 mb-4">
<p class="mb-0">When you delete your account, your profile, photos, videos, comments, likes and followers will be <b>permanently removed</b> after {{config('pixelfed.account_delete_after')}} days. You can log in during that period to prevent your account from permanent deletion. If you'd just like to take a break, you can <a href="{{route('settings.remove.temporary')}}">temporarily disable</a> your account instead.</p>
</div>
@endif
<p>After you delete your account, you can't sign up again with the same username on this instance or add that username to another account on this instance, and we can't reactivate deleted accounts.</p>
<p>To permanently delete your account:</p>
<ol class="font-weight-light">
@ -178,5 +185,6 @@
</ol>
</div>
</div>
</p> --}}
</p>
@endif
@endsection

View file

@ -0,0 +1,168 @@
<?php
namespace Tests\Unit\ActivityPub\Verb;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use App\Util\ActivityPub\Validator\Announce;
class AnnounceTest extends TestCase
{
public function setUp()
{
parent::setUp();
$this->validAnnounce = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
"type" => "Announce",
"actor" => "https://example.org/users/alice",
"published" => "2018-12-31T23:59:59Z",
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
"https://example.org/users/bob",
"https://example.org/users/alice/followers"
],
"object" => "https://example.org/p/bob/100000000000000",
];
$this->invalidAnnounce = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
"type" => "Announce2",
"actor" => "https://example.org/users/alice",
"published" => "2018-12-31T23:59:59Z",
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
"https://example.org/users/bob",
"https://example.org/users/alice/followers"
],
"object" => "https://example.org/p/bob/100000000000000",
];
$this->invalidDate = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
"type" => "Announce",
"actor" => "https://example.org/users/alice",
"published" => "2018-12-31T23:59:59ZEZE",
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
"https://example.org/users/bob",
"https://example.org/users/alice/followers"
],
"object" => "https://example.org/p/bob/100000000000000",
];
$this->contextMissing = [
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
"type" => "Announce",
"actor" => "https://example.org/users/alice",
"published" => "2018-12-31T23:59:59Z",
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
"https://example.org/users/bob",
"https://example.org/users/alice/followers"
],
"object" => "https://example.org/p/bob/100000000000000",
];
$this->audienceMissing = [
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
"type" => "Announce",
"actor" => "https://example.org/users/alice",
"published" => "2018-12-31T23:59:59Z",
"object" => "https://example.org/p/bob/100000000000000",
];
$this->audienceMissing2 = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
"type" => "Announce",
"actor" => "https://example.org/users/alice",
"published" => "2018-12-31T23:59:59Z",
"to" => null,
"cc" => null,
"object" => "https://example.org/p/bob/100000000000000",
];
$this->invalidActor = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
"type" => "Announce",
"actor" => "10000",
"published" => "2018-12-31T23:59:59Z",
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
"https://example.org/users/bob",
"https://example.org/users/alice/followers"
],
"object" => "https://example.org/p/bob/100000000000000",
];
$this->invalidActor2 = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://example.org/users/alice/statuses/100000000000001/activity",
"type" => "Announce",
"published" => "2018-12-31T23:59:59Z",
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
"https://example.org/users/bob",
"https://example.org/users/alice/followers"
],
"object" => "https://example.org/p/bob/100000000000000",
];
}
/** @test */
public function basic_accept()
{
$this->assertTrue(Announce::validate($this->validAnnounce));
}
/** @test */
public function invalid_accept()
{
$this->assertFalse(Announce::validate($this->invalidAnnounce));
}
/** @test */
public function invalid_date()
{
$this->assertFalse(Announce::validate($this->invalidDate));
}
/** @test */
public function context_missing()
{
$this->assertFalse(Announce::validate($this->contextMissing));
}
/** @test */
public function audience_missing()
{
$this->assertFalse(Announce::validate($this->audienceMissing));
$this->assertFalse(Announce::validate($this->audienceMissing2));
}
/** @test */
public function invalid_actor()
{
$this->assertFalse(Announce::validate($this->invalidActor));
$this->assertFalse(Announce::validate($this->invalidActor2));
}
}