mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-10 08:44:49 +00:00
commit
afc758764c
198 changed files with 7395 additions and 8839 deletions
11
.env.example
11
.env.example
|
@ -53,3 +53,14 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
|||
MIX_APP_URL="${APP_URL}"
|
||||
MIX_API_BASE="${API_BASE}"
|
||||
MIX_API_SEARCH="${API_SEARCH}"
|
||||
|
||||
ACTIVITYPUB_INBOX=false
|
||||
ACTIVITYPUB_SHAREDINBOX=false
|
||||
|
||||
# Set these both "true" to enable federation.
|
||||
# You might need to also run:
|
||||
# php artisan cache:clear
|
||||
# php artisan optimize:clear
|
||||
# php artisan optimize
|
||||
ACTIVITY_PUB=false
|
||||
REMOTE_FOLLOW=false
|
||||
|
|
|
@ -40,7 +40,7 @@ SESSION_SECURE_COOKIE=true
|
|||
API_BASE="/api/1/"
|
||||
API_SEARCH="/api/search"
|
||||
|
||||
OPEN_REGISTRATION=true
|
||||
OPEN_REGISTRATION=false
|
||||
RECAPTCHA_ENABLED=false
|
||||
ENFORCE_EMAIL_VERIFICATION=true
|
||||
|
||||
|
@ -55,3 +55,4 @@ MIX_API_BASE="${API_BASE}"
|
|||
MIX_API_SEARCH="${API_SEARCH}"
|
||||
|
||||
TELESCOPE_ENABLED=false
|
||||
PF_MAX_USERS=1000
|
||||
|
|
103
README.md
103
README.md
|
@ -1,66 +1,27 @@
|
|||
# PixelFed: Federated Image Sharing
|
||||
[![Backers on Open Collective](https://opencollective.com/pixelfed-528/backers/badge.svg)](#backers)
|
||||
[![Sponsors on Open Collective](https://opencollective.com/pixelfed-528/sponsors/badge.svg)](#sponsors)
|
||||
<p align="center"><img src="https://pixelfed.nyc3.cdn.digitaloceanspaces.com/logos/pixelfed-full-color.svg" width="300px"></p>
|
||||
|
||||
PixelFed is a federated social image sharing platform, similar to Instagram.
|
||||
Federation is done using the [ActivityPub](https://activitypub.rocks/) protocol,
|
||||
which is used by [Mastodon](http://joinmastodon.org/), [PeerTube](https://joinpeertube.org/en/),
|
||||
[Pleroma](https://pleroma.social/), and more. Through ActivityPub PixelFed can share
|
||||
and interact with these platforms, as well as other instances of PixelFed.
|
||||
<p align="center">
|
||||
<a href="https://circleci.com/gh/pixelfed/pixelfed"><img src="https://circleci.com/gh/pixelfed/pixelfed.svg?style=svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/pixelfed/pixelfed"><img src="https://poser.pugx.org/pixelfed/pixelfed/d/total.svg" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/pixelfed/pixelfed"><img src="https://poser.pugx.org/pixelfed/pixelfed/v/stable.svg" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/pixelfed/pixelfed"><img src="https://poser.pugx.org/pixelfed/pixelfed/license.svg" alt="License"></a>
|
||||
</p>
|
||||
|
||||
**_Please note this is alpha software, not recommended for production use,
|
||||
and federation is not supported yet._**
|
||||
## Introduction
|
||||
|
||||
PixelFed is very early into the development stage. If you would like to have a
|
||||
permanent instance with minimal breakage, **do not use this software until
|
||||
there is a stable release**. The following setup instructions are intended for
|
||||
testing and development.
|
||||
A free and ethical photo sharing platform, powered by ActivityPub federation.
|
||||
|
||||
## Requirements
|
||||
- PHP >= 7.1.3 < 7.3 (7.2.x recommended for stable version)
|
||||
- MySQL >= 5.7 (Postgres, MariaDB and sqlite are not supported)
|
||||
- Redis
|
||||
- Composer
|
||||
- GD or ImageMagick
|
||||
- OpenSSL PHP Extension
|
||||
- PDO PHP Extension
|
||||
- Mbstring PHP Extension
|
||||
- Tokenizer PHP Extension
|
||||
- XML PHP Extension
|
||||
- Ctype PHP Extension
|
||||
- JSON PHP Extension
|
||||
- BCMath PHP Extension
|
||||
- JpegOptim
|
||||
- Optipng
|
||||
- Pngquant 2
|
||||
- SVGO
|
||||
- Gifsicle
|
||||
<p align="center">
|
||||
<img src="https://pixelfed.nyc3.cdn.digitaloceanspaces.com/media/Screen%20Shot%202019-02-05%20at%206.34.59%20PM.png">
|
||||
</p>
|
||||
|
||||
## Installation
|
||||
## Official Documentation
|
||||
|
||||
This guide assumes you have NGINX/Apache installed, along with the dependencies.
|
||||
Those will not be covered in these early docs.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/pixelfed/pixelfed.git
|
||||
cd pixelfed
|
||||
composer install
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
**Edit .env file with proper values**
|
||||
|
||||
```bash
|
||||
php artisan key:generate
|
||||
```
|
||||
|
||||
```bash
|
||||
php artisan storage:link
|
||||
php artisan migrate
|
||||
php artisan horizon
|
||||
```
|
||||
Documentation for Pixelfed can be found on the [Pixelfed documentation website](https://pixelfed.github.io/docs/master/).
|
||||
|
||||
## License
|
||||
|
||||
Pixelfed is open-sourced software licensed under the AGPL license.
|
||||
|
||||
## Communication
|
||||
|
||||
|
@ -68,7 +29,7 @@ The ways you can communicate on the project are below. Before interacting, pleas
|
|||
read through the [Code Of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
* IRC: #pixelfed on irc.freenode.net ([#freenode_#pixelfed:matrix.org through
|
||||
Matrix](https://matrix.to/#/#freenode_#pixelfed:matrix.org)
|
||||
Matrix](https://matrix.to/#/#freenode_#pixelfed:matrix.org))
|
||||
* Project on Mastodon: [@pixelfed@mastodon.social](https://mastodon.social/@pixelfed)
|
||||
* E-mail: [hello@pixelfed.org](mailto:hello@pixelfed.org)
|
||||
|
||||
|
@ -80,29 +41,27 @@ https://www.patreon.com/dansup
|
|||
### Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
<a href="https://github.com/pixelfed/pixelfed/graphs/contributors"><img src="https://opencollective.com/pixelfed-528/contributors.svg?width=890&button=false" /></a>
|
||||
<a href="https://github.com/pixelfed/pixelfed/graphs/contributors"><img src="https://opencollective.com/pixelfed/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
|
||||
### Backers
|
||||
|
||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/pixelfed-528#backer)]
|
||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/pixelfed#backer)]
|
||||
|
||||
<a href="https://opencollective.com/pixelfed-528#backers" target="_blank"><img src="https://opencollective.com/pixelfed-528/backers.svg?width=890"></a>
|
||||
<a href="https://opencollective.com/pixelfed#backers" target="_blank"><img src="https://opencollective.com/pixelfed/backers.svg?width=890"></a>
|
||||
|
||||
|
||||
### Sponsors
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/pixelfed-528#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/0/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/1/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/2/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/3/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/4/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/5/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/6/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/7/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/8/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed-528/sponsor/9/website" target="_blank"><img src="https://opencollective.com/pixelfed-528/sponsor/9/avatar.svg"></a>
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/pixelfed#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/0/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/1/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/2/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/3/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/4/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/5/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/6/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/7/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/8/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pixelfed/sponsor/9/website" target="_blank"><img src="https://opencollective.com/pixelfed/sponsor/9/avatar.svg"></a>
|
|
@ -6,5 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class AccountLog extends Model
|
||||
{
|
||||
//
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,14 @@ use Illuminate\Database\Eloquent\Model;
|
|||
class Activity extends Model
|
||||
{
|
||||
protected $dates = ['processed_at'];
|
||||
|
||||
public function toProfile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'to_id');
|
||||
}
|
||||
|
||||
public function fromProfile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'from_id');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,16 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class Bookmark extends Model
|
||||
{
|
||||
protected $fillable = ['profile_id', 'status_id'];
|
||||
protected $fillable = ['profile_id', 'status_id'];
|
||||
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(Status::class);
|
||||
}
|
||||
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
}
|
||||
|
|
38
app/Circle.php
Normal file
38
app/Circle.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Circle extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'bcc',
|
||||
'scope',
|
||||
'active'
|
||||
];
|
||||
|
||||
public function members()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Profile::class,
|
||||
CircleProfile::class,
|
||||
'circle_id',
|
||||
'id',
|
||||
'id',
|
||||
'profile_id'
|
||||
);
|
||||
}
|
||||
|
||||
public function owner()
|
||||
{
|
||||
return $this->belongsTo(Profile::class, 'profile_id');
|
||||
}
|
||||
|
||||
public function url()
|
||||
{
|
||||
return url("/i/circle/show/{$this->id}");
|
||||
}
|
||||
}
|
13
app/CircleProfile.php
Normal file
13
app/CircleProfile.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CircleProfile extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'circle_id',
|
||||
'profile_id'
|
||||
];
|
||||
}
|
|
@ -6,5 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class Collection extends Model
|
||||
{
|
||||
//
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class CollectionItem extends Model
|
||||
{
|
||||
//
|
||||
public function collection()
|
||||
{
|
||||
return $this->belongsTo(Collection::class);
|
||||
}
|
||||
}
|
||||
|
|
64
app/DiscoverCategory.php
Normal file
64
app/DiscoverCategory.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\{Status, StatusHashtag};
|
||||
|
||||
class DiscoverCategory extends Model
|
||||
{
|
||||
protected $fillable = ['slug'];
|
||||
|
||||
public function media()
|
||||
{
|
||||
return $this->belongsTo(Media::class);
|
||||
}
|
||||
|
||||
public function url()
|
||||
{
|
||||
return url('/discover/c/'.$this->slug);
|
||||
}
|
||||
|
||||
public function editUrl()
|
||||
{
|
||||
return url('/i/admin/discover/category/edit/' . $this->id);
|
||||
}
|
||||
|
||||
public function thumb()
|
||||
{
|
||||
return $this->media->thumb();
|
||||
}
|
||||
|
||||
public function mediaUrl()
|
||||
{
|
||||
return $this->media->url();
|
||||
}
|
||||
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(DiscoverCategoryHashtag::class, 'discover_category_id');
|
||||
}
|
||||
|
||||
public function hashtags()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Hashtag::class,
|
||||
DiscoverCategoryHashtag::class,
|
||||
'discover_category_id',
|
||||
'id',
|
||||
'id',
|
||||
'hashtag_id'
|
||||
);
|
||||
}
|
||||
|
||||
public function posts()
|
||||
{
|
||||
return Status::select('*')
|
||||
->join('status_hashtags', 'statuses.id', '=', 'status_hashtags.status_id')
|
||||
->join('hashtags', 'status_hashtags.hashtag_id', '=', 'hashtags.id')
|
||||
->join('discover_category_hashtags', 'hashtags.id', '=', 'discover_category_hashtags.hashtag_id')
|
||||
->join('discover_categories', 'discover_category_hashtags.discover_category_id', '=', 'discover_categories.id')
|
||||
->where('discover_categories.id', $this->id);
|
||||
}
|
||||
}
|
13
app/DiscoverCategoryHashtag.php
Normal file
13
app/DiscoverCategoryHashtag.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DiscoverCategoryHashtag extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'discover_category_id',
|
||||
'hashtag_id'
|
||||
];
|
||||
}
|
|
@ -13,4 +13,9 @@ class EmailVerification extends Model
|
|||
|
||||
return "{$base}{$path}";
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
|
|
19
app/FailedJob.php
Normal file
19
app/FailedJob.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class FailedJob extends Model
|
||||
{
|
||||
const CREATED_AT = 'failed_at';
|
||||
const UPDATED_AT = 'failed_at';
|
||||
|
||||
public $timestamps = 'failed_at';
|
||||
|
||||
public function getFailedAtAttribute($val)
|
||||
{
|
||||
return Carbon::parse($val);
|
||||
}
|
||||
}
|
105
app/Http/Controllers/Admin/AdminDiscoverController.php
Normal file
105
app/Http/Controllers/Admin/AdminDiscoverController.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use DB, Cache;
|
||||
use App\{
|
||||
DiscoverCategory,
|
||||
DiscoverCategoryHashtag,
|
||||
Hashtag,
|
||||
Media,
|
||||
Profile,
|
||||
StatusHashtag
|
||||
};
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
trait AdminDiscoverController
|
||||
{
|
||||
public function discoverHome()
|
||||
{
|
||||
$categories = DiscoverCategory::orderByDesc('id')->paginate(10);
|
||||
return view('admin.discover.home', compact('categories'));
|
||||
}
|
||||
|
||||
public function discoverCreateCategory()
|
||||
{
|
||||
return view('admin.discover.create-category');
|
||||
}
|
||||
|
||||
public function discoverCreateCategoryStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|min:1',
|
||||
'active' => 'required|boolean',
|
||||
'media' => 'nullable|integer|min:1'
|
||||
]);
|
||||
|
||||
$name = $request->input('name');
|
||||
$slug = str_slug($name);
|
||||
$active = $request->input('active');
|
||||
$media = (int) $request->input('media');
|
||||
|
||||
$media = Media::findOrFail($media);
|
||||
|
||||
$category = DiscoverCategory::firstOrNew(['slug' => $slug]);
|
||||
$category->name = $name;
|
||||
$category->active = $active;
|
||||
$category->media_id = $media->id;
|
||||
$category->save();
|
||||
return $category;
|
||||
}
|
||||
|
||||
public function discoverCategoryEdit(Request $request, $id)
|
||||
{
|
||||
$category = DiscoverCategory::findOrFail($id);
|
||||
return view('admin.discover.show', compact('category'));
|
||||
}
|
||||
|
||||
public function discoverCategoryUpdate(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|min:1',
|
||||
'active' => 'required|boolean',
|
||||
'media' => 'nullable|integer|min:1',
|
||||
'hashtags' => 'nullable|string'
|
||||
]);
|
||||
$name = $request->input('name');
|
||||
$slug = str_slug($name);
|
||||
$active = $request->input('active');
|
||||
$media = (int) $request->input('media');
|
||||
$media = Media::findOrFail($media);
|
||||
|
||||
$category = DiscoverCategory::findOrFail($id);
|
||||
$category->name = $name;
|
||||
$category->active = $active;
|
||||
$category->media_id = $media->id;
|
||||
$category->save();
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
public function discoveryCategoryTagStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'category_id' => 'required|integer|min:1',
|
||||
'hashtag' => 'required|string',
|
||||
'action' => 'required|string|min:1|max:6'
|
||||
]);
|
||||
$category_id = $request->input('category_id');
|
||||
$category = DiscoverCategory::findOrFail($category_id);
|
||||
$hashtag = Hashtag::whereName($request->input('hashtag'))->firstOrFail();
|
||||
|
||||
$tag = DiscoverCategoryHashtag::firstOrCreate([
|
||||
'hashtag_id' => $hashtag->id,
|
||||
'discover_category_id' => $category->id
|
||||
]);
|
||||
|
||||
if($request->input('action') == 'delete') {
|
||||
$tag->delete();
|
||||
return [];
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
}
|
101
app/Http/Controllers/Admin/AdminInstanceController.php
Normal file
101
app/Http/Controllers/Admin/AdminInstanceController.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use DB, Cache;
|
||||
use App\{Instance, Profile};
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
trait AdminInstanceController
|
||||
{
|
||||
|
||||
public function instances(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'filter' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'min:1',
|
||||
'max:20',
|
||||
Rule::in(['autocw', 'unlisted', 'banned'])
|
||||
],
|
||||
]);
|
||||
if($request->has('filter') && $request->filled('filter')) {
|
||||
switch ($request->filter) {
|
||||
case 'autocw':
|
||||
$instances = Instance::whereAutoCw(true)->orderByDesc('id')->paginate(5);
|
||||
break;
|
||||
case 'unlisted':
|
||||
$instances = Instance::whereUnlisted(true)->orderByDesc('id')->paginate(5);
|
||||
break;
|
||||
case 'banned':
|
||||
$instances = Instance::whereBanned(true)->orderByDesc('id')->paginate(5);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$instances = Instance::orderByDesc('id')->paginate(5);
|
||||
}
|
||||
return view('admin.instances.home', compact('instances'));
|
||||
}
|
||||
|
||||
public function instanceScan(Request $request)
|
||||
{
|
||||
DB::transaction(function() {
|
||||
Profile::whereNotNull('domain')
|
||||
->groupBy('domain')
|
||||
->chunk(50, function($domains) {
|
||||
foreach($domains as $domain) {
|
||||
Instance::firstOrCreate([
|
||||
'domain' => $domain->domain
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function instanceShow(Request $request, $id)
|
||||
{
|
||||
$instance = Instance::findOrFail($id);
|
||||
return view('admin.instances.show', compact('instance'));
|
||||
}
|
||||
|
||||
public function instanceEdit(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'action' => [
|
||||
'required',
|
||||
'string',
|
||||
'min:1',
|
||||
'max:20',
|
||||
Rule::in(['autocw', 'unlist', 'ban'])
|
||||
],
|
||||
]);
|
||||
|
||||
$instance = Instance::findOrFail($id);
|
||||
$unlisted = $instance->unlisted;
|
||||
$autocw = $instance->auto_cw;
|
||||
$banned = $instance->banned;
|
||||
|
||||
switch ($request->action) {
|
||||
case 'autocw':
|
||||
$instance->auto_cw = $autocw == true ? false : true;
|
||||
$instance->save();
|
||||
break;
|
||||
|
||||
case 'unlist':
|
||||
$instance->unlisted = $unlisted == true ? false : true;
|
||||
$instance->save();
|
||||
break;
|
||||
|
||||
case 'ban':
|
||||
$instance->banned = $banned == true ? false : true;
|
||||
$instance->save();
|
||||
break;
|
||||
}
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
48
app/Http/Controllers/Admin/AdminMediaController.php
Normal file
48
app/Http/Controllers/Admin/AdminMediaController.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use DB, Cache;
|
||||
use App\{
|
||||
Media,
|
||||
Profile,
|
||||
Status
|
||||
};
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
trait AdminMediaController
|
||||
{
|
||||
public function media(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'layout' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'min:1',
|
||||
'max:4',
|
||||
Rule::in(['grid','list'])
|
||||
],
|
||||
'search' => 'nullable|string|min:1|max:20'
|
||||
]);
|
||||
if($request->filled('search')) {
|
||||
$profiles = Profile::where('username', 'like', '%'.$request->input('search').'%')->pluck('id')->toArray();
|
||||
$media = Media::whereHas('status')
|
||||
->with('status')
|
||||
->orderby('id', 'desc')
|
||||
->whereIn('profile_id', $profiles)
|
||||
->orWhere('mime', $request->input('search'))
|
||||
->paginate(12);
|
||||
} else {
|
||||
$media = Media::whereHas('status')->with('status')->orderby('id', 'desc')->paginate(12);
|
||||
}
|
||||
return view('admin.media.home', compact('media'));
|
||||
}
|
||||
|
||||
public function mediaShow(Request $request, $id)
|
||||
{
|
||||
$media = Media::findOrFail($id);
|
||||
return view('admin.media.show', compact('media'));
|
||||
}
|
||||
}
|
114
app/Http/Controllers/Admin/AdminSettingsController.php
Normal file
114
app/Http/Controllers/Admin/AdminSettingsController.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use Artisan, Cache, DB;
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
use App\{Comment, Like, Media, Page, Profile, Report, Status, User};
|
||||
use App\Http\Controllers\Controller;
|
||||
use Jackiedo\DotenvEditor\Facades\DotenvEditor;
|
||||
use App\Util\Lexer\PrettyNumber;
|
||||
|
||||
trait AdminSettingsController
|
||||
{
|
||||
public function settings(Request $request)
|
||||
{
|
||||
return view('admin.settings.home');
|
||||
}
|
||||
|
||||
public function settingsBackups(Request $request)
|
||||
{
|
||||
$path = storage_path('app/PixelFed');
|
||||
$files = new \DirectoryIterator($path);
|
||||
return view('admin.settings.backups', compact('files'));
|
||||
}
|
||||
|
||||
public function settingsConfig(Request $request, DotenvEditor $editor)
|
||||
{
|
||||
return view('admin.settings.config', compact('editor'));
|
||||
}
|
||||
|
||||
public function settingsMaintenance(Request $request)
|
||||
{
|
||||
return view('admin.settings.maintenance');
|
||||
}
|
||||
|
||||
public function settingsStorage(Request $request)
|
||||
{
|
||||
$databaseSum = Cache::remember('admin:settings:storage:db:storageUsed', 360, function() {
|
||||
$q = 'SELECT sum(ROUND(((data_length + index_length)), 0)) AS size FROM information_schema.TABLES WHERE table_schema = ?';
|
||||
$db = config('database.default');
|
||||
$db = config("database.connections.{$db}.database");
|
||||
return DB::select($q, [$db])[0]->size;
|
||||
});
|
||||
$mediaSum = Cache::remember('admin:settings:storage:media:storageUsed', 360, function() {
|
||||
return Media::sum('size');
|
||||
});
|
||||
$backupSum = Cache::remember('admin:settings:storage:backups:storageUsed', 360, function() {
|
||||
$dir = storage_path('app/'.config('app.name'));
|
||||
$size = 0;
|
||||
foreach (glob(rtrim($dir, '/').'/*', GLOB_NOSORT) as $each) {
|
||||
$size += is_file($each) ? filesize($each) : folderSize($each);
|
||||
}
|
||||
return $size;
|
||||
});
|
||||
$storage = new \StdClass;
|
||||
$storage->total = disk_total_space(base_path());
|
||||
$storage->free = disk_free_space(base_path());
|
||||
$storage->prettyTotal = PrettyNumber::size($storage->total, false, false);
|
||||
$storage->prettyFree = PrettyNumber::size($storage->free, false, false);
|
||||
$storage->percentFree = ceil($storage->free / $storage->total * 100);
|
||||
$storage->percentUsed = ceil(100 - $storage->percentFree);
|
||||
$storage->media = [
|
||||
'used' => $mediaSum,
|
||||
'prettyUsed' => PrettyNumber::size($mediaSum),
|
||||
'percentUsed' => ceil($mediaSum / $storage->total * 100)
|
||||
];
|
||||
$storage->backups = [
|
||||
'used' => $backupSum
|
||||
];
|
||||
$storage->database = [
|
||||
'used' => $databaseSum
|
||||
];
|
||||
return view('admin.settings.storage', compact('storage'));
|
||||
}
|
||||
|
||||
public function settingsFeatures(Request $request)
|
||||
{
|
||||
return view('admin.settings.features');
|
||||
}
|
||||
|
||||
public function settingsHomeStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'APP_NAME' => 'required|string',
|
||||
]);
|
||||
Artisan::call('config:clear');
|
||||
DotenvEditor::setKey('APP_NAME', $request->input('APP_NAME'));
|
||||
DotenvEditor::save();
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function settingsPages(Request $request)
|
||||
{
|
||||
$pages = Page::orderByDesc('updated_at')->paginate(10);
|
||||
return view('admin.pages.home', compact('pages'));
|
||||
}
|
||||
|
||||
public function settingsPageEdit(Request $request)
|
||||
{
|
||||
return view('admin.pages.edit');
|
||||
}
|
||||
|
||||
public function settingsSystem(Request $request)
|
||||
{
|
||||
$sys = [
|
||||
'pixelfed' => config('pixelfed.version'),
|
||||
'mysql' => DB::select( DB::raw("select version()") )[0]->{'version()'},
|
||||
'php' => phpversion(),
|
||||
'redis' => explode(' ',exec('redis-cli -v'))[1],
|
||||
];
|
||||
return view('admin.settings.system', compact('sys'));
|
||||
}
|
||||
}
|
|
@ -2,21 +2,38 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Media;
|
||||
use App\Like;
|
||||
use App\Profile;
|
||||
use App\Report;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
use App\{
|
||||
FailedJob,
|
||||
Hashtag,
|
||||
Instance,
|
||||
Media,
|
||||
Like,
|
||||
OauthClient,
|
||||
Profile,
|
||||
Report,
|
||||
Status,
|
||||
User
|
||||
};
|
||||
use DB, Cache;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Jackiedo\DotenvEditor\DotenvEditor;
|
||||
use App\Http\Controllers\Admin\AdminReportController;
|
||||
use App\Http\Controllers\Admin\{
|
||||
AdminDiscoverController,
|
||||
AdminInstanceController,
|
||||
AdminReportController,
|
||||
AdminMediaController,
|
||||
AdminSettingsController
|
||||
};
|
||||
use App\Util\Lexer\PrettyNumber;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
use AdminReportController;
|
||||
use AdminReportController,
|
||||
AdminDiscoverController,
|
||||
AdminMediaController,
|
||||
AdminSettingsController,
|
||||
AdminInstanceController;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
@ -26,7 +43,55 @@ class AdminController extends Controller
|
|||
|
||||
public function home()
|
||||
{
|
||||
return view('admin.home');
|
||||
$data = Cache::remember('admin:dashboard:home:data', 15, function() {
|
||||
return [
|
||||
'failedjobs' => [
|
||||
'count' => PrettyNumber::convert(FailedJob::where('failed_at', '>=', \Carbon\Carbon::now()->subDay())->count()),
|
||||
'graph' => FailedJob::selectRaw('count(*) as count, day(failed_at) as d')->groupBy('d')->whereBetween('failed_at',[now()->subDays(24), now()])->orderBy('d')->pluck('count')
|
||||
],
|
||||
'reports' => [
|
||||
'count' => PrettyNumber::convert(Report::whereNull('admin_seen')->count()),
|
||||
'graph' => Report::selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'statuses' => [
|
||||
'count' => PrettyNumber::convert(Status::whereNull('in_reply_to_id')->whereNull('reblog_of_id')->count()),
|
||||
'graph' => Status::selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'replies' => [
|
||||
'count' => PrettyNumber::convert(Status::whereNotNull('in_reply_to_id')->count()),
|
||||
'graph' => Status::whereNotNull('in_reply_to_id')->selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'shares' => [
|
||||
'count' => PrettyNumber::convert(Status::whereNotNull('reblog_of_id')->count()),
|
||||
'graph' => Status::whereNotNull('reblog_of_id')->selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'likes' => [
|
||||
'count' => PrettyNumber::convert(Like::count()),
|
||||
'graph' => Like::selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'profiles' => [
|
||||
'count' => PrettyNumber::convert(Profile::count()),
|
||||
'graph' => Profile::selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'users' => [
|
||||
'count' => PrettyNumber::convert(User::count()),
|
||||
'graph' => User::selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'instances' => [
|
||||
'count' => PrettyNumber::convert(Instance::count()),
|
||||
'graph' => Instance::selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(28), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'media' => [
|
||||
'count' => PrettyNumber::convert(Media::count()),
|
||||
'graph' => Media::selectRaw('count(*) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
],
|
||||
'storage' => [
|
||||
'count' => Media::sum('size'),
|
||||
'graph' => Media::selectRaw('sum(size) as count, day(created_at) as day')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
|
||||
]
|
||||
];
|
||||
});
|
||||
return view('admin.home', compact('data'));
|
||||
}
|
||||
|
||||
public function users(Request $request)
|
||||
|
@ -35,6 +100,7 @@ class AdminController extends Controller
|
|||
$dir = $request->query('dir') ?? 'desc';
|
||||
$stats = $this->collectUserStats($request);
|
||||
$users = User::withCount('statuses')->orderBy($col, $dir)->paginate(10);
|
||||
|
||||
return view('admin.users.home', compact('users', 'stats'));
|
||||
}
|
||||
|
||||
|
@ -59,16 +125,23 @@ class AdminController extends Controller
|
|||
return view('admin.statuses.show', compact('status'));
|
||||
}
|
||||
|
||||
public function media(Request $request)
|
||||
{
|
||||
$media = Status::whereHas('media')->orderby('id', 'desc')->paginate(12);
|
||||
|
||||
return view('admin.media.home', compact('media'));
|
||||
}
|
||||
|
||||
public function reports(Request $request)
|
||||
{
|
||||
$reports = Report::orderBy('created_at','desc')->paginate(12);
|
||||
$filter = $request->input('filter');
|
||||
if(in_array($filter, ['open', 'closed'])) {
|
||||
if($filter == 'open') {
|
||||
$reports = Report::orderBy('created_at','desc')
|
||||
->whereNotNull('admin_seen')
|
||||
->paginate(10);
|
||||
} else {
|
||||
$reports = Report::orderBy('created_at','desc')
|
||||
->whereNull('admin_seen')
|
||||
->paginate(10);
|
||||
}
|
||||
} else {
|
||||
$reports = Report::orderBy('created_at','desc')
|
||||
->paginate(10);
|
||||
}
|
||||
return view('admin.reports.home', compact('reports'));
|
||||
}
|
||||
|
||||
|
@ -78,7 +151,6 @@ class AdminController extends Controller
|
|||
return view('admin.reports.show', compact('report'));
|
||||
}
|
||||
|
||||
|
||||
protected function collectUserStats($request)
|
||||
{
|
||||
$total_duration = $request->query('total_duration') ?? '30';
|
||||
|
@ -106,4 +178,35 @@ class AdminController extends Controller
|
|||
return $stats;
|
||||
|
||||
}
|
||||
|
||||
public function profiles(Request $request)
|
||||
{
|
||||
$profiles = Profile::orderBy('id','desc')->paginate(10);
|
||||
return view('admin.profiles.home', compact('profiles'));
|
||||
}
|
||||
|
||||
public function appsHome(Request $request)
|
||||
{
|
||||
$filter = $request->input('filter');
|
||||
if(in_array($filter, ['revoked'])) {
|
||||
$apps = OauthClient::with('user')
|
||||
->whereNotNull('user_id')
|
||||
->whereRevoked(true)
|
||||
->orderByDesc('id')
|
||||
->paginate(10);
|
||||
} else {
|
||||
$apps = OauthClient::with('user')
|
||||
->whereNotNull('user_id')
|
||||
->orderByDesc('id')
|
||||
->paginate(10);
|
||||
}
|
||||
return view('admin.apps.home', compact('apps'));
|
||||
}
|
||||
|
||||
public function hashtagsHome(Request $request)
|
||||
{
|
||||
$hashtags = Hashtag::orderByDesc('id')->paginate(10);
|
||||
return view('admin.hashtags.home', compact('hashtags'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@ use App\{
|
|||
Avatar,
|
||||
Notification,
|
||||
Media,
|
||||
Profile
|
||||
Profile,
|
||||
Status
|
||||
};
|
||||
use App\Transformer\Api\{
|
||||
AccountTransformer,
|
||||
|
@ -23,6 +24,7 @@ use App\Transformer\Api\{
|
|||
};
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use App\Jobs\AvatarPipeline\AvatarOptimize;
|
||||
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||
use App\Jobs\VideoPipeline\{
|
||||
|
@ -97,13 +99,46 @@ class BaseApiController extends Controller
|
|||
|
||||
public function accountStatuses(Request $request, $id)
|
||||
{
|
||||
$pid = Auth::user()->profile->id;
|
||||
$profile = Profile::findOrFail($id);
|
||||
$statuses = $profile->statuses();
|
||||
if($pid === $profile->id) {
|
||||
$statuses = $statuses->orderBy('id', 'desc')->paginate(20);
|
||||
$this->validate($request, [
|
||||
'only_media' => 'nullable',
|
||||
'pinned' => 'nullable',
|
||||
'exclude_replies' => 'nullable',
|
||||
'max_id' => 'nullable|integer|min:1',
|
||||
'since_id' => 'nullable|integer|min:1',
|
||||
'min_id' => 'nullable|integer|min:1',
|
||||
'limit' => 'nullable|integer|min:1|max:24'
|
||||
]);
|
||||
$limit = $request->limit ?? 20;
|
||||
$max_id = $request->max_id ?? false;
|
||||
$min_id = $request->min_id ?? false;
|
||||
$since_id = $request->since_id ?? false;
|
||||
$only_media = $request->only_media ?? false;
|
||||
$user = Auth::user();
|
||||
$account = Profile::findOrFail($id);
|
||||
$statuses = $account->statuses()->getQuery();
|
||||
if($only_media == true) {
|
||||
$statuses = $statuses
|
||||
->whereHas('media')
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id');
|
||||
}
|
||||
if($id == $account->id && !$max_id && !$min_id && !$since_id) {
|
||||
$statuses = $statuses->orderBy('id', 'desc')
|
||||
->paginate($limit);
|
||||
} else if($since_id) {
|
||||
$statuses = $statuses->where('id', '>', $since_id)
|
||||
->orderBy('id', 'DESC')
|
||||
->paginate($limit);
|
||||
} else if($min_id) {
|
||||
$statuses = $statuses->where('id', '>', $min_id)
|
||||
->orderBy('id', 'ASC')
|
||||
->paginate($limit);
|
||||
} else if($max_id) {
|
||||
$statuses = $statuses->where('id', '<', $max_id)
|
||||
->orderBy('id', 'DESC')
|
||||
->paginate($limit);
|
||||
} else {
|
||||
$statuses = $statuses->whereVisibility('public')->orderBy('id', 'desc')->paginate(20);
|
||||
$statuses = $statuses->whereVisibility('public')->orderBy('id', 'desc')->paginate($limit);
|
||||
}
|
||||
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
@ -265,4 +300,5 @@ class BaseApiController extends Controller
|
|||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -116,7 +116,13 @@ class RegisterController extends Controller
|
|||
*/
|
||||
public function showRegistrationForm()
|
||||
{
|
||||
$view = config('pixelfed.open_registration') == true ? 'auth.register' : 'site.closed-registration';
|
||||
$count = User::count();
|
||||
$limit = config('pixelfed.max_users');
|
||||
if($limit && $limit <= $count) {
|
||||
$view = 'site.closed-registration';
|
||||
} else {
|
||||
$view = config('pixelfed.open_registration') == true ? 'auth.register' : 'site.closed-registration';
|
||||
}
|
||||
return view($view);
|
||||
}
|
||||
|
||||
|
@ -128,7 +134,9 @@ class RegisterController extends Controller
|
|||
*/
|
||||
public function register(Request $request)
|
||||
{
|
||||
if(false == config('pixelfed.open_registration')) {
|
||||
$count = User::count();
|
||||
$limit = config('pixelfed.max_users');
|
||||
if(false == config('pixelfed.open_registration') || $limit && $limit <= $count) {
|
||||
return abort(403);
|
||||
}
|
||||
|
||||
|
|
69
app/Http/Controllers/CircleController.php
Normal file
69
app/Http/Controllers/CircleController.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Auth;
|
||||
use App\{
|
||||
Circle,
|
||||
CircleProfile,
|
||||
Profile,
|
||||
Status,
|
||||
};
|
||||
|
||||
class CircleController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function home(Request $request)
|
||||
{
|
||||
$circles = Circle::whereProfileId(Auth::user()->profile->id)
|
||||
->orderByDesc('created_at')
|
||||
->paginate(10);
|
||||
return view('account.circles.home', compact('circles'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view('account.circles.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required|string|min:1',
|
||||
'description' => 'nullable|string|max:255',
|
||||
'scope' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in([
|
||||
'public',
|
||||
'private',
|
||||
'unlisted',
|
||||
'exclusive'
|
||||
])
|
||||
],
|
||||
]);
|
||||
|
||||
$circle = Circle::firstOrCreate([
|
||||
'profile_id' => Auth::user()->profile->id,
|
||||
'name' => $request->input('name')
|
||||
], [
|
||||
'description' => $request->input('description'),
|
||||
'scope' => $request->input('scope'),
|
||||
'active' => false
|
||||
]);
|
||||
|
||||
return redirect(route('account.circles'));
|
||||
}
|
||||
|
||||
public function show(Request $request, $id)
|
||||
{
|
||||
$circle = Circle::findOrFail($id);
|
||||
return view('account.circles.show', compact('circle'));
|
||||
}
|
||||
}
|
10
app/Http/Controllers/CircleProfileController.php
Normal file
10
app/Http/Controllers/CircleProfileController.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CircleProfileController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
24
app/Http/Controllers/DeckController.php
Normal file
24
app/Http/Controllers/DeckController.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DeckController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function home()
|
||||
{
|
||||
return view('deck.index');
|
||||
}
|
||||
|
||||
|
||||
public function insights()
|
||||
{
|
||||
return view('deck.insights.index');
|
||||
}
|
||||
}
|
|
@ -20,11 +20,12 @@ class DirectMessageController extends Controller
|
|||
public function inbox(Request $request)
|
||||
{
|
||||
$profile = Auth::user()->profile;
|
||||
$inbox = DirectMessage::whereToId($profile->id)
|
||||
$inbox = DirectMessage::selectRaw('*, max(created_at) as createdAt')
|
||||
->whereToId($profile->id)
|
||||
->with(['author','status'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->groupBy('from_id')
|
||||
->paginate(10);
|
||||
->orderBy('createdAt', 'desc')
|
||||
->groupBy('from_id')
|
||||
->paginate(12);
|
||||
return view('account.messages', compact('inbox'));
|
||||
|
||||
}
|
||||
|
@ -40,10 +41,12 @@ class DirectMessageController extends Controller
|
|||
$msg = DirectMessage::whereToId($profile->id)
|
||||
->findOrFail($mid);
|
||||
|
||||
$thread = DirectMessage::whereToId($profile->id)
|
||||
->orWhere([['from_id', $profile->id],['to_id', $msg->from_id]])
|
||||
$thread = DirectMessage::whereIn('to_id', [$profile->id, $msg->from_id])
|
||||
->whereIn('from_id', [$profile->id,$msg->from_id])
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(10);
|
||||
->paginate(30);
|
||||
|
||||
$thread = $thread->reverse();
|
||||
|
||||
return view('account.message', compact('msg', 'profile', 'thread'));
|
||||
}
|
||||
|
|
10
app/Http/Controllers/DiscoverCategoryController.php
Normal file
10
app/Http/Controllers/DiscoverCategoryController.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DiscoverCategoryController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
10
app/Http/Controllers/DiscoverCategoryHashtagController.php
Normal file
10
app/Http/Controllers/DiscoverCategoryHashtagController.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DiscoverCategoryHashtagController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
|
@ -3,10 +3,12 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\{
|
||||
DiscoverCategory,
|
||||
Follower,
|
||||
Hashtag,
|
||||
Profile,
|
||||
Status,
|
||||
StatusHashtag,
|
||||
UserFilter
|
||||
};
|
||||
use Auth, DB, Cache;
|
||||
|
@ -28,7 +30,7 @@ class DiscoverController extends Controller
|
|||
{
|
||||
$this->validate($request, [
|
||||
'page' => 'nullable|integer|min:1|max:10',
|
||||
]);
|
||||
]);
|
||||
|
||||
$tag = Hashtag::with('posts')
|
||||
->withCount('posts')
|
||||
|
@ -51,4 +53,30 @@ class DiscoverController extends Controller
|
|||
|
||||
return view('discover.tags.show', compact('tag', 'posts'));
|
||||
}
|
||||
|
||||
public function showCategory(Request $request, $slug)
|
||||
{
|
||||
$tag = DiscoverCategory::whereActive(true)
|
||||
->whereSlug($slug)
|
||||
->firstOrFail();
|
||||
|
||||
// todo refactor this mess
|
||||
$tagids = $tag->hashtags->pluck('id')->toArray();
|
||||
$sids = StatusHashtag::whereIn('hashtag_id', $tagids)->orderByDesc('status_id')->take(500)->pluck('status_id')->toArray();
|
||||
$posts = Status::whereIn('id', $sids)->whereNull('uri')->whereType('photo')->whereNull('in_reply_to_id')->whereNull('reblog_of_id')->orderByDesc('created_at')->paginate(21);
|
||||
$tag->posts_count = $tag->posts()->count();
|
||||
return view('discover.tags.category', compact('tag', 'posts'));
|
||||
}
|
||||
|
||||
public function showPersonal(Request $request)
|
||||
{
|
||||
$profile = Auth::user()->profile;
|
||||
// todo refactor this mess
|
||||
$tags = Hashtag::whereHas('posts')->orderByRaw('rand()')->take(5)->get();
|
||||
$following = $profile->following->pluck('id');
|
||||
$following = $following->push($profile->id)->toArray();
|
||||
$posts = Status::withCount(['likes','comments'])->whereNotIn('profile_id', $following)->whereHas('media')->whereType('photo')->orderByDesc('created_at')->paginate(21);
|
||||
$posts->post_count = Status::whereNotIn('profile_id', $following)->whereHas('media')->whereType('photo')->count();
|
||||
return view('discover.personal', compact('posts', 'tags'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,37 +82,38 @@ class FederationController extends Controller
|
|||
{
|
||||
$res = Cache::remember('api:nodeinfo', 60, function () {
|
||||
return [
|
||||
'metadata' => [
|
||||
'nodeName' => config('app.name'),
|
||||
'software' => [
|
||||
'homepage' => 'https://pixelfed.org',
|
||||
'github' => 'https://github.com/pixelfed',
|
||||
'follow' => 'https://mastodon.social/@pixelfed',
|
||||
],
|
||||
],
|
||||
'openRegistrations' => config('pixelfed.open_registration'),
|
||||
'protocols' => [
|
||||
'activitypub',
|
||||
],
|
||||
'services' => [
|
||||
'inbound' => [],
|
||||
'outbound' => [],
|
||||
],
|
||||
'software' => [
|
||||
'name' => 'pixelfed',
|
||||
'version' => config('pixelfed.version'),
|
||||
],
|
||||
'usage' => [
|
||||
'localPosts' => \App\Status::whereLocal(true)->whereHas('media')->count(),
|
||||
'localComments' => \App\Status::whereLocal(true)->whereNotNull('in_reply_to_id')->count(),
|
||||
'users' => [
|
||||
'total' => \App\User::count(),
|
||||
'activeHalfyear' => \App\AccountLog::select('user_id')->whereAction('auth.login')->where('updated_at', '>',Carbon::now()->subMonths(6)->toDateTimeString())->groupBy('user_id')->get()->count(),
|
||||
'activeMonth' => \App\AccountLog::select('user_id')->whereAction('auth.login')->where('updated_at', '>',Carbon::now()->subMonths(1)->toDateTimeString())->groupBy('user_id')->get()->count(),
|
||||
],
|
||||
],
|
||||
'version' => '2.0',
|
||||
];
|
||||
'metadata' => [
|
||||
'nodeName' => config('app.name'),
|
||||
'software' => [
|
||||
'homepage' => 'https://pixelfed.org',
|
||||
'github' => 'https://github.com/pixelfed',
|
||||
'follow' => 'https://mastodon.social/@pixelfed',
|
||||
],
|
||||
'captcha' => (bool) config('pixelfed.recaptcha'),
|
||||
],
|
||||
'openRegistrations' => config('pixelfed.open_registration'),
|
||||
'protocols' => [
|
||||
'activitypub',
|
||||
],
|
||||
'services' => [
|
||||
'inbound' => [],
|
||||
'outbound' => [],
|
||||
],
|
||||
'software' => [
|
||||
'name' => 'pixelfed',
|
||||
'version' => config('pixelfed.version'),
|
||||
],
|
||||
'usage' => [
|
||||
'localPosts' => \App\Status::whereLocal(true)->whereHas('media')->count(),
|
||||
'localComments' => \App\Status::whereLocal(true)->whereNotNull('in_reply_to_id')->count(),
|
||||
'users' => [
|
||||
'total' => \App\User::count(),
|
||||
'activeHalfyear' => \App\AccountLog::select('user_id')->whereAction('auth.login')->where('updated_at', '>',Carbon::now()->subMonths(6)->toDateTimeString())->groupBy('user_id')->get()->count(),
|
||||
'activeMonth' => \App\AccountLog::select('user_id')->whereAction('auth.login')->where('updated_at', '>',Carbon::now()->subMonths(1)->toDateTimeString())->groupBy('user_id')->get()->count(),
|
||||
],
|
||||
],
|
||||
'version' => '2.0',
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($res, 200, [
|
||||
|
|
|
@ -39,6 +39,7 @@ class FollowerController extends Controller
|
|||
$user = Auth::user()->profile;
|
||||
$target = Profile::where('id', '!=', $user->id)->whereNull('status')->findOrFail($item);
|
||||
$private = (bool) $target->is_private;
|
||||
$remote = (bool) $target->domain;
|
||||
$blocked = UserFilter::whereUserId($target->id)
|
||||
->whereFilterType('block')
|
||||
->whereFilterableId($user->id)
|
||||
|
@ -51,7 +52,7 @@ class FollowerController extends Controller
|
|||
|
||||
$isFollowing = Follower::whereProfileId($user->id)->whereFollowingId($target->id)->count();
|
||||
|
||||
if($private == true && $isFollowing == 0) {
|
||||
if($private == true && $isFollowing == 0 || $remote == true) {
|
||||
$follow = FollowRequest::firstOrCreate([
|
||||
'follower_id' => $user->id,
|
||||
'following_id' => $target->id
|
||||
|
|
|
@ -124,7 +124,7 @@ trait Instagram
|
|||
->firstOrFail();
|
||||
$media = $request->file('media');
|
||||
$file = file_get_contents($media);
|
||||
$json = json_decode($file, true);
|
||||
$json = json_decode($file, true, 5);
|
||||
if(!$json || !isset($json['photos'])) {
|
||||
return abort(500);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
|||
use Illuminate\Http\Request;
|
||||
use App\{
|
||||
DirectMessage,
|
||||
DiscoverCategory,
|
||||
Hashtag,
|
||||
Follower,
|
||||
Like,
|
||||
|
@ -25,6 +26,7 @@ use App\Transformer\Api\{
|
|||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class InternalApiController extends Controller
|
||||
{
|
||||
|
@ -199,14 +201,21 @@ class InternalApiController extends Controller
|
|||
{
|
||||
$profile = Auth::user()->profile;
|
||||
$pid = $profile->id;
|
||||
$following = Cache::remember('feature:discover:following:'.$pid, 60, function() use ($pid) {
|
||||
$following = Cache::remember('feature:discover:following:'.$pid, 15, function() use ($pid) {
|
||||
return Follower::whereProfileId($pid)->pluck('following_id')->toArray();
|
||||
});
|
||||
$filters = Cache::remember("user:filter:list:$pid", 60, function() use($pid) {
|
||||
return UserFilter::whereUserId($pid)
|
||||
->whereFilterableType('App\Profile')
|
||||
->whereIn('filter_type', ['mute', 'block'])
|
||||
->pluck('filterable_id')->toArray();
|
||||
$filters = Cache::remember("user:filter:list:$pid", 15, function() use($pid) {
|
||||
$private = Profile::whereIsPrivate(true)
|
||||
->orWhere('unlisted', true)
|
||||
->orWhere('status', '!=', null)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
$filters = UserFilter::whereUserId($pid)
|
||||
->whereFilterableType('App\Profile')
|
||||
->whereIn('filter_type', ['mute', 'block'])
|
||||
->pluck('filterable_id')
|
||||
->toArray();
|
||||
return array_merge($private, $filters);
|
||||
});
|
||||
$following = array_merge($following, $filters);
|
||||
|
||||
|
@ -281,4 +290,94 @@ class InternalApiController extends Controller
|
|||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function stories(Request $request)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function discoverCategories(Request $request)
|
||||
{
|
||||
$categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get();
|
||||
$res = $categories->map(function($item) {
|
||||
return [
|
||||
'name' => $item->name,
|
||||
'url' => $item->url(),
|
||||
'thumb' => $item->thumb()
|
||||
];
|
||||
});
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function modAction(Request $request)
|
||||
{
|
||||
abort_unless(Auth::user()->is_admin, 403);
|
||||
$this->validate($request, [
|
||||
'action' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in([
|
||||
'autocw',
|
||||
'noautolink',
|
||||
'unlisted',
|
||||
'disable',
|
||||
'suspend'
|
||||
])
|
||||
],
|
||||
'item_id' => 'required|integer|min:1',
|
||||
'item_type' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in(['status'])
|
||||
]
|
||||
]);
|
||||
|
||||
$action = $request->input('action');
|
||||
$item_id = $request->input('item_id');
|
||||
$item_type = $request->input('item_type');
|
||||
|
||||
switch($action) {
|
||||
case 'autocw':
|
||||
$profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
|
||||
$profile->cw = true;
|
||||
$profile->save();
|
||||
break;
|
||||
|
||||
case 'noautolink':
|
||||
$profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
|
||||
$profile->no_autolink = true;
|
||||
$profile->save();
|
||||
break;
|
||||
|
||||
case 'unlisted':
|
||||
$profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
|
||||
$profile->unlisted = true;
|
||||
$profile->save();
|
||||
break;
|
||||
|
||||
case 'disable':
|
||||
$profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
|
||||
$user = $profile->user;
|
||||
$profile->status = 'disabled';
|
||||
$user->status = 'disabled';
|
||||
$profile->save();
|
||||
$user->save();
|
||||
break;
|
||||
|
||||
|
||||
case 'suspend':
|
||||
$profile = $item_type == 'status' ? Status::findOrFail($item_id)->profile : null;
|
||||
$user = $profile->user;
|
||||
$profile->status = 'suspended';
|
||||
$user->status = 'suspended';
|
||||
$profile->save();
|
||||
$user->save();
|
||||
break;
|
||||
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
return ['msg' => 200];
|
||||
}
|
||||
}
|
||||
|
|
67
app/Http/Controllers/MicroController.php
Normal file
67
app/Http/Controllers/MicroController.php
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\{
|
||||
Profile,
|
||||
Status,
|
||||
};
|
||||
use Auth, DB, Purify;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class MicroController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function composeText(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'type' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in(['text'])
|
||||
],
|
||||
'title' => 'nullable|string|max:140',
|
||||
'content' => 'required|string|max:500',
|
||||
'visibility' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in([
|
||||
'public',
|
||||
'unlisted',
|
||||
'private',
|
||||
'draft'
|
||||
])
|
||||
]
|
||||
]);
|
||||
$profile = Auth::user()->profile;
|
||||
$title = $request->input('title');
|
||||
$content = $request->input('content');
|
||||
$visibility = $request->input('visibility');
|
||||
|
||||
$status = DB::transaction(function() use($profile, $content, $visibility, $title) {
|
||||
$status = new Status;
|
||||
$status->type = 'text';
|
||||
$status->profile_id = $profile->id;
|
||||
$status->caption = strip_tags($content);
|
||||
$status->rendered = Purify::clean($content);
|
||||
$status->is_nsfw = false;
|
||||
|
||||
// TODO: remove deprecated visibility in favor of scope
|
||||
$status->visibility = $visibility;
|
||||
$status->scope = $visibility;
|
||||
$status->entities = json_encode(['title'=>$title]);
|
||||
$status->save();
|
||||
return $status;
|
||||
});
|
||||
|
||||
$fractal = new \League\Fractal\Manager();
|
||||
$fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer());
|
||||
$s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer());
|
||||
return $fractal->createData($s)->toArray();
|
||||
}
|
||||
}
|
53
app/Http/Controllers/PageController.php
Normal file
53
app/Http/Controllers/PageController.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Auth;
|
||||
use App\Page;
|
||||
|
||||
class PageController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(['auth', 'admin']);
|
||||
}
|
||||
|
||||
protected function authCheck($admin_only = false)
|
||||
{
|
||||
$auth = $admin_only ?
|
||||
Auth::check() && Auth::user()->is_admin == true :
|
||||
Auth::check();
|
||||
if($auth == false) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
public function edit(Request $request)
|
||||
{
|
||||
$this->authCheck(true);
|
||||
$this->validate($request, [
|
||||
'page' => 'required|string'
|
||||
]);
|
||||
$slug = urldecode($request->page);
|
||||
$page = Page::firstOrCreate(['slug' => $slug]);
|
||||
return view('admin.pages.edit', compact('page'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'slug' => 'required|string',
|
||||
'content' => 'required|string',
|
||||
'title' => 'nullable|string',
|
||||
'active' => 'required|boolean'
|
||||
]);
|
||||
$slug = urldecode($request->input('slug'));
|
||||
$page = Page::firstOrCreate(['slug' => $slug]);
|
||||
$page->content = $request->input('content');
|
||||
$page->title = $request->input('title');
|
||||
$page->active = (bool) $request->input('active');
|
||||
$page->save();
|
||||
return response()->json(['msg' => 200]);
|
||||
}
|
||||
}
|
|
@ -187,7 +187,7 @@ class ProfileController extends Controller
|
|||
return view('profile.private', compact('user', 'is_following'));
|
||||
}
|
||||
}
|
||||
$followers = $profile->followers()->whereNull('status')->orderBy('created_at', 'desc')->simplePaginate(12);
|
||||
$followers = $profile->followers()->whereNull('status')->orderBy('followers.created_at', 'desc')->simplePaginate(12);
|
||||
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
|
||||
if ($user->remote_url) {
|
||||
$settings = new \StdClass;
|
||||
|
@ -217,7 +217,7 @@ class ProfileController extends Controller
|
|||
return view('profile.private', compact('user', 'is_following'));
|
||||
}
|
||||
}
|
||||
$following = $profile->following()->whereNull('status')->orderBy('created_at', 'desc')->simplePaginate(12);
|
||||
$following = $profile->following()->whereNull('status')->orderBy('followers.created_at', 'desc')->simplePaginate(12);
|
||||
$is_admin = is_null($user->domain) ? $user->user->is_admin : false;
|
||||
if ($user->remote_url) {
|
||||
$settings = new \StdClass;
|
||||
|
|
|
@ -19,6 +19,7 @@ use Carbon\Carbon;
|
|||
use League\Fractal;
|
||||
use App\Transformer\Api\{
|
||||
AccountTransformer,
|
||||
RelationshipTransformer,
|
||||
StatusTransformer,
|
||||
};
|
||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
|
@ -32,7 +33,6 @@ class PublicApiController extends Controller
|
|||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('throttle:3000, 30');
|
||||
$this->fractal = new Fractal\Manager();
|
||||
$this->fractal->setSerializer(new ArraySerializer());
|
||||
}
|
||||
|
@ -222,7 +222,11 @@ class PublicApiController extends Controller
|
|||
// $timeline = Timeline::build()->local();
|
||||
$pid = Auth::user()->profile->id;
|
||||
|
||||
$private = Profile::whereIsPrivate(true)->orWhereNotNull('status')->where('id', '!=', $pid)->pluck('id');
|
||||
$private = Profile::whereIsPrivate(true)
|
||||
->orWhere('unlisted', true)
|
||||
->orWhere('status', '!=', null)
|
||||
->where('id', '!=', $pid)
|
||||
->pluck('id');
|
||||
$filters = UserFilter::whereUserId($pid)
|
||||
->whereFilterableType('App\Profile')
|
||||
->whereIn('filter_type', ['mute', 'block'])
|
||||
|
@ -330,4 +334,100 @@ class PublicApiController extends Controller
|
|||
return response()->json($res);
|
||||
|
||||
}
|
||||
|
||||
public function relationships(Request $request)
|
||||
{
|
||||
abort_if(!Auth::check(), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'id' => 'required|array|min:1|max:20',
|
||||
'id.*' => 'required|integer'
|
||||
]);
|
||||
$ids = collect($request->input('id'));
|
||||
$filtered = $ids->filter(function($v) {
|
||||
return $v != Auth::user()->profile->id;
|
||||
});
|
||||
$relations = Profile::findOrFail($filtered->all());
|
||||
$fractal = new Fractal\Resource\Collection($relations, new RelationshipTransformer());
|
||||
$res = $this->fractal->createData($fractal)->toArray();
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function account(Request $request, $id)
|
||||
{
|
||||
$profile = Profile::whereNull('status')->findOrFail($id);
|
||||
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function accountFollowers(Request $request, $id)
|
||||
{
|
||||
$profile = Profile::findOrFail($id);
|
||||
$followers = $profile->followers;
|
||||
$resource = new Fractal\Resource\Collection($followers, new AccountTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function accountFollowing(Request $request, $id)
|
||||
{
|
||||
$profile = Profile::findOrFail($id);
|
||||
$following = $profile->following;
|
||||
$resource = new Fractal\Resource\Collection($following, new AccountTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function accountStatuses(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'only_media' => 'nullable',
|
||||
'pinned' => 'nullable',
|
||||
'exclude_replies' => 'nullable',
|
||||
'max_id' => 'nullable|integer|min:1',
|
||||
'since_id' => 'nullable|integer|min:1',
|
||||
'min_id' => 'nullable|integer|min:1',
|
||||
'limit' => 'nullable|integer|min:1|max:24'
|
||||
]);
|
||||
$limit = $request->limit ?? 20;
|
||||
$max_id = $request->max_id ?? false;
|
||||
$min_id = $request->min_id ?? false;
|
||||
$since_id = $request->since_id ?? false;
|
||||
$only_media = $request->only_media ?? false;
|
||||
$user = Auth::user();
|
||||
$account = Profile::findOrFail($id);
|
||||
$statuses = $account->statuses()->getQuery();
|
||||
if($only_media == true) {
|
||||
$statuses = $statuses
|
||||
->whereHas('media')
|
||||
->whereNull('in_reply_to_id')
|
||||
->whereNull('reblog_of_id');
|
||||
}
|
||||
if($id == $account->id && !$max_id && !$min_id && !$since_id) {
|
||||
$statuses = $statuses->orderBy('id', 'desc')
|
||||
->paginate($limit);
|
||||
} else if($since_id) {
|
||||
$statuses = $statuses->where('id', '>', $since_id)
|
||||
->orderBy('id', 'DESC')
|
||||
->paginate($limit);
|
||||
} else if($min_id) {
|
||||
$statuses = $statuses->where('id', '>', $min_id)
|
||||
->orderBy('id', 'ASC')
|
||||
->paginate($limit);
|
||||
} else if($max_id) {
|
||||
$statuses = $statuses->where('id', '<', $max_id)
|
||||
->orderBy('id', 'DESC')
|
||||
->paginate($limit);
|
||||
} else {
|
||||
$statuses = $statuses->whereVisibility('public')->orderBy('id', 'desc')->paginate($limit);
|
||||
}
|
||||
$resource = new Fractal\Resource\Collection($statuses, new StatusTransformer());
|
||||
$res = $this->fractal->createData($resource)->toArray();
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use App\Hashtag;
|
|||
use App\Profile;
|
||||
use App\Status;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class SearchController extends Controller
|
||||
|
@ -24,6 +25,35 @@ class SearchController extends Controller
|
|||
$hash = hash('sha256', $tag);
|
||||
$tokens = Cache::remember('api:search:tag:'.$hash, 5, function () use ($tag) {
|
||||
$tokens = collect([]);
|
||||
if(Helpers::validateUrl($tag)) {
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
if(isset($remote['type']) && in_array($remote['type'], ['Create', 'Person']) == true) {
|
||||
$type = $remote['type'];
|
||||
if($type == 'Person') {
|
||||
$item = Helpers::profileFirstOrNew($tag);
|
||||
$tokens->push([[
|
||||
'count' => 1,
|
||||
'url' => $item->url(),
|
||||
'type' => 'profile',
|
||||
'value' => $item->username,
|
||||
'tokens' => [$item->username],
|
||||
'name' => $item->name,
|
||||
]]);
|
||||
} else if ($type == 'Create') {
|
||||
$item = Helpers::statusFirstOrFetch($tag, false);
|
||||
$tokens->push([[
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'status',
|
||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'tokens' => [$item->caption],
|
||||
'name' => $item->caption,
|
||||
'thumb' => $item->thumb(),
|
||||
]]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$hashtags = Hashtag::select('id', 'name', 'slug')->where('slug', 'like', '%'.$tag.'%')->limit(20)->get();
|
||||
if($hashtags->count() > 0) {
|
||||
$tags = $hashtags->map(function ($item, $key) {
|
||||
|
@ -41,6 +71,7 @@ class SearchController extends Controller
|
|||
$users = Profile::select('username', 'name', 'id')
|
||||
->whereNull('status')
|
||||
->where('username', 'like', '%'.$tag.'%')
|
||||
->orWhere('remote_url', $tag)
|
||||
->limit(20)
|
||||
->get();
|
||||
|
||||
|
@ -66,6 +97,7 @@ class SearchController extends Controller
|
|||
->whereNull('reblog_of_id')
|
||||
->whereProfileId(Auth::user()->profile->id)
|
||||
->where('caption', 'like', '%'.$tag.'%')
|
||||
->orWhere('uri', $tag)
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\Settings;
|
|||
|
||||
use App\AccountLog;
|
||||
use App\EmailVerification;
|
||||
use App\Instance;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\User;
|
||||
|
@ -121,7 +122,56 @@ trait PrivacySettings
|
|||
|
||||
public function blockedInstances()
|
||||
{
|
||||
$settings = Auth::user()->settings;
|
||||
return view('settings.privacy.blocked-instances');
|
||||
$pid = Auth::user()->profile->id;
|
||||
$filters = UserFilter::whereUserId($pid)
|
||||
->whereFilterableType('App\Instance')
|
||||
->whereFilterType('block')
|
||||
->orderByDesc('id')
|
||||
->paginate(10);
|
||||
return view('settings.privacy.blocked-instances', compact('filters'));
|
||||
}
|
||||
|
||||
public function blockedInstanceStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'domain' => [
|
||||
'required',
|
||||
'min:3',
|
||||
'max:100',
|
||||
function($attribute, $value, $fail) {
|
||||
if(!filter_var($value, FILTER_VALIDATE_DOMAIN)) {
|
||||
$fail($attribute. 'is invalid');
|
||||
}
|
||||
}
|
||||
]
|
||||
]);
|
||||
$domain = $request->input('domain');
|
||||
$instance = Instance::firstOrCreate(['domain' => $domain]);
|
||||
$filter = new UserFilter;
|
||||
$filter->user_id = Auth::user()->profile->id;
|
||||
$filter->filterable_id = $instance->id;
|
||||
$filter->filterable_type = 'App\Instance';
|
||||
$filter->filter_type = 'block';
|
||||
$filter->save();
|
||||
return response()->json(['msg' => 200]);
|
||||
}
|
||||
|
||||
public function blockedInstanceUnblock(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'id' => 'required|integer|min:1'
|
||||
]);
|
||||
$pid = Auth::user()->profile->id;
|
||||
|
||||
$filter = UserFilter::whereFilterableType('App\Instance')
|
||||
->whereUserId($pid)
|
||||
->findOrFail($request->input('id'));
|
||||
$filter->delete();
|
||||
return redirect(route('settings.privacy.blocked-instances'));
|
||||
}
|
||||
|
||||
public function blockedKeywords()
|
||||
{
|
||||
return view('settings.privacy.blocked-keywords');
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
|||
|
||||
use App\AccountLog;
|
||||
use App\Following;
|
||||
use App\Report;
|
||||
use App\UserFilter;
|
||||
use Auth, DB, Cache, Purify;
|
||||
use Carbon\Carbon;
|
||||
|
@ -160,6 +161,7 @@ class SettingsController extends Controller
|
|||
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.');
|
||||
|
@ -175,5 +177,18 @@ class SettingsController extends Controller
|
|||
Auth::logout();
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
public function requestFullExport(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
return view('settings.export.show');
|
||||
}
|
||||
|
||||
public function reportsHome(Request $request)
|
||||
{
|
||||
$profile = Auth::user()->profile;
|
||||
$reports = Report::whereProfileId($profile->id)->orderByDesc('created_at')->paginate(10);
|
||||
return view('settings.reports', compact('reports'));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,16 +2,10 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App;
|
||||
use App\Follower;
|
||||
use App\Profile;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
use App\UserFilter;
|
||||
use App\Util\Lexer\PrettyNumber;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use Illuminate\Http\Request;
|
||||
use App, Auth, Cache, View;
|
||||
use App\Util\Lexer\PrettyNumber;
|
||||
use App\{Follower, Page, Profile, Status, User, UserFilter};
|
||||
|
||||
class SiteController extends Controller
|
||||
{
|
||||
|
@ -47,18 +41,42 @@ class SiteController extends Controller
|
|||
|
||||
public function about()
|
||||
{
|
||||
$stats = Cache::remember('site:about:stats', 1440, function() {
|
||||
return [
|
||||
'posts' => Status::whereLocal(true)->count(),
|
||||
'users' => User::count(),
|
||||
'admin' => User::whereIsAdmin(true)->first()
|
||||
];
|
||||
$res = Cache::remember('site:about', 120, function() {
|
||||
$custom = Page::whereSlug('/site/about')->whereActive(true)->exists();
|
||||
if($custom) {
|
||||
$stats = Cache::remember('site:about:stats', 60, function() {
|
||||
return [
|
||||
'posts' => Status::whereLocal(true)->count(),
|
||||
'users' => User::count(),
|
||||
'admin' => User::whereIsAdmin(true)->first()
|
||||
];
|
||||
});
|
||||
return View::make('site.about')->with('stats', $stats)->render();
|
||||
} else {
|
||||
$stats = Cache::remember('site:about:stats', 60, function() {
|
||||
return [
|
||||
'posts' => Status::whereLocal(true)->count(),
|
||||
'users' => User::count(),
|
||||
'admin' => User::whereIsAdmin(true)->first()
|
||||
];
|
||||
});
|
||||
//return view('site.about', compact('stats'));
|
||||
return View::make('site.about')->with('stats', $stats)->render();
|
||||
}
|
||||
});
|
||||
return view('site.about', compact('stats'));
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function language()
|
||||
{
|
||||
return view('site.language');
|
||||
}
|
||||
|
||||
public function communityGuidelines(Request $request)
|
||||
{
|
||||
$slug = '/site/kb/community-guidelines';
|
||||
$page = Page::whereSlug($slug)->whereActive(true)->first();
|
||||
return view('site.help.community-guidelines', compact('page'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
|||
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
use App\Jobs\StatusPipeline\StatusDelete;
|
||||
use App\Jobs\SharePipeline\SharePipeline;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\Status;
|
||||
|
@ -234,8 +235,10 @@ class StatusController extends Controller
|
|||
$share = new Status();
|
||||
$share->profile_id = $profile->id;
|
||||
$share->reblog_of_id = $status->id;
|
||||
$share->in_reply_to_profile_id = $status->profile_id;
|
||||
$share->save();
|
||||
$count++;
|
||||
SharePipeline::dispatch($share);
|
||||
}
|
||||
|
||||
if ($request->ajax()) {
|
||||
|
|
|
@ -2,6 +2,18 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StoryController extends Controller
|
||||
{
|
||||
|
||||
public function construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
public function home(Request $request)
|
||||
{
|
||||
return view('stories.home');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,63 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class Instance extends Model
|
||||
{
|
||||
protected $fillable = ['domain'];
|
||||
protected $fillable = ['domain'];
|
||||
|
||||
public function profiles()
|
||||
{
|
||||
return $this->hasMany(Profile::class, 'domain', 'domain');
|
||||
}
|
||||
|
||||
public function statuses()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Status::class,
|
||||
Profile::class,
|
||||
'domain',
|
||||
'profile_id',
|
||||
'domain',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
public function reported()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Report::class,
|
||||
Profile::class,
|
||||
'domain',
|
||||
'reported_profile_id',
|
||||
'domain',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
public function reports()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Report::class,
|
||||
Profile::class,
|
||||
'domain',
|
||||
'profile_id',
|
||||
'domain',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
public function media()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Media::class,
|
||||
Profile::class,
|
||||
'domain',
|
||||
'profile_id',
|
||||
'domain',
|
||||
'id'
|
||||
);
|
||||
}
|
||||
|
||||
public function getUrl()
|
||||
{
|
||||
return url("/i/admin/instances/show/{$this->id}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,13 @@ class AvatarOptimize implements ShouldQueue
|
|||
protected $profile;
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -20,6 +20,13 @@ class CreateAvatar implements ShouldQueue
|
|||
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
95
app/Jobs/AvatarPipeline/ImportAvatar.php
Normal file
95
app/Jobs/AvatarPipeline/ImportAvatar.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Avatar;
|
||||
use App\Profile;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
||||
class ImportAvatar implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $url;
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($url, Profile $profile)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->profile = $profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$url = $this->url;
|
||||
$profile = $this->profile;
|
||||
|
||||
$basePath = $this->buildPath();
|
||||
}
|
||||
|
||||
public function buildPath()
|
||||
{
|
||||
$baseDir = storage_path('app/public/avatars');
|
||||
if (!is_dir($baseDir)) {
|
||||
mkdir($baseDir);
|
||||
}
|
||||
|
||||
$prefix = $this->profile->id;
|
||||
$padded = str_pad($prefix, 12, 0, STR_PAD_LEFT);
|
||||
$parts = str_split($padded, 3);
|
||||
foreach ($parts as $k => $part) {
|
||||
if ($k == 0) {
|
||||
$prefix = storage_path('app/public/avatars/'.$parts[0]);
|
||||
if (!is_dir($prefix)) {
|
||||
mkdir($prefix);
|
||||
}
|
||||
}
|
||||
if ($k == 1) {
|
||||
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1]);
|
||||
if (!is_dir($prefix)) {
|
||||
mkdir($prefix);
|
||||
}
|
||||
}
|
||||
if ($k == 2) {
|
||||
$prefix = storage_path('app/public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2]);
|
||||
if (!is_dir($prefix)) {
|
||||
mkdir($prefix);
|
||||
}
|
||||
}
|
||||
if ($k == 3) {
|
||||
$avatarpath = 'public/avatars/'.$parts[0].'/'.$parts[1].'/'.$parts[2].'/'.$parts[3];
|
||||
$prefix = storage_path('app/'.$avatarpath);
|
||||
if (!is_dir($prefix)) {
|
||||
mkdir($prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
$dir = storage_path('app/'.$avatarpath);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir);
|
||||
}
|
||||
$path = $avatarpath.'/avatar.svg';
|
||||
return storage_path('app/'.$path);
|
||||
}
|
||||
}
|
|
@ -20,6 +20,13 @@ class CommentPipeline implements ShouldQueue
|
|||
protected $status;
|
||||
protected $comment;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -21,6 +21,13 @@ class FollowActivityPubDeliver implements ShouldQueue
|
|||
|
||||
protected $followRequest;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -18,6 +18,13 @@ class FollowPipeline implements ShouldQueue
|
|||
|
||||
protected $follower;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -15,6 +15,13 @@ class ImageOptimize implements ShouldQueue
|
|||
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -16,6 +16,13 @@ class ImageResize implements ShouldQueue
|
|||
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -17,6 +17,13 @@ class ImageThumbnail implements ShouldQueue
|
|||
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Jobs\ImageOptimizePipeline;
|
||||
|
||||
use Storage;
|
||||
use App\Media;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
@ -9,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use ImageOptimizer;
|
||||
use Illuminate\Http\File;
|
||||
|
||||
class ImageUpdate implements ShouldQueue
|
||||
{
|
||||
|
@ -17,11 +19,17 @@ class ImageUpdate implements ShouldQueue
|
|||
protected $media;
|
||||
|
||||
protected $protectedMimes = [
|
||||
'image/gif',
|
||||
'image/bmp',
|
||||
'video/mp4',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
];
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
@ -43,21 +51,31 @@ class ImageUpdate implements ShouldQueue
|
|||
$path = storage_path('app/'.$media->media_path);
|
||||
$thumb = storage_path('app/'.$media->thumbnail_path);
|
||||
|
||||
try {
|
||||
if (!in_array($media->mime, $this->protectedMimes)) {
|
||||
ImageOptimizer::optimize($thumb);
|
||||
ImageOptimizer::optimize($path);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
if (in_array($media->mime, $this->protectedMimes) == true) {
|
||||
ImageOptimizer::optimize($thumb);
|
||||
ImageOptimizer::optimize($path);
|
||||
}
|
||||
|
||||
if (!is_file($path) || !is_file($thumb)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$photo_size = filesize($path);
|
||||
$thumb_size = filesize($thumb);
|
||||
$total = ($photo_size + $thumb_size);
|
||||
$media->size = $total;
|
||||
$media->save();
|
||||
|
||||
if(config('pixelfed.cloud_storage') == true) {
|
||||
$p = explode('/', $media->media_path);
|
||||
$monthHash = $p[2];
|
||||
$userHash = $p[3];
|
||||
$storagePath = "public/m/{$monthHash}/{$userHash}";
|
||||
$file = Storage::disk(config('filesystems.cloud'))->putFile($storagePath, new File($path), 'public');
|
||||
$url = Storage::disk(config('filesystems.cloud'))->url($file);
|
||||
$media->cdn_url = $url;
|
||||
$media->optimized_url = $url;
|
||||
$media->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,14 @@ class ImportInstagram implements ShouldQueue
|
|||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $job;
|
||||
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -19,6 +19,13 @@ class LikePipeline implements ShouldQueue
|
|||
|
||||
protected $like;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -18,6 +18,13 @@ class MentionPipeline implements ShouldQueue
|
|||
protected $status;
|
||||
protected $mention;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -17,7 +17,14 @@ class SharePipeline implements ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $like;
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
|
@ -37,32 +44,32 @@ class SharePipeline implements ShouldQueue
|
|||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$actor = $this->status->profile;
|
||||
$target = $this->status->parent()->profile;
|
||||
$actor = $status->profile;
|
||||
$target = $status->parent()->profile;
|
||||
|
||||
if ($status->url !== null) {
|
||||
if ($status->uri !== null) {
|
||||
// Ignore notifications to remote statuses
|
||||
return;
|
||||
}
|
||||
|
||||
$exists = Notification::whereProfileId($status->profile_id)
|
||||
->whereActorId($actor->id)
|
||||
->whereAction('like')
|
||||
->whereItemId($status->id)
|
||||
$exists = Notification::whereProfileId($target->id)
|
||||
->whereActorId($status->profile_id)
|
||||
->whereAction('share')
|
||||
->whereItemId($status->reblog_of_id)
|
||||
->whereItemType('App\Status')
|
||||
->count();
|
||||
|
||||
if ($actor->id === $status->profile_id || $exists !== 0) {
|
||||
if ($target->id === $status->profile_id || $exists !== 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$notification = new Notification();
|
||||
$notification->profile_id = $status->profile_id;
|
||||
$notification = new Notification;
|
||||
$notification->profile_id = $target->id;
|
||||
$notification->actor_id = $actor->id;
|
||||
$notification->action = 'like';
|
||||
$notification->message = $like->toText();
|
||||
$notification->rendered = $like->toHtml();
|
||||
$notification->action = 'share';
|
||||
$notification->message = $status->shareToText();
|
||||
$notification->rendered = $status->shareToHtml();
|
||||
$notification->item_id = $status->id;
|
||||
$notification->item_type = "App\Status";
|
||||
$notification->save();
|
||||
|
|
|
@ -16,7 +16,14 @@ class NewStatusPipeline 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.
|
||||
*
|
||||
|
|
|
@ -18,7 +18,14 @@ class StatusActivityPubDeliver 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.
|
||||
*
|
||||
|
|
|
@ -19,7 +19,14 @@ class StatusDelete 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.
|
||||
*
|
||||
|
|
|
@ -24,7 +24,14 @@ class StatusEntityLexer implements ShouldQueue
|
|||
protected $status;
|
||||
protected $entities;
|
||||
protected $autolink;
|
||||
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
|
|
|
@ -49,7 +49,7 @@ class VideoThumbnail implements ShouldQueue
|
|||
} elseif($video->getDurationInSeconds() < 5) {
|
||||
$video->getFrameFromSeconds(4);
|
||||
}
|
||||
$video->export()
|
||||
$video->export()
|
||||
->save($save);
|
||||
|
||||
$media->thumbnail_path = $save;
|
||||
|
|
|
@ -17,13 +17,23 @@ class Media extends Model
|
|||
*/
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(Status::class);
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
|
||||
public function url()
|
||||
{
|
||||
if(!empty($this->remote_media) && $this->remote_url) {
|
||||
$url = $this->remote_url;
|
||||
} else {
|
||||
$path = $this->media_path;
|
||||
$url = Storage::url($path);
|
||||
$url = $this->cdn_url ?? Storage::url($path);
|
||||
}
|
||||
|
||||
return url($url);
|
||||
|
@ -37,6 +47,11 @@ class Media extends Model
|
|||
return url($url);
|
||||
}
|
||||
|
||||
public function thumb()
|
||||
{
|
||||
return $this->thumbnailUrl();
|
||||
}
|
||||
|
||||
public function mimeType()
|
||||
{
|
||||
return explode('/', $this->mime)[0];
|
||||
|
|
18
app/OauthClient.php
Normal file
18
app/OauthClient.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class OauthClient extends Model
|
||||
{
|
||||
|
||||
protected $table = 'oauth_clients';
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
}
|
25
app/Page.php
Normal file
25
app/Page.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Page extends Model
|
||||
{
|
||||
const SLUG_ROOT = [
|
||||
'site',
|
||||
'page'
|
||||
];
|
||||
|
||||
protected $fillable = ['slug'];
|
||||
|
||||
public function url()
|
||||
{
|
||||
return url($this->slug);
|
||||
}
|
||||
|
||||
public function editUrl()
|
||||
{
|
||||
return url("/i/admin/settings/pages/edit?page=".urlencode($this->slug));
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ class Profile extends Model
|
|||
|
||||
protected $dates = ['deleted_at'];
|
||||
protected $hidden = ['private_key'];
|
||||
protected $visible = ['username', 'name'];
|
||||
protected $visible = ['id', 'user_id', 'username', 'name'];
|
||||
|
||||
public function user()
|
||||
{
|
||||
|
@ -274,4 +274,9 @@ class Profile extends Model
|
|||
->unique()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function circles()
|
||||
{
|
||||
return $this->hasMany(Circle::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,10 +25,8 @@ class AuthServiceProvider extends ServiceProvider
|
|||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
// Passport::routes();
|
||||
|
||||
// Passport::tokensExpireIn(now()->addDays(15));
|
||||
|
||||
// Passport::refreshTokensExpireIn(now()->addDays(30));
|
||||
Passport::routes();
|
||||
Passport::tokensExpireIn(now()->addDays(15));
|
||||
Passport::refreshTokensExpireIn(now()->addDays(30));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class ReportComment extends Model
|
||||
{
|
||||
//
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class ReportLog extends Model
|
||||
{
|
||||
//
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,11 @@ class Status extends Model
|
|||
$media = $this->firstMedia();
|
||||
$path = $media->media_path;
|
||||
$hash = is_null($media->processed_at) ? md5('unprocessed') : md5($media->created_at);
|
||||
$url = Storage::url($path)."?v={$hash}";
|
||||
if(config('pixelfed.cloud_storage') == true) {
|
||||
$url = Storage::disk(config('filesystems.cloud'))->url($path)."?v={$hash}";
|
||||
} else {
|
||||
$url = Storage::url($path)."?v={$hash}";
|
||||
}
|
||||
|
||||
return url($url);
|
||||
}
|
||||
|
@ -270,6 +274,22 @@ class Status extends Model
|
|||
__('notification.commented');
|
||||
}
|
||||
|
||||
public function shareToText()
|
||||
{
|
||||
$actorName = $this->profile->username;
|
||||
|
||||
return "{$actorName} ".__('notification.shared');
|
||||
}
|
||||
|
||||
public function shareToHtml()
|
||||
{
|
||||
$actorName = $this->profile->username;
|
||||
$actorUrl = $this->profile->url();
|
||||
|
||||
return "<a href='{$actorUrl}' class='profile-link'>{$actorName}</a> ".
|
||||
__('notification.shared');
|
||||
}
|
||||
|
||||
public function recentComments()
|
||||
{
|
||||
return $this->comments()->orderBy('created_at', 'desc')->take(3);
|
||||
|
|
|
@ -7,4 +7,14 @@ use Illuminate\Database\Eloquent\Model;
|
|||
class StatusHashtag extends Model
|
||||
{
|
||||
public $fillable = ['status_id', 'hashtag_id'];
|
||||
|
||||
public function status()
|
||||
{
|
||||
return $this->belongsTo(Status::class);
|
||||
}
|
||||
|
||||
public function hashtag()
|
||||
{
|
||||
return $this->belongsTo(Hashtag::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,36 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use Auth;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Story extends Model
|
||||
{
|
||||
//
|
||||
protected $visible = ['id'];
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo(Profile::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(StoryItem::class);
|
||||
}
|
||||
|
||||
public function reactions()
|
||||
{
|
||||
return $this->hasMany(StoryReaction::class);
|
||||
}
|
||||
|
||||
public function views()
|
||||
{
|
||||
return $this->hasMany(StoryView::class);
|
||||
}
|
||||
|
||||
public function seen($pid = false)
|
||||
{
|
||||
$id = $pid ?? Auth::user()->profile->id;
|
||||
return $this->views()->whereProfileId($id)->exists();
|
||||
}
|
||||
}
|
||||
|
|
19
app/StoryItem.php
Normal file
19
app/StoryItem.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Storage;
|
||||
|
||||
class StoryItem extends Model
|
||||
{
|
||||
public function story()
|
||||
{
|
||||
return $this->belongsTo(Story::class);
|
||||
}
|
||||
|
||||
public function url()
|
||||
{
|
||||
return Storage::url($this->media_path);
|
||||
}
|
||||
}
|
|
@ -6,5 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class StoryReaction extends Model
|
||||
{
|
||||
//
|
||||
public function story()
|
||||
{
|
||||
return $this->belongsTo(Story::class);
|
||||
}
|
||||
}
|
||||
|
|
13
app/StoryView.php
Normal file
13
app/StoryView.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class StoryView extends Model
|
||||
{
|
||||
public function story()
|
||||
{
|
||||
return $this->belongsTo(Story::class);
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
|||
'type' => 'Document',
|
||||
'mediaType' => $media->mime,
|
||||
'url' => $media->url(),
|
||||
'name' => null,
|
||||
'name' => $media->caption
|
||||
];
|
||||
}),
|
||||
'tag' => [],
|
||||
|
|
|
@ -9,8 +9,9 @@ class AccountTransformer extends Fractal\TransformerAbstract
|
|||
{
|
||||
public function transform(Profile $profile)
|
||||
{
|
||||
$is_admin = $profile->domain ? false : $profile->user->is_admin;
|
||||
return [
|
||||
'id' => $profile->id,
|
||||
'id' => (string) $profile->id,
|
||||
'username' => $profile->username,
|
||||
'acct' => $profile->username,
|
||||
'display_name' => $profile->name,
|
||||
|
@ -28,6 +29,9 @@ class AccountTransformer extends Fractal\TransformerAbstract
|
|||
'moved' => null,
|
||||
'fields' => null,
|
||||
'bot' => null,
|
||||
'website' => $profile->website,
|
||||
'software' => 'pixelfed',
|
||||
'is_admin' => (bool) $is_admin
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ class AttachmentTransformer extends Fractal\TransformerAbstract
|
|||
public function transform(Media $media)
|
||||
{
|
||||
return [
|
||||
'id' => $media->id,
|
||||
'id' => (string) $media->id,
|
||||
'type' => $media->activityVerb(),
|
||||
'url' => $media->url(),
|
||||
'remote_url' => null,
|
||||
|
|
|
@ -10,7 +10,7 @@ class MediaTransformer extends Fractal\TransformerAbstract
|
|||
public function transform(Media $media)
|
||||
{
|
||||
return [
|
||||
'id' => $media->id,
|
||||
'id' => (string) $media->id,
|
||||
'type' => $media->activityVerb(),
|
||||
'url' => $media->url(),
|
||||
'remote_url' => null,
|
||||
|
|
|
@ -10,7 +10,7 @@ class MentionTransformer extends Fractal\TransformerAbstract
|
|||
public function transform(Profile $profile)
|
||||
{
|
||||
return [
|
||||
'id' => $profile->id,
|
||||
'id' => (string) $profile->id,
|
||||
'url' => $profile->url(),
|
||||
'username' => $profile->username,
|
||||
'acct' => $profile->username,
|
||||
|
|
|
@ -15,7 +15,7 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
|||
public function transform(Notification $notification)
|
||||
{
|
||||
return [
|
||||
'id' => $notification->id,
|
||||
'id' => (string) $notification->id,
|
||||
'type' => $this->replaceTypeVerb($notification->action),
|
||||
'created_at' => (string) $notification->created_at,
|
||||
'account' => null,
|
||||
|
@ -44,6 +44,7 @@ class NotificationTransformer extends Fractal\TransformerAbstract
|
|||
'follow' => 'follow',
|
||||
'mention' => 'mention',
|
||||
'reblog' => 'share',
|
||||
'share' => 'share',
|
||||
'like' => 'favourite',
|
||||
'comment' => 'comment',
|
||||
];
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use Auth;
|
||||
use App\Profile;
|
||||
use League\Fractal;
|
||||
|
||||
|
@ -9,17 +10,18 @@ class RelationshipTransformer extends Fractal\TransformerAbstract
|
|||
{
|
||||
public function transform(Profile $profile)
|
||||
{
|
||||
$user = Auth::user()->profile;
|
||||
return [
|
||||
'id' => $profile->id,
|
||||
'following' => null,
|
||||
'followed_by' => null,
|
||||
'id' => (string) $profile->id,
|
||||
'following' => $user->follows($profile),
|
||||
'followed_by' => $user->followedBy($profile),
|
||||
'blocking' => null,
|
||||
'muting' => null,
|
||||
'muting_notifications' => null,
|
||||
'requested' => null,
|
||||
'domain_blocking' => null,
|
||||
'showing_reblogs' => null,
|
||||
'endorsed' => null
|
||||
'endorsed' => false
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,12 @@ class ResultsTransformer extends Fractal\TransformerAbstract
|
|||
{
|
||||
|
||||
protected $defaultIncludes = [
|
||||
'account',
|
||||
'mentions',
|
||||
'media_attachments',
|
||||
'tags',
|
||||
'accounts',
|
||||
'statuses',
|
||||
'hashtags',
|
||||
];
|
||||
public function transform()
|
||||
|
||||
public function transform($results)
|
||||
{
|
||||
return [
|
||||
'accounts' => [],
|
||||
|
@ -21,4 +21,22 @@ class ResultsTransformer extends Fractal\TransformerAbstract
|
|||
'hashtags' => []
|
||||
];
|
||||
}
|
||||
|
||||
public function includeAccounts($results)
|
||||
{
|
||||
$accounts = $results->accounts;
|
||||
return $this->collection($accounts, new AccountTransformer());
|
||||
}
|
||||
|
||||
public function includeStatuses($results)
|
||||
{
|
||||
$statuses = $results->statuses;
|
||||
return $this->collection($statuses, new StatusTransformer());
|
||||
}
|
||||
|
||||
public function includeTags($results)
|
||||
{
|
||||
$hashtags = $status->hashtags;
|
||||
return $this->collection($hashtags, new HashtagTransformer());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
|||
public function transform(Status $status)
|
||||
{
|
||||
return [
|
||||
'id' => $status->id,
|
||||
'id' => (string) $status->id,
|
||||
'uri' => $status->url(),
|
||||
'url' => $status->url(),
|
||||
'in_reply_to_id' => $status->in_reply_to_id,
|
||||
|
|
27
app/Transformer/Api/StoryItemTransformer.php
Normal file
27
app/Transformer/Api/StoryItemTransformer.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\StoryItem;
|
||||
use League\Fractal;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StoryItemTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
|
||||
public function transform(StoryItem $item)
|
||||
{
|
||||
return [
|
||||
'id' => (string) Str::uuid(),
|
||||
'type' => $item->type,
|
||||
'length' => $item->duration,
|
||||
'src' => $item->url(),
|
||||
'preview' => null,
|
||||
'link' => null,
|
||||
'linkText' => null,
|
||||
'time' => $item->updated_at->format('U'),
|
||||
'seen' => $item->story->seen(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
34
app/Transformer/Api/StoryTransformer.php
Normal file
34
app/Transformer/Api/StoryTransformer.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Transformer\Api;
|
||||
|
||||
use App\Story;
|
||||
use League\Fractal;
|
||||
|
||||
class StoryTransformer extends Fractal\TransformerAbstract
|
||||
{
|
||||
protected $defaultIncludes = [
|
||||
'items',
|
||||
];
|
||||
|
||||
public function transform(Story $story)
|
||||
{
|
||||
return [
|
||||
'id' => (string) $story->id,
|
||||
'photo' => $story->profile->avatarUrl(),
|
||||
'name' => '',
|
||||
'link' => '',
|
||||
'lastUpdated' => $story->updated_at->format('U'),
|
||||
'seen' => $story->seen(),
|
||||
'items' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function includeItems(Story $story)
|
||||
{
|
||||
$items = $story->items;
|
||||
|
||||
return $this->collection($items, new StoryItemTransformer());
|
||||
}
|
||||
|
||||
}
|
|
@ -62,6 +62,11 @@ class User extends Authenticatable
|
|||
);
|
||||
}
|
||||
|
||||
public function filters()
|
||||
{
|
||||
return $this->hasMany(UserFilter::class);
|
||||
}
|
||||
|
||||
public function receivesBroadcastNotificationsOn()
|
||||
{
|
||||
return 'App.User.'.$this->id;
|
||||
|
|
|
@ -21,7 +21,6 @@ class UserFilter extends Model
|
|||
->pluck('filterable_id');
|
||||
}
|
||||
|
||||
|
||||
public function blockedUserIds($profile_id)
|
||||
{
|
||||
return $this->whereUserId($profile_id)
|
||||
|
@ -29,4 +28,9 @@ class UserFilter extends Model
|
|||
->whereFilterType('block')
|
||||
->pluck('filterable_id');
|
||||
}
|
||||
|
||||
public function instance()
|
||||
{
|
||||
return $this->belongsTo(Instance::class, 'filterable_id');
|
||||
}
|
||||
}
|
||||
|
|
25
app/Util/ActivityPub/Validator/Follow.php
Normal file
25
app/Util/ActivityPub/Validator/Follow.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\ActivityPub\Validator;
|
||||
|
||||
use Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Follow {
|
||||
|
||||
public static function validate($payload)
|
||||
{
|
||||
$valid = Validator::make($payload, [
|
||||
'@context' => 'required',
|
||||
'id' => 'required|string',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(['Follow'])
|
||||
],
|
||||
'actor' => 'required|url|active_url',
|
||||
'object' => 'required|url|active_url'
|
||||
])->passes();
|
||||
|
||||
return $valid;
|
||||
}
|
||||
}
|
25
app/Util/ActivityPub/Validator/Like.php
Normal file
25
app/Util/ActivityPub/Validator/Like.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Util\ActivityPub\Validator;
|
||||
|
||||
use Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class Like {
|
||||
|
||||
public static function validate($payload)
|
||||
{
|
||||
$valid = Validator::make($payload, [
|
||||
'@context' => 'required',
|
||||
'id' => 'required|string',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in(['Like'])
|
||||
],
|
||||
'actor' => 'required|url|active_url',
|
||||
'object' => 'required|url|active_url'
|
||||
])->passes();
|
||||
|
||||
return $valid;
|
||||
}
|
||||
}
|
|
@ -17,15 +17,6 @@ class RestrictedNames
|
|||
'contact-us',
|
||||
'contact_us',
|
||||
'copyright',
|
||||
'd',
|
||||
'dashboard',
|
||||
'dev',
|
||||
'developer',
|
||||
'developers',
|
||||
'discover',
|
||||
'discovers',
|
||||
'doc',
|
||||
'docs',
|
||||
'download',
|
||||
'domainadmin',
|
||||
'domainadministrator',
|
||||
|
@ -41,10 +32,7 @@ class RestrictedNames
|
|||
'guests',
|
||||
'hostmaster',
|
||||
'hostmaster',
|
||||
'image',
|
||||
'images',
|
||||
'imap',
|
||||
'img',
|
||||
'info',
|
||||
'info',
|
||||
'is',
|
||||
|
@ -57,7 +45,6 @@ class RestrictedNames
|
|||
'mailerdaemon',
|
||||
'marketing',
|
||||
'me',
|
||||
'media',
|
||||
'mis',
|
||||
'mx',
|
||||
'new',
|
||||
|
@ -82,7 +69,6 @@ class RestrictedNames
|
|||
'pop3',
|
||||
'postmaster',
|
||||
'pricing',
|
||||
'privacy',
|
||||
'root',
|
||||
'sales',
|
||||
'security',
|
||||
|
@ -96,7 +82,6 @@ class RestrictedNames
|
|||
'sys',
|
||||
'sysadmin',
|
||||
'system',
|
||||
'terms',
|
||||
'tutorial',
|
||||
'tutorials',
|
||||
'usenet',
|
||||
|
@ -121,34 +106,68 @@ class RestrictedNames
|
|||
'account',
|
||||
'api',
|
||||
'auth',
|
||||
'bartender',
|
||||
'broadcast',
|
||||
'broadcaster',
|
||||
'booth',
|
||||
'bouncer',
|
||||
'c',
|
||||
'css',
|
||||
'checkpoint',
|
||||
'collection',
|
||||
'collections',
|
||||
'c',
|
||||
'costar',
|
||||
'costars',
|
||||
'cdn',
|
||||
'd',
|
||||
'dashboard',
|
||||
'deck',
|
||||
'dev',
|
||||
'developer',
|
||||
'developers',
|
||||
'discover',
|
||||
'discovers',
|
||||
'dj',
|
||||
'doc',
|
||||
'docs',
|
||||
'docs',
|
||||
'drive',
|
||||
'driver',
|
||||
'error',
|
||||
'explore',
|
||||
'font',
|
||||
'fonts',
|
||||
'gdpr',
|
||||
'home',
|
||||
'help',
|
||||
'helpcenter',
|
||||
'help-center',
|
||||
'help_center',
|
||||
'help_center_',
|
||||
'help-center-',
|
||||
'help-center_',
|
||||
'help_center-',
|
||||
'i',
|
||||
'img',
|
||||
'imgs',
|
||||
'image',
|
||||
'images',
|
||||
'js',
|
||||
'legal',
|
||||
'live',
|
||||
'login',
|
||||
'logout',
|
||||
'media',
|
||||
'menu',
|
||||
'oauth',
|
||||
'official',
|
||||
'p',
|
||||
'page',
|
||||
'pages',
|
||||
'photo',
|
||||
'photos',
|
||||
'password',
|
||||
'privacy',
|
||||
'reset',
|
||||
'report',
|
||||
'reports',
|
||||
|
@ -161,10 +180,14 @@ class RestrictedNames
|
|||
'statuses',
|
||||
'site',
|
||||
'sites',
|
||||
'stage',
|
||||
'static',
|
||||
'story',
|
||||
'stories',
|
||||
'support',
|
||||
'svg',
|
||||
'svgs',
|
||||
'terms',
|
||||
'telescope',
|
||||
'timeline',
|
||||
'timelines',
|
||||
|
@ -174,9 +197,11 @@ class RestrictedNames
|
|||
'username',
|
||||
'usernames',
|
||||
'vendor',
|
||||
'waiter',
|
||||
'ws',
|
||||
'wss',
|
||||
'www',
|
||||
'valet',
|
||||
'400',
|
||||
'401',
|
||||
'403',
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"ext-bcmath": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-openssl": "*",
|
||||
"beyondcode/laravel-self-diagnosis": "^1.0.2",
|
||||
"bitverse/identicon": "^1.1",
|
||||
"doctrine/dbal": "^2.7",
|
||||
|
|
|
@ -42,7 +42,7 @@ return [
|
|||
],
|
||||
|
||||
'api' => [
|
||||
'driver' => 'token',
|
||||
'driver' => 'passport',
|
||||
'provider' => 'users',
|
||||
],
|
||||
],
|
||||
|
|
|
@ -65,6 +65,21 @@ return [
|
|||
'endpoint' => env('AWS_ENDPOINT'),
|
||||
],
|
||||
|
||||
'spaces' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('DO_SPACES_KEY'),
|
||||
'secret' => env('DO_SPACES_SECRET'),
|
||||
'endpoint' => env('DO_SPACES_ENDPOINT'),
|
||||
'region' => env('DO_SPACES_REGION'),
|
||||
'bucket' => env('DO_SPACES_BUCKET'),
|
||||
'visibility' => 'public',
|
||||
'options' => [
|
||||
'CacheControl' => 'max-age=31536000'
|
||||
],
|
||||
'root' => env('DO_SPACES_ROOT','/'),
|
||||
'url' => str_replace(env('DO_SPACES_REGION'),env('DO_SPACES_BUCKET').'.'.env('DO_SPACES_REGION'),str_replace("digitaloceanspaces","cdn.digitaloceanspaces",env('DO_SPACES_ENDPOINT'))),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
@ -23,7 +23,7 @@ return [
|
|||
| This value is the version of your PixelFed instance.
|
||||
|
|
||||
*/
|
||||
'version' => '0.7.10',
|
||||
'version' => '0.8.0rc1',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -198,6 +198,46 @@ return [
|
|||
*/
|
||||
'account_delete_after' => env('ACCOUNT_DELETE_AFTER', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Enable Cloud Storage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Store media on object storage like S3, Digital Ocean Spaces, Rackspace
|
||||
|
|
||||
*/
|
||||
'cloud_storage' => env('PF_ENABLE_CLOUD', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Max User Limit
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Allow a maximum number of user accounts. Default: off
|
||||
|
|
||||
*/
|
||||
'max_users' => env('PF_MAX_USERS', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Optimize Images
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Resize and optimize image uploads. Default: on
|
||||
|
|
||||
*/
|
||||
'optimize_image' => env('PF_OPTIMIZE_IMAGES', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Optimize Videos
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Resize and optimize video uploads. Default: on
|
||||
|
|
||||
*/
|
||||
'optimize_video' => env('PF_OPTIMIZE_VIDEOS', true),
|
||||
|
||||
|
||||
'media_types' => env('MEDIA_TYPES', 'image/jpeg,image/png,image/gif'),
|
||||
'enforce_account_limit' => env('LIMIT_ACCOUNT_SIZE', true),
|
||||
|
|
|
@ -54,7 +54,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'HTML.Doctype' => 'XHTML 1.0 Strict',
|
||||
'HTML.Doctype' => 'XHTML 1.0 Transitional',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -67,7 +67,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'HTML.Allowed' => 'a[href|title|rel],p',
|
||||
'HTML.Allowed' => 'a[href|title|rel],p,strong,em,i,u,h1,h2,h3,h4,h5,ul,ol,li',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -17,7 +17,15 @@ RUN apt-get update \
|
|||
&& docker-php-ext-install pdo_mysql pcntl gd exif bcmath \
|
||||
&& pecl install imagick \
|
||||
&& docker-php-ext-enable imagick pcntl imagick gd exif \
|
||||
&& a2enmod rewrite \
|
||||
&& a2enmod rewrite remoteip \
|
||||
&& {\
|
||||
echo RemoteIPHeader X-Real-IP ;\
|
||||
echo RemoteIPTrustedProxy 10.0.0.0/8 ;\
|
||||
echo RemoteIPTrustedProxy 172.16.0.0/12 ;\
|
||||
echo RemoteIPTrustedProxy 192.168.0.0/16 ;\
|
||||
echo SetEnvIf X-Forwarded-Proto "https" HTTPS=on ;\
|
||||
} > /etc/apache2/conf-available/remoteip.conf \
|
||||
&& a2enconf remoteip \
|
||||
&& curl -LsS https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar -o /usr/bin/composer \
|
||||
&& echo "${COMPOSER_CHECKSUM} /usr/bin/composer" | sha256sum -c - \
|
||||
&& chmod 755 /usr/bin/composer \
|
||||
|
|
77
database/migrations/2019_01_12_054413_stories.php
Normal file
77
database/migrations/2019_01_12_054413_stories.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class Stories extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('story_items', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('story_id')->unsigned()->index();
|
||||
$table->string('media_path')->nullable();
|
||||
$table->string('media_url')->nullable();
|
||||
$table->tinyInteger('duration')->unsigned();
|
||||
$table->string('filter')->nullable();
|
||||
$table->string('link_url')->nullable()->index();
|
||||
$table->string('link_text')->nullable();
|
||||
$table->tinyInteger('order')->unsigned()->nullable();
|
||||
$table->string('type')->default('photo');
|
||||
$table->json('layers')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('story_views', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('story_id')->unsigned()->index();
|
||||
$table->bigInteger('profile_id')->unsigned()->index();
|
||||
$table->unique(['story_id', 'profile_id']);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::table('stories', function (Blueprint $table) {
|
||||
$table->string('title')->nullable()->after('profile_id');
|
||||
$table->boolean('preview_photo')->default(false)->after('title');
|
||||
$table->boolean('local_only')->default(false)->after('preview_photo');
|
||||
$table->boolean('is_live')->default(false)->after('local_only');
|
||||
$table->string('broadcast_url')->nullable()->after('is_live');
|
||||
$table->string('broadcast_key')->nullable()->after('broadcast_url');
|
||||
});
|
||||
|
||||
Schema::table('story_reactions', function (Blueprint $table) {
|
||||
$table->bigInteger('story_id')->unsigned()->index()->after('profile_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('story_items');
|
||||
Schema::dropIfExists('story_views');
|
||||
|
||||
Schema::table('stories', function (Blueprint $table) {
|
||||
$table->dropColumn('title');
|
||||
$table->dropColumn('preview_photo');
|
||||
$table->dropColumn('local_only');
|
||||
$table->dropColumn('is_live');
|
||||
$table->dropColumn('broadcast_url');
|
||||
$table->dropColumn('broadcast_key');
|
||||
});
|
||||
|
||||
Schema::table('story_reactions', function (Blueprint $table) {
|
||||
$table->dropColumn('story_id');
|
||||
});
|
||||
}
|
||||
}
|
40
database/migrations/2019_01_22_030129_create_pages_table.php
Normal file
40
database/migrations/2019_01_22_030129_create_pages_table.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreatePagesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('pages', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('root')->nullable()->index();
|
||||
$table->string('slug')->nullable()->unique()->index();
|
||||
$table->string('title')->nullable();
|
||||
$table->unsignedInteger('category_id')->nullable()->index();
|
||||
$table->longText('content')->nullable();
|
||||
$table->string('template')->default('layouts.app')->index();
|
||||
$table->boolean('active')->default(false)->index();
|
||||
$table->boolean('cached')->default(true)->index();
|
||||
$table->timestamp('active_until')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('pages');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddRemoteToAvatarsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('avatars', function (Blueprint $table) {
|
||||
$table->string('remote_url')->nullable()->index()->after('thumb_path');
|
||||
$table->timestamp('last_fetched_at')->nullable()->after('change_count');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('avatars', function (Blueprint $table) {
|
||||
$table->dropColumn('remote_url');
|
||||
$table->dropColumn('last_fetched_at');
|
||||
});
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue