1
0

New Service: Wikidata

This commit is contained in:
Richard Dern 2024-04-19 11:21:33 +02:00
parent 433e0a3199
commit 5b19490296
16 changed files with 1126 additions and 145 deletions

View File

@ -137,7 +137,7 @@ public function merge(array $array)
*/
public function get($key, $default = null)
{
return $this->content->get($key, $default);
return collect($this->content)->dot()->get($key, $default);
}
/**

View File

@ -0,0 +1,92 @@
<?php
namespace App\Console\Commands\Wikidata;
use App\Models\WikidataProperty;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
class UpdateProperties extends Command
{
protected $signature = 'wikidata:update-properties';
protected $description = 'Import Wikidata properties into the database.';
public function handle()
{
$data = $this->fetchData();
$this->importData($data);
}
/**
* Fetch data from Wikidata or cache.
*/
protected function fetchData(): array
{
$endpoint = 'https://query.wikidata.org/sparql';
$query = "
SELECT ?property ?propertyType ?propertyLabel ?propertyDescription ?propertyAltLabel WHERE {
?property wikibase:propertyType ?propertyType .
SERVICE wikibase:label { bd:serviceParam wikibase:language \"fr\". }
}
ORDER BY ASC(xsd:integer(STRAFTER(STR(?property), 'P')))";
$response = Http::throw()->timeout(60)->withHeaders(['Accept' => 'application/sparql-results+json'])
->get($endpoint, ['query' => $query]);
return $response->json();
}
/**
* Import data into the database.
*/
protected function importData(array $data): void
{
$total = count($data['results']['bindings']);
$this->info("Starting import of $total Wikidata properties.");
$bar = $this->output->createProgressBar($total);
$bar->start();
DB::transaction(function () use ($data, &$bar) {
$batchSize = 200;
foreach (array_chunk($data['results']['bindings'], $batchSize) as $batch) {
$this->insertBatch($batch, $bar);
}
});
$bar->finish();
$this->newLine(2);
$this->info('Properties imported successfully.');
}
/**
* Insert a batch of properties into the database.
*
* @param \Symfony\Component\Console\Helper\ProgressBar $bar
*/
protected function insertBatch(array $batch, $bar): void
{
$upsertData = [];
foreach ($batch as $result) {
$propertyId = str_replace('http://www.wikidata.org/entity/', '', $result['property']['value']);
$upsertData[] = [
'property_id' => $propertyId,
'property_type' => $result['propertyType']['value'] ?? null,
'label' => $result['propertyLabel']['value'] ?? null,
'description' => $result['propertyDescription']['value'] ?? null,
'alt_label' => $result['propertyAltLabel']['value'] ?? null,
];
$bar->advance();
}
WikidataProperty::upsert($upsertData, 'property_id', ['property_type', 'label', 'description', 'alt_label']);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class WikidataProperty extends Model
{
use HasFactory;
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Providers;
use App\Services\Wikidata\WikidataClient;
use App\Services\Wikidata\WikidataExtractor;
use Illuminate\Support\ServiceProvider;
class WikidataServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
$this->app->singleton(WikidataClient::class, function ($app) {
$config = $app['config']->get('services.wikidata');
return new WikidataClient($config);
});
$this->app->singleton(WikidataExtractor::class, function ($app) {
$inclusions = $app['config']->get('wikidata.inclusions', []);
$exclusions = $app['config']->get('wikidata.exclusions', []);
return new WikidataExtractor($exclusions, $inclusions);
});
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}

View File

@ -3,8 +3,14 @@
namespace App\Services\BundleCreator\Creators;
use App\Classes\Bundle;
use App\Exceptions\BundleAlreadyExists;
use App\Services\Wikidata\WikidataClient;
use App\Services\Wikidata\WikidataExtractor;
use Exception;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Str;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
@ -17,6 +23,63 @@ public function __construct(protected ?array $data, protected FilesystemAdapter
//
}
/**
* Create a bundle
*/
public function createBundle(): string
{
$entityId = $this->data['entity_id'];
if ($entityId === false) {
throw new Exception('Bundle creation cancelled');
}
$kind = $this->data['kind'];
$title = $this->data['title'];
$slug = Str::slug($title);
$date = now();
$path = sprintf('%s/%s/%s', static::$section, $kind, $slug);
$bundle = new Bundle($path, $this->disk);
if ($bundle->exists()) {
throw new BundleAlreadyExists(
sprintf('A bundle already exists in %s', $path)
);
}
$wikidata = app()->make(WikidataClient::class);
$extractor = app()->make(WikidataExtractor::class);
$completeEntity = $wikidata->getEntityData($entityId, true)['entities'][$entityId];
$extractor->extract($completeEntity, $entityId);
$bundle->metadata('wikidata/included')->setMany($extractor->included());
$bundle->metadata('wikidata/excluded')->setMany($extractor->excluded());
$bundle->metadata('wikidata/unused')->setMany($extractor->unused());
$bundle->metadata('wikidata/entity')->setMany($extractor->everythingElse());
$frenchTitle = $bundle->metadata('wikidata/entity')->get('labels.fr.value', null);
$originalTitle = $bundle->metadata('wikidata/entity')->get('labels.en.value', null);
if (!empty($originalTitle)) {
$bundle->metadata()->set('title', $originalTitle);
if (!empty($frenchTitle) && $frenchTitle !== $originalTitle) {
$bundle->metadata()->set('subTitle', $frenchTitle);
}
}
$bundle->metadata()->setMany([
'date' => $date->toIso8601String(),
]);
$bundle->markdown()->set('');
$bundle->save();
return $path;
}
/**
* Return a boolean value indicating if the creator can actually make the
* bundle using known data.
@ -26,7 +89,7 @@ public function canCreateBundle(): bool
return
!empty($this->data['kind'])
&& !empty($this->data['title'])
&& !empty($this->data['entityId']);
&& array_key_exists('entity_id', $this->data);
}
/**
@ -45,8 +108,16 @@ public function formSpecs(): ?array
$specs['title'] = fn () => text('Work title', '', '', true);
}
if (empty($this->data['entityId'])) {
$specs['entityId'] = fn () => text('Entity ID', '', '', true);
if (!empty($this->data['kind']) && !empty($this->data['title']) && empty($this->data['entity_id'])) {
$options = app()->make(WikidataClient::class)->searchEntityId($this->data['title']);
if (!empty($options)) {
$specs['entity_id'] = fn () => select('Confirm searched work', $options);
} else {
$specs['entity_id'] = fn () => confirm(
'No entityId was found in Wikidata for this work. Do you want to create the bundle anyway?'
);
}
}
return $specs;

View File

@ -0,0 +1,132 @@
<?php
namespace App\Services\Wikidata;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class WikidataClient
{
protected static string $cachePrefix = 'wikidata';
public function __construct(protected array $config)
{
}
/**
* Search for an entity ID for text submitted as $expression
*/
public function searchEntityId(string $expression)
{
$data = [
'action' => 'wbsearchentities',
'search' => $expression,
'language' => 'en',
'limit' => 10,
'format' => 'json',
];
$response = $this->getFromApi($data);
$items = [];
if (!empty($response['search'])) {
foreach ($response['search'] as $item) {
$items[$item['id']] = sprintf('[%s] %s (%s)', $item['id'], $item['label'], $item['description'] ?? null);
}
}
return $items;
}
/**
* Return complete set of data related to specified entity
*/
public function getEntityData(string $entityId)
{
$data = [
'action' => 'wbgetentities',
'ids' => $entityId,
'languages' => 'fr|en',
'format' => 'json',
];
return $this->getFromApi($data);
// if ($replaceIds) {
// $data = json_encode($result, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
// $properties = $this->getDeclaredPropertiesInEntity($data);
// $entities = $this->getDeclaredEntitiesInEntity($data, $entityId);
// $result = $this->replaceLabelsIn($result, $properties, $entities, $entityId);
// }
}
/**
* Return an array containing Wikidata Entities Id as keys and corresponding
* labels as values
*/
public function getLabelsForEntities(array $entities)
{
$result = [];
$batchSize = 50;
$batches = array_chunk($entities, $batchSize);
foreach ($batches as $batch) {
$ids = implode('|', $batch);
$data = [
'action' => 'wbgetentities',
'ids' => $ids,
'props' => 'labels|descriptions',
'languages' => 'fr|en',
'format' => 'json',
];
$result += $this->getFromApi($data)['entities'];
}
$labels = [];
foreach ($result as $id => $entity) {
if (
!isset($entity['labels']['fr']['value'])
&& !isset($entity['labels']['en']['value'])
) {
continue;
}
$label = $entity['labels']['fr']['value']
?? $entity['labels']['en']['value'];
$description = $entity['descriptions']['fr']['value']
?? $entity['descriptions']['en']['value']
?? null;
if ($label) {
$labels[$id] = Str::ucfirst($label);
}
}
return $labels;
}
/**
* Perform a GET request against wikidata API
*/
private function getFromApi(array $params)
{
$baseUrl = $this->config['baseApiUrl'];
$slug = Str::slug(http_build_query($params));
$cacheKey = sprintf('%s_%s', static::$cachePrefix, $slug);
if (Cache::has($cacheKey)) {
return Cache::get($cacheKey);
}
$response = Http::throw()->get($baseUrl, $params)->json();
Cache::put($cacheKey, $response, now()->addMonth());
return $response;
}
}

View File

@ -0,0 +1,308 @@
<?php
namespace App\Services\Wikidata;
use App\Models\WikidataProperty;
class WikidataExtractor
{
protected array $included;
protected array $excluded;
protected array $unused;
protected array $everythingElse;
protected string $entityId;
protected $properties;
protected $entities;
public function included()
{
return $this->included;
}
public function excluded()
{
return $this->excluded;
}
public function unused()
{
return $this->unused;
}
public function everythingElse()
{
return $this->everythingElse;
}
public function __construct(protected array $exclusions, protected array $inclusions)
{
}
/**
* Split data from specified array in three arrays containing explicitely
* included properties, explicitely excluded properties and unused
* properties (neither included or excluded)
*/
public function extract(array $entityData, string $entityId)
{
$json = json_encode($entityData, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$this->properties = $this->getDeclaredPropertiesInEntity($json);
$this->entities = $this->getDeclaredEntitiesInEntity($json, $entityId);
$result = $this->browse($entityData['claims']);
$this->included = $result['included'];
$this->excluded = $result['excluded'];
$this->unused = $result['unused'];
unset($entityData['claims']);
$this->everythingElse = $entityData;
}
/**
* Return an array containing Wikidata Property ID as keys and corresponding
* label as values
*/
private function getDeclaredPropertiesInEntity(string $data)
{
preg_match_all('/P\d{1,}/', $data, $matches);
natsort($matches[0]);
$ids = collect(array_values($matches[0]))->unique()->all();
$properties = WikidataProperty::whereIn('property_id', $ids)->get();
$result = collect($ids)->combine($properties->pluck('label'));
return $result->toArray();
}
/**
* Return an array containing Wikidata Property ID as keys and corresponding
* label as values
*/
private function getDeclaredEntitiesInEntity(string $data, string $entityId)
{
preg_match_all('/Q\d{1,}/', $data, $matches);
natsort($matches[0]);
$ids = collect(array_values($matches[0]))->except($entityId)->unique()->all();
return app()->make(WikidataClient::class)->getLabelsForEntities($ids);
}
/**
* Recursively browse Wikidata array
*/
private function browse(array $claims)
{
$included = [];
$excluded = [];
$unused = [];
foreach ($claims as $key => $data) {
$isExcluded = in_array($key, $this->exclusions);
$isIncluded = in_array($key, collect($this->inclusions)->flatten()->values()->toArray());
$isUnused = !$isExcluded && !$isIncluded;
$claim = $this->parseClaims($data, $isIncluded);
if ($isExcluded) {
$newKey = $this->replaceValue($key, true, true);
$excluded[$newKey] = $claim;
} elseif ($isIncluded) {
$newKey = $this->replaceValue($key, true);
$included[$key] = $claim;
} elseif ($isUnused) {
$newKey = $this->replaceValue($key, true, true);
$unused[$newKey] = $claim;
}
}
return [
'excluded' => $excluded,
'included' => $this->reorganizeIncluded($included),
'unused' => $unused,
];
}
/**
* Parse claims of a specific property
*/
private function parseClaims(array $data, bool $parentIncluded)
{
$result = [];
foreach ($data as $claim) {
$result[] = $this->parseClaim($claim, $parentIncluded);
}
return $result;
}
/**
* Parse a specific claim
*/
private function parseClaim(array $data, bool $parentIncluded)
{
$value = $this->parseSnak($data['mainsnak'], $parentIncluded);
if (!empty($data['qualifiers'])) {
$itemQualifiers = [];
foreach ($data['qualifiers'] as $qualifierProperty => $qualifiers) {
$qualifierKey = $this->replaceValue($qualifierProperty, true, !$parentIncluded);
foreach ($qualifiers as $qualifierData) {
$qualifierValue = $this->parseSnak($qualifierData, $parentIncluded);
$itemQualifiers[$qualifierKey][] = $qualifierValue;
}
}
$result = [
$value => $itemQualifiers,
];
} else {
$result = $value;
}
return $result;
}
/**
* Parse a specific snak
*/
private function parseSnak(array $data, bool $parentIncluded)
{
if (empty($data['datavalue']['value'])) {
dd($data);
}
$value = $data['datavalue']['value'];
$valueType = $data['datavalue']['type'];
switch ($valueType) {
case 'wikibase-entityid':
$value = $this->replaceValue($value['id'], true, !$parentIncluded);
break;
case 'string':
$value = $this->replaceValue($value, true, !$parentIncluded);
break;
case 'time':
$value = $value['time'];
break;
case 'quantity':
$value = $value['amount'];
break;
case 'monolingualtext':
$value = $value['text'];
break;
default:
dd($data['mainsnak']);
}
return $value;
}
/**
* Replace a value with a more human-friendly version. Basically replaces
* Wikidata entities and properties with labels stored in database, if it
* applies.
*/
private function replaceValue(string $value, bool $showCode = true, bool $showLabel = true)
{
$isExcluded = in_array($value, $this->exclusions);
$isIncluded = in_array($value, collect($this->inclusions)->flatten()->values()->toArray());
$isUnused = !$isExcluded && !$isIncluded;
$code = $value;
$label = $value;
if (array_key_exists($value, $this->properties)) {
$label = $this->properties[$value];
} elseif (array_key_exists($value, $this->entities)) {
$label = $this->entities[$value];
}
$both = $code !== $label ? sprintf('[%s] %s', $code, $label) : $value;
if ($showCode && $showLabel) {
return $both;
} elseif ($showCode) {
return $code;
} else {
return $label;
}
}
/**
* Take the "raw" included data and reorganize it according to the
* "inclusions" Wikidata configuration
*/
private function reorganizeIncluded(array $includedData)
{
$reorganized = [];
foreach ($this->inclusions as $category => $properties) {
$result = $this->includeProperties($includedData, $properties);
if (!empty($result)) {
$reorganized[$category] = $result;
}
}
return $reorganized;
}
/**
* Include specific properties
*/
private function includeProperties($includedData, $properties)
{
$result = [];
foreach ($properties as $propertyId) {
if (!array_key_exists($propertyId, $includedData)) {
continue;
}
$newKey = $this->replaceValue($propertyId, false, true);
$values = $includedData[$propertyId];
$result[$newKey] = $this->includeValues($values);
}
return $result;
}
/**
* Include specific values
*/
private function includeValues(array $values)
{
$newValues = [];
foreach ($values as $key => $value) {
$newKey = $this->replaceValue($key, false, true);
if (is_array($value)) {
$value = $this->includeValues($value);
} else {
$value = $this->replaceValue($value, false, true);
}
$newValues[$newKey] = $value;
}
return $newValues;
}
}

View File

@ -4,4 +4,5 @@
App\Providers\AppServiceProvider::class,
App\Providers\BundleCreatorServiceProvider::class,
App\Providers\RebrickableServiceProvider::class,
App\Providers\WikidataServiceProvider::class,
];

View File

@ -19,6 +19,10 @@
'key' => env('REBRICKABLE_API_KEY'),
],
'wikidata' => [
'baseApiUrl' => 'https://www.wikidata.org/w/api.php',
],
'postmark' => [
'token' => env('POSTMARK_TOKEN'),
],

View File

@ -0,0 +1,234 @@
<?php
return [
'P18',
'P31',
'P214',
'P227',
'P244',
'P268',
'P269',
'P373',
'P434',
'P436',
'P437',
'P444',
'P462',
'P480',
'P527',
'P580',
'P582',
'P646',
'P674',
'P691',
'P905',
'P950',
'P973',
'P1015',
'P1110',
'P1237',
'P1343',
'P1407',
'P1417',
'P1424',
'P1562',
'P1617',
'P1657',
'P1695',
'P1804',
'P1889',
'P1933',
'P1970',
'P1981',
'P2047',
'P2061',
'P2130',
'P2142',
'P2163',
'P2334',
'P2346',
'P2363',
'P2465',
'P2508',
'P2509',
'P2518',
'P2529',
'P2572',
'P2581',
'P2603',
'P2629',
'P2631',
'P2636',
'P2637',
'P2638',
'P2671',
'P2684',
'P2703',
'P2704',
'P2747',
'P2755',
'P2756',
'P2758',
'P2769',
'P2899',
'P3077',
'P3107',
'P3110',
'P3121',
'P3129',
'P3135',
'P3138',
'P3141',
'P3143',
'P3145',
'P3156',
'P3203',
'P3212',
'P3216',
'P3222',
'P3306',
'P3402',
'P3417',
'P3428',
'P3478',
'P3553',
'P3593',
'P3650',
'P3803',
'P3804',
'P3808',
'P3834',
'P3844',
'P3854',
'P3933',
'P4276',
'P4277',
'P4282',
'P4312',
'P4342',
'P4437',
'P4438',
'P4784',
'P4835',
'P4515',
'P4529',
'P4632',
'P4665',
'P4780',
'P4783',
'P4786',
'P4834',
'P4839',
'P5021',
'P5032',
'P5099',
'P5128',
'P5150',
'P5201',
'P5255',
'P5253',
'P5311',
'P5327',
'P5357',
'P5387',
'P5495',
'P5693',
'P5786',
'P5829',
'P5845',
'P5865',
'P5885',
'P5905',
'P5925',
'P5970',
'P5987',
'P5990',
'P6058',
'P6081',
'P6104',
'P6127',
'P6133',
'P6145',
'P6181',
'P6466',
'P6467',
'P6562',
'P6584',
'P6643',
'P6658',
'P6760',
'P6839',
'P7003',
'P7091',
'P7107',
'P7118',
'P7132',
'P7236',
'P7285',
'P7293',
'P7299',
'P7334',
'P7501',
'P7502',
'P7573',
'P7777',
'P7822',
'P7970',
'P7975',
'P7978',
'P8013',
'P8033',
'P8179',
'P8189',
'P8278',
'P8313',
'P8419',
'P8600',
'P8687',
'P8796',
'P8823',
'P8847',
'P8874',
'P8885',
'P8889',
'P8958',
'P9022',
'P9086',
'P9629',
'P9697',
'P9821',
'P9885',
'P9984',
'P9979',
'P10045',
'P10096',
'P10164',
'P10167',
'P10239',
'P10255',
'P10267',
'P10269',
'P10291',
'P10432',
'P10565',
'P10688',
'P10845',
'P11196',
'P11346',
'P11408',
'P11505',
'P11514',
'P11682',
'P11686',
'P11871',
'P11948',
'P12035',
'P12086',
'P12096',
'P12159',
'P12196',
'P12241',
'P12329',
'P12492',
'P12544',
];

View File

@ -0,0 +1,82 @@
<?php
return [
'afterWorkBy' => [
'P1877',
],
'staff' => [
'P50',
'P57',
'P58',
'P86',
'P162',
'P170',
'P175',
'P178',
'P344',
'P371',
'P1040',
'P1431',
'P2515',
'P2554',
'P3092',
'P5028',
],
'productionCompanies' => [
'P272',
],
'genres' => [
'P136',
],
'subjects' => [
'P921',
],
'publishers' => [
'P123',
],
'distribution' => [
'P161',
],
'sagas' => [
'P179',
],
'links' => [
'P856',
'P345',
'P1258',
'P1265',
'P1267',
'P1651',
'P1712',
'P1733',
'P1874',
'P2002',
'P2003',
'P2013',
'P2397',
'P3984',
'P4013',
'P4073',
'P4477',
'P4947',
'P4983',
'P5749',
'P6262',
'P6398',
'P7595',
'P7596',
'P8055',
'P9586',
'P9751',
'P11460',
],
'locations' => [
'P840',
'P915',
],
'miscKeywords' => [
'P180',
'P8371',
'P8411',
],
];

View File

@ -0,0 +1,118 @@
<?php
return [
'links' => [
'P856' => [
'template' => null,
'title' => 'Site officiel',
],
'P345' => [
'template' => 'https://www.imdb.com/title/%s/',
'title' => 'IMDB',
],
'P1258' => [
'template' => 'https://www.rottentomatoes.com/%s',
'title' => 'Rotten Tomatoes',
],
'P1265' => [
'template' => 'https://www.allocine.fr/film/fichefilm_gen_cfilm=%s.html',
'title' => 'AlloCiné',
],
'P1267' => [
'template' => 'https://www.allocine.fr/series/ficheserie_gen_cserie=%s.html',
'title' => 'AlloCiné',
],
'P1651' => [
'template' => 'https://www.youtube.com/watch?v=%s',
'title' => 'YouTube',
],
'P1712' => [
'template' => 'https://www.metacritic.com/%s',
'title' => 'Metacritic',
],
'P1733' => [
'template' => 'https://store.steampowered.com/app/%s/',
'title' => 'Steam',
],
'P1874' => [
'template' => 'https://www.netflix.com/title/%s',
'title' => 'Netflix',
],
'P2002' => [
'template' => 'https://twitter.com/%s',
'title' => 'X',
],
'P2003' => [
'template' => 'https://www.instagram.com/%s',
'title' => 'Instagram',
],
'P2013' => [
'template' => 'https://www.facebook.com/%s',
'title' => 'facebook',
],
'P2397' => [
'template' => 'https://www.youtube.com/channel/%s',
'title' => 'Chaîne YouTube',
],
'P3984' => [
'template' => 'https://www.reddit.com/r/%s/',
'title' => 'Reddit',
],
'P4013' => [
'template' => 'https://giphy.com/%s',
'title' => 'Giphy',
],
'P4073' => [
'template' => 'https://community.fandom.com/wiki/w:c:%s',
'title' => 'Fandom',
],
'P4477' => [
'template' => 'https://www.humblebundle.com/store/%s',
'title' => 'Humble Store',
],
'P4947' => [
'template' => 'https://www.themoviedb.org/movie/%s',
'title' => 'TMDB',
],
'P4983' => [
'template' => 'https://www.themoviedb.org/tv/%s',
'title' => 'TMBD',
],
'P5749' => [
'template' => 'https://www.amazon.com/dp/%s',
'title' => 'Amazon',
],
'P6262' => [
'template' => 'https://community.fandom.com/wiki/w:c:%s',
'title' => 'Fandom',
],
'P6398' => [
'template' => 'https://itunes.apple.com/us/movie/id%s',
'title' => 'iTunes',
],
'P7595' => [
'template' => 'https://www.disneyplus.com/movies/wd/%s',
'title' => 'Disney+',
],
'P7596' => [
'template' => 'https://www.disneyplus.com/series/wp/%s',
'title' => 'Disney+',
],
'P8055' => [
'template' => 'https://www.amazon.com/gp/video/detail/%s',
'title' => 'Amazon Prime',
],
'P9586' => [
'template' => 'https://tv.apple.com/movie/%s',
'title' => 'Apple TV',
],
'P9751' => [
'template' => 'https://tv.apple.com/show/%s',
'title' => 'Apple TV',
],
'P11460' => [
'template' => 'https://app.plex.tv/desktop/#!/provider/tv.plex.provider.metadata/details?key=/library/metadata/%s',
'title' => 'Plex',
],
],
];

View File

@ -1,49 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@ -1,35 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

View File

@ -1,57 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('wikidata_properties', function (Blueprint $table) {
$table->id();
$table->string('property_id')->unique();
$table->string('property_type')->nullable();
$table->text('label')->nullable();
$table->text('description')->nullable();
$table->text('alt_label')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('wikidata_properties');
}
};