New Plugins and Themes tables built from AspireSync data (#65)

* chore: rename sync tables to use sync_ prefix

* feat: migration for new plugins table

* chore: create themes table migration

* refactor: move sync models to new names and subdir

* refactor: move Plugin and Theme models to App\Models\WpOrg

* feat: use parallel tasks in php-cs-fixer

* format: keep attributes on their own lines

* fix: use sync_themes for stub search
This commit is contained in:
Chuck Adams 2024-10-26 14:07:26 -06:00 committed by GitHub
parent 70cedeea24
commit 4fd07ea9b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 638 additions and 40 deletions

4
.gitignore vendored
View file

@ -21,3 +21,7 @@ yarn-error.log
/.idea
/.vscode
/.zed

# helper files would not normally be ignored, but they're currently broken, so it's a local decision to use them
_ide_helper.php
.phpstorm.meta.php

View file

@ -8,9 +8,10 @@ $finder = (new PhpCsFixer\Finder())
'bootstrap',
])
->notPath([
// 'dump.php',
// 'src/exception_file.php',
]);
'_ide_helper.php',
'.phpstorm.meta.php',
])
;

return (new PhpCsFixer\Config())
->setRules([
@ -18,4 +19,5 @@ return (new PhpCsFixer\Config())
'@PHP83Migration' => true,
])
->setFinder($finder)
->setCacheFile(__DIR__ . '/.cache/.php_cs.cache');
->setCacheFile(__DIR__ . '/.cache/.php_cs.cache')
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect());

View file

@ -74,6 +74,16 @@ sh-%: ## Execute shell for the container where % is a service name (webapp, post
clear-cache: ## Clear cache
bin/dcrun php artisan optimize:clear

helpers: ## Generate Laravel IDE helpers
@if [ -z $(FORCE) ]; then \
echo "*** laravel-ide-helper generates incorrect code for Laravel 11, and is not recommended at this time."; \
echo "*** if you wish to generate helpers anyway, run 'make helpers FORCE=1'"; \
exit 1; \
fi
bin/dcrun php artisan ide-helper:generate
bin/dcrun php artisan ide-helper:meta
bin/dcrun php artisan ide-helper:models --write --smart-reset

lint: style quality ## Check code standards conformance

check: lint test ## Run lint and unit tests

View file

@ -4,6 +4,7 @@ namespace App\Data\WpOrg\Themes;

use App\Data\WpOrg\PageInfo;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Attributes\Validation\Present;
use Spatie\LaravelData\Data;
use stdClass;

@ -16,6 +17,7 @@ class QueryThemesResponse extends Data
public function __construct(
#[MapOutputName('info')]
public readonly PageInfo $pageInfo,
#[Present]
public readonly array $themes,
) {}


View file

@ -35,7 +35,7 @@ class ThemeController extends Controller
$skip = ($page - 1) * $perPage;

// TODO: process search and other filters
$themes = DB::table('themes')
$themes = DB::table('sync_themes')
->skip($skip)
->take($perPage)
->get()

View file

@ -4,9 +4,11 @@ namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
abstract class BaseModel extends Model
{
// use HasUuids; // can't be undone, so it's better to define on each subclass

public $timestamps = false;

protected $guarded = []; // everything is fillable by default
}

View file

@ -1,16 +1,26 @@
<?php

namespace App\Models;
namespace App\Models\Sync;

use App\Models\BaseModel;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Concerns\HasUuids;

class Plugin extends BaseModel
/**
* @property string $id
* @property string $name
* @property string $slug
* @property string|null $current_version
* @property CarbonImmutable $updated
* @property string $status
* @property CarbonImmutable $pulled_at
* @property array|null $metadata
*/
class SyncPlugin extends BaseModel
{
use HasUuids;

protected $table = 'plugins';

protected $fillable = ['name', 'slug', 'current_version', 'status', 'metadata'];
protected $table = 'sync_plugins';

protected function casts(): array
{

View file

@ -1,16 +1,26 @@
<?php

namespace App\Models;
namespace App\Models\Sync;

use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Concerns\HasUuids;

class PluginFile extends BaseModel
/**
* @property string $id
* @property string $plugin_id
* @property string|null $file_url
* @property string $type
* @property string $version
* @property array|null $metadata
* @property \Carbon\CarbonImmutable $created
* @property \Carbon\CarbonImmutable|null $processed
* @property string|null $hash
*/
class SyncPluginFile extends BaseModel
{
use HasUuids;

protected $table = 'plugin_files';

protected $fillable = ['file_url', 'type', 'metadata', 'hash'];
protected $table = 'sync_plugin_files';

protected function casts(): array
{

View file

@ -1,16 +1,24 @@
<?php

namespace App\Models;
namespace App\Models\Sync;

use App\Models\BaseModel;
use Illuminate\Database\Eloquent\Concerns\HasUuids;

class Theme extends BaseModel
/**
* @property string $id
* @property string $name
* @property string $slug
* @property string|null $current_version
* @property string $updated
* @property string $pulled_at
* @property array|null $metadata
*/
class SyncTheme extends BaseModel
{
use HasUuids;

protected $table = 'themes';

protected $fillable = ['file_url', 'type', 'metadata', 'hash'];
protected $table = 'sync_themes';

protected function casts(): array
{

View file

@ -1,14 +1,27 @@
<?php

namespace App\Models;
namespace App\Models\Sync;

use App\Models\BaseModel;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Concerns\HasUuids;

class ThemeFile extends BaseModel
/**
* @property string $id
* @property string $theme_id
* @property string|null $file_url
* @property string $type
* @property string $version
* @property array|null $metadata
* @property CarbonImmutable $created
* @property CarbonImmutable|null $processed
* @property string|null $hash
*/
class SyncThemeFile extends BaseModel
{
use HasUuids;

protected $table = 'theme_files';
protected $table = 'sync_theme_files';

protected $fillable = ['file_url', 'type', 'metadata', 'hash'];

View file

@ -5,40 +5,40 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\DatabaseNotification;
use Illuminate\Notifications\DatabaseNotificationCollection;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;

/**
* @property int $id
* @property string $name
* @property string $email
* @property Carbon|null $email_verified_at
* @property string $password
* @property string|null $remember_token
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read DatabaseNotificationCollection<int, DatabaseNotification> $notifications
* @property-read int|null $notifications_count
*/
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory;
use Notifiable;

/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];

/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];

/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [

100
app/Models/WpOrg/Plugin.php Normal file
View file

@ -0,0 +1,100 @@
<?php

namespace App\Models\WpOrg;

use App\Models\BaseModel;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Concerns\HasUuids;

/**
* @property string $id
* @property string $slug
* @property string $name
* @property string $short_description
* @property string $description
* @property string $version
* @property string $author
* @property string $requires
* @property string $requires_php
* @property string $tested
* @property string $download_link
* @property CarbonImmutable $added
* @property CarbonImmutable $last_updated
* @property string $author_profile
* @property int $rating
* @property array $ratings
* @property int $num_ratings
* @property int $support_threads
* @property int $support_threads_resolved
* @property int $active_installs
* @property int $downloaded
* @property string $homepage
* @property array $banners
* @property array $tags
* @property string $donate_link
* @property array $contributors
* @property array $icons
* @property array $source
* @property string $business_model
* @property string $commercial_support_url
* @property string $support_url
* @property string $preview_link
* @property string $repository_url
* @property array $requires_plugins
* @property array $compatibility
* @property array $screenshots
* @property array $sections
* @property array $versions
* @property array $upgrade_notice
*/
class Plugin extends BaseModel
{
use HasUuids;

protected $table = 'plugins';

protected function casts(): array
{
return [
'id' => 'string',
'slug' => 'string',
'name' => 'string',
'short_description' => 'string',
'description' => 'string',
'version' => 'string',
'author' => 'string',
'requires' => 'string',
'requires_php' => 'string',
'tested' => 'string',
'download_link' => 'string',
'added' => 'immutable_datetime',
'last_updated' => 'immutable_datetime',
'author_profile' => 'string',
'rating' => 'integer',
'ratings' => 'array',
'num_ratings' => 'integer',
'support_threads' => 'integer',
'support_threads_resolved' => 'integer',
'active_installs' => 'integer',
'downloaded' => 'integer',
'homepage' => 'string',
'banners' => 'array',
'tags' => 'array',
'donate_link' => 'string',
'contributors' => 'array',
'icons' => 'array',
'source' => 'array',
'business_model' => 'string',
'commercial_support_url' => 'string',
'support_url' => 'string',
'preview_link' => 'string',
'repository_url' => 'string',
'requires_plugins' => 'array',
'compatibility' => 'array',
'screenshots' => 'array',
'sections' => 'array',
'versions' => 'array',
'upgrade_notice' => 'array',
];
}
}

View file

@ -0,0 +1,72 @@
<?php

namespace App\Models\WpOrg;

use App\Models\BaseModel;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Concerns\HasUuids;

/**
* @property string $id
* @property string $slug
* @property string $name
* @property string $version
* @property string $download_link
* @property string $requires_php
* @property CarbonImmutable $last_updated
* @property CarbonImmutable $creation_time
* @property string $preview_url
* @property string $screenshot_url
* @property array $ratings
* @property int $rating
* @property int $num_ratings
* @property string $reviews_url
* @property int $downloaded
* @property int $active_installs
* @property string $homepage
* @property array $sections
* @property array $tags
* @property array $versions
* @property array $requires
* @property bool $is_commercial
* @property string $external_support_url
* @property bool $is_community
* @property string $external_repository_url
*/
class Theme extends BaseModel
{
use HasUuids;

protected $table = 'themes';

protected function casts(): array
{
return [
'id' => 'string',
'slug' => 'string',
'name' => 'string',
'version' => 'string',
'download_link' => 'string',
'requires_php' => 'string',
'last_updated' => 'datetime',
'creation_time' => 'datetime',
'preview_url' => 'string',
'screenshot_url' => 'string',
'ratings' => 'array',
'rating' => 'integer',
'num_ratings' => 'integer',
'reviews_url' => 'string',
'downloaded' => 'integer',
'active_installs' => 'integer',
'homepage' => 'string',
'sections' => 'array',
'tags' => 'array',
'versions' => 'array',
'requires' => 'array',
'is_commercial' => 'boolean',
'external_support_url' => 'string',
'is_community' => 'boolean',
'external_repository_url' => 'string',
];
}
}

View file

@ -13,6 +13,7 @@
"thecodingmachine/safe": "^2.5"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.2",
"fakerphp/faker": "^1.23",
"friendsofphp/php-cs-fixer": "^3.64",
"larastan/larastan": "^2.0",

221
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "98641244ec843f95084a8f87359e0b36",
"content-hash": "7a779242c84a9bf7dd79194833da8178",
"packages": [
{
"name": "amphp/amp",
@ -8133,6 +8133,152 @@
}
],
"packages-dev": [
{
"name": "barryvdh/laravel-ide-helper",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "7956ccb4943f8532d008c17a1094b34bb6394d56"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/7956ccb4943f8532d008c17a1094b34bb6394d56",
"reference": "7956ccb4943f8532d008c17a1094b34bb6394d56",
"shasum": ""
},
"require": {
"barryvdh/reflection-docblock": "^2.1.2",
"composer/class-map-generator": "^1.0",
"ext-json": "*",
"illuminate/console": "^11.15",
"illuminate/database": "^11.15",
"illuminate/filesystem": "^11.15",
"illuminate/support": "^11.15",
"nikic/php-parser": "^4.18 || ^5",
"php": "^8.2",
"phpdocumentor/type-resolver": "^1.1.0"
},
"require-dev": {
"ext-pdo_sqlite": "*",
"friendsofphp/php-cs-fixer": "^3",
"illuminate/config": "^11.15",
"illuminate/view": "^11.15",
"mockery/mockery": "^1.4",
"orchestra/testbench": "^9.2",
"phpunit/phpunit": "^10.5",
"spatie/phpunit-snapshot-assertions": "^4 || ^5",
"vimeo/psalm": "^5.4"
},
"suggest": {
"illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10|^11)."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.2-dev"
},
"laravel": {
"providers": [
"Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Barryvdh\\LaravelIdeHelper\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.",
"keywords": [
"autocomplete",
"codeintel",
"helper",
"ide",
"laravel",
"netbeans",
"phpdoc",
"phpstorm",
"sublime"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-ide-helper/issues",
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.2.0"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2024-10-17T16:43:13+00:00"
},
{
"name": "barryvdh/reflection-docblock",
"version": "v2.1.3",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/ReflectionDocBlock.git",
"reference": "c6fad15f7c878be21650c51e1f841bca7e49752e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/c6fad15f7c878be21650c51e1f841bca7e49752e",
"reference": "c6fad15f7c878be21650c51e1f841bca7e49752e",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "^8.5.14|^9"
},
"suggest": {
"dflydev/markdown": "~1.0",
"erusev/parsedown": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-0": {
"Barryvdh": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "mike.vanriel@naenius.com"
}
],
"support": {
"source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.1.3"
},
"time": "2024-10-23T11:41:03+00:00"
},
{
"name": "brianium/paratest",
"version": "v7.6.0",
@ -8290,6 +8436,79 @@
],
"time": "2022-12-23T10:58:28+00:00"
},
{
"name": "composer/class-map-generator",
"version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/composer/class-map-generator.git",
"reference": "98bbf6780e56e0fd2404fe4b82eb665a0f93b783"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/class-map-generator/zipball/98bbf6780e56e0fd2404fe4b82eb665a0f93b783",
"reference": "98bbf6780e56e0fd2404fe4b82eb665a0f93b783",
"shasum": ""
},
"require": {
"composer/pcre": "^2.1 || ^3.1",
"php": "^7.2 || ^8.0",
"symfony/finder": "^4.4 || ^5.3 || ^6 || ^7"
},
"require-dev": {
"phpstan/phpstan": "^1.6",
"phpstan/phpstan-deprecation-rules": "^1",
"phpstan/phpstan-phpunit": "^1",
"phpstan/phpstan-strict-rules": "^1.1",
"phpunit/phpunit": "^8",
"symfony/filesystem": "^5.4 || ^6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\ClassMapGenerator\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Utilities to scan PHP code and generate class maps.",
"keywords": [
"classmap"
],
"support": {
"issues": "https://github.com/composer/class-map-generator/issues",
"source": "https://github.com/composer/class-map-generator/tree/1.4.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2024-10-03T18:14:00+00:00"
},
{
"name": "composer/pcre",
"version": "3.3.1",

View file

@ -0,0 +1,30 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
public function up(): void
{
Schema::rename('plugin_files', 'sync_plugin_files');
Schema::rename('plugins', 'sync_plugins');
Schema::rename('revisions', 'sync_revisions');
Schema::rename('sites', 'sync_sites');
Schema::rename('stats', 'sync_stats');
Schema::rename('theme_files', 'sync_theme_files');
Schema::rename('themes', 'sync_themes');
Schema::rename('not_found_items', 'sync_not_found_items');
}

public function down(): void
{
Schema::rename('sync_plugin_files', 'plugin_files');
Schema::rename('sync_plugins', 'plugins');
Schema::rename('sync_revisions', 'revisions');
Schema::rename('sync_sites', 'sites');
Schema::rename('sync_stats', 'stats');
Schema::rename('sync_theme_files', 'theme_files');
Schema::rename('sync_themes', 'themes');
Schema::rename('sync_not_found_items', 'not_found_items');
}
};

View file

@ -0,0 +1,59 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
public function up(): void
{
Schema::create('plugins', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->foreignUuid('sync_id')->nullable()->references('id')->on('sync_plugins');
$table->string('slug')->index();
$table->string('name')->index();
$table->string('short_description', 150)->fulltext();
$table->text('description');
$table->string('version');
$table->string('author')->index();
$table->string('requires');
$table->string('requires_php');
$table->string('tested');
$table->string('download_link');
$table->dateTime('added')->index();
$table->dateTime('last_updated')->index();
// all fields below are nullable or have a zero default
$table->string('author_profile')->nullable();
$table->unsignedSmallInteger('rating')->default(0);
$table->jsonb('ratings')->nullable();
$table->unsignedInteger('num_ratings')->default(0);
$table->unsignedInteger('support_threads')->default(0);
$table->unsignedInteger('support_threads_resolved')->default(0);
$table->unsignedInteger('active_installs')->default(0);
$table->unsignedInteger('downloaded')->default(0);
$table->string('homepage')->nullable();
$table->jsonb('banners')->nullable();
$table->jsonb('tags')->nullable(); // denormalized, will be in a join table too
$table->string('donate_link')->nullable();
$table->jsonb('contributors')->nullable();
$table->jsonb('icons')->nullable();
$table->jsonb('source')->nullable();
$table->string('business_model')->nullable(); // 'commercial'|'community'|false (we'll store false as a string)
$table->string('commercial_support_url')->nullable();
$table->string('support_url')->nullable();
$table->string('preview_link')->nullable();
$table->string('repository_url')->nullable();
$table->jsonb('requires_plugins')->nullable(); // string[]
$table->jsonb('compatibility')->nullable(); // string[]
$table->jsonb('screenshots')->nullable();
$table->jsonb('sections')->nullable();
$table->jsonb('versions')->nullable();
$table->jsonb('upgrade_notice')->nullable();
});
}

public function down(): void
{
Schema::dropIfExists('plugins');
}
};

View file

@ -0,0 +1,56 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
public function up(): void
{
Schema::create('authors', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('user_nicename'); // more or less the user "slug", used in permalinks
$table->string('profile')->nullable();
$table->string('avatar')->nullable();
$table->string('display_name')->nullable();
$table->string('author')->nullable();
$table->string('author_url')->nullable();
});

Schema::create('themes', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->foreignUuid('sync_id')->nullable()->references('id')->on('sync_themes');
$table->string('slug');
$table->string('name');
$table->string('version');
$table->foreignUuid('author_id')->references('id')->on('authors');
$table->string('download_link');
$table->string('requires_php');
$table->datetime('last_updated');
$table->datetime('creation_time');
$table->string('preview_url')->nullable();
$table->string('screenshot_url')->nullable();
$table->jsonb('ratings')->nullable();
$table->unsignedSmallInteger('rating')->default(0);
$table->unsignedInteger('num_ratings')->default(0);
$table->string('reviews_url')->nullable();
$table->unsignedInteger('downloaded')->default(0);
$table->unsignedInteger('active_installs')->default(0);
$table->string('homepage')->nullable();
$table->jsonb('sections')->nullable();
$table->jsonb('tags')->nullable();
$table->jsonb('versions')->nullable();
$table->jsonb('requires')->nullable();
$table->boolean('is_commercial')->default(false);
$table->string('external_support_url')->nullable();
$table->boolean('is_community')->default(false);
$table->string('external_repository_url')->nullable();
});
}

public function down(): void
{
Schema::dropIfExists('themes');
Schema::dropIfExists('authors');
}
};