1
0
Fork 0

Compare commits

...

104 Commits

Author SHA1 Message Date
chris 7d03ac49e3 allow 30 char usernames
raise maximum username length, because why not?
2024-02-19 16:15:14 +01:00
chris eab2c517eb disable beagle service
beagle is a remote API service provided by dansup and used for centralised lookups.
Using the beagle service without users explicit consent violates GDPR.
As it's not configurable at the moment this patch disables remote communication with beagle.
2024-02-19 16:15:09 +01:00
chris c49bcecdc6 point to modified sourcecode
as per AGPL license of original source, modifications must be disclosed.
2024-02-19 16:15:02 +01:00
chris 2b39d60d91 hardcode discovery settings
force enable discovery (as dynamic settings are not saved properly)
2024-02-19 16:14:56 +01:00
chris 5193a8f90c remove IP logging
Replace unneeded logging of IPs and User-Agent strings with meaningless static data.
2024-02-19 16:14:50 +01:00
daniel 1f6dc94a34
Merge pull request #4946 from pixelfed/staging
Add Curated Onboarding
2024-02-19 05:57:04 -07:00
Daniel Supernault e1715d40b8
Update changelog 2024-02-19 05:44:34 -07:00
Daniel Supernault 0ad3654da3
Update compiled assets 2024-02-19 04:35:11 -07:00
Daniel Supernault 06655c3a8b
Update LandingService, add curated onboarding parameter 2024-02-19 04:34:57 -07:00
Daniel Supernault cae26c666d
Update landing nav, fix curated onboarding state 2024-02-19 04:33:29 -07:00
Daniel Supernault 8355d5d00c
Add AdminCuratedRegisterController 2024-02-19 04:03:39 -07:00
Daniel Supernault 8dac2caf1d
Add Curated Onboarding 2024-02-19 04:00:31 -07:00
daniel 4057ee3bb3
Merge pull request #4944 from pixelfed/staging
Update ApiV1Controller, implement better limit logic to gracefully ha…
2024-02-19 01:48:28 -07:00
Daniel Supernault 9409c569bd
Update compiled assets 2024-02-19 01:47:57 -07:00
Daniel Supernault b6c97d1d26
Update npm deps 2024-02-19 01:46:45 -07:00
Daniel Supernault eb0e76f8e2
Update changelog 2024-02-19 01:39:52 -07:00
Daniel Supernault 1f74a95d0c
Update ApiV1Controller, implement better limit logic to gracefully handle requests with limits that exceed the max 2024-02-19 01:36:09 -07:00
daniel 6c0c61e45a
Merge pull request #4943 from pixelfed/staging
Staging
2024-02-18 22:19:21 -07:00
daniel 8cb7ebdd8b
Merge branch 'dev' into staging 2024-02-18 22:19:00 -07:00
Daniel Supernault ce9c0e0b24
Update changelog 2024-02-18 22:16:46 -07:00
Daniel Supernault 545f7d5e70
Update api v1/v2 instance endpoints, bump mastoapi version from 2.7.2 to 3.5.3 2024-02-18 22:14:23 -07:00
daniel f97afcf47e
Merge pull request #4938 from ThisIsMissEm/patch-2
Update .gitattributes to collapse diffs on generated files
2024-02-18 19:21:14 -07:00
daniel b339f4a5b0
Merge pull request #4942 from pixelfed/staging
Staging
2024-02-18 18:11:07 -07:00
Daniel Supernault 147113cc95
Update changelog 2024-02-18 18:02:08 -07:00
Daniel Supernault ea6b162340
Update cache config, use predis as default redis driver client 2024-02-18 18:01:14 -07:00
Daniel Supernault 4c26f59cd0
Update composer deps, add php 8.3 support 2024-02-18 18:00:43 -07:00
daniel 4eb0c36480
Merge pull request #4941 from pixelfed/staging
Update federation config, increase default timeline days falloff to 9…
2024-02-18 13:08:00 -07:00
Daniel Supernault 17027c3487
Update changelog 2024-02-18 13:07:26 -07:00
Daniel Supernault 011834f473
Update federation config, increase default timeline days falloff to 90 days from 2 days. Fixes #4905 2024-02-18 13:05:49 -07:00
Emelia Smith 9978b2b959
Update .gitattributes to collapse diffs on generated files 2024-02-16 17:56:13 +01:00
daniel 01a86009e6
Merge pull request #4936 from pixelfed/staging
Update Inbox, cast live filters to lowercase
2024-02-16 06:58:13 -07:00
Daniel Supernault d835e0adaa
Update Inbox, cast live filters to lowercase 2024-02-16 06:57:04 -07:00
daniel f45d293707
Merge pull request #4935 from pixelfed/staging
Bump version to 0.11.12
2024-02-16 03:09:23 -07:00
Daniel Supernault 66f6640072
Bump version to 0.11.12 2024-02-16 03:08:50 -07:00
daniel 25dd2e46fe
Merge pull request #4934 from pixelfed/staging
Add Software Update banner to admin home feeds
2024-02-16 03:02:36 -07:00
Daniel Supernault f07843a0f2
Update compiled assets 2024-02-16 03:01:51 -07:00
Daniel Supernault 56b736c325
Update changelog 2024-02-16 03:00:51 -07:00
Daniel Supernault b0fb198829
Add Software Update banner to admin home feeds 2024-02-16 02:58:27 -07:00
daniel e1d579b10b
Merge pull request #4933 from pixelfed/staging
Autospam Live Filters - block remote activities based on comma separated keywords
2024-02-16 02:20:23 -07:00
Daniel Supernault 83eadbb811
Update changelog 2024-02-16 02:20:11 -07:00
Daniel Supernault 40b45b2a11
Update Autospam, add live filters to block remote activities based on comma separated keywords 2024-02-16 02:15:39 -07:00
daniel ccbba91e70
Merge pull request #4932 from pixelfed/staging
Update routes
2024-02-15 23:36:51 -07:00
Daniel Supernault bc4d223714
Update routes 2024-02-15 22:20:40 -07:00
daniel 0032415459
Merge pull request #4931 from pixelfed/staging
Staging
2024-02-15 21:42:15 -07:00
Daniel Supernault 70fc44dfe5
Update changelog 2024-02-15 21:41:40 -07:00
Daniel Supernault 0f3ca19461
Update status view, fix unlisted/private scope bug 2024-02-15 21:41:18 -07:00
daniel 0dc54e9ac0
Merge pull request #4930 from pixelfed/staging
Staging
2024-02-15 21:23:29 -07:00
Daniel Supernault df5e61266c
Update changelog 2024-02-15 21:23:00 -07:00
Daniel Supernault 1232cfc86a
Update ActivityPubFetchService, enforce stricter Content-Type validation 2024-02-15 21:22:41 -07:00
daniel af935c729b
Merge pull request #4929 from pixelfed/staging
Staging
2024-02-15 20:59:35 -07:00
Daniel Supernault 4c6ec20e36
Update changelog 2024-02-15 20:59:08 -07:00
Daniel Supernault fb0bb9a34f
Update Federation, use proper Content-Type headers for following/follower collections 2024-02-15 20:58:43 -07:00
daniel b10c60584b
Merge pull request #4924 from pixelfed/staging
Update public/network timelines, fix non-redis response and fix reblo…
2024-02-11 20:26:31 -07:00
Daniel Supernault 221fe43638
Update compiled assets 2024-02-11 20:25:18 -07:00
Daniel Supernault 97c131fdf2
Update AccountImport component 2024-02-11 20:24:27 -07:00
Daniel Supernault 78da12004f
Update changelog 2024-02-11 20:23:19 -07:00
Daniel Supernault 8b4ac5cc0b
Update public/network timelines, fix non-redis response and fix reblogs in home feed 2024-02-11 20:21:46 -07:00
daniel bbd3688333
Merge pull request #4923 from pixelfed/staging
Update ApiV1Controller, fix network timeline
2024-02-11 14:30:46 -07:00
Daniel Supernault 7b8977e9cc
Update changelog 2024-02-11 14:27:38 -07:00
Daniel Supernault 0faf59e3b7
Update ApiV1Controller, fix network timeline 2024-02-11 14:26:45 -07:00
daniel 8fa6ae421b
Merge pull request #4915 from pixelfed/staging
Bump version to v0.11.11
2024-02-09 20:58:43 -07:00
Daniel Supernault e5bbe9340a
Bump version to v0.11.11 2024-02-09 20:57:45 -07:00
Daniel Supernault 7a7b4bc717
Update AuthServiceProvider 2024-02-09 20:54:17 -07:00
daniel 8ab9951909
Merge pull request #4914 from pixelfed/staging
Fix api endpoints
2024-02-09 20:52:09 -07:00
Daniel Supernault 62b9eef805
Fix api endpoints 2024-02-09 20:51:37 -07:00
daniel 67167a5b90
Merge pull request #4913 from pixelfed/staging
Fix api endpoints
2024-02-09 20:45:44 -07:00
Daniel Supernault fd7f5dbba1
Fix api endpoints 2024-02-09 20:45:10 -07:00
daniel 0649bb4754
Merge pull request #4912 from pixelfed/staging
Fix api endpoints
2024-02-09 20:41:42 -07:00
Daniel Supernault e354750808
Fix api endpoints 2024-02-09 20:41:12 -07:00
daniel 0dbbc6a6b4
Merge pull request #4911 from pixelfed/staging
Staging
2024-02-09 20:06:20 -07:00
Daniel Supernault 607b239c1a
Bump version to v0.11.10 2024-02-09 20:05:22 -07:00
Daniel Supernault 2e6100f275
Update BearerTokenResponse, fix scope bug 2024-02-09 20:04:15 -07:00
daniel 7e47d6dccb
Merge pull request from GHSA-gccq-h3xj-jgvf
[staging] Implement proper OAuth authorization on API endpoints
2024-02-09 19:44:27 -07:00
Emelia Smith 0f8e45fe75
Implement proper OAuth authorization on API endpoints 2024-02-09 02:28:08 +01:00
Emelia Smith 9330cd02f7
Implement proper OAuth authorization on Admin API endpoints 2024-02-09 02:28:08 +01:00
Emelia Smith 7b0a6060b2
Return access tokens' scopes, not hardcoded list 2024-02-09 02:28:08 +01:00
daniel 73b4dab9a8
Merge pull request #4904 from pixelfed/staging
Update ApiV2Controller, add vapid key to instance object. Thanks this…
2024-02-07 06:00:20 -07:00
Daniel Supernault 2becd273c4
Update changelog 2024-02-07 06:00:09 -07:00
Daniel Supernault 4d02d6f12e
Update ApiV2Controller, add vapid key to instance object. Thanks thisismissem! 2024-02-07 05:59:12 -07:00
daniel a1b0d3d3c0
Merge pull request #4903 from pixelfed/staging
Add migration
2024-02-07 04:50:33 -07:00
Daniel Supernault 97b7cb2719
Add migration 2024-02-07 04:49:44 -07:00
daniel 6ea20716bc
Merge pull request #4902 from pixelfed/staging
Staging
2024-02-07 04:45:47 -07:00
Daniel Supernault 1f3f0cae65
Update changelog 2024-02-07 04:43:32 -07:00
Daniel Supernault 6921d3568e
Add InstanceMananger command 2024-02-07 04:42:27 -07:00
Daniel Supernault 5b284cacea
Update ApiV1Controller, enforce blocked instance domain logic 2024-02-07 04:41:12 -07:00
Daniel Supernault 01b33fb37e
Update PublicApiController, consume InstanceService blocked domains for account and statuses endpoints 2024-02-07 03:43:20 -07:00
Daniel Supernault 1e3acadefb
Update horizon.php config 2024-02-07 02:52:37 -07:00
Daniel Supernault ac01f51ab6
Update FetchNodeinfoPipeline, use more efficient dispatch 2024-02-07 02:51:50 -07:00
Daniel Supernault 289cad470b
Update Instance model, add entity casts 2024-02-07 02:49:29 -07:00
Daniel Supernault 240e6bbe4f
Update NodeinfoService, disable redirects 2024-02-07 02:47:34 -07:00
daniel 111ba70473
Merge pull request #4897 from pixelfed/staging
Staging
2024-02-04 07:20:53 -07:00
Daniel Supernault 80e0ada946
Update changelog 2024-02-04 07:18:54 -07:00
Daniel Supernault fa97a1f38e
Update notification pipelines, fix non-local saving 2024-02-04 07:18:05 -07:00
Daniel Supernault 4d4013896c
Update NotificationEpochUpdatePipeline, use more efficient query 2024-02-04 07:11:05 -07:00
daniel 3a557d7ffc
Merge pull request #4896 from pixelfed/staging
Update AP helpers, fix sensitive bug
2024-02-04 03:19:08 -07:00
Daniel Supernault 152b6eab9a
Update changelog 2024-02-04 03:18:58 -07:00
Daniel Supernault 00ed330cf3
Update AP helpers, fix sensitive bug 2024-02-04 03:16:57 -07:00
daniel 2483832754
Merge pull request #4895 from pixelfed/staging
Update AP helpers, refactor post count decrement logic
2024-02-04 03:03:16 -07:00
Daniel Supernault 09ca96cc2b
Update changelog 2024-02-04 02:54:37 -07:00
Daniel Supernault 8b843d620c
Update ProfilePipeline jobs 2024-02-04 02:53:40 -07:00
Daniel Supernault b81ae5773f
Update AP helpers, refactor post count decrement logic 2024-02-04 02:50:48 -07:00
daniel 5d89fe8130
Merge pull request #4894 from pixelfed/staging
Update AP helpers, more efficently update post counts
2024-02-04 02:41:08 -07:00
Daniel Supernault ddf7f09ad4
Update changelog 2024-02-04 02:40:59 -07:00
Daniel Supernault 7caed381fb
Update AP helpers, more efficently update post counts 2024-02-04 02:40:04 -07:00
188 changed files with 7976 additions and 3022 deletions

7
.gitattributes vendored
View File

@ -3,3 +3,10 @@
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore
# Collapse diffs for generated files:
public/**/*.js text -diff
public/**/*.json text -diff
public/**/*.css text -diff
public/img/* binary -diff
public/fonts/* binary -diff

View File

@ -1,6 +1,41 @@
# Release Notes
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.9...dev)
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.12...dev)
### Features
- Curated Onboarding ([8dac2caf](https://github.com/pixelfed/pixelfed/commit/8dac2caf))
### Updates
- Update Inbox, cast live filters to lowercase ([d835e0ad](https://github.com/pixelfed/pixelfed/commit/d835e0ad))
- Update federation config, increase default timeline days falloff to 90 days from 2 days. Fixes #4905 ([011834f4](https://github.com/pixelfed/pixelfed/commit/011834f4))
- Update cache config, use predis as default redis driver client ([ea6b1623](https://github.com/pixelfed/pixelfed/commit/ea6b1623))
- Update .gitattributes to collapse diffs on generated files ([ThisIsMissEm](https://github.com/pixelfed/pixelfed/commit/9978b2b9))
- Update api v1/v2 instance endpoints, bump mastoapi version from 2.7.2 to 3.5.3 ([545f7d5e](https://github.com/pixelfed/pixelfed/commit/545f7d5e))
- Update ApiV1Controller, implement better limit logic to gracefully handle requests with limits that exceed the max ([1f74a95d](https://github.com/pixelfed/pixelfed/commit/1f74a95d))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.12 (2024-02-16)](https://github.com/pixelfed/pixelfed/compare/v0.11.11...v0.11.12)
### Features
- Autospam Live Filters - block remote activities based on comma separated keywords ([40b45b2a](https://github.com/pixelfed/pixelfed/commit/40b45b2a))
- Added Software Update banner to admin home feeds ([b0fb1988](https://github.com/pixelfed/pixelfed/commit/b0fb1988))
### Updates
- Update ApiV1Controller, fix network timeline ([0faf59e3](https://github.com/pixelfed/pixelfed/commit/0faf59e3))
- Update public/network timelines, fix non-redis response and fix reblogs in home feed ([8b4ac5cc](https://github.com/pixelfed/pixelfed/commit/8b4ac5cc))
- Update Federation, use proper Content-Type headers for following/follower collections ([fb0bb9a3](https://github.com/pixelfed/pixelfed/commit/fb0bb9a3))
- Update ActivityPubFetchService, enforce stricter Content-Type validation ([1232cfc8](https://github.com/pixelfed/pixelfed/commit/1232cfc8))
- Update status view, fix unlisted/private scope bug ([0f3ca194](https://github.com/pixelfed/pixelfed/commit/0f3ca194))
## [v0.11.11 (2024-02-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.10...v0.11.11)
### Fixes
- Fix api endpoints ([fd7f5dbb](https://github.com/pixelfed/pixelfed/commit/fd7f5dbb))
## [v0.11.10 (2024-02-09)](https://github.com/pixelfed/pixelfed/compare/v0.11.9...v0.11.10)
### Added
- Resilient Media Storage ([#4665](https://github.com/pixelfed/pixelfed/pull/4665)) ([fb1deb6](https://github.com/pixelfed/pixelfed/commit/fb1deb6))
@ -20,7 +55,6 @@
### Federation
- Update Privacy Settings, add support for Mastodon `indexable` search flag ([fc24630e](https://github.com/pixelfed/pixelfed/commit/fc24630e))
- Update AP Helpers, consume actor `indexable` attribute ([fbdcdd9d](https://github.com/pixelfed/pixelfed/commit/fbdcdd9d))
- ([](https://github.com/pixelfed/pixelfed/commit/))
### Updates
- Update FollowerService, add forget method to RelationshipService call to reduce load when mass purging ([347e4f59](https://github.com/pixelfed/pixelfed/commit/347e4f59))
@ -93,7 +127,18 @@
- Update ApiV1Controller, properly cast boolean sensitive parameter. Fixes #4888 ([0aff126a](https://github.com/pixelfed/pixelfed/commit/0aff126a))
- Update AccountImport.vue, fix new IG export format ([59aa6a4b](https://github.com/pixelfed/pixelfed/commit/59aa6a4b))
- Update TransformImports command, fix import service condition ([32c59f04](https://github.com/pixelfed/pixelfed/commit/32c59f04))
- ([](https://github.com/pixelfed/pixelfed/commit/))
- Update AP helpers, more efficently update post count ([7caed381](https://github.com/pixelfed/pixelfed/commit/7caed381))
- Update AP helpers, refactor post count decrement logic ([b81ae577](https://github.com/pixelfed/pixelfed/commit/b81ae577))
- Update AP helpers, fix sensitive bug ([00ed330c](https://github.com/pixelfed/pixelfed/commit/00ed330c))
- Update NotificationEpochUpdatePipeline, use more efficient query ([4d401389](https://github.com/pixelfed/pixelfed/commit/4d401389))
- Update notification pipelines, fix non-local saving ([fa97a1f3](https://github.com/pixelfed/pixelfed/commit/fa97a1f3))
- Update NodeinfoService, disable redirects ([240e6bbe](https://github.com/pixelfed/pixelfed/commit/240e6bbe))
- Update Instance model, add entity casts ([289cad47](https://github.com/pixelfed/pixelfed/commit/289cad47))
- Update FetchNodeinfoPipeline, use more efficient dispatch ([ac01f51a](https://github.com/pixelfed/pixelfed/commit/ac01f51a))
- Update horizon.php config ([1e3acade](https://github.com/pixelfed/pixelfed/commit/1e3acade))
- Update PublicApiController, consume InstanceService blocked domains for account and statuses endpoints ([01b33fb3](https://github.com/pixelfed/pixelfed/commit/01b33fb3))
- Update ApiV1Controller, enforce blocked instance domain logic ([5b284cac](https://github.com/pixelfed/pixelfed/commit/5b284cac))
- Update ApiV2Controller, add vapid key to instance object. Thanks thisismissem! ([4d02d6f1](https://github.com/pixelfed/pixelfed/commit/4d02d6f1))
## [v0.11.9 (2023-08-21)](https://github.com/pixelfed/pixelfed/compare/v0.11.8...v0.11.9)

View File

@ -18,8 +18,7 @@ class BearerTokenResponse extends \League\OAuth2\Server\ResponseTypes\BearerToke
protected function getExtraParams(AccessTokenEntityInterface $accessToken)
{
return [
'created_at' => time(),
'scope' => 'read write follow push'
'created_at' => time(),
];
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\AccountService;
use App\Services\Account\AccountStatService;
use App\Status;
use App\Profile;
class AccountPostCountStatUpdate extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:account-post-count-stat-update';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update post counts from recent activities';
/**
* Execute the console command.
*/
public function handle()
{
$ids = AccountStatService::getAllPostCountIncr();
if(!$ids || !count($ids)) {
return;
}
foreach($ids as $id) {
$acct = AccountService::get($id, true);
if(!$acct) {
AccountStatService::removeFromPostCount($id);
continue;
}
$statusCount = Status::whereProfileId($id)->count();
if($statusCount != $acct['statuses_count']) {
$profile = Profile::find($id);
if(!$profile) {
AccountStatService::removeFromPostCount($id);
continue;
}
$profile->status_count = $statusCount;
$profile->save();
AccountService::del($id);
}
AccountStatService::removeFromPostCount($id);
}
return;
}
}

View File

@ -0,0 +1,298 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Instance;
use App\Profile;
use App\Services\InstanceService;
use App\Jobs\InstancePipeline\FetchNodeinfoPipeline;
use function Laravel\Prompts\select;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\progress;
use function Laravel\Prompts\search;
use function Laravel\Prompts\table;
class InstanceManager extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:instance-manager';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Manage Instances';
/**
* Execute the console command.
*/
public function handle()
{
$action = select(
'What action do you want to perform?',
[
'Recalculate Stats',
'Ban Instance',
'Unlist Instance',
'Unlisted Instances',
'Banned Instances',
'Unban Instance',
'Relist Instance',
],
);
switch($action) {
case 'Recalculate Stats':
return $this->recalculateStats();
break;
case 'Unlisted Instances':
return $this->viewUnlistedInstances();
break;
case 'Banned Instances':
return $this->viewBannedInstances();
break;
case 'Unlist Instance':
return $this->unlistInstance();
break;
case 'Ban Instance':
return $this->banInstance();
break;
case 'Unban Instance':
return $this->unbanInstance();
break;
case 'Relist Instance':
return $this->relistInstance();
break;
}
}
protected function recalculateStats()
{
$instanceCount = Instance::count();
$confirmed = confirm('Do you want to recalculate stats for all ' . $instanceCount . ' instances?');
if(!$confirmed) {
$this->error('Aborting...');
exit;
}
$users = progress(
label: 'Updating instance stats...',
steps: Instance::all(),
callback: fn ($instance) => $this->updateInstanceStats($instance),
);
}
protected function updateInstanceStats($instance)
{
FetchNodeinfoPipeline::dispatch($instance)->onQueue('intbg');
}
protected function unlistInstance()
{
$id = search(
'Search by domain',
fn (string $value) => strlen($value) > 0
? Instance::whereUnlisted(false)->where('domain', 'like', "%{$value}%")->pluck('domain', 'id')->all()
: []
);
$instance = Instance::find($id);
if(!$instance) {
$this->error('Oops, an error occured');
exit;
}
$tbl = [
[
$instance->domain,
number_format($instance->status_count),
number_format($instance->user_count),
]
];
table(
['Domain', 'Status Count', 'User Count'],
$tbl
);
$confirmed = confirm('Are you sure you want to unlist this instance?');
if(!$confirmed) {
$this->error('Aborting instance unlisting');
exit;
}
$instance->unlisted = true;
$instance->save();
InstanceService::refresh();
$this->info('Successfully unlisted ' . $instance->domain . '!');
exit;
}
protected function relistInstance()
{
$id = search(
'Search by domain',
fn (string $value) => strlen($value) > 0
? Instance::whereUnlisted(true)->where('domain', 'like', "%{$value}%")->pluck('domain', 'id')->all()
: []
);
$instance = Instance::find($id);
if(!$instance) {
$this->error('Oops, an error occured');
exit;
}
$tbl = [
[
$instance->domain,
number_format($instance->status_count),
number_format($instance->user_count),
]
];
table(
['Domain', 'Status Count', 'User Count'],
$tbl
);
$confirmed = confirm('Are you sure you want to re-list this instance?');
if(!$confirmed) {
$this->error('Aborting instance re-listing');
exit;
}
$instance->unlisted = false;
$instance->save();
InstanceService::refresh();
$this->info('Successfully re-listed ' . $instance->domain . '!');
exit;
}
protected function banInstance()
{
$id = search(
'Search by domain',
fn (string $value) => strlen($value) > 0
? Instance::whereBanned(false)->where('domain', 'like', "%{$value}%")->pluck('domain', 'id')->all()
: []
);
$instance = Instance::find($id);
if(!$instance) {
$this->error('Oops, an error occured');
exit;
}
$tbl = [
[
$instance->domain,
number_format($instance->status_count),
number_format($instance->user_count),
]
];
table(
['Domain', 'Status Count', 'User Count'],
$tbl
);
$confirmed = confirm('Are you sure you want to ban this instance?');
if(!$confirmed) {
$this->error('Aborting instance ban');
exit;
}
$instance->banned = true;
$instance->save();
InstanceService::refresh();
$this->info('Successfully banned ' . $instance->domain . '!');
exit;
}
protected function unbanInstance()
{
$id = search(
'Search by domain',
fn (string $value) => strlen($value) > 0
? Instance::whereBanned(true)->where('domain', 'like', "%{$value}%")->pluck('domain', 'id')->all()
: []
);
$instance = Instance::find($id);
if(!$instance) {
$this->error('Oops, an error occured');
exit;
}
$tbl = [
[
$instance->domain,
number_format($instance->status_count),
number_format($instance->user_count),
]
];
table(
['Domain', 'Status Count', 'User Count'],
$tbl
);
$confirmed = confirm('Are you sure you want to unban this instance?');
if(!$confirmed) {
$this->error('Aborting instance unban');
exit;
}
$instance->banned = false;
$instance->save();
InstanceService::refresh();
$this->info('Successfully un-banned ' . $instance->domain . '!');
exit;
}
protected function viewBannedInstances()
{
$data = Instance::whereBanned(true)
->get(['domain', 'user_count', 'status_count'])
->map(function($d) {
return [
'domain' => $d->domain,
'user_count' => number_format($d->user_count),
'status_count' => number_format($d->status_count),
];
})
->toArray();
table(
['Domain', 'User Count', 'Status Count'],
$data
);
}
protected function viewUnlistedInstances()
{
$data = Instance::whereUnlisted(true)
->get(['domain', 'user_count', 'status_count', 'banned'])
->map(function($d) {
return [
'domain' => $d->domain,
'user_count' => number_format($d->user_count),
'status_count' => number_format($d->status_count),
'banned' => $d->banned ? '✅' : null
];
})
->toArray();
table(
['Domain', 'User Count', 'Status Count', 'Banned'],
$data
);
}
}

View File

@ -38,7 +38,7 @@ class Kernel extends ConsoleKernel
}
if(config('import.instagram.enabled')) {
$schedule->command('app:transform-imports')->everyFourMinutes();
$schedule->command('app:transform-imports')->everyTenMinutes();
$schedule->command('app:import-upload-garbage-collection')->hourlyAt(51);
$schedule->command('app:import-remove-deleted-accounts')->hourlyAt(37);
$schedule->command('app:import-upload-clean-storage')->twiceDailyAt(1, 13, 32);
@ -49,6 +49,7 @@ class Kernel extends ConsoleKernel
}
$schedule->command('app:notification-epoch-update')->weeklyOn(1, '2:21');
$schedule->command('app:hashtag-cached-count-update')->hourlyAt(25);
$schedule->command('app:account-post-count-stat-update')->everySixHours(25);
}
/**

View File

@ -17,269 +17,288 @@ use Illuminate\Support\Str;
trait AdminSettingsController
{
public function settings(Request $request)
{
$cloud_storage = ConfigCacheService::get('pixelfed.cloud_storage');
$cloud_disk = config('filesystems.cloud');
$cloud_ready = !empty(config('filesystems.disks.' . $cloud_disk . '.key')) && !empty(config('filesystems.disks.' . $cloud_disk . '.secret'));
$types = explode(',', ConfigCacheService::get('pixelfed.media_types'));
$rules = ConfigCacheService::get('app.rules') ? json_decode(ConfigCacheService::get('app.rules'), true) : null;
$jpeg = in_array('image/jpg', $types) || in_array('image/jpeg', $types);
$png = in_array('image/png', $types);
$gif = in_array('image/gif', $types);
$mp4 = in_array('video/mp4', $types);
$webp = in_array('image/webp', $types);
public function settings(Request $request)
{
$cloud_storage = ConfigCacheService::get('pixelfed.cloud_storage');
$cloud_disk = config('filesystems.cloud');
$cloud_ready = !empty(config('filesystems.disks.' . $cloud_disk . '.key')) && !empty(config('filesystems.disks.' . $cloud_disk . '.secret'));
$types = explode(',', ConfigCacheService::get('pixelfed.media_types'));
$rules = ConfigCacheService::get('app.rules') ? json_decode(ConfigCacheService::get('app.rules'), true) : null;
$jpeg = in_array('image/jpg', $types) || in_array('image/jpeg', $types);
$png = in_array('image/png', $types);
$gif = in_array('image/gif', $types);
$mp4 = in_array('video/mp4', $types);
$webp = in_array('image/webp', $types);
$availableAdmins = User::whereIsAdmin(true)->get();
$currentAdmin = config_cache('instance.admin.pid') ? AccountService::get(config_cache('instance.admin.pid'), true) : null;
$availableAdmins = User::whereIsAdmin(true)->get();
$currentAdmin = config_cache('instance.admin.pid') ? AccountService::get(config_cache('instance.admin.pid'), true) : null;
$openReg = (bool) config_cache('pixelfed.open_registration');
$curOnboarding = (bool) config_cache('instance.curated_registration.enabled');
$regState = $openReg ? 'open' : ($curOnboarding ? 'filtered' : 'closed');
// $system = [
// 'permissions' => is_writable(base_path('storage')) && is_writable(base_path('bootstrap')),
// 'max_upload_size' => ini_get('post_max_size'),
// 'image_driver' => config('image.driver'),
// 'image_driver_loaded' => extension_loaded(config('image.driver'))
// ];
return view('admin.settings.home', compact(
'jpeg',
'png',
'gif',
'mp4',
'webp',
'rules',
'cloud_storage',
'cloud_disk',
'cloud_ready',
'availableAdmins',
'currentAdmin',
'regState'
));
}
return view('admin.settings.home', compact(
'jpeg',
'png',
'gif',
'mp4',
'webp',
'rules',
'cloud_storage',
'cloud_disk',
'cloud_ready',
'availableAdmins',
'currentAdmin'
// 'system'
));
}
public function settingsHomeStore(Request $request)
{
$this->validate($request, [
'name' => 'nullable|string',
'short_description' => 'nullable',
'long_description' => 'nullable',
'max_photo_size' => 'nullable|integer|min:1',
'max_album_length' => 'nullable|integer|min:1|max:100',
'image_quality' => 'nullable|integer|min:1|max:100',
'type_jpeg' => 'nullable',
'type_png' => 'nullable',
'type_gif' => 'nullable',
'type_mp4' => 'nullable',
'type_webp' => 'nullable',
'admin_account_id' => 'nullable',
'regs' => 'required|in:open,filtered,closed'
]);
public function settingsHomeStore(Request $request)
{
$this->validate($request, [
'name' => 'nullable|string',
'short_description' => 'nullable',
'long_description' => 'nullable',
'max_photo_size' => 'nullable|integer|min:1',
'max_album_length' => 'nullable|integer|min:1|max:100',
'image_quality' => 'nullable|integer|min:1|max:100',
'type_jpeg' => 'nullable',
'type_png' => 'nullable',
'type_gif' => 'nullable',
'type_mp4' => 'nullable',
'type_webp' => 'nullable',
'admin_account_id' => 'nullable',
]);
$orb = false;
$cob = false;
switch($request->input('regs')) {
case 'open':
$orb = true;
$cob = false;
break;
if($request->filled('admin_account_id')) {
ConfigCacheService::put('instance.admin.pid', $request->admin_account_id);
Cache::forget('api:v1:instance-data:contact');
Cache::forget('api:v1:instance-data-response-v1');
}
if($request->filled('rule_delete')) {
$index = (int) $request->input('rule_delete');
$rules = ConfigCacheService::get('app.rules');
$json = json_decode($rules, true);
if(!$rules || empty($json)) {
return;
}
unset($json[$index]);
$json = json_encode(array_values($json));
ConfigCacheService::put('app.rules', $json);
Cache::forget('api:v1:instance-data:rules');
Cache::forget('api:v1:instance-data-response-v1');
return 200;
}
case 'filtered':
$orb = false;
$cob = true;
break;
$media_types = explode(',', config_cache('pixelfed.media_types'));
$media_types_original = $media_types;
case 'closed':
$orb = false;
$cob = false;
break;
}
$mimes = [
'type_jpeg' => 'image/jpeg',
'type_png' => 'image/png',
'type_gif' => 'image/gif',
'type_mp4' => 'video/mp4',
'type_webp' => 'image/webp',
];
ConfigCacheService::put('pixelfed.open_registration', (bool) $orb);
ConfigCacheService::put('instance.curated_registration.enabled', (bool) $cob);
foreach ($mimes as $key => $value) {
if($request->input($key) == 'on') {
if(!in_array($value, $media_types)) {
array_push($media_types, $value);
}
} else {
$media_types = array_diff($media_types, [$value]);
}
}
if($request->filled('admin_account_id')) {
ConfigCacheService::put('instance.admin.pid', $request->admin_account_id);
Cache::forget('api:v1:instance-data:contact');
Cache::forget('api:v1:instance-data-response-v1');
}
if($request->filled('rule_delete')) {
$index = (int) $request->input('rule_delete');
$rules = ConfigCacheService::get('app.rules');
$json = json_decode($rules, true);
if(!$rules || empty($json)) {
return;
}
unset($json[$index]);
$json = json_encode(array_values($json));
ConfigCacheService::put('app.rules', $json);
Cache::forget('api:v1:instance-data:rules');
Cache::forget('api:v1:instance-data-response-v1');
return 200;
}
if($media_types !== $media_types_original) {
ConfigCacheService::put('pixelfed.media_types', implode(',', array_unique($media_types)));
}
$media_types = explode(',', config_cache('pixelfed.media_types'));
$media_types_original = $media_types;
$keys = [
'name' => 'app.name',
'short_description' => 'app.short_description',
'long_description' => 'app.description',
'max_photo_size' => 'pixelfed.max_photo_size',
'max_album_length' => 'pixelfed.max_album_length',
'image_quality' => 'pixelfed.image_quality',
'account_limit' => 'pixelfed.max_account_size',
'custom_css' => 'uikit.custom.css',
'custom_js' => 'uikit.custom.js',
'about_title' => 'about.title'
];
$mimes = [
'type_jpeg' => 'image/jpeg',
'type_png' => 'image/png',
'type_gif' => 'image/gif',
'type_mp4' => 'video/mp4',
'type_webp' => 'image/webp',
];
foreach ($keys as $key => $value) {
$cc = ConfigCache::whereK($value)->first();
$val = $request->input($key);
if($cc && $cc->v != $val) {
ConfigCacheService::put($value, $val);
} else if(!empty($val)) {
ConfigCacheService::put($value, $val);
}
}
foreach ($mimes as $key => $value) {
if($request->input($key) == 'on') {
if(!in_array($value, $media_types)) {
array_push($media_types, $value);
}
} else {
$media_types = array_diff($media_types, [$value]);
}
}
$bools = [
'activitypub' => 'federation.activitypub.enabled',
'open_registration' => 'pixelfed.open_registration',
'mobile_apis' => 'pixelfed.oauth_enabled',
'stories' => 'instance.stories.enabled',
'ig_import' => 'pixelfed.import.instagram.enabled',
'spam_detection' => 'pixelfed.bouncer.enabled',
'require_email_verification' => 'pixelfed.enforce_email_verification',
'enforce_account_limit' => 'pixelfed.enforce_account_limit',
'show_custom_css' => 'uikit.show_custom.css',
'show_custom_js' => 'uikit.show_custom.js',
'cloud_storage' => 'pixelfed.cloud_storage',
'account_autofollow' => 'account.autofollow',
'show_directory' => 'instance.landing.show_directory',
'show_explore_feed' => 'instance.landing.show_explore',
];
if($media_types !== $media_types_original) {
ConfigCacheService::put('pixelfed.media_types', implode(',', array_unique($media_types)));
}
foreach ($bools as $key => $value) {
$active = $request->input($key) == 'on';
$keys = [
'name' => 'app.name',
'short_description' => 'app.short_description',
'long_description' => 'app.description',
'max_photo_size' => 'pixelfed.max_photo_size',
'max_album_length' => 'pixelfed.max_album_length',
'image_quality' => 'pixelfed.image_quality',
'account_limit' => 'pixelfed.max_account_size',
'custom_css' => 'uikit.custom.css',
'custom_js' => 'uikit.custom.js',
'about_title' => 'about.title'
];
if($key == 'activitypub' && $active && !InstanceActor::exists()) {
Artisan::call('instance:actor');
}
foreach ($keys as $key => $value) {
$cc = ConfigCache::whereK($value)->first();
$val = $request->input($key);
if($cc && $cc->v != $val) {
ConfigCacheService::put($value, $val);
} else if(!empty($val)) {
ConfigCacheService::put($value, $val);
}
}
if( $key == 'mobile_apis' &&
$active &&
!file_exists(storage_path('oauth-public.key')) &&
!file_exists(storage_path('oauth-private.key'))
) {
Artisan::call('passport:keys');
Artisan::call('route:cache');
}
$bools = [
'activitypub' => 'federation.activitypub.enabled',
// 'open_registration' => 'pixelfed.open_registration',
'mobile_apis' => 'pixelfed.oauth_enabled',
'stories' => 'instance.stories.enabled',
'ig_import' => 'pixelfed.import.instagram.enabled',
'spam_detection' => 'pixelfed.bouncer.enabled',
'require_email_verification' => 'pixelfed.enforce_email_verification',
'enforce_account_limit' => 'pixelfed.enforce_account_limit',
'show_custom_css' => 'uikit.show_custom.css',
'show_custom_js' => 'uikit.show_custom.js',
'cloud_storage' => 'pixelfed.cloud_storage',
'account_autofollow' => 'account.autofollow',
'show_directory' => 'instance.landing.show_directory',
'show_explore_feed' => 'instance.landing.show_explore',
];
if(config_cache($value) !== $active) {
ConfigCacheService::put($value, (bool) $active);
}
}
foreach ($bools as $key => $value) {
$active = $request->input($key) == 'on';
if($request->filled('new_rule')) {
$rules = ConfigCacheService::get('app.rules');
$val = $request->input('new_rule');
if(!$rules) {
ConfigCacheService::put('app.rules', json_encode([$val]));
} else {
$json = json_decode($rules, true);
$json[] = $val;
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
}
Cache::forget('api:v1:instance-data:rules');
Cache::forget('api:v1:instance-data-response-v1');
}
if($key == 'activitypub' && $active && !InstanceActor::exists()) {
Artisan::call('instance:actor');
}
if($request->filled('account_autofollow_usernames')) {
$usernames = explode(',', $request->input('account_autofollow_usernames'));
$names = [];
if( $key == 'mobile_apis' &&
$active &&
!file_exists(storage_path('oauth-public.key')) &&
!file_exists(storage_path('oauth-private.key'))
) {
Artisan::call('passport:keys');
Artisan::call('route:cache');
}
foreach($usernames as $n) {
$p = Profile::whereUsername($n)->first();
if(!$p) {
continue;
}
array_push($names, $p->username);
}
if(config_cache($value) !== $active) {
ConfigCacheService::put($value, (bool) $active);
}
}
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
}
if($request->filled('new_rule')) {
$rules = ConfigCacheService::get('app.rules');
$val = $request->input('new_rule');
if(!$rules) {
ConfigCacheService::put('app.rules', json_encode([$val]));
} else {
$json = json_decode($rules, true);
$json[] = $val;
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
}
Cache::forget('api:v1:instance-data:rules');
Cache::forget('api:v1:instance-data-response-v1');
}
Cache::forget(Config::CACHE_KEY);
if($request->filled('account_autofollow_usernames')) {
$usernames = explode(',', $request->input('account_autofollow_usernames'));
$names = [];
return redirect('/i/admin/settings')->with('status', 'Successfully updated settings!');
}
foreach($usernames as $n) {
$p = Profile::whereUsername($n)->first();
if(!$p) {
continue;
}
array_push($names, $p->username);
}
public function settingsBackups(Request $request)
{
$path = storage_path('app/'.config('app.name'));
$files = is_dir($path) ? new \DirectoryIterator($path) : [];
return view('admin.settings.backups', compact('files'));
}
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
}
public function settingsMaintenance(Request $request)
{
return view('admin.settings.maintenance');
}
Cache::forget(Config::CACHE_KEY);
public function settingsStorage(Request $request)
{
$storage = [];
return view('admin.settings.storage', compact('storage'));
}
return redirect('/i/admin/settings')->with('status', 'Successfully updated settings!');
}
public function settingsFeatures(Request $request)
{
return view('admin.settings.features');
}
public function settingsBackups(Request $request)
{
$path = storage_path('app/'.config('app.name'));
$files = is_dir($path) ? new \DirectoryIterator($path) : [];
return view('admin.settings.backups', compact('files'));
}
public function settingsPages(Request $request)
{
$pages = Page::orderByDesc('updated_at')->paginate(10);
return view('admin.pages.home', compact('pages'));
}
public function settingsMaintenance(Request $request)
{
return view('admin.settings.maintenance');
}
public function settingsPageEdit(Request $request)
{
return view('admin.pages.edit');
}
public function settingsStorage(Request $request)
{
$storage = [];
return view('admin.settings.storage', compact('storage'));
}
public function settingsSystem(Request $request)
{
$sys = [
'pixelfed' => config('pixelfed.version'),
'php' => phpversion(),
'laravel' => app()->version(),
];
switch (config('database.default')) {
case 'pgsql':
$exp = DB::raw('select version();');
$expQuery = $exp->getValue(DB::connection()->getQueryGrammar());
$sys['database'] = [
'name' => 'Postgres',
'version' => explode(' ', DB::select($expQuery)[0]->version)[1]
];
break;
public function settingsFeatures(Request $request)
{
return view('admin.settings.features');
}
case 'mysql':
$exp = DB::raw('select version()');
$expQuery = $exp->getValue(DB::connection()->getQueryGrammar());
$sys['database'] = [
'name' => 'MySQL',
'version' => DB::select($expQuery)[0]->{'version()'}
];
break;
public function settingsPages(Request $request)
{
$pages = Page::orderByDesc('updated_at')->paginate(10);
return view('admin.pages.home', compact('pages'));
}
default:
$sys['database'] = [
'name' => 'Unknown',
'version' => '?'
];
break;
}
return view('admin.settings.system', compact('sys'));
}
public function settingsPageEdit(Request $request)
{
return view('admin.pages.edit');
}
public function settingsSystem(Request $request)
{
$sys = [
'pixelfed' => config('pixelfed.version'),
'php' => phpversion(),
'laravel' => app()->version(),
];
switch (config('database.default')) {
case 'pgsql':
$exp = DB::raw('select version();');
$expQuery = $exp->getValue(DB::connection()->getQueryGrammar());
$sys['database'] = [
'name' => 'Postgres',
'version' => explode(' ', DB::select($expQuery)[0]->version)[1]
];
break;
case 'mysql':
$exp = DB::raw('select version()');
$expQuery = $exp->getValue(DB::connection()->getQueryGrammar());
$sys['database'] = [
'name' => 'MySQL',
'version' => DB::select($expQuery)[0]->{'version()'}
];
break;
default:
$sys['database'] = [
'name' => 'Unknown',
'version' => '?'
];
break;
}
return view('admin.settings.system', compact('sys'));
}
}

View File

@ -0,0 +1,226 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\CuratedRegister;
use App\Models\CuratedRegisterActivity;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Mail;
use App\Mail\CuratedRegisterRequestDetailsFromUser;
use App\Mail\CuratedRegisterAcceptUser;
use App\Mail\CuratedRegisterRejectUser;
use App\User;
class AdminCuratedRegisterController extends Controller
{
public function __construct()
{
$this->middleware(['auth','admin']);
}
public function index(Request $request)
{
$this->validate($request, [
'filter' => 'sometimes|in:open,all,awaiting,approved,rejected'
]);
$filter = $request->input('filter', 'open');
$records = CuratedRegister::when($filter, function($q, $filter) {
if($filter === 'open') {
return $q->where('is_rejected', false)
->whereNotNull('email_verified_at')
->whereIsClosed(false);
} else if($filter === 'all') {
return $q;
} elseif ($filter === 'awaiting') {
return $q->whereIsClosed(false)
->whereNull('is_rejected')
->whereNull('is_approved');
} elseif ($filter === 'approved') {
return $q->whereIsClosed(true)->whereIsApproved(true);
} elseif ($filter === 'rejected') {
return $q->whereIsClosed(true)->whereIsRejected(true);
}
})
->latest()
->paginate(10);
return view('admin.curated-register.index', compact('records', 'filter'));
}
public function show(Request $request, $id)
{
$record = CuratedRegister::findOrFail($id);
return view('admin.curated-register.show', compact('record'));
}
public function apiActivityLog(Request $request, $id)
{
$record = CuratedRegister::findOrFail($id);
$res = collect([
[
'id' => 1,
'action' => 'created',
'title' => 'Onboarding application created',
'message' => null,
'link' => null,
'timestamp' => $record->created_at,
]
]);
if($record->email_verified_at) {
$res->push([
'id' => 3,
'action' => 'email_verified_at',
'title' => 'Applicant successfully verified email address',
'message' => null,
'link' => null,
'timestamp' => $record->email_verified_at,
]);
}
$activities = CuratedRegisterActivity::whereRegisterId($record->id)->get();
$idx = 4;
$userResponses = collect([]);
foreach($activities as $activity) {
$idx++;
if($activity->from_user) {
$userResponses->push($activity);
continue;
}
$res->push([
'id' => $idx,
'aid' => $activity->id,
'action' => $activity->type,
'title' => $activity->from_admin ? 'Admin requested info' : 'User responded',
'message' => $activity->message,
'link' => $activity->adminReviewUrl(),
'timestamp' => $activity->created_at,
]);
}
foreach($userResponses as $ur) {
$res = $res->map(function($r) use($ur) {
if(!isset($r['aid'])) {
return $r;
}
if($ur->reply_to_id === $r['aid']) {
$r['user_response'] = $ur;
return $r;
}
return $r;
});
}
if($record->is_approved) {
$idx++;
$res->push([
'id' => $idx,
'action' => 'approved',
'title' => 'Application Approved',
'message' => null,
'link' => null,
'timestamp' => $record->action_taken_at,
]);
} else if ($record->is_rejected) {
$idx++;
$res->push([
'id' => $idx,
'action' => 'rejected',
'title' => 'Application Rejected',
'message' => null,
'link' => null,
'timestamp' => $record->action_taken_at,
]);
}
return $res->reverse()->values();
}
public function apiMessagePreviewStore(Request $request, $id)
{
$record = CuratedRegister::findOrFail($id);
return $request->all();
}
public function apiMessageSendStore(Request $request, $id)
{
$this->validate($request, [
'message' => 'required|string|min:5|max:1000'
]);
$record = CuratedRegister::findOrFail($id);
abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
$activity = new CuratedRegisterActivity;
$activity->register_id = $record->id;
$activity->admin_id = $request->user()->id;
$activity->secret_code = Str::random(32);
$activity->type = 'request_details';
$activity->from_admin = true;
$activity->message = $request->input('message');
$activity->save();
$record->is_awaiting_more_info = true;
$record->save();
Mail::to($record->email)->send(new CuratedRegisterRequestDetailsFromUser($record, $activity));
return $request->all();
}
public function previewDetailsMessageShow(Request $request, $id)
{
$record = CuratedRegister::findOrFail($id);
abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
$activity = new CuratedRegisterActivity;
$activity->message = $request->input('message');
return new \App\Mail\CuratedRegisterRequestDetailsFromUser($record, $activity);
}
public function previewMessageShow(Request $request, $id)
{
$record = CuratedRegister::findOrFail($id);
abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
$record->message = $request->input('message');
return new \App\Mail\CuratedRegisterSendMessage($record);
}
public function apiHandleReject(Request $request, $id)
{
$this->validate($request, [
'action' => 'required|in:reject-email,reject-silent'
]);
$action = $request->input('action');
$record = CuratedRegister::findOrFail($id);
abort_if($record->email_verified_at === null, 400, 'Cannot reject an unverified email');
$record->is_rejected = true;
$record->is_closed = true;
$record->action_taken_at = now();
$record->save();
if($action === 'reject-email') {
Mail::to($record->email)->send(new CuratedRegisterRejectUser($record));
}
return [200];
}
public function apiHandleApprove(Request $request, $id)
{
$record = CuratedRegister::findOrFail($id);
abort_if($record->email_verified_at === null, 400, 'Cannot reject an unverified email');
$record->is_approved = true;
$record->is_closed = true;
$record->action_taken_at = now();
$record->save();
$user = User::create([
'name' => $record->username,
'username' => $record->username,
'email' => $record->email,
'password' => $record->password,
'app_register_ip' => $record->ip_address,
'email_verified_at' => now(),
'register_source' => 'cur_onboarding'
]);
Mail::to($record->email)->send(new CuratedRegisterAcceptUser($record));
return [200];
}
}

View File

@ -40,16 +40,20 @@ class AdminApiController extends Controller
{
public function supported(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
return response()->json(['supported' => true]);
}
public function getStats(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
$res = AdminStatsService::summary();
$res['autospam_count'] = AccountInterstitial::whereType('post.autospam')
@ -60,8 +64,10 @@ class AdminApiController extends Controller
public function autospam(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
$appeals = AccountInterstitial::whereType('post.autospam')
->whereNull('appeal_handled_at')
@ -95,8 +101,10 @@ class AdminApiController extends Controller
public function autospamHandle(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:write'), 404);
$this->validate($request, [
'action' => 'required|in:dismiss,approve,dismiss-all,approve-all,delete-post,delete-account',
@ -239,8 +247,10 @@ class AdminApiController extends Controller
public function modReports(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
$reports = Report::whereNull('admin_seen')
->orderBy('created_at','desc')
@ -285,8 +295,10 @@ class AdminApiController extends Controller
public function modReportHandle(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:write'), 404);
$this->validate($request, [
'action' => 'required|string',
@ -343,8 +355,11 @@ class AdminApiController extends Controller
public function getConfiguration(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
abort_unless(config('instance.enable_cc'), 400);
return collect([
@ -386,8 +401,11 @@ class AdminApiController extends Controller
public function updateConfiguration(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:write'), 404);
abort_unless(config('instance.enable_cc'), 400);
$this->validate($request, [
@ -448,8 +466,11 @@ class AdminApiController extends Controller
public function getUsers(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
$this->validate($request, [
'sort' => 'sometimes|in:asc,desc',
]);
@ -466,8 +487,10 @@ class AdminApiController extends Controller
public function getUser(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
$id = $request->input('user_id');
$key = 'pf-admin-api:getUser:byId:' . $id;
@ -497,8 +520,10 @@ class AdminApiController extends Controller
public function userAdminAction(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:write'), 404);
$this->validate($request, [
'id' => 'required',
@ -669,8 +694,10 @@ class AdminApiController extends Controller
public function instances(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:write'), 404);
$this->validate($request, [
'q' => 'sometimes',
@ -707,8 +734,10 @@ class AdminApiController extends Controller
public function getInstance(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
$id = $request->input('id');
$res = Instance::findOrFail($id);
@ -718,8 +747,10 @@ class AdminApiController extends Controller
public function moderateInstance(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:write'), 404);
$this->validate($request, [
'id' => 'required',
@ -742,8 +773,10 @@ class AdminApiController extends Controller
public function refreshInstanceStats(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin == 1, 404);
abort_unless($request->user()->tokenCan('admin:write'), 404);
$this->validate($request, [
'id' => 'required',
@ -760,8 +793,10 @@ class AdminApiController extends Controller
public function getAllStats(Request $request)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user() || !$request->user()->token(), 404);
abort_unless($request->user()->is_admin === 1, 404);
abort_unless($request->user()->tokenCan('admin:read'), 404);
if($request->has('refresh')) {
Cache::forget('admin-api:instance-all-stats-v1');

File diff suppressed because it is too large Load Diff

View File

@ -68,9 +68,10 @@ class ApiV1Dot1Controller extends Controller
public function report(Request $request)
{
$user = $request->user();
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('write'), 403);
abort_if(!$user, 403);
$user = $request->user();
abort_if($user->status != null, 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
@ -175,9 +176,10 @@ class ApiV1Dot1Controller extends Controller
*/
public function deleteAvatar(Request $request)
{
$user = $request->user();
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('write'), 403);
abort_if(!$user, 403);
$user = $request->user();
abort_if($user->status != null, 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
@ -215,9 +217,10 @@ class ApiV1Dot1Controller extends Controller
*/
public function accountPosts(Request $request, $id)
{
$user = $request->user();
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
abort_if(!$user, 403);
$user = $request->user();
abort_if($user->status != null, 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
@ -255,8 +258,10 @@ class ApiV1Dot1Controller extends Controller
*/
public function accountChangePassword(Request $request)
{
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('write'), 403);
$user = $request->user();
abort_if(!$user, 403);
abort_if($user->status != null, 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
@ -280,8 +285,8 @@ class ApiV1Dot1Controller extends Controller
$log->action = 'account.edit.password';
$log->message = 'Password changed';
$log->link = null;
$log->ip_address = $request->ip();
$log->user_agent = $request->userAgent();
$log->ip_address = "127.0.0.23";
$log->user_agent = "Pixelfed.de";
$log->save();
Mail::to($request->user())->send(new PasswordChange($user));
@ -296,14 +301,16 @@ class ApiV1Dot1Controller extends Controller
*/
public function accountLoginActivity(Request $request)
{
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
$user = $request->user();
abort_if(!$user, 403);
abort_if($user->status != null, 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
}
$agent = new Agent();
$currentIp = $request->ip();
$currentIp = "127.0.0.23";
$activity = AccountLog::whereUserId($user->id)
->whereAction('auth.login')
@ -316,8 +323,8 @@ class ApiV1Dot1Controller extends Controller
return [
'id' => $item->id,
'action' => $item->action,
'ip' => $item->ip_address,
'ip_current' => $item->ip_address === $currentIp,
'ip' => "127.0.0.23",
'ip_current' => "127.0.0.23" === $currentIp,
'is_mobile' => $agent->isMobile(),
'device' => $agent->device(),
'browser' => $agent->browser(),
@ -336,8 +343,10 @@ class ApiV1Dot1Controller extends Controller
*/
public function accountTwoFactor(Request $request)
{
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
$user = $request->user();
abort_if(!$user, 403);
abort_if($user->status != null, 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
@ -358,8 +367,10 @@ class ApiV1Dot1Controller extends Controller
*/
public function accountEmailsFromPixelfed(Request $request)
{
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
$user = $request->user();
abort_if(!$user, 403);
abort_if($user->status != null, 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
@ -433,8 +444,10 @@ class ApiV1Dot1Controller extends Controller
*/
public function accountApps(Request $request)
{
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
$user = $request->user();
abort_if(!$user, 403);
abort_if($user->status != null, 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
@ -474,7 +487,7 @@ class ApiV1Dot1Controller extends Controller
abort_if(BouncerService::checkIp($request->ip()), 404);
}
$rl = RateLimiter::attempt('pf:apiv1.1:iar:'.$request->ip(), config('pixelfed.app_registration_rate_limit_attempts', 3), function(){}, config('pixelfed.app_registration_rate_limit_decay', 1800));
$rl = RateLimiter::attempt('pf:apiv1.1:iar:'.Str::Random(10), config('pixelfed.app_registration_rate_limit_attempts', 3), function(){}, config('pixelfed.app_registration_rate_limit_decay', 1800));
abort_if(!$rl, 400, 'Too many requests');
$this->validate($request, [
@ -546,7 +559,7 @@ class ApiV1Dot1Controller extends Controller
$user->email = $email;
$user->password = Hash::make($password);
$user->register_source = 'app';
$user->app_register_ip = $request->ip();
$user->app_register_ip = "127.0.0.23";
$user->app_register_token = Str::random(40);
$user->save();
@ -603,7 +616,7 @@ class ApiV1Dot1Controller extends Controller
abort_if(BouncerService::checkIp($request->ip()), 404);
}
$rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.$request->ip(), config('pixelfed.app_registration_confirm_rate_limit_attempts', 20), function(){}, config('pixelfed.app_registration_confirm_rate_limit_decay', 1800));
$rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.Str::Random(10), config('pixelfed.app_registration_confirm_rate_limit_attempts', 20), function(){}, config('pixelfed.app_registration_confirm_rate_limit_decay', 1800));
abort_if(!$rl, 429, 'Too many requests');
$this->validate($request, [
@ -640,7 +653,8 @@ class ApiV1Dot1Controller extends Controller
public function archive(Request $request, $id)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('write'), 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
@ -672,7 +686,8 @@ class ApiV1Dot1Controller extends Controller
public function unarchive(Request $request, $id)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('write'), 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
@ -703,7 +718,8 @@ class ApiV1Dot1Controller extends Controller
public function archivedPosts(Request $request)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
@ -719,7 +735,8 @@ class ApiV1Dot1Controller extends Controller
public function placesById(Request $request, $id, $slug)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
@ -757,8 +774,9 @@ class ApiV1Dot1Controller extends Controller
public function moderatePost(Request $request, $id)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_if($request->user()->is_admin != true, 403);
abort_unless($request->user()->tokenCan('admin:write'), 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
@ -864,7 +882,9 @@ class ApiV1Dot1Controller extends Controller
public function getWebSettings(Request $request)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
$uid = $request->user()->id;
$settings = UserSetting::firstOrCreate([
'user_id' => $uid
@ -877,7 +897,9 @@ class ApiV1Dot1Controller extends Controller
public function setWebSettings(Request $request)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('write'), 403);
$this->validate($request, [
'field' => 'required|in:enable_reblogs,hide_reblog_banner',
'value' => 'required'
@ -901,7 +923,9 @@ class ApiV1Dot1Controller extends Controller
public function getMutualAccounts(Request $request, $id)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('follows'), 403);
$account = AccountService::get($id, true);
if(!$account || !isset($account['id'])) { return []; }
$res = collect(FollowerService::mutualAccounts($request->user()->profile_id, $id))

View File

@ -71,69 +71,77 @@ class ApiV2Controller extends Controller
->toArray() : [];
});
$res = [
'domain' => config('pixelfed.domain.app'),
'title' => config_cache('app.name'),
'version' => config('pixelfed.version'),
'source_url' => 'https://github.com/pixelfed/pixelfed',
'description' => config_cache('app.short_description'),
'usage' => [
'users' => [
'active_month' => (int) Nodeinfo::activeUsersMonthly()
]
],
'thumbnail' => [
'url' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
'blurhash' => InstanceService::headerBlurhash(),
'versions' => [
'@1x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
'@2x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg'))
]
],
'languages' => [config('app.locale')],
'configuration' => [
'urls' => [
'streaming' => 'wss://' . config('pixelfed.domain.app'),
'status' => null
$res = Cache::remember('api:v2:instance-data-response-v2', 1800, function () use($contact, $rules) {
return [
'domain' => config('pixelfed.domain.app'),
'title' => config_cache('app.name'),
'version' => '3.5.3 (compatible; Pixelfed ' . config('pixelfed.version') .')',
'source_url' => 'https://github.com/pixelfed/pixelfed',
'description' => config_cache('app.short_description'),
'usage' => [
'users' => [
'active_month' => (int) Nodeinfo::activeUsersMonthly()
]
],
'accounts' => [
'max_featured_tags' => 0,
'thumbnail' => [
'url' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
'blurhash' => InstanceService::headerBlurhash(),
'versions' => [
'@1x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
'@2x' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg'))
]
],
'statuses' => [
'max_characters' => (int) config('pixelfed.max_caption_length'),
'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
'characters_reserved_per_url' => 23
'languages' => [config('app.locale')],
'configuration' => [
'urls' => [
'streaming' => null,
'status' => null
],
'vapid' => [
'public_key' => config('webpush.vapid.public_key'),
],
'accounts' => [
'max_featured_tags' => 0,
],
'statuses' => [
'max_characters' => (int) config('pixelfed.max_caption_length'),
'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
'characters_reserved_per_url' => 23
],
'media_attachments' => [
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
'image_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
'image_matrix_limit' => 3686400,
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
'video_frame_rate_limit' => 240,
'video_matrix_limit' => 3686400
],
'polls' => [
'max_options' => 0,
'max_characters_per_option' => 0,
'min_expiration' => 0,
'max_expiration' => 0,
],
'translation' => [
'enabled' => false,
],
],
'media_attachments' => [
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
'image_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
'image_matrix_limit' => 3686400,
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
'video_frame_rate_limit' => 240,
'video_matrix_limit' => 3686400
'registrations' => [
'enabled' => null,
'approval_required' => false,
'message' => null,
'url' => null,
],
'polls' => [
'max_options' => 4,
'max_characters_per_option' => 50,
'min_expiration' => 300,
'max_expiration' => 2629746,
'contact' => [
'email' => config('instance.email'),
'account' => $contact
],
'translation' => [
'enabled' => false,
],
],
'registrations' => [
'enabled' => (bool) config_cache('pixelfed.open_registration'),
'approval_required' => false,
'message' => null
],
'contact' => [
'email' => config('instance.email'),
'account' => $contact
],
'rules' => $rules
];
'rules' => $rules
];
});
$res['registrations']['enabled'] = (bool) config_cache('pixelfed.open_registration');
$res['registrations']['approval_required'] = (bool) config_cache('instance.curated_registration.enabled');
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
}
@ -145,7 +153,8 @@ class ApiV2Controller extends Controller
*/
public function search(Request $request)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
$this->validate($request, [
'q' => 'required|string|min:1|max:100',
@ -196,7 +205,8 @@ class ApiV2Controller extends Controller
*/
public function mediaUploadV2(Request $request)
{
abort_if(!$request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('write'), 403);
$this->validate($request, [
'file.*' => [

View File

@ -99,6 +99,7 @@ class BaseApiController extends Controller
public function avatarUpdate(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'upload' => 'required|mimetypes:image/jpeg,image/jpg,image/png|max:'.config('pixelfed.max_avatar_size'),
]);
@ -134,9 +135,10 @@ class BaseApiController extends Controller
public function verifyCredentials(Request $request)
{
abort_if(!$request->user(), 403);
$user = $request->user();
abort_if(!$user, 403);
if($user->status != null) {
if ($user->status != null) {
Auth::logout();
abort(403);
}
@ -147,6 +149,7 @@ class BaseApiController extends Controller
public function accountLikes(Request $request)
{
abort_if(!$request->user(), 403);
$this->validate($request, [
'page' => 'sometimes|int|min:1|max:20',
'limit' => 'sometimes|int|min:1|max:10'

View File

@ -23,7 +23,8 @@ class DomainBlockController extends Controller
public function index(Request $request)
{
abort_unless($request->user(), 403);
abort_if(!$request->user(), 403);
$this->validate($request, [
'limit' => 'sometimes|integer|min:1|max:200'
]);
@ -52,7 +53,7 @@ class DomainBlockController extends Controller
public function store(Request $request)
{
abort_unless($request->user(), 403);
abort_if(!$request->user(), 403);
$this->validate($request, [
'domain' => 'required|active_url|min:1|max:120'
@ -99,7 +100,7 @@ class DomainBlockController extends Controller
public function delete(Request $request)
{
abort_unless($request->user(), 403);
abort_if(!$request->user(), 403);
$this->validate($request, [
'domain' => 'required|min:1|max:120'

View File

@ -32,7 +32,9 @@ class TagsController extends Controller
*/
public function relatedTags(Request $request, $tag)
{
abort_unless($request->user(), 403);
abort_if(!$request->user() || !$request->user()->token(), 403);
abort_unless($request->user()->tokenCan('read'), 403);
$tag = Hashtag::whereSlug($tag)->firstOrFail();
return HashtagRelatedService::get($tag->id);
}

View File

@ -109,8 +109,8 @@ class LoginController extends Controller
$log->action = 'auth.login';
$log->message = 'Account Login';
$log->link = null;
$log->ip_address = $request->ip();
$log->user_agent = $request->userAgent();
$log->ip_address = "127.0.0.23";
$log->user_agent = "Pixelfed.de";
$log->save();
}

View File

@ -70,7 +70,7 @@ class RegisterController extends Controller
$usernameRules = [
'required',
'min:2',
'max:15',
'max:30',
'unique:users',
function ($attribute, $value, $fail) {
$dash = substr_count($value, '-');
@ -174,7 +174,7 @@ class RegisterController extends Controller
*/
public function showRegistrationForm()
{
if(config_cache('pixelfed.open_registration')) {
if((bool) config_cache('pixelfed.open_registration')) {
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp(request()->ip()), 404);
}
@ -191,7 +191,11 @@ class RegisterController extends Controller
return view('auth.register');
}
} else {
abort(404);
if((bool) config_cache('instance.curated_registration.enabled') && config('instance.curated_registration.state.fallback_on_closed_reg')) {
return redirect('/auth/sign_up');
} else {
abort(404);
}
}
}

View File

@ -260,6 +260,8 @@ class ComposeController extends Controller
$q = mb_substr($q, 1);
}
$user = $request->user();
abort_if($user->has_roles && !UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
$blocked = UserFilter::whereFilterableType('App\Profile')

View File

@ -0,0 +1,398 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\User;
use App\Models\CuratedRegister;
use App\Models\CuratedRegisterActivity;
use App\Services\EmailService;
use App\Services\BouncerService;
use App\Util\Lexer\RestrictedNames;
use App\Mail\CuratedRegisterConfirmEmail;
use App\Mail\CuratedRegisterNotifyAdmin;
use Illuminate\Support\Facades\Mail;
use App\Jobs\CuratedOnboarding\CuratedOnboardingNotifyAdminNewApplicationPipeline;
class CuratedRegisterController extends Controller
{
public function __construct()
{
abort_unless((bool) config_cache('instance.curated_registration.enabled'), 404);
if((bool) config_cache('pixelfed.open_registration')) {
abort_if(config('instance.curated_registration.state.only_enabled_on_closed_reg'), 404);
} else {
abort_unless(config('instance.curated_registration.state.fallback_on_closed_reg'), 404);
}
}
public function index(Request $request)
{
abort_if($request->user(), 404);
return view('auth.curated-register.index', ['step' => 1]);
}
public function concierge(Request $request)
{
abort_if($request->user(), 404);
$emailConfirmed = $request->session()->has('cur-reg-con.email-confirmed') &&
$request->has('next') &&
$request->session()->has('cur-reg-con.cr-id');
return view('auth.curated-register.concierge', compact('emailConfirmed'));
}
public function conciergeResponseSent(Request $request)
{
return view('auth.curated-register.user_response_sent');
}
public function conciergeFormShow(Request $request)
{
abort_if($request->user(), 404);
abort_unless(
$request->session()->has('cur-reg-con.email-confirmed') &&
$request->session()->has('cur-reg-con.cr-id') &&
$request->session()->has('cur-reg-con.ac-id'), 404);
$crid = $request->session()->get('cur-reg-con.cr-id');
$arid = $request->session()->get('cur-reg-con.ac-id');
$showCaptcha = config('instance.curated_registration.captcha_enabled');
if($attempts = $request->session()->get('cur-reg-con-attempt')) {
$showCaptcha = $attempts && $attempts >= 2;
} else {
$showCaptcha = false;
}
$activity = CuratedRegisterActivity::whereRegisterId($crid)->whereFromAdmin(true)->findOrFail($arid);
return view('auth.curated-register.concierge_form', compact('activity', 'showCaptcha'));
}
public function conciergeFormStore(Request $request)
{
abort_if($request->user(), 404);
$request->session()->increment('cur-reg-con-attempt');
abort_unless(
$request->session()->has('cur-reg-con.email-confirmed') &&
$request->session()->has('cur-reg-con.cr-id') &&
$request->session()->has('cur-reg-con.ac-id'), 404);
$attempts = $request->session()->get('cur-reg-con-attempt');
$messages = [];
$rules = [
'response' => 'required|string|min:5|max:1000',
'crid' => 'required|integer|min:1',
'acid' => 'required|integer|min:1'
];
if(config('instance.curated_registration.captcha_enabled') && $attempts >= 3) {
$rules['h-captcha-response'] = 'required|captcha';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
}
$this->validate($request, $rules, $messages);
$crid = $request->session()->get('cur-reg-con.cr-id');
$acid = $request->session()->get('cur-reg-con.ac-id');
abort_if((string) $crid !== $request->input('crid'), 404);
abort_if((string) $acid !== $request->input('acid'), 404);
if(CuratedRegisterActivity::whereRegisterId($crid)->whereReplyToId($acid)->exists()) {
return redirect()->back()->withErrors(['code' => 'You already replied to this request.']);
}
$act = CuratedRegisterActivity::create([
'register_id' => $crid,
'reply_to_id' => $acid,
'type' => 'user_response',
'message' => $request->input('response'),
'from_user' => true,
'action_required' => true,
]);
$request->session()->pull('cur-reg-con');
$request->session()->pull('cur-reg-con-attempt');
return view('auth.curated-register.user_response_sent');
}
public function conciergeStore(Request $request)
{
abort_if($request->user(), 404);
$rules = [
'sid' => 'required_if:action,email|integer|min:1|max:20000000',
'id' => 'required_if:action,email|integer|min:1|max:20000000',
'code' => 'required_if:action,email',
'action' => 'required|string|in:email,message',
'email' => 'required_if:action,email|email',
'response' => 'required_if:action,message|string|min:20|max:1000',
];
$messages = [];
if(config('instance.curated_registration.captcha_enabled')) {
$rules['h-captcha-response'] = 'required|captcha';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
}
$this->validate($request, $rules, $messages);
$action = $request->input('action');
$sid = $request->input('sid');
$id = $request->input('id');
$code = $request->input('code');
$email = $request->input('email');
$cr = CuratedRegister::whereIsClosed(false)->findOrFail($sid);
$ac = CuratedRegisterActivity::whereRegisterId($cr->id)->whereFromAdmin(true)->findOrFail($id);
if(!hash_equals($ac->secret_code, $code)) {
return redirect()->back()->withErrors(['code' => 'Invalid code']);
}
if(!hash_equals($cr->email, $email)) {
return redirect()->back()->withErrors(['email' => 'Invalid email']);
}
$request->session()->put('cur-reg-con.email-confirmed', true);
$request->session()->put('cur-reg-con.cr-id', $cr->id);
$request->session()->put('cur-reg-con.ac-id', $ac->id);
$emailConfirmed = true;
return redirect('/auth/sign_up/concierge/form');
}
public function confirmEmail(Request $request)
{
if($request->user()) {
return redirect(route('help.email-confirmation-issues'));
}
return view('auth.curated-register.confirm_email');
}
public function emailConfirmed(Request $request)
{
if($request->user()) {
return redirect(route('help.email-confirmation-issues'));
}
return view('auth.curated-register.email_confirmed');
}
public function resendConfirmation(Request $request)
{
return view('auth.curated-register.resend-confirmation');
}
public function resendConfirmationProcess(Request $request)
{
$rules = [
'email' => [
'required',
'string',
app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
'exists:curated_registers',
]
];
$messages = [];
if(config('instance.curated_registration.captcha_enabled')) {
$rules['h-captcha-response'] = 'required|captcha';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
}
$this->validate($request, $rules, $messages);
$cur = CuratedRegister::whereEmail($request->input('email'))->whereIsClosed(false)->first();
if(!$cur) {
return redirect()->back()->withErrors(['email' => 'The selected email is invalid.']);
}
$totalCount = CuratedRegisterActivity::whereRegisterId($cur->id)
->whereType('user_resend_email_confirmation')
->count();
if($totalCount && $totalCount >= config('instance.curated_registration.resend_confirmation_limit')) {
return redirect()->back()->withErrors(['email' => 'You have re-attempted too many times. To proceed with your application, please <a href="/site/contact" class="text-white" style="text-decoration: underline;">contact the admin team</a>.']);
}
$count = CuratedRegisterActivity::whereRegisterId($cur->id)
->whereType('user_resend_email_confirmation')
->where('created_at', '>', now()->subHours(12))
->count();
if($count) {
return redirect()->back()->withErrors(['email' => 'You can only re-send the confirmation email once per 12 hours. Try again later.']);
}
CuratedRegisterActivity::create([
'register_id' => $cur->id,
'type' => 'user_resend_email_confirmation',
'admin_only_view' => true,
'from_admin' => false,
'from_user' => false,
'action_required' => false,
]);
Mail::to($cur->email)->send(new CuratedRegisterConfirmEmail($cur));
return view('auth.curated-register.resent-confirmation');
return $request->all();
}
public function confirmEmailHandle(Request $request)
{
$rules = [
'sid' => 'required',
'code' => 'required'
];
$messages = [];
if(config('instance.curated_registration.captcha_enabled')) {
$rules['h-captcha-response'] = 'required|captcha';
$messages['h-captcha-response.required'] = 'The captcha must be filled';
}
$this->validate($request, $rules, $messages);
$cr = CuratedRegister::whereNull('email_verified_at')
->where('created_at', '>', now()->subHours(24))
->find($request->input('sid'));
if(!$cr) {
return redirect(route('help.email-confirmation-issues'));
}
if(!hash_equals($cr->verify_code, $request->input('code'))) {
return redirect(route('help.email-confirmation-issues'));
}
$cr->email_verified_at = now();
$cr->save();
if(config('instance.curated_registration.notify.admin.on_verify_email.enabled')) {
CuratedOnboardingNotifyAdminNewApplicationPipeline::dispatch($cr);
}
return view('auth.curated-register.email_confirmed');
}
public function proceed(Request $request)
{
$this->validate($request, [
'step' => 'required|integer|in:1,2,3,4'
]);
$step = $request->input('step');
switch($step) {
case 1:
$step = 2;
$request->session()->put('cur-step', 1);
return view('auth.curated-register.index', compact('step'));
break;
case 2:
$this->stepTwo($request);
$step = 3;
$request->session()->put('cur-step', 2);
return view('auth.curated-register.index', compact('step'));
break;
case 3:
$this->stepThree($request);
$step = 3;
$request->session()->put('cur-step', 3);
$verifiedEmail = true;
$request->session()->pull('cur-reg');
return view('auth.curated-register.index', compact('step', 'verifiedEmail'));
break;
}
}
protected function stepTwo($request)
{
if($request->filled('reason')) {
$request->session()->put('cur-reg.form-reason', $request->input('reason'));
}
if($request->filled('username')) {
$request->session()->put('cur-reg.form-username', $request->input('username'));
}
if($request->filled('email')) {
$request->session()->put('cur-reg.form-email', $request->input('email'));
}
$this->validate($request, [
'username' => [
'required',
'min:2',
'max:15',
'unique:curated_registers',
'unique:users',
function ($attribute, $value, $fail) {
$dash = substr_count($value, '-');
$underscore = substr_count($value, '_');
$period = substr_count($value, '.');
if(ends_with($value, ['.php', '.js', '.css'])) {
return $fail('Username is invalid.');
}
if(($dash + $underscore + $period) > 1) {
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
}
if (!ctype_alnum($value[0])) {
return $fail('Username is invalid. Must start with a letter or number.');
}
if (!ctype_alnum($value[strlen($value) - 1])) {
return $fail('Username is invalid. Must end with a letter or number.');
}
$val = str_replace(['_', '.', '-'], '', $value);
if(!ctype_alnum($val)) {
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
}
$restricted = RestrictedNames::get();
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
return $fail('Username cannot be used.');
}
},
],
'email' => [
'required',
'string',
app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
'max:255',
'unique:users',
'unique:curated_registers',
function ($attribute, $value, $fail) {
$banned = EmailService::isBanned($value);
if($banned) {
return $fail('Email is invalid.');
}
},
],
'password' => 'required|min:8',
'password_confirmation' => 'required|same:password',
'reason' => 'required|min:20|max:1000',
'agree' => 'required|accepted'
]);
$request->session()->put('cur-reg.form-email', $request->input('email'));
$request->session()->put('cur-reg.form-password', $request->input('password'));
}
protected function stepThree($request)
{
$this->validate($request, [
'email' => [
'required',
'string',
app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
'max:255',
'unique:users',
'unique:curated_registers',
function ($attribute, $value, $fail) {
$banned = EmailService::isBanned($value);
if($banned) {
return $fail('Email is invalid.');
}
},
]
]);
$cr = new CuratedRegister;
$cr->email = $request->email;
$cr->username = $request->session()->get('cur-reg.form-username');
$cr->password = bcrypt($request->session()->get('cur-reg.form-password'));
$cr->ip_address = $request->ip();
$cr->reason_to_join = $request->session()->get('cur-reg.form-reason');
$cr->verify_code = Str::random(40);
$cr->save();
Mail::to($cr->email)->send(new CuratedRegisterConfirmEmail($cr));
}
}

View File

@ -279,16 +279,16 @@ class DiscoverController extends Controller
}
return [
'hashtags' => [
'enabled' => false,
'enabled' => true,
],
'memories' => [
'enabled' => false,
'enabled' => true,
],
'insights' => [
'enabled' => false,
'enabled' => true,
],
'friends' => [
'enabled' => false,
'enabled' => true,
],
'server' => [
'enabled' => false,

View File

@ -253,7 +253,7 @@ class FederationController extends Controller
'type' => 'OrderedCollection',
'totalItems' => $account['following_count'] ?? 0,
];
return response()->json($obj);
return response()->json($obj)->header('Content-Type', 'application/activity+json');
}
public function userFollowers(Request $request, $username)
@ -269,6 +269,6 @@ class FederationController extends Controller
'type' => 'OrderedCollection',
'totalItems' => $account['followers_count'] ?? 0,
];
return response()->json($obj);
return response()->json($obj)->header('Content-Type', 'application/activity+json');
}
}

View File

@ -42,6 +42,7 @@ use App\Services\{
use App\Jobs\StatusPipeline\NewStatusPipeline;
use League\Fractal\Serializer\ArraySerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Services\InstanceService;
class PublicApiController extends Controller
{
@ -661,6 +662,10 @@ class PublicApiController extends Controller
public function account(Request $request, $id)
{
$res = AccountService::get($id);
if($res && isset($res['local'], $res['url']) && !$res['local']) {
$domain = parse_url($res['url'], PHP_URL_HOST);
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
}
return response()->json($res);
}
@ -680,6 +685,11 @@ class PublicApiController extends Controller
$profile = AccountService::get($id);
abort_if(!$profile, 404);
if($profile && isset($profile['local'], $profile['url']) && !$profile['local']) {
$domain = parse_url($profile['url'], PHP_URL_HOST);
abort_if(in_array($domain, InstanceService::getBannedDomains()), 404);
}
$limit = $request->limit ?? 9;
$max_id = $request->max_id;
$min_id = $request->min_id;

View File

@ -320,7 +320,7 @@ class RemoteAuthController extends Controller
'webfinger' => $res['_webfinger'],
], [
'software' => 'mastodon',
'ip_address' => $request->ip(),
'ip_address' => "127.0.0.23",
'bearer_token' => $token,
'verify_credentials' => $res,
'last_verify_credentials_at' => now(),
@ -702,7 +702,7 @@ class RemoteAuthController extends Controller
'email' => $data['email'],
'password' => Hash::make($data['password']),
'email_verified_at' => config('remote-auth.mastodon.contraints.skip_email_verification') ? now() : null,
'app_register_ip' => request()->ip(),
'app_register_ip' => "127.0.0.23",
'register_source' => 'mastodon'
])));

View File

@ -230,8 +230,8 @@ class SeasonalController extends Controller
'action' => 'seasonal.my2020.view'
],
[
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent()
'ip_address' => "127.0.0.23",
'user_agent' => "Pixelfed.de"
]
]);
return response()->json(200);

View File

@ -139,8 +139,8 @@ trait HomeSettings
$log->action = 'account.edit.password';
$log->message = 'Password changed';
$log->link = null;
$log->ip_address = $request->ip();
$log->user_agent = $request->userAgent();
$log->ip_address = "127.0.0.23";
$log->user_agent = "Pixelfed.de";
$log->save();
Mail::to($request->user())->send(new PasswordChange($user));
@ -186,8 +186,8 @@ trait HomeSettings
$log->action = 'account.edit.email';
$log->message = 'Email changed';
$log->link = null;
$log->ip_address = $request->ip();
$log->user_agent = $request->userAgent();
$log->ip_address = "127.0.0.23";
$log->user_agent = "Pixelfed.de";
$log->save();
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\Internal\SoftwareUpdateService;
class SoftwareUpdateController extends Controller
{
public function __construct()
{
$this->middleware('auth');
$this->middleware('admin');
}
public function getSoftwareUpdateCheck(Request $request)
{
$res = SoftwareUpdateService::get();
return $res;
}
}

View File

@ -80,8 +80,8 @@ class UserEmailForgotController extends Controller
{
UserEmailForgot::create([
'user_id' => $user->id,
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
'ip_address' => "127.0.0.23",
'user_agent' => "Pixelfed.de",
'email_sent_at' => now()
]);

View File

@ -6,63 +6,77 @@ use Illuminate\Database\Eloquent\Model;
class Instance extends Model
{
protected $fillable = ['domain', 'banned', 'auto_cw', 'unlisted', 'notes'];
protected $casts = [
'last_crawled_at' => 'datetime',
'actors_last_synced_at' => 'datetime',
'notes' => 'array',
'nodeinfo_last_fetched' => 'datetime',
'delivery_next_after' => 'datetime',
];
public function profiles()
{
return $this->hasMany(Profile::class, 'domain', 'domain');
}
protected $fillable = [
'domain',
'banned',
'auto_cw',
'unlisted',
'notes'
];
public function statuses()
{
return $this->hasManyThrough(
Status::class,
Profile::class,
'domain',
'profile_id',
'domain',
'id'
);
}
public function profiles()
{
return $this->hasMany(Profile::class, 'domain', 'domain');
}
public function reported()
{
return $this->hasManyThrough(
Report::class,
Profile::class,
'domain',
'reported_profile_id',
'domain',
'id'
);
}
public function statuses()
{
return $this->hasManyThrough(
Status::class,
Profile::class,
'domain',
'profile_id',
'domain',
'id'
);
}
public function reports()
{
return $this->hasManyThrough(
Report::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 media()
{
return $this->hasManyThrough(
Media::class,
Profile::class,
'domain',
'profile_id',
'domain',
'id'
);
}
public function reports()
{
return $this->hasManyThrough(
Report::class,
Profile::class,
'domain',
'profile_id',
'domain',
'id'
);
}
public function getUrl()
{
return url("/i/admin/instances/show/{$this->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}");
}
}

View File

@ -91,19 +91,21 @@ class CommentPipeline implements ShouldQueue
return;
}
DB::transaction(function() use($target, $actor, $comment) {
$notification = new Notification();
$notification->profile_id = $target->id;
$notification->actor_id = $actor->id;
$notification->action = 'comment';
$notification->item_id = $comment->id;
$notification->item_type = "App\Status";
$notification->save();
if($target->user_id && $target->domain === null) {
DB::transaction(function() use($target, $actor, $comment) {
$notification = new Notification();
$notification->profile_id = $target->id;
$notification->actor_id = $actor->id;
$notification->action = 'comment';
$notification->item_id = $comment->id;
$notification->item_type = "App\Status";
$notification->save();
NotificationService::setNotification($notification);
NotificationService::set($notification->profile_id, $notification->id);
StatusService::del($comment->id);
});
NotificationService::setNotification($notification);
NotificationService::set($notification->profile_id, $notification->id);
StatusService::del($comment->id);
});
}
if($exists = Cache::get('status:replies:all:' . $status->id)) {
if($exists && $exists->count() == 3) {

View File

@ -0,0 +1,65 @@
<?php
namespace App\Jobs\CuratedOnboarding;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\CuratedRegister;
use App\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use App\Mail\CuratedRegisterNotifyAdmin;
class CuratedOnboardingNotifyAdminNewApplicationPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $cr;
/**
* Create a new job instance.
*/
public function __construct(CuratedRegister $cr)
{
$this->cr = $cr;
}
/**
* Execute the job.
*/
public function handle(): void
{
if(!config('instance.curated_registration.notify.admin.on_verify_email.enabled')) {
return;
}
config('instance.curated_registration.notify.admin.on_verify_email.bundle') ?
$this->handleBundled() :
$this->handleUnbundled();
}
protected function handleBundled()
{
$cr = $this->cr;
Storage::append('conanap.json', json_encode([
'id' => $cr->id,
'email' => $cr->email,
'created_at' => $cr->created_at,
'updated_at' => $cr->updated_at,
]));
}
protected function handleUnbundled()
{
$cr = $this->cr;
if($aid = config_cache('instance.admin.pid')) {
$admin = User::whereProfileId($aid)->first();
if($admin && $admin->email) {
Mail::to($admin->email)->send(new CuratedRegisterNotifyAdmin($cr));
}
}
}
}

View File

@ -22,9 +22,9 @@ use App\Notification;
use App\Services\AccountService;
use App\Services\NetworkTimelineService;
use App\Services\StatusService;
use App\Jobs\ProfilePipeline\DecrementPostCount;
use App\Jobs\MediaPipeline\MediaDeletePipeline;
use Cache;
use App\Services\Account\AccountStatService;
class DeleteRemoteStatusPipeline implements ShouldQueue
{
@ -56,10 +56,7 @@ class DeleteRemoteStatusPipeline implements ShouldQueue
{
$status = $this->status;
if(AccountService::get($status->profile_id, true)) {
DecrementPostCount::dispatch($status->profile_id)->onQueue('low');
}
AccountStatService::decrementPostCount($status->profile_id);
NetworkTimelineService::del($status->id);
StatusService::del($status->id, true);
Bookmark::whereStatusId($status->id)->delete();

View File

@ -72,16 +72,18 @@ class FollowPipeline implements ShouldQueue
$target->save();
AccountService::del($target->id);
try {
$notification = new Notification();
$notification->profile_id = $target->id;
$notification->actor_id = $actor->id;
$notification->action = 'follow';
$notification->item_id = $target->id;
$notification->item_type = "App\Profile";
$notification->save();
} catch (Exception $e) {
Log::error($e);
if($target->user_id && $target->domain === null) {
try {
$notification = new Notification();
$notification->profile_id = $target->id;
$notification->actor_id = $actor->id;
$notification->action = 'follow';
$notification->item_id = $target->id;
$notification->item_type = "App\Profile";
$notification->save();
} catch (Exception $e) {
Log::error($e);
}
}
}
}

View File

@ -4,6 +4,7 @@ namespace App\Jobs\InstancePipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
@ -12,45 +13,71 @@ use Illuminate\Support\Facades\Http;
use App\Instance;
use App\Profile;
use App\Services\NodeinfoService;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
class FetchNodeinfoPipeline implements ShouldQueue
class FetchNodeinfoPipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $instance;
protected $instance;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Instance $instance)
{
$this->instance = $instance;
}
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Instance $instance)
{
$this->instance = $instance;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$instance = $this->instance;
/**
* The number of seconds after which the job's unique lock will be released.
*
* @var int
*/
public $uniqueFor = 14400;
$ni = NodeinfoService::get($instance->domain);
if($ni) {
if(isset($ni['software']) && is_array($ni['software']) && isset($ni['software']['name'])) {
$software = $ni['software']['name'];
$instance->software = strtolower(strip_tags($software));
$instance->last_crawled_at = now();
$instance->user_count = Profile::whereDomain($instance->domain)->count();
$instance->save();
}
} else {
$instance->user_count = Profile::whereDomain($instance->domain)->count();
$instance->last_crawled_at = now();
$instance->save();
}
}
/**
* Get the unique ID for the job.
*/
public function uniqueId(): string
{
return $this->instance->id;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$instance = $this->instance;
if( $instance->nodeinfo_last_fetched &&
$instance->nodeinfo_last_fetched->gt(now()->subHours(12)) ||
$instance->delivery_timeout &&
$instance->delivery_next_after->gt(now())
) {
return;
}
$ni = NodeinfoService::get($instance->domain);
$instance->last_crawled_at = now();
if($ni) {
if(isset($ni['software']) && is_array($ni['software']) && isset($ni['software']['name'])) {
$software = $ni['software']['name'];
$instance->software = strtolower(strip_tags($software));
$instance->user_count = Profile::whereDomain($instance->domain)->count();
$instance->nodeinfo_last_fetched = now();
$instance->save();
}
} else {
$instance->delivery_timeout = 1;
$instance->delivery_next_after = now()->addHours(14);
$instance->save();
}
}
}

View File

@ -61,7 +61,12 @@ class NotificationEpochUpdatePipeline implements ShouldQueue, ShouldBeUniqueUnti
*/
public function handle(): void
{
$rec = Notification::where('created_at', '>', now()->subMonths(6))->first();
$pid = Cache::get(NotificationService::EPOCH_CACHE_KEY . '6');
if($pid && $pid > 1) {
$rec = Notification::where('id', '>', $pid)->whereDate('created_at', now()->subMonths(6)->format('Y-m-d'))->first();
} else {
$rec = Notification::whereDate('created_at', now()->subMonths(6)->format('Y-m-d'))->first();
}
$id = 1;
if($rec) {
$id = $rec->id;

View File

@ -79,16 +79,18 @@ class LikePipeline implements ShouldQueue
return true;
}
try {
$notification = new Notification();
$notification->profile_id = $status->profile_id;
$notification->actor_id = $actor->id;
$notification->action = 'like';
$notification->item_id = $status->id;
$notification->item_type = "App\Status";
$notification->save();
if($status->uri === null && $status->object_url === null && $status->url === null) {
try {
$notification = new Notification();
$notification->profile_id = $status->profile_id;
$notification->actor_id = $actor->id;
$notification->action = 'like';
$notification->item_id = $status->id;
$notification->item_type = "App\Status";
$notification->save();
} catch (Exception $e) {
} catch (Exception $e) {
}
}
}

View File

@ -35,18 +35,7 @@ class DecrementPostCount implements ShouldQueue
*/
public function handle()
{
$id = $this->id;
$profile = Profile::find($id);
if(!$profile) {
return 1;
}
$profile->status_count = $profile->status_count ? $profile->status_count - 1 : 0;
$profile->save();
AccountService::del($id);
return 1;
// deprecated
return;
}
}

View File

@ -14,42 +14,12 @@ use App\Profile;
use App\Status;
use App\Services\AccountService;
class IncrementPostCount implements ShouldQueue, ShouldBeUniqueUntilProcessing
class IncrementPostCount implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $id;
public $timeout = 900;
public $tries = 3;
public $maxExceptions = 1;
public $failOnTimeout = true;
/**
* The number of seconds after which the job's unique lock will be released.
*
* @var int
*/
public $uniqueFor = 3600;
/**
* Get the unique ID for the job.
*/
public function uniqueId(): string
{
return 'propipe:ipc:' . $this->id;
}
/**
* Get the middleware the job should pass through.
*
* @return array<int, object>
*/
public function middleware(): array
{
return [(new WithoutOverlapping("propipe:ipc:{$this->id}"))->shared()->dontRelease()];
}
/**
* Create a new job instance.
*
@ -67,20 +37,7 @@ class IncrementPostCount implements ShouldQueue, ShouldBeUniqueUntilProcessing
*/
public function handle()
{
$id = $this->id;
$profile = Profile::find($id);
if(!$profile) {
return 1;
}
$profile->status_count = $profile->status_count + 1;
$profile->last_status_at = now();
$profile->save();
AccountService::del($id);
AccountService::get($id);
return 1;
// deprecated
return;
}
}

View File

@ -39,8 +39,8 @@ use App\Services\AccountService;
use App\Services\CollectionService;
use App\Services\StatusService;
use App\Jobs\MediaPipeline\MediaDeletePipeline;
use App\Jobs\ProfilePipeline\DecrementPostCount;
use App\Services\NotificationService;
use App\Services\Account\AccountStatService;
class RemoteStatusDelete implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
@ -109,9 +109,7 @@ class RemoteStatusDelete implements ShouldQueue, ShouldBeUniqueUntilProcessing
}
StatusService::del($status->id, true);
DecrementPostCount::dispatch($status->profile_id)->onQueue('inbox');
AccountStatService::decrementPostCount($status->profile_id);
return $this->unlinkRemoveMedia($status);
}

View File

@ -87,18 +87,20 @@ class StatusReplyPipeline implements ShouldQueue
Cache::forget('status:replies:all:' . $reply->id);
Cache::forget('status:replies:all:' . $status->id);
DB::transaction(function() use($target, $actor, $status) {
$notification = new Notification();
$notification->profile_id = $target->id;
$notification->actor_id = $actor->id;
$notification->action = 'comment';
$notification->item_id = $status->id;
$notification->item_type = "App\Status";
$notification->save();
if($target->user_id && $target->domain === null) {
DB::transaction(function() use($target, $actor, $status) {
$notification = new Notification();
$notification->profile_id = $target->id;
$notification->actor_id = $actor->id;
$notification->action = 'comment';
$notification->item_id = $status->id;
$notification->item_type = "App\Status";
$notification->save();
NotificationService::setNotification($notification);
NotificationService::set($notification->profile_id, $notification->id);
});
NotificationService::setNotification($notification);
NotificationService::set($notification->profile_id, $notification->id);
});
}
if($exists = Cache::get('status:replies:all:' . $reply->id)) {
if($exists && $exists->count() == 3) {

View File

@ -122,8 +122,8 @@ class AuthLogin
$device = DB::transaction(function() use($user) {
return UserDevice::firstOrCreate([
'user_id' => $user->id,
'ip' => request()->ip(),
'user_agent' => str_limit(request()->userAgent(), 180),
'ip' => "127.0.0.23",
'user_agent' => "Pixelfed.de",
]);
});
}

View File

@ -41,8 +41,8 @@ class LogFailedLogin
$log->action = 'auth.failed';
$log->message = 'Failed login attempt';
$log->link = null;
$log->ip_address = $request->ip();
$log->user_agent = $request->userAgent();
$log->ip_address = "127.0.0.23";
$log->user_agent = "Pixelfed.de";
$log->save();
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CuratedRegisterAcceptUser extends Mailable
{
use Queueable, SerializesModels;
public $verify;
/**
* Create a new message instance.
*/
public function __construct($verify)
{
$this->verify = $verify;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Your ' . config('pixelfed.domain.app') . ' Registration Update',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.curated-register.request-accepted',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use App\Models\CuratedRegister;
class CuratedRegisterConfirmEmail extends Mailable
{
use Queueable, SerializesModels;
public $verify;
/**
* Create a new message instance.
*/
public function __construct(CuratedRegister $verify)
{
$this->verify = $verify;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Welcome to Pixelfed! Please Confirm Your Email',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.curated-register.confirm_email',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use App\Models\CuratedRegister;
class CuratedRegisterNotifyAdmin extends Mailable
{
use Queueable, SerializesModels;
public $verify;
/**
* Create a new message instance.
*/
public function __construct(CuratedRegister $verify)
{
$this->verify = $verify;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: '[Requires Action]: New Curated Onboarding Application',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.curated-register.admin_notify',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CuratedRegisterNotifyAdminUserResponse extends Mailable
{
use Queueable, SerializesModels;
public $activity;
/**
* Create a new message instance.
*/
public function __construct($activity)
{
$this->activity = $activity;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Curated Register Notify Admin User Response',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.curated-register.admin_notify_user_response',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CuratedRegisterRejectUser extends Mailable
{
use Queueable, SerializesModels;
public $verify;
/**
* Create a new message instance.
*/
public function __construct($verify)
{
$this->verify = $verify;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Your ' . config('pixelfed.domain.app') . ' Registration Update',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.curated-register.request-rejected',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use App\Models\CuratedRegister;
use App\Models\CuratedRegisterActivity;
class CuratedRegisterRequestDetailsFromUser extends Mailable
{
use Queueable, SerializesModels;
public $verify;
public $activity;
/**
* Create a new message instance.
*/
public function __construct(CuratedRegister $verify, CuratedRegisterActivity $activity)
{
$this->verify = $verify;
$this->activity = $activity;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: '[Action Needed]: Additional information requested',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.curated-register.request-details-from-user',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class CuratedRegisterSendMessage extends Mailable
{
use Queueable, SerializesModels;
public $verify;
/**
* Create a new message instance.
*/
public function __construct($verify)
{
$this->verify = $verify;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Your ' . config('pixelfed.domain.app') . ' Registration Update',
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'emails.curated-register.message-from-admin',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CuratedRegister extends Model
{
use HasFactory;
protected $casts = [
'autofollow_account_ids' => 'array',
'admin_notes' => 'array',
'email_verified_at' => 'datetime',
'admin_notified_at' => 'datetime',
'action_taken_at' => 'datetime',
];
public function adminStatusLabel()
{
if(!$this->email_verified_at) {
return '<span class="border border-danger px-3 py-1 rounded text-white font-weight-bold">Unverified email</span>';
}
if($this->is_accepted) { return 'Approved'; }
if($this->is_rejected) { return 'Rejected'; }
if($this->is_awaiting_more_info ) {
return '<span class="border border-info px-3 py-1 rounded text-white font-weight-bold">Awaiting Details</span>';
}
if($this->is_closed ) { return 'Closed'; }
return '<span class="border border-success px-3 py-1 rounded text-white font-weight-bold">Open</span>';
}
public function emailConfirmUrl()
{
return url('/auth/sign_up/confirm?sid=' . $this->id . '&code=' . $this->verify_code);
}
public function emailReplyUrl()
{
return url('/auth/sign_up/concierge?sid=' . $this->id . '&code=' . $this->verify_code . '&sc=' . str_random(8));
}
public function adminReviewUrl()
{
return url('/i/admin/curated-onboarding/show/' . $this->id);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CuratedRegisterActivity extends Model
{
use HasFactory;
protected $guarded = [];
protected $casts = [
'metadata' => 'array',
'admin_notified_at' => 'datetime',
'action_taken_at' => 'datetime',
];
public function application()
{
return $this->belongsTo(CuratedRegister::class, 'register_id');
}
public function emailReplyUrl()
{
return url('/auth/sign_up/concierge?sid='.$this->register_id . '&id=' . $this->id . '&code=' . $this->secret_code);
}
public function adminReviewUrl()
{
$url = '/i/admin/curated-onboarding/show/' . $this->register_id . '/?ah=' . $this->id;
if($this->reply_to_id) {
$url .= '&rtid=' . $this->reply_to_id;
}
return url($url);
}
}

View File

@ -31,17 +31,20 @@ class AuthServiceProvider extends ServiceProvider
if(config('instance.oauth.pat.enabled')) {
Passport::personalAccessClientId(config('instance.oauth.pat.id'));
}
Passport::setDefaultScope([
'read',
'write',
'follow',
]);
Passport::tokensCan([
'read' => 'Full read access to your account',
'write' => 'Full write access to your account',
'follow' => 'Ability to follow other profiles',
'push' => ''
'admin:read' => 'Read all data on the server',
'admin:write' => 'Modify all data on the server',
'push' => 'Receive your push notifications'
]);
Passport::setDefaultScope([
'read',
'write',
'follow',
]);
}

View File

@ -23,8 +23,6 @@ class RouteServiceProvider extends ServiceProvider
*/
public function boot()
{
//
parent::boot();
}
@ -36,10 +34,7 @@ class RouteServiceProvider extends ServiceProvider
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}
/**
@ -51,6 +46,18 @@ class RouteServiceProvider extends ServiceProvider
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web-admin.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web-portfolio.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web-api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));

View File

@ -0,0 +1,31 @@
<?php
namespace App\Services\Account;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
class AccountStatService
{
const REFRESH_CACHE_KEY = 'pf:services:accountstats:refresh:daily';
public static function incrementPostCount($pid)
{
return Redis::zadd(self::REFRESH_CACHE_KEY, $pid, $pid);
}
public static function decrementPostCount($pid)
{
return Redis::zadd(self::REFRESH_CACHE_KEY, $pid, $pid);
}
public static function removeFromPostCount($pid)
{
return Redis::zrem(self::REFRESH_CACHE_KEY, $pid);
}
public static function getAllPostCountIncr($limit = -1)
{
return Redis::zrange(self::REFRESH_CACHE_KEY, 0, $limit);
}
}

View File

@ -120,6 +120,7 @@ class RemoteAuthService
}
return Cache::remember(self::CACHE_KEY . 'domain-compatible:' . $domain, 14400, function() use($domain) {
return true;
try {
$res = Http::timeout(20)->retry(3, 750)->get('https://beagle.pixelfed.net/api/v1/raa/domain?domain=' . $domain);
if(!$res->ok()) {
@ -144,6 +145,7 @@ class RemoteAuthService
public static function lookupWebfingerUses($wf)
{
return 0;
try {
$res = Http::timeout(20)->retry(3, 750)->get('https://beagle.pixelfed.net/api/v1/raa/lookup?webfinger=' . $wf);
if(!$res->ok()) {
@ -166,6 +168,7 @@ class RemoteAuthService
public static function submitToBeagle($ow, $ou, $dw, $du)
{
return;
try {
$url = 'https://beagle.pixelfed.net/api/v1/raa/submit';
$res = Http::throw()->timeout(10)->get($url, [

View File

@ -11,38 +11,61 @@ use Illuminate\Http\Client\RequestException;
class ActivityPubFetchService
{
public static function get($url, $validateUrl = true)
{
public static function get($url, $validateUrl = true)
{
if($validateUrl === true) {
if(!Helpers::validateUrl($url)) {
return 0;
}
if(!Helpers::validateUrl($url)) {
return 0;
}
}
$baseHeaders = [
'Accept' => 'application/activity+json, application/ld+json',
];
$baseHeaders = [
'Accept' => 'application/activity+json, application/ld+json',
];
$headers = HttpSignature::instanceActorSign($url, false, $baseHeaders, 'get');
$headers['Accept'] = 'application/activity+json, application/ld+json';
$headers['User-Agent'] = 'PixelFedBot/1.0.0 (Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')';
$headers = HttpSignature::instanceActorSign($url, false, $baseHeaders, 'get');
$headers['Accept'] = 'application/activity+json, application/ld+json';
$headers['User-Agent'] = 'PixelFedBot/1.0.0 (Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')';
try {
$res = Http::withOptions(['allow_redirects' => false])->withHeaders($headers)
->timeout(30)
->connectTimeout(5)
->retry(3, 500)
->get($url);
} catch (RequestException $e) {
return;
} catch (ConnectionException $e) {
return;
} catch (Exception $e) {
return;
}
if(!$res->ok()) {
return;
}
return $res->body();
}
try {
$res = Http::withOptions(['allow_redirects' => false])
->withHeaders($headers)
->timeout(30)
->connectTimeout(5)
->retry(3, 500)
->get($url);
} catch (RequestException $e) {
return;
} catch (ConnectionException $e) {
return;
} catch (Exception $e) {
return;
}
if(!$res->ok()) {
return;
}
if(!$res->hasHeader('Content-Type')) {
return;
}
$acceptedTypes = [
'application/activity+json; charset=utf-8',
'application/activity+json',
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
];
$contentType = $res->getHeader('Content-Type')[0];
if(!$contentType) {
return;
}
if(!in_array($contentType, $acceptedTypes)) {
return;
}
return $res->body();
}
}

View File

@ -72,6 +72,8 @@ class ConfigCacheService
'instance.banner.blurhash',
'autospam.nlp.enabled',
'instance.curated_registration.enabled',
// 'system.user_mode'
];

View File

@ -0,0 +1,68 @@
<?php
namespace App\Services\Internal;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
class SoftwareUpdateService
{
const CACHE_KEY = 'pf:services:software-update:';
public static function get()
{
$curVersion = config('pixelfed.version');
$versions = Cache::remember(self::CACHE_KEY . 'latest:v1.0.0', 1800, function() {
return self::fetchLatest();
});
if(!$versions || !isset($versions['latest'], $versions['latest']['version'])) {
$hideWarning = (bool) config('instance.software-update.disable_failed_warning');
return [
'current' => $curVersion,
'latest' => [
'version' => null,
'published_at' => null,
'url' => null,
],
'running_latest' => $hideWarning ? true : null
];
}
return [
'current' => $curVersion,
'latest' => [
'version' => $versions['latest']['version'],
'published_at' => $versions['latest']['published_at'],
'url' => $versions['latest']['url'],
],
'running_latest' => strval($versions['latest']['version']) === strval($curVersion)
];
}
public static function fetchLatest()
{
try {
$res = Http::withOptions(['allow_redirects' => false])
->timeout(5)
->connectTimeout(5)
->retry(2, 500)
->get('https://versions.pixelfed.org/versions.json');
} catch (RequestException $e) {
return;
} catch (ConnectionException $e) {
return;
} catch (Exception $e) {
return;
}
if(!$res->ok()) {
return;
}
return $res->json();
}
}

View File

@ -48,13 +48,16 @@ class LandingService
->toArray() : [];
});
$openReg = (bool) config_cache('pixelfed.open_registration');
$res = [
'name' => config_cache('app.name'),
'url' => config_cache('app.url'),
'domain' => config('pixelfed.domain.app'),
'show_directory' => config_cache('instance.landing.show_directory'),
'show_explore_feed' => config_cache('instance.landing.show_explore'),
'open_registration' => config_cache('pixelfed.open_registration') == 1,
'open_registration' => (bool) $openReg,
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
'version' => config('pixelfed.version'),
'about' => [
'banner_image' => config_cache('app.banner_image') ?? url('/storage/headers/default.jpg'),

View File

@ -22,7 +22,10 @@ class NodeinfoService
$wk = $url . '/.well-known/nodeinfo';
try {
$res = Http::withHeaders($headers)
$res = Http::withOptions([
'allow_redirects' => false,
])
->withHeaders($headers)
->timeout(5)
->get($wk);
} catch (RequestException $e) {
@ -61,7 +64,10 @@ class NodeinfoService
}
try {
$res = Http::withHeaders($headers)
$res = Http::withOptions([
'allow_redirects' => false,
])
->withHeaders($headers)
->timeout(5)
->get($href);
} catch (RequestException $e) {

View File

@ -39,10 +39,9 @@ use App\Jobs\HomeFeedPipeline\FeedInsertRemotePipeline;
use App\Util\Media\License;
use App\Models\Poll;
use Illuminate\Contracts\Cache\LockTimeoutException;
use App\Jobs\ProfilePipeline\IncrementPostCount;
use App\Jobs\ProfilePipeline\DecrementPostCount;
use App\Services\DomainService;
use App\Services\UserFilterService;
use App\Services\Account\AccountStatService;
class Helpers {
@ -316,6 +315,23 @@ class Helpers {
return;
}
if(config('autospam.live_filters.enabled')) {
$filters = config('autospam.live_filters.filters');
if(!empty($filters) && isset($res['content']) && !empty($res['content']) && strlen($filters) > 3) {
$filters = array_map('trim', explode(',', $filters));
$content = $res['content'];
foreach($filters as $filter) {
$filter = trim(strtolower($filter));
if(!$filter || !strlen($filter)) {
continue;
}
if(str_contains(strtolower($content), $filter)) {
return;
}
}
}
}
if(isset($res['object'])) {
$activity = $res;
} else {
@ -373,6 +389,10 @@ class Helpers {
$idDomain = parse_url($id, PHP_URL_HOST);
$urlDomain = parse_url($url, PHP_URL_HOST);
if($idDomain && $urlDomain && strtolower($idDomain) !== strtolower($urlDomain)) {
return;
}
if(!self::validateUrl($id)) {
return;
}
@ -456,14 +476,21 @@ class Helpers {
public static function storeStatus($url, $profile, $activity)
{
$originalUrl = $url;
$id = isset($activity['id']) ? self::pluckval($activity['id']) : self::pluckval($activity['url']);
$url = isset($activity['url']) && is_string($activity['url']) ? self::pluckval($activity['url']) : self::pluckval($id);
$idDomain = parse_url($id, PHP_URL_HOST);
$urlDomain = parse_url($url, PHP_URL_HOST);
$originalUrlDomain = parse_url($originalUrl, PHP_URL_HOST);
if(!self::validateUrl($id) || !self::validateUrl($url)) {
return;
}
if( strtolower($originalUrlDomain) !== strtolower($idDomain) ||
strtolower($originalUrlDomain) !== strtolower($urlDomain) ) {
return;
}
$reply_to = self::getReplyTo($activity);
$ts = self::pluckval($activity['published']);
@ -536,7 +563,7 @@ class Helpers {
}
}
IncrementPostCount::dispatch($pid)->onQueue('low');
AccountStatService::incrementPostCount($pid);
if( $status->in_reply_to_id === null &&
in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
@ -549,10 +576,11 @@ class Helpers {
public static function getSensitive($activity, $url)
{
$id = isset($activity['id']) ? self::pluckval($activity['id']) : self::pluckval($url);
$url = isset($activity['url']) ? self::pluckval($activity['url']) : $id;
$urlDomain = parse_url($url, PHP_URL_HOST);
if(!$url || !strlen($url)) {
return true;
}
$urlDomain = parse_url($url, PHP_URL_HOST);
$cw = isset($activity['sensitive']) ? (bool) $activity['sensitive'] : false;
if(in_array($urlDomain, InstanceService::getNsfwDomains())) {
@ -763,7 +791,11 @@ class Helpers {
if(!$res || isset($res['id']) == false) {
return;
}
$urlDomain = parse_url($url, PHP_URL_HOST);
$domain = parse_url($res['id'], PHP_URL_HOST);
if(strtolower($urlDomain) !== strtolower($domain)) {
return;
}
if(!isset($res['preferredUsername']) && !isset($res['nickname'])) {
return;
}
@ -831,6 +863,9 @@ class Helpers {
public static function sendSignedObject($profile, $url, $body)
{
if(app()->environment() !== 'production') {
return;
}
ActivityPubDeliveryService::queue()
->from($profile)
->to($url)

View File

@ -48,8 +48,6 @@ use App\Services\UserFilterService;
use App\Services\NetworkTimelineService;
use App\Models\Conversation;
use App\Models\RemoteReport;
use App\Jobs\ProfilePipeline\IncrementPostCount;
use App\Jobs\ProfilePipeline\DecrementPostCount;
use App\Jobs\HomeFeedPipeline\FeedRemoveRemotePipeline;
class Inbox
@ -199,6 +197,22 @@ class Inbox
public function handleCreateActivity()
{
$activity = $this->payload['object'];
if(config('autospam.live_filters.enabled')) {
$filters = config('autospam.live_filters.filters');
if(!empty($filters) && isset($activity['content']) && !empty($activity['content']) && strlen($filters) > 3) {
$filters = array_map('trim', explode(',', $filters));
$content = $activity['content'];
foreach($filters as $filter) {
$filter = trim(strtolower($filter));
if(!$filter || !strlen($filter)) {
continue;
}
if(str_contains(strtolower($content), $filter)) {
return;
}
}
}
}
$actor = $this->actorFirstOrCreate($this->payload['actor']);
if(!$actor || $actor->domain == null) {
return;

View File

@ -5,7 +5,7 @@
"license": "AGPL-3.0-only",
"type": "project",
"require": {
"php": "^8.1|^8.2",
"php": "^8.1|^8.2|^8.3",
"ext-bcmath": "*",
"ext-ctype": "*",
"ext-curl": "*",

1078
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -33,5 +33,10 @@ return [
'nlp' => [
'enabled' => false,
'spam_sample_limit' => env('PF_AUTOSPAM_NLP_SPAM_SAMPLE_LIMIT', 200),
],
'live_filters' => [
'enabled' => env('PF_AUTOSPAM_LIVE_FILTERS_ENABLED', false),
'filters' => env('PF_AUTOSPAM_LIVE_FILTERS_CSV', ''),
]
];

View File

@ -74,7 +74,7 @@ return [
'redis' => [
'driver' => 'redis',
'lock_connection' => 'default',
'client' => env('REDIS_CLIENT', 'phpredis'),
'client' => env('REDIS_CLIENT', 'predis'),
'default' => [
'scheme' => env('REDIS_SCHEME', 'tcp'),

View File

@ -49,7 +49,7 @@ return [
],
'network_timeline' => env('PF_NETWORK_TIMELINE', true),
'network_timeline_days_falloff' => env('PF_NETWORK_TIMELINE_DAYS_FALLOFF', 2),
'network_timeline_days_falloff' => env('PF_NETWORK_TIMELINE_DAYS_FALLOFF', 90),
'custom_emoji' => [
'enabled' => env('CUSTOM_EMOJI', false),

View File

@ -2,201 +2,202 @@
return [
/*
|--------------------------------------------------------------------------
| Horizon Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Horizon will be accessible from. If this
| setting is null, Horizon will reside under the same domain as the
| application. Otherwise, this value will serve as the subdomain.
|
*/
/*
|--------------------------------------------------------------------------
| Horizon Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Horizon will be accessible from. If this
| setting is null, Horizon will reside under the same domain as the
| application. Otherwise, this value will serve as the subdomain.
|
*/
'domain' => null,
'domain' => null,
/*
|--------------------------------------------------------------------------
| Horizon Path
|--------------------------------------------------------------------------
|
| This is the URI path where Horizon will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
/*
|--------------------------------------------------------------------------
| Horizon Path
|--------------------------------------------------------------------------
|
| This is the URI path where Horizon will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
'path' => 'horizon',
'path' => 'horizon',
/*
|--------------------------------------------------------------------------
| Horizon Redis Connection
|--------------------------------------------------------------------------
|
| This is the name of the Redis connection where Horizon will store the
| meta information required for it to function. It includes the list
| of supervisors, failed jobs, job metrics, and other information.
|
*/
/*
|--------------------------------------------------------------------------
| Horizon Redis Connection
|--------------------------------------------------------------------------
|
| This is the name of the Redis connection where Horizon will store the
| meta information required for it to function. It includes the list
| of supervisors, failed jobs, job metrics, and other information.
|
*/
'use' => 'default',
'use' => 'default',
/*
|--------------------------------------------------------------------------
| Horizon Redis Prefix
|--------------------------------------------------------------------------
|
| This prefix will be used when storing all Horizon data in Redis. You
| may modify the prefix when you are running multiple installations
| of Horizon on the same server so that they don't have problems.
|
*/
/*
|--------------------------------------------------------------------------
| Horizon Redis Prefix
|--------------------------------------------------------------------------
|
| This prefix will be used when storing all Horizon data in Redis. You
| may modify the prefix when you are running multiple installations
| of Horizon on the same server so that they don't have problems.
|
*/
'prefix' => env('HORIZON_PREFIX', 'horizon-'),
'prefix' => env('HORIZON_PREFIX', 'horizon-'),
/*
|--------------------------------------------------------------------------
| Horizon Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will get attached onto each Horizon route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
/*
|--------------------------------------------------------------------------
| Horizon Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will get attached onto each Horizon route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => ['web'],
'middleware' => ['web'],
/*
|--------------------------------------------------------------------------
| Queue Wait Time Thresholds
|--------------------------------------------------------------------------
|
| This option allows you to configure when the LongWaitDetected event
| will be fired. Every connection / queue combination may have its
| own, unique threshold (in seconds) before this event is fired.
|
*/
/*
|--------------------------------------------------------------------------
| Queue Wait Time Thresholds
|--------------------------------------------------------------------------
|
| This option allows you to configure when the LongWaitDetected event
| will be fired. Every connection / queue combination may have its
| own, unique threshold (in seconds) before this event is fired.
|
*/
'waits' => [
'redis:feed' => 30,
'redis:follow' => 30,
'redis:shared' => 30,
'redis:default' => 30,
'redis:inbox' => 30,
'redis:low' => 30,
'redis:high' => 30,
'redis:delete' => 30,
'redis:story' => 30,
'redis:mmo' => 30,
],
'waits' => [
'redis:feed' => 30,
'redis:follow' => 30,
'redis:shared' => 30,
'redis:default' => 30,
'redis:inbox' => 30,
'redis:low' => 30,
'redis:high' => 30,
'redis:delete' => 30,
'redis:story' => 30,
'redis:mmo' => 30,
'redis:intbg' => 30,
],
/*
|--------------------------------------------------------------------------
| Job Trimming Times
|--------------------------------------------------------------------------
|
| Here you can configure for how long (in minutes) you desire Horizon to
| persist the recent and failed jobs. Typically, recent jobs are kept
| for one hour while all failed jobs are stored for an entire week.
|
*/
/*
|--------------------------------------------------------------------------
| Job Trimming Times
|--------------------------------------------------------------------------
|
| Here you can configure for how long (in minutes) you desire Horizon to
| persist the recent and failed jobs. Typically, recent jobs are kept
| for one hour while all failed jobs are stored for an entire week.
|
*/
'trim' => [
'recent' => 60,
'pending' => 60,
'completed' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
],
'trim' => [
'recent' => 60,
'pending' => 60,
'completed' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
],
/*
|--------------------------------------------------------------------------
| Metrics
|--------------------------------------------------------------------------
|
| Here you can configure how many snapshots should be kept to display in
| the metrics graph. This will get used in combination with Horizon's
| `horizon:snapshot` schedule to define how long to retain metrics.
|
*/
/*
|--------------------------------------------------------------------------
| Metrics
|--------------------------------------------------------------------------
|
| Here you can configure how many snapshots should be kept to display in
| the metrics graph. This will get used in combination with Horizon's
| `horizon:snapshot` schedule to define how long to retain metrics.
|
*/
'metrics' => [
'trim_snapshots' => [
'job' => 24,
'queue' => 24,
],
],
'metrics' => [
'trim_snapshots' => [
'job' => 24,
'queue' => 24,
],
],
/*
|--------------------------------------------------------------------------
| Fast Termination
|--------------------------------------------------------------------------
|
| When this option is enabled, Horizon's "terminate" command will not
| wait on all of the workers to terminate unless the --wait option
| is provided. Fast termination can shorten deployment delay by
| allowing a new instance of Horizon to start while the last
| instance will continue to terminate each of its workers.
|
*/
/*
|--------------------------------------------------------------------------
| Fast Termination
|--------------------------------------------------------------------------
|
| When this option is enabled, Horizon's "terminate" command will not
| wait on all of the workers to terminate unless the --wait option
| is provided. Fast termination can shorten deployment delay by
| allowing a new instance of Horizon to start while the last
| instance will continue to terminate each of its workers.
|
*/
'fast_termination' => false,
'fast_termination' => false,
/*
|--------------------------------------------------------------------------
| Memory Limit (MB)
|--------------------------------------------------------------------------
|
| This value describes the maximum amount of memory the Horizon worker
| may consume before it is terminated and restarted. You should set
| this value according to the resources available to your server.
|
*/
/*
|--------------------------------------------------------------------------
| Memory Limit (MB)
|--------------------------------------------------------------------------
|
| This value describes the maximum amount of memory the Horizon worker
| may consume before it is terminated and restarted. You should set
| this value according to the resources available to your server.
|
*/
'memory_limit' => env('HORIZON_MEMORY_LIMIT', 64),
'memory_limit' => env('HORIZON_MEMORY_LIMIT', 64),
/*
|--------------------------------------------------------------------------
| Queue Worker Configuration
|--------------------------------------------------------------------------
|
| Here you may define the queue worker settings used by your application
| in all environments. These supervisors and settings handle all your
| queued jobs and will be provisioned by Horizon during deployment.
|
*/
/*
|--------------------------------------------------------------------------
| Queue Worker Configuration
|--------------------------------------------------------------------------
|
| Here you may define the queue worker settings used by your application
| in all environments. These supervisors and settings handle all your
| queued jobs and will be provisioned by Horizon during deployment.
|
*/
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'follow', 'shared', 'inbox', 'feed', 'low', 'story', 'delete', 'mmo'],
'balance' => env('HORIZON_BALANCE_STRATEGY', 'auto'),
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 20),
'memory' => env('HORIZON_SUPERVISOR_MEMORY', 64),
'tries' => env('HORIZON_SUPERVISOR_TRIES', 3),
'nice' => env('HORIZON_SUPERVISOR_NICE', 0),
'timeout' => env('HORIZON_SUPERVISOR_TIMEOUT', 300),
],
],
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'follow', 'shared', 'inbox', 'feed', 'low', 'story', 'delete', 'mmo', 'intbg'],
'balance' => env('HORIZON_BALANCE_STRATEGY', 'auto'),
'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 20),
'memory' => env('HORIZON_SUPERVISOR_MEMORY', 64),
'tries' => env('HORIZON_SUPERVISOR_TRIES', 3),
'nice' => env('HORIZON_SUPERVISOR_NICE', 0),
'timeout' => env('HORIZON_SUPERVISOR_TIMEOUT', 300),
],
],
'local' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'follow', 'shared', 'inbox', 'feed', 'low', 'story', 'delete', 'mmo'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 20,
'memory' => 128,
'tries' => 3,
'nice' => 0,
'timeout' => 300
],
],
],
'local' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default', 'follow', 'shared', 'inbox', 'feed', 'low', 'story', 'delete', 'mmo', 'intbg'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 20,
'memory' => 128,
'tries' => 3,
'nice' => 0,
'timeout' => 300
],
],
],
'darkmode' => env('HORIZON_DARKMODE', false),
'darkmode' => env('HORIZON_DARKMODE', false),
];

View File

@ -29,11 +29,12 @@ return [
],
'local' => [
'cached' => env('INSTANCE_PUBLIC_TIMELINE_CACHED', false),
'is_public' => env('INSTANCE_PUBLIC_LOCAL_TIMELINE', false)
],
'network' => [
'cached' => env('PF_NETWORK_TIMELINE') ? env('INSTANCE_NETWORK_TIMELINE_CACHED', true) : false,
'cached' => env('PF_NETWORK_TIMELINE') ? env('INSTANCE_NETWORK_TIMELINE_CACHED', false) : false,
'cache_dropoff' => env('INSTANCE_NETWORK_TIMELINE_CACHE_DROPOFF', 100),
'max_hours_old' => env('INSTANCE_NETWORK_TIMELINE_CACHE_MAX_HOUR_INGEST', 6)
]
@ -139,5 +140,40 @@ return [
'max_children' => env('INSTANCE_PARENTAL_CONTROLS_MAX_CHILDREN', 1),
'auto_verify_email' => true,
],
]
],
'software-update' => [
'disable_failed_warning' => env('INSTANCE_SOFTWARE_UPDATE_DISABLE_FAILED_WARNING', false)
],
'notifications' => [
'gc' => [
'enabled' => env('INSTANCE_NOTIFY_AUTO_GC', false),
'delete_after_days' => env('INSTANCE_NOTIFY_AUTO_GC_DEL_AFTER_DAYS', 365)
]
],
'curated_registration' => [
'enabled' => env('INSTANCE_CUR_REG', false),
'resend_confirmation_limit' => env('INSTANCE_CUR_REG_RESEND_LIMIT', 5),
'captcha_enabled' => env('INSTANCE_CUR_REG_CAPTCHA', env('CAPTCHA_ENABLED', false)),
'state' => [
'fallback_on_closed_reg' => true,
'only_enabled_on_closed_reg' => env('INSTANCE_CUR_REG_STATE_ONLY_ON_CLOSED', true),
],
'notify' => [
'admin' => [
'on_verify_email' => [
'enabled' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_VERIFY', false),
'bundle' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_VERIFY_BUNDLE', false),
'max_per_day' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_VERIFY_MPD', 10),
],
'on_user_response' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_USER_RESPONSE', false),
]
],
],
];

View File

@ -23,7 +23,7 @@ return [
| This value is the version of your Pixelfed instance.
|
*/
'version' => '0.11.9',
'version' => '0.11.12',
/*
|--------------------------------------------------------------------------

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('instances', function (Blueprint $table) {
$table->boolean('active_deliver')->nullable()->index()->after('domain');
$table->boolean('valid_nodeinfo')->nullable();
$table->timestamp('nodeinfo_last_fetched')->nullable();
$table->boolean('delivery_timeout')->default(false);
$table->timestamp('delivery_next_after')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('instances', function (Blueprint $table) {
$table->dropColumn('active_deliver');
$table->dropColumn('valid_nodeinfo');
$table->dropColumn('nodeinfo_last_fetched');
$table->dropColumn('delivery_timeout');
$table->dropColumn('delivery_next_after');
});
}
};

View File

@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('curated_registers', function (Blueprint $table) {
$table->id();
$table->string('email')->unique()->nullable()->index();
$table->string('username')->unique()->nullable()->index();
$table->string('password')->nullable();
$table->string('ip_address')->nullable();
$table->string('verify_code')->nullable();
$table->text('reason_to_join')->nullable();
$table->unsignedBigInteger('invited_by')->nullable()->index();
$table->boolean('is_approved')->default(0)->index();
$table->boolean('is_rejected')->default(0)->index();
$table->boolean('is_awaiting_more_info')->default(0)->index();
$table->boolean('is_closed')->default(0)->index();
$table->json('autofollow_account_ids')->nullable();
$table->json('admin_notes')->nullable();
$table->unsignedInteger('approved_by_admin_id')->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->timestamp('admin_notified_at')->nullable();
$table->timestamp('action_taken_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('curated_registers');
}
};

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('curated_register_activities', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('register_id')->nullable()->index();
$table->unsignedInteger('admin_id')->nullable();
$table->unsignedInteger('reply_to_id')->nullable()->index();
$table->string('secret_code')->nullable();
$table->string('type')->nullable()->index();
$table->string('title')->nullable();
$table->string('link')->nullable();
$table->text('message')->nullable();
$table->json('metadata')->nullable();
$table->boolean('from_admin')->default(false)->index();
$table->boolean('from_user')->default(false)->index();
$table->boolean('admin_only_view')->default(true);
$table->boolean('action_required')->default(false);
$table->timestamp('admin_notified_at')->nullable();
$table->timestamp('action_taken_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('curated_register_activities');
}
};

1543
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
},
"devDependencies": {
"acorn": "^8.7.1",
"axios": "^0.21.1",
"axios": ">=1.6.0",
"bootstrap": "^4.5.2",
"cross-env": "^5.2.1",
"jquery": "^3.6.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/admin.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
"use strict";(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[9008],{23331:(e,t,o)=>{o.r(t);var a=o(70538),l=o(25518),n=o(30306),r=o.n(n),s=o(16654),d=o.n(s),c=o(92987),i=o(37409),u=o.n(i),f=o(74870),h=o.n(f),m=(o(86368),o(46737),o(19755));function p(e){return p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p(e)}window.Vue=a.default,a.default.use(h()),a.default.use(u()),a.default.use(l.default),a.default.use(r()),a.default.use(d()),a.default.use(c.default,{name:"Timeago",locale:"en"}),pixelfed.readmore=function(){m(".read-more").each((function(e,t){var o=m(this),a=o.attr("data-readmore");"undefined"!==p(a)&&!1!==a||o.readmore({collapsedHeight:45,heightMargin:48,moreLink:'<a href="#" class="d-block small font-weight-bold text-dark text-center">Show more</a>',lessLink:'<a href="#" class="d-block small font-weight-bold text-dark text-center">Show less</a>'})}))};try{document.createEvent("TouchEvent"),m("body").addClass("touch")}catch(e){}window.filesize=o(42317),m('[data-toggle="tooltip"]').tooltip();console.log("%cStop!","color:red; font-size:60px; font-weight: bold; -webkit-text-stroke: 1px black;"),console.log('%cThis is a browser feature intended for developers. If someone told you to copy and paste something here to enable a Pixelfed feature or "hack" someone\'s account, it is a scam and will give them access to your Pixelfed account.',"font-size: 18px;")}},e=>{e.O(0,[8898],(()=>{return t=23331,e(e.s=t);var t}));e.O()}]);
"use strict";(self.webpackChunkpixelfed=self.webpackChunkpixelfed||[]).push([[4774],{92182:(e,t,o)=>{o.r(t);var a=o(62893),l=o(63288),n=o(32252),r=o.n(n),s=o(65201),d=o.n(s),c=o(24786),i=o(57742),u=o.n(i),f=o(89829),h=o.n(f),m=(o(34352),o(80158),o(74692));function p(e){return p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},p(e)}window.Vue=a.default,a.default.use(h()),a.default.use(u()),a.default.use(l.default),a.default.use(r()),a.default.use(d()),a.default.use(c.default,{name:"Timeago",locale:"en"}),pixelfed.readmore=function(){m(".read-more").each((function(e,t){var o=m(this),a=o.attr("data-readmore");"undefined"!==p(a)&&!1!==a||o.readmore({collapsedHeight:45,heightMargin:48,moreLink:'<a href="#" class="d-block small font-weight-bold text-dark text-center">Show more</a>',lessLink:'<a href="#" class="d-block small font-weight-bold text-dark text-center">Show less</a>'})}))};try{document.createEvent("TouchEvent"),m("body").addClass("touch")}catch(e){}window.filesize=o(91139),m('[data-toggle="tooltip"]').tooltip();console.log("%cStop!","color:red; font-size:60px; font-weight: bold; -webkit-text-stroke: 1px black;"),console.log('%cThis is a browser feature intended for developers. If someone told you to copy and paste something here to enable a Pixelfed feature or "hack" someone\'s account, it is a scam and will give them access to your Pixelfed account.',"font-size: 18px;")}},e=>{e.O(0,[3660],(()=>{return t=92182,e(e.s=t);var t}));e.O()}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/direct.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More