diff --git a/lib/Store/Personal/Store.php b/lib/Store/Personal/Store.php index c8bd872..5b98aa9 100644 --- a/lib/Store/Personal/Store.php +++ b/lib/Store/Personal/Store.php @@ -9,13 +9,16 @@ declare(strict_types=1); namespace KTXM\ProviderLocalChrono\Store\Personal; +use DateTimeImmutable; use KTXC\Db\DataStore; +use KTXF\Chrono\Entity\EventRangeMetadata; use KTXF\Resource\Filter\Filter; use KTXF\Resource\Filter\FilterComparisonOperator; use KTXF\Resource\Filter\FilterConjunctionOperator; use KTXF\Resource\Range\Range; use KTXF\Resource\Range\RangeType; use KTXF\Resource\Sort\Sort; +use KTXF\Utile\Timestamp; use KTXF\Utile\UUID; use KTXM\ProviderLocalChrono\Providers\Personal\CollectionResource; use KTXM\ProviderLocalChrono\Providers\Personal\EntityResource; @@ -97,6 +100,37 @@ class Store { return $mongoFilter; } + /** + * construct query conditions and find options from a range + * + * @param Range $range range options + * + * @return array{0: array, 1: array} query conditions and find options + */ + protected function constructRange(Range $range): array { + $conditions = []; + $options = []; + + if ($range->type() === RangeType::TALLY) { + // For TALLY ranges, use position (skip) and tally (limit) + /** @var \KTXF\Resource\Range\IRangeTally $range */ + $options['skip'] = $range->getPosition(); + $options['limit'] = $range->getTally(); + } elseif ($range->type() === RangeType::DATE) { + // Select entities whose calculated bounds overlap the half-open interval [start, end). + /** @var \KTXF\Resource\Range\IRangeDate $range */ + $conditions['startsOn'] = [ + '$lt' => $range->getEnd(), + ]; + $conditions['$or'] = [ + ['endsOn' => ['$gt' => $range->getStart()]], + ['endsOn' => null], + ]; + } + + return [$conditions, $options]; + } + protected function constructSort(array $map, Sort $sort): array { $mongoSort = []; @@ -222,8 +256,8 @@ class Store { $data['uid'] = $userId; $data['cid'] = UUID::v4(); $data['signature'] = isset($data['signature']) && is_numeric($data['signature']) ? (int)$data['signature'] : 0; - $data['createdOn'] = date('c'); - $data['modifiedOn'] = $data['createdOn']; + $data['createdOn'] = Timestamp::now(); + $data['modifiedOn'] = Timestamp::now(); // create entry $result = $this->_store->selectCollection($this->_CollectionTable)->insertOne($data); if ($result->getInsertedCount() === 1) { @@ -250,7 +284,7 @@ class Store { if ($cid === null) { throw new \InvalidArgumentException('Collection identifier is required for modification'); } - $data['modifiedOn'] = date('c'); + $data['modifiedOn'] = Timestamp::now(); unset($data['_id'], $data['tid'], $data['uid'], $data['cid']); // modify entry $this->_store->selectCollection($this->_CollectionTable)->updateOne( @@ -328,21 +362,11 @@ class Store { // Apply range/pagination if provided if ($range !== null) { - if ($range->type() === RangeType::TALLY) { - // For TALLY ranges, use position (skip) and tally (limit) - /** @var \KTXF\Resource\Range\IRangeTally $rangeTally */ - $rangeTally = $range; - $findOptions['skip'] = $rangeTally->getPosition(); - $findOptions['limit'] = $rangeTally->getTally(); - } elseif ($range->type() === RangeType::DATE) { - // For DATE ranges, filter by date fields - /** @var \KTXF\Resource\Range\IRangeDate $rangeDate */ - $rangeDate = $range; - $query['data.startsOn'] = [ - '\$gte' => $rangeDate->getStart()->format('c'), - '\$lte' => $rangeDate->getEnd()->format('c') - ]; + [$rangeConditions, $rangeOptions] = $this->constructRange($range); + if ($rangeConditions !== []) { + $query = ['$and' => [$query, $rangeConditions]]; } + $findOptions = array_merge($findOptions, $rangeOptions); } $cursor = $this->_store->selectCollection($this->_EntityTable)->find($query, $findOptions); @@ -450,15 +474,16 @@ class Store { public function entityCreate(string $tenantId, string $userId, string $collectionId, EntityResource $entity): EntityResource { // convert entity to store format $data = $entity->toStore(); + $data = array_merge($data, EventRangeMetadata::fromStoreDocument($data)); // assign identifiers and timestamps $data['tid'] = $tenantId; $data['uid'] = $userId; $data['cid'] = $collectionId; $data['eid'] = UUID::v4(); - $data['createdOn'] = date('c'); + $data['createdOn'] = Timestamp::now(); $data['createdBy'] = $userId; - $data['modifiedOn'] = $data['createdOn']; - $data['modifiedBy'] = $data['createdBy']; + $data['modifiedOn'] = Timestamp::now(); + $data['modifiedBy'] = $userId; $result = $this->_store->selectCollection($this->_EntityTable)->insertOne($data); @@ -487,7 +512,9 @@ class Store { public function entityModify(string $tenantId, string $userId, string $collectionId, string $identifier, EntityResource $entity): EntityResource { // convert entity to store format $data = $entity->toStore(); - $data['modifiedOn'] = date('c'); + $data = array_merge($data, EventRangeMetadata::fromStoreDocument($data)); + // assign identifiers and timestamps + $data['modifiedOn'] = Timestamp::now(); $data['modifiedBy'] = $userId; // Remove identifiers from update data (they shouldn't change) unset($data['_id'], $data['tid'], $data['uid'], $data['cid'], $data['eid']); @@ -599,7 +626,7 @@ class Store { 'eid' => $eid, 'operation' => $operation, 'signature' => $signature, - 'mutatedOn' => time(), + 'mutatedOn' => Timestamp::now(), ]); // update signature as normalized numeric value