From cdb4b382b73e84a3331ebdfb93c858a2baafa1f2 Mon Sep 17 00:00:00 2001 From: Aldarien Date: Mon, 8 Aug 2022 22:36:04 -0400 Subject: [PATCH] Limpieza de objetos externos --- api/ProVM/Alias/Controller/Json.php | 16 ++ api/{common => ProVM}/Alias/Database.php | 58 ++--- api/ProVM/Alias/Database/Query.php | 12 + api/{common => ProVM}/Alias/Factory/Model.php | 8 +- api/{common => ProVM}/Alias/File.php | 4 +- api/{common => ProVM}/Alias/Filesystem.php | 4 +- api/ProVM/Alias/Model.php | 77 +++++++ api/ProVM/Alias/Repository.php | 119 ++++++++++ api/ProVM/Concept/Database/Query.php | 7 + api/ProVM/Concept/Factory/Model.php | 10 + api/{common => ProVM}/Concept/File.php | 2 +- api/{common => ProVM}/Concept/Filesystem.php | 2 +- api/ProVM/Concept/Model.php | 16 ++ api/{common => ProVM}/Concept/Repository.php | 5 +- api/ProVM/Implement/Collection.php | 117 ++++++++++ .../Implement/Database/MySQL.php | 2 +- .../Implement/Database/Query/MySQL/Delete.php | 47 ++++ .../Implement/Database/Query/MySQL/Insert.php | 103 +++++++++ .../Implement/Database/Query/MySQL/Select.php | 210 ++++++++++++++++++ .../Implement/Database/Query/MySQL/Update.php | 67 ++++++ api/ProVM/Implement/Database/QueryBuilder.php | 61 +++++ api/{src => ProVM}/Implement/File.php | 2 +- api/{src => ProVM}/Implement/Filesystem.php | 2 +- api/Psr/Collection/CollectionInterface.php | 14 ++ .../Database/DatabaseInterface.php} | 18 +- api/common/Alias/Model.php | 8 - api/common/Alias/Repository.php | 62 ------ api/common/Concept/Factory/Model.php | 10 - api/common/Concept/Model.php | 6 - api/common/Controller/Base.php | 24 ++ api/common/Controller/Cuentas.php | 125 +++++++++++ api/common/Middleware/Auth.php | 4 +- api/common/Service/Auth.php | 2 +- api/composer.json | 1 + api/resources/documentation/base.json | 12 + api/resources/routes/base.php | 5 +- api/resources/routes/categorias.php | 13 -- api/resources/routes/consolidar.php | 7 - api/resources/routes/cuentas.php | 20 +- api/resources/routes/import.php | 5 - api/resources/routes/monedas.php | 12 - api/resources/routes/queues.php | 8 - api/resources/routes/tipos.php | 16 -- api/resources/routes/tipos/cambios.php | 13 -- api/resources/routes/tipos/categorias.php | 13 -- api/resources/routes/tipos/cuentas.php | 12 - api/resources/routes/transacciones.php | 12 - api/resources/routes/uploads.php | 12 - api/resources/routes/workers.php | 6 - api/setup/middlewares/01_auth.php | 2 +- api/setup/settings/02_common.php | 59 +++-- api/setup/settings/03_database.php | 11 +- api/setup/setups/01_auth.php | 14 ++ api/setup/setups/02_database.php | 12 +- api/src/Model/Cuenta.php | 12 +- api/src/Repository/Cuenta.php | 36 ++- docker-compose.yml | 12 +- 57 files changed, 1210 insertions(+), 339 deletions(-) create mode 100644 api/ProVM/Alias/Controller/Json.php rename api/{common => ProVM}/Alias/Database.php (62%) create mode 100644 api/ProVM/Alias/Database/Query.php rename api/{common => ProVM}/Alias/Factory/Model.php (78%) rename api/{common => ProVM}/Alias/File.php (93%) rename api/{common => ProVM}/Alias/Filesystem.php (93%) create mode 100644 api/ProVM/Alias/Model.php create mode 100644 api/ProVM/Alias/Repository.php create mode 100644 api/ProVM/Concept/Database/Query.php create mode 100644 api/ProVM/Concept/Factory/Model.php rename api/{common => ProVM}/Concept/File.php (94%) rename api/{common => ProVM}/Concept/Filesystem.php (94%) create mode 100644 api/ProVM/Concept/Model.php rename api/{common => ProVM}/Concept/Repository.php (64%) create mode 100644 api/ProVM/Implement/Collection.php rename api/{src => ProVM}/Implement/Database/MySQL.php (94%) create mode 100644 api/ProVM/Implement/Database/Query/MySQL/Delete.php create mode 100644 api/ProVM/Implement/Database/Query/MySQL/Insert.php create mode 100644 api/ProVM/Implement/Database/Query/MySQL/Select.php create mode 100644 api/ProVM/Implement/Database/Query/MySQL/Update.php create mode 100644 api/ProVM/Implement/Database/QueryBuilder.php rename api/{src => ProVM}/Implement/File.php (89%) rename api/{src => ProVM}/Implement/Filesystem.php (91%) create mode 100644 api/Psr/Collection/CollectionInterface.php rename api/{common/Concept/Database.php => Psr/Database/DatabaseInterface.php} (70%) delete mode 100644 api/common/Alias/Model.php delete mode 100644 api/common/Alias/Repository.php delete mode 100644 api/common/Concept/Factory/Model.php delete mode 100644 api/common/Concept/Model.php create mode 100644 api/common/Controller/Base.php create mode 100644 api/common/Controller/Cuentas.php create mode 100644 api/resources/documentation/base.json delete mode 100644 api/resources/routes/categorias.php delete mode 100644 api/resources/routes/consolidar.php delete mode 100644 api/resources/routes/import.php delete mode 100644 api/resources/routes/monedas.php delete mode 100644 api/resources/routes/queues.php delete mode 100644 api/resources/routes/tipos.php delete mode 100644 api/resources/routes/tipos/cambios.php delete mode 100644 api/resources/routes/tipos/categorias.php delete mode 100644 api/resources/routes/tipos/cuentas.php delete mode 100644 api/resources/routes/transacciones.php delete mode 100644 api/resources/routes/uploads.php delete mode 100644 api/resources/routes/workers.php create mode 100644 api/setup/setups/01_auth.php diff --git a/api/ProVM/Alias/Controller/Json.php b/api/ProVM/Alias/Controller/Json.php new file mode 100644 index 0000000..66716a0 --- /dev/null +++ b/api/ProVM/Alias/Controller/Json.php @@ -0,0 +1,16 @@ +getBody()->write(json_encode($json_data)); + return $response + ->withStatus($status_code) + ->withHeader('Content-Type', 'application/json'); + } +} diff --git a/api/common/Alias/Database.php b/api/ProVM/Alias/Database.php similarity index 62% rename from api/common/Alias/Database.php rename to api/ProVM/Alias/Database.php index ba103a7..8520e4e 100644 --- a/api/common/Alias/Database.php +++ b/api/ProVM/Alias/Database.php @@ -1,7 +1,9 @@ port; } protected string $name; - public function setName(string $database_name): Database + public function setName(string $database_name): DatabaseInterface { $this->name = $database_name; return $this; @@ -35,7 +37,7 @@ abstract class Database implements DatabaseInterface } protected string $username; protected string $password; - public function setUser(string $username, string $password): Database + public function setUser(string $username, string $password): DatabaseInterface { $this->username = $username; $this->password = $password; @@ -50,17 +52,17 @@ abstract class Database implements DatabaseInterface return $this->password; } - protected \PDO $connection; - public function connect(): Database + protected PDO $connection; + public function connect(): DatabaseInterface { if ($this->needsUser()) { - $this->connection = new \PDO($this->getDsn(), $this->getUser(), $this->getPassword()); + $this->connection = new PDO($this->getDsn(), $this->getUser(), $this->getPassword()); return $this; } - $this->connection = new \PDO($this->getDsn()); + $this->connection = new PDO($this->getDsn()); return $this; } - public function getConnection(): \PDO + public function getConnection(): PDO { if (!isset($this->connection)) { return $this->connect()->connection; @@ -71,38 +73,38 @@ abstract class Database implements DatabaseInterface { $st = $this->getConnection()->query($query); if (!$st) { - throw new \PDOException("Could not retrieve anything with '{$query}'."); + throw new PDOException("Could not retrieve anything with '{$query}'."); } - $results = $st->fetchAll(\PDO::FETCH_ASSOC); + $results = $st->fetchAll(PDO::FETCH_ASSOC); if (!$results) { - throw new \PDOException('Could not retrieve any results.'); + throw new PDOException('Could not retrieve any results.'); } return $results; } public function beginTransaction(): void { if (!$this->getConnection()->beginTransaction()) { - throw new \PDOException('Could not begin transaction.'); + throw new PDOException('Could not begin transaction.'); } } public function commit(): void { if (!$this->getConnection()->commit()) { - throw new \PDOException('Could not commit'); + throw new PDOException('Could not commit'); } } public function rollBack(): void { if (!$this->getConnection()->rollBack()) { - throw new \PDOException('Could not rollback.'); + throw new PDOException('Could not rollback.'); } } - protected \PDOStatement $prepared_statement; - public function prepare(string $query): Database + protected PDOStatement $prepared_statement; + public function prepare(string $query): DatabaseInterface { $st = $this->getConnection()->prepare($query); if (!$st) { - throw new \PDOException("Could not prepare query '{$query}'."); + throw new PDOException("Could not prepare query '{$query}'."); } $this->prepared_statement = $st; return $this; @@ -110,42 +112,42 @@ abstract class Database implements DatabaseInterface public function execute(array $data): array { if (!isset($this->prepared_statement)) { - throw new \Exception('No prepared statement.'); + throw new Exception('No prepared statement.'); } if (!$this->prepared_statement->execute($data)) { - throw new \PDOException('Could not execute prepared statement.'); + throw new PDOException('Could not execute prepared statement.'); } - $results = $this->prepared_statement->fetchAll(\PDO::FETCH_ASSOC); + $results = $this->prepared_statement->fetchAll(PDO::FETCH_ASSOC); if (!$results) { - throw new \PDOException('Could not retrieve any results.'); + throw new PDOException('Could not retrieve any results.'); } return $results; } public function insert(array $values): void { if (!isset($this->prepared_statement)) { - throw new \Exception('No prepared statement.'); + throw new Exception('No prepared statement.'); } if (!$this->prepared_statement->execute($values)) { - throw new \PDOException('Could not insert.'); + throw new PDOException('Could not insert.'); } } public function update(array $data): void { if (!isset($this->prepared_statement)) { - throw new \Exception('No prepared statement.'); + throw new Exception('No prepared statement.'); } if (!$this->prepared_statement->execute($data)) { - throw new \PDOException('Could not update.'); + throw new PDOException('Could not update.'); } } public function delete(array $data): void { if (!isset($this->prepared_statement)) { - throw new \Exception('No prepared statement.'); + throw new Exception('No prepared statement.'); } if (!$this->prepared_statement->execute($data)) { - throw new \PDOException('Could not delete.'); + throw new PDOException('Could not delete.'); } } } diff --git a/api/ProVM/Alias/Database/Query.php b/api/ProVM/Alias/Database/Query.php new file mode 100644 index 0000000..0bda90d --- /dev/null +++ b/api/ProVM/Alias/Database/Query.php @@ -0,0 +1,12 @@ +build(); + } +} diff --git a/api/common/Alias/Factory/Model.php b/api/ProVM/Alias/Factory/Model.php similarity index 78% rename from api/common/Alias/Factory/Model.php rename to api/ProVM/Alias/Factory/Model.php index 3a4d830..32aed52 100644 --- a/api/common/Alias/Factory/Model.php +++ b/api/ProVM/Alias/Factory/Model.php @@ -1,10 +1,10 @@ new = true; + return $this; + } + public function isNew(): bool + { + return $this->new; + } + + protected array $edited; + public function setEdited(string $property): ModelInterface + { + $this->edited []= $property; + return $this; + } + public function getEdited(): array + { + return $this->edited; + } + public function isDirty(): bool + { + return isset($this->edited) and count($this->edited) > 0; + } + + public function edit(array $data): void + { + $this->edited = []; + foreach ($data as $key => $val) { + if ($key === 'id') { + continue; + } + $getter = Model::parseGetter($key); + $setter = Model::parseSetter($key); + if (method_exists($this, $getter) and $this->{$getter}() !== $val) { + $this->{$setter}($val); + $this->setEdited($key); + } + } + } + + public static function parseSetter(string $property): string + { + return 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $property))); + } + public static function parseGetter(string $property): string + { + return 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $property))); + } + + public function jsonSerialize(): mixed + { + $ref = new ReflectionObject($this); + $properties = $ref->getProperties(); + $output = []; + foreach ($properties as $property) { + if (get_called_class() !== $property->getDeclaringClass()->getName()) { + continue; + } + $key = $property->getName(); + $method = Model::parseGetter($key); + if (method_exists($this, $method)) { + $output[$key] = $this->{$method}(); + } + } + return $output; + } +} diff --git a/api/ProVM/Alias/Repository.php b/api/ProVM/Alias/Repository.php new file mode 100644 index 0000000..8a9fdfe --- /dev/null +++ b/api/ProVM/Alias/Repository.php @@ -0,0 +1,119 @@ +setDatabase($database); + $this->setQueryBuilder($builder); + } + + protected QueryBuilder $query; + public function setQueryBuilder(QueryBuilder $builder): RepositoryInterface + { + $this->query = $builder; + return $this; + } + public function getQueryBuilder(): QueryBuilder + { + return $this->query; + } + + protected string $table; + public function setTable(string $table): RepositoryInterface + { + $this->table = $table; + return $this; + } + public function getTable(): string + { + return $this->table; + } + protected array $properties; + public function addProperty(string $name): RepositoryInterface + { + $this->properties []= $name; + return $this; + } + public function setProperties(array $properties): RepositoryInterface + { + foreach ($properties as $property) { + $this->addProperty($property); + } + return $this; + } + public function getProperties(): array + { + return $this->properties; + } + protected DatabaseInterface $database; + public function setDatabase(DatabaseInterface $database): RepositoryInterface + { + $this->database = $database; + return $this; + } + public function getDatabase(): DatabaseInterface + { + return $this->database; + } + + public function fetchAll(): array + { + $query = $this->getQueryBuilder()->select()->from($this->getTable())->build(); + return array_map([$this, 'load'], $this->getDatabase()->query($query)); + } + + protected function extractProperties(Model $model): array + { + $properties = []; + foreach ($this->getProperties() as $p) { + $method = $model::parseGetter($p); + if (method_exists($model, $method)) { + try { + $properties[$p] = $model->{$method}(); + } catch (Error $e) { + error_log($e); + } + } + } + return $properties; + } + public function save(Model $model): void + { + if ($model->isNew()) { + $this->saveNew($model); + return; + } + if ($model->isDirty()) { + $this->saveUpdate($model); + } + } + protected function saveNew(Model $model): void + { + $properties = $this->extractProperties($model); + $columns = array_keys($properties); + $ivalues = array_map(function($item) {return '?';}, array_keys($properties)); + $values = array_values($properties); + $query = $this->getQueryBuilder()->insert()->into($this->getTable())->columns($columns)->values($ivalues)->build(); + $this->getDatabase()->prepare($query)->insert($values); + } + protected function saveUpdate(Model $model): void + { + $edited = $model->getEdited(); + $properties = array_intersect_key($this->extractProperties($model), array_combine($edited, $edited)); + $sets = array_map(function($item) { + return "{$item} = ?"; + }, array_keys($properties)); + $values = array_values($properties); + $query = $this->getQueryBuilder()->update()->table($this->getTable())->set($sets)->where('id = ?')->build(); + $this->getDatabase()->prepare($query)->update(array_merge($values, [$model->getId()])); + } +} diff --git a/api/ProVM/Concept/Database/Query.php b/api/ProVM/Concept/Database/Query.php new file mode 100644 index 0000000..cf8eb57 --- /dev/null +++ b/api/ProVM/Concept/Database/Query.php @@ -0,0 +1,7 @@ +set(CollectionInterface::class, $this); + } + protected array $data; + public function set(mixed $name, $value): CollectionInterface + { + $this->data[$name] = $value; + return $this; + } + public function has(mixed $name): bool + { + return isset($this->data[$name]); + } + public function get(mixed $name) + { + return $this->process($this->data[$name]) ?? null; + } + public function remove(mixed $name): CollectionInterface + { + unset($this->data[$name]); + return $this; + } + + protected function process($value) + { + if (is_callable($value)) { + return $this->processCallable($value); + } + if (is_array($value)) { + return $this->processArray($value); + } + return $value; + } + protected function processCallable(Callable $value) + { + $ref = new \ReflectionFunction($value); + $param_array = []; + $params = $ref->getParameters(); + foreach ($params as $p) { + $param_array[$p->getName()] = $this->get($p->getType()->getName()); + } + return $ref->invokeArgs($param_array); + } + protected function processArray(array $value) + { + return $value; + } + + public static function fromArray(array $source): CollectionInterface + { + $obj = new Collection(); + foreach ($source as $key => $value) { + $obj->set($key, $value); + } + return $obj; + } + public static function fromObject(object $source): CollectionInterface + { + return Collection::fromArray((array) $source); + } + + public function __get(mixed $name): mixed + { + return $this->get($name); + } + + public function current(): mixed + { + return $this->data[$this->key()]; + } + public function next(): void + { + next($this->data); + } + public function key(): mixed + { + return key($this->data); + } + public function valid(): bool + { + return isset($this->data); + } + public function rewind(): void + { + reset($this->data); + } + + public function offsetExists(mixed $offset): bool + { + return $this->has($offset); + } + public function offsetGet(mixed $offset): mixed + { + return $this->get($offset); + } + public function offsetSet(mixed $offset, mixed $value): void + { + $this->set($offset, $value); + } + public function offsetUnset(mixed $offset): void + { + $this->remove($offset); + } + + public function count(): int + { + return count($this->data); + } +} diff --git a/api/src/Implement/Database/MySQL.php b/api/ProVM/Implement/Database/MySQL.php similarity index 94% rename from api/src/Implement/Database/MySQL.php rename to api/ProVM/Implement/Database/MySQL.php index 7261f49..325554d 100644 --- a/api/src/Implement/Database/MySQL.php +++ b/api/ProVM/Implement/Database/MySQL.php @@ -1,5 +1,5 @@ table = $table; + return $this; + } + public function getFrom(): string + { + return $this->table; + } + protected array $conditions; + public function where(array|string $conditions): Delete + { + if (is_string($conditions)) { + return $this->addCondition($conditions); + } + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + return $this; + } + public function addCondition(string $condition): Delete + { + $this->conditions []= $condition; + return $this; + } + public function getWhere(): string + { + return implode(' AND ', $this->conditions); + } + + public function build(): string + { + $output = ['DELETE FROM']; + $output []= $this->getFrom(); + $output []= "WHERE {$this->getWhere()}"; + + return implode(' ', $output); + } +} diff --git a/api/ProVM/Implement/Database/Query/MySQL/Insert.php b/api/ProVM/Implement/Database/Query/MySQL/Insert.php new file mode 100644 index 0000000..953c2ca --- /dev/null +++ b/api/ProVM/Implement/Database/Query/MySQL/Insert.php @@ -0,0 +1,103 @@ +table = $table; + return $this; + } + public function getTable(): string + { + return $this->table; + } + protected Select $select; + public function select(Select $select): Insert + { + $this->select = $select; + return $this; + } + public function getSelect(): string + { + return $this->select->build(); + } + protected array $columns; + public function columns(array $columns): Insert + { + foreach ($columns as $column) { + $this->addColumn($column); + } + return $this; + } + public function addColumn(string $column): Insert + { + $this->columns []= $column; + return $this; + } + public function getColumns(): string + { + return implode(', ', array_map(function($item) {return "`{$item}`";}, $this->columns)); + } + protected array $values; + public function values(array $values): Insert + { + foreach ($values as $value) { + $this->addValue($value); + } + return $this; + } + public function addValue(string $value): Insert + { + $this->values []= $value; + return $this; + } + public function getValues(): string + { + return implode(', ', array_map(function($item) { + return $item; + }, $this->values)); + } + protected array $updates; + public function update(array $updates): Insert + { + foreach ($updates as $update) { + $this->addUpdate($update); + } + return $this; + } + public function addUpdate(string $update_string): Insert + { + $this->updates []= $update_string; + return $this; + } + public function getUpdates(): string + { + return implode(', ', $this->updates); + } + + public function build(): string + { + $output = ['INSERT INTO']; + $output []= $this->getTable(); + try { + $output []= "({$this->getColumns()})"; + } catch (\Error $e) { + \Safe\error_log($e); + } + try { + $output []= $this->getSelect(); + } catch (\Error $e) { + $output []= "VALUES ({$this->getValues()})"; + } + try { + $output []= "ON DUPLICATE KEY UPDATE {$this->getUpdates()}"; + } catch (\Error $e) { + \Safe\error_log($e); + } + return implode(' ', $output); + } +} diff --git a/api/ProVM/Implement/Database/Query/MySQL/Select.php b/api/ProVM/Implement/Database/Query/MySQL/Select.php new file mode 100644 index 0000000..49d25b1 --- /dev/null +++ b/api/ProVM/Implement/Database/Query/MySQL/Select.php @@ -0,0 +1,210 @@ +addColumn($columns); + } + foreach ($columns as $column) { + $this->addColumn($column); + } + return $this; + } + public function addColumn(string $column): Select + { + $this->columns []= $column; + return $this; + } + public function getSelect(): string + { + return implode(', ', array_map(function($item) { + return "`{$item}`"; + }, $this->columns)); + } + protected string $from; + public function from(string $table): Select + { + $this->from = $table; + return $this; + } + public function getFrom(): string + { + return $this->from; + } + protected array $joins; + public function join(array $joins): Select + { + if (is_string($joins[0])) { + return $this->addJoin($joins[0], $joins[1], $joins[2]); + } + foreach ($joins as $join) { + $this->addJoin($join[0], $join[1], $join[2]); + } + return $this; + } + public function addJoin(string $join_type, string $join_table, string $conditions): Select + { + $join_type = strtoupper($join_type); + $this->joins []= "{$join_type} {$join_table} {$conditions}"; + return $this; + } + public function getJoins(): string + { + return implode('', $this->joins); + } + protected array $conditions; + public function where(array|string $conditions): Select + { + if (is_string($conditions)) { + return $this->addWhere($conditions); + } + foreach ($conditions as $condition) { + $this->addWhere($condition); + } + return $this; + } + public function addWhere($condition): Select + { + $this->conditions []= $condition; + return $this; + } + public function getWheres(): string + { + return implode(' AND ', $this->conditions); + } + protected array $orders; + public function order(array|string $orders): Select + { + if (is_string($orders)) { + return $this->addOrder($orders); + } + foreach ($orders as $order) { + if (is_array($order)) { + $this->addOrder($order[0], $order[1]); + continue; + } + $this->addOrder($order); + } + return $this; + } + public function addOrder(string $order, string $dir = 'desc'): Select + { + $dir = strtoupper($dir); + $this->orders []= "{$order} {$dir}"; + return $this; + } + public function getOrders(): string + { + return implode(', ', $this->orders); + } + protected array $groups; + public function group(array|string $groups): Select + { + if (is_string($groups)) { + return $this->addGroup($groups); + } + foreach ($groups as $group) { + $this->addGroup($group); + } + return $this; + } + public function addGroup(string $group): Select + { + $this->groups []= $group; + return $this; + } + public function getGroups(): string + { + return implode(', ', $this->groups); + } + protected array $having; + public function having(array|string $havings): Select + { + if (is_string($havings)) { + return $this->addHaving($havings); + } + foreach ($havings as $having) { + $this->addHaving($having); + } + return $this; + } + public function addHaving(string $having): Select + { + $this->having []= $having; + return $this; + } + public function getHaving(): string + { + return implode(' AND ', $this->having); + } + protected int $limit; + public function limit(int $limit): Select + { + $this->limit = $limit; + return $this; + } + protected int $offset; + public function offset(int $offset): Select + { + $this->offset = $offset; + return $this; + } + public function getLimit(): string + { + $output = [$this->limit]; + if (isset($this->offset)) { + $output []= 'OFFSET'; + $output []= $this->offset; + } + return implode(' ', $output); + } + + public function build(): string + { + $output = ['SELECT']; + try { + $output []= $this->getSelect(); + } catch (\Error $e) { + $output []= '*'; + } + $output []= 'FROM'; + $output []= $this->getFrom(); + try { + $output [] = $this->getJoins(); + } catch (\Error $e) { + \Safe\error_log($e); + } + try { + $output []= "WHERE {$this->getWheres()}"; + } catch (\Error $e) { + \Safe\error_log($e); + } + try { + $output []= "GROUP BY {$this->getGroups()}"; + } catch (\Error $e) { + \Safe\error_log($e); + } + try { + $output []= "HAVING {$this->getHaving()}"; + } catch (\Error $e) { + \Safe\error_log($e); + } + try { + $output []= "ORDER BY {$this->getOrders()}"; + } catch (\Error $e) { + \Safe\error_log($e); + } + try { + $output []= "LIMIT {$this->getLimit()}"; + } catch (\Error $e) { + \Safe\error_log($e); + } + return implode(' ', $output); + } +} diff --git a/api/ProVM/Implement/Database/Query/MySQL/Update.php b/api/ProVM/Implement/Database/Query/MySQL/Update.php new file mode 100644 index 0000000..608dd0f --- /dev/null +++ b/api/ProVM/Implement/Database/Query/MySQL/Update.php @@ -0,0 +1,67 @@ +table = $table; + return $this; + } + public function getTable(): string + { + return $this->table; + } + protected array $sets; + public function set(array|string $set_strings): Update + { + if (is_string($set_strings)) { + return $this->addSet($set_strings); + } + foreach ($set_strings as $set_string) { + $this->addSet($set_string); + } + return $this; + } + public function addSet(string $set_string): Update + { + $this->sets []= $set_string; + return $this; + } + public function getSets(): string + { + return implode(', ', $this->sets); + } + protected array $conditions; + public function where(array|string $conditions): Update + { + if (is_string($conditions)) { + return $this->addCondition($conditions); + } + foreach ($conditions as $condition) { + $this->addCondition($condition); + } + return $this; + } + public function addCondition(string $condition): Update + { + $this->conditions []= $condition; + return $this; + } + public function getWhere(): string + { + return implode(' AND ', $this->conditions); + } + + public function build(): string + { + $output = ['UPDATE']; + $output []= $this->getTable(); + $output []= "SET {$this->getSets()}"; + $output []= "WHERE {$this->getWhere()}"; + return implode(' ', $output); + } +} diff --git a/api/ProVM/Implement/Database/QueryBuilder.php b/api/ProVM/Implement/Database/QueryBuilder.php new file mode 100644 index 0000000..91c9efb --- /dev/null +++ b/api/ProVM/Implement/Database/QueryBuilder.php @@ -0,0 +1,61 @@ +addBuilder($container->get($builder)); + } + } + protected array $builders; + public function addBuilder(Query $builder): QueryBuilder + { + $name = explode("\\", get_class($builder)); + $name = array_pop($name); + $this->builders[strtolower($name)] = $builder; + return $this; + } + public function start(string $name): Query + { + return $this->builders[strtolower($name)]; + } + public function select(): Select + { + return $this->start('select'); + } + public function insert(): Insert + { + return $this->start('insert'); + } + public function update(): Update + { + return $this->start('update'); + } + public function delete(): Delete + { + return $this->start('delete'); + } +} diff --git a/api/src/Implement/File.php b/api/ProVM/Implement/File.php similarity index 89% rename from api/src/Implement/File.php rename to api/ProVM/Implement/File.php index 2b44572..3c35b3a 100644 --- a/api/src/Implement/File.php +++ b/api/ProVM/Implement/File.php @@ -1,5 +1,5 @@ table = $table; - return $this; - } - public function getTable(): string - { - return $this->table; - } - protected array $properties; - public function addProperty(string $name): RepositoryInterface - { - $this->properties []= $name; - return $this; - } - public function setProperties(array $properties): RepositoryInterface - { - foreach ($properties as $property) { - $this->addProperty($property); - } - return $this; - } - public function getProperties(): array - { - return $this->properties; - } - protected Database $database; - public function setDatabase(Database $database): RepositoryInterface - { - $this->database = $database; - return $this; - } - public function getDatabase(): Database - { - return $this->database; - } - - public function fetchAll(): array - { - $query = "SELECT * FROM {$this->getTable()}"; - return array_map([$this, 'load'], $this->getDatabase()->query($query)); - } - - public function save(Model $model): void - { - $columns = implode(', ', array_map(function($item) {return "'{$item}'";}, $this->getProperties())); - $values = implode(', ', array_map(function($item) {return '?';}, $this->getProperties())); - $query = "INSERT INTO {$this->getTable()} ({$columns}) VALUES ({$values})"; - $values = array_map(function($item) use ($model) {$method = str_replace(' ', '', ucwords(str_replace('_', '', $item)));return $model->{"get{$method}"}();}, $this->getProperties()); - $this->getDatabase()->prepare($query)->insert($values); - } -} diff --git a/api/common/Concept/Factory/Model.php b/api/common/Concept/Factory/Model.php deleted file mode 100644 index 0290760..0000000 --- a/api/common/Concept/Factory/Model.php +++ /dev/null @@ -1,10 +0,0 @@ -get('folders')->documentation; + $filename = implode(DIRECTORY_SEPARATOR, [ + $folder, + 'base.json' + ]); + $documentation = json_decode(file_get_contents($filename)); + return $this->withJson($response, $documentation); + } +} diff --git a/api/common/Controller/Cuentas.php b/api/common/Controller/Cuentas.php new file mode 100644 index 0000000..f8c8487 --- /dev/null +++ b/api/common/Controller/Cuentas.php @@ -0,0 +1,125 @@ +fetchAll(); + } catch (PDOException $e) { + error_log($e); + } + return $this->withJson($response, compact('cuentas')); + } + public function get(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository, $cuenta_id): ResponseInterface + { + $cuenta = null; + try { + $cuenta = $repository->fetchById($cuenta_id); + } catch (PDOException $e) { + error_log($e); + } + return $this->withJson($response, compact('cuenta')); + } + public function add(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository): ResponseInterface + { + $body = $request->getBody(); + $contents = $body->getContents(); + $json = json_decode($contents); + if (!is_array($json)) { + $json = [$json]; + } + $output = [ + 'input' => $json, + 'cuentas' => [] + ]; + foreach ($json as $data) { + $cuenta = $repository->create((array) $data); + $status = true; + $exists = true; + if ($cuenta->isNew()) { + $exists = false; + try { + $repository->save($cuenta); + } catch (PDOException $e) { + error_log($e); + $status = false; + } + } + $output['cuentas'] []= [ + 'cuenta' => $cuenta, + 'exists' => $exists, + 'added' => $status + ]; + } + return $this->withJson($response, $output); + } + public function edit(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository): ResponseInterface + { + $body = $request->getBody(); + $contents = $body->getContents(); + $json = json_decode($contents); + if (!is_array($json)) { + $json = [$json]; + } + $output = [ + 'input' => $json, + 'cuentas' => [] + ]; + foreach ($json as $data) { + $cuenta = $repository->fetchById($data->id); + $old = clone $cuenta; + try { + $cuenta->edit((array) $data); + $status = $cuenta->isDirty(); + if ($status) { + $repository->save($cuenta); + } + } catch (PDOException $e) { + error_log($e); + $status = false; + } + $output['cuentas'] []= [ + 'antes' => $old, + 'cuenta' => $cuenta, + 'edited' => $status + ]; + } + return $this->withJson($response, $output); + } + public function editOne(ServerRequestInterface $request, ResponseInterface $response, Cuenta $repository, $cuenta_id): ResponseInterface + { + $cuenta = $repository->fetchById($cuenta_id); + $body = $request->getBody(); + $contents = $body->getContents(); + $json = json_decode($contents, JSON_OBJECT_AS_ARRAY); + $output = [ + 'input' => $json, + 'old' => clone $cuenta + ]; + try { + $cuenta->edit((array) $json); + $status = $cuenta->isDirty(); + if ($status) { + $repository->save($cuenta); + } + } catch (PDOException $e) { + error_log($e); + $status = false; + } + $output['cuenta'] = $cuenta; + $output['edited'] = $status; + return $this->withJson($response, $output); + } +} diff --git a/api/common/Middleware/Auth.php b/api/common/Middleware/Auth.php index b19cd4c..99e646e 100644 --- a/api/common/Middleware/Auth.php +++ b/api/common/Middleware/Auth.php @@ -1,11 +1,11 @@ get('/key/generate[/]', [Base::class, 'generate_key']); -$app->get('/balance[/]', [Contabilidad\Common\Controller\TiposCategorias::class, 'balance']); -$app->get('/info', [Base::class, 'info']); $app->get('/', Base::class); diff --git a/api/resources/routes/categorias.php b/api/resources/routes/categorias.php deleted file mode 100644 index 361cfae..0000000 --- a/api/resources/routes/categorias.php +++ /dev/null @@ -1,13 +0,0 @@ -group('/categorias', function($app) { - $app->post('/add[/]', [Categorias::class, 'add']); - $app->get('[/]', Categorias::class); -}); -$app->group('/categoria/{categoria_id}', function($app) { - $app->get('/cuentas', [Categorias::class, 'cuentas']); - $app->put('/edit', [Categorias::class, 'edit']); - $app->delete('/delete', [Categorias::class, 'delete']); - $app->get('[/]', [Categorias::class, 'show']); -}); diff --git a/api/resources/routes/consolidar.php b/api/resources/routes/consolidar.php deleted file mode 100644 index ab63298..0000000 --- a/api/resources/routes/consolidar.php +++ /dev/null @@ -1,7 +0,0 @@ -group('/consolidar', function($app) { - $app->get('/update/{mes}/{cuenta_id}', [Consolidados::class, 'update']); - $app->get('[/]', [Consolidados::class, 'cli']); -}); diff --git a/api/resources/routes/cuentas.php b/api/resources/routes/cuentas.php index 76d6d03..3fd3ea6 100644 --- a/api/resources/routes/cuentas.php +++ b/api/resources/routes/cuentas.php @@ -1,20 +1,12 @@ group('/cuentas', function($app) { - $app->post('/add[/]', [Cuentas::class, 'add']); - $app->get('[/]', Cuentas::class); + $app->post('/add[/]', [Cuentas::class, 'add']); + $app->post('/edit[/]', [Cuentas::class, 'edit']); + $app->get('[/]', Cuentas::class); }); $app->group('/cuenta/{cuenta_id}', function($app) { - $app->get('/entradas', [Cuentas::class, 'entradas']); - $app->group('/transacciones', function($app) { - $app->get('/amount', [Cuentas::class, 'transaccionesAmount']); - $app->get('/month/{month}', [Cuentas::class, 'transaccionesMonth']); - $app->get('/acum/{date}', [Cuentas::class, 'transaccionesAcumulation']); - $app->get('[/{limit:[0-9]+}[/{start:[0-9]+}]]', [Cuentas::class, 'transacciones']); - }); - $app->get('/categoria', [Cuentas::class, 'categoria']); - $app->put('/edit', [Cuentas::class, 'edit']); - $app->delete('/delete', [Cuentas::class, 'delete']); - $app->get('[/]', [Cuentas::class, 'show']); + $app->post('/edit[/]', [Cuentas::class, 'editOne']); + $app->get('[/]', [Cuentas::class, 'get']); }); diff --git a/api/resources/routes/import.php b/api/resources/routes/import.php deleted file mode 100644 index 0a6bc5b..0000000 --- a/api/resources/routes/import.php +++ /dev/null @@ -1,5 +0,0 @@ -post('/import', Import::class); -$app->get('/import/uploads', [Import::class, 'uploads']); \ No newline at end of file diff --git a/api/resources/routes/monedas.php b/api/resources/routes/monedas.php deleted file mode 100644 index a7ef909..0000000 --- a/api/resources/routes/monedas.php +++ /dev/null @@ -1,12 +0,0 @@ -group('/monedas', function($app) { - $app->post('/add[/]', [Monedas::class, 'add']); - $app->get('[/]', Monedas::class); -}); -$app->group('/moneda/{moneda_id}', function($app) { - $app->put('/edit', [Monedas::class, 'edit']); - $app->delete('/delete', [Monedas::class, 'delete']); - $app->get('[/]', [Monedas::class, 'show']); -}); diff --git a/api/resources/routes/queues.php b/api/resources/routes/queues.php deleted file mode 100644 index d4a802f..0000000 --- a/api/resources/routes/queues.php +++ /dev/null @@ -1,8 +0,0 @@ -group('/queues', function($app) { - $app->get('/pending', [Queues::class, 'pending']); - $app->post('/processed', [Queues::class, 'processed']); - $app->get('[/]', Queues::class); -}); diff --git a/api/resources/routes/tipos.php b/api/resources/routes/tipos.php deleted file mode 100644 index 8f636c4..0000000 --- a/api/resources/routes/tipos.php +++ /dev/null @@ -1,16 +0,0 @@ -group('/tipos', function($app) use ($folder) { - $files = new DirectoryIterator($folder); - foreach ($files as $file) { - if ($file->isDir() or $file->getExtension() != 'php') { - continue; - } - include_once $file->getRealPath(); - } - }); -} \ No newline at end of file diff --git a/api/resources/routes/tipos/cambios.php b/api/resources/routes/tipos/cambios.php deleted file mode 100644 index edf5cf7..0000000 --- a/api/resources/routes/tipos/cambios.php +++ /dev/null @@ -1,13 +0,0 @@ -group('/cambios', function($app) { - $app->post('/obtener[/]', [TiposCambios::class, 'obtain']); - $app->post('/add[/]', [TiposCambios::class, 'add']); - $app->get('[/]', TiposCambios::class); -}); -$app->group('/cambio/{tipo_id}', function($app) { - $app->put('/edit[/]', [TiposCambios::class, 'edit']); - $app->delete('/delete[/]', [TiposCambios::class, 'delete']); - $app->get('[/]', [TiposCambios::class, 'show']); -}); diff --git a/api/resources/routes/tipos/categorias.php b/api/resources/routes/tipos/categorias.php deleted file mode 100644 index 38f9272..0000000 --- a/api/resources/routes/tipos/categorias.php +++ /dev/null @@ -1,13 +0,0 @@ -group('/categorias', function($app) { - $app->post('/add[/]', [TiposCategorias::class, 'add']); - $app->get('[/]', TiposCategorias::class); -}); -$app->group('/categoria/{tipo_id}', function($app) { - $app->get('/categorias', [TiposCategorias::class, 'categorias']); - $app->put('/edit', [TiposCategorias::class, 'edit']); - $app->delete('/delete', [TiposCategorias::class, 'delete']); - $app->get('[/]', [TiposCategorias::class, 'show']); -}); diff --git a/api/resources/routes/tipos/cuentas.php b/api/resources/routes/tipos/cuentas.php deleted file mode 100644 index 4b6f4ba..0000000 --- a/api/resources/routes/tipos/cuentas.php +++ /dev/null @@ -1,12 +0,0 @@ -group('/cuentas', function($app) { - $app->post('/add[/]', [TiposCuentas::class, 'add']); - $app->get('[/]', TiposCuentas::class); -}); -$app->group('/cuenta/{tipo_id}', function($app) { - $app->put('/edit', [TiposCuentas::class, 'edit']); - $app->delete('/delete', [TiposCuentas::class, 'delete']); - $app->get('[/]', [TiposCuentas::class, 'show']); -}); diff --git a/api/resources/routes/transacciones.php b/api/resources/routes/transacciones.php deleted file mode 100644 index 60a7437..0000000 --- a/api/resources/routes/transacciones.php +++ /dev/null @@ -1,12 +0,0 @@ -group('/transacciones', function($app) { - $app->post('/add[/]', [Transacciones::class, 'add']); - $app->get('[/]', Transacciones::class); -}); -$app->group('/transaccion/{transaccion_id}', function($app) { - $app->put('/edit', [Transacciones::class, 'edit']); - $app->delete('/delete', [Transacciones::class, 'delete']); - $app->get('[/]', [Transacciones::class, 'show']); -}); diff --git a/api/resources/routes/uploads.php b/api/resources/routes/uploads.php deleted file mode 100644 index 6138be8..0000000 --- a/api/resources/routes/uploads.php +++ /dev/null @@ -1,12 +0,0 @@ -group('/uploads', function($app) { - $app->post('/add[/]', [Files::class, 'upload']); - $app->get('[/]', Files::class); -}); -$app->group('/upload/{folder}/{filename}', function($app) { - $app->put('[/]', [Files::class, 'edit']); - $app->delete('[/]', [Files::class, 'delete']); - $app->get('[/]', [Files::class, 'get']); -}); diff --git a/api/resources/routes/workers.php b/api/resources/routes/workers.php deleted file mode 100644 index acf34a9..0000000 --- a/api/resources/routes/workers.php +++ /dev/null @@ -1,6 +0,0 @@ -group('/workers', function($app) { - $app->post('/register', [Workers::class, 'register']); -}); diff --git a/api/setup/middlewares/01_auth.php b/api/setup/middlewares/01_auth.php index 36fb2e0..674904a 100644 --- a/api/setup/middlewares/01_auth.php +++ b/api/setup/middlewares/01_auth.php @@ -1,2 +1,2 @@ add($app->getContainer()->get(Contabilidad\Common\Middleware\Auth::class)); +$app->add($app->getContainer()->get(Common\Middleware\Auth::class)); diff --git a/api/setup/settings/02_common.php b/api/setup/settings/02_common.php index 86d76fc..ee3d5da 100644 --- a/api/setup/settings/02_common.php +++ b/api/setup/settings/02_common.php @@ -2,28 +2,39 @@ use Psr\Container\ContainerInterface as Container; return [ - 'folders' => function(Container $c) { - $arr = [ - 'base' => dirname(__DIR__, 2) - ]; - $arr['resources'] = implode(DIRECTORY_SEPARATOR, [ - $arr['base'], - 'resources' - ]); - $arr['routes'] = implode(DIRECTORY_SEPARATOR, [ - $arr['resources'], - 'routes' - ]); - $arr['public'] = implode(DIRECTORY_SEPARATOR, [ - $arr['base'], - 'public' - ]); - return (object) $arr; - }, - 'urls' => function(Container $c) { - $arr = [ - 'python' => 'http://python:5000' - ]; - return (object) $arr; - } + 'folders' => function(Container $c) { + return \ProVM\Implement\Collection::fromArray([ + 'base' => dirname(__DIR__, 2), + 'resources' => function(\Psr\Collection\CollectionInterface $collection) { + return implode(DIRECTORY_SEPARATOR, [ + $collection['base'], + 'resources' + ]); + }, + 'documentation' => function(\Psr\Collection\CollectionInterface $collection) { + return implode(DIRECTORY_SEPARATOR, [ + $collection['resources'], + 'documentation' + ]); + }, + 'routes' => function(\Psr\Collection\CollectionInterface $collection) { + return implode(DIRECTORY_SEPARATOR, [ + $collection['resources'], + 'routes' + ]); + }, + 'public' => function(\Psr\Collection\CollectionInterface $collection) { + return implode(DIRECTORY_SEPARATOR, [ + $collection['base'], + 'public' + ]); + } + ]); + }, + 'urls' => function(Container $c) { + $arr = [ + 'python' => 'http://python:5000' + ]; + return (object) $arr; + } ]; diff --git a/api/setup/settings/03_database.php b/api/setup/settings/03_database.php index 242d727..6131e82 100644 --- a/api/setup/settings/03_database.php +++ b/api/setup/settings/03_database.php @@ -14,15 +14,6 @@ return [ 'name' => $_ENV['MYSQL_DATABASE'] ] ]; - function toObj($arr) { - $obj = (object) $arr; - foreach ($arr as $k => $v) { - if (is_array($v)) { - $obj->{$k} = toObj($v); - } - } - return $obj; - } - return (object) ['databases' => toObj($arr), 'short_names' => true]; + return (object) ['databases' => \ProVM\Implement\Collection::fromArray($arr), 'short_names' => true]; } ]; diff --git a/api/setup/setups/01_auth.php b/api/setup/setups/01_auth.php new file mode 100644 index 0000000..1ad7322 --- /dev/null +++ b/api/setup/setups/01_auth.php @@ -0,0 +1,14 @@ + function(ContainerInterface $container) { + return new \Common\Service\Auth($container->get('api_key')); + }, + \Common\Middleware\Auth::class => function(ContainerInterface $container) { + return new \Common\Middleware\Auth( + $container->get(\Nyholm\Psr7\Factory\Psr17Factory::class), + $container->get(\Common\Service\Auth::class) + ); + } +]; diff --git a/api/setup/setups/02_database.php b/api/setup/setups/02_database.php index 6186f93..f602bc4 100644 --- a/api/setup/setups/02_database.php +++ b/api/setup/setups/02_database.php @@ -2,12 +2,12 @@ use Psr\Container\ContainerInterface; return [ - \Common\Concept\Database::class => function(ContainerInterface $container) { - $settings = $container->get('databases')->default; - return new \Contabilidad\Implement\Database\MySQL( - $settings->host->name, - $settings->user->name, - $settings->user->password, + \Psr\Database\DatabaseInterface::class => function(ContainerInterface $container) { + $settings = (object) $container->get('databases')->databases->default; + return new \ProVM\Implement\Database\MySQL( + $settings->host['name'], + $settings->user['name'], + $settings->user['password'], $settings->name ); } diff --git a/api/src/Model/Cuenta.php b/api/src/Model/Cuenta.php index d7a4b11..34d2da2 100644 --- a/api/src/Model/Cuenta.php +++ b/api/src/Model/Cuenta.php @@ -1,7 +1,7 @@ id; } + protected string $nombre; + public function setNombre(string $nombre): Cuenta + { + $this->nombre = $nombre; + return $this; + } + public function getNombre(): string + { + return $this->nombre; + } } diff --git a/api/src/Repository/Cuenta.php b/api/src/Repository/Cuenta.php index e6acf72..4b1cc85 100644 --- a/api/src/Repository/Cuenta.php +++ b/api/src/Repository/Cuenta.php @@ -1,14 +1,18 @@ setTable('cuentas'); $this->setProperties(['id', 'nombre', 'tipo']); } @@ -16,17 +20,33 @@ class Cuenta extends Repository public function load(array $data_row): Model { return (new BaseModel()) - ->setId($data_row['id']); + ->setId($data_row['id']) + ->setNombre($data_row['nombre']); + } + public function create(array $data): Model + { + try { + return $this->fetchByNombre($data['nombre']); + } catch (PDOException $e) { + return (new BaseModel()) + ->setNew() + ->setNombre($data['nombre']); + } } - public function fetchByKey($key): Model + public function fetchById(int $id): Model { - $query = "SELECT * FROM {$this->getTable()} WHERE `id` = ?"; - return $this->load($this->getDatabase()->prepare($query)->execute($key)[0]); + $query = $this->getQueryBuilder()->select()->from($this->getTable())->where('id = ?')->build(); + return $this->load($this->getDatabase()->prepare($query)->execute([$id])[0]); + } + public function fetchByNombre(string $nombre): Model + { + $query = $this->getQueryBuilder()->select()->from($this->getTable())->where('nombre = ?')->build(); + return $this->load($this->getDatabase()->prepare($query)->execute([$nombre])[0]); } public function delete(Model $model): void { - $query = "DELETE FROM {$this->getTable()} WHERE `id` = ?"; + $query = $this->getQueryBuilder()->delete()->from($this->getTable())->where('id = ?')->build(); $this->getDatabase()->prepare($query)->delete([$model->getId()]); } } diff --git a/docker-compose.yml b/docker-compose.yml index 7c33a42..762a041 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: api: profiles: - api + container_name: api <<: *restart image: php build: @@ -19,9 +20,10 @@ services: - ./api/:/app/ - ./api/php.ini:/usr/local/etc/php/conf.d/php.ini - ./logs/api/php/:/var/log/php/ - api-proxy: + api_proxy: profiles: - api + container_name: api_proxy <<: *restart image: nginx ports: @@ -33,6 +35,7 @@ services: db: profiles: - api + container_name: db <<: *restart image: mariadb env_file: .db.env @@ -41,6 +44,7 @@ services: adminer: profiles: - api + container_name: adminer <<: *restart image: adminer ports: @@ -49,6 +53,7 @@ services: ui: profiles: - ui + container_name: ui <<: *restart image: php-ui env_file: @@ -60,9 +65,10 @@ services: - ./ui/:/app/ - ./ui/php.ini:/usr/local/etc/php/conf.d/php.ini - ./logs/ui/php/:/var/log/php/ - ui-proxy: + ui_proxy: profiles: - ui + container_name: ui_proxy <<: *restart image: nginx ports: @@ -75,6 +81,7 @@ services: python: profiles: - python + container_name: python <<: *restart build: context: ./python @@ -91,6 +98,7 @@ services: console: profiles: - console + container_name: console <<: *restart build: context: ./console