Skip to content

Commit d4a4aa3

Browse files
authored
Merge pull request #35 from Kovah/dev
v0.0.7
2 parents e0769d5 + 7cbdb1a commit d4a4aa3

31 files changed

+1093
-175
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/storage/*.key
55
/vendor
66
/.idea
7+
/.tmp
78
/.vscode
89
/.vagrant
910
Homestead.json

app/Console/CheckLinksCommand.php

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use App\Models\Link;
6+
use GuzzleHttp\Client;
7+
use Illuminate\Console\Command;
8+
use Illuminate\Support\Facades\Cache;
9+
10+
/**
11+
* Class CheckLinksCommand
12+
*
13+
* @package App\Console\Commands
14+
*/
15+
class CheckLinksCommand extends Command
16+
{
17+
/**
18+
* The name and signature of the console command.
19+
*
20+
* @var string
21+
*/
22+
protected $signature = 'links:check';
23+
24+
/**
25+
* The console command description.
26+
*
27+
* @var string
28+
*/
29+
protected $description = 'Checks links for availablility';
30+
31+
/**
32+
* Check a maximum of 100 links at once
33+
*
34+
* @var int
35+
*/
36+
protected $limit = 100;
37+
38+
/** @var int */
39+
protected $offset;
40+
41+
/** @var int */
42+
protected $total;
43+
44+
/** @var int */
45+
protected $checked_link_count;
46+
47+
/** @var Client */
48+
protected $client;
49+
50+
/** @var string */
51+
protected $cache_key_offset = 'command_links:check_offset';
52+
53+
/** @var string */
54+
protected $cache_key_skip_timestamp = 'command_links:check_skip_timestamp';
55+
56+
/** @var string */
57+
protected $cache_key_checked_count = 'command_links:check_checked_count';
58+
59+
/**
60+
* RegisterUser constructor.
61+
*/
62+
public function __construct()
63+
{
64+
$this->client = new Client();
65+
66+
parent::__construct();
67+
}
68+
69+
/**
70+
* Execute the console command.
71+
*
72+
* @return void
73+
*/
74+
public function handle(): void
75+
{
76+
// Check if the command should skip the execution
77+
$skip_timestamp = Cache::get($this->cache_key_skip_timestamp);
78+
$this->offset = Cache::get($this->cache_key_offset, 0);
79+
$this->checked_link_count = Cache::get($this->cache_key_checked_count, 0);
80+
81+
if (now()->timestamp < $skip_timestamp) {
82+
return;
83+
}
84+
85+
$links = $this->getLinks();
86+
87+
// Cancel if there are no links to check
88+
if ($links->isEmpty()) {
89+
Cache::forget($this->cache_key_offset);
90+
Cache::forget($this->cache_key_skip_timestamp);
91+
92+
$this->comment('No links found, aborting...');
93+
return;
94+
}
95+
96+
// Check all provided links
97+
$this->comment('Checking ' . $links->count() . ' links now.');
98+
99+
$links->each(function ($link) {
100+
$this->checkLink($link);
101+
102+
// Prevent spam-ish behaviour by limiting outgoing HTTP requests
103+
sleep(1);
104+
});
105+
106+
// Check if there are more links to check
107+
$checked_count = $this->checked_link_count + $links->count();
108+
Cache::forever($this->cache_key_checked_count, $checked_count);
109+
110+
if ($this->total > $checked_count) {
111+
112+
// If yes, simply save the offset to the cache.
113+
// The next link check will pick it up and continue the check
114+
$next_offset = $this->offset + $this->limit;
115+
Cache::forever($this->cache_key_offset, $next_offset);
116+
117+
$this->comment('Saving offset for next link check.');
118+
119+
} else {
120+
121+
// If not, all links have been successfully checked.
122+
// Save a cache flag that prevents link checks for the next days.
123+
$next_check = now()->addDays(5)->timestamp;
124+
Cache::forever($this->cache_key_skip_timestamp, $next_check);
125+
126+
$this->comment('All existing links checked, next link check scheduled for ' . now()->addDays(5)->toDateTimeString());
127+
128+
}
129+
}
130+
131+
/**
132+
* Get links but limit the results to a fixed number of links
133+
* If there is an offset saved, use this instead of beginning from the first entry
134+
*
135+
* @return mixed
136+
*/
137+
protected function getLinks()
138+
{
139+
// Get the total amount of remaining links
140+
$this->total = Link::count();
141+
142+
// Get a porton of the remaining links based on the limit
143+
return Link::orderBy('id', 'ASC')->offset($this->offset)->limit($this->limit)->get();
144+
}
145+
146+
/**
147+
* Check the URL of an link and set the status accordingly
148+
*
149+
* @param Link $link
150+
* @return void
151+
* @throws \GuzzleHttp\Exception\GuzzleException
152+
*/
153+
protected function checkLink(Link $link): void
154+
{
155+
$this->comment('Checking link ' . $link->url);
156+
157+
$options = [
158+
'http_errors' => false, // Do not throw exceptions for 4xx and 5xx errors
159+
'timeout' => 5, // wait a maximum of 5 seconds for the request to finish
160+
];
161+
162+
try {
163+
$res = $this->client->request('GET', $link->url, $options);
164+
$status_code = $res->getStatusCode();
165+
} catch (\Exception $e) {
166+
// Just abort now, may be a temporary issue
167+
$this->warn('› Unknown error while trying to check the URL, trying again later.');
168+
return;
169+
}
170+
171+
// Check if the status code is not 200
172+
if ($status_code !== 200) {
173+
// If the link target is a redirect, set the status 2 (moved)
174+
// else set the status to 3 (broken)
175+
if ($status_code === 301 || $status_code === 302) {
176+
$link->status = 2;
177+
$this->warn('› Link moved to another URL!');
178+
} else {
179+
$link->status = 3;
180+
$this->error('› Link seems to be broken!');
181+
}
182+
183+
$link->save();
184+
185+
} else {
186+
$this->info('› Link looks okay.');
187+
}
188+
}
189+
}

app/Console/Kernel.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22

33
namespace App\Console;
44

5-
use App\Console\Commands\RegisterUser;
5+
use App\Console\Commands\CheckLinksCommand;
6+
use App\Console\Commands\RegisterUserCommand;
67
use Illuminate\Console\Scheduling\Schedule;
78
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
89

10+
/**
11+
* Class Kernel
12+
*
13+
* @package App\Console
14+
*/
915
class Kernel extends ConsoleKernel
1016
{
1117
/**
@@ -14,7 +20,8 @@ class Kernel extends ConsoleKernel
1420
* @var array
1521
*/
1622
protected $commands = [
17-
RegisterUser::class
23+
RegisterUserCommand::class,
24+
CheckLinksCommand::class,
1825
];
1926

2027
/**
@@ -25,8 +32,7 @@ class Kernel extends ConsoleKernel
2532
*/
2633
protected function schedule(Schedule $schedule)
2734
{
28-
// $schedule->command('inspire')
29-
// ->hourly();
35+
$schedule->command('links:check')->hourly();
3036
}
3137

3238
/**

app/Console/RegisterUser.php renamed to app/Console/RegisterUserCommand.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
*
1515
* @package App\Console\Commands
1616
*/
17-
class RegisterUser extends Command
17+
class RegisterUserCommand extends Command
1818
{
1919
/**
2020
* The name and signature of the console command.
2121
*
2222
* @var string
2323
*/
24-
protected $signature = 'registeruser {name : Username} {email : User email address}';
24+
protected $signature = 'registeruser {name? : Username} {email? : User email address}';
2525

2626
/**
2727
* The console command description.
@@ -30,6 +30,11 @@ class RegisterUser extends Command
3030
*/
3131
protected $description = 'Register a new user';
3232

33+
private $user_roles = [
34+
'admin',
35+
'user',
36+
];
37+
3338
/**
3439
* RegisterUser constructor.
3540
*/
@@ -49,29 +54,31 @@ public function handle()
4954
$email = $this->argument('email');
5055

5156
if (empty($name)) {
52-
$this->warn('Name is missing!');
53-
return;
57+
$name = $this->ask('Please enter the user name:');
5458
}
5559

5660
if (empty($email)) {
57-
$this->warn('Email address is missing!');
58-
return;
61+
$email = $this->ask('Please enter the user email address:');
5962
}
6063

61-
$password = $this->secret('Please enter a password for ' . $name);
62-
6364
// Check if the user exists
6465
if (User::where('email', $email)->first()) {
6566
$this->error('An user with the email address "' . $email . '" already exists!');
6667
return;
6768
}
6869

70+
$password = $this->secret('Please enter a password for ' . $name);
71+
72+
$role = $this->choice('Please choose the user role:', $this->user_roles, 'user');
73+
6974
$new_user = User::create([
7075
'name' => $name,
7176
'email' => $email,
7277
'password' => Hash::make($password),
7378
]);
7479

80+
$new_user->assignRole($role);
81+
7582
$this->info('User ' . $name . ' registered.');
7683
return;
7784
}

app/Helper/functions.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
use App\Models\Setting;
4+
35
/**
46
* Shorthand for the current user settings
57
*
@@ -19,6 +21,21 @@ function usersettings(string $key = '')
1921
return auth()->user()->settings();
2022
}
2123

24+
/**
25+
* Retrieve system settings
26+
*
27+
* @param string $key
28+
* @return mixed
29+
*/
30+
function systemsettings(string $key = '')
31+
{
32+
if ($key === '') {
33+
return Setting::systemOnly()->get();
34+
}
35+
36+
return Setting::systemOnly()->where('key', $key)->pluck('value')->first() ?: null;
37+
}
38+
2239
/**
2340
* Output a correctly formatted date with the correct timezone
2441
*
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\API;
4+
5+
use App\Http\Controllers\Controller;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\Artisan;
8+
9+
/**
10+
* Class CronController
11+
*
12+
* @package App\Http\Controllers
13+
*/
14+
class CronController extends Controller
15+
{
16+
/**
17+
* @param Request $request
18+
* @param null|string $cron_token
19+
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
20+
*/
21+
public function run(Request $request, $cron_token)
22+
{
23+
// Verify the cron token
24+
if (!$cron_token || $cron_token !== systemsettings('cron_token')) {
25+
abort(403);
26+
}
27+
28+
// Run all cron tasks
29+
Artisan::call('schedule:run');
30+
31+
return response('Cron successfully executed');
32+
}
33+
}

0 commit comments

Comments
 (0)