apptank/horusync

Data synchronization between mobile devices and a Laravel application

v0.3.7 2025-03-27 16:44 UTC

README

Horusync Logo
Build Status Latest Stable Version License

Please note: This library currently is in testing stage until publish the version 1.0.0.

Horus Sync

This library provides an easy way to manage data synchronization between a remote database on a server and a local database on a mobile device. It defines a set of synchronizable entities by specifying their parameters and relationships with other entities.

Features

  • Data Synchronization: Enables synchronization of the defined entities' data between the local database and the remote database.
  • Schema Migration: Allows retrieval of the schema for synchronizable entities and their relationships.
  • Integrity Validation: Ensures the integrity of synchronized data.
  • Authentication and Permissions: Defines an authenticated user and the permissions associated with the entities.
  • Upload Files: Allows uploading files to the server.
  • Middlewares: Supports defining custom middlewares for synchronization routes.
  • UUID Support: Allows specifying whether UUIDs or Int will be used as the primary key for entities.
  • Foreign Key Support: Supports defining foreign keys in synchronizable entities with cascading delete functionality.
  • UserActingAs Support: Indicates if a user has permission to act as another user and access the entities where permissions have been granted.
  • Support for Synchronizable Entity Types: Supports defining entities as writable or readable, depending on whether they can be edited or not.

📦 Installation

This library must be used with Laravel version 11 or higher.

Use the following command to install the package using Composer:

composer require apptank/horusync

🔨 Usage

All synchronizable models with editable records must inherit from WritableEntitySynchronizable, defining the properties they contain. If you want a read-only entity, you should inherit from ReadableEntitySynchronizable.

You can use an artisan command to generate a synchronizable model for you. You just need to specify the path where the model should be stored. The following example will create a model in App/Models/Sync/MyModelSync:

php artisan horus:entity MyModelSync
namespace App\Models\Sync;

use AppTank\Horus\Core\Entity\SyncParameter;
use AppTank\Horus\Illuminate\Database\WritableEntitySynchronizable;

class MyModel extends WritableEntitySynchronizable
{
    public static function parameters(): array
    {
        return [
            // Define the parameters of the entity
            SyncParameter::createString("name", 1),
            SyncParameter::createInt("age", 1),
            SyncParameter::createPrimaryKeyString("email", 1),
            SyncParameter::createTimestamp("datetime", 1),
            SyncParameter::createBoolean("active", 1),
            SyncParameter::createFloat("price", 1),
            SyncParameter::createRelationOneToMany("children", [ChildModel::class], 1)
        ];
    }

    public static function getEntityName(): string
    {
        return "my_model";
    }

    public static function getVersionNumber(): int
    {
        return 1;
    }
}

If you want to create a read-only entity, you can add the --readable option to the artisan command.

php artisan horus:entity MyModelSync --readable

🚀 Quick Start

1. Initialization

It is necessary to initialize the Horus container in the AppServiceProvider of Laravel by passing an array with the hierarchical map of entities.

You can optionally

  • Define the database connection name
  • Whether UUIDs or Int will be used as the primary key.
  • Define a prefix for the tables of entities in the database.
  • Enable or disable the validation of access to entities. Improve performance by disabling this option.
  • Prefix for the tables of entities in the database.
  • Define the entity restrictions.
class AppServiceProvider extends ServiceProvider
{

    public function boot(): void
    {
       // Define the hierarchical map of entities
       $entityMap = [
            MyModel::class => [
                ChildModel::class
            ]
       ];
        
       // Define the configuration
       $config = new Config(
            validateAccess: true,
            connectionName: "sync_database",
            usesUUIDs: true,
            prefixTables: 'hs',
            entityRestrictions: [
                new MaxCountEntityRestriction("entity_name", maxCount: 10)
            ],
        );
       
         // Initialize Horus
        Horus::initialize($entityMap)->setConfig($config);
    }

}

2. Run Migrations

Once the Horus container has been initialized, you need to run the synchronization tables migrations.

php artisan migrate

⬆️ Enable upload files

To enable the upload files feature, you need setup a file handler in horus initialization, implementing the IFileHandler interface.

Horus::setFileHandler($this->app->make(IFileHandler::class));

In your writable entity synchronizable model, you can define the file attribute using the SyncParameter::createReferenceFile method.

class UserImageModel extends WritableEntitySynchronizable
{
    public static function parameters(): array
    {
        return [
            // User Id
            SyncParameter::createUUIDForeignKey("user_id", 1,"users"),
            // Reference to the image file
            SyncParameter::createReferenceFile("image", 1),
        ];
    }
}

Local File Handler example

The following example shows how to implement a file handler for local file storage using Laravel's storage system.

class LocalFileHandler implements IFileHandler
{
    /**
     * @throws \Exception
     */
    function upload(int|string $userOwnerId, string $fileId, string $path, UploadedFile $file): FileUploaded
    {
        $filename = basename($path);
        $basePath = dirname($path);

        $pathResult = $file->storeAs($basePath, $filename);

        if (!$pathResult) {
            throw new \Exception("Error uploading file");
        }

        $mimeType = $file->getMimeType();
        $publicUrl = $this->generateUrl($pathResult);

        return new FileUploaded(
            $fileId,
            $mimeType,
            $pathResult,
            $publicUrl,
            $userOwnerId
        );
    }

    function delete(string $pathFile): bool
    {
        return Storage::delete($pathFile);
    }

    function getMimeTypesAllowed(): array
    {
        return MimeType::IMAGES;
    }

    function copy(string $pathFrom, string $pathTo): bool
    {
        return Storage::copy($pathFrom, $pathTo);
    }

    function generateUrl(string $path): string
    {
        return Storage::url($path);
    }
}

Prune files

If you want to delete files that are no longer referenced by any entity, you can use the following command:

php artisan horus:prune --expirationDays=7
  • expirationDays: The number of days after which files will be deleted.

↔️ Middlewares

If you want to implement custom middlewares for the synchronization routes, you can do so in your Service Provider as follows:

Horus::setMiddleware([MyMiddleware::class,'throttle:60,1']);

🔒 Authentication and Permissions

The following code shows how to configure user authentication and the permissions associated with entities. Using the UserAuth class, you define an authenticated user along with the entities they have access to and the corresponding permissions.

Horus::getInstance()->setUserAuthenticated(
 new UserAuth(
   "07a35af0-7317-41e4-99a3-e3583099aff2", // User Id Authenticated
   [ // Array of Entities Granted
   new EntityGranted(
   "971785f7-0f01-46cd-a3ce-af9ce6273d3d", // User Owner Id
   "entity_name", // Entity Name
    "9135e859-b053-4cfb-b701-d5f240b0aab1", // Entity Id
    // Set the permissions for the entity
   , new AccessLevel(Permission::READ, Permission::CREATE)),
    // User Acting As
   new UserAuth("b253a0e8-027b-463c-b87a-b18f09c99ddd")
   ]
 )
);

⛔ Entity Restrictions

If you want to apply a restriction to an entity, you can use the next restriction classes:

  • MaxCountEntityRestriction: Restricts the number of records that can be synchronized for an entity to specific user.

Setup after initialization

By default, the setup is set the Config class in the Horus initialization, but if you want to change the restrictions after you can use the next code:

Horus::getInstance()->setEntityRestrictions([
    new MaxCountEntityRestriction("entity_name", maxCount: 10)
]);

✉️ Events

If you want to listen to events that occur during synchronization, you can use the following code:

Insert Event

Event::listen("horus.sync.insert", function (string $entityName, array $data) {
    // Your code here
});

Update Event

Event::listen("horus.sync.update", function (string $entityName, array $data) {
    // Your code here
});

Delete Event

Event::listen("horus.sync.delete", function (string $entityName, array $data) {
    // Your code here
});