- add : doc of stories/children

- fix : limiting the number of children with search stories (story_max_records=N in url) respects coll/mask rights
This commit is contained in:
jygaulier
2020-09-17 18:56:19 +02:00
parent 4d4b163def
commit f9fa3e0802
4 changed files with 151 additions and 78 deletions

View File

@@ -118,3 +118,46 @@ paths:
description: Record not found description: Record not found
default: default:
$ref: 'responses.yaml#/error_response' $ref: 'responses.yaml#/error_response'
'/stories/{sbas_id}/{record_id}/children':
get:
tags:
- story
summary: Returns uri of each record (child) contained in the story ; Optional pagination
description: Returns children of a story
operationId: getRecordById
parameters:
- name: sbas_id
in: path
description: ID of the databox
required: true
schema:
$ref: schemas.yaml#/ID
- name: record_id
in: path
description: ID of the story record
required: true
schema:
$ref: schemas.yaml#/ID
- name: page
in: query
description: page number (default 1)
required: false
schema:
type: integer
- name: per_page
in: query
description: number of children (records uri) per page (default 10)
required: false
schema:
type: integer
responses:
200:
description: ok
content:
application/json:
schema:
$ref: schemas.yaml#/ApiResponse_RecordsUriArray
404:
description: Story (record) not found
default:
$ref: 'responses.yaml#/error_response'

View File

@@ -1,2 +1,15 @@
record_response:
type: object
properties:
200:
description: successful operation
content:
application/json:
schema:
$ref: schemas.yaml#/ApiResponse_record
400:
description: Any other error
404:
description: Record not found
error_response: error_response:
description: Any (other) error description: Any (other) error

View File

@@ -169,4 +169,19 @@ Record:
- video - video
uuid: uuid:
type: string type: string
RecordUri:
type: string
example:
'/api/v3/records/1/48'
RecordsUriArray:
type: array
items:
$ref: '#/RecordUri'
ApiResponse_RecordsUriArray:
type: object
properties:
meta:
$ref: '#/ApiResponse_meta'
response:
$ref: '#/RecordsUriArray'

View File

@@ -12,8 +12,11 @@ namespace Alchemy\Phrasea\Databox\Record;
use Alchemy\Phrasea\Application; use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Cache\Exception; use Alchemy\Phrasea\Cache\Exception;
use Alchemy\Phrasea\Model\Entities\User; use Alchemy\Phrasea\Model\Entities\User;
use databox;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Query\QueryBuilder;
use record_adapter;
use set_selection;
class LegacyRecordRepository implements RecordRepository class LegacyRecordRepository implements RecordRepository
{ {
@@ -23,7 +26,7 @@ class LegacyRecordRepository implements RecordRepository
private $app; private $app;
/** /**
* @var \databox * @var databox
*/ */
private $databox; private $databox;
@@ -32,7 +35,7 @@ class LegacyRecordRepository implements RecordRepository
*/ */
private $site; private $site;
public function __construct(Application $app, \databox $databox, $site) public function __construct(Application $app, databox $databox, $site)
{ {
$this->app = $app; $this->app = $app;
$this->databox = $databox; $this->databox = $databox;
@@ -41,7 +44,7 @@ class LegacyRecordRepository implements RecordRepository
public function find($record_id, $number = null) public function find($record_id, $number = null)
{ {
$record = new \record_adapter($this->app, $this->databox->get_sbas_id(), $record_id, $number, false); $record = new record_adapter($this->app, $this->databox->get_sbas_id(), $record_id, $number, false);
try { try {
$data = $record->get_data_from_cache(); $data = $record->get_data_from_cache();
} catch (Exception $exception) { } catch (Exception $exception) {
@@ -67,7 +70,7 @@ class LegacyRecordRepository implements RecordRepository
/** /**
* @param string $sha256 * @param string $sha256
* @return \record_adapter[] * @return record_adapter[]
*/ */
public function findBySha256($sha256) public function findBySha256($sha256)
{ {
@@ -112,7 +115,7 @@ class LegacyRecordRepository implements RecordRepository
/** /**
* @param string $uuid * @param string $uuid
* @return \record_adapter[] * @return record_adapter[]
*/ */
public function findByUuid($uuid) public function findByUuid($uuid)
{ {
@@ -130,7 +133,7 @@ class LegacyRecordRepository implements RecordRepository
/** /**
* @param string $uuid * @param string $uuid
* @param array $excludedCollIds * @param array $excludedCollIds
* @return \record_adapter[] * @return record_adapter[]
*/ */
public function findByUuidWithExcludedCollIds($uuid, $excludedCollIds = []) public function findByUuidWithExcludedCollIds($uuid, $excludedCollIds = [])
{ {
@@ -190,85 +193,86 @@ class LegacyRecordRepository implements RecordRepository
$connection = $this->databox->get_connection(); $connection = $this->databox->get_connection();
// the columns we want from the record
//
$selects = $this->getRecordSelects(); $selects = $this->getRecordSelects();
array_unshift($selects, 'r.rid_parent AS story_id'); // add this to default
// sql parameters will be completed depending of (paginated / not paginated) and/or (user / no user)
//
$parmValues = [
':storyIds' => $storyIds,
];
$parmTypes = [
':storyIds' => Connection::PARAM_INT_ARRAY,
];
// if there is a user, we must join collusr to filter results depending on coll/masks
//
$userFilter = "";
if(!is_null($user)) {
$userFilter = " INNER JOIN collusr c ON c.site = :site AND c.usr_id = :userId AND c.coll_id=r.coll_id AND ((r.status ^ c.mask_xor) & c.mask_and) = 0\n";
$parmValues[':site'] = $this->site;
$parmValues[':userId'] = $user instanceof User ? $user->getId() : (int)$user;
}
if ($max_items) { if ($max_items) {
array_unshift($selects, 'sr.rid_parent as story_id'); //
// we want paginated results AFTER applying all filters, we build a dynamic cptr
$subBuilder = $connection->createQueryBuilder(); // WARNING : due to bugs (?) in mysql optimizer, do NOT try to optimize this sql (e.g. removing a sub-q, or moving cpt to anothe sub-q)
//
$subBuilder $sql = "SELECT " . join(', ', $selects) . "\n"
->select('s.*, . "FROM (\n"
IF(@old_rid_parent != s.rid_parent, @cpt := 1, @cpt := @cpt+1) AS CPT') . " SELECT t.*,\n"
->addSelect("IF(@old_rid_parent != s.rid_parent, IF(@old_rid_parent:=s.rid_parent,'NEW PARENT',0), '----------') AS Y") . " IF(@old_rid_parent != t.rid_parent, @cpt := 1, @cpt := @cpt+1) AS CPT,\n"
->from('regroup', 's') . " IF(@old_rid_parent != t.rid_parent, IF(@old_rid_parent:=t.rid_parent,'NEW PARENT',0), '----------') AS Y\n"
->where('s.rid_parent IN (:storyIds)') . " FROM (\n"
->setParameter('storyIds', $storyIds, Connection::PARAM_INT_ARRAY) . " SELECT g.ord, g.rid_parent, r.coll_id, r.record_id, r.credate, r.status, r.uuid, r.moddate, r.parent_record_id,r.type, r.originalname, r.sha256, r.mime\n"
->orderBy('s.rid_parent, s.ord') . " FROM regroup g\n"
. " INNER JOIN record r ON r.record_id=g.rid_child\n"
. $userFilter
. " WHERE g.rid_parent IN ( :storyIds )\n"
. " ORDER BY g.rid_parent, g.ord ASC\n"
. " ) t\n"
. ") r\n"
. "WHERE CPT BETWEEN :offset AND :maxresult"
; ;
$builder = $subBuilder->getConnection()->createQueryBuilder(); $parmValues[':offset'] = $offset;
$parmValues[':maxresult'] = ($offset + $max_items -1);
$builder->select($selects)
->from(sprintf('( %s )', $subBuilder->getSQL()), 'sr')
->innerJoin('sr', 'record', 'r', 'r.record_id = sr.rid_child')
->where('sr.CPT BETWEEN :offset AND :maxresult')
->andWhere('r.parent_record_id = 0')
->setParameter('offset', $offset)
->setParameter('maxresult', ($offset + $max_items -1))
->orderBy('story_id, sr.CPT')
;
if (null !== $user) {
$this->addUserFilter($builder, $user);
}
$connection->executeQuery('SET @cpt = 1'); $connection->executeQuery('SET @cpt = 1');
$connection->executeQuery('SET @old_rid_parent = -1'); $connection->executeQuery('SET @old_rid_parent = -1');
}
else {
$data = $connection->fetchAll( //
$builder->getSQL(), // se want all children, easy
array_merge($subBuilder->getParameters(), $builder->getParameters()), //
array_merge($subBuilder->getParameterTypes(), $builder->getParameterTypes()) $sql = "SELECT " . join(', ', $selects) . "\n"
); . "FROM (\n"
. " SELECT g.ord, g.rid_parent, r.coll_id, r.record_id, r.credate, r.status, r.uuid, r.moddate, r.parent_record_id,r.type, r.originalname, r.sha256, r.mime\n"
} else { . " FROM regroup g\n"
array_unshift($selects, 's.rid_parent as story_id'); . " INNER JOIN record r ON r.record_id=g.rid_child\n"
. $userFilter
$builder = $connection->createQueryBuilder(); . " WHERE g.rid_parent IN ( :storyIds )\n"
. " ORDER BY g.rid_parent, g.ord ASC\n"
$builder . ") r\n"
->select($selects)
->from('regroup', 's')
->innerJoin('s', 'record', 'r', 'r.record_id = s.rid_child')
->where(
's.rid_parent IN (:storyIds)',
'r.parent_record_id = 0'
)
->orderBy('s.ord', 'ASC')
->setParameter('storyIds', $storyIds, Connection::PARAM_INT_ARRAY)
; ;
if (null !== $user) {
$this->addUserFilter($builder, $user);
} }
$data = $connection->fetchAll($builder->getSQL(), $builder->getParameters(), $builder->getParameterTypes()); $data = $connection->fetchAll($sql, $parmValues, $parmTypes);
}
$records = $this->mapRecordsFromResultSet($data); $records = $this->mapRecordsFromResultSet($data);
$selections = array_map( $selections = array_map(
function () { function () {
return new \set_selection($this->app); return new set_selection($this->app);
}, },
array_flip($storyIds) array_flip($storyIds)
); );
foreach ($records as $index => $child) { foreach ($records as $index => $child) {
/** @var \set_selection $selection */ /** @var set_selection $selection */
$selection = $selections[$data[$index]['story_id']]; $selection = $selections[$data[$index]['story_id']];
$child->setNumber($selection->get_count() + 1); $child->setNumber($selection->get_count() + 1);
@@ -312,12 +316,12 @@ class LegacyRecordRepository implements RecordRepository
$stories = $this->mapRecordsFromResultSet($data); $stories = $this->mapRecordsFromResultSet($data);
$selections = array_map(function () { $selections = array_map(function () {
return new \set_selection($this->app); return new set_selection($this->app);
}, array_flip($recordIds)); }, array_flip($recordIds));
foreach ($stories as $index => $child) { foreach ($stories as $index => $child) {
/** @var \set_selection $selection */ /** @var set_selection $selection */
$selection = $selections[$data[$index]['child_id']]; $selection = $selections[$data[$index]['child_id']];
$selection->add_element($child); $selection->add_element($child);
@@ -357,7 +361,7 @@ class LegacyRecordRepository implements RecordRepository
/** /**
* @param array $result * @param array $result
* @return \record_adapter[] * @return record_adapter[]
*/ */
private function mapRecordsFromResultSet(array $result) private function mapRecordsFromResultSet(array $result)
{ {
@@ -372,13 +376,13 @@ class LegacyRecordRepository implements RecordRepository
/** /**
* @param array $row * @param array $row
* @param \record_adapter|null $record * @param record_adapter|null $record
* @return \record_adapter * @return record_adapter
*/ */
private function mapRecordFromResultRow(array $row, \record_adapter $record = null) private function mapRecordFromResultRow(array $row, record_adapter $record = null)
{ {
if (null === $record) { if (null === $record) {
$record = new \record_adapter($this->app, $this->databox->get_sbas_id(), $row['record_id'], null, false); $record = new record_adapter($this->app, $this->databox->get_sbas_id(), $row['record_id'], null, false);
} }
$record->mapFromData($row); $record->mapFromData($row);
@@ -400,15 +404,13 @@ class LegacyRecordRepository implements RecordRepository
->select('1') ->select('1')
->from('collusr', 'c') ->from('collusr', 'c')
->where( ->where(
'c.usr_id = :userId', 'c.usr_id = ' . ($user instanceof User ? $user->getId() : (int)$user),
'c.site = :site', 'c.site = \'' . $this->site . '\'',
'((r.status ^ c.mask_xor) & c.mask_and) = 0', '((r.status ^ c.mask_xor) & c.mask_and) = 0',
'c.coll_id = r.coll_id' 'c.coll_id = r.coll_id'
); );
$builder $builder
->andWhere(sprintf('EXISTS(%s)', $subBuilder->getSQL())) ->andWhere(sprintf('EXISTS(%s)', $subBuilder->getSQL()));
->setParameter('userId', $user instanceof User ? $user->getId() : (int)$user)
->setParameter('site', $this->site);
} }
} }