Incorporate HTTP endpoint logic so we can now do websockets or HTTP endpoints
This commit is contained in:
parent
b0c3897e45
commit
6b16d07d80
21
src/Base.php
21
src/Base.php
@ -6,6 +6,11 @@ use Illuminate\Http\Request;
|
|||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Slack\Models\{Channel,Enterprise,Team,User};
|
use Slack\Models\{Channel,Enterprise,Team,User};
|
||||||
|
|
||||||
|
use App\Models\Channel as AppChannel;
|
||||||
|
use App\Models\Enterprise as AppEnterprise;
|
||||||
|
use App\Models\Team as AppTeam;
|
||||||
|
use App\Models\User as AppUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Base - is a Base to all incoming Slack POST requests
|
* Class Base - is a Base to all incoming Slack POST requests
|
||||||
*
|
*
|
||||||
@ -43,7 +48,9 @@ abstract class Base
|
|||||||
*/
|
*/
|
||||||
final public function channel(bool $create=FALSE): ?Channel
|
final public function channel(bool $create=FALSE): ?Channel
|
||||||
{
|
{
|
||||||
$o = Channel::firstOrNew(
|
$class = class_exists(AppChannel::class) ? AppChannel::class : Channel::class;
|
||||||
|
|
||||||
|
$o = $class::firstOrNew(
|
||||||
[
|
[
|
||||||
'channel_id'=>$this->channel_id,
|
'channel_id'=>$this->channel_id,
|
||||||
]);
|
]);
|
||||||
@ -59,7 +66,9 @@ abstract class Base
|
|||||||
|
|
||||||
final public function enterprise(): Enterprise
|
final public function enterprise(): Enterprise
|
||||||
{
|
{
|
||||||
return Enterprise::firstOrNew(
|
$class = class_exists(AppEnterprise::class) ? AppEnterprise::class : Enterprise::class;
|
||||||
|
|
||||||
|
return $class::firstOrNew(
|
||||||
[
|
[
|
||||||
'enterprise_id'=>$this->enterprise_id
|
'enterprise_id'=>$this->enterprise_id
|
||||||
]);
|
]);
|
||||||
@ -73,7 +82,9 @@ abstract class Base
|
|||||||
*/
|
*/
|
||||||
final public function team(bool $any=FALSE): ?Team
|
final public function team(bool $any=FALSE): ?Team
|
||||||
{
|
{
|
||||||
$o = Team::firstOrNew(
|
$class = class_exists(AppTeam::class) ? AppTeam::class : Team::class;
|
||||||
|
|
||||||
|
$o = $class::firstOrNew(
|
||||||
[
|
[
|
||||||
'team_id'=>$this->team_id
|
'team_id'=>$this->team_id
|
||||||
]);
|
]);
|
||||||
@ -94,7 +105,9 @@ abstract class Base
|
|||||||
*/
|
*/
|
||||||
final public function user(): User
|
final public function user(): User
|
||||||
{
|
{
|
||||||
$o = User::firstOrNew(
|
$class = class_exists(AppUser::class) ? AppUser::class : User::class;
|
||||||
|
|
||||||
|
$o = $class::firstOrNew(
|
||||||
[
|
[
|
||||||
'user_id'=>$this->user_id,
|
'user_id'=>$this->user_id,
|
||||||
]);
|
]);
|
||||||
|
@ -17,9 +17,9 @@ class Payload implements \ArrayAccess, \JsonSerializable
|
|||||||
*
|
*
|
||||||
* @param array $data The payload data.
|
* @param array $data The payload data.
|
||||||
*/
|
*/
|
||||||
public function __construct(array $data)
|
public function __construct(array $data,bool $key=FALSE)
|
||||||
{
|
{
|
||||||
$this->data = $data;
|
$this->data = $key ? ['payload'=>$data ] : $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,14 +15,14 @@ class Factory {
|
|||||||
* @var array event type to event class mapping
|
* @var array event type to event class mapping
|
||||||
*/
|
*/
|
||||||
public const map = [
|
public const map = [
|
||||||
'app_home_opened'=>AppHomeOpened::class,
|
'app_home_opened' => AppHomeOpened::class,
|
||||||
'member_joined_channel'=>MemberJoinedChannel::class,
|
'member_joined_channel' => MemberJoinedChannel::class,
|
||||||
'channel_left'=>ChannelLeft::class,
|
'channel_left' => ChannelLeft::class,
|
||||||
'group_left'=>GroupLeft::class,
|
'group_left' => GroupLeft::class,
|
||||||
'message'=>Message::class,
|
'message' => Message::class,
|
||||||
'reaction_added'=>ReactionAdded::class,
|
'reaction_added' => ReactionAdded::class,
|
||||||
'pin_added'=>PinAdded::class,
|
'pin_added' => PinAdded::class,
|
||||||
'pin_removed'=>PinRemoved::class,
|
'pin_removed' => PinRemoved::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
30
src/Http/Controllers/EventsController.php
Normal file
30
src/Http/Controllers/EventsController.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Client\Payload;
|
||||||
|
use Slack\Event\Factory as SlackEventFactory;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class EventsController extends Controller
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'CEC';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire slack event
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\Response|\Laravel\Lumen\Http\ResponseFactory
|
||||||
|
*/
|
||||||
|
public function fire(Request $request)
|
||||||
|
{
|
||||||
|
$event = SlackEventFactory::make(new Payload($request->all(),TRUE));
|
||||||
|
Log::info(sprintf('%s:Dispatching Event [%s]',static::LOGKEY,get_class($event)));
|
||||||
|
event($event);
|
||||||
|
|
||||||
|
return response('Event Processed',200);
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ use Slack\Models\{Enterprise,Team,Token,User};
|
|||||||
|
|
||||||
class SlackAppController extends Controller
|
class SlackAppController extends Controller
|
||||||
{
|
{
|
||||||
private const LOGKEY = 'CSA';
|
protected const LOGKEY = 'CSA';
|
||||||
|
|
||||||
private const slack_authorise_url = 'https://slack.com/oauth/v2/authorize';
|
private const slack_authorise_url = 'https://slack.com/oauth/v2/authorize';
|
||||||
private const slack_oauth_url = 'https://slack.com/api/oauth.v2.access';
|
private const slack_oauth_url = 'https://slack.com/api/oauth.v2.access';
|
||||||
@ -39,7 +39,7 @@ class SlackAppController extends Controller
|
|||||||
|
|
||||||
public function home()
|
public function home()
|
||||||
{
|
{
|
||||||
return sprintf('Hi, for instructions on how to install me, please reach out to <strong>@deon.</strong>');
|
return sprintf('Hi, for instructions on how to install me, please reach out to <strong>%s</strong>.',config('slack.app_admin','Your slack admin'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setup()
|
public function setup()
|
||||||
@ -54,7 +54,7 @@ class SlackAppController extends Controller
|
|||||||
* @return string
|
* @return string
|
||||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||||
*/
|
*/
|
||||||
public function install(Request $request)
|
public function install(Request $request,bool $oauth=FALSE)
|
||||||
{
|
{
|
||||||
if (! config('slack.client_id') OR ! config('slack.client_secret'))
|
if (! config('slack.client_id') OR ! config('slack.client_secret'))
|
||||||
abort(403,'Slack ClientID or Secret not set');
|
abort(403,'Slack ClientID or Secret not set');
|
||||||
@ -158,7 +158,7 @@ class SlackAppController extends Controller
|
|||||||
$so->admin_id = $uo->id;
|
$so->admin_id = $uo->id;
|
||||||
$so->save();
|
$so->save();
|
||||||
|
|
||||||
return sprintf('All set up! Head back to your slack instance <strong>%s</strong>.',$so->description);
|
return $oauth ? $output : sprintf('All set up! Head back to your slack instance <strong>%s</strong>.',$so->description);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
81
src/Http/Middleware/CheckRequest.php
Normal file
81
src/Http/Middleware/CheckRequest.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Client\Payload;
|
||||||
|
use Slack\Event\Factory as EventFactory;
|
||||||
|
|
||||||
|
use App\Slack\Interactive\Factory as InteractiveFactory;
|
||||||
|
use App\Slack\Options\Factory as OptionsFactory;
|
||||||
|
|
||||||
|
class CheckRequest
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'MCR';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that we have the right token before proceeding.
|
||||||
|
* We should only have 1 message (since the token is an object in the message.)
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Closure $next
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle(Request $request,Closure $next)
|
||||||
|
{
|
||||||
|
Log::info(sprintf('%s:Incoming request to [%s]',static::LOGKEY,$request->path()),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// For app installs, we have nothing to check.
|
||||||
|
if (in_array($request->path(),config('slack.bypass_routes')))
|
||||||
|
return $next($request);
|
||||||
|
|
||||||
|
switch ($request->path()) {
|
||||||
|
// For slashcmd full validation is done in the controller
|
||||||
|
case 'api/slashcmd':
|
||||||
|
return $next($request);
|
||||||
|
|
||||||
|
case 'api/event':
|
||||||
|
// URL Verification
|
||||||
|
if ($request->input('type') === 'url_verification') {
|
||||||
|
Log::debug(sprintf('%s:Responding directly to URL Verification',static::LOGKEY),['m'=>__METHOD__,'r'=>$request->all()]);
|
||||||
|
return response($request->input('challenge'),200);
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = EventFactory::make(new Payload($request->all(),TRUE));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'api/imsgopt':
|
||||||
|
$event = OptionsFactory::make($request);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'api/imsg':
|
||||||
|
$event = InteractiveFactory::make($request);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Quietly die if we got here.
|
||||||
|
return response('',444);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore events for inactive workspaces
|
||||||
|
if ($event->enterprise_id AND (! $event->enterprise()->active)) {
|
||||||
|
Log::notice(sprintf('%s:IGNORING post, Enterprise INACTIVE [%s]',static::LOGKEY,$event->enterprise_id),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Quietly die if the team is not active
|
||||||
|
return response('',200);
|
||||||
|
|
||||||
|
} elseif ((! $event->enterprise_id) AND ((! $event->team()) OR (! $event->team()->active))) {
|
||||||
|
Log::notice(sprintf('%s:IGNORING post, Team INACTIVE [%s]',static::LOGKEY,$event->team_id),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Quietly die if the team is not active
|
||||||
|
return response('',200);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log::debug(sprintf('%s:Incoming Request Allowed',static::LOGKEY),['m'=>__METHOD__,'e'=>$event->enterprise_id,'t'=>$event->team_id,'eo'=>$event->enterprise()->id,'to'=>$event->team()]);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
src/Http/Middleware/CheckSignature.php
Normal file
69
src/Http/Middleware/CheckSignature.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Slack\Http\Middleware;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Slack\Base;
|
||||||
|
|
||||||
|
class CheckSignature
|
||||||
|
{
|
||||||
|
private const LOGKEY = 'MCS';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a slack request
|
||||||
|
* by the slack signing secret (not the token)
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Closure $next
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle(Request $request,Closure $next)
|
||||||
|
{
|
||||||
|
// Make sure we are not an installation call
|
||||||
|
if (! in_array($request->path(),config('slack.bypass_routes'))) {
|
||||||
|
// get the remote sign
|
||||||
|
$remote_signature = $request->header('X-Slack-Signature');
|
||||||
|
Log::info(sprintf('%s:Incoming request - check slack SIGNATURE [%s]',static::LOGKEY,$remote_signature),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
// Load the secret, you also can load it from env(YOUR_OWN_SLACK_SECRET)
|
||||||
|
$secret = config('slack.signing_secret');
|
||||||
|
|
||||||
|
$body = $request->getContent();
|
||||||
|
|
||||||
|
// Compare timestamp with the local time, according to the slack official documents
|
||||||
|
// the gap should under 5 minutes
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
if (! $timestamp = $request->header('X-Slack-Request-Timestamp')) {
|
||||||
|
Log::alert(sprintf('%s:No slack timestamp - aborting...',static::LOGKEY),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
return response('',444);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($x=Carbon::now()->diffInMinutes(Carbon::createFromTimestamp($timestamp))) > 5) {
|
||||||
|
Log::alert(sprintf('%s:Invalid slack timestamp [%d]',static::LOGKEY,$x),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
return response('',444);
|
||||||
|
}
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
|
||||||
|
// generate the string base
|
||||||
|
$sig_basestring = sprintf('%s:%s:%s',Base::signature_version,$timestamp,$body);
|
||||||
|
|
||||||
|
// generate the local sign
|
||||||
|
$hash = hash_hmac('sha256',$sig_basestring,$secret);
|
||||||
|
$local_signature = sprintf('%s=%s',Base::signature_version,$hash);
|
||||||
|
|
||||||
|
// check two signs, if not match, throw an error
|
||||||
|
if ($remote_signature !== $local_signature) {
|
||||||
|
Log::alert(sprintf('%s:Invalid slack signature [%s]',static::LOGKEY,$remote_signature),['m'=>__METHOD__]);
|
||||||
|
|
||||||
|
return response('',444);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
@ -4,14 +4,13 @@ namespace Slack\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Leenooks\Traits\ScopeActive;
|
use Slack\Traits\ScopeActive;
|
||||||
|
|
||||||
class Channel extends Model
|
class Channel extends Model
|
||||||
{
|
{
|
||||||
use ScopeActive;
|
use ScopeActive;
|
||||||
|
|
||||||
protected $fillable = ['team_id','channel_id','name','active'];
|
protected $fillable = ['team_id','channel_id','name','active'];
|
||||||
protected $table = 'slack_channels';
|
|
||||||
|
|
||||||
/* RELATIONS */
|
/* RELATIONS */
|
||||||
|
|
||||||
|
@ -3,14 +3,13 @@
|
|||||||
namespace Slack\Models;
|
namespace Slack\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Leenooks\Traits\ScopeActive;
|
use Slack\Traits\ScopeActive;
|
||||||
|
|
||||||
class Enterprise extends Model
|
class Enterprise extends Model
|
||||||
{
|
{
|
||||||
use ScopeActive;
|
use ScopeActive;
|
||||||
|
|
||||||
protected $fillable = ['enterprise_id'];
|
protected $fillable = ['enterprise_id'];
|
||||||
protected $table = 'slack_enterprises';
|
|
||||||
|
|
||||||
/* RELATIONS */
|
/* RELATIONS */
|
||||||
|
|
||||||
|
@ -3,15 +3,14 @@
|
|||||||
namespace Slack\Models;
|
namespace Slack\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Leenooks\Traits\ScopeActive;
|
|
||||||
use Slack\API;
|
use Slack\API;
|
||||||
|
use Slack\Traits\ScopeActive;
|
||||||
|
|
||||||
class Team extends Model
|
class Team extends Model
|
||||||
{
|
{
|
||||||
use ScopeActive;
|
use ScopeActive;
|
||||||
|
|
||||||
protected $fillable = ['team_id'];
|
protected $fillable = ['team_id'];
|
||||||
protected $table = 'slack_teams';
|
|
||||||
|
|
||||||
/* RELATIONS */
|
/* RELATIONS */
|
||||||
|
|
||||||
|
@ -4,14 +4,12 @@ namespace Slack\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Leenooks\Traits\ScopeActive;
|
use Slack\Traits\ScopeActive;
|
||||||
|
|
||||||
class Token extends Model
|
class Token extends Model
|
||||||
{
|
{
|
||||||
use ScopeActive;
|
use ScopeActive;
|
||||||
|
|
||||||
protected $table = 'slack_tokens';
|
|
||||||
|
|
||||||
/* RELATIONS */
|
/* RELATIONS */
|
||||||
|
|
||||||
public function team()
|
public function team()
|
||||||
|
@ -4,16 +4,15 @@ namespace Slack\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Leenooks\Traits\ScopeActive;
|
use Slack\Traits\ScopeActive;
|
||||||
|
|
||||||
class User extends Model
|
class User extends Model
|
||||||
{
|
{
|
||||||
use ScopeActive;
|
use ScopeActive;
|
||||||
|
|
||||||
private const LOGKEY = '-MU';
|
protected const LOGKEY = '-MU';
|
||||||
|
|
||||||
protected $fillable = ['user_id'];
|
protected $fillable = ['user_id'];
|
||||||
protected $table = 'slack_users';
|
|
||||||
|
|
||||||
/* RELATIONS */
|
/* RELATIONS */
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ use Slack\API;
|
|||||||
use Slack\Channels\SlackBotChannel;
|
use Slack\Channels\SlackBotChannel;
|
||||||
use Slack\Console\Commands\SlackSocketClient;
|
use Slack\Console\Commands\SlackSocketClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo For Lumen installs, this service provider is not required?
|
||||||
|
*/
|
||||||
class SlackServiceProvider extends ServiceProvider
|
class SlackServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
17
src/Traits/ScopeActive.php
Normal file
17
src/Traits/ScopeActive.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a ScopeActive to an Eloquent Model
|
||||||
|
*/
|
||||||
|
namespace Slack\Traits;
|
||||||
|
|
||||||
|
trait ScopeActive
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Only query active records
|
||||||
|
*/
|
||||||
|
public function scopeActive($query)
|
||||||
|
{
|
||||||
|
return $query->where($this->getTable().'.active',TRUE);
|
||||||
|
}
|
||||||
|
}
|
@ -6,4 +6,7 @@ return [
|
|||||||
'client_secret' => env('SLACK_CLIENT_SECRET',NULL),
|
'client_secret' => env('SLACK_CLIENT_SECRET',NULL),
|
||||||
'signing_secret' => env('SLACK_SIGNING_SECRET',NULL),
|
'signing_secret' => env('SLACK_SIGNING_SECRET',NULL),
|
||||||
'register_notification' => env('SLACK_REGISTER_NOTIFICATION',TRUE),
|
'register_notification' => env('SLACK_REGISTER_NOTIFICATION',TRUE),
|
||||||
|
|
||||||
|
// Our routes that we dont check for signatures
|
||||||
|
'bypass_routes' => ['/','slack-install-button','slack-install'],
|
||||||
];
|
];
|
||||||
|
@ -4,7 +4,8 @@ $routeConfig = [
|
|||||||
'namespace' => 'Slack\Http\Controllers',
|
'namespace' => 'Slack\Http\Controllers',
|
||||||
];
|
];
|
||||||
|
|
||||||
app('router')->group($routeConfig, function ($router) {
|
app('router')
|
||||||
|
->group($routeConfig, function ($router) {
|
||||||
$router->get('slack-install-button', [
|
$router->get('slack-install-button', [
|
||||||
'uses' => 'SlackAppController@button',
|
'uses' => 'SlackAppController@button',
|
||||||
'as' => 'slack-install-button',
|
'as' => 'slack-install-button',
|
||||||
@ -14,4 +15,17 @@ app('router')->group($routeConfig, function ($router) {
|
|||||||
'uses' => 'SlackAppController@install',
|
'uses' => 'SlackAppController@install',
|
||||||
'as' => 'slack-install',
|
'as' => 'slack-install',
|
||||||
]);
|
]);
|
||||||
});
|
|
||||||
|
$router->get('', [
|
||||||
|
'uses' => 'SlackAppController@home',
|
||||||
|
'as' => 'home',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
app('router')
|
||||||
|
->group(array_merge($routeConfig,['prefix'=>'api']), function ($router) {
|
||||||
|
$router->post('event', [
|
||||||
|
'uses' => 'EventsController@fire',
|
||||||
|
'as' => 'event',
|
||||||
|
]);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user