diff --git a/lib/Controller/ObjectsController.php b/lib/Controller/ObjectsController.php
index 9cc6802..e1aa1f2 100644
--- a/lib/Controller/ObjectsController.php
+++ b/lib/Controller/ObjectsController.php
@@ -33,7 +33,7 @@ public function __construct(
/**
* Returns the template of the main app's page
- *
+ *
* This method renders the main page of the application, adding any necessary data to the template.
*
* @NoAdminRequired
@@ -42,17 +42,17 @@ public function __construct(
* @return TemplateResponse The rendered template response
*/
public function page(): TemplateResponse
- {
+ {
return new TemplateResponse(
'openconnector',
'index',
[]
);
}
-
+
/**
* Retrieves a list of all objects
- *
+ *
* This method returns a JSON response containing an array of all objects in the system.
*
* @NoAdminRequired
@@ -69,12 +69,12 @@ public function index(ObjectService $objectService, SearchService $searchService
$searchConditions = $searchService->createMySQLSearchConditions(filters: $filters, fieldsToSearch: $fieldsToSearch);
$filters = $searchService->unsetSpecialQueryParams(filters: $filters);
- return new JSONResponse(['results' => $this->objectEntityMapper->findAll(limit: null, offset: null, filters: $filters, searchConditions: $searchConditions, searchParams: $searchParams)]);
+ return new JSONResponse(['results' => $this->objectEntityMapper->findAll(filters: $filters, searchConditions: $searchConditions, searchParams: $searchParams)]);
}
/**
* Retrieves a single object by its ID
- *
+ *
* This method returns a JSON response containing the details of a specific object.
*
* @NoAdminRequired
@@ -94,7 +94,7 @@ public function show(string $id): JSONResponse
/**
* Creates a new object
- *
+ *
* This method creates a new object based on POST data.
*
* @NoAdminRequired
@@ -111,17 +111,17 @@ public function create(): JSONResponse
unset($data[$key]);
}
}
-
+
if (isset($data['id'])) {
unset($data['id']);
}
-
+
return new JSONResponse($this->objectEntityMapper->createFromArray(object: $data));
}
/**
* Updates an existing object
- *
+ *
* This method updates an existing object based on its ID.
*
* @NoAdminRequired
@@ -147,7 +147,7 @@ public function update(int $id): JSONResponse
/**
* Deletes an object
- *
+ *
* This method deletes an object based on its ID.
*
* @NoAdminRequired
@@ -162,4 +162,4 @@ public function destroy(int $id): JSONResponse
return new JSONResponse([]);
}
-}
\ No newline at end of file
+}
diff --git a/lib/Db/ObjectEntity.php b/lib/Db/ObjectEntity.php
index 44bc3ea..66099ae 100644
--- a/lib/Db/ObjectEntity.php
+++ b/lib/Db/ObjectEntity.php
@@ -60,14 +60,16 @@ public function hydrate(array $object): self
public function jsonSerialize(): array
{
- return [
- 'id' => $this->id,
- 'uuid' => $this->uuid,
- 'register' => $this->register,
- 'schema' => $this->schema,
- 'object' => $this->object,
- 'updated' => isset($this->updated) ? $this->updated->format('c') : null,
- 'created' => isset($this->created) ? $this->created->format('c') : null
- ];
+// return [
+// 'id' => $this->id,
+// 'uuid' => $this->uuid,
+// 'register' => $this->register,
+// 'schema' => $this->schema,
+// 'object' => $this->object,
+// 'updated' => isset($this->updated) ? $this->updated->format('c') : null,
+// 'created' => isset($this->created) ? $this->created->format('c') : null
+// ];
+
+ return $this->object;
}
}
diff --git a/lib/Db/ObjectEntityMapper.php b/lib/Db/ObjectEntityMapper.php
index 4641e00..88e830b 100644
--- a/lib/Db/ObjectEntityMapper.php
+++ b/lib/Db/ObjectEntityMapper.php
@@ -2,9 +2,12 @@
namespace OCA\OpenRegister\Db;
+use Doctrine\DBAL\Platforms\MySQLPlatform;
use OCA\OpenRegister\Db\ObjectEntity;
use OCA\OpenRegister\Db\Register;
use OCA\OpenRegister\Db\Schema;
+use OCA\OpenRegister\Service\IDatabaseJsonService;
+use OCA\OpenRegister\Service\MySQLJsonService;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -13,9 +16,17 @@
class ObjectEntityMapper extends QBMapper
{
- public function __construct(IDBConnection $db)
+ private IDatabaseJsonService $databaseJsonService;
+
+ public const MAIN_FILTERS = ['register', 'schema', 'uuid', 'created', 'updated'];
+
+ public function __construct(IDBConnection $db, MySQLJsonService $mySQLJsonService)
{
parent::__construct($db, 'openregister_objects');
+
+ if($db->getDatabasePlatform() instanceof MySQLPlatform === true) {
+ $this->databaseJsonService = $mySQLJsonService;
+ }
}
/**
@@ -89,6 +100,37 @@ public function findByRegisterAndSchema(string $register, string $schema): Objec
return $this->findEntities(query: $qb);
}
+ public function countAll(?array $filters = [], ?array $searchConditions = [], ?array $searchParams = []): int
+ {
+ $qb = $this->db->getQueryBuilder();
+
+ $qb->selectAlias(select: $qb->createFunction(call: 'count(id)'), alias: 'count')
+ ->from(from: 'openregister_objects');
+ foreach ($filters as $filter => $value) {
+ if ($value === 'IS NOT NULL' && in_array(needle: $filter, haystack: self::MAIN_FILTERS) === true) {
+ $qb->andWhere($qb->expr()->isNotNull($filter));
+ } elseif ($value === 'IS NULL' && in_array(needle: $filter, haystack: self::MAIN_FILTERS) === true) {
+ $qb->andWhere($qb->expr()->isNull($filter));
+ } else if (in_array(needle: $filter, haystack: self::MAIN_FILTERS) === true) {
+ $qb->andWhere($qb->expr()->eq($filter, $qb->createNamedParameter($value)));
+ }
+ }
+
+ if (!empty($searchConditions)) {
+ $qb->andWhere('(' . implode(' OR ', $searchConditions) . ')');
+ foreach ($searchParams as $param => $value) {
+ $qb->setParameter($param, $value);
+ }
+ }
+ $qb = $this->databaseJsonService->filterJson($qb, $filters);
+
+ $result = $qb->executeQuery();
+
+ $count = $result->fetchAll()[0]['count'];
+
+ return $count;
+ }
+
/**
* Find all ObjectEntitys
*
@@ -109,11 +151,11 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters
->setFirstResult($offset);
foreach ($filters as $filter => $value) {
- if ($value === 'IS NOT NULL') {
+ if ($value === 'IS NOT NULL' && in_array(needle: $filter, haystack: self::MAIN_FILTERS) === true) {
$qb->andWhere($qb->expr()->isNotNull($filter));
- } elseif ($value === 'IS NULL') {
+ } elseif ($value === 'IS NULL' && in_array(needle: $filter, haystack: self::MAIN_FILTERS) === true) {
$qb->andWhere($qb->expr()->isNull($filter));
- } else {
+ } else if (in_array(needle: $filter, haystack: self::MAIN_FILTERS) === true) {
$qb->andWhere($qb->expr()->eq($filter, $qb->createNamedParameter($value)));
}
}
@@ -124,6 +166,8 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters
$qb->setParameter($param, $value);
}
}
+ $qb = $this->databaseJsonService->filterJson($qb, $filters);
+
return $this->findEntities(query: $qb);
}
@@ -148,4 +192,36 @@ public function updateFromArray(int $id, array $object): ObjectEntity
return $this->update($obj);
}
+
+ public function getFacets(array $filters = [])
+ {
+ if(key_exists(key: 'register', array: $filters) === true) {
+ $register = $filters['register'];
+ }
+ if(key_exists(key: 'schema', array: $filters) === true) {
+ $schema = $filters['schema'];
+ }
+
+ $fields = [];
+ if(isset($filters['_queries'])) {
+ $fields = $filters['_queries'];
+ }
+
+ unset(
+ $filters['_fields'],
+ $filters['register'],
+ $filters['schema'],
+ $filters['created'],
+ $filters['updated'],
+ $filters['uuid']
+ );
+
+ return $this->databaseJsonService->getAggregations(
+ builder: $this->db->getQueryBuilder(),
+ fields: $fields,
+ register: $register,
+ schema: $schema,
+ filters: $filters
+ );
+ }
}
diff --git a/lib/Service/IDatabaseJsonService.php b/lib/Service/IDatabaseJsonService.php
new file mode 100644
index 0000000..7424d18
--- /dev/null
+++ b/lib/Service/IDatabaseJsonService.php
@@ -0,0 +1,11 @@
+$value) {
+ $builder->createNamedParameter(value: $value, placeHolder: ":value$filter");
+ $builder->createNamedParameter(value: "$.$filter", placeHolder: ":path$filter");
+
+ $builder
+ ->andWhere("json_extract(object, :path$filter) = :value$filter");
+ }
+
+ return $builder;
+ }
+
+ public function getAggregations(IQueryBuilder $builder, array $fields, int $register, int $schema, array $filters = []): array
+ {
+ $facets = [];
+
+ foreach($fields as $field) {
+ $builder->createNamedParameter(value: "$.$field", placeHolder: ":$field");
+
+
+ $builder
+ ->selectAlias($builder->createFunction("json_unquote(json_extract(object, :$field))"), '_id')
+ ->selectAlias($builder->createFunction("count(*)"), 'count')
+ ->from('openregister_objects')
+ ->where(
+ $builder->expr()->eq('register', $builder->createNamedParameter($register, IQueryBuilder::PARAM_INT)),
+ $builder->expr()->eq('schema', $builder->createNamedParameter($schema, IQueryBuilder::PARAM_INT)),
+ )
+ ->groupBy('_id');
+
+ $builder = $this->filterJson($builder, $filters);
+
+ $result = $builder->executeQuery();
+ $facets[$field] = $result->fetchAll();
+
+ $builder->resetQueryParts();
+ $builder->setParameters([]);
+
+ }
+ return $facets;
+ }
+}
diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php
index 2911d17..af6d179 100755
--- a/lib/Service/ObjectService.php
+++ b/lib/Service/ObjectService.php
@@ -14,6 +14,10 @@
class ObjectService
{
+
+ private int $register;
+ private int $schema;
+
private $callLogMapper;
/**
@@ -28,6 +32,127 @@ public function __construct(ObjectEntityMapper $objectEntityMapper, RegisterMapp
$this->schemaMapper = $schemaMapper;
}
+ public function find(int|string $id) {
+ return $this->getObject(
+ register: $this->registerMapper->find($this->getRegister()),
+ schema: $this->schemaMapper->find($this->getSchema()),
+ uuid: $id
+ );
+ }
+
+ public function createFromArray(array $object) {
+ return $this->saveObject(
+ register: $this->getRegister(),
+ schema: $this->getSchema(),
+ object: $object
+ );
+ }
+
+ public function updateFromArray(string $id, array $object, bool $updatedObject) {
+ $object['id'] = $id;
+ return $this->saveObject(
+ register: $this->getRegister(),
+ schema: $this->getSchema(),
+ object: $object
+ );
+ }
+
+ public function delete(array|\JsonSerializable $object): bool
+ {
+ if($object instanceof \JsonSerializable === true) {
+ $object = $object->jsonSerialize();
+ }
+
+ return $this->deleteObject(
+ register: $this->registerMapper->find($this->getRegister()),
+ schema: $this->schemaMapper->find($this->getSchema()),
+ uuid: $object['id']
+ );
+ }
+
+ public function findAll(?int $limit = null, ?int $offset = null, array $filters = []): array
+ {
+ $objects = $this->getObjects(
+ register: $this->getRegister(),
+ schema: $this->getSchema(),
+ limit: $limit,
+ offset: $offset,
+ filters: $filters
+ );
+// $data = array_map([$this, 'getDataFromObject'], $objects);
+
+ return $objects;
+ }
+
+ public function count(array $filters = []): int
+ {
+ if($this->getSchema() !== null && $this->getRegister() !== null) {
+ $filters['register'] = $this->getRegister();
+ $filters['schema'] = $this->getSchema();
+ }
+ $count = $this->objectEntityMapper
+ ->countAll(filters: $filters);
+
+ return $count;
+ }
+
+ public function findMultiple(array $ids): array
+ {
+ $result = [];
+ foreach($ids as $id) {
+ $result[] = $this->find($id);
+ }
+
+ return $result;
+ }
+
+ public function getAggregations(array $filters): array
+ {
+ $mapper = $this->getMapper(objectType: 'objectEntity');
+
+ $filters['register'] = $this->getRegister();
+ $filters['schema'] = $this->getSchema();
+
+ if ($mapper instanceof ObjectEntityMapper === true) {
+ $facets = $this->objectEntityMapper->getFacets($filters);
+ return $facets;
+ }
+
+ return [];
+ }
+
+ private function getDataFromObject(mixed $object) {
+
+ return $object->getObject();
+ }
+
+ /**
+ * Gets all objects of a specific type.
+ *
+ * @param string|null $objectType The type of objects to retrieve.
+ * @param int|null $register
+ * @param int|null $schema
+ * @param int|null $limit The maximum number of objects to retrieve.
+ * @param int|null $offset The offset from which to start retrieving objects.
+ * @param array $filters
+ * @return array The retrieved objects.
+ * @throws \Exception
+ */
+ public function getObjects(?string $objectType = null, ?int $register = null, ?int $schema = null, ?int $limit = null, ?int $offset = null, array $filters = []): array
+ {
+ if($objectType === null && $register !== null && $schema !== null) {
+ $objectType = 'objectEntity';
+ $filters['register'] = $register;
+ $filters['schema'] = $schema;
+ }
+
+ // Get the appropriate mapper for the object type
+ $mapper = $this->getMapper($objectType);
+
+ // Use the mapper to find and return all objects of the specified type
+ return $mapper->findAll(limit: $limit, offset: $offset, filters: $filters);
+ }
+
/**
* Save an object
*
@@ -37,7 +162,7 @@ public function __construct(ObjectEntityMapper $objectEntityMapper, RegisterMapp
*
* @return ObjectEntity The resulting object.
*/
- public function saveObject($register, $schema, array $object): ObjectEntity
+ public function saveObject(int $register, int $schema, array $object): ObjectEntity
{
// Convert register and schema to their respective objects if they are strings
@@ -48,13 +173,15 @@ public function saveObject($register, $schema, array $object): ObjectEntity
$schema = $this->schemaMapper->find($schema);
}
- // Does the object already exist?
- $objectEntity = $this->objectEntityMapper->findByUuid($register, $schema, $object['id']);
+ if(isset($object['id']) === true) {
+ // Does the object already exist?
+ $objectEntity = $this->objectEntityMapper->findByUuid($this->registerMapper->find($register), $this->schemaMapper->find($schema), $object['id']);
+ }
if($objectEntity === null){
$objectEntity = new ObjectEntity();
- $objectEntity->setRegister($register->getId());
- $objectEntity->setSchema($schema->getId());
+ $objectEntity->setRegister($register);
+ $objectEntity->setSchema($schema);
///return $this->objectEntityMapper->update($objectEntity);
}
@@ -91,11 +218,12 @@ public function saveObject($register, $schema, array $object): ObjectEntity
*
* @return ObjectEntity The resulting object.
*/
- public function getObject(Register $register, string $uuid): ObjectEntity
+ public function getObject(Register $register, Schema $schema, string $uuid): ObjectEntity
{
+
// Lets see if we need to save to an internal source
- if ($register->getSource() === 'internal') {
- return $this->objectEntityMapper->findByUuid($register,$uuid);
+ if ($register->getSource() === 'internal' || $register->getSource() === '') {
+ return $this->objectEntityMapper->findByUuid($register, $schema, $uuid);
}
//@todo mongodb support
@@ -112,12 +240,14 @@ public function getObject(Register $register, string $uuid): ObjectEntity
* @return ObjectEntity The resulting object.
*/
- public function deleteObject(Register $register, string $uuid)
+ public function deleteObject(Register $register, Schema $schema, string $uuid): bool
{
// Lets see if we need to save to an internal source
- if ($register->getSource() === 'internal') {
- $object = $this->objectEntityMapper->findByUuid($uuid);
+ if ($register->getSource() === 'internal' || $register->getSource() === '') {
+ $object = $this->objectEntityMapper->findByUuid(register: $register, schema: $schema, uuid: $uuid);
$this->objectEntityMapper->delete($object);
+
+ return true;
}
//@todo mongodb support
@@ -134,8 +264,15 @@ public function deleteObject(Register $register, string $uuid)
* @throws \InvalidArgumentException If an unknown object type is provided.
* @throws \Exception If OpenRegister service is not available or if register/schema is not configured.
*/
- public function getMapper(string $objectType)
+ public function getMapper(?string $objectType = null, ?int $register = null, ?int $schema = null)
{
+ if($register !== null && $schema !== null) {
+ $this->setSchema($schema);
+ $this->setRegister($register);
+
+ return $this;
+ }
+
// If the source is internal, return the appropriate mapper based on the object type
switch ($objectType) {
case 'register':
@@ -149,6 +286,8 @@ public function getMapper(string $objectType)
}
}
+
+
/**
* Gets multiple objects based on the object type and ids.
*
@@ -277,4 +416,24 @@ public function getRegisters(): array
return $registers;
}
+
+ public function getRegister(): int
+ {
+ return $this->register;
+ }
+
+ public function setRegister(int $register): void
+ {
+ $this->register = $register;
+ }
+
+ public function getSchema(): int
+ {
+ return $this->schema;
+ }
+
+ public function setSchema(int $schema): void
+ {
+ $this->schema = $schema;
+ }
}
diff --git a/lib/Service/PostgresqlScripts/fetch_aggregations_example.sql b/lib/Service/PostgresqlScripts/fetch_aggregations_example.sql
new file mode 100644
index 0000000..142c373
--- /dev/null
+++ b/lib/Service/PostgresqlScripts/fetch_aggregations_example.sql
@@ -0,0 +1 @@
+select (object#>>'{tooi}') as gemeentecode, count(*) as count from oc_openregister_objects where register = '2' group by gemeentecode;
\ No newline at end of file
diff --git a/lib/Service/PostgresqlScripts/filter_json.sql b/lib/Service/PostgresqlScripts/filter_json.sql
new file mode 100644
index 0000000..ff20bff
--- /dev/null
+++ b/lib/Service/PostgresqlScripts/filter_json.sql
@@ -0,0 +1,3 @@
+SELECT * FROM public.oc_openregister_objects
+WHERE register = '2' AND object#>>'{tooi}' = '0268' OR object#>>'{tooi}' = '0935'
+ORDER BY id ASC
\ No newline at end of file
diff --git a/src/entities/source/source.mock.ts b/src/entities/source/source.mock.ts
index a9e1f95..c6ba94c 100644
--- a/src/entities/source/source.mock.ts
+++ b/src/entities/source/source.mock.ts
@@ -4,10 +4,10 @@ import { TSource } from './source.types'
export const mockSourceData = (): TSource[] => [
{
id: 1,
- title: 'Main PostgreSQL Database',
+ title: 'Main MongoDB Database',
description: 'Primary database for user data',
- databaseUrl: 'postgresql://user:password@localhost:5432/maindb',
- type: 'postgresql',
+ databaseUrl: 'mongodb://user:password@localhost:27017/maindb',
+ type: 'mongodb',
updated: new Date().toISOString(),
created: new Date().toISOString(),
},
diff --git a/src/entities/source/source.ts b/src/entities/source/source.ts
index ffa715e..b3ac52c 100644
--- a/src/entities/source/source.ts
+++ b/src/entities/source/source.ts
@@ -7,7 +7,7 @@ export class Source implements TSource {
public title: string
public description: string
public databaseUrl: string
- public type: string
+ public type: 'internal' | 'mongodb'
public updated: string
public created: string
@@ -16,7 +16,7 @@ export class Source implements TSource {
this.title = source.title || ''
this.description = source.description || ''
this.databaseUrl = source.databaseUrl || ''
- this.type = source.type || ''
+ this.type = source.type || 'internal'
this.updated = source.updated || ''
this.created = source.created || ''
}
@@ -27,7 +27,7 @@ export class Source implements TSource {
title: z.string().min(1),
description: z.string(),
databaseUrl: z.string().url(),
- type: z.string(),
+ type: z.enum(['internal', 'mongodb']),
})
return schema.safeParse(this)
diff --git a/src/entities/source/source.types.ts b/src/entities/source/source.types.ts
index 05e55d1..b3f1e0b 100644
--- a/src/entities/source/source.types.ts
+++ b/src/entities/source/source.types.ts
@@ -3,7 +3,7 @@ export type TSource = {
title: string;
description: string;
databaseUrl: string;
- type: string;
+ type: 'internal' | 'mongodb';
updated: string;
created: string;
}
diff --git a/src/modals/source/EditSource.vue b/src/modals/source/EditSource.vue
index 6b6cac9..468fcb1 100644
--- a/src/modals/source/EditSource.vue
+++ b/src/modals/source/EditSource.vue
@@ -24,9 +24,10 @@ import { sourceStore, navigationStore } from '../../store/store.js'
-
+
@@ -57,6 +58,7 @@ import {
NcDialog,
NcTextField,
NcTextArea,
+ NcSelect,
NcLoadingIcon,
NcNoteCard,
} from '@nextcloud/vue'
@@ -71,6 +73,7 @@ export default {
NcDialog,
NcTextField,
NcTextArea,
+ NcSelect,
NcButton,
NcLoadingIcon,
NcNoteCard,
@@ -85,9 +88,14 @@ export default {
title: '',
description: '',
databaseUrl: '',
- type: '',
- created: '',
- updated: '',
+ },
+ typeOptions: {
+ clearable: false,
+ options: [
+ { label: 'Internal', id: 'internal' },
+ { label: 'MongoDB', id: 'mongodb' },
+ ],
+ value: { label: 'Internal', id: 'internal' },
},
success: false,
loading: false,
@@ -110,6 +118,9 @@ export default {
this.sourceItem = {
...sourceStore.sourceItem,
}
+
+ // set typeOptions to the sourceItem type
+ this.typeOptions.value = this.typeOptions.options.find(option => option.id === this.sourceItem.type)
}
},
closeModal() {
@@ -123,16 +134,16 @@ export default {
title: '',
description: '',
databaseUrl: '',
- type: '',
- created: '',
- updated: '',
}
+ // reset typeOptions to the internal option
+ this.typeOptions.value = this.typeOptions.options.find(option => option.id === 'internal')
},
async editSource() {
this.loading = true
sourceStore.saveSource({
...this.sourceItem,
+ type: this.typeOptions.value.id,
}).then(({ response }) => {
this.success = response.ok
this.error = false