Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: merge rc into main #312

Merged
merged 58 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
53f94b2
parent e50dae58558be5cf87b8b85cf32d5c47b2292ec5
patricio0312rev Oct 5, 2023
9c50c93
refactor: use EXISTS instead of JOIN when checking for a collection's…
crnkovic Oct 5, 2023
6623d38
fix: update overlay styles when switching address (#180)
patricio0312rev Oct 10, 2023
7a97744
fix: update nft pagination arguments in gallery overview page (#197)
patricio0312rev Oct 10, 2023
69c8bbf
refactor: remove unnecessary database queries from wallet data (#190)
crnkovic Oct 10, 2023
ade428d
chore: use "password" password for filament in local env (#201)
ItsANameToo Oct 10, 2023
03126c6
feat: dark mode for error pages and filters (#199)
patricio0312rev Oct 10, 2023
4c965ed
Merge branch 'rc-0.7.0' into develop
ItsANameToo Oct 11, 2023
07fd5ac
refactor: simplify query for retrieving collections for a signed wall…
crnkovic Oct 11, 2023
2748aa1
refactor: batch nft metadata requests (#128)
goga-m Oct 11, 2023
592a4f3
fix: make collections grid view respect list view sorting (#203)
patricio0312rev Oct 12, 2023
b9ed9d8
fix: flaky wallet test (#212)
alfonsobries Oct 12, 2023
fcc9536
feat: dark mode for gallery details page (#208)
patricio0312rev Oct 12, 2023
d2e6279
feat: rate limit opensea requests (#169)
alfonsobries Oct 12, 2023
de5595a
Merge branch 'main' into develop
ItsANameToo Oct 12, 2023
5ef126f
refactor: collection controller optimizations (#207)
crnkovic Oct 12, 2023
108ad8e
fix: handle existing local user when seeding (#226)
ItsANameToo Oct 13, 2023
03012fc
chore: update PHP dependencies (#233)
ItsANameToo Oct 16, 2023
e80e1d3
feat: dark mode for create gallery page (#238)
patricio0312rev Oct 17, 2023
edf6dbc
refactor: fetch opensea slugs more often (#219)
alfonsobries Oct 17, 2023
28010ba
fix: timestamp issue for report a gallery test (#240)
patricio0312rev Oct 17, 2023
a10d997
fix: axios cancel alternative not working (#236)
alfonsobries Oct 17, 2023
a2fd0b9
fix: persist network filter collection when sorting (#218)
patricio0312rev Oct 18, 2023
bb1f981
refactor: add nft descriptions (#122)
crnkovic Oct 18, 2023
7ed58d0
chore: update JavaScript dependencies (#232)
ItsANameToo Oct 18, 2023
69a2dfb
refactor: handle moralis spam tokens (#245)
alfonsobries Oct 19, 2023
e4ca3ad
feat: handle errors in Alchemy response for getWalletNfts (#230)
patricio0312rev Oct 19, 2023
87f1ef7
feat: dark mode for my collections page (#258)
patricio0312rev Oct 20, 2023
4a480b9
feat: dark mode for my wallet page (#259)
patricio0312rev Oct 23, 2023
08f2b97
chore: update PHP dependencies (#263)
ItsANameToo Oct 23, 2023
fafbaa7
chore: run ui on json and yaml changes (#265)
ItsANameToo Oct 23, 2023
071c493
chore: update JavaScript dependencies (#264)
ItsANameToo Oct 23, 2023
393c45e
feat: gallery dynamic meta image (#188)
alfonsobries Oct 24, 2023
bbc8e7c
fix: collections page header (#269)
shahin-hq Oct 24, 2023
5de714c
feat: dark mode for collection and nft details pages (#270)
patricio0312rev Oct 24, 2023
429b3a6
Merge branch 'main' into develop
ItsANameToo Oct 24, 2023
b69f5b2
refactor: prevent adding NFTs without an image to gallery (#268)
shahin-hq Oct 25, 2023
9ba7169
refactor: session timeout handling (#231)
alfonsobries Oct 25, 2023
301cd1f
refactor: add the button to refresh the collections (#183)
crnkovic Oct 26, 2023
adb6e13
refactor: navbar width (#292)
crnkovic Oct 26, 2023
aea8c36
fix: "about nft" modal title (#294)
crnkovic Oct 26, 2023
96d26e0
refactor: add the job to index nft descriptions (#250)
crnkovic Oct 26, 2023
3c12f77
feat: dark mode for wallet components (#289)
patricio0312rev Oct 26, 2023
ad6e496
refactor: handle image aspect ratio (#290)
goga-m Oct 27, 2023
85058aa
refactor: separate the gallery liking logic into its own controller (…
crnkovic Oct 27, 2023
ed6fb73
refactor: use data objects only in classes that are sent to the front…
crnkovic Oct 27, 2023
c5096cc
refactor: create "simple" wallet data object (#297)
crnkovic Oct 27, 2023
455bc59
chore: update JavaScript dependencies (#310)
ItsANameToo Oct 30, 2023
55bbabc
chore: update PHP dependencies (#311)
ItsANameToo Oct 30, 2023
25901f1
feat: add manual activity refresh button (#267)
goga-m Oct 30, 2023
876c74a
fix: correct aspect ratio in collection activity images (#313)
goga-m Oct 30, 2023
0dec26e
fix: correct image dimensions in nft & gallery page (#317)
goga-m Oct 30, 2023
13e87ff
refactor: disable signature requirement for refresh actions (#314)
crnkovic Oct 31, 2023
4f40ce6
fix: refresh collection button styles (#324)
patricio0312rev Oct 31, 2023
30edb30
fix: remove redundant tooltip in activity refresh button (#328)
goga-m Oct 31, 2023
9d1b9fc
fix: set height for navbar menu items (#330)
patricio0312rev Nov 1, 2023
e601a7f
refactor: use async call in authenticatedAction (#331)
alfonsobries Nov 1, 2023
1d20136
fix: show thumbnail in gallery form cover image button (#342)
goga-m Nov 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,7 @@ DISCORD_INVITE_API_URL=https://discord.com/api/v9/invites/MJyWKkCJ5k?with_counts
TELESCOPE_ENABLED=false

2FA_ENABLED=true

BROWSERSHOT_NODE_BINARY="/usr/local/bin/node"
BROWSERSHOT_NPM_BINARY="/usr/local/bin/npm"
BROWSERSHOT_TIMEOUT=60
2 changes: 2 additions & 0 deletions .github/workflows/ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ on:
- "**.css"
- "**.js"
- "**.ts"
- "**.yaml"
- "**.json"
- "**.php"
- "**.svg"
- "**.tsx"
Expand Down
13 changes: 13 additions & 0 deletions SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ When you run `composer db:dev` command, you'll notice there are several users se

You can always create a new admin user by running `php artisan make:admin` CLI command.

## Dynamic Meta Images

Dashbrd utilizes [`spatie/browsershot`](https://spatie.be/docs/browsershot/v2) to generate dynamic meta images for the galleries section. For this package to function properly, Node 7.6.0 or higher, along with the Puppeteer Node library, are required.

You need to adjust the following .env variables according to your server configuration:

```plaintext
BROWSERSHOT_NODE_BINARY="/usr/local/bin/node"
BROWSERSHOT_NPM_BINARY="/usr/local/bin/npm"
```

For details on driver requirements and installation, [visit here](https://spatie.be/docs/browsershot/v2/requirements).

## Reports

When a model gets reported (Gallery, Collection, NFT), this creates an entry in the Admin panel to review. Depending on your needs, you may want to be notified as well to quickly respond to new reports. For this reason, it's possible to add a webhook to get reports pushed to Slack as well. You can adjust the `.env` as follows to achieve this:
Expand Down
108 changes: 108 additions & 0 deletions app/Console/Commands/DependsOnOpenseaRateLimit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Jobs\FetchCollectionFloorPrice;
use App\Jobs\FetchCollectionOpenseaSlug;
use Carbon\Carbon;
use Closure;
use Illuminate\Support\Facades\Config;

trait DependsOnOpenseaRateLimit
{
/**
* @var array<string>
*/
private array $jobsThatMayUseOpensea = [
FetchCollectionFloorPrice::class,
];

/**
* @var array<string>
*/
private array $jobsThatUseOpensea = [
FetchCollectionOpenseaSlug::class,
];

/**
* Used to delay jobs by a certain amount of seconds to prevent overlapping
*
* @var array<string, int>
*/
private array $jobsDelayThreshold = [
FetchCollectionFloorPrice::class => 0,
FetchCollectionOpenseaSlug::class => 1,
];

private function getDispatchAt(string $job, int $index): Carbon
{
$maxRequests = config('services.opensea.rate.max_requests');

$perSeconds = config('services.opensea.rate.per_seconds');

$delayInSeconds = (int) floor($index / $maxRequests) * $perSeconds * $this->getRateLimitFactor();

return Carbon::now()
// Round to the next minute so the delay is always calculated from
// the beginning of the minute which prevents overlapping considering
// jobs may be dispatched at different times
// e.g. 12:00:34 -> 12:01:00, 12:00:59 -> 12:01:00
->addMinute()->startOfMinute()
// Delay the job by the amount of seconds + the delay treshold
// e.g. 12:01:00 + 1 -> 12:01:01, 12:01:00 + 2 -> 12:01:02
->addSeconds($delayInSeconds + $this->getDelayTreshold($job));
}

private function getDelayTreshold(string $job): int
{
return $this->jobsDelayThreshold[$job] ?? 0;
}

private function getLimitPerMinutes(int $minutes): int
{
$maxRequests = config('services.opensea.rate.max_requests');

$perSeconds = config('services.opensea.rate.per_seconds');

$requestsPerHour = $maxRequests * $minutes * 60 / $perSeconds;

// limit to the requests per hour to leave some room for other tasks
return (int) floor($requestsPerHour / $this->getRateLimitFactor());
}

/**
* This value is used to ensure we only run as many jobs as we can with opensea,
* it depends of the number of jobs that use opensea consider the rare but
* possible case where jobs are running at the same time.
*/
private function getRateLimitFactor(): int
{
$totalJobsThatUseOpensea
= collect($this->jobsThatMayUseOpensea)->filter(fn ($job) => $this->usesOpensea($job))->count()
+ count($this->jobsThatUseOpensea);

return $totalJobsThatUseOpensea;
}

private function usesOpensea(string $job): bool
{
return Config::get('dashbrd.web3_providers.'.$job) === 'opensea' || in_array($job, $this->jobsThatUseOpensea);
}

private function dispatchDelayed(Closure $callback, int $index, string $job): void
{
if (! $this->usesOpensea($job)) {

$callback();

return;
}

// Note: I cant use delay directly on the job because it throws
// the "too many attempts" error after some time so im delaying
// with a queued closure instead
dispatch($callback)->delay($this->getDispatchAt($job, $index));
}
}
4 changes: 2 additions & 2 deletions app/Console/Commands/FetchCollectionBannerBatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public function handle(): int

$networks->map(function ($network) {
Collection::query()
->select(['collections.id', 'collections.address'])
->where('collections.network_id', '=', $network->id)
->select('id', 'address')
->where('network_id', $network->id)
->withoutSpamContracts()
->when($this->option('missing-only'), function (Builder $query) {
$query->whereNull('extra_attributes->banner');
Expand Down
30 changes: 26 additions & 4 deletions app/Console/Commands/FetchCollectionFloorPrice.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class FetchCollectionFloorPrice extends Command
{
use InteractsWithCollections;
use DependsOnOpenseaRateLimit, InteractsWithCollections;

/**
* The name and signature of the console command.
Expand All @@ -30,9 +30,31 @@ class FetchCollectionFloorPrice extends Command
*/
public function handle(): int
{
$this->forEachCollection(function ($collection) {
FetchCollectionFloorPriceJob::dispatch($collection->network->chain_id, $collection->address);
});
$usesOpensea = $this->usesOpensea(FetchCollectionFloorPriceJob::class);

// Job runs every hour (60 minutes)
$limit = $usesOpensea ? $this->getLimitPerMinutes(60) : null;

$this->forEachCollection(
callback: function ($collection, $index) {
$this->dispatchDelayed(
callback: fn () => FetchCollectionFloorPriceJob::dispatch(
chainId: $collection->network->chain_id,
address: $collection->address,
),
index: $index,
job: FetchCollectionFloorPriceJob::class,
);
},
queryCallback: function ($query) use ($limit) {
return $query
->when(
$limit !== null,
fn ($q) => $q->orderByFloorPriceLastFetchedAt()
);
},
limit: $limit === null ? null : (int) $limit
);

return Command::SUCCESS;
}
Expand Down
15 changes: 12 additions & 3 deletions app/Console/Commands/FetchCollectionOpenseaSlug.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class FetchCollectionOpenseaSlug extends Command
{
use InteractsWithCollections;
use DependsOnOpenseaRateLimit, InteractsWithCollections;

/**
* The name and signature of the console command.
Expand All @@ -30,15 +30,24 @@ class FetchCollectionOpenseaSlug extends Command
*/
public function handle(): int
{
// Job runs every 5 minutes
$limit = $this->getLimitPerMinutes(5);

$this->forEachCollection(
callback: function ($collection) {
FetchCollectionOpenseaSlugJob::dispatch($collection);
callback: function ($collection, $index) {
$this->dispatchDelayed(
callback: fn () => FetchCollectionOpenseaSlugJob::dispatch($collection),
index: $index,
job: FetchCollectionOpenseaSlugJob::class,
);
},
queryCallback: fn ($query) => $query
->orderByOpenseaSlugLastFetchedAt()
// Does not have an opensea slug
->whereNull('extra_attributes->opensea_slug')
// Has not been fetched
->whereNull('extra_attributes->opensea_slug_last_fetched_at'),
limit: $limit
);

return Command::SUCCESS;
Expand Down
6 changes: 3 additions & 3 deletions app/Console/Commands/InteractsWithCollections.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
trait InteractsWithCollections
{
/**
* @param Closure(Collection):void $callback
* @param Closure(Collection, int):void $callback
* @param Closure(Builder<Collection>):Builder<Collection>|null $queryCallback
*/
public function forEachCollection(Closure $callback, Closure $queryCallback = null, int $limit = null): void
Expand All @@ -25,7 +25,7 @@ public function forEachCollection(Closure $callback, Closure $queryCallback = nu
->withoutSpamContracts()
->first();

$collection && $callback($collection);
$collection && $callback($collection, 0);

return;
}
Expand All @@ -41,7 +41,7 @@ public function forEachCollection(Closure $callback, Closure $queryCallback = nu
)
->when($limit == null, fn ($query) => $query->chunkById(
100,
fn ($collections) => $collections->each($callback),
fn ($collections, $index) => $collections->each($callback, $index),
'collections.id', 'id')
);
}
Expand Down
56 changes: 56 additions & 0 deletions app/Console/Commands/PruneMetaImages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

class PruneMetaImages extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'prune-meta-images';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Removes unused meta images';

/**
* Execute the console command.
*/
public function handle(): int
{
$validImages = array_map(fn ($item) => $item->image_name, DB::select(get_query('gallery.get_meta_images')));
// Convert to associative array so we can use `isset`
$validImages = array_combine($validImages, $validImages);

$directory = storage_path(sprintf('meta/galleries/'));

if (is_dir($directory)) {
// Open directory
if ($handle = opendir($directory)) {
// Loop through each file in the directory
while (false !== ($file = readdir($handle))) {
// Check if file is a PNG and not in validImages
if (pathinfo($file, PATHINFO_EXTENSION) === 'png' && ! isset($validImages[$file])) {
// Delete the file
unlink($directory.$file);
}
}

// Close directory handle
closedir($handle);
}
}

return Command::SUCCESS;
}
}
44 changes: 44 additions & 0 deletions app/Console/Commands/UpdateDescriptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Jobs\UpdateNftDescription;
use App\Models\Network;
use Illuminate\Console\Command;

class UpdateDescriptions extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'nfts:update-descriptions {--start=} {--network=}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Dispatch a job to update descriptions for NFTs';

/**
* Execute the console command.
*/
public function handle(): int
{
$network = Network::find((int) $this->option('network'));

if (! $network) {
$this->warn('Network does not exist.');

return Command::SUCCESS;
}

UpdateNftDescription::dispatch((int) ($this->option('start') ?? 1), $network);

return Command::SUCCESS;
}
}
11 changes: 11 additions & 0 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use App\Console\Commands\MarketData\FetchPriceHistory;
use App\Console\Commands\MarketData\UpdateTokenDetails;
use App\Console\Commands\MarketData\VerifySupportedCurrencies;
use App\Console\Commands\PruneMetaImages;
use App\Console\Commands\SyncSpamContracts;
use App\Console\Commands\UpdateCollectionsFiatValue;
use App\Console\Commands\UpdateDiscordMembers;
Expand Down Expand Up @@ -120,6 +121,12 @@ private function scheduleJobsForCollectionsOrGalleries(Schedule $schedule): void
->command(FetchWalletNfts::class)
->withoutOverlapping()
->hourlyAt(10); // offset by 10 mins so it's not run the same time as FetchEnsDetails...

$schedule
->command(FetchCollectionOpenseaSlug::class)
->withoutOverlapping()
->everyFiveMinutes();

}

private function scheduleJobsForGalleries(Schedule $schedule): void
Expand All @@ -129,6 +136,10 @@ private function scheduleJobsForGalleries(Schedule $schedule): void
->withoutOverlapping()
->hourlyAt(2);

$schedule
->command(PruneMetaImages::class)
->daily();

$schedule
->command(UpdateGalleriesValue::class)
->withoutOverlapping()
Expand Down
Loading
Loading