Add product options and department categories API

Introduce endpoints for managing product options and department categories, including listing, creating, editing, and deleting functionalities. Updated related objects and traits to support new operations, including added array serialization methods and improved query handling for better flexibility.
This commit is contained in:
Jepp9350
2025-02-17 11:38:00 +01:00
parent 2151cf2b7b
commit 67e6bb8bf2
7 changed files with 503 additions and 3 deletions
@@ -80,4 +80,16 @@ class categories_o extends db
self::objectChanged();
return $this;
}
public function asArray(): array
{
return [
'id' => (int)$this->id,
'name' => (string)$this->name,
'description' => (string)$this->description,
'meta' => [
'product_count' => (int)(new products_o())->countRowsWhere(['category' => $this->id])
]
];
}
}
@@ -0,0 +1,111 @@
<?php
namespace objects;
use classes\db;
use classes\object_property;
use Exception;
use traits\db_object_t;
class department_categories_o extends db
{
use db_object_t;
public object_property $department_id;
public object_property $category_id;
public object_property $created_at;
public object_property $updated_at;
public object_property $deleted_at;
public function structure(): void
{
$this->setTable('department_categories');
}
/**
* Add a department category, and set this object to the new object
* @param int $department_id
* @param int $category_id
* @return void
* @throws Exception If the object was not created successfully
*/
public function add(int $department_id, int $category_id): void
{
// Make sure we don't add the same department category twice
if (count(self::getFieldsWhere(
[
'department_id' => $department_id,
'category_id' => $category_id,
'deleted_at' => null
], ['id'])) > 0) {
throw new Exception('Department category already exists');
}
$tmp_id = self::add_object([
'department_id' => $department_id,
'category_id' => $category_id
]);
$this->id = $tmp_id;
self::getObjectProperties();
self::objectChanged();
}
public function getObjectProperties(): void
{
$this->department_id = new object_property($this->table, $this->id, 'department_id', 'int', false);
$this->category_id = new object_property($this->table, $this->id, 'category_id', 'int', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'string', false);
$this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'string', false);
$this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'string', false);
}
public function objectChanged(): void
{
//TODO: Add cache invalidation
}
/**
* Get the categories for a department
* @param int $department_id
* @return array The categories for the department
*/
public function getCategoriesForDepartment(int $department_id, $parseFunction = null): array
{
$category_ids = self::getFieldsWhere(
[
'department_id' => $department_id,
'deleted_at' => null
],
['id', 'category_id']
);
$categories = [];
foreach ( $category_ids as $category_id ) {
$tmp = (new department_categories_o())->select($category_id['id'])->asArray();
$tmp['category'] = (new categories_o())->select($tmp['category_id']);
$categories[] = $tmp;
}
if ($parseFunction) {
$tmp = [];
foreach ( $categories as $category ) {
$tmp[] = $parseFunction($category);
}
$categories = $tmp;
}
return $categories;
}
public function asArray(): array
{
return [
'id' => (int)$this->id,
'department_id' => (int)$this->department_id->value(),
'category_id' => (int)$this->category_id->value(),
'created_at' => (string)$this->created_at->value(),
'updated_at' => (string)$this->updated_at->value(),
'deleted_at' => (string)$this->deleted_at->value()
];
}
}
@@ -0,0 +1,92 @@
<?php
namespace objects;
use classes\db;
use classes\object_property;
use Exception;
use traits\db_object_t;
class product_options_o extends db
{
use db_object_t;
public object_property $product_id;
public object_property $option_id;
public object_property $name;
public function structure(): void
{
$this->setTable('products_options');
}
/**
* Add a product option, and set this object to the new object
* @param int $product_id
* @param int $option_id
* @return void
* @throws Exception If the object was not created successfully
*/
public function add(int $product_id, int $option_id): void
{
$tmp_id = self::add_object([
'product_id' => $product_id,
'option_id' => $option_id
]);
$this->id = $tmp_id;
self::getObjectProperties();
self::objectChanged();
}
public function getObjectProperties(): void
{
$this->product_id = new object_property($this->table, $this->id, 'product_id', 'int', false);
$this->option_id = new object_property($this->table, $this->id, 'option_id', 'int', false);
$this->name = new object_property($this->table, $this->id, 'name', 'string', false);
}
public function objectChanged(): void
{
//TODO: Add cache invalidation
}
/**
* Set the name of the product option
* @param string $name
* @throws Exception If the object was not selected
*/
public function set_name(string $name): void
{
self::requireSelected();
$this->name->set($name);
self::objectChanged();
}
public function getProductOptions(int $id): array
{
$options = self::getFieldsWhere(['product_id' => $id],
[
'id',
'product_id',
'option_id',
'name',
'created_at',
'updated_at'
]
);
$tmp = [];
foreach ( $options as $option ) {
$tmp_product = (new products_o())->select($option['option_id'])->asArray();
$tmp[] = [
'id' => (int)$option['id'],
'product_id' => (int)$option['product_id'],
'option_id' => (int)$option['option_id'],
'name' => (string)$option['name'] === '' ? $tmp_product['name'] : (string)$option['name'],
'created_at' => (string)$option['created_at'],
'updated_at' => (string)$option['updated_at'],
'product' => $tmp_product
];
}
return $tmp;
}
}
@@ -3,6 +3,8 @@
namespace routes;
use classes\authentication;
use objects\categories_o;
use objects\department_categories_o;
use objects\departments_o;
use objects\logs_o;
use traits\route_t;
@@ -128,5 +130,135 @@ class departmentsRoute
$response->error('Invalid session', 400);
}
});
$this->get('/departments/categories', function () {
// Require the user to be logged in
global $response;
self::requirePermission('list_department_categories');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Require the department id
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::TYPE_INT());
// Get the department object
$department = (new departments_o())->select(self::getParameter('id'));
// Validate the department categories object
if (!$department->exists()) {
// Log the incident
(new logs_o())->add('departments', 'global', 1, $user->id, 'LIST_DEPARTMENT_CATEGORIES', 'Department categories not found');
// Return an error
$response->error('Department categories not found', 400);
}
// Get the department categories
$department_categories = new department_categories_o();
// Log the incident
(new logs_o())->add('departments', 'global', 1, $user->id, 'LIST_DEPARTMENT_CATEGORIES', 'Successfully listed department categories');
// Return the list of department categories
$response->success(
$department_categories
->getCategoriesForDepartment(self::getParameter('id'),
function ($department_category) {
return [
'id' => (int)$department_category['id'],
'department_id' => (int)$department_category['department_id'],
'category_id' => (int)$department_category['category_id'],
'created_at' => (string)$department_category['created_at'],
'updated_at' => $department_category['updated_at'],
'category' => $department_category['category']->asArray()
];
}
)
);
} else {
// Log the incident
(new logs_o())->add('departments', 'global', 1, 0, 'LIST_DEPARTMENT_CATEGORIES', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
});
$this->post('/departments/categories', function () {
// Require the user to be logged in
global $response;
self::requirePermission('add_department_category');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Require the parameters
self::requireParameters(['department_id', 'category_id']);
self::requireType((int)self::getParameter('department_id'), self::TYPE_INT());
self::requireType((int)self::getParameter('category_id'), self::TYPE_INT());
// Get the department object
$department = (new departments_o())->select(self::getParameter('department_id'));
// Validate the department object
if (!$department->exists()) {
// Log the incident
(new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Department not found');
// Return an error
$response->error('Department not found', 400);
}
// Get the category object
$category = (new categories_o())->select(self::getParameter('category_id'));
// Validate the category object
if (!$category->exists()) {
// Log the incident
(new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Category not found');
// Return an error
$response->error('Category not found', 400);
}
// Add the department category
$department_categories = new department_categories_o();
$department_categories->add(
self::getParameter('department_id'),
self::getParameter('category_id')
);
// Log the incident
(new logs_o())->add('departments', 'global', 1, $user->id, 'ADD_DEPARTMENT_CATEGORY', 'Successfully added a department category');
// Return a success message
$response->success(['message' => 'Department category added successfully']);
} else {
// Log the incident
(new logs_o())->add('departments', 'global', 1, 0, 'ADD_DEPARTMENT_CATEGORY', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
});
$this->delete('/departments/categories', function () {
// Require the user to be logged in
global $response;
self::requirePermission('delete_department_category');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Require the parameters
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::TYPE_INT());
// Get the department category object
$department_category = (new department_categories_o())->select(self::getParameter('id'));
// Validate the department category object
if (!$department_category->exists()) {
// Log the incident
(new logs_o())->add('departments', 'global', 1, $user->id, 'DELETE_DEPARTMENT_CATEGORY', 'Department category not found');
// Return an error
$response->error('Department category not found', 400);
}
// Delete the department category
$department_category->delete();
// Log the incident
(new logs_o())->add('departments', 'global', 1, $user->id, 'DELETE_DEPARTMENT_CATEGORY', 'Successfully deleted a department category');
// Return a success message
$response->success(['message' => 'Department category deleted successfully']);
} else {
// Log the incident
(new logs_o())->add('departments', 'global', 1, 0, 'DELETE_DEPARTMENT_CATEGORY', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
});
}
}
@@ -0,0 +1,123 @@
<?php
namespace routes;
use classes\authentication;
use objects\logs_o;
use objects\product_options_o;
use traits\route_t;
class productOptionsRoute
{
use route_t;
public function run(): void
{
$this->get('/product/options', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('list_product_options');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Log the incident
(new logs_o())->add('product_options', 'global', 1, $user->id, 'LIST_PRODUCT_OPTIONS', 'User listed product options');
// Return the list of departments
$response->success(
(new product_options_o())
->setSearchableFields([
// The fields that can be searched. This would otherwise make it possible to get secret information from the database, simply by searching for it and getting the result count back
'id',
'product_id',
'option_id',
])
->listObjectsWithPaginationIfSet(
function ($option) use ($user) {
// Return the object as an array
return [
'id' => (int)$option['id'],
'product_id' => (int)$option['product_id'],
'option_id' => (int)$option['option_id'],
'created_at' => (string)$option['created_at'],
'updated_at' => (string)$option['updated_at'],
];
}
)
);
} else {
// Log the incident
(new logs_o())->add('product_options', 'global', 1, 0, 'LIST_PRODUCT_OPTIONS', 'User tried to list product options without being logged in');
// Return an error
$response->error('Invalid session', 400);
}
});
$this->post('/product/options', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('add_product_option');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Check if the required parameters are set
self::requireParameters(['product_id', 'option_id']);
// Check if the parameters are of the correct type
self::requireType(self::getParameter('product_id'), self::TYPE_INT());
self::requireType(self::getParameter('option_id'), self::TYPE_INT());
// Create the object
$product_options = new product_options_o();
// Add the object
$product_options->add(
self::getParameter('product_id'),
self::getParameter('option_id')
);
// Log the incident
(new logs_o())->add('product_options', 'global', 1, $user->id, 'ADD_PRODUCT_OPTION', 'User added a product option');
// Return the object
$response->success(
$product_options->__toString()
);
} else {
// Log the incident
(new logs_o())->add('product_options', 'global', 1, 0, 'ADD_PRODUCT_OPTION', 'User tried to add a product option without being logged in');
// Return an error
$response->error('Invalid session', 400);
}
});
$this->put('/product/options', function () {
// Require the user to be logged in
global $response;
$this->requirePermission('edit_product_option');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Check if the required parameters are set
self::requireParameters(['id', 'name']);
// Check if the parameters are of the correct type
self::requireType(self::getParameter('id'), self::TYPE_INT());
// Create the object
$product_options = new product_options_o();
// Select the object
$product_options->select(self::getParameter('id'));
// Set the name
$product_options->set_name(self::getParameter('name'));
// Log the incident
(new logs_o())->add('product_options', 'global', 1, $user->id, 'EDIT_PRODUCT_OPTION', 'User edited a product option');
// Return the object
$response->success(
$product_options->__toString()
);
} else {
// Log the incident
(new logs_o())->add('product_options', 'global', 1, 0, 'EDIT_PRODUCT_OPTION', 'User tried to edit a product option without being logged in');
// Return an error
$response->error('Invalid session', 400);
}
});
}
}
+3 -1
View File
@@ -4,6 +4,7 @@ namespace routes;
use classes\authentication;
use objects\logs_o;
use objects\product_options_o;
use objects\products_o;
use traits\route_t;
@@ -26,7 +27,8 @@ class productsRoute
'economic_product_id' => (int)$product['economic_product_id'],
'apply_category_discount' => (int)$product['apply_category_discount'],
'created_at' => (string)$product['created_at'],
'updated_at' => (string)$product['updated_at']
'updated_at' => (string)$product['updated_at'],
'addons' => (new product_options_o())->getProductOptions($product['id'])
];
}
+30 -2
View File
@@ -6,10 +6,12 @@ use classes\db;
use classes\response;
use Exception;
use objects\bookings_o;
use objects\categories_o;
use objects\cron_o;
use objects\customer_codes_o;
use objects\customer_notes_o;
use objects\customer_vehicles_o;
use objects\department_categories_o;
use objects\departments_o;
use objects\economic_module_orders;
use objects\logs_o;
@@ -17,6 +19,7 @@ use objects\order_items_o;
use objects\orders_o;
use objects\plate_scanners_o;
use objects\plate_scans_o;
use objects\product_options_o;
use objects\products_o;
use objects\ratelimit_o;
use objects\tokens_o;
@@ -29,6 +32,7 @@ trait db_object_t
public int $id; // The id of the object in the database
private string $table; // The table of the objects in the database (e.g. users)
private array $searchableFields = []; // The fields to search in the database (e.g. ['name', 'email']). If empty, all fields will be searched
private array $whereClauses = []; // The where clauses to add to the pagination query
public function __construct()
{
@@ -64,7 +68,12 @@ trait db_object_t
$table = $this->table;
$where = [];
foreach ( $fieldsAndValues as $field => $value ) {
$where[] = "$field = '$value'";
// If the value is null, add a where clause to check if the field is null
if ($value === null) {
$where[] = "$field IS NULL";
} else {
$where[] = "$field = '$value'";
}
}
$where = implode(' AND ', $where);
$sql = "SELECT " . implode(', ', $fields) . " FROM $table WHERE $where";
@@ -83,6 +92,18 @@ trait db_object_t
return $this;
}
/**
* Add a where clause to the pagination query
* @param string $field
* @param mixed $value
* @return users_o|bookings_o|categories_o|cron_o|customer_codes_o|customer_notes_o|customer_vehicles_o|department_categories_o|departments_o|economic_module_orders|logs_o|order_items_o|orders_o|plate_scanners_o|plate_scans_o|product_options_o|products_o|ratelimit_o|tokens_o|user_key_value_pairs_o|user_price_overrides_o|db_object_t
*/
public function addWhereClause(string $field, mixed $value): self
{
$this->whereClauses[] = "$field = '$value'";
return $this;
}
public function __toString(): string
{
// Return the object as a string
@@ -111,6 +132,7 @@ trait db_object_t
/**
* List objects with pagination (if set)
* @return array The list of objects in the table
* @throws Exception
*/
public function listObjectsWithPaginationIfSet($parseFunction = null): array
{
@@ -177,6 +199,11 @@ trait db_object_t
$whereClauses = [];
$params = [];
// Add where clauses from the object, if set. This is done to allow for custom where clauses in routes, while still allowing for pagination
if (!empty($this->whereClauses)) {
$whereClauses = $this->whereClauses;
}
// Search clause
if (!empty($search)) {
// Use prepared statements to prevent SQL injection
@@ -593,7 +620,8 @@ trait db_object_t
$id = $this->id;
$sql = "SELECT * FROM $this->table WHERE id = $id";
$result = $db->query($sql);
return $result->num_rows > 0;
// Check if the object exists, and if there's a deleted_at column, check if the object is not deleted
return $db->num_rows($result) > 0 && (!$this->columnsExist(['deleted_at']) || $db->fetch_assoc($result)['deleted_at'] === null);
}
/**