generated from Flycro/laravel-nuxt
Compare commits
50 Commits
ae83ec3ff6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cae791ae27 | |||
| ee3d211af1 | |||
| 779bc7d91f | |||
| 53628a319e | |||
| ec92f4bd5d | |||
| 4c57faef52 | |||
| f9c83d6459 | |||
| 91592b06d7 | |||
| 5534f00a7d | |||
| 5c0dab3448 | |||
| b1cb25c823 | |||
| dbdc7c1540 | |||
| 4e65112476 | |||
| b0ea02928a | |||
| fb64c29015 | |||
| 1887add00d | |||
| 4bb02faf14 | |||
| 18388246f8 | |||
| 041232bcae | |||
| 3e58347acb | |||
| 73dab4dd6e | |||
| c2a2ae0d07 | |||
| b6f9968a46 | |||
| f497b3af55 | |||
| 5f1e3ee176 | |||
| d6ec298e56 | |||
| 74c39c8c89 | |||
| b07d0d3e9d | |||
| ecc98924e3 | |||
| 999bd8e3d4 | |||
| 78e3f0ff0c | |||
| 9fd7b9f4c3 | |||
| 1297ba88c5 | |||
| ac371e4451 | |||
| 20d03b385b | |||
| f52415346e | |||
| 829fc93c98 | |||
| 70278dc6c0 | |||
| 8d276ee0d7 | |||
| e97c3bcf83 | |||
| 05898fbd9b | |||
| 13a4730c15 | |||
| aff6a6b6e4 | |||
| 0c42894960 | |||
| 09ef7f2c9e | |||
| 62042f3457 | |||
| bffcd2f414 | |||
| 6330268d20 | |||
| 87b8d48b84 | |||
| 517e166547 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
**/node_modules/
|
||||||
|
**/dist
|
||||||
|
.git
|
||||||
22
.drone.yml
22
.drone.yml
@@ -12,16 +12,22 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- cp -af . /var/www/html/bookstack.octolabs.net
|
- cp -af . /var/www/html/bookstack.octolabs.net
|
||||||
- cd /var/www/html/bookstack.octolabs.net
|
- cd /var/www/html/bookstack.octolabs.net
|
||||||
- composer install --no-dev --optimize-autoloader
|
- git reset --hard
|
||||||
|
- git clean -fd
|
||||||
|
- composer install
|
||||||
- pnpm install
|
- pnpm install
|
||||||
- pnpm run build
|
|
||||||
- chown -R flycro:www-data /var/www/html/bookstack.octolabs.net
|
- chown -R flycro:www-data /var/www/html/bookstack.octolabs.net
|
||||||
- find /var/www/html/bookstack.octolabs.net/www -type f -exec chmod 664 {} \\;
|
- docker compose -f docker-compose-production.yml down
|
||||||
- find /var/www/html/bookstack.octolabs.net/www -type d -exec chmod 775 {} \\;
|
- docker compose -f docker-compose-production.yml build
|
||||||
- docker -f docker-compose-production.yml down
|
- docker compose -f docker-compose-production.yml up -d
|
||||||
- docker -f docker-compose-production.yml build
|
|
||||||
- docker -f docker-compose-production.yml up -d
|
|
||||||
- docker compose exec php php artisan migrate --force
|
- docker compose exec php php artisan migrate --force
|
||||||
- docker compose exec php php artisan optimize
|
- docker compose exec php php artisan optimize
|
||||||
- docker compose exec php php artisan storage:link
|
- docker compose exec php php artisan storage:link
|
||||||
- pm2 restart ecosystem.config.js
|
- git config --global --add safe.directory /var/www/html/bookstack.octolabs.net
|
||||||
|
- npx nuxi cleanup
|
||||||
|
- pnpm run build
|
||||||
|
- find /var/www/html/bookstack.octolabs.net -type f -exec chmod 664 {} \\;
|
||||||
|
- find /var/www/html/bookstack.octolabs.net -type d -exec chmod 775 {} \\;
|
||||||
|
- export GIT_HASH=$(git rev-parse --short HEAD)
|
||||||
|
- env HOME=/home/flycro GIT_HASH=$GIT_HASH pm2 stop ecosystem.config.cjs
|
||||||
|
- env HOME=/home/flycro GIT_HASH=$GIT_HASH pm2 start ecosystem.config.cjs
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ CACHE_PREFIX=
|
|||||||
|
|
||||||
MEMCACHED_HOST=127.0.0.1
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
REDIS_CLIENT=phpredis
|
REDIS_CLIENT=predis
|
||||||
REDIS_HOST=redis
|
REDIS_HOST=redis
|
||||||
REDIS_PASSWORD=null
|
REDIS_PASSWORD=null
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|||||||
42
app/Events/BookRecommendation/BookRecommendationCreated.php
Normal file
42
app/Events/BookRecommendation/BookRecommendationCreated.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events\BookRecommendation;
|
||||||
|
|
||||||
|
use App\Models\BookRecommendation;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class BookRecommendationCreated implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public BookRecommendation $bookRecommendation
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastAs(): string
|
||||||
|
{
|
||||||
|
return 'BookRecommendationCreated';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channels the event should broadcast on.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||||
|
*/
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel('BookRecommendation'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Events/BookRecommendation/BookRecommendationDeleted.php
Normal file
42
app/Events/BookRecommendation/BookRecommendationDeleted.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events\BookRecommendation;
|
||||||
|
|
||||||
|
use App\Models\BookRecommendation;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class BookRecommendationDeleted implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public BookRecommendation $bookRecommendation
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastAs(): string
|
||||||
|
{
|
||||||
|
return 'BookRecommendationDeleted';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channels the event should broadcast on.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||||
|
*/
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel('BookRecommendation'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Events/BookRecommendation/BookRecommendationUpdated.php
Normal file
42
app/Events/BookRecommendation/BookRecommendationUpdated.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events\BookRecommendation;
|
||||||
|
|
||||||
|
use App\Models\BookRecommendation;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class BookRecommendationUpdated implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public BookRecommendation $bookRecommendation
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastAs(): string
|
||||||
|
{
|
||||||
|
return 'BookRecommendationUpdated';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channels the event should broadcast on.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||||
|
*/
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel('BookRecommendation'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Events/Deadline/DeadlineCreated.php
Normal file
40
app/Events/Deadline/DeadlineCreated.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events\Deadline;
|
||||||
|
|
||||||
|
use App\Models\Deadline;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class DeadlineCreated implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(public Deadline $deadline)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastAs(): string
|
||||||
|
{
|
||||||
|
return 'DeadlineCreated';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channels the event should broadcast on.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||||
|
*/
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel('Deadline'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Events/Vote/VoteCreated.php
Normal file
42
app/Events/Vote/VoteCreated.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events\Vote;
|
||||||
|
|
||||||
|
use App\Models\Vote;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class VoteCreated implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public Vote $vote
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastAs(): string
|
||||||
|
{
|
||||||
|
return 'VoteCreated';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channels the event should broadcast on.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||||
|
*/
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel('Vote'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
|||||||
|
|
||||||
use App\Models\BookRecommendation;
|
use App\Models\BookRecommendation;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class BookRecommendationController extends Controller
|
class BookRecommendationController extends Controller
|
||||||
@@ -136,4 +137,10 @@ class BookRecommendationController extends Controller
|
|||||||
$bookRecommendation->delete();
|
$bookRecommendation->delete();
|
||||||
return response()->json(null, 204);
|
return response()->json(null, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function fetchCover(Request $request)
|
||||||
|
{
|
||||||
|
Artisan::call('book:open-library-fetch-cover-art');
|
||||||
|
return response()->json(Artisan::output());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Events\BookRecommendation\BookRecommendationCreated;
|
||||||
|
use App\Events\BookRecommendation\BookRecommendationDeleted;
|
||||||
|
use App\Events\BookRecommendation\BookRecommendationUpdated;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
class BookRecommendation extends Model
|
class BookRecommendation extends Model
|
||||||
{
|
{
|
||||||
@@ -20,20 +25,35 @@ class BookRecommendation extends Model
|
|||||||
'published_at',
|
'published_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted() :void
|
||||||
|
{
|
||||||
|
static::created(static function ($bookRecommendation) {
|
||||||
|
broadcast(new BookRecommendationCreated($bookRecommendation))->toOthers();
|
||||||
|
});
|
||||||
|
|
||||||
|
static::updated(static function ($task) {
|
||||||
|
broadcast(new BookRecommendationUpdated($task))->toOthers();
|
||||||
|
});
|
||||||
|
|
||||||
|
static::deleting(static function ($task) {
|
||||||
|
broadcast(new BookRecommendationDeleted($task))->toOthers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the user that recommended the book.
|
* Get the user that recommended the book.
|
||||||
*/
|
*/
|
||||||
public function recommender()
|
public function recommender(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class, 'recommended_by');
|
return $this->belongsTo(User::class, 'recommended_by');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function votes()
|
public function votes(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Vote::class);
|
return $this->hasMany(Vote::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deadlines()
|
public function deadlines(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Deadline::class);
|
return $this->hasMany(Deadline::class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Events\Deadline\DeadlineCreated;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
@@ -16,6 +17,13 @@ class Deadline extends Model
|
|||||||
'target_chapter',
|
'target_chapter',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted() :void
|
||||||
|
{
|
||||||
|
static::created(static function ($deadline) {
|
||||||
|
event(new DeadlineCreated($deadline));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function bookRecommendation()
|
public function bookRecommendation()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(BookRecommendation::class);
|
return $this->belongsTo(BookRecommendation::class);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Events\Vote\VoteCreated;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
@@ -14,6 +15,13 @@ class Vote extends Model
|
|||||||
'book_recommendation_id',
|
'book_recommendation_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted() :void
|
||||||
|
{
|
||||||
|
static::created(static function ($vote) {
|
||||||
|
broadcast(new VoteCreated($vote))->toOthers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
api: __DIR__.'/../routes/api.php',
|
api: __DIR__.'/../routes/api.php',
|
||||||
apiPrefix: '',
|
apiPrefix: '',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
channels: __DIR__.'/../routes/channels.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware) {
|
->withMiddleware(function (Middleware $middleware) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"laravel/tinker": "^2.9",
|
"laravel/tinker": "^2.9",
|
||||||
"league/flysystem-aws-s3-v3": "^3.24",
|
"league/flysystem-aws-s3-v3": "^3.24",
|
||||||
"predis/predis": "*",
|
"predis/predis": "*",
|
||||||
|
"pusher/pusher-php-server": "^7.2",
|
||||||
"spatie/laravel-permission": "^6.4"
|
"spatie/laravel-permission": "^6.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|||||||
359
composer.lock
generated
359
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "ec31886b4063db8aa1efc22c187f614a",
|
"content-hash": "572762c5d2b5b91e9a8012b47f3cd07f",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aws/aws-crt-php",
|
"name": "aws/aws-crt-php",
|
||||||
@@ -62,16 +62,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "aws/aws-sdk-php",
|
"name": "aws/aws-sdk-php",
|
||||||
"version": "3.301.1",
|
"version": "3.301.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||||
"reference": "0a910d2b35e7087337cdf3569dc9b6ce232aafba"
|
"reference": "18c0ebd71d3071304f1ea02aa9af75f95863177a"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/0a910d2b35e7087337cdf3569dc9b6ce232aafba",
|
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/18c0ebd71d3071304f1ea02aa9af75f95863177a",
|
||||||
"reference": "0a910d2b35e7087337cdf3569dc9b6ce232aafba",
|
"reference": "18c0ebd71d3071304f1ea02aa9af75f95863177a",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -151,9 +151,9 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.301.1"
|
"source": "https://github.com/aws/aws-sdk-php/tree/3.301.6"
|
||||||
},
|
},
|
||||||
"time": "2024-03-15T18:14:42+00:00"
|
"time": "2024-03-22T18:05:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -1408,16 +1408,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "intervention/image",
|
"name": "intervention/image",
|
||||||
"version": "3.4.0",
|
"version": "3.5.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Intervention/image.git",
|
"url": "https://github.com/Intervention/image.git",
|
||||||
"reference": "fe1b0e2e64157133322974c28b44c25c2770a0c5"
|
"reference": "408d3655c7705339e8c79731ea7efb51546cfa10"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/Intervention/image/zipball/fe1b0e2e64157133322974c28b44c25c2770a0c5",
|
"url": "https://api.github.com/repos/Intervention/image/zipball/408d3655c7705339e8c79731ea7efb51546cfa10",
|
||||||
"reference": "fe1b0e2e64157133322974c28b44c25c2770a0c5",
|
"reference": "408d3655c7705339e8c79731ea7efb51546cfa10",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1428,7 +1428,7 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"phpstan/phpstan": "^1",
|
"phpstan/phpstan": "^1",
|
||||||
"phpunit/phpunit": "^9",
|
"phpunit/phpunit": "^10.0",
|
||||||
"slevomat/coding-standard": "~8.0",
|
"slevomat/coding-standard": "~8.0",
|
||||||
"squizlabs/php_codesniffer": "^3.8"
|
"squizlabs/php_codesniffer": "^3.8"
|
||||||
},
|
},
|
||||||
@@ -1464,7 +1464,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/Intervention/image/issues",
|
"issues": "https://github.com/Intervention/image/issues",
|
||||||
"source": "https://github.com/Intervention/image/tree/3.4.0"
|
"source": "https://github.com/Intervention/image/tree/3.5.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -1476,20 +1476,20 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-02-14T15:11:21+00:00"
|
"time": "2024-03-13T16:26:15+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "jaybizzle/crawler-detect",
|
"name": "jaybizzle/crawler-detect",
|
||||||
"version": "v1.2.116",
|
"version": "v1.2.117",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
|
"url": "https://github.com/JayBizzle/Crawler-Detect.git",
|
||||||
"reference": "97e9fe30219e60092e107651abb379a38b342921"
|
"reference": "6785557f03d0fa9e2205352ebae9a12a4484cc8e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/97e9fe30219e60092e107651abb379a38b342921",
|
"url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/6785557f03d0fa9e2205352ebae9a12a4484cc8e",
|
||||||
"reference": "97e9fe30219e60092e107651abb379a38b342921",
|
"reference": "6785557f03d0fa9e2205352ebae9a12a4484cc8e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1526,9 +1526,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/JayBizzle/Crawler-Detect/issues",
|
"issues": "https://github.com/JayBizzle/Crawler-Detect/issues",
|
||||||
"source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.116"
|
"source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.117"
|
||||||
},
|
},
|
||||||
"time": "2023-07-21T15:49:49+00:00"
|
"time": "2024-03-19T22:51:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laminas/laminas-diactoros",
|
"name": "laminas/laminas-diactoros",
|
||||||
@@ -1617,16 +1617,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/framework",
|
"name": "laravel/framework",
|
||||||
"version": "v11.0.7",
|
"version": "v11.0.8",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/framework.git",
|
"url": "https://github.com/laravel/framework.git",
|
||||||
"reference": "28eabe9dcdcb017a21ce226eda4538c5c8c93b1c"
|
"reference": "0379a7ccb77e2029c43ce508fa76e251a0d68fce"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/framework/zipball/28eabe9dcdcb017a21ce226eda4538c5c8c93b1c",
|
"url": "https://api.github.com/repos/laravel/framework/zipball/0379a7ccb77e2029c43ce508fa76e251a0d68fce",
|
||||||
"reference": "28eabe9dcdcb017a21ce226eda4538c5c8c93b1c",
|
"reference": "0379a7ccb77e2029c43ce508fa76e251a0d68fce",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1729,7 +1729,7 @@
|
|||||||
"league/flysystem-sftp-v3": "^3.0",
|
"league/flysystem-sftp-v3": "^3.0",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nyholm/psr7": "^1.2",
|
"nyholm/psr7": "^1.2",
|
||||||
"orchestra/testbench-core": "^9.0",
|
"orchestra/testbench-core": "^9.0.6",
|
||||||
"pda/pheanstalk": "^5.0",
|
"pda/pheanstalk": "^5.0",
|
||||||
"phpstan/phpstan": "^1.4.7",
|
"phpstan/phpstan": "^1.4.7",
|
||||||
"phpunit/phpunit": "^10.5|^11.0",
|
"phpunit/phpunit": "^10.5|^11.0",
|
||||||
@@ -1818,7 +1818,7 @@
|
|||||||
"issues": "https://github.com/laravel/framework/issues",
|
"issues": "https://github.com/laravel/framework/issues",
|
||||||
"source": "https://github.com/laravel/framework"
|
"source": "https://github.com/laravel/framework"
|
||||||
},
|
},
|
||||||
"time": "2024-03-15T23:17:58+00:00"
|
"time": "2024-03-21T14:15:49+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/octane",
|
"name": "laravel/octane",
|
||||||
@@ -2414,16 +2414,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/flysystem",
|
"name": "league/flysystem",
|
||||||
"version": "3.25.0",
|
"version": "3.25.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/thephpleague/flysystem.git",
|
"url": "https://github.com/thephpleague/flysystem.git",
|
||||||
"reference": "4c44347133618cccd9b3df1729647a1577b4ad99"
|
"reference": "abbd664eb4381102c559d358420989f835208f18"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4c44347133618cccd9b3df1729647a1577b4ad99",
|
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/abbd664eb4381102c559d358420989f835208f18",
|
||||||
"reference": "4c44347133618cccd9b3df1729647a1577b4ad99",
|
"reference": "abbd664eb4381102c559d358420989f835208f18",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2488,7 +2488,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/thephpleague/flysystem/issues",
|
"issues": "https://github.com/thephpleague/flysystem/issues",
|
||||||
"source": "https://github.com/thephpleague/flysystem/tree/3.25.0"
|
"source": "https://github.com/thephpleague/flysystem/tree/3.25.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2500,20 +2500,20 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-03-09T17:06:45+00:00"
|
"time": "2024-03-16T12:53:19+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/flysystem-aws-s3-v3",
|
"name": "league/flysystem-aws-s3-v3",
|
||||||
"version": "3.24.0",
|
"version": "3.25.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
|
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
|
||||||
"reference": "809474e37b7fb1d1f8bcc0f8a98bc1cae99aa513"
|
"reference": "6a5be0e6d6a93574e80805c9cc108a4b63c824d8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/809474e37b7fb1d1f8bcc0f8a98bc1cae99aa513",
|
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/6a5be0e6d6a93574e80805c9cc108a4b63c824d8",
|
||||||
"reference": "809474e37b7fb1d1f8bcc0f8a98bc1cae99aa513",
|
"reference": "6a5be0e6d6a93574e80805c9cc108a4b63c824d8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2553,7 +2553,7 @@
|
|||||||
"storage"
|
"storage"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.24.0"
|
"source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.25.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2565,20 +2565,20 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-01-26T18:43:21+00:00"
|
"time": "2024-03-15T19:58:44+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/flysystem-local",
|
"name": "league/flysystem-local",
|
||||||
"version": "3.23.1",
|
"version": "3.25.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/thephpleague/flysystem-local.git",
|
"url": "https://github.com/thephpleague/flysystem-local.git",
|
||||||
"reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00"
|
"reference": "61a6a90d6e999e4ddd9ce5adb356de0939060b92"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/b884d2bf9b53bb4804a56d2df4902bb51e253f00",
|
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/61a6a90d6e999e4ddd9ce5adb356de0939060b92",
|
||||||
"reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00",
|
"reference": "61a6a90d6e999e4ddd9ce5adb356de0939060b92",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2612,8 +2612,7 @@
|
|||||||
"local"
|
"local"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/thephpleague/flysystem-local/issues",
|
"source": "https://github.com/thephpleague/flysystem-local/tree/3.25.1"
|
||||||
"source": "https://github.com/thephpleague/flysystem-local/tree/3.23.1"
|
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2625,7 +2624,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-01-26T18:25:23+00:00"
|
"time": "2024-03-15T19:58:44+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/mime-type-detection",
|
"name": "league/mime-type-detection",
|
||||||
@@ -3570,6 +3569,142 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-03-06T16:17:14+00:00"
|
"time": "2024-03-06T16:17:14+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "paragonie/random_compat",
|
||||||
|
"version": "v9.99.100",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/paragonie/random_compat.git",
|
||||||
|
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||||
|
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">= 7"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "4.*|5.*",
|
||||||
|
"vimeo/psalm": "^1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Paragon Initiative Enterprises",
|
||||||
|
"email": "security@paragonie.com",
|
||||||
|
"homepage": "https://paragonie.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||||
|
"keywords": [
|
||||||
|
"csprng",
|
||||||
|
"polyfill",
|
||||||
|
"pseudorandom",
|
||||||
|
"random"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "info@paragonie.com",
|
||||||
|
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||||
|
"source": "https://github.com/paragonie/random_compat"
|
||||||
|
},
|
||||||
|
"time": "2020-10-15T08:29:30+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "paragonie/sodium_compat",
|
||||||
|
"version": "v1.20.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/paragonie/sodium_compat.git",
|
||||||
|
"reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/e592a3e06d1fa0d43988c7c7d9948ca836f644b6",
|
||||||
|
"reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"paragonie/random_compat": ">=1",
|
||||||
|
"php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
|
||||||
|
"ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"autoload.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"ISC"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Paragon Initiative Enterprises",
|
||||||
|
"email": "security@paragonie.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frank Denis",
|
||||||
|
"email": "jedisct1@pureftpd.org"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists",
|
||||||
|
"keywords": [
|
||||||
|
"Authentication",
|
||||||
|
"BLAKE2b",
|
||||||
|
"ChaCha20",
|
||||||
|
"ChaCha20-Poly1305",
|
||||||
|
"Chapoly",
|
||||||
|
"Curve25519",
|
||||||
|
"Ed25519",
|
||||||
|
"EdDSA",
|
||||||
|
"Edwards-curve Digital Signature Algorithm",
|
||||||
|
"Elliptic Curve Diffie-Hellman",
|
||||||
|
"Poly1305",
|
||||||
|
"Pure-PHP cryptography",
|
||||||
|
"RFC 7748",
|
||||||
|
"RFC 8032",
|
||||||
|
"Salpoly",
|
||||||
|
"Salsa20",
|
||||||
|
"X25519",
|
||||||
|
"XChaCha20-Poly1305",
|
||||||
|
"XSalsa20-Poly1305",
|
||||||
|
"Xchacha20",
|
||||||
|
"Xsalsa20",
|
||||||
|
"aead",
|
||||||
|
"cryptography",
|
||||||
|
"ecdh",
|
||||||
|
"elliptic curve",
|
||||||
|
"elliptic curve cryptography",
|
||||||
|
"encryption",
|
||||||
|
"libsodium",
|
||||||
|
"php",
|
||||||
|
"public-key cryptography",
|
||||||
|
"secret-key cryptography",
|
||||||
|
"side-channel resistant"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/paragonie/sodium_compat/issues",
|
||||||
|
"source": "https://github.com/paragonie/sodium_compat/tree/v1.20.0"
|
||||||
|
},
|
||||||
|
"time": "2023-04-30T00:54:53+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
"version": "1.9.2",
|
"version": "1.9.2",
|
||||||
@@ -4120,16 +4255,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psy/psysh",
|
"name": "psy/psysh",
|
||||||
"version": "v0.12.1",
|
"version": "v0.12.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/bobthecow/psysh.git",
|
"url": "https://github.com/bobthecow/psysh.git",
|
||||||
"reference": "39621c73e0754328252f032c6997b983afc50431"
|
"reference": "9185c66c2165bbf4d71de78a69dccf4974f9538d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/39621c73e0754328252f032c6997b983afc50431",
|
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/9185c66c2165bbf4d71de78a69dccf4974f9538d",
|
||||||
"reference": "39621c73e0754328252f032c6997b983afc50431",
|
"reference": "9185c66c2165bbf4d71de78a69dccf4974f9538d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4193,9 +4328,70 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/bobthecow/psysh/issues",
|
"issues": "https://github.com/bobthecow/psysh/issues",
|
||||||
"source": "https://github.com/bobthecow/psysh/tree/v0.12.1"
|
"source": "https://github.com/bobthecow/psysh/tree/v0.12.2"
|
||||||
},
|
},
|
||||||
"time": "2024-03-15T03:22:57+00:00"
|
"time": "2024-03-17T01:53:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pusher/pusher-php-server",
|
||||||
|
"version": "7.2.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/pusher/pusher-http-php.git",
|
||||||
|
"reference": "de2f72296808f9cafa6a4462b15a768ff130cddb"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/de2f72296808f9cafa6a4462b15a768ff130cddb",
|
||||||
|
"reference": "de2f72296808f9cafa6a4462b15a768ff130cddb",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
|
"paragonie/sodium_compat": "^1.6",
|
||||||
|
"php": "^7.3|^8.0",
|
||||||
|
"psr/log": "^1.0|^2.0|^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"overtrue/phplint": "^2.3",
|
||||||
|
"phpunit/phpunit": "^9.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Pusher\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "Library for interacting with the Pusher REST API",
|
||||||
|
"keywords": [
|
||||||
|
"events",
|
||||||
|
"messaging",
|
||||||
|
"php-pusher-server",
|
||||||
|
"publish",
|
||||||
|
"push",
|
||||||
|
"pusher",
|
||||||
|
"real time",
|
||||||
|
"real-time",
|
||||||
|
"realtime",
|
||||||
|
"rest",
|
||||||
|
"trigger"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/pusher/pusher-http-php/issues",
|
||||||
|
"source": "https://github.com/pusher/pusher-http-php/tree/7.2.4"
|
||||||
|
},
|
||||||
|
"time": "2023-12-15T10:58:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ralouphie/getallheaders",
|
"name": "ralouphie/getallheaders",
|
||||||
@@ -7438,16 +7634,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/pcre",
|
"name": "composer/pcre",
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/pcre.git",
|
"url": "https://github.com/composer/pcre.git",
|
||||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace"
|
"reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/pcre/zipball/4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
"url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
|
||||||
"reference": "4775f35b2d70865807c89d32c8e7385b86eb0ace",
|
"reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -7489,7 +7685,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/composer/pcre/issues",
|
"issues": "https://github.com/composer/pcre/issues",
|
||||||
"source": "https://github.com/composer/pcre/tree/3.1.2"
|
"source": "https://github.com/composer/pcre/tree/3.1.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -7505,7 +7701,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-03-07T15:38:35+00:00"
|
"time": "2024-03-19T10:26:25+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/deprecations",
|
"name": "doctrine/deprecations",
|
||||||
@@ -7807,16 +8003,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/sail",
|
"name": "laravel/sail",
|
||||||
"version": "v1.29.0",
|
"version": "v1.29.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/sail.git",
|
"url": "https://github.com/laravel/sail.git",
|
||||||
"reference": "e40cc7ffb5186c45698dbd47e9477e0e429396d0"
|
"reference": "8be4a31150eab3b46af11a2e7b2c4632eefaad7e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/sail/zipball/e40cc7ffb5186c45698dbd47e9477e0e429396d0",
|
"url": "https://api.github.com/repos/laravel/sail/zipball/8be4a31150eab3b46af11a2e7b2c4632eefaad7e",
|
||||||
"reference": "e40cc7ffb5186c45698dbd47e9477e0e429396d0",
|
"reference": "8be4a31150eab3b46af11a2e7b2c4632eefaad7e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -7824,6 +8020,7 @@
|
|||||||
"illuminate/contracts": "^9.52.16|^10.0|^11.0",
|
"illuminate/contracts": "^9.52.16|^10.0|^11.0",
|
||||||
"illuminate/support": "^9.52.16|^10.0|^11.0",
|
"illuminate/support": "^9.52.16|^10.0|^11.0",
|
||||||
"php": "^8.0",
|
"php": "^8.0",
|
||||||
|
"symfony/console": "^6.0|^7.0",
|
||||||
"symfony/yaml": "^6.0|^7.0"
|
"symfony/yaml": "^6.0|^7.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
@@ -7865,7 +8062,7 @@
|
|||||||
"issues": "https://github.com/laravel/sail/issues",
|
"issues": "https://github.com/laravel/sail/issues",
|
||||||
"source": "https://github.com/laravel/sail"
|
"source": "https://github.com/laravel/sail"
|
||||||
},
|
},
|
||||||
"time": "2024-03-08T16:32:33+00:00"
|
"time": "2024-03-20T20:09:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/telescope",
|
"name": "laravel/telescope",
|
||||||
@@ -7937,16 +8134,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mockery/mockery",
|
"name": "mockery/mockery",
|
||||||
"version": "1.6.9",
|
"version": "1.6.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/mockery/mockery.git",
|
"url": "https://github.com/mockery/mockery.git",
|
||||||
"reference": "0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06"
|
"reference": "81a161d0b135df89951abd52296adf97deb0723d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/mockery/mockery/zipball/0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06",
|
"url": "https://api.github.com/repos/mockery/mockery/zipball/81a161d0b135df89951abd52296adf97deb0723d",
|
||||||
"reference": "0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06",
|
"reference": "81a161d0b135df89951abd52296adf97deb0723d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -7958,8 +8155,8 @@
|
|||||||
"phpunit/phpunit": "<8.0"
|
"phpunit/phpunit": "<8.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^8.5 || ^9.6.10",
|
"phpunit/phpunit": "^8.5 || ^9.6.17",
|
||||||
"symplify/easy-coding-standard": "^12.0.8"
|
"symplify/easy-coding-standard": "^12.1.14"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -8016,7 +8213,7 @@
|
|||||||
"security": "https://github.com/mockery/mockery/security/advisories",
|
"security": "https://github.com/mockery/mockery/security/advisories",
|
||||||
"source": "https://github.com/mockery/mockery"
|
"source": "https://github.com/mockery/mockery"
|
||||||
},
|
},
|
||||||
"time": "2023-12-10T02:24:34+00:00"
|
"time": "2024-03-21T18:34:15+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "myclabs/deep-copy",
|
"name": "myclabs/deep-copy",
|
||||||
@@ -8405,16 +8602,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpdoc-parser",
|
"name": "phpstan/phpdoc-parser",
|
||||||
"version": "1.26.0",
|
"version": "1.27.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||||
"reference": "231e3186624c03d7e7c890ec662b81e6b0405227"
|
"reference": "86e4d5a4b036f8f0be1464522f4c6b584c452757"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227",
|
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/86e4d5a4b036f8f0be1464522f4c6b584c452757",
|
||||||
"reference": "231e3186624c03d7e7c890ec662b81e6b0405227",
|
"reference": "86e4d5a4b036f8f0be1464522f4c6b584c452757",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -8446,9 +8643,9 @@
|
|||||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0"
|
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.27.0"
|
||||||
},
|
},
|
||||||
"time": "2024-02-23T16:05:55+00:00"
|
"time": "2024-03-21T13:14:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
@@ -8773,16 +8970,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/phpunit",
|
"name": "phpunit/phpunit",
|
||||||
"version": "10.5.13",
|
"version": "10.5.15",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||||
"reference": "20a63fc1c6db29b15da3bd02d4b6cf59900088a7"
|
"reference": "86376e05e8745ed81d88232ff92fee868247b07b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/20a63fc1c6db29b15da3bd02d4b6cf59900088a7",
|
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86376e05e8745ed81d88232ff92fee868247b07b",
|
||||||
"reference": "20a63fc1c6db29b15da3bd02d4b6cf59900088a7",
|
"reference": "86376e05e8745ed81d88232ff92fee868247b07b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -8854,7 +9051,7 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.13"
|
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.15"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -8870,7 +9067,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-03-12T15:37:41+00:00"
|
"time": "2024-03-22T04:17:47+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/cli-parser",
|
"name": "sebastian/cli-parser",
|
||||||
|
|||||||
82
config/broadcasting.php
Normal file
82
config/broadcasting.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Default Broadcaster
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the default broadcaster that will be used by the
|
||||||
|
| framework when an event needs to be broadcast. You may set this to
|
||||||
|
| any of the connections defined in the "connections" array below.
|
||||||
|
|
|
||||||
|
| Supported: "reverb", "pusher", "ably", "redis", "log", "null"
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'default' => env('BROADCAST_CONNECTION', 'null'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Broadcast Connections
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may define all of the broadcast connections that will be used
|
||||||
|
| to broadcast events to other systems or over WebSockets. Samples of
|
||||||
|
| each available type of connection are provided inside this array.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'connections' => [
|
||||||
|
|
||||||
|
'reverb' => [
|
||||||
|
'driver' => 'reverb',
|
||||||
|
'key' => env('REVERB_APP_KEY'),
|
||||||
|
'secret' => env('REVERB_APP_SECRET'),
|
||||||
|
'app_id' => env('REVERB_APP_ID'),
|
||||||
|
'options' => [
|
||||||
|
'host' => env('REVERB_HOST'),
|
||||||
|
'port' => env('REVERB_PORT', 443),
|
||||||
|
'scheme' => env('REVERB_SCHEME', 'https'),
|
||||||
|
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
||||||
|
],
|
||||||
|
'client_options' => [
|
||||||
|
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'pusher' => [
|
||||||
|
'driver' => 'pusher',
|
||||||
|
'key' => env('PUSHER_APP_KEY'),
|
||||||
|
'secret' => env('PUSHER_APP_SECRET'),
|
||||||
|
'app_id' => env('PUSHER_APP_ID'),
|
||||||
|
'options' => [
|
||||||
|
'cluster' => env('PUSHER_APP_CLUSTER'),
|
||||||
|
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
|
||||||
|
'port' => env('PUSHER_PORT', 443),
|
||||||
|
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||||
|
'encrypted' => true,
|
||||||
|
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||||
|
],
|
||||||
|
'client_options' => [
|
||||||
|
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'ably' => [
|
||||||
|
'driver' => 'ably',
|
||||||
|
'key' => env('ABLY_KEY'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'log' => [
|
||||||
|
'driver' => 'log',
|
||||||
|
],
|
||||||
|
|
||||||
|
'null' => [
|
||||||
|
'driver' => 'null',
|
||||||
|
],
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -98,7 +98,7 @@ class BookRecommendationsTableSeeder extends Seeder
|
|||||||
'book_name' => 'After Dark',
|
'book_name' => 'After Dark',
|
||||||
'author' => 'Haruki Murakami',
|
'author' => 'Haruki Murakami',
|
||||||
'description' => 'Geschichten zwischen Mitternacht und Morgengrauen: Murakami begleitet seine Helden eine Nacht lang und zeichnet ein eindrucksvolles, geheimnisvoll schillerndes Großstadtporträt: Die 19-jährige Mari, ihre schöne, aber unglückliche Schwester Eri, der unscheinbare Posaunist, die Prostituierte aus einem Love Hotel und ein gewissenloser Freier streifen durch das nächtliche Tokyo.',
|
'description' => 'Geschichten zwischen Mitternacht und Morgengrauen: Murakami begleitet seine Helden eine Nacht lang und zeichnet ein eindrucksvolles, geheimnisvoll schillerndes Großstadtporträt: Die 19-jährige Mari, ihre schöne, aber unglückliche Schwester Eri, der unscheinbare Posaunist, die Prostituierte aus einem Love Hotel und ein gewissenloser Freier streifen durch das nächtliche Tokyo.',
|
||||||
'isbn' => '978-3-44273564-8',
|
'isbn' => '978-3-83217940-3',
|
||||||
'pages' => 191,
|
'pages' => 191,
|
||||||
'recommended_by' => 2,
|
'recommended_by' => 2,
|
||||||
'cover_image' => null,
|
'cover_image' => null,
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ module.exports = {
|
|||||||
port: '3377',
|
port: '3377',
|
||||||
exec_mode: 'cluster',
|
exec_mode: 'cluster',
|
||||||
instances: 'max',
|
instances: 'max',
|
||||||
script: './.output/server/index.mjs'
|
script: './.output/server/index.mjs',
|
||||||
|
env_production: {
|
||||||
|
"GIT_HASH": process.env.GIT_HASH,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
import pkg from './package.json'
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
srcDir: 'nuxt/',
|
srcDir: 'nuxt/',
|
||||||
|
|
||||||
@@ -76,6 +78,8 @@ export default defineNuxtConfig({
|
|||||||
apiBase: process.env.API_URL,
|
apiBase: process.env.API_URL,
|
||||||
apiPrefix: '/api/v1',
|
apiPrefix: '/api/v1',
|
||||||
storageBase: `${process.env.API_URL}/storage/`,
|
storageBase: `${process.env.API_URL}/storage/`,
|
||||||
|
packageVersion: pkg.version,
|
||||||
|
gitHash: process.env.GIT_HASH,
|
||||||
providers: {
|
providers: {
|
||||||
google: {
|
google: {
|
||||||
name: 'Google',
|
name: 'Google',
|
||||||
@@ -83,6 +87,17 @@ export default defineNuxtConfig({
|
|||||||
color: 'gray',
|
color: 'gray',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
echo: {
|
||||||
|
broadcaster: process.env.BROADCAST_CONNECTION,
|
||||||
|
key: process.env.PUSHER_APP_KEY,
|
||||||
|
cluster: process.env.PUSHER_APP_CLUSTER,
|
||||||
|
wsHost: process.env.PUSHER_APP_HOST,
|
||||||
|
wsPort: process.env.PUSHER_APP_PORT,
|
||||||
|
forceTLS: process.env.PUSHER_APP_TLS,
|
||||||
|
encrypted: process.env.PUSHER_APP_ENCRYPTED,
|
||||||
|
disableStats: process.env.PUSHER_APP_DISABLE_STATS,
|
||||||
|
enabledTransports: ['ws', 'wss'],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NuxtLayout>
|
<UMain>
|
||||||
<NuxtPage />
|
<NuxtLayout>
|
||||||
</NuxtLayout>
|
<NuxtPage />
|
||||||
|
</NuxtLayout>
|
||||||
|
</UMain>
|
||||||
|
<Footer />
|
||||||
|
|
||||||
<UNotifications />
|
<UNotifications />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
33
nuxt/components/Footer.vue
Normal file
33
nuxt/components/Footer.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import party from 'party-js'
|
||||||
|
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
function confirmDeadline(event) {
|
||||||
|
party.confetti(event)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<UDivider />
|
||||||
|
<UFooter>
|
||||||
|
<template #left>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<!-- <OctolabsLogo class="fill-primary rotate-45" width="36" /> -->
|
||||||
|
<div class="text-xs">
|
||||||
|
Copyright © {{ new Date().getFullYear() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #center>
|
||||||
|
Made with <span class="cursor-pointer transition duration-500 hover:scale-125 hover:animate-pulse" @click="confirmDeadline">❤️</span> by <a class="text-primary hover:text-primary-600 ml-1" href="https://flycro.me" target="_blank">Flycro</a>
|
||||||
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<div class="font-mono text-xs">
|
||||||
|
{{ config.public.packageVersion }} - {{ config.public.gitHash !== '' ? config.public.gitHash : '0000000' }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UFooter>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -17,7 +17,7 @@ const links = []
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #panel>
|
<template #panel>
|
||||||
<UNavigationTree :links="links" />
|
<Navigation />
|
||||||
</template>
|
</template>
|
||||||
</UHeader>
|
</UHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ if (authStore.user?.roles.includes('admin')) {
|
|||||||
{
|
{
|
||||||
label: 'Votes',
|
label: 'Votes',
|
||||||
to: '/admin/votes',
|
to: '/admin/votes',
|
||||||
icon: 'i-heroicons-star',
|
icon: 'i-heroicons-check-circle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Jobs',
|
||||||
|
to: '/admin/jobs',
|
||||||
|
icon: 'i-heroicons-briefcase',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -44,7 +49,5 @@ if (authStore.user?.roles.includes('admin')) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UContainer>
|
<UNavigationTree :links="links" />
|
||||||
<UNavigationTree :links="links" />
|
|
||||||
</UContainer>
|
|
||||||
</template>
|
</template>
|
||||||
31
nuxt/components/OctolabsLogo.vue
Normal file
31
nuxt/components/OctolabsLogo.vue
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
width?: string
|
||||||
|
height?: string
|
||||||
|
class?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const cssClasses = computed(() => {
|
||||||
|
return twMerge([
|
||||||
|
'fill-current',
|
||||||
|
props.class,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" :class="cssClasses" :width="props.width" :height="props.height" viewBox="0 0 101.27 145.9">
|
||||||
|
<g id="Ebene_2" data-name="Ebene 2">
|
||||||
|
<g id="Ebene_8" data-name="Ebene 8">
|
||||||
|
<path d="M50.63 0C22.67 0 0 21.46 0 47.93 0 61.85 6.69 76.49 19.35 85a69.67 69.67 0 0 0 21.59 10c6.49 1.62 13.4 1.7 19.38 1.7 28 0 40.95-22.34 40.95-48.81S78.6 0 50.63 0Zm15.18 65.91c-5.46 5.24-13.51 7.59-20.51 5.71a20.29 20.29 0 0 1-14.43-14.47c-1.49-5.43-.48-12.33 2.88-16.9 2.45-3.34 4.76-6 8.51-8a20.24 20.24 0 0 1 15-2 24.4 24.4 0 0 1 6.67 3.14 22.12 22.12 0 0 1 2.48 2.15 23.32 23.32 0 0 1 6.26 14.76c.07 8.44-5.17 13.98-6.86 15.61Z" class="cls-1" />
|
||||||
|
<path d="M57.8 49.89c.67-2.92-.89-5.08-3.49-6.15-3.13-1.3-7 .07-9.14 2.5A8.35 8.35 0 0 0 45 57.1c4.53 5.9 14 3.39 15.73-3.68 1.79-7.42-6.38-14.55-13.25-10.23-3.17 2-5.39 7.23-4.11 10.8s5.45 4.43 9.14 3.94c4.58-.62 7.51-5.9 5.11-10M19.62 74.51c-4.41 4-7.87 9.7-6.41 15.89 1.59 6.71 7.23 11.22 5.08 18.75-1.83 6.42-8.36 10-10.34 16.38-1.54 5-.45 9.84 3.78 13 3.1 2.31 6.1-2.9 3-5.18-5.27-3.92 2.69-11.66 5-14.57A21.51 21.51 0 0 0 24.72 107a21 21 0 0 0-2.8-12C20.58 92.51 19 90.3 19 87.43c0-3.62 2.32-6.35 4.87-8.68 2.86-2.61-1.39-6.84-4.24-4.24ZM55.6 88c-9.62 2.3-11.4 12.24-7 20 3 5.23 7.65 11.16 5.78 17.61-1.37 4.72-5.26 7.5-5.63 12.71-.27 3.85 5.73 3.83 6 0s3.7-6.47 5-9.88a18.49 18.49 0 0 0 0-11.76c-1.83-6.1-14.25-20.06-2.57-22.86 3.75-.89 2.16-6.68-1.6-5.78ZM78.89 84.18c10 4.89.34 21.37 1.44 29.69.56 4.26 2.72 7.27 5.31 10.55 4.11 5.2 5.53 10.92 2 17-1.93 3.35 3.26 6.38 5.18 3a20 20 0 0 0 1.93-16.37c-2.06-6.19-8.63-9.79-8.63-16.72 0-10.37 9-25.92-4.23-32.35-3.46-1.69-6.51 3.48-3 5.18Z" class="cls-1" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -13,7 +13,7 @@ const { refresh: onClick, status } = useFetch<any>(`admin/add-total-votes-all`,
|
|||||||
async onResponse({ response }) {
|
async onResponse({ response }) {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
useToast().add({
|
useToast().add({
|
||||||
icon: 'i-heroicons-check-circle-20-solid',
|
icon: 'i-heroicons-check-circle-solid',
|
||||||
title: 'Es wurden allen Nutzern 2 Votes hinzugefügt.',
|
title: 'Es wurden allen Nutzern 2 Votes hinzugefügt.',
|
||||||
color: 'emerald',
|
color: 'emerald',
|
||||||
})
|
})
|
||||||
@@ -25,12 +25,12 @@ const { refresh: onClick, status } = useFetch<any>(`admin/add-total-votes-all`,
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UButton icon="i-heroicons-star" solid label="Nutzern Votes hinzufügen" @click="isOpen = true" />
|
<UButton icon="i-heroicons-check-circle" solid label="Nutzern Votes hinzufügen" @click="isOpen = true" />
|
||||||
<UDashboardModal
|
<UDashboardModal
|
||||||
v-model="isOpen"
|
v-model="isOpen"
|
||||||
title="Votes hinzufügen"
|
title="Votes hinzufügen"
|
||||||
description="Bist du dir sicher das du jedem Benutzer 2 Votes geben möchtest?"
|
description="Bist du dir sicher das du jedem Benutzer 2 Votes geben möchtest?"
|
||||||
icon="i-heroicons-star"
|
icon="i-heroicons-check-circle"
|
||||||
:ui="{
|
:ui="{
|
||||||
icon: { base: 'text-primary dark:text-primary-400' } as any,
|
icon: { base: 'text-primary dark:text-primary-400' } as any,
|
||||||
footer: { base: 'ml-16' } as any,
|
footer: { base: 'ml-16' } as any,
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import NewBookRecommendation from '~/components/modal/NewBookRecommendation.vue'
|
|||||||
import CastVote from '~/components/modal/CastVote.vue'
|
import CastVote from '~/components/modal/CastVote.vue'
|
||||||
|
|
||||||
const dayjs = useDayjs()
|
const dayjs = useDayjs()
|
||||||
|
const { $echo } = useNuxtApp()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const bookRecommendationStore = useBookRecommendationStore()
|
const bookRecommendationStore = useBookRecommendationStore()
|
||||||
bookRecommendationStore.fetchRecommendations()
|
bookRecommendationStore.fetchRecommendations()
|
||||||
@@ -14,14 +16,17 @@ const columns = [
|
|||||||
{
|
{
|
||||||
key: 'book_name',
|
key: 'book_name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'author',
|
key: 'author',
|
||||||
label: 'Autor',
|
label: 'Autor',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'published_at',
|
key: 'published_at',
|
||||||
label: 'Erstveröffentlichung',
|
label: 'Erstveröffentlichung',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'description',
|
key: 'description',
|
||||||
@@ -30,18 +35,22 @@ const columns = [
|
|||||||
{
|
{
|
||||||
key: 'pages',
|
key: 'pages',
|
||||||
label: 'Seiten',
|
label: 'Seiten',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'recommender.name',
|
key: 'recommender.name',
|
||||||
label: 'Empfohlen von',
|
label: 'Empfohlen von',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
label: 'Erstellt am',
|
label: 'Erstellt am',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'status',
|
key: 'status',
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'votes',
|
key: 'votes',
|
||||||
@@ -61,23 +70,49 @@ const sort = ref({
|
|||||||
function resolveStatus(status: string) {
|
function resolveStatus(status: string) {
|
||||||
return bookRecommendationStore.statusOptions.find(option => option.value === status)
|
return bookRecommendationStore.statusOptions.find(option => option.value === status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rows = computed(() => {
|
||||||
|
// return all bookRecommendationStore.recommendations but add actions
|
||||||
|
return bookRecommendationStore.recommendations.map((recommendation) => {
|
||||||
|
return {
|
||||||
|
...recommendation,
|
||||||
|
actions: {
|
||||||
|
class: '!max-w-96',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$echo.private(`BookRecommendation`)
|
||||||
|
.listen('.BookRecommendationUpdated', (e) => {
|
||||||
|
bookRecommendationStore.updateRecommendationWS(e.bookRecommendation)
|
||||||
|
})
|
||||||
|
.listen('.BookRecommendationDeleted', (e) => {
|
||||||
|
bookRecommendationStore.deleteRecommendationWS(e.bookRecommendation)
|
||||||
|
})
|
||||||
|
.listen('.BookRecommendationCreated', (e) => {
|
||||||
|
bookRecommendationStore.createRecommendationWS(e.bookRecommendation)
|
||||||
|
})
|
||||||
|
authStore.socketId = $echo.socketId()
|
||||||
|
|
||||||
|
$echo.private(`Vote`)
|
||||||
|
.listen('.VoteCreated', (e) => {
|
||||||
|
bookRecommendationStore.createVoteWS(e.vote)
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<NewBookRecommendation />
|
<NewBookRecommendation />
|
||||||
<UTable :sort="sort" :loading="bookRecommendationStore.fetchRecommendationsStatus === 'pending'" :columns="columns" :rows="bookRecommendationStore.recommendations">
|
<UTable :ui="{ td: { base: 'max-w-[0] truncate' } }" :sort="sort" :loading="bookRecommendationStore.fetchRecommendationsStatus === 'pending'" :columns="columns" :rows="rows">
|
||||||
<template #created_at-data="{ row }">
|
<template #created_at-data="{ row }">
|
||||||
<div>{{ dayjs(row.created_at).format('DD.MM.YYYY') }}</div>
|
<div>{{ dayjs(row.created_at).format('DD.MM.YYYY') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template #published_at-data="{ row }">
|
<template #published_at-data="{ row }">
|
||||||
<div>{{ dayjs(row.published_at).format('DD.MM.YYYY') }}</div>
|
<div>{{ dayjs(row.published_at).format('DD.MM.YYYY') }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template #description-data="{ row }">
|
|
||||||
<div v-if="row.description">
|
|
||||||
{{ `${row.description.substring(0, 50)}...` }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #votes-data="{ row }">
|
<template #votes-data="{ row }">
|
||||||
{{ row.votes.length }}
|
{{ row.votes.length }}
|
||||||
</template>
|
</template>
|
||||||
@@ -87,7 +122,7 @@ function resolveStatus(status: string) {
|
|||||||
</UBadge>
|
</UBadge>
|
||||||
</template>
|
</template>
|
||||||
<template #actions-data="{ row }">
|
<template #actions-data="{ row }">
|
||||||
<div class="flex space-x-2">
|
<div class="flex max-w-full space-x-2">
|
||||||
<CastVote :row="row" />
|
<CastVote :row="row" />
|
||||||
<EditBookRecommendation :row="row" />
|
<EditBookRecommendation :row="row" />
|
||||||
<DeleteBookRecommendation :row="row" />
|
<DeleteBookRecommendation :row="row" />
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const gray = computed({
|
|||||||
<UPopover mode="hover" :popper="{ strategy: 'absolute' }" :ui="{ width: 'w-[156px]' }">
|
<UPopover mode="hover" :popper="{ strategy: 'absolute' }" :ui="{ width: 'w-[156px]' }">
|
||||||
<template #default="{ open }">
|
<template #default="{ open }">
|
||||||
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']" aria-label="Color picker">
|
<UButton color="gray" variant="ghost" square :class="[open && 'bg-gray-50 dark:bg-gray-800']" aria-label="Color picker">
|
||||||
<UIcon name="i-heroicons-swatch-20-solid" class="text-primary-500 dark:text-primary-400 size-5" />
|
<UIcon name="i-heroicons-paint-brush-20-solid" class="text-primary-500 dark:text-primary-400 size-5" />
|
||||||
</UButton>
|
</UButton>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -17,19 +17,21 @@ const dayjs = useDayjs()
|
|||||||
<div class="hidden w-1/5 md:block">
|
<div class="hidden w-1/5 md:block">
|
||||||
<img :src="$storage(props.book.cover_image)" :alt="props.book.book_name" class="rounded-lg">
|
<img :src="$storage(props.book.cover_image)" :alt="props.book.book_name" class="rounded-lg">
|
||||||
</div>
|
</div>
|
||||||
<div class="w-4/5 space-y-4">
|
<div class="w-full space-y-4 md:w-4/5">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<h1 class="font-sans text-3xl font-bold">
|
<h1 class="font-sans text-3xl font-bold">
|
||||||
{{ props.book.book_name }}
|
{{ props.book.book_name }}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex justify-between">
|
<div class="flex flex-col flex-wrap gap-y-2 md:flex-row md:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<UBadge>
|
<UBadge>
|
||||||
{{ props.book.author }}
|
{{ props.book.author }}
|
||||||
</UBadge>
|
</UBadge>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<NewDeadline v-if="auth.user?.roles.includes('admin')" :book-recommendation-id="props.book.id" />
|
<ClientOnly>
|
||||||
|
<NewDeadline v-if="auth.user?.roles.includes('admin')" :book-recommendation-id="props.book.id" />
|
||||||
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import ConfirmUserDeadline from '~/components/modal/ConfirmUserDeadline.vue'
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
bookRecommendationId: number
|
bookRecommendationId: number
|
||||||
}>()
|
}>()
|
||||||
|
const { $echo } = useNuxtApp()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const { refresh: deadlineRefresh, status: deadlineStatus, data: deadlines } = useFetch(() => `book-recommendations/${props.bookRecommendationId}/deadlines`)
|
const { refresh: deadlineRefresh, status: deadlineStatus, data: deadlines } = useFetch(() => `book-recommendations/${props.bookRecommendationId}/deadlines`)
|
||||||
|
|
||||||
@@ -40,6 +42,13 @@ const rows = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$echo.private(`Deadline`)
|
||||||
|
.listen('.DeadlineCreated', (e) => {
|
||||||
|
deadlineRefresh()
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const { refresh: onVote, status } = useFetch<any>(`vote`, {
|
|||||||
async onResponse({ response }) {
|
async onResponse({ response }) {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
useToast().add({
|
useToast().add({
|
||||||
icon: 'i-heroicons-check-circle-20-solid',
|
icon: 'i-heroicons-check-circle',
|
||||||
title: 'Abstimmung erfolgreich.',
|
title: 'Abstimmung erfolgreich.',
|
||||||
color: 'emerald',
|
color: 'emerald',
|
||||||
})
|
})
|
||||||
@@ -45,12 +45,12 @@ const { refresh: onVote, status } = useFetch<any>(`vote`, {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UButton v-if="props.row.status === 'PENDING'" class="transition-150 transform-gpu hover:scale-110" icon="i-heroicons-star" size="sm" color="green" variant="solid" square :disabled="authStore.user.total_votes === 0" @click="isOpen = true" />
|
<UButton v-if="props.row.status === 'PENDING'" class="transition-150 transform-gpu hover:scale-110" icon="i-heroicons-check-circle" size="sm" color="green" variant="solid" square :disabled="authStore.user.total_votes === 0" @click="isOpen = true" />
|
||||||
<UDashboardModal
|
<UDashboardModal
|
||||||
v-model="isOpen"
|
v-model="isOpen"
|
||||||
title="Für Buch abstimmen"
|
title="Für Buch abstimmen"
|
||||||
:description="`Bist du dir sicher das du für die Buchempfehlung "${row.book_name}" abstimmen möchtest?`"
|
:description="`Bist du dir sicher das du für die Buchempfehlung "${row.book_name}" abstimmen möchtest?`"
|
||||||
icon="i-heroicons-star"
|
icon="i-heroicons-check-circle"
|
||||||
:ui="{
|
:ui="{
|
||||||
icon: { base: 'text-primary-500 dark:text-primary-400' } as any,
|
icon: { base: 'text-primary-500 dark:text-primary-400' } as any,
|
||||||
footer: { base: 'ml-16' } as any,
|
footer: { base: 'ml-16' } as any,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import party from 'party-js'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
userDeadlineId: number
|
userDeadlineId: number
|
||||||
}>()
|
}>()
|
||||||
@@ -23,6 +25,11 @@ const { refresh: onConfirmDeadline, status } = useFetch<any>(() => `user-deadlin
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function confirmDeadline(event) {
|
||||||
|
party.confetti(event)
|
||||||
|
onConfirmDeadline()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -38,7 +45,7 @@ const { refresh: onConfirmDeadline, status } = useFetch<any>(() => `user-deadlin
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<UButton color="primary" label="Abschließen" :loading="status === 'pending'" @click="onConfirmDeadline" />
|
<UButton color="primary" label="Abschließen" :loading="status === 'pending'" @click="confirmDeadline" />
|
||||||
<UButton color="white" label="Abbrechen" @click="isOpen = false" />
|
<UButton color="white" label="Abbrechen" @click="isOpen = false" />
|
||||||
</template>
|
</template>
|
||||||
</UDashboardModal>
|
</UDashboardModal>
|
||||||
|
|||||||
@@ -114,6 +114,14 @@ async function onSubmit(event: FormSubmitEvent<any>) {
|
|||||||
})
|
})
|
||||||
await bookRecommendationStore.fetchRecommendations()
|
await bookRecommendationStore.fetchRecommendations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isFile(value: any): value is File {
|
||||||
|
return value instanceof File
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileUrl(file: File) {
|
||||||
|
return URL.createObjectURL(file)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -144,7 +152,10 @@ async function onSubmit(event: FormSubmitEvent<any>) {
|
|||||||
<UFormGroup label="Beschreibung" name="description">
|
<UFormGroup label="Beschreibung" name="description">
|
||||||
<UTextarea v-model="state.description" />
|
<UTextarea v-model="state.description" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
<img v-if="state.cover_image" :src="$storage(state.cover_image)" alt="Cover" class="size-1/3 content-center rounded-lg">
|
<img v-if="state.cover_image && typeof state.cover_image === 'string'" :src="$storage(state.cover_image)" alt="Cover" class="size-1/3 content-center rounded-lg">
|
||||||
|
<ClientOnly>
|
||||||
|
<img v-if="state.cover_image && isFile(state.cover_image)" :src="getFileUrl(state.cover_image)" alt="Cover" class="size-1/3 content-center rounded-lg">
|
||||||
|
</ClientOnly>
|
||||||
<UFormGroup label="Cover" name="cover_image">
|
<UFormGroup label="Cover" name="cover_image">
|
||||||
<UInput type="file" @change="handleCoverImageInput" />
|
<UInput type="file" @change="handleCoverImageInput" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|||||||
@@ -91,6 +91,13 @@ async function onSubmit() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function isFile(value: any): value is File {
|
||||||
|
return value instanceof File
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileUrl(file: File) {
|
||||||
|
return URL.createObjectURL(file)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -123,6 +130,9 @@ async function onSubmit() {
|
|||||||
<UFormGroup label="Beschreibung" name="description">
|
<UFormGroup label="Beschreibung" name="description">
|
||||||
<UTextarea v-model="state.description" />
|
<UTextarea v-model="state.description" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
<ClientOnly>
|
||||||
|
<img v-if="state.cover_image && isFile(state.cover_image)" :src="getFileUrl(state.cover_image)" alt="Cover" class="size-1/3 content-center rounded-lg">
|
||||||
|
</ClientOnly>
|
||||||
<UFormGroup label="Cover" name="cover_image">
|
<UFormGroup label="Cover" name="cover_image">
|
||||||
<UInput type="file" @change="handleCoverImageInput" />
|
<UInput type="file" @change="handleCoverImageInput" />
|
||||||
</UFormGroup>
|
</UFormGroup>
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
<UPage>
|
<UPage>
|
||||||
<template #left>
|
<template #left>
|
||||||
<UAside class="lg:static">
|
<UAside class="lg:static">
|
||||||
<Navigation />
|
<UContainer>
|
||||||
|
<Navigation />
|
||||||
|
</UContainer>
|
||||||
</UAside>
|
</UAside>
|
||||||
</template>
|
</template>
|
||||||
<UPageBody>
|
<UPageBody>
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ export default defineNuxtRouteMiddleware((to, from) => {
|
|||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
if (process.server) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (auth.isLoggedIn && !auth.user.roles.includes('admin')) {
|
if (auth.isLoggedIn && !auth.user.roles.includes('admin')) {
|
||||||
return nuxtApp.runWithContext(() => {
|
return nuxtApp.runWithContext(() => {
|
||||||
useToast().add({
|
useToast().add({
|
||||||
icon: "i-heroicons-exclamation-circle-solid",
|
icon: 'i-heroicons-exclamation-circle-solid',
|
||||||
title: "Access denied.",
|
title: 'Access denied.',
|
||||||
color: "red",
|
color: 'red',
|
||||||
});
|
})
|
||||||
|
|
||||||
return navigateTo('/')
|
return navigateTo('/')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ export default defineNuxtRouteMiddleware((to, from) => {
|
|||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
if (process.server) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (auth.isLoggedIn && !auth.user.roles.includes('user')) {
|
if (auth.isLoggedIn && !auth.user.roles.includes('user')) {
|
||||||
return nuxtApp.runWithContext(() => {
|
return nuxtApp.runWithContext(() => {
|
||||||
useToast().add({
|
useToast().add({
|
||||||
icon: "i-heroicons-exclamation-circle-solid",
|
icon: 'i-heroicons-exclamation-circle-solid',
|
||||||
title: "Access denied.",
|
title: 'Access denied.',
|
||||||
color: "red",
|
color: 'red',
|
||||||
});
|
})
|
||||||
|
|
||||||
return navigateTo('/')
|
return navigateTo('/')
|
||||||
})
|
})
|
||||||
|
|||||||
40
nuxt/pages/admin/jobs.vue
Normal file
40
nuxt/pages/admin/jobs.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
definePageMeta({ middleware: ['role-admin'] })
|
||||||
|
|
||||||
|
const serverLogs = ref<string>('')
|
||||||
|
|
||||||
|
async function runJobs(job: string) {
|
||||||
|
switch (job) {
|
||||||
|
case 'fetch_cover':
|
||||||
|
serverLogs.value = await $fetch('jobs/fetch-cover', { method: 'POST' })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-4 md:flex-row">
|
||||||
|
<UCard class="w-full md:w-1/2">
|
||||||
|
<template #header>
|
||||||
|
<h1 class="font-sans text-3xl font-bold">
|
||||||
|
Server Jobs
|
||||||
|
</h1>
|
||||||
|
<UButton class="mt-4" @click="runJobs('fetch_cover')">
|
||||||
|
Cover Bilder anfragen
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
|
<UCard class="w-full md:w-1/2">
|
||||||
|
<template #header>
|
||||||
|
<h1 class="font-sans text-3xl font-bold">
|
||||||
|
Server Logs
|
||||||
|
</h1>
|
||||||
|
<UTextarea v-model="serverLogs" autoresize disabled class="mt-4" />
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@ definePageMeta({ middleware: ['role-admin'] })
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<UCard class="w-1/2">
|
<UCard class="w-full md:w-1/2">
|
||||||
<template #header>
|
<template #header>
|
||||||
<h1 class="font-sans text-3xl font-bold">
|
<h1 class="font-sans text-3xl font-bold">
|
||||||
Benutzer Aktionen
|
Benutzer Aktionen
|
||||||
|
|||||||
@@ -4,8 +4,24 @@ import BookInfoCard from '~/components/dashboard/BookInfoCard.vue'
|
|||||||
definePageMeta({ middleware: ['auth'] })
|
definePageMeta({ middleware: ['auth'] })
|
||||||
|
|
||||||
const bookRecommendationStore = useBookRecommendationStore()
|
const bookRecommendationStore = useBookRecommendationStore()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const { $echo } = useNuxtApp()
|
||||||
|
|
||||||
bookRecommendationStore.fetchActiveRecommendations()
|
bookRecommendationStore.fetchActiveRecommendations()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
$echo.private(`BookRecommendation`)
|
||||||
|
.listen('.BookRecommendationUpdated', (e) => {
|
||||||
|
bookRecommendationStore.updateRecommendationWS(e.bookRecommendation)
|
||||||
|
})
|
||||||
|
.listen('.BookRecommendationDeleted', (e) => {
|
||||||
|
bookRecommendationStore.deleteRecommendationWS(e.bookRecommendation)
|
||||||
|
})
|
||||||
|
.listen('.BookRecommendationCreated', (e) => {
|
||||||
|
bookRecommendationStore.createRecommendationWS(e.bookRecommendation)
|
||||||
|
})
|
||||||
|
authStore.socketId = $echo.socketId()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ async function handleMessage(event: { data: any }): Promise<void> {
|
|||||||
<div class="mx-auto flex min-h-screen w-full items-center justify-center">
|
<div class="mx-auto flex min-h-screen w-full items-center justify-center">
|
||||||
<UCard class="w-96">
|
<UCard class="w-96">
|
||||||
<template #header>
|
<template #header>
|
||||||
<h1 class="text-center text-2xl font-bold">
|
<div class="text-center text-4xl font-bold">
|
||||||
Login
|
<Logo />
|
||||||
</h1>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<UForm ref="form" :state="state" class="space-y-4" @submit="onSubmit">
|
<UForm ref="form" :state="state" class="space-y-4" @submit="onSubmit">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ofetch } from 'ofetch'
|
import { ofetch } from 'ofetch'
|
||||||
import type { FetchOptions } from 'ofetch';
|
import type { FetchOptions } from 'ofetch'
|
||||||
|
|
||||||
export default defineNuxtPlugin({
|
export default defineNuxtPlugin({
|
||||||
name: 'app',
|
name: 'app',
|
||||||
@@ -10,10 +10,10 @@ export default defineNuxtPlugin({
|
|||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
nuxtApp.provide('storage', (path: string): string => {
|
nuxtApp.provide('storage', (path: string): string => {
|
||||||
if (!path) return ''
|
if (!path) { return '' }
|
||||||
|
|
||||||
return path.startsWith('http://') || path.startsWith('https://') ?
|
return path.startsWith('http://') || path.startsWith('https://')
|
||||||
path
|
? path
|
||||||
: config.public.storageBase + path
|
: config.public.storageBase + path
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -21,45 +21,53 @@ export default defineNuxtPlugin({
|
|||||||
// Initial headers with Accept
|
// Initial headers with Accept
|
||||||
const initialHeaders = {
|
const initialHeaders = {
|
||||||
...headers,
|
...headers,
|
||||||
'Accept': 'application/json',
|
Accept: 'application/json',
|
||||||
};
|
}
|
||||||
|
|
||||||
// Conditionally add server-specific headers
|
// Conditionally add server-specific headers
|
||||||
if (process.server) {
|
if (process.server) {
|
||||||
const serverHeaders = {
|
const serverHeaders = {
|
||||||
'referer': useRequestURL().toString(),
|
referer: useRequestURL().toString(),
|
||||||
...useRequestHeaders(['x-forwarded-for', 'user-agent', 'referer']),
|
...useRequestHeaders(['x-forwarded-for', 'user-agent', 'referer']),
|
||||||
};
|
}
|
||||||
Object.assign(initialHeaders, serverHeaders);
|
Object.assign(initialHeaders, serverHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conditionally add authorization header if logged in
|
// Conditionally add authorization header if logged in
|
||||||
if (auth.isLoggedIn) {
|
if (auth.isLoggedIn) {
|
||||||
const authHeaders = {
|
const authHeaders = {
|
||||||
'Authorization': `Bearer ${auth.token}`,
|
Authorization: `Bearer ${auth.token}`,
|
||||||
};
|
}
|
||||||
Object.assign(initialHeaders, authHeaders);
|
Object.assign(initialHeaders, authHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
return initialHeaders;
|
// Conditionally add X-Socket-ID header if socket is connected
|
||||||
|
if (auth.isLoggedIn && auth.socketId) {
|
||||||
|
const socketHeaders = {
|
||||||
|
'X-Socket-ID': auth.socketId,
|
||||||
|
}
|
||||||
|
Object.assign(initialHeaders, socketHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildBaseURL(baseURL: string): string {
|
function buildBaseURL(baseURL: string): string {
|
||||||
if (baseURL) return baseURL;
|
if (baseURL) { return baseURL }
|
||||||
|
|
||||||
return process.server ?
|
return process.server
|
||||||
config.apiLocal + config.public.apiPrefix
|
? config.apiLocal + config.public.apiPrefix
|
||||||
: config.public.apiBase + config.public.apiPrefix;
|
: config.public.apiBase + config.public.apiPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSecureMethod(options: FetchOptions): void {
|
function buildSecureMethod(options: FetchOptions): void {
|
||||||
if (process.server) return;
|
if (process.server) { return }
|
||||||
|
|
||||||
const method = options.method?.toLowerCase() ?? 'get'
|
const method = options.method?.toLowerCase() ?? 'get'
|
||||||
|
|
||||||
if (options.body instanceof FormData && method === 'put') {
|
if (options.body instanceof FormData && method === 'put') {
|
||||||
options.method = 'POST';
|
options.method = 'POST'
|
||||||
options.body.append('_method', 'PUT');
|
options.body.append('_method', 'PUT')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,27 +75,27 @@ export default defineNuxtPlugin({
|
|||||||
return !baseURL
|
return !baseURL
|
||||||
&& !path.startsWith('/_nuxt')
|
&& !path.startsWith('/_nuxt')
|
||||||
&& !path.startsWith('http://')
|
&& !path.startsWith('http://')
|
||||||
&& !path.startsWith('https://');
|
&& !path.startsWith('https://')
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.$fetch = ofetch.create({
|
globalThis.$fetch = ofetch.create({
|
||||||
retry: false,
|
retry: false,
|
||||||
|
|
||||||
onRequest({ request, options }) {
|
onRequest({ request, options }) {
|
||||||
if (!isRequestWithAuth(options.baseURL ?? '', request.toString())) return
|
if (!isRequestWithAuth(options.baseURL ?? '', request.toString())) { return }
|
||||||
|
|
||||||
options.credentials = 'include';
|
options.credentials = 'include'
|
||||||
|
|
||||||
options.baseURL = buildBaseURL(options.baseURL ?? '');
|
options.baseURL = buildBaseURL(options.baseURL ?? '')
|
||||||
options.headers = buildHeaders(options.headers);
|
options.headers = buildHeaders(options.headers)
|
||||||
|
|
||||||
buildSecureMethod(options);
|
buildSecureMethod(options)
|
||||||
},
|
},
|
||||||
|
|
||||||
onRequestError({ error }) {
|
onRequestError({ error }) {
|
||||||
if (process.server) return;
|
if (process.server) { return }
|
||||||
|
|
||||||
if (error.name === 'AbortError') return;
|
if (error.name === 'AbortError') { return }
|
||||||
|
|
||||||
useToast().add({
|
useToast().add({
|
||||||
icon: 'i-heroicons-exclamation-circle-solid',
|
icon: 'i-heroicons-exclamation-circle-solid',
|
||||||
@@ -110,7 +118,8 @@ export default defineNuxtPlugin({
|
|||||||
color: 'primary',
|
color: 'primary',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (response.status !== 422) {
|
}
|
||||||
|
else if (response.status !== 422) {
|
||||||
if (process.client) {
|
if (process.client) {
|
||||||
useToast().add({
|
useToast().add({
|
||||||
icon: 'i-heroicons-exclamation-circle-solid',
|
icon: 'i-heroicons-exclamation-circle-solid',
|
||||||
@@ -119,11 +128,11 @@ export default defineNuxtPlugin({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
} as FetchOptions)
|
} as FetchOptions)
|
||||||
|
|
||||||
if (auth.isLoggedIn) {
|
if (auth.isLoggedIn) {
|
||||||
await auth.fetchUser();
|
await auth.fetchUser()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
55
nuxt/plugins/echo.client.ts
Normal file
55
nuxt/plugins/echo.client.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import Echo from 'laravel-echo'
|
||||||
|
import Pusher from 'pusher-js'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
Pusher: typeof Pusher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Pusher = Pusher
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const echo = new Echo({
|
||||||
|
broadcaster: runtimeConfig.public.echo.broadcaster,
|
||||||
|
key: runtimeConfig.public.echo.key,
|
||||||
|
cluster: 'mt1',
|
||||||
|
wsHost: runtimeConfig.public.echo.wsHost,
|
||||||
|
wsPort: runtimeConfig.public.echo.wsPort,
|
||||||
|
wssPort: runtimeConfig.public.echo.wsPort,
|
||||||
|
forceTLS: false,
|
||||||
|
encrypted: runtimeConfig.public.echo.encrypted,
|
||||||
|
disableStats: runtimeConfig.public.echo.disableStats,
|
||||||
|
enabledTransports: ['ws', 'wss'],
|
||||||
|
authorizer: (channel: { name: any }) => {
|
||||||
|
return {
|
||||||
|
authorize: async (socketId: string, callback: (error: any, respnse?: any) => void) => {
|
||||||
|
try {
|
||||||
|
const response = await $fetch(`/broadcasting/auth`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
socket_id: socketId,
|
||||||
|
channel_name: channel.name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
callback(null, response)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
callback(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
authStore.socketId = echo.socketId()
|
||||||
|
|
||||||
|
// Make Echo instance available through the Nuxt app
|
||||||
|
return {
|
||||||
|
provide: {
|
||||||
|
echo,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -24,6 +24,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
maxAge: 60 * 60 * 24 * 365,
|
maxAge: 60 * 60 * 24 * 365,
|
||||||
})
|
})
|
||||||
const isLoggedIn = computed(() => !!token.value)
|
const isLoggedIn = computed(() => !!token.value)
|
||||||
|
const socketId = ref('')
|
||||||
|
|
||||||
const { refresh: logout } = useFetch<any>('logout', {
|
const { refresh: logout } = useFetch<any>('logout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -49,5 +50,5 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return { user, isLoggedIn, logout, fetchUser, token }
|
return { user, isLoggedIn, socketId, logout, fetchUser, token }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,11 +21,20 @@ export interface BookRecommendation {
|
|||||||
email: string
|
email: string
|
||||||
avatar: string
|
avatar: string
|
||||||
}
|
}
|
||||||
|
votes?: Vote[]
|
||||||
status: BookRecommendationStatusEnum
|
status: BookRecommendationStatusEnum
|
||||||
cover_image?: string
|
cover_image?: string
|
||||||
published_at?: string
|
published_at?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Vote {
|
||||||
|
book_recommendation_id: number
|
||||||
|
id: number
|
||||||
|
user_id: number
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
export const useBookRecommendationStore = defineStore('bookRecommendations', () => {
|
export const useBookRecommendationStore = defineStore('bookRecommendations', () => {
|
||||||
const recommendations = ref<BookRecommendation[]>([])
|
const recommendations = ref<BookRecommendation[]>([])
|
||||||
|
|
||||||
@@ -72,6 +81,59 @@ export const useBookRecommendationStore = defineStore('bookRecommendations', ()
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const updateRecommendationWS = async (data: Partial<BookRecommendation>) => {
|
||||||
|
// This data can be Partial, the id should always be present. We need to only update the properties that are present in the data object.
|
||||||
|
// We also have a special case for activeRecommendations, in this case we could have a new recommendation that needs to be added to the list. This should only happen if the status is ACTIVE.
|
||||||
|
// If the Status is not ACTIVE, we need to remove the recommendation from the list.
|
||||||
|
const index = recommendations.value.findIndex(r => r.id === data.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
recommendations.value[index] = { ...recommendations.value[index], ...data }
|
||||||
|
}
|
||||||
|
switch (data.status) {
|
||||||
|
case BookRecommendationStatusEnum.ACTIVE:
|
||||||
|
const activeIndex = recommendations.value.findIndex(r => r.id === data.id)
|
||||||
|
if (activeIndex === -1) {
|
||||||
|
await createRecommendationWS(data)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
const inactiveIndex = recommendations.value.findIndex(r => r.id === data.id)
|
||||||
|
if (inactiveIndex !== -1) {
|
||||||
|
recommendations.value.splice(inactiveIndex, 1)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRecommendationWS = async (data: Partial<BookRecommendation>) => {
|
||||||
|
const index = recommendations.value.findIndex(r => r.id === data.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
recommendations.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createRecommendationWS = async (data: Partial<BookRecommendation>) => {
|
||||||
|
// Here we need to get the missing with data from the server
|
||||||
|
await useFetch<BookRecommendation>(`book-recommendations/${data.id}?with=recommender,votes`, {
|
||||||
|
onResponse({ response }) {
|
||||||
|
if (response.status === 200) {
|
||||||
|
recommendations.value.push(response._data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createVoteWS = async (data: Partial<Vote>) => {
|
||||||
|
const index = recommendations.value.findIndex(r => r.id === data.book_recommendation_id)
|
||||||
|
// check if vote already exists otherwise add an empty array
|
||||||
|
if (index !== -1) {
|
||||||
|
if (!recommendations.value[index]?.votes) {
|
||||||
|
recommendations.value[index].votes = []
|
||||||
|
}
|
||||||
|
recommendations.value[index].votes.push(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resetRecommendations() {
|
function resetRecommendations() {
|
||||||
recommendations.value = []
|
recommendations.value = []
|
||||||
}
|
}
|
||||||
@@ -79,6 +141,10 @@ export const useBookRecommendationStore = defineStore('bookRecommendations', ()
|
|||||||
return {
|
return {
|
||||||
recommendations,
|
recommendations,
|
||||||
resetRecommendations,
|
resetRecommendations,
|
||||||
|
updateRecommendationWS,
|
||||||
|
deleteRecommendationWS,
|
||||||
|
createRecommendationWS,
|
||||||
|
createVoteWS,
|
||||||
statusOptions,
|
statusOptions,
|
||||||
fetchRecommendations,
|
fetchRecommendations,
|
||||||
fetchRecommendationsStatus,
|
fetchRecommendationsStatus,
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "laravel-nuxt",
|
"name": "laravel-nuxt",
|
||||||
|
"version": "0.0.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "cross-env PORT=3000 HOST=127.0.0.1 node .output/server/index.mjs",
|
"serve": "cross-env PORT=3000 HOST=127.0.0.1 node .output/server/index.mjs",
|
||||||
"build": "nuxi cleanup && nuxi build",
|
"build": "GIT_HASH=$(git rev-parse --short HEAD) nuxi build",
|
||||||
"cleanup": "nuxi cleanup",
|
"cleanup": "nuxi cleanup",
|
||||||
"dev": "nuxt dev --port=3000 --host=127.0.0.1",
|
"dev": "GIT_HASH=$(git rev-parse --short HEAD) nuxt dev --port=3000 --host=127.0.0.1",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare",
|
"postinstall": "nuxt prepare",
|
||||||
@@ -32,5 +33,10 @@
|
|||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"vue": "3.4.21"
|
"vue": "3.4.21"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"laravel-echo": "^1.16.0",
|
||||||
|
"party-js": "^2.2.0",
|
||||||
|
"pusher-js": "8.4.0-rc2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -7,6 +7,17 @@ settings:
|
|||||||
overrides:
|
overrides:
|
||||||
vue: 3.4.21
|
vue: 3.4.21
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
laravel-echo:
|
||||||
|
specifier: ^1.16.0
|
||||||
|
version: 1.16.0
|
||||||
|
party-js:
|
||||||
|
specifier: ^2.2.0
|
||||||
|
version: 2.2.0
|
||||||
|
pusher-js:
|
||||||
|
specifier: 8.4.0-rc2
|
||||||
|
version: 8.4.0-rc2
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@antfu/eslint-config':
|
'@antfu/eslint-config':
|
||||||
specifier: ^2.8.3
|
specifier: ^2.8.3
|
||||||
@@ -5910,6 +5921,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/laravel-echo@1.16.0:
|
||||||
|
resolution: {integrity: sha512-BJGUa4tcKvYmTkzTmcBGMHiO2tq+k7Do5wPmLbRswWfzKwyfZEUR+J5iwBTPEfLLwNPZlA9Kjo6R/NV6pmyIpg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/launch-editor@2.6.1:
|
/launch-editor@2.6.1:
|
||||||
resolution: {integrity: sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==}
|
resolution: {integrity: sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7163,6 +7179,10 @@ packages:
|
|||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/party-js@2.2.0:
|
||||||
|
resolution: {integrity: sha512-50hGuALCpvDTrQLPQ1fgUgxKIWAH28ShVkmeK/3zhO0YJyCqkhrZhQEkWPxDYLvbFJ7YAXyROmFEu35gKpZLtQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/path-exists@4.0.0:
|
/path-exists@4.0.0:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -7762,6 +7782,12 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/pusher-js@8.4.0-rc2:
|
||||||
|
resolution: {integrity: sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==}
|
||||||
|
dependencies:
|
||||||
|
tweetnacl: 1.0.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/queue-microtask@1.2.3:
|
/queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -8754,6 +8780,10 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/tweetnacl@1.0.3:
|
||||||
|
resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/type-check@0.4.0:
|
/type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|||||||
14
resources/js/echo.js
Normal file
14
resources/js/echo.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import Echo from 'laravel-echo';
|
||||||
|
|
||||||
|
import Pusher from 'pusher-js';
|
||||||
|
window.Pusher = Pusher;
|
||||||
|
|
||||||
|
window.Echo = new Echo({
|
||||||
|
broadcaster: 'reverb',
|
||||||
|
key: import.meta.env.VITE_REVERB_APP_KEY,
|
||||||
|
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||||
|
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
|
||||||
|
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
|
||||||
|
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
|
||||||
|
enabledTransports: ['ws', 'wss'],
|
||||||
|
});
|
||||||
@@ -27,6 +27,7 @@ Route::prefix('api/v1')->group(function () {
|
|||||||
Route::post('devices/disconnect', [AuthController::class, 'deviceDisconnect'])->name('devices.disconnect');
|
Route::post('devices/disconnect', [AuthController::class, 'deviceDisconnect'])->name('devices.disconnect');
|
||||||
Route::get('devices', [AuthController::class, 'devices'])->name('devices');
|
Route::get('devices', [AuthController::class, 'devices'])->name('devices');
|
||||||
Route::get('user', [AuthController::class, 'user'])->name('user');
|
Route::get('user', [AuthController::class, 'user'])->name('user');
|
||||||
|
Route::match(['get', 'post'], '/broadcasting/auth', [\Illuminate\Broadcasting\BroadcastController::class, 'authenticate'])->withoutMiddleware(\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class)->name('broadcasting.auth');
|
||||||
|
|
||||||
Route::resource('book-recommendations', BookRecommendationController::class);
|
Route::resource('book-recommendations', BookRecommendationController::class);
|
||||||
Route::get('/book-recommendations/{bookRecommendationId}/deadlines', [DeadlineController::class, 'deadlinesByBookRecommendation']);
|
Route::get('/book-recommendations/{bookRecommendationId}/deadlines', [DeadlineController::class, 'deadlinesByBookRecommendation']);
|
||||||
@@ -41,6 +42,8 @@ Route::prefix('api/v1')->group(function () {
|
|||||||
Route::post('account/update', [AccountController::class, 'update'])->name('account.update');
|
Route::post('account/update', [AccountController::class, 'update'])->name('account.update');
|
||||||
Route::post('account/password', [AccountController::class, 'password'])->name('account.password');
|
Route::post('account/password', [AccountController::class, 'password'])->name('account.password');
|
||||||
|
|
||||||
|
Route::post('jobs/fetch-cover', [BookRecommendationController::class, 'fetchCover'])->name('jobs.fetch-cover');
|
||||||
|
|
||||||
Route::middleware(['throttle:uploads'])->group(function () {
|
Route::middleware(['throttle:uploads'])->group(function () {
|
||||||
Route::post('upload', [UploadController::class, 'image'])->name('upload.image');
|
Route::post('upload', [UploadController::class, 'image'])->name('upload.image');
|
||||||
});
|
});
|
||||||
|
|||||||
19
routes/channels.php
Normal file
19
routes/channels.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Broadcast;
|
||||||
|
|
||||||
|
Broadcast::channel('App.Models.User.{ulid}', function ($user, $ulid) {
|
||||||
|
return $user->ulid === $ulid;
|
||||||
|
});
|
||||||
|
|
||||||
|
Broadcast::channel('BookRecommendation', function ($user) {
|
||||||
|
return $user;
|
||||||
|
});
|
||||||
|
|
||||||
|
Broadcast::channel('Vote', function ($user) {
|
||||||
|
return $user;
|
||||||
|
});
|
||||||
|
|
||||||
|
Broadcast::channel('Deadline', function ($user) {
|
||||||
|
return $user;
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user