Merge branch 'master' into 'production'
Master See merge request Romulus21/portal!68
This commit is contained in:
18
.idea/php.xml
generated
18
.idea/php.xml
generated
@@ -121,6 +121,24 @@
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
|
||||
<path value="$PROJECT_DIR$/vendor/league/mime-type-detection" />
|
||||
<path value="$PROJECT_DIR$/vendor/graham-campbell/result-type" />
|
||||
<path value="$PROJECT_DIR$/vendor/laminas/laminas-zendframework-bridge" />
|
||||
<path value="$PROJECT_DIR$/vendor/laminas/laminas-diactoros" />
|
||||
<path value="$PROJECT_DIR$/vendor/laravel/legacy-factories" />
|
||||
<path value="$PROJECT_DIR$/vendor/laravel/slack-notification-channel" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
|
||||
<path value="$PROJECT_DIR$/vendor/jean85/pretty-package-versions" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
|
||||
<path value="$PROJECT_DIR$/vendor/clue/stream-filter" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/message" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/promise" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/httplug" />
|
||||
<path value="$PROJECT_DIR$/vendor/sentry/sentry-laravel" />
|
||||
<path value="$PROJECT_DIR$/vendor/sentry/sentry" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/http-client" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/http-client-contracts" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/discovery" />
|
||||
<path value="$PROJECT_DIR$/vendor/php-http/client-common" />
|
||||
<path value="$PROJECT_DIR$/vendor/http-interop/http-factory-guzzle" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" />
|
||||
|
||||
16
.idea/portal.iml
generated
16
.idea/portal.iml
generated
@@ -7,6 +7,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/asm89/stack-cors" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/brick/math" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/clue/stream-filter" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/defuse/php-encryption" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/dnoegel/php-xdg-base-dir" />
|
||||
@@ -28,9 +29,13 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/hamcrest/hamcrest-php" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/http-interop/http-factory-guzzle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/intervention/image" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/jean85/pretty-package-versions" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/framework" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/legacy-factories" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/passport" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/slack-notification-channel" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/tinker" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/ui" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/lcobucci/jwt" />
|
||||
@@ -50,7 +55,12 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/paragonie/random_compat" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/client-common" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/discovery" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/httplug" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/message" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/message-factory" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/php-http/promise" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
|
||||
@@ -87,6 +97,8 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/resource-operations" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/sentry/sentry-laravel" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/spatie/laravel-web-tinker" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/swiftmailer/swiftmailer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
|
||||
@@ -96,9 +108,12 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-client-contracts" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mime" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-iconv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-grapheme" />
|
||||
@@ -109,6 +124,7 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php72" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php73" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php80" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/process" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/psr-http-message-bridge" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
|
||||
|
||||
70
Envoy.blade.php
Normal file
70
Envoy.blade.php
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
@setup
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__, '.env.prod');
|
||||
$dotenv->load();
|
||||
|
||||
$config = new \StdClass;
|
||||
$config->test_path = "/var/www/html/portal";
|
||||
$config->prod_path = "/var/www/html/portal";
|
||||
$config->test_branch = "development";
|
||||
$config->prod_branch = "production";
|
||||
$date = ( new DateTime )->format('Y-m-d H:i:s');
|
||||
$dateSave = ( new DateTime )->format('Ymd-Hi');
|
||||
$db_host = $_ENV['DB_DATABASE'];
|
||||
$db_user = $_ENV['DB_USERNAME'];
|
||||
$db_pass = $_ENV['DB_PASSWORD'];
|
||||
$rsync = $_ENV['RSYNC_SAV'];
|
||||
$slack = $_ENV['SLACK_URL'];
|
||||
|
||||
@endsetup
|
||||
|
||||
@servers(['test' => ['raspitest'], 'prod' => ['raspigate']])
|
||||
|
||||
@story('deploy')
|
||||
sql-backup
|
||||
deploy-prod
|
||||
@endstory
|
||||
|
||||
@task('sql-backup', ['on' => ['prod']])
|
||||
cd {{ $config->prod_path }}
|
||||
mysqldump --user={{ $db_user }} --password={{ $db_pass }} -B {{ $db_host }} > storage/logs/database/save-{{ $db_host }}-{{ $dateSave }}.sql
|
||||
scp -p storage/logs/database/save-{{ $db_host }}-{{ $dateSave }}.sql {{ $rsync }}
|
||||
@endtask
|
||||
|
||||
@task('deploy-test', ['on' => ['test']])
|
||||
|
||||
cd {{ $config->test_path }}
|
||||
{{-- php artisan down--}}
|
||||
{{-- git reset --hard HEAD--}}
|
||||
{{-- git pull origin {{ $config->test_branch }}--}}
|
||||
{{-- composer install--}}
|
||||
{{-- npm install--}}
|
||||
{{-- npm run production--}}
|
||||
{{-- php artisan migrate --force--}}
|
||||
{{-- php artisan cache:clear--}}
|
||||
{{-- php artisan config:cache--}}
|
||||
{{-- php artisan view:clear--}}
|
||||
{{-- php artisan optimize--}}
|
||||
{{-- php artisan up--}}
|
||||
|
||||
@endtask
|
||||
|
||||
@task('deploy-prod', ['on' => ['prod']])
|
||||
|
||||
cd {{ $config->prod_path }}
|
||||
php artisan down
|
||||
git reset --hard HEAD
|
||||
git pull origin {{ $config->prod_branch }}
|
||||
composer install
|
||||
npm install
|
||||
npm run production
|
||||
php artisan migrate --force
|
||||
php artisan optimize
|
||||
php artisan up
|
||||
|
||||
@endtask
|
||||
|
||||
@finished
|
||||
@slack($slack, '#envoy', "Deployment on prod: {$date} complete")
|
||||
@endfinished
|
||||
@@ -36,6 +36,10 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
if ($this->shouldReport($exception) && app()->bound('sentry')) {
|
||||
app('sentry')->captureException($exception);
|
||||
}
|
||||
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Http\Controllers;
|
||||
use App\Http\Requests\EventRequest;
|
||||
use App\Models\Event;
|
||||
use App\Http\Resources\Event as EventResource;
|
||||
use App\Models\EventGuestsNonUsers;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EventController extends Controller
|
||||
@@ -12,21 +14,15 @@ class EventController extends Controller
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Http\Response|object
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
$events = Event::all();
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
return (EventResource::collection($events))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,27 +56,22 @@ class EventController extends Controller
|
||||
->setStatusCode(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Event $event
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(Event $event)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \App\Models\Event $event
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function update(Request $request, Event $event)
|
||||
public function update(EventRequest $request, Event $event)
|
||||
{
|
||||
//
|
||||
$this->authorize('update', $event);
|
||||
|
||||
$event->update($request->all());
|
||||
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,6 +82,112 @@ class EventController extends Controller
|
||||
*/
|
||||
public function destroy(Event $event)
|
||||
{
|
||||
//
|
||||
$this->authorize('delete', $event);
|
||||
|
||||
$event->delete();
|
||||
|
||||
return response([], 204);
|
||||
}
|
||||
|
||||
public function inviteUser(Event $event, User $user)
|
||||
{
|
||||
|
||||
$this->authorize('update', $event);
|
||||
|
||||
$event->guests()->attach($user);
|
||||
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
}
|
||||
|
||||
public function removeInviteUser(Event $event, User $user)
|
||||
{
|
||||
|
||||
$this->authorize('delete', $event);
|
||||
|
||||
$event->guests()->detach($user);
|
||||
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(204);
|
||||
}
|
||||
|
||||
public function addGuestToStaffEvent(Event $event, User $user)
|
||||
{
|
||||
|
||||
$this->authorize('delete', $event);
|
||||
|
||||
$event->guests()->updateExistingPivot($user, ['is_staff' => true], false);
|
||||
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
}
|
||||
|
||||
public function deleteGuestToStaffEvent(Event $event, User $user)
|
||||
{
|
||||
$this->authorize('delete', $event);
|
||||
|
||||
$event->guests()->updateExistingPivot($user, ['is_staff' => false], false);
|
||||
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
}
|
||||
|
||||
public function userConfirmParticipation(Event $event)
|
||||
{
|
||||
$this->authorize('participation', $event);
|
||||
|
||||
$event->guests()->updateExistingPivot(auth()->user(), ['validated_at' => now()->toDateTimeString()], false);
|
||||
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
}
|
||||
|
||||
public function userDeleteInvitation(Event $event)
|
||||
{
|
||||
$this->authorize('participation', $event);
|
||||
|
||||
$event->guests()->detach(auth()->user());
|
||||
|
||||
return response([], 204);
|
||||
}
|
||||
|
||||
public function addGuestWithEmail(Event $event)
|
||||
{
|
||||
$data = request()->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
$event->emailedGuests()->save(new EventGuestsNonUsers(['email' => $data['email']]));
|
||||
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function guestCanReadEvent(Event $event)
|
||||
{
|
||||
$guest = request()->guest;
|
||||
|
||||
if (!$guest->read_at) {
|
||||
$guest->update(['read_at' => now()->toDateTimeString()]);
|
||||
}
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
}
|
||||
|
||||
public function guestCanConfirmEvent(Event $event)
|
||||
{
|
||||
$guest = request()->guest;
|
||||
$guest->update(['validated_at' => now()->toDateTimeString()]);
|
||||
|
||||
return (new EventResource($event))
|
||||
->response()
|
||||
->setStatusCode(200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,5 +63,6 @@ class Kernel extends HttpKernel
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'guest-mail' => \App\Http\Middleware\EventGuestWithEmail::class,
|
||||
];
|
||||
}
|
||||
|
||||
32
app/Http/Middleware/EventGuestWithEmail.php
Normal file
32
app/Http/Middleware/EventGuestWithEmail.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\EventGuestsNonUsers;
|
||||
use Closure;
|
||||
|
||||
class EventGuestWithEmail
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$data = request()->validate([
|
||||
'email' => 'required|email',
|
||||
'token' => 'required|uuid',
|
||||
]);
|
||||
|
||||
$guest = EventGuestsNonUsers::where('email', $data['email'])->where('token', $data['token'])->first();
|
||||
if ($guest) {
|
||||
$request->guest = $guest;
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return response([], 403);
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,9 @@ class EventRequest extends FormRequest
|
||||
'name' => 'required',
|
||||
'description' => 'nullable|string',
|
||||
'category_id' => 'required|exists:event_categories,id',
|
||||
'private' => 'boolean',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'date|after_or_equal:start_date',
|
||||
'end_date' => 'date|after_or_equal:start_date|nullable',
|
||||
'location' => 'string|nullable'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Http\Resources\User as UserResource;
|
||||
use App\Http\Resources\EventCategory as EventCategoryResource;
|
||||
use App\Http\Resources\EventGuestWithoutEmail as GuestsWithoutEmailResource;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class Event extends JsonResource
|
||||
@@ -25,16 +28,16 @@ class Event extends JsonResource
|
||||
'start_date' => $this->start_date,
|
||||
'end_date' => $this->end_date,
|
||||
'location' => $this->location,
|
||||
'category' => [
|
||||
'data' => [
|
||||
'category_id' => $this->category_id
|
||||
],
|
||||
'category' => new EventCategoryResource($this->category),
|
||||
'invitations' => UserResource::collection($this->guests),
|
||||
'invitations-with-email' => [
|
||||
'data' => GuestsWithoutEmailResource::collection($this->emailedGuests)
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'links' => [
|
||||
'self' => url('/events/'.$this->id),
|
||||
'self' => $this->path(),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
23
app/Http/Resources/EventGuestWithoutEmail.php
Normal file
23
app/Http/Resources/EventGuestWithoutEmail.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class EventGuestWithoutEmail extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'email' => $this->email,
|
||||
'read_at' => $this->read_at,
|
||||
'validated_at' => $this->validated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,12 @@ class User extends JsonResource
|
||||
'thumbnail_cover_image' => new ImageResource($this->thumbnailImage),
|
||||
'last_login' => optional($this->login_at)->diffForHumans(),
|
||||
'is_admin' => $this->isAdmin(),
|
||||
'is_staff' => $this->whenPivotLoaded('event_guest', function () {
|
||||
return (int) $this->pivot->is_staff;
|
||||
}),
|
||||
'validated_at' => $this->whenPivotLoaded('event_guest', function () {
|
||||
return (int) $this->pivot->validated_at;
|
||||
}),
|
||||
],
|
||||
],
|
||||
'links' => [
|
||||
|
||||
@@ -3,8 +3,33 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Event extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function path()
|
||||
{
|
||||
return '/events/' . $this->id;
|
||||
}
|
||||
|
||||
public function category() :BelongsTo
|
||||
{
|
||||
return $this->belongsTo(EventCategory::class, 'category_id');
|
||||
}
|
||||
|
||||
public function guests() :BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'event_guest')
|
||||
->withPivot('is_staff', 'validated_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function emailedGuests() :HasMany
|
||||
{
|
||||
return $this->hasMany(EventGuestsNonUsers::class, 'event_id');
|
||||
}
|
||||
}
|
||||
|
||||
12
app/Models/EventGuestsNonUsers.php
Normal file
12
app/Models/EventGuestsNonUsers.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EventGuestsNonUsers extends Model
|
||||
{
|
||||
protected $table = 'event_non_user_guests';
|
||||
|
||||
protected $guarded = [];
|
||||
}
|
||||
@@ -8,12 +8,14 @@ use App\Models\Image;
|
||||
use App\Models\Memo;
|
||||
use App\Models\ToDoList;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Passport\HasApiTokens;
|
||||
use phpDocumentor\Reflection\Types\Boolean;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
@@ -110,4 +112,11 @@ class User extends Authenticatable
|
||||
{
|
||||
return $this->hasMany(Event::class);
|
||||
}
|
||||
|
||||
public function invitedEvent(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Event::class, 'event_guest', 'user_id', 'event_id')
|
||||
->withPivot('is_staff', 'validated_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
}
|
||||
|
||||
122
app/Policies/EventPolicy.php
Normal file
122
app/Policies/EventPolicy.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class EventPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function viewAny(User $user)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Models\Event $event
|
||||
* @return mixed
|
||||
*/
|
||||
public function view(User $user, Event $event)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function create(User $user)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Models\Event $event
|
||||
* @return mixed
|
||||
*/
|
||||
public function update(User $user, Event $event)
|
||||
{
|
||||
if ($user->id == $event->user_id) {
|
||||
return true;
|
||||
} else {
|
||||
$testedUser = $event->guests()->where('users.id', $user->id)->first();
|
||||
if ($testedUser !== null) {
|
||||
if($testedUser->pivot->is_staff) {
|
||||
return $testedUser->pivot->is_staff;
|
||||
} else if (!$event->private) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Models\Event $event
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete(User $user, Event $event)
|
||||
{
|
||||
return $user->id == $event->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Models\Event $event
|
||||
* @return mixed
|
||||
*/
|
||||
public function restore(User $user, Event $event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Models\Event $event
|
||||
* @return mixed
|
||||
*/
|
||||
public function forceDelete(User $user, Event $event)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the guest can permanently delete invitation.
|
||||
*
|
||||
* @param \App\User $user
|
||||
* @param \App\Models\Event $event
|
||||
* @return mixed
|
||||
*/
|
||||
public function participation(User $user, Event $event)
|
||||
{
|
||||
$testedUser = $event->guests()->where('users.id', $user->id)->first();
|
||||
if ($testedUser !== null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
'App\Models\Memo' => 'App\Policies\MemoPolicy',
|
||||
'App\Models\ToDoList' => 'App\Policies\ToDoListPolicy',
|
||||
'App\Models\Bookmark' => 'App\Policies\BookmarkPolicy',
|
||||
'App\Models\Event' => 'App\Policies\EventPolicy',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"intervention/image": "^2.5",
|
||||
"laravel/framework": "^8.0",
|
||||
"laravel/legacy-factories": "^1.0",
|
||||
"laravel/passport": "^10.0",
|
||||
"laravel/slack-notification-channel": "^2.2",
|
||||
"laravel/tinker": "^2.0",
|
||||
"laravel/ui": "^2.0"
|
||||
"laravel/ui": "^2.0",
|
||||
"sentry/sentry-laravel": "^2.0",
|
||||
"vlucas/phpdotenv": "^5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"facade/ignition": "^2.3.6",
|
||||
|
||||
1238
composer.lock
generated
1238
composer.lock
generated
File diff suppressed because it is too large
Load Diff
34
config/sentry.php
Normal file
34
config/sentry.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
|
||||
|
||||
// capture release as git sha
|
||||
// 'release' => trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')),
|
||||
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => env('SENTRY_ENVIRONMENT'),
|
||||
|
||||
'breadcrumbs' => [
|
||||
// Capture Laravel logs in breadcrumbs
|
||||
'logs' => true,
|
||||
|
||||
// Capture SQL queries in breadcrumbs
|
||||
'sql_queries' => true,
|
||||
|
||||
// Capture bindings on SQL queries logged in breadcrumbs
|
||||
'sql_bindings' => true,
|
||||
|
||||
// Capture queue job information in breadcrumbs
|
||||
'queue_info' => true,
|
||||
|
||||
// Capture command information in breadcrumbs
|
||||
'command_info' => true,
|
||||
],
|
||||
|
||||
// @see: https://docs.sentry.io/error-reporting/configuration/?platform=php#send-default-pii
|
||||
'send_default_pii' => false,
|
||||
|
||||
'traces_sample_rate' => (float)(env('SENTRY_TRACES_SAMPLE_RATE', 0.0)),
|
||||
];
|
||||
13
database/factories/EventCategoryFactory.php
Normal file
13
database/factories/EventCategoryFactory.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Factory $factory */
|
||||
|
||||
use App\Models\EventCategory;
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(EventCategory::class, function (Faker $faker) {
|
||||
return [
|
||||
'name' => $faker->words(3, [false]),
|
||||
'description' => $faker->words(rand(10, 30), [false]),
|
||||
];
|
||||
});
|
||||
24
database/factories/EventFactory.php
Normal file
24
database/factories/EventFactory.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Factory $factory */
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventCategory;
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(Event::class, function (Faker $faker) {
|
||||
$startDate = $faker->dateTimeThisMonth;
|
||||
$endDate = $faker->dateTimeBetween($startDate, date_add($startDate, date_interval_create_from_date_string('10 days')));
|
||||
$endDate = (rand(0, 1) === 0) ? null : $endDate;
|
||||
return [
|
||||
'user_id' => factory(User::class),
|
||||
'category_id' => factory(EventCategory::class),
|
||||
'name' => $faker->words(3, [false]),
|
||||
'description' => $faker->words(rand(10, 300), [false]),
|
||||
'private' => !(rand(0,1)),
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'location' => $faker->city,
|
||||
];
|
||||
});
|
||||
@@ -19,6 +19,7 @@ class CreateEventsTable extends Migration
|
||||
$table->unsignedBigInteger('category_id');
|
||||
$table->string('name');
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('private')->default(true);
|
||||
$table->timestamp('start_date');
|
||||
$table->timestamp('end_date')->nullable();
|
||||
$table->string('location')->nullable();
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateEventGuestTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('event_guest', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('event_id');
|
||||
$table->foreignId('user_id');
|
||||
$table->boolean('is_staff')->default(false);
|
||||
$table->timestamp('validated_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('event_id')->references('id')->on('events');
|
||||
$table->foreign('user_id')->references('id')->on('users');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('event_guest', function (Blueprint $table) {
|
||||
$table->dropForeign(['event_id']);
|
||||
$table->dropForeign(['user_id']);
|
||||
});
|
||||
|
||||
Schema::dropIfExists('event_guest');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateEventNonUserGuestsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('event_non_user_guests', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('event_id');
|
||||
$table->string('email');
|
||||
$table->uuid('token')->default(\Illuminate\Support\Str::uuid());
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->timestamp('validated_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('event_id')
|
||||
->references('id')
|
||||
->on('events')
|
||||
->onDelete('restrict')
|
||||
->onUpdate('restrict');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('event_non_user_guests', function (Blueprint $table) {
|
||||
$table->dropForeign(['event_id']);
|
||||
});
|
||||
|
||||
Schema::dropIfExists('event_non_user_guests');
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ if [ -z "$1" ]; then
|
||||
|
||||
composer install
|
||||
php artisan migrate --force
|
||||
php artisan db:seed
|
||||
php artisan optimize
|
||||
|
||||
npm install --no-progress
|
||||
@@ -79,6 +80,7 @@ elif [ "$1" = git-prod ]; then
|
||||
|
||||
composer install
|
||||
php artisan migrate --force
|
||||
php artisan db:seed
|
||||
php artisan optimize
|
||||
fi
|
||||
|
||||
|
||||
85
resources/js/components/Form/DateTimeField.vue
Normal file
85
resources/js/components/Form/DateTimeField.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="relative mb-2">
|
||||
<label v-if="label" :for="name" class="pb-2 font-bold text-xl ml-1">{{ label }}</label>
|
||||
<div class="flex">
|
||||
<input type="date" :name="name" v-model="dateInput" :class="'flex-2 p-2 ' + classes + ' ' + errorClassObject()">
|
||||
<input type="time" v-model="timeInput" :class="'flex-1 p-2 ' + classes + ' ' + errorClassObject()">
|
||||
</div>
|
||||
<p class="text-red no-indent m-0" v-text="errorMessage()">Error Here</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DateTimeField',
|
||||
props: {
|
||||
classes: String,
|
||||
label: String,
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selected: String,
|
||||
errors: Object,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
dateInput: '',
|
||||
timeInput: '00:00',
|
||||
value: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasError: function () {
|
||||
return this.required && this.errors && this.errors[this.name] && this.errors[this.name].length > 0
|
||||
},
|
||||
date: function () {
|
||||
return this.selected.split(' ')[0]
|
||||
},
|
||||
time: function () {
|
||||
return this.selected.split(' ')[1]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateField: function () {
|
||||
this.value = this.dateInput + ' ' + this.timeInput
|
||||
this.clearErrors(this.date)
|
||||
this.$emit('update:field', this.value)
|
||||
},
|
||||
errorMessage: function () {
|
||||
if (this.hasError) {
|
||||
return this.errors[this.name][0]
|
||||
}
|
||||
},
|
||||
clearErrors: function () {
|
||||
if (this.hasError) {
|
||||
this.errors[this.name] = null
|
||||
}
|
||||
},
|
||||
errorClassObject: function () {
|
||||
if (this.hasError) {
|
||||
return 'border-red'
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
date: function () {
|
||||
if (this.selected) {
|
||||
this.dateInput = this.date
|
||||
}
|
||||
this.updateField()
|
||||
},
|
||||
time: function () {
|
||||
if (this.selected) {
|
||||
this.timeInput = this.time
|
||||
}
|
||||
this.updateField()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="relative mb-2">
|
||||
<label v-if="label" :for="name" class="pb-2 font-bold text-xl ml-1">{{ label }}</label>
|
||||
<input :id="name" :type="type" :placeholder="placeholder" v-model="value" @input="updateField()" :class="'w-full p-2 ' + classes + ' ' + errorClassObject()">
|
||||
<p class="text-red-600 m-0" v-text="errorMessage()">Error Here</p>
|
||||
<p class="text-red no-indent m-0" v-text="errorMessage()">Error Here</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -55,8 +55,8 @@ export default {
|
||||
}
|
||||
},
|
||||
errorClassObject: function () {
|
||||
return {
|
||||
'error-field': this.hasError
|
||||
if (this.hasError) {
|
||||
return 'border-red'
|
||||
}
|
||||
}
|
||||
},
|
||||
62
resources/js/components/Form/SelectorField.vue
Normal file
62
resources/js/components/Form/SelectorField.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="relative mb-2">
|
||||
<label v-if="label" :for="name" class="pb-2 font-bold text-xl ml-1">{{ label }}</label>
|
||||
<select :name="name"
|
||||
v-model="selected"
|
||||
:class="'block w-full p-2 ' + errorClassObject()">
|
||||
<option v-for="option in options"
|
||||
:key="option.value"
|
||||
:value="option.value">{{ option.label }}</option>
|
||||
</select>
|
||||
<p class="text-red no-indent m-0" v-text="errorMessage()">Error Here</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SelectorField',
|
||||
props: {
|
||||
classes: String,
|
||||
label: String,
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
options: Array,
|
||||
selected: Number,
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
errors: Object,
|
||||
},
|
||||
computed: {
|
||||
hasError: function () {
|
||||
return this.required && this.errors && this.errors[this.name] && this.errors[this.name].length > 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
errorMessage: function () {
|
||||
if (this.hasError) {
|
||||
return this.errors[this.name][0]
|
||||
}
|
||||
},
|
||||
clearErrors: function () {
|
||||
if (this.hasError) {
|
||||
this.errors[this.name] = null
|
||||
}
|
||||
},
|
||||
errorClassObject: function () {
|
||||
if (this.hasError) {
|
||||
return 'border-red'
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selected: function () {
|
||||
this.clearErrors(this.name)
|
||||
this.$emit('update:field', this.selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="relative mb-2">
|
||||
<label v-if="label" :for="name" class="pb-1">{{ label }}</label>
|
||||
<label v-if="label" :for="name" class="pb-2 font-bold text-xl ml-1">{{ label }}</label>
|
||||
<textarea :id="name"
|
||||
type="text"
|
||||
v-model="value"
|
||||
@@ -9,7 +9,7 @@
|
||||
:class="errorClassObject()"
|
||||
class="w-full h-64 rounded p-2">
|
||||
</textarea>
|
||||
<p class="text-red-600 m-0" v-text="errorMessage()">Error Here</p>
|
||||
<p class="text-red no-indent m-0" v-text="errorMessage()">Error Here</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -60,8 +60,8 @@ export default {
|
||||
}
|
||||
},
|
||||
errorClassObject: function () {
|
||||
return {
|
||||
'error-field': this.hasError
|
||||
if (this.hasError) {
|
||||
return 'border-red'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -17,6 +17,10 @@
|
||||
<svg-vue icon="globe" />
|
||||
<span>Bookmarks</span>
|
||||
</router-link>
|
||||
<router-link to="/events" class="nav-item p-2">
|
||||
<svg-vue icon="calendar" />
|
||||
<span>Evénements</span>
|
||||
</router-link>
|
||||
<router-link to="/jeux" class="nav-item p-2">
|
||||
<svg-vue icon="games" />
|
||||
<span>Jeux</span>
|
||||
|
||||
18
resources/js/router.js
vendored
18
resources/js/router.js
vendored
@@ -15,6 +15,10 @@ import BookmarkIndex from './views/Bookmark/BookmarkIndex'
|
||||
import GameIndex from './views/Games/GameIndex'
|
||||
import Hangman from './views/Games/HangMan/Hangman'
|
||||
import Quizz from './views/Games/Quizz/Quizz'
|
||||
import EventIndex from './views/Event/EventIndex'
|
||||
import EventCreate from './views/Event/EventCreate'
|
||||
import EventShow from './views/Event/EventShow'
|
||||
import EventEdit from './views/Event/EventEdit'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
@@ -66,6 +70,20 @@ export default new VueRouter({
|
||||
meta: {title: 'Details of List'}
|
||||
},
|
||||
|
||||
{
|
||||
path: '/events', component: EventIndex,
|
||||
meta: {title: 'Events List'}
|
||||
}, {
|
||||
path: '/events/create', component: EventCreate,
|
||||
meta: {title: 'Add New Event'}
|
||||
}, {
|
||||
path: '/events/:id', component: EventShow,
|
||||
meta: {title: 'Detail for Event'}
|
||||
}, {
|
||||
path: '/events/:id/edit', component: EventEdit,
|
||||
meta: {title: 'Edit Event'}
|
||||
},
|
||||
|
||||
{
|
||||
path: '/bookmarks', component: BookmarkIndex,
|
||||
meta: {title: 'Bookmark Lists'}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<script>
|
||||
import Loader from '../../components/Loader'
|
||||
import InputField from '../../components/InputField'
|
||||
import InputField from '../../components/Form/InputField'
|
||||
import Bookmark from './Bookmark'
|
||||
|
||||
export default {
|
||||
|
||||
112
resources/js/views/Event/EventCreate.vue
Normal file
112
resources/js/views/Event/EventCreate.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="mx-2 p-2">
|
||||
<form @submit.prevent="submitForm">
|
||||
<div class="flex justify-between flex-center mb-4">
|
||||
<router-link to="/memos/" class="btn">Back</router-link>
|
||||
<button class="btn-primary">Add New Memo</button>
|
||||
</div>
|
||||
<div class="box">
|
||||
<InputField name="name"
|
||||
label="Nom de l'événement"
|
||||
placeholder="Nom de l'événement"
|
||||
required
|
||||
@update:field="form.name = $event" :errors="errors" />
|
||||
<CheckBoxField :check-it="!!(checked)"
|
||||
label="Privé"
|
||||
@update:field="checked = $event"
|
||||
class="block mb-2" />
|
||||
<div class="flex">
|
||||
<InputField name="location"
|
||||
label="Lieu"
|
||||
placeholder="Lieu"
|
||||
class="w-64 mr-4"
|
||||
required
|
||||
@update:field="form.location = $event" :errors="errors" />
|
||||
<SelectorField name="category_id"
|
||||
label="Catégorie"
|
||||
:options="categories"
|
||||
class="mr-4"
|
||||
required
|
||||
@update:field="form.category_id = $event" :errors="errors" />
|
||||
<DateTimeField
|
||||
class="mr-4"
|
||||
label="Date de début"
|
||||
name="start_date"
|
||||
required
|
||||
@update:field="form.start_date = $event" :errors="errors" />
|
||||
<DateTimeField
|
||||
label="Date de fin"
|
||||
name="end_date"
|
||||
@update:field="form.end_date = $event" :errors="errors" />
|
||||
</div>
|
||||
<TextAreaField name="description"
|
||||
label="Description"
|
||||
placeholder="Your Description"
|
||||
@update:field="form.description = $event" :errors="errors" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InputField from '../../components/Form/InputField'
|
||||
import TextAreaField from '../../components/Form/TextAreaField'
|
||||
import CheckBoxField from '../../components/Form/CheckBoxField'
|
||||
import DateTimeField from '../../components/Form/DateTimeField'
|
||||
import SelectorField from '../../components/Form/SelectorField'
|
||||
|
||||
export default {
|
||||
name: 'EventCreate',
|
||||
components: {
|
||||
InputField, TextAreaField, CheckBoxField, DateTimeField, SelectorField
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
checked: true,
|
||||
categories: [],
|
||||
form: {
|
||||
'category_id': '',
|
||||
'name': '',
|
||||
'private': '',
|
||||
'description': '',
|
||||
'start_date': '',
|
||||
'end_date': '',
|
||||
'location': '',
|
||||
},
|
||||
errors: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.form.private = this.checked
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.get('/api/events/categories')
|
||||
.then(response => {
|
||||
response.data.data.forEach(item => this.categories.push({value: item.data.event_category_id, label: item.data.attributes.data.name}))
|
||||
})
|
||||
.catch(errors => {
|
||||
this.errors = errors.response.data.errors
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
submitForm: function () {
|
||||
this.form.private = this.checked
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.post('/api/events', this.form)
|
||||
.then(response => {
|
||||
this.$router.push(response.data.links.self)
|
||||
})
|
||||
.catch(errors => {
|
||||
this.errors = errors.response.data.errors
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
checked: function () {
|
||||
console.log(this.checked, this.form)
|
||||
this.form.private = this.checked
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
137
resources/js/views/Event/EventEdit.vue
Normal file
137
resources/js/views/Event/EventEdit.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="mx-2 p-2">
|
||||
<Loader v-if="loading" />
|
||||
<form v-else @submit.prevent="submitForm">
|
||||
<div class="flex justify-between flex-center mb-4">
|
||||
<router-link to="/memos/" class="btn">Back</router-link>
|
||||
<button type="submit" class="btn-primary">Update Event</button>
|
||||
</div>
|
||||
<div class="box">
|
||||
<InputField name="name"
|
||||
label="Nom de l'événement"
|
||||
placeholder="Nom de l'événement"
|
||||
:data="form.name"
|
||||
required
|
||||
@update:field="form.name = $event" :errors="errors" />
|
||||
<CheckBoxField :check-it="!!(checked)"
|
||||
label="Privé"
|
||||
@update:field="checked = $event"
|
||||
class="block mb-2" />
|
||||
<div class="flex">
|
||||
<InputField name="location"
|
||||
label="Lieu"
|
||||
placeholder="Lieu"
|
||||
class="w-64 mr-4"
|
||||
:data="form.location"
|
||||
required
|
||||
@update:field="form.location = $event" :errors="errors" />
|
||||
<SelectorField name="category_id"
|
||||
v-if="form.category_id"
|
||||
label="Catégorie"
|
||||
:options="categories"
|
||||
class="mr-4"
|
||||
required
|
||||
:selected="form.category_id"
|
||||
@update:field="form.category_id = $event" :errors="errors" />
|
||||
<DateTimeField
|
||||
class="mr-4"
|
||||
label="Date de début"
|
||||
name="start_date"
|
||||
:selected="form.start_date"
|
||||
required
|
||||
@update:field="form.start_date = $event" :errors="errors" />
|
||||
<DateTimeField
|
||||
label="Date de fin"
|
||||
name="end_date"
|
||||
@update:field="form.end_date = $event" :errors="errors" />
|
||||
</div>
|
||||
<TextAreaField name="description"
|
||||
label="Description"
|
||||
placeholder="Your Description"
|
||||
:data="form.description"
|
||||
@update:field="form.description = $event" :errors="errors" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InputField from '../../components/Form/InputField'
|
||||
import TextAreaField from '../../components/Form/TextAreaField'
|
||||
import CheckBoxField from '../../components/Form/CheckBoxField'
|
||||
import DateTimeField from '../../components/Form/DateTimeField'
|
||||
import SelectorField from '../../components/Form/SelectorField'
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
export default {
|
||||
name: 'EventEdit',
|
||||
components: {
|
||||
InputField, TextAreaField, CheckBoxField, DateTimeField, SelectorField, Loader
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
loading: false,
|
||||
checked: true,
|
||||
event: null,
|
||||
categories: [],
|
||||
form: {
|
||||
'category_id': '',
|
||||
'name': '',
|
||||
'private': '',
|
||||
'description': '',
|
||||
'start_date': '',
|
||||
'end_date': '',
|
||||
'location': '',
|
||||
},
|
||||
errors: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.form.private = this.checked
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.get('/api/events/categories')
|
||||
.then(response => {
|
||||
response.data.data.forEach(item => this.categories.push({value: item.data.event_category_id, label: item.data.attributes.data.name}))
|
||||
})
|
||||
.catch(errors => {
|
||||
this.errors = errors.response.data.errors
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.get('/api/events/' + this.$route.params.id)
|
||||
.then(response => {
|
||||
this.event = response.data
|
||||
this.form = this.event.data.attributes.data
|
||||
this.form.description = this.event.data.attributes.data.description
|
||||
this.form.category_id = this.event.data.attributes.data.category.data.event_category_id
|
||||
})
|
||||
.catch(errors => {
|
||||
this.errors = errors.response.data.errors
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
submitForm: function () {
|
||||
this.form.private = this.checked
|
||||
delete this.form.category
|
||||
delete this.form.invitations
|
||||
delete this.form['invitations-with-email']
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.patch('/api/events/' + this.event.data.event_id, this.form)
|
||||
.then(response => {
|
||||
this.$router.push(response.data.links.self)
|
||||
})
|
||||
.catch(errors => {
|
||||
this.errors = errors.response.data.errors
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
checked: function () {
|
||||
this.form.private = this.checked
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
58
resources/js/views/Event/EventIndex.vue
Normal file
58
resources/js/views/Event/EventIndex.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="mx-2 p-2">
|
||||
<div class="flex justify-between items-center flex-wrap flex-center mb-4">
|
||||
<a href="#" class="btn" @click="$router.back()">Back</a>
|
||||
<router-link :to="'/events/create'" class="btn-primary">Add New Event</router-link>
|
||||
</div>
|
||||
<Loader v-if="loading" />
|
||||
<div v-else class="flex flex-wrap -m-2">
|
||||
<div v-if="events === []">
|
||||
<p>No memos yet. <router-link to="/memos/create">Get Started ></router-link></p>
|
||||
</div>
|
||||
<router-link v-for="event in events" :key="event.data.event_id" :to="'/events/' + event.data.event_id" class="flex-initial sm:mx-auto md:w-1/2 md:mx-0 lg:w-1/3 xl:w-1/4 mb-4 no-underline">
|
||||
<div class="card sm:max-w-124">
|
||||
<div class="h-full flex flex-col justify-between">
|
||||
<div class="flex justify-between flex-wrap">
|
||||
<h1 class="text-xl text-marine">{{ event.data.attributes.data.name }}</h1>
|
||||
<h2 class="text-sm text-marine">{{ event.data.attributes.data.location }}</h2>
|
||||
</div>
|
||||
<div v-if="event.data.attributes.data.description">{{ event.data.attributes.data.description }}</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="mt-auto self-end bg-gray-400 text-white tag">{{ event.data.attributes.data.category.data.attributes.data.name }}</span>
|
||||
<span class="mt-auto self-end bg-gray-400 text-white tag">{{ event.data.attributes.data.start_date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
export default {
|
||||
name: 'EventIndex',
|
||||
components: {
|
||||
Loader
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
loading: true,
|
||||
events: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.get('/api/events')
|
||||
.then(response => {
|
||||
this.events = response.data.data
|
||||
this.loading = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
console.log('Unable to fetch events.')
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
77
resources/js/views/Event/EventShow.vue
Normal file
77
resources/js/views/Event/EventShow.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="mx-2 p-2">
|
||||
<div v-if="modal" class="modal-container" @click="modal = ! modal"></div>
|
||||
<div v-if="modal" class="modal">
|
||||
<p class="m-2 text-center">Are you sure you want to delete this record ?</p>
|
||||
<div class="flex justify-center mx-2 my-4">
|
||||
<button class="btn mr-2" @click="modal = ! modal">Cancel</button>
|
||||
<button class="btn-alert" @click="destroy"> Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center flex-wrap flex-center mb-4">
|
||||
<a href="#" class="btn" @click="$router.back()">Back</a>
|
||||
<div>
|
||||
<router-link :to="'/events/' + $route.params.id + '/edit'" class="btn-primary">Update Event</router-link>
|
||||
<a href="#" class="btn-alert" @click="modal = ! modal">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
<Loader v-if="loading" />
|
||||
<div v-else class="box">
|
||||
<div class="flex justify-between flex-wrap">
|
||||
<h1 class="text-xl text-marine">{{ event.data.attributes.data.name }}</h1>
|
||||
<span class="mt-auto self-end bg-gray-400 text-white tag">{{ event.data.attributes.data.category.data.attributes.data.name }}</span>
|
||||
</div>
|
||||
<h2 class="text-lg text-marine">{{ event.data.attributes.data.location }}</h2>
|
||||
<div v-if="event.data.attributes.data.description">{{ event.data.attributes.data.description }}</div>
|
||||
<div class="flex justify-end">
|
||||
<div>
|
||||
<span class="mt-auto self-end bg-gray-400 text-white tag">{{ event.data.attributes.data.start_date }}</span>
|
||||
<span v-if="event.data.attributes.data.end_date" class="mt-auto self-end bg-gray-400 text-white tag">{{ event.data.attributes.data.end_date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
export default {
|
||||
name: 'EventShow',
|
||||
components: {
|
||||
Loader
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
loading: true,
|
||||
modal: false,
|
||||
event: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.get('/api/events/' + this.$route.params.id)
|
||||
.then(response => {
|
||||
this.event = response.data
|
||||
this.loading = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
console.log('Unable to fetch events.')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
destroy: function () {
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.delete('/api/events/' + this.$route.params.id)
|
||||
.then(() => {
|
||||
this.$router.push('/events')
|
||||
})
|
||||
.catch(errorRes => {
|
||||
console.log('Internal Error, Unable to delete event.' + errorRes)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InputField from '../../components/InputField'
|
||||
import TextAreaField from '../../components/TextAreaField'
|
||||
import InputField from '../../components/Form/InputField'
|
||||
import TextAreaField from '../../components/Form/TextAreaField'
|
||||
|
||||
export default {
|
||||
name: 'MemoCreate',
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InputField from '../../components/InputField'
|
||||
import TextAreaField from '../../components/TextAreaField'
|
||||
import UploadableImage from '../../components/UploadableImage'
|
||||
import InputField from '../../components/Form/InputField'
|
||||
import TextAreaField from '../../components/Form/TextAreaField'
|
||||
import UploadableImage from '../../components/Form/UploadableImage'
|
||||
|
||||
export default {
|
||||
name: 'MemoEdit',
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CheckBoxField from '../../components/CheckBoxField'
|
||||
import CheckBoxField from '../../components/Form/CheckBoxField'
|
||||
|
||||
export default {
|
||||
name: 'ToDo',
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<script>
|
||||
import Loader from '../../components/Loader'
|
||||
import InputField from '../../components/InputField'
|
||||
import InputField from '../../components/Form/InputField'
|
||||
|
||||
export default {
|
||||
name: 'ToDoListIndex',
|
||||
@@ -73,7 +73,6 @@ export default {
|
||||
// eslint-disable-next-line no-undef
|
||||
axios.post('/api/to-do-lists', {name: this.name})
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
this.modal = false
|
||||
this.name = ''
|
||||
this.toDoLists.push(res.data)
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import UploadableImage from '../../components/UploadableImage'
|
||||
import UploadableImage from '../../components/Form/UploadableImage'
|
||||
|
||||
export default {
|
||||
name: 'Profil',
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
import {mapGetters} from 'vuex'
|
||||
import Avatar from '../../components/Avatar'
|
||||
import AlertBox from '../../components/AlertBox'
|
||||
import InputField from '../../components/InputField'
|
||||
import InputField from '../../components/Form/InputField'
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
export default {
|
||||
|
||||
8
resources/sass/components/_base.scss
vendored
8
resources/sass/components/_base.scss
vendored
@@ -35,6 +35,14 @@ textarea {
|
||||
@apply ;
|
||||
}
|
||||
|
||||
select {
|
||||
@apply border py-2 px-4 rounded bg-white;
|
||||
|
||||
option {
|
||||
@apply bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
.text-shadow {
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
}
|
||||
|
||||
9
resources/svg/calendar.svg
Normal file
9
resources/svg/calendar.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 426.667 426.667" style="enable-background:new 0 0 426.667 426.667;" xml:space="preserve">
|
||||
<g>
|
||||
<rect x="106.667" y="192" width="106.667" height="106.667"/>
|
||||
<path d="M362.667,42.667h-21.333V0h-42.667v42.667H128V0H85.333v42.667H64c-23.573,0-42.453,19.093-42.453,42.667L21.333,384
|
||||
c0,23.573,19.093,42.667,42.667,42.667h298.667c23.573,0,42.667-19.093,42.667-42.667V85.333
|
||||
C405.333,61.76,386.24,42.667,362.667,42.667z M362.667,384H64V149.333h298.667V384z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 631 B |
@@ -14,12 +14,25 @@ use Illuminate\Support\Facades\Route;
|
||||
|
|
||||
*/
|
||||
|
||||
Route::middleware('guest-mail')->group(function () {
|
||||
Route::get('/events/{event}/with-email', 'EventController@guestCanReadEvent');
|
||||
Route::patch('/events/{event}/with-email', 'EventController@guestCanConfirmEvent');
|
||||
});
|
||||
|
||||
Route::middleware('auth:api')->group(function () {
|
||||
|
||||
Route::get('auth-user', 'AuthUserController@show');
|
||||
|
||||
Route::get('/memos/home', 'MemosController@home');
|
||||
|
||||
Route::delete('/events/{event}/invite/delete', 'EventController@userDeleteInvitation');
|
||||
Route::delete('/events/{event}/invite/validation', 'EventController@userConfirmParticipation');
|
||||
Route::post('/events/{event}/invite/with-email', 'EventController@addGuestWithEmail');
|
||||
Route::post('/events/{event}/invite/{user}', 'EventController@inviteUser');
|
||||
Route::delete('/events/{event}/invite/{user}', 'EventController@removeInviteUser');
|
||||
Route::post('/events/{event}/staff/{user}', 'EventController@addGuestToStaffEvent');
|
||||
Route::delete('/events/{event}/staff/{user}', 'EventController@deleteGuestToStaffEvent');
|
||||
|
||||
Route::apiResources([
|
||||
'/users' => 'UserController',
|
||||
'/memos' => 'MemosController',
|
||||
|
||||
@@ -80,7 +80,7 @@ class BookmarkTest extends TestCase
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_user_can_retrueved_all_this_to_do_lists()
|
||||
public function a_user_can_retrieved_all_this_to_do_lists()
|
||||
{
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$bookmarkOne = factory(Bookmark::class)->create(['user_id' => $user->id]);
|
||||
|
||||
@@ -4,11 +4,11 @@ namespace Tests\Feature;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\EventCategory;
|
||||
use App\Models\EventGuestsNonUsers;
|
||||
use App\Models\Memo;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EventsTest extends TestCase
|
||||
@@ -173,7 +173,7 @@ class EventsTest extends TestCase
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function only_the_admin_can_patch_the_memo()
|
||||
public function only_the_admin_can_patch_the_event_category()
|
||||
{
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$category = EventCategory::create(['name' => 'Test category', 'description' => 'Test description']);
|
||||
@@ -242,14 +242,14 @@ class EventsTest extends TestCase
|
||||
'location' => $event->location,
|
||||
'category' => [
|
||||
'data' => [
|
||||
'category_id' => 1
|
||||
'event_category_id' => 1
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
'links' => [
|
||||
'self' => url('/events/'.$event->id),
|
||||
'self' => '/events/'.$event->id,
|
||||
]
|
||||
]);
|
||||
}
|
||||
@@ -297,7 +297,7 @@ class EventsTest extends TestCase
|
||||
'location' => $event->location,
|
||||
'category' => [
|
||||
'data' => [
|
||||
'category_id' => 1
|
||||
'event_category_id' => 1
|
||||
],
|
||||
],
|
||||
]
|
||||
@@ -306,6 +306,457 @@ class EventsTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function all_event_can_be_retrieved()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$events = factory(Event::class, 10)->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->get('/api/events');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$this->assertCount(10, Event::all());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_to_event_can_be_patch()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->patch('/api/events/' . $event->id, [
|
||||
'category_id' => $event->category_id,
|
||||
'name' => 'Event Update',
|
||||
'description' => 'Event Update description',
|
||||
'start_date' => '2020-07-20 09:00:00',
|
||||
]);
|
||||
|
||||
$event = $event->fresh();
|
||||
|
||||
$this->assertEquals('Event Update', $event->name);
|
||||
$this->assertEquals('Event Update description', $event->description);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'name' => 'Event Update',
|
||||
'description' => 'Event Update description',
|
||||
'start_date' => '2020-07-20 09:00:00',
|
||||
'category' => [
|
||||
'data' => [
|
||||
'event_category_id' => $event->category_id,
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function only_the_owner_or_staff_can_patch_the_event()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$event = factory(Event::class)->create(['id' => 123, 'user_id' => $user->id]);
|
||||
|
||||
$this->actingAs($anotherUser = factory(User::class)->create(), 'api');
|
||||
|
||||
$this->patch('/api/events/'. $event->id, $this->data())
|
||||
->assertStatus(403);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_event_can_be_delete()
|
||||
{
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id]);
|
||||
|
||||
$response = $this->delete('/api/events/' . $event->id);
|
||||
|
||||
$event = $event->fresh();
|
||||
|
||||
$this->assertCount(0, Event::all());
|
||||
|
||||
$response->assertStatus(204);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function only_the_owner_can_delete_the_bookmark()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$event = factory(Event::class)->create();
|
||||
|
||||
$this->actingAs($anotherUser = factory(User::class)->create(), 'api');
|
||||
|
||||
$response = $this->delete('/api/events/' . $event->id);
|
||||
|
||||
$response->assertStatus(403);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function owner_can_invite_user_to_an_event()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id]);
|
||||
$userTwo = factory(User::class)->create();
|
||||
|
||||
$response = $this->post('api/events/'.$event->id.'/invite/'.$userTwo->id);
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations' => [
|
||||
[
|
||||
'data' => [
|
||||
'user_id' => $userTwo->id,
|
||||
]
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function owner_can_remove_invitation()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id]);
|
||||
$userTwo = factory(User::class)->create();
|
||||
$event->guests()->attach($userTwo);
|
||||
|
||||
$response = $this->delete('api/events/'.$event->id.'/invite/'.$userTwo->id);
|
||||
|
||||
$event = $event->fresh();
|
||||
|
||||
$this->assertCount(0, $event->guests);
|
||||
|
||||
$response->assertStatus(204);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function owner_can_add_guest_to_staff_event()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id]);
|
||||
$userTwo = factory(User::class)->create();
|
||||
$event->guests()->attach($userTwo);
|
||||
|
||||
$response = $this->post('api/events/'.$event->id.'/staff/'.$userTwo->id);
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations' => [
|
||||
[
|
||||
'data' => [
|
||||
'user_id' => $userTwo->id,
|
||||
'attributes' => [
|
||||
'is_staff' => true,
|
||||
]
|
||||
|
||||
],
|
||||
'links' => [
|
||||
'self' => url('/users/'.$userTwo->id),
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function owner_can_remove_guest_to_staff_event()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id]);
|
||||
$userTwo = factory(User::class)->create();
|
||||
$event->guests()->attach($userTwo);
|
||||
|
||||
$response = $this->delete('api/events/'.$event->id.'/staff/'.$userTwo->id);
|
||||
$response->assertStatus(200);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations' => [
|
||||
[
|
||||
'data' => [
|
||||
'user_id' => $userTwo->id,
|
||||
'attributes' => [
|
||||
'is_staff' => false,
|
||||
]
|
||||
|
||||
],
|
||||
'links' => [
|
||||
'self' => url('/users/'.$userTwo->id),
|
||||
],
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function staff_can_add_guest()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id]);
|
||||
$this->actingAs($userTwo = factory(User::class)->create(), 'api');
|
||||
$event->guests()->attach($userTwo);
|
||||
$event->guests()->updateExistingPivot($userTwo, ['is_staff' => true], false);
|
||||
|
||||
$nexGuestUser = factory(User::class)->create();
|
||||
|
||||
$response = $this->post('api/events/'.$event->id.'/invite/'.$nexGuestUser->id);
|
||||
|
||||
$event = $event->fresh();
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(2, $event->guests);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations' => [
|
||||
[
|
||||
'data' => [
|
||||
'user_id' => $userTwo->id,
|
||||
'attributes' => [
|
||||
'is_staff' => 1,
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
'data' => [
|
||||
'user_id' => $nexGuestUser->id,
|
||||
'attributes' => [
|
||||
'is_staff' => 0,
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function guest_can_invite_another_guest_if_public_event()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id,'private' => false]);
|
||||
$this->actingAs($userTwo = factory(User::class)->create(), 'api');
|
||||
$event->guests()->attach($userTwo);
|
||||
|
||||
$nexGuestUser = factory(User::class)->create();
|
||||
|
||||
$response = $this->post('api/events/'.$event->id.'/invite/'.$nexGuestUser->id);
|
||||
|
||||
$event = $event->fresh();
|
||||
|
||||
$this->assertCount(2, $event->guests);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations' => [
|
||||
[
|
||||
'data' => [
|
||||
'user_id' => $userTwo->id,
|
||||
],
|
||||
],
|
||||
[
|
||||
'data' => [
|
||||
'user_id' => $nexGuestUser->id,
|
||||
]
|
||||
]
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function guest_can_validate_event_participation()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->withoutExceptionHandling();
|
||||
$user = factory(User::class)->create();
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id,'private' => false]);
|
||||
$this->actingAs($userTwo = factory(User::class)->create(), 'api');
|
||||
$event->guests()->attach($userTwo);
|
||||
|
||||
$response = $this->delete('api/events/'.$event->id.'/invite/validation');
|
||||
|
||||
$event = $event->fresh();
|
||||
|
||||
$this->assertCount(1, $event->guests);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations' => [
|
||||
[
|
||||
'data' => [
|
||||
'user_id' => $userTwo->id,
|
||||
'attributes' => [
|
||||
'validated_at' => now()->toDateTimeString(),
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function guest_can_delete_invitation()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$user = factory(User::class)->create();
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id,'private' => false]);
|
||||
$this->actingAs($userTwo = factory(User::class)->create(), 'api');
|
||||
$event->guests()->attach($userTwo);
|
||||
|
||||
$response = $this->delete('api/events/'.$event->id.'/invite/delete');
|
||||
|
||||
$event = $event->fresh();
|
||||
|
||||
$response->assertStatus(204);
|
||||
$this->assertCount(0, $event->guests);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function owner_or_staff_can_invite_a_non_user_with_an_email()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$this->actingAs($user = factory(User::class)->create(), 'api');
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id, 'private' => false]);
|
||||
|
||||
$response = $this->post('api/events/'.$event->id.'/invite/with-email', ['email' => 'test@mail.fr']);
|
||||
|
||||
$event = $event->fresh();
|
||||
|
||||
$this->assertCount(1, $event->emailedGuests);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations-with-email' => [
|
||||
'data' => [
|
||||
[
|
||||
'email' => 'test@mail.fr',
|
||||
'read_at' => null,
|
||||
'validated_at' => null,
|
||||
]
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_guest_with_an_email_can_read_event()
|
||||
{
|
||||
$user = factory(User::class)->create();
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id, 'private' => false]);
|
||||
$token = (string) Str::uuid();
|
||||
$guest = new EventGuestsNonUsers(['event_id' => $event->id, 'email' => 'test@mail.fr', 'token' => $token]);
|
||||
$guest->save();
|
||||
|
||||
$response = $this->get('api/events/'.$event->id.'/with-email?email=test@mail.fr&token='.$token);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(0, $event->guests);
|
||||
$this->assertCount(1, $event->emailedGuests);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations-with-email' => [
|
||||
'data' => [
|
||||
[
|
||||
'email' => 'test@mail.fr',
|
||||
'read_at' => now()->toDateTimeString(),
|
||||
'validated_at' => null,
|
||||
]
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function a_guest_with_an_email_can_confirm_participation()
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
$user = factory(User::class)->create();
|
||||
$event = factory(Event::class)->create(['user_id' => $user->id, 'private' => false]);
|
||||
$token = (string) Str::uuid();
|
||||
$guest = new EventGuestsNonUsers(['event_id' => $event->id, 'email' => 'test@mail.fr', 'token' => $token]);
|
||||
$guest->save();
|
||||
|
||||
$response = $this->patch('api/events/'.$event->id.'/with-email', [ 'email' => 'test@mail.fr', 'token' => $token ]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
$this->assertCount(1, $event->emailedGuests);
|
||||
$response->assertJson([
|
||||
'data' => [
|
||||
'event_id' => $event->id,
|
||||
'attributes' => [
|
||||
'data' => [
|
||||
'invitations-with-email' => [
|
||||
'data' => [
|
||||
[
|
||||
'validated_at' => now()->toDateTimeString(),
|
||||
]
|
||||
],
|
||||
],
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// todo an_invitation_mail_is_seed_to_new_guest
|
||||
// https://laravel.com/docs/5.8/mocking#mail-fake
|
||||
|
||||
// todo a_guest_can_confirm_participation_in_mail
|
||||
|
||||
private function data()
|
||||
{
|
||||
@@ -313,6 +764,7 @@ class EventsTest extends TestCase
|
||||
'name' => 'Test name event',
|
||||
'description' => 'Test description event',
|
||||
'category_id' => 1,
|
||||
'private' => false,
|
||||
'start_date' => '2020-07-20 09:00:00',
|
||||
'end_date' => '2020-07-26 09:00:00',
|
||||
'location' => 'Marcillac',
|
||||
|
||||
@@ -34,7 +34,7 @@ class MemosTest extends TestCase
|
||||
|
||||
$memo = Memo::first();
|
||||
|
||||
// $this->assertCount(1, Contact::all());
|
||||
$this->assertCount(1, Memo::all());
|
||||
$this->assertEquals('Test Name', $memo->name);
|
||||
$this->assertEquals('Test Memo', $memo->memo);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user