-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 02bb843
Showing
26 changed files
with
2,314 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/vendor | ||
.env | ||
.phpunit.result.cache | ||
composer.phar | ||
composer.lock | ||
phpunit.xml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright 2021 Nathan Heffley | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
# Laravel Watermelon | ||
|
||
This package provides a [Watermelon DB](https://nozbe.github.io/WatermelonDB/) backend sync implementation for Laravel. | ||
Watermelon DB is a robust local database synchronization tool to help develop offline-first application. One of the | ||
biggest hurdles is implementing the logic on your server to handle the synchronization process. | ||
|
||
That's where this package comes in to provide a quick synchronization route you can get up and running in minutes. | ||
|
||
> __This project is still in active development__ and does not support schema versions or migrations yet. Both of these | ||
> are major parts of the Watermelon DB spec. Please expect large changes at least until those features are implemented. | ||
## Installation | ||
|
||
Before getting started you'll need to install the package and publish the config file. | ||
|
||
``` | ||
composer require nathanheffley/laravel-watermelon | ||
``` | ||
|
||
``` | ||
php artisan vendor:publish --tag="watermelon-config" | ||
``` | ||
|
||
## Usage | ||
|
||
Once you've installed the package, you need to specify which models will be available through the synchronization | ||
endpoint. Open up the `config/watermelon.php` file and update the `models` array. The key needs to be the name of table | ||
used locally in your application, and the value must be classname of the related model. | ||
|
||
You can also change the route to be something other than `/sync` by editing the config file or setting the | ||
`WATERMELON_ROUTE` environment variable. You will have to ensure your application makes synchronization requests to | ||
whatever route you specify. | ||
|
||
By default, only your global middleware will be applied to the synchronization endpoint. This means that unless you changed | ||
the default global middleware in your Laravel project the synchronization endpoint will be unauthenticated. If you want to | ||
have access to the currently authenticated user you will need to add the `web` middleware to the config file's | ||
`middleware` array. If you want to restrict access to the synchronization endpoint to authenticated users only, you can | ||
add the `auth` middleware in addition to the `web` middleware. Of course, you can add any middleware you would like as | ||
long as it's registered in your project. | ||
|
||
```php | ||
<?php | ||
|
||
use App\Models\Project; | ||
use App\Models\Task; | ||
|
||
return [ | ||
|
||
'route' => env('WATERMELON_ROUTE', '/watermelon'), | ||
|
||
'middleware' => [ | ||
'web', | ||
'auth', | ||
], | ||
|
||
'models' => [ | ||
'projects' => Project::class, | ||
'tasks' => Task::class, | ||
], | ||
|
||
]; | ||
``` | ||
|
||
Once you've specified which models should be available through the synchronization endpoint, you'll need to implement | ||
some functionality in the models to support being served as Watermelon change objects. | ||
|
||
You will need to add a database column to all of your models to keep track of what the Watermelon ID is, called | ||
`watermelon_id`. The default IDs generated by Watermelon are alphanumeric strings, although you can change the type of | ||
the column if you don't use the default IDs autogenerated by Watermelon. A unique index on the column is recommended, | ||
and I like placing it directly after the `id` column. | ||
|
||
```php | ||
<?php | ||
|
||
use Illuminate\Database\Migrations\Migration; | ||
use Illuminate\Database\Schema\Blueprint; | ||
use Illuminate\Support\Facades\Schema; | ||
|
||
class AddWatermelonIdToTasksTable extends Migration | ||
{ | ||
public function up() | ||
{ | ||
Schema::table('tasks', function (Blueprint $table) { | ||
$table->string('watermelon_id')->after('id')->unique(); | ||
}); | ||
} | ||
|
||
public function down() | ||
{ | ||
Schema::table('tasks', function (Blueprint $table) { | ||
$table->dropColumn('watermelon_id'); | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
If you did not originally include the `created_at`, `updated_at`, and `deleted_at` timestamp columns, you will also need | ||
to add those columns. Please refer to the Laravel documentation for implementing the | ||
[timestamps](https://laravel.com/docs/8.x/eloquent#timestamps) and | ||
[soft deleting](https://laravel.com/docs/8.x/eloquent#soft-deleting) functionality. | ||
|
||
The only thing you __must__ change in your model class is to use the `Watermelon` trait. | ||
|
||
```php | ||
<?php | ||
|
||
namespace App\Models; | ||
|
||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Database\Eloquent\SoftDeletes; | ||
use NathanHeffley\LaravelWatermelon\Traits\Watermelon; | ||
|
||
class Task extends Model | ||
{ | ||
use SoftDeletes, Watermelon; | ||
} | ||
``` | ||
|
||
The attributes returned through the synchronization endpoint are whitelisted by default. Out of the box, only the | ||
`watermelon_id` will be returned (although it will be passed through the endpoint as just `id`, this is intentional). To | ||
include more attributes, you will need to add a `watermelonAttributes` property to your class. These attributes will be | ||
included alongside the Watermelon ID and can be updated by change objects posted to the synchronization endpoint. | ||
|
||
```php | ||
class Task extends Model | ||
{ | ||
use SoftDeletes, Watermelon; | ||
|
||
protected array $watermelonAttributes = [ | ||
'content', | ||
'is_completed', | ||
]; | ||
} | ||
``` | ||
|
||
If you need more control over the attributes returned you can override the entire `toWatermelonArray` function. | ||
|
||
## Authorization | ||
|
||
By default, all models will be accessible and able to be updated through the synchronization endpoint. This is obviously | ||
not ideal in most projects where you need control to authorize which models users can see and what they can update. | ||
|
||
### Scoping entire records | ||
|
||
You can implement a query scope on your models by overriding the `scopeWatermelon` function. This scope will be applied | ||
before returning data to pull requests. This can be used, for example, to restrict records to only be retrievable by | ||
users authorized to see them (to have access to the `Auth::user()` like in this example, don't forget to add the `web` | ||
and `auth` middlewares as shown in the config example at the start of the Usage section). | ||
|
||
```php | ||
use Illuminate\Support\Facades\Auth; | ||
|
||
class Task extends Model | ||
{ | ||
// ... | ||
|
||
public function scopeWatermelon($query) | ||
{ | ||
return $query->where('user_id', Auth::user()->id); | ||
} | ||
} | ||
``` | ||
|
||
## Package Development | ||
|
||
If you have PHP and Composer installed on your local machine you should be able to easily run the PHPUnit test suite. | ||
|
||
If you prefer to run the tests within a Docker container, this project includes | ||
[Laravel Sail](https://laravel.com/docs/8.x/sail). | ||
|
||
To install the dependencies: | ||
|
||
``` | ||
docker run --rm \ | ||
-u "$(id -u):$(id -g)" \ | ||
-v $(pwd):/opt \ | ||
-w /opt \ | ||
laravelsail/php80-composer:latest \ | ||
composer install --ignore-platform-reqs | ||
``` | ||
|
||
To run the test suite: | ||
|
||
``` | ||
sail exec laravel.test ./vendor/bin/phpunit | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
"name": "nathanheffley/laravel-watermelon", | ||
"type": "library", | ||
"description": "Easily set up a sync endpoint to support Watermelon DB in your Laravel projects.", | ||
"keywords": ["package", "laravel", "offline", "database", "watermelon", "watermelondb"], | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "Nathan Heffley", | ||
"email": "nathan@nathanheffley.com" | ||
} | ||
], | ||
"require": { | ||
"php": "^8.0", | ||
"illuminate/database": "^8.0", | ||
"illuminate/http": "^8.0", | ||
"illuminate/routing": "^8.0", | ||
"illuminate/support": "^8.0" | ||
}, | ||
"require-dev": { | ||
"laravel/sail": "^1.9", | ||
"orchestra/testbench": "^6.19", | ||
"phpunit/phpunit": "^9.5" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"NathanHeffley\\LaravelWatermelon\\": "src/" | ||
} | ||
}, | ||
"autoload-dev": { | ||
"psr-4": { | ||
"NathanHeffley\\LaravelWatermelon\\Tests\\": "tests/" | ||
} | ||
}, | ||
"config": { | ||
"sort-packages": true | ||
}, | ||
"extra": { | ||
"laravel": { | ||
"providers": [ | ||
"NathanHeffley\\LaravelWatermelon\\WatermelonServiceProvider" | ||
] | ||
} | ||
}, | ||
"minimum-stability": "dev", | ||
"prefer-stable": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
return [ | ||
|
||
'route' => env('WATERMELON_ROUTE', '/sync'), | ||
|
||
'middleware' => [], | ||
|
||
'models' => [ | ||
// 'tasks' => '\App\Models\Task', | ||
], | ||
|
||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# For more information: https://laravel.com/docs/sail | ||
version: '3' | ||
services: | ||
laravel.test: | ||
build: | ||
context: ./vendor/laravel/sail/runtimes/8.0 | ||
dockerfile: Dockerfile | ||
args: | ||
WWWGROUP: '${WWWGROUP}' | ||
image: sail-8.0/app | ||
environment: | ||
WWWUSER: '${WWWUSER}' | ||
LARAVEL_SAIL: 1 | ||
volumes: | ||
- '.:/var/www/html' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" | ||
backupGlobals="false" | ||
backupStaticAttributes="false" | ||
beStrictAboutTestsThatDoNotTestAnything="true" | ||
bootstrap="vendor/autoload.php" | ||
colors="true" | ||
convertErrorsToExceptions="true" | ||
convertNoticesToExceptions="true" | ||
convertWarningsToExceptions="true" | ||
processIsolation="false" | ||
stopOnError="false" | ||
stopOnFailure="false" | ||
verbose="true" | ||
> | ||
<php> | ||
<env name="APP_KEY" value="EncryptionKeyABCDEFGHIJKLMNOPQRS"/> | ||
<env name="DB_CONNECTION" value="sqlite"/> | ||
<env name="DB_DATABASE" value=":memory:"/> | ||
</php> | ||
<coverage processUncoveredFiles="true"> | ||
<include> | ||
<directory suffix=".php">./src</directory> | ||
</include> | ||
</coverage> | ||
<testsuites> | ||
<testsuite name="Laravel Watermelon Unit Test Suite"> | ||
<directory suffix="Test.php">./tests/Unit</directory> | ||
</testsuite> | ||
<testsuite name="Laravel Watermelon Feature Test Suite"> | ||
<directory suffix="Test.php">./tests/Feature</directory> | ||
</testsuite> | ||
</testsuites> | ||
</phpunit> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
|
||
namespace NathanHeffley\LaravelWatermelon\Exceptions; | ||
|
||
use Exception; | ||
|
||
class ConflictException extends Exception | ||
{ | ||
// | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
|
||
namespace NathanHeffley\LaravelWatermelon; | ||
|
||
use Illuminate\Http\JsonResponse; | ||
use Illuminate\Http\Request; | ||
use Illuminate\Routing\Controller; | ||
|
||
class SyncController extends Controller | ||
{ | ||
public function pull(SyncService $watermelon, Request $request): JsonResponse | ||
{ | ||
return $watermelon->pull($request); | ||
} | ||
|
||
public function push(SyncService $watermelon, Request $request): JsonResponse | ||
{ | ||
return $watermelon->push($request); | ||
} | ||
} |
Oops, something went wrong.