Added metadata status to item record view.

This commit is contained in:
arabcoders
2025-05-09 18:56:02 +03:00
parent 0ff127875f
commit 689716e366
3 changed files with 295 additions and 127 deletions

25
composer.lock generated
View File

@@ -3588,12 +3588,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "45b01f4e60c350f72a8697056674e449e053935a"
"reference": "59be420c5cdc0c3c9cdae31804d32fefb515a918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/45b01f4e60c350f72a8697056674e449e053935a",
"reference": "45b01f4e60c350f72a8697056674e449e053935a",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/59be420c5cdc0c3c9cdae31804d32fefb515a918",
"reference": "59be420c5cdc0c3c9cdae31804d32fefb515a918",
"shasum": ""
},
"conflict": {
@@ -3611,7 +3611,7 @@
"airesvsg/acf-to-rest-api": "<=3.1",
"akaunting/akaunting": "<2.1.13",
"akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53",
"alextselegidis/easyappointments": "<=1.5",
"alextselegidis/easyappointments": "<=1.5.1",
"alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1",
"amazing/media2click": ">=1,<1.3.3",
"ameos/ameos_tarteaucitron": "<1.2.23",
@@ -3715,7 +3715,7 @@
"contao/managed-edition": "<=1.5",
"corveda/phpsandbox": "<1.3.5",
"cosenary/instagram": "<=2.3",
"craftcms/cms": "<=4.14.14|>=5,<=5.6.16",
"craftcms/cms": "<4.15.3|>=5,<5.7.5",
"croogo/croogo": "<4",
"cuyz/valinor": "<0.12",
"czim/file-handling": "<1.5|>=2,<2.3",
@@ -3948,6 +3948,7 @@
"klaviyo/magento2-extension": ">=1,<3",
"knplabs/knp-snappy": "<=1.4.2",
"kohana/core": "<3.3.3",
"koillection/koillection": "<1.6.12",
"krayin/laravel-crm": "<=1.3",
"kreait/firebase-php": ">=3.2,<3.8.1",
"kumbiaphp/kumbiapp": "<=1.1.1",
@@ -3966,7 +3967,7 @@
"latte/latte": "<2.10.8",
"lavalite/cms": "<=9|==10.1",
"lcobucci/jwt": ">=3.4,<3.4.6|>=4,<4.0.4|>=4.1,<4.1.5",
"league/commonmark": "<2.6",
"league/commonmark": "<2.7",
"league/flysystem": "<1.1.4|>=2,<2.1.1",
"league/oauth2-server": ">=8.3.2,<8.4.2|>=8.5,<8.5.3",
"leantime/leantime": "<3.3",
@@ -4066,9 +4067,9 @@
"nzo/url-encryptor-bundle": ">=4,<4.3.2|>=5,<5.0.1",
"october/backend": "<1.1.2",
"october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1",
"october/october": "<=3.6.4",
"october/october": "<3.7.5",
"october/rain": "<1.0.472|>=1.1,<1.1.2",
"october/system": "<1.0.476|>=1.1,<1.1.12|>=2,<2.2.34|>=3,<3.5.15",
"october/system": "<3.7.5",
"oliverklee/phpunit": "<3.5.15",
"omeka/omeka-s": "<4.0.3",
"onelogin/php-saml": "<2.10.4",
@@ -4196,8 +4197,8 @@
"serluck/phpwhois": "<=4.2.6",
"sfroemken/url_redirect": "<=1.2.1",
"sheng/yiicms": "<1.2.1",
"shopware/core": "<6.5.8.17-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev",
"shopware/platform": "<6.5.8.17-dev|>=6.6,<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev",
"shopware/core": "<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev",
"shopware/platform": "<6.6.10.3-dev|>=6.7.0.0-RC1-dev,<6.7.0.0-RC2-dev",
"shopware/production": "<=6.3.5.2",
"shopware/shopware": "<=5.7.17",
"shopware/storefront": "<=6.4.8.1|>=6.5.8,<6.5.8.7-dev",
@@ -4240,7 +4241,7 @@
"slim/slim": "<2.6",
"slub/slub-events": "<3.0.3",
"smarty/smarty": "<4.5.3|>=5,<5.1.1",
"snipe/snipe-it": "<=7.0.13",
"snipe/snipe-it": "<8.1",
"socalnick/scn-social-auth": "<1.15.2",
"socialiteproviders/steam": "<1.1",
"spatie/browsershot": "<5.0.5",
@@ -4510,7 +4511,7 @@
"type": "tidelift"
}
],
"time": "2025-05-01T20:05:59+00:00"
"time": "2025-05-08T20:06:04+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@@ -320,9 +320,25 @@
<span class="icon">
<i class="fas" :class="{ 'fa-arrow-up': item?._toggle, 'fa-arrow-down': !item?._toggle }"/>
</span>
&nbsp;
<i class="fas" :class="{
'fa-spinner fa-spin': undefined === item?.validated,
'fa-check has-text-success': true === item?.validated,
'fa-xmark has-text-danger': false === item?.validated,
}"/>&nbsp;
Metadata via
</div>
<div class="card-header-icon">
<div class="field is-grouped">
<div class="control" v-if="false === item?.validated">
<NuxtLink @click="deleteMetadata(key)">
<span class="icon-text has-text-danger">
<span class="icon"><i class="fas fa-trash"/></span>
<span>Delete</span>
</span>
</NuxtLink>
</div>
<div class="control">
<span class="icon-text">
<span class="icon"><i class="fas fa-server"/></span>
<span>
@@ -330,10 +346,14 @@
</span>
</span>
</div>
</div>
</div>
</header>
<div class="card-content" v-if="item?._toggle">
<div class="columns is-multiline is-mobile">
<div class="column is-12" v-if="false === item?.validated && item.validated_message">
<span class="has-text-danger">({{ item.validated_message }})</span>
</div>
<div class="column is-6">
<span class="icon-text">
<span class="icon"><i class="fas fa-passport"/></span>
@@ -629,6 +649,8 @@ const loadContent = async (id) => {
useHead({title: `History : ${makeName(json) ?? id}`})
await loadImage()
await nextTick();
await validateItem()
}
watch(breakpoints.active(), async () => await loadImage())
@@ -720,6 +742,48 @@ const toggleWatched = async () => {
}
}
const validateItem = async () => {
try {
const response = await request(`/history/${id}/validate`)
if (!response.ok) {
return
}
const json = await response.json()
for (const [backend, item] of Object.entries(json)) {
if (data.value.metadata[backend] === undefined) {
continue
}
data.value.metadata[backend]['validated'] = item.status
data.value.metadata[backend]['validated_message'] = item.message
}
} catch (e) {
}
}
const deleteMetadata = async backend => {
if (!confirm(`Remove metadata from '${backend}'?`)) {
return
}
try {
const response = await request(`/history/${id}/metadata/${backend}`, {method: 'DELETE'})
if (200 !== response.status) {
const json = await parse_api_response(response)
notification('error', 'Error', `${json.error.code}: ${json.error.message}`)
return
}
notification('success', 'Success!', `Deleted '${backend}' metadata.`)
await loadContent(id);
} catch (e) {
notification('error', 'Error', `Request error. ${e}`)
}
}
const getMoment = (time) => time.toString().length < 13 ? moment.unix(time) : moment(time)
const headerTitle = computed(() => isLoading.value ? id : makeName(data.value))

View File

@@ -12,6 +12,7 @@ use App\Libs\Attributes\Route\Get;
use App\Libs\Attributes\Route\Route;
use App\Libs\Container;
use App\Libs\DataUtil;
use App\Libs\Entity\StateEntity;
use App\Libs\Entity\StateInterface as iState;
use App\Libs\Enums\Http\Method;
use App\Libs\Enums\Http\Status;
@@ -19,12 +20,15 @@ use App\Libs\Exceptions\RuntimeException;
use App\Libs\Guid;
use App\Libs\Mappers\Import\DirectMapper;
use App\Libs\Mappers\ImportInterface as iImport;
use App\Libs\Options;
use App\Libs\Traits\APITraits;
use DateInterval;
use JsonException;
use PDO;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
use Psr\Log\LoggerInterface as iLogger;
use Psr\SimpleCache\CacheInterface as iCache;
use SplFileInfo;
use Throwable;
@@ -689,6 +693,105 @@ final class Index
return $this->read($request, $id);
}
#[Get(self::URL . '/{id:\d+}/validate[/]', name: 'history.validate')]
public function validate_item(iCache $cache, iRequest $request, string $id): iResponse
{
try {
$userContext = $this->getUserContext(request: $request, mapper: $this->mapper, logger: $this->logger);
} catch (RuntimeException $e) {
return api_error($e->getMessage(), Status::NOT_FOUND);
}
$entity = Container::get(iState::class)::fromArray([iState::COLUMN_ID => $id]);
if (null === ($item = $userContext->db->get($entity))) {
return api_error('Item Not found', Status::NOT_FOUND);
}
$cacheKey = r('validate_item_{id}_{user}_{backends}', [
'id' => $item->id,
'user' => $userContext->name,
'backends' => md5(implode(',', array_keys($item->getMetadata()))),
]);
try {
if (null !== ($cached = $cache->get($cacheKey))) {
return api_response(Status::OK, $cached, headers: ['X-Cache' => 'HIT']);
}
} catch (Throwable) {
// Ignore cache errors.
}
$validation = [];
foreach ($item->getMetadata() as $name => $metadata) {
$id = ag($metadata, StateEntity::COLUMN_ID, null);
$validation[$name] = [
'id' => $id,
'status' => false,
'message' => 'Item not found.',
];
if (null === $userContext->config->get($name)) {
$validation[$name]['message'] = 'Backend not found.';
continue;
}
if (null === $id) {
$validation[$name]['message'] = 'Item ID is missing.';
continue;
}
try {
$client = $this->getClient(name: $name, userContext: $userContext);
$item = $client->getMetadata($id, [Options::NO_LOGGING => true]);
if (count($item) > 0) {
$validation[$name]['status'] = true;
$validation[$name]['message'] = 'Item found.';
} else {
$validation[$name]['status'] = false;
$validation[$name]['message'] = 'Item not found.';
}
} catch (Throwable $e) {
$validation[$name]['message'] = $e->getMessage();
}
}
try {
$cache->set($cacheKey, $validation, new DateInterval('PT10M'));
} catch (Throwable) {
// Ignore cache errors.
}
return api_response(Status::OK, $validation, headers: ['X-Cache' => 'MISS']);
}
#[Delete(self::URL . '/{id:\d+}/metadata/{backend}[/]', name: 'history.metadata.delete')]
public function delete_item_metadata(iCache $cache, iRequest $request, string $id, string $backend): iResponse
{
try {
$userContext = $this->getUserContext(request: $request, mapper: $this->mapper, logger: $this->logger);
} catch (RuntimeException $e) {
return api_error($e->getMessage(), Status::NOT_FOUND);
}
$entity = Container::get(iState::class)::fromArray([iState::COLUMN_ID => $id]);
if (null === ($item = $userContext->db->get($entity))) {
return api_error('Item Not found', Status::NOT_FOUND);
}
if (null === ($item->getMetadata($backend) ?? null)) {
return api_error('Item metadata not found.', Status::NOT_FOUND);
}
$item->metadata = ag_delete($item->getMetadata(), $backend);
$userContext->db->update($item);
return $this->read($request, $id);
}
#[Get(self::URL . '/{id:\d+}/images/{type:poster|background}[/]', name: 'history.item.images')]
public function images(iRequest $request, string $id, string $type): iResponse
{