Implement attachments module with CRUD operations and integration points
- Added `attachments` module for creating, reading, updating, and deleting attachments. - Introduced new classes such as `attachments`, `attachment_store`, `attachment_content`, and `attachment_relation` to handle attachment operations and their relationships. - Integrated `attachments` features into `db_object_t` for seamless object-attachment interactions. - Added `attachments_i` interface for standardized attachments module operations. - Created `attachmentsRoute` for defining endpoints related to attachments. - Enabled module configuration through `attachments_enabled_c` for attachment management control.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace classes;
|
||||
|
||||
use interfaces\minio_uploads_i;
|
||||
use traits\minio_t;
|
||||
|
||||
class attachment_store implements minio_uploads_i
|
||||
{
|
||||
use minio_t;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
self::setBucket('attachments'); // Change the bucket to 'pdfs'
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function generatePresignedUrl(string $objectName, int $expiry = 3600): string
|
||||
{
|
||||
// Generate a presigned URL for the given object name with the specified expiry time
|
||||
return self::getPresignedUrl($objectName, $expiry, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isValidFileName(string $fileName): bool
|
||||
{
|
||||
// Check if the file name is valid
|
||||
return preg_match('/^[a-zA-Z0-9_\-.]+$/', $fileName) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isValidFilePath(string $filePath): bool
|
||||
{
|
||||
// Check if the file path is valid
|
||||
return preg_match('/^[a-zA-Z0-9_\-\/.]+$/', $filePath) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requireValidFileName(string $fileName): void
|
||||
{
|
||||
if (!$this->isValidFileName($fileName)) {
|
||||
throw new \InvalidArgumentException("Invalid file name: $fileName");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function requireValidFilePath(string $filePath): void
|
||||
{
|
||||
if (!$this->isValidFilePath($filePath)) {
|
||||
throw new \InvalidArgumentException("Invalid file path: $filePath");
|
||||
}
|
||||
}
|
||||
|
||||
public function isFileInStore(string $fileName): bool
|
||||
{
|
||||
// Check if the file exists in the store
|
||||
return self::doesObjectExist($fileName);
|
||||
}
|
||||
|
||||
public function download(string $file): string
|
||||
{
|
||||
$path = '/tmp/' . $file;
|
||||
$result = self::getS3Client()->getObject([
|
||||
'Bucket' => self::getBucket(),
|
||||
'Key' => $file,
|
||||
'SaveAs' => $path
|
||||
]);
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function generateDirectDownloadUrl(string $fileName): string
|
||||
{
|
||||
// Generate a direct download URL for the given file name
|
||||
return 'https://api.truckwash.dk:4433/files/' . $fileName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace classes;
|
||||
require_once WD . '/modules/attachments/attachments_c.php';
|
||||
|
||||
use attachments\helpers\attachment_content;
|
||||
use Exception;
|
||||
use interfaces\attachments_i;
|
||||
use attachments\helpers\attachment;
|
||||
use attachments\attachments_c;
|
||||
use objects\object_attachments_o;
|
||||
|
||||
class attachments implements attachments_i
|
||||
{
|
||||
/**
|
||||
* The configuration of the module
|
||||
* @var attachments_c $config
|
||||
*/
|
||||
public attachments_c $config;
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = new attachments_c();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function list(string $type, int $object_id, array $options = []): array
|
||||
{
|
||||
if (count($options) === 0) {
|
||||
$options = ['id', 'content', 'created_at', 'object_id', 'object_type', 'created_at', 'updated_at']; // Default fields to return
|
||||
}
|
||||
return array_map(function ($item) {
|
||||
$item = (object)$item;
|
||||
$item->content = json_decode($item->content, true); // Decode JSON content
|
||||
return (new attachment())->populate((object)$item);
|
||||
}, (new object_attachments_o())->getFieldsWhere([
|
||||
'object_type' => $type,
|
||||
'object_id' => $object_id,
|
||||
'deleted_at' => null
|
||||
], $options));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception If the attachment is not found
|
||||
*/
|
||||
public function get(int $attachment_id): ?object_attachments_o
|
||||
{
|
||||
return (new object_attachments_o())->select($attachment_id) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception If the attachment cannot be created
|
||||
*/
|
||||
public function create(string $type, int $object_id, attachment_content $attachment_content): bool
|
||||
{
|
||||
$object_attachments_o = new object_attachments_o();
|
||||
$object_attachments_o->add([
|
||||
'object_type' => $type,
|
||||
'object_id' => $object_id,
|
||||
'content' => json_encode($attachment_content->toArray()), // Store as JSON
|
||||
]);
|
||||
return (bool)$object_attachments_o->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception If the attachment cannot be deleted
|
||||
*/
|
||||
public function delete(int $attachment_id): bool
|
||||
{
|
||||
$object_attachments_o = new object_attachments_o();
|
||||
if ($object_attachments_o->select($attachment_id)) {
|
||||
$object_attachments_o->delete();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws Exception If the attachment cannot be updated
|
||||
*/
|
||||
public function update(int $attachment_id, attachment_content $attachment_content): bool
|
||||
{
|
||||
$object_attachments_o = new object_attachments_o();
|
||||
if ($object_attachments_o->select($attachment_id)) {
|
||||
$object_attachments_o->content->set(json_encode($attachment_content->toArray()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,8 @@ require_once 'classes/openai.php';
|
||||
require_once 'classes/licenseplaterecognizer.php';
|
||||
require_once 'classes/image_processor.php';
|
||||
require_once 'classes/upload_store.php';
|
||||
require_once 'classes/attachments.php';
|
||||
require_once 'classes/attachment_store.php';
|
||||
|
||||
/**
|
||||
* Modules
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace interfaces;
|
||||
|
||||
use attachments\helpers\attachment_content;
|
||||
use objects\object_attachments_o;
|
||||
|
||||
interface attachments_i
|
||||
{
|
||||
/**
|
||||
* List attachments for a given entity.
|
||||
* @param string $type The type of the entity parent. (E.g. users_o, groups_o, etc.)
|
||||
* @param int $object_id The ID of the entity.
|
||||
* @param array $options Optional parameters for filtering or modifying the retrieval.
|
||||
* @return object_attachments_o[] An array of attachment objects.
|
||||
*/
|
||||
public function list(string $type, int $object_id, array $options = []): array;
|
||||
/**
|
||||
* Get an attachment by its ID.
|
||||
* @param int $attachment_id The ID of the attachment to be retrieved.
|
||||
* @return array|null An associative array representing the attachment, or null if not found.
|
||||
*/
|
||||
public function get(int $attachment_id): ?object_attachments_o;
|
||||
/**
|
||||
* Add an attachment to a given entity.
|
||||
* @param string $type The type of the entity parent. (E.g. users_o, groups_o, etc.)
|
||||
* @param int $object_id The ID of the entity.
|
||||
* @param attachment_content $attachment_content The content of the attachment to be added.
|
||||
* @return bool True on success, false on failure.
|
||||
* @see attachment_content
|
||||
*/
|
||||
public function create(string $type, int $object_id, attachment_content $attachment_content): bool;
|
||||
/**
|
||||
* Delete an attachment by its ID.
|
||||
* @param int $attachment_id The ID of the attachment to be deleted.
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function delete(int $attachment_id): bool;
|
||||
/**
|
||||
* Update an existing attachment.
|
||||
* @param int $attachment_id The ID of the attachment to be updated.
|
||||
* @param attachment_content $attachment_content The new content for the attachment.
|
||||
* @return bool True on success, false on failure.
|
||||
* @see attachment_content
|
||||
*/
|
||||
public function update(int $attachment_id, attachment_content $attachment_content): bool;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace attachments;
|
||||
require_once WD . '/modules/attachments/config/attachments_enabled_c.php';
|
||||
require_once WD . '/modules/attachments/helpers/attachment_types.php';
|
||||
require_once WD . '/modules/attachments/helpers/attachment_relation.php';
|
||||
require_once WD . '/modules/attachments/helpers/attachment_content.php';
|
||||
require_once WD . '/modules/attachments/helpers/attachment.php';
|
||||
|
||||
|
||||
use attachments\config\attachments_enabled_c;
|
||||
use traits\module_config_t;
|
||||
|
||||
class attachments_c
|
||||
{
|
||||
use module_config_t;
|
||||
|
||||
/**
|
||||
* The status of attachments, whether it is enabled or not
|
||||
* @var attachments_enabled_c
|
||||
*/
|
||||
public attachments_enabled_c $enabled;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setupConfig('attachments');
|
||||
$this->allowUpdate([
|
||||
attachments_enabled_c::class,
|
||||
]);
|
||||
$this->enabled = new attachments_enabled_c();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace attachments\config;
|
||||
|
||||
use Exception;
|
||||
use traits\module_config_variable;
|
||||
|
||||
class attachments_enabled_c
|
||||
{
|
||||
use module_config_variable;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
self::setupConfigVariable(
|
||||
'attachments',
|
||||
'enabled',
|
||||
'bool',
|
||||
true,
|
||||
null,
|
||||
'Whether attachments are enabled',
|
||||
'1',
|
||||
false,
|
||||
'false'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace attachments\helpers;
|
||||
|
||||
use objects\object_attachments_o;
|
||||
|
||||
class attachment
|
||||
{
|
||||
public ?int $id;
|
||||
public ?string $object_type;
|
||||
public ?int $object_id;
|
||||
public attachment_content $content;
|
||||
public ?string $created_at;
|
||||
public ?string $updated_at;
|
||||
public ?string $deleted_at;
|
||||
|
||||
public function __construct(?object_attachments_o $object_attachments_o = null)
|
||||
{
|
||||
// If no object is provided, initialize with default values
|
||||
if ($object_attachments_o) {
|
||||
$decodedContent = (object)json_decode($object_attachments_o->content->value(), true);
|
||||
$this->populate((object)[
|
||||
// Convert object_attachments_o properties to the expected types
|
||||
'id' => $object_attachments_o->id,
|
||||
'object_type' => $object_attachments_o->object_type->value(),
|
||||
'object_id' => $object_attachments_o->object_id->value(),
|
||||
'content' => $decodedContent,
|
||||
'created_at' => $object_attachments_o->created_at,
|
||||
'updated_at' => $object_attachments_o->updated_at,
|
||||
'deleted_at' => $object_attachments_o->deleted_at,
|
||||
]);
|
||||
}
|
||||
}
|
||||
public function populate(object $object): self
|
||||
{
|
||||
// If the object is provided, populate the properties
|
||||
if ($object) {
|
||||
$this->id = isset($object->id) ? (int)$object->id : null;
|
||||
$this->object_type = isset($object->object_type) ? (string)$object->object_type : null;
|
||||
$this->object_id = isset($object->object_id) ? (int)$object->object_id : null;
|
||||
$this->content = isset($object->content) ? new attachment_content((object)$object->content) : null;
|
||||
$this->created_at = isset($object->created_at) ? (string)$object->created_at : null;
|
||||
$this->updated_at = isset($object->updated_at) ? (string)$object->updated_at : null;
|
||||
$this->deleted_at = isset($object->deleted_at) ? (string)$object->deleted_at : null;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace attachments\helpers;
|
||||
|
||||
class attachment_content
|
||||
{
|
||||
public ?string $image; // Used to store the attachment object name, in the attachment store.
|
||||
public ?string $document; // Used to store the attachment object name, in the attachment store.
|
||||
public ?attachment_relation $relation; // Used to store the attachment relation object.
|
||||
public mixed $other; // Used to store the attachment object. (if any other type)
|
||||
|
||||
public function __construct(?object $data = null)
|
||||
{
|
||||
$this->image = $data->image ?? null;
|
||||
$this->document = $data->document ?? null;
|
||||
$this->relation = isset($data->relation) ? new attachment_relation($data->relation) : null;
|
||||
$this->other = $data->other ?? null;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'image' => $this->image,
|
||||
'document' => $this->document,
|
||||
'relation' => $this->relation ? $this->relation->toArray() : null,
|
||||
'other' => $this->other,
|
||||
];
|
||||
}
|
||||
|
||||
/** Setters */
|
||||
public function setOther(mixed $string): self
|
||||
{
|
||||
$this->other = $string;
|
||||
return $this;
|
||||
}
|
||||
public function setImage(string $string): self
|
||||
{
|
||||
$this->image = $string;
|
||||
return $this;
|
||||
}
|
||||
public function setDocument(string $string): self
|
||||
{
|
||||
$this->document = $string;
|
||||
return $this;
|
||||
}
|
||||
public function setRelation(attachment_relation $relation): self
|
||||
{
|
||||
$this->relation = $relation;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace attachments\helpers;
|
||||
|
||||
class attachment_relation
|
||||
{
|
||||
public string $object_type; // Type of the object (e.g., 'users_o', 'orders_o', etc.)
|
||||
public int $object_id; // ID of the object
|
||||
public function __construct(?object $options = null)
|
||||
{
|
||||
if ($options) {
|
||||
foreach ($options as $key => $value) {
|
||||
if (property_exists($this, $key)) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'object_type' => $this->object_type,
|
||||
'object_id' => $this->object_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace attachments\helpers;
|
||||
|
||||
enum attachment_types
|
||||
{
|
||||
case IMAGE; // image file
|
||||
case DOCUMENT; // document file (pdf, docx, txt, etc.)
|
||||
case RELATION; // relation to another object
|
||||
case OTHER; // anything else
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace objects;
|
||||
|
||||
use classes\db;
|
||||
use classes\object_property;
|
||||
use classes\xlvask;
|
||||
use Exception;
|
||||
use helpers\xlvask_customer;
|
||||
use helpers\xlvask_vehicle;
|
||||
use traits\db_object_t;
|
||||
|
||||
class object_attachments_o extends db
|
||||
{
|
||||
use db_object_t;
|
||||
|
||||
public object_property $object_type; // The object type (e.g. 'users_o', 'customers_o', etc.)
|
||||
public object_property $object_id; // The ID of the object this attachment is linked to
|
||||
public object_property $content; // The content of the attachment (JSON formatted)
|
||||
public object_property $created_at; // The timestamp when the attachment was created
|
||||
public object_property $updated_at; // The timestamp when the attachment was last updated
|
||||
public object_property $deleted_at; // The timestamp when the attachment was deleted (if applicable)
|
||||
|
||||
public function structure(): void
|
||||
{
|
||||
$this->setTable('object_attachments');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new attachment
|
||||
* @param array $data The properties
|
||||
* @returns void
|
||||
* @throws Exception If the object was not created successfully
|
||||
*/
|
||||
public function add(array $data): void
|
||||
{
|
||||
$tmp_id = self::add_object($data);
|
||||
$this->id = $tmp_id;
|
||||
self::getObjectProperties();
|
||||
self::objectChanged();
|
||||
}
|
||||
|
||||
public function getObjectProperties(): void
|
||||
{
|
||||
$this->object_type = new object_property($this->table, $this->id, 'object_type', 'string', false);
|
||||
$this->object_id = new object_property($this->table, $this->id, 'object_id', 'number', false);
|
||||
$this->content = new object_property($this->table, $this->id, 'content', 'string', 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace routes;
|
||||
|
||||
use attachments\helpers\attachment;
|
||||
use attachments\helpers\attachment_content;
|
||||
use classes\attachments;
|
||||
use objects\orders_o;
|
||||
use traits\route_t;
|
||||
|
||||
class attachmentsRoute
|
||||
{
|
||||
use route_t;
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->get('/attachments/example', function () {
|
||||
global $response;
|
||||
throw new \Exception('EXAMPLE ROUTE, SHOULD BE IMPLEMENTED IN THE INDIVIDUAL OBJECT ROUTES');
|
||||
$orders_o = new orders_o();
|
||||
$orders_o->select(22636);
|
||||
// Debug: Create an attachment
|
||||
$orders_o->addAttachment((new attachment_content())->setOther('Hello World!'));
|
||||
$response->success($orders_o->listAttachments());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace traits;
|
||||
|
||||
use attachments\helpers\attachment_content;
|
||||
use classes\attachments;
|
||||
use classes\db;
|
||||
use classes\response;
|
||||
use Exception;
|
||||
@@ -29,6 +31,7 @@ use objects\logs_o;
|
||||
use objects\module_action_logs_o;
|
||||
use objects\motorapi_lookups_o;
|
||||
use objects\notifications_o;
|
||||
use objects\object_attachments_o;
|
||||
use objects\order_items_o;
|
||||
use objects\orders_o;
|
||||
use objects\plate_scanners_o;
|
||||
@@ -1144,4 +1147,56 @@ trait db_object_t
|
||||
self::objectChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an attachment to the (current) object
|
||||
* @param attachment_content $content The content of the attachment
|
||||
* @throws Exception If the object is not selected, it throws an exception
|
||||
*/
|
||||
public function addAttachment(attachment_content $content): void
|
||||
{
|
||||
self::requireSelected();
|
||||
// Add the attachment to the object
|
||||
$attachment = new attachments();
|
||||
$attachment->create($this->table, $this->id, $content);
|
||||
}
|
||||
/**
|
||||
* List attachments of the (current) object
|
||||
* @return object_attachments_o[] The list of attachments of the object
|
||||
* @throws Exception If the object is not selected, it throws an exception
|
||||
*/
|
||||
public function listAttachments(): array
|
||||
{
|
||||
self::requireSelected();
|
||||
// List the attachments of the object
|
||||
$attachment = new attachments();
|
||||
return $attachment->list($this->table, $this->id);
|
||||
}
|
||||
/**
|
||||
* Remove an attachment
|
||||
* @param int $attachmentId The id of the attachment to remove
|
||||
* @throws Exception If the object is not selected, it throws an exception
|
||||
*/
|
||||
public function removeAttachment(int $attachmentId): void
|
||||
{
|
||||
self::requireSelected();
|
||||
// Remove the attachment from the object
|
||||
$attachment = new attachments();
|
||||
$attachment->delete($attachmentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attachment
|
||||
* @param int $attachmentId The id of the attachment to get
|
||||
* @return object_attachments_o The attachment object
|
||||
* @throws Exception If the object is not selected, it throws an exception
|
||||
* @throws Exception If the attachment does not exist, it throws an exception
|
||||
*/
|
||||
public function getAttachment(int $attachmentId): object_attachments_o
|
||||
{
|
||||
self::requireSelected();
|
||||
// Get the attachment from the object
|
||||
$attachment = new attachments();
|
||||
return $attachment->get($attachmentId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user