diff --git a/lib/Db/Listing.php b/lib/Db/Listing.php index de0825ad..a26a5a48 100644 --- a/lib/Db/Listing.php +++ b/lib/Db/Listing.php @@ -21,6 +21,7 @@ class Listing extends Entity implements JsonSerializable protected ?DateTime $lastSync = null; protected ?bool $default = false; protected ?bool $available = false; + protected ?string $organisation = null; public function __construct() { $this->addType(fieldName: 'title', type: 'string'); @@ -34,6 +35,7 @@ public function __construct() { $this->addType(fieldName: 'lastSync', type: 'datetime'); $this->addType(fieldName: 'default', type: 'boolean'); $this->addType(fieldName: 'available', type: 'boolean'); + $this->addType(fieldName: 'organisation', type: 'string'); } public function getJsonFields(): array @@ -81,6 +83,7 @@ public function jsonSerialize(): array 'lastSync' => $this->lastSync->format('c'), 'default' => $this->default, 'available' => $this->available, + 'organisation'=> $this->organisation, ]; $jsonFields = $this->getJsonFields(); diff --git a/lib/Db/ListingMapper.php b/lib/Db/ListingMapper.php index bbe56e0e..fb04d79d 100644 --- a/lib/Db/ListingMapper.php +++ b/lib/Db/ListingMapper.php @@ -3,6 +3,7 @@ namespace OCA\OpenCatalogi\Db; use OCA\OpenCatalogi\Db\Listing; +use OCA\OpenCatalogi\Db\Organisation; use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -19,43 +20,144 @@ public function find(int $id): Listing { $qb = $this->db->getQueryBuilder(); - $qb->select('*') - ->from('listings') - ->where( - $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) - ); + $qb->select( + 'l.*', + 'o.id AS organisation_id', + 'o.title AS organisation_title', + 'o.summary AS organisation_summary', + 'o.description AS organisation_description', + 'o.image AS organisation_image', + 'o.oin AS organisation_oin', + 'o.tooi AS organisation_tooi', + 'o.rsin AS organisation_rsin', + 'o.pki AS organisation_pki' + ) + ->from('listings', 'l') + ->leftJoin('l', 'organizations', 'o', 'l.organisation = o.id') + ->where( + $qb->expr()->eq('l.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) + ); + + return $this->findEntityCustom(query: $qb); + } - return $this->findEntity(query: $qb); + /** + * Returns an db result and throws exceptions when there are more or less + * results CUSTOM FOR JOINS + * + * @param IQueryBuilder $query + * @return Entity the entity + * @psalm-return T the entity + * @throws Exception + * @throws MultipleObjectsReturnedException if more than one item exist + * @throws DoesNotExistException if the item does not exist + * @since 14.0.0 + */ + protected function findEntityCustom(IQueryBuilder $query): Entity { + return $this->mapRowToEntityCustom($this->findOneQuery($query)); } - public function findAll(?int $limit = null, ?int $offset = null, ?array $filters = [], ?array $searchConditions = [], ?array $searchParams = []): array - { - $qb = $this->db->getQueryBuilder(); + /** + * CUSTOM FOR JOINS + */ + protected function mapRowToEntityCustom(array $row): Entity { + unset($row['DOCTRINE_ROWNUM']); // remove doctrine/dbal helper column + + // Map the Organisation fields to a sub-array + $organisationData = [ + 'id' => $row['organisation_id'] ?? null, + 'title' => $row['organisation_title'] ?? null, + 'summary' => $row['organisation_summary'] ?? null, + 'description' => $row['organisation_description'] ?? null, + 'image' => $row['organisation_image'] ?? null, + 'oin' => $row['organisation_oin'] ?? null, + 'tooi' => $row['organisation_tooi'] ?? null, + 'rsin' => $row['organisation_rsin'] ?? null, + 'pki' => $row['organisation_pki'] ?? null, + ]; + + $organisationIsEmpty = true; + foreach ($organisationData as $key => $value) { + if ($value !== null) { + $organisationIsEmpty = false; + } - $qb->select('*') - ->from('listings') - ->setMaxResults($limit) - ->setFirstResult($offset); - - foreach($filters as $filter => $value) { - if ($value === 'IS NOT NULL') { - $qb->andWhere($qb->expr()->isNotNull($filter)); - } elseif ($value === 'IS NULL') { - $qb->andWhere($qb->expr()->isNull($filter)); - } else { - $qb->andWhere($qb->expr()->eq($filter, $qb->createNamedParameter($value))); - } + if (array_key_exists("organisation_$key", $row) === true) { + unset($row["organisation_$key"]); + } } + $row['organisation'] = $organisationIsEmpty === true ? null : json_encode(Organisation::fromRow($organisationData)->jsonSerialize()); + + return \call_user_func($this->entityClass .'::fromRow', $row); + } + + /** + * Runs a sql query and returns an array of entities CUSTOM FOR JOINS + * + * @param IQueryBuilder $query + * @return Entity[] all fetched entities + * @psalm-return T[] all fetched entities + * @throws Exception + * @since 14.0.0 + */ + protected function findEntitiesCustom(IQueryBuilder $query): array { + $result = $query->executeQuery(); + try { + $entities = []; + while ($row = $result->fetch()) { + $entities[] = $this->mapRowToEntityCustom($row); + } + return $entities; + } finally { + $result->closeCursor(); + } + } + + public function findAll(?int $limit = null, ?int $offset = null, ?array $filters = [], ?array $searchConditions = [], ?array $searchParams = []): array + { + $qb = $this->db->getQueryBuilder(); + + $qb->select( + 'l.*', + 'o.id AS organisation_id', + 'o.title AS organisation_title', + 'o.summary AS organisation_summary', + 'o.description AS organisation_description', + 'o.image AS organisation_image', + 'o.oin AS organisation_oin', + 'o.tooi AS organisation_tooi', + 'o.rsin AS organisation_rsin', + 'o.pki AS organisation_pki' + ) + ->from('listings', 'l') + ->leftJoin('l', 'organizations', 'o', 'l.organisation = o.id') + ->setMaxResults($limit) + ->setFirstResult($offset); + + + // Apply filters + foreach ($filters as $filter => $value) { + if ($value === 'IS NOT NULL') { + $qb->andWhere($qb->expr()->isNotNull($filter)); + } elseif ($value === 'IS NULL') { + $qb->andWhere($qb->expr()->isNull($filter)); + } else { + $qb->andWhere($qb->expr()->eq($filter, $qb->createNamedParameter($value))); + } + } + + // Apply search conditions if (!empty($searchConditions)) { $qb->andWhere('(' . implode(' OR ', $searchConditions) . ')'); foreach ($searchParams as $param => $value) { $qb->setParameter($param, $value); } } - - return $this->findEntities(query: $qb); - } + + // Use the existing findEntities method to fetch and map the results + return $this->findEntitiesCustom($qb); + } public function createFromArray(array $object): Listing { diff --git a/lib/Migration/Version6Date20240809120147.php b/lib/Migration/Version6Date20240809120147.php new file mode 100644 index 00000000..eb923ff0 --- /dev/null +++ b/lib/Migration/Version6Date20240809120147.php @@ -0,0 +1,72 @@ +hasTable(tableName: 'listings') === true) { + $table = $schema->getTable(tableName: 'listings'); + + if($table->hasColumn(name: 'organization') === true) { + $column = $table->dropColumn('organization'); + } + if($table->hasColumn(name: 'organisation') === false) { + $table->addColumn( + name: 'organisation', + typeName: Types::STRING, + options: [ + 'notNull' => false, + 'default' => null + ]); + $output->info('organisation should be added to listing'); + } + + } + + return $schema; + } + + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } +} diff --git a/lib/Service/DirectoryService.php b/lib/Service/DirectoryService.php index dc47b754..4de3e99b 100644 --- a/lib/Service/DirectoryService.php +++ b/lib/Service/DirectoryService.php @@ -222,7 +222,9 @@ public function listCatalog (array $catalog): array $listing = $this->getDirectoryEntry(catalogId: $catalogId); - $listing['title'] = $catalog['title']; + $listing['title'] = $catalog['title']; + $listing['organisation'] = $catalog['organisation']; + $listing['metaData'] = $catalog['metaData']; if($this->config->hasKey($this->appName, 'mongoStorage') === false || $this->config->getValueString($this->appName, 'mongoStorage') !== '1' diff --git a/src/entities/listing/listing.mock.ts b/src/entities/listing/listing.mock.ts index b2132ba5..ec6ec053 100644 --- a/src/entities/listing/listing.mock.ts +++ b/src/entities/listing/listing.mock.ts @@ -16,6 +16,16 @@ export const mockListingsData = (): TListing[] => [ lastSync: '2024-07-25T00:00:00Z', default: true, available: false, + organisation: { // full data + id: '1', + title: 'Decat', + summary: 'a short form summary', + description: 'a really really long description about this organisation', + oin: 'string', + tooi: 'string', + rsin: 'string', + pki: 'string', + }, }, { id: '2', @@ -31,6 +41,16 @@ export const mockListingsData = (): TListing[] => [ lastSync: '', default: true, available: false, + organisation: { // full data + id: '1', + title: 'Decat', + summary: 'a short form summary', + description: 'a really really long description about this organisation', + oin: 'string', + tooi: 'string', + rsin: 'string', + pki: 'string', + }, }, { id: '1', diff --git a/src/entities/listing/listing.ts b/src/entities/listing/listing.ts index 66d5a42e..1fe81346 100644 --- a/src/entities/listing/listing.ts +++ b/src/entities/listing/listing.ts @@ -1,3 +1,4 @@ +import { Organisation, TOrganisation } from '../organisation' import { TListing } from './listing.types' import { SafeParseReturnType, z } from 'zod' @@ -16,6 +17,7 @@ export class Listing implements TListing { public lastSync: string | Date public available: boolean public default: boolean + public organisation: string|TOrganisation constructor(data: TListing) { this.hydrate(data) @@ -36,6 +38,7 @@ export class Listing implements TListing { this.lastSync = data.lastSync || '' this.available = data.available || true this.default = data.default || false + this.organisation = data.organisation || '' } diff --git a/src/entities/listing/listing.types.ts b/src/entities/listing/listing.types.ts index 140f7b60..870648d0 100644 --- a/src/entities/listing/listing.types.ts +++ b/src/entities/listing/listing.types.ts @@ -1,3 +1,5 @@ +import { TOrganisation } from "../organisation" + export type TListing = { id: string catalogusId: string @@ -12,4 +14,5 @@ export type TListing = { lastSync: string | Date available: boolean default: boolean + organisation: string|TOrganisation } diff --git a/src/store/modules/directory.js b/src/store/modules/directory.js index bad527f5..b9a85c42 100644 --- a/src/store/modules/directory.js +++ b/src/store/modules/directory.js @@ -35,7 +35,10 @@ export const useDirectoryStore = defineStore( response.json().then( (data) => { this.listingList = data.results.map( - (listingItem) => new Listing(listingItem), + (listingItem) => { + listingItem.organisation = listingItem?.organisation ? JSON.parse(listingItem.organisation): null; + return new Listing(listingItem) + }, ) }, ) diff --git a/src/views/directory/DirectoryList.vue b/src/views/directory/DirectoryList.vue index 017aad2b..65c1a737 100644 --- a/src/views/directory/DirectoryList.vue +++ b/src/views/directory/DirectoryList.vue @@ -42,6 +42,7 @@ import { navigationStore, directoryStore } from '../../store/store.js' :key="`${listing}${i}`" :name="listing.name ?? listing.title" :active="directoryStore.listingItem?.id === listing?.id" + :details="listing?.organisation?.title || 'Geen organisatie'" @click="directoryStore.setListingItem(listing)">