Orzu Ionut
3 years ago
26 changed files with 1034 additions and 133 deletions
-
1.gitignore
-
6.idea/core.iml
-
6.idea/php.xml
-
91README.md
-
108app/Console/Commands/RunSearchDisplace.php
-
38app/Events/IngestDocumentReceived.php
-
15app/Http/Controllers/HomeController.php
-
51app/Http/Controllers/SearchAndDisplaceController.php
-
1app/Http/Middleware/VerifyCsrfToken.php
-
29app/Listeners/RunSearchAndDisplaceOnDocument.php
-
9app/Providers/EventServiceProvider.php
-
32app/SearchDisplace/Documents/DocumentFile.php
-
3app/SearchDisplace/Ingest/HandleReceivedDocument.php
-
59app/SearchDisplace/Ingest/SendDocument.php
-
64app/SearchDisplace/SearchAndDisplace.php
-
56app/SearchDisplace/SearchAndDisplaceFromFiles.php
-
40app/SearchDisplace/Searchers/Duckling.php
-
8app/SearchDisplace/Searchers/Handlers/Emails.php
-
64app/SearchDisplace/Searchers/Mapper.php
-
26app/SearchDisplace/Searchers/Searcher.php
-
1app/SearchDisplace/Webhooks/HandleRequest.php
-
1composer.json
-
384composer.lock
-
65resources/js/components/Home.vue
-
2resources/views/pages/home.blade.php
-
7routes/web.php
@ -1,78 +1,13 @@ |
|||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400"></a></p> |
|||
|
|||
<p align="center"> |
|||
<a href="https://travis-ci.org/laravel/framework"><img src="https://travis-ci.org/laravel/framework.svg" alt="Build Status"></a> |
|||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/d/total.svg" alt="Total Downloads"></a> |
|||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/v/stable.svg" alt="Latest Stable Version"></a> |
|||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://poser.pugx.org/laravel/framework/license.svg" alt="License"></a> |
|||
</p> |
|||
|
|||
## About Laravel |
|||
|
|||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: |
|||
|
|||
- [Simple, fast routing engine](https://laravel.com/docs/routing). |
|||
- [Powerful dependency injection container](https://laravel.com/docs/container). |
|||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. |
|||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). |
|||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations). |
|||
- [Robust background job processing](https://laravel.com/docs/queues). |
|||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). |
|||
|
|||
Laravel is accessible, powerful, and provides tools required for large, robust applications. |
|||
|
|||
## Learning Laravel |
|||
|
|||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. |
|||
|
|||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 1500 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. |
|||
|
|||
## Laravel Sponsors |
|||
|
|||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell). |
|||
|
|||
- **[Vehikl](https://vehikl.com/)** |
|||
- **[Tighten Co.](https://tighten.co)** |
|||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** |
|||
- **[64 Robots](https://64robots.com)** |
|||
- **[Cubet Techno Labs](https://cubettech.com)** |
|||
- **[Cyber-Duck](https://cyber-duck.co.uk)** |
|||
- **[British Software Development](https://www.britishsoftware.co)** |
|||
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)** |
|||
- **[DevSquad](https://devsquad.com)** |
|||
- [UserInsights](https://userinsights.com) |
|||
- [Fragrantica](https://www.fragrantica.com) |
|||
- [SOFTonSOFA](https://softonsofa.com/) |
|||
- [User10](https://user10.com) |
|||
- [Soumettre.fr](https://soumettre.fr/) |
|||
- [CodeBrisk](https://codebrisk.com) |
|||
- [1Forge](https://1forge.com) |
|||
- [TECPRESSO](https://tecpresso.co.jp/) |
|||
- [Runtime Converter](http://runtimeconverter.com/) |
|||
- [WebL'Agence](https://weblagence.com/) |
|||
- [Invoice Ninja](https://www.invoiceninja.com) |
|||
- [iMi digital](https://www.imi-digital.de/) |
|||
- [Earthlink](https://www.earthlink.ro/) |
|||
- [Steadfast Collective](https://steadfastcollective.com/) |
|||
- [We Are The Robots Inc.](https://watr.mx/) |
|||
- [Understand.io](https://www.understand.io/) |
|||
- [Abdel Elrafa](https://abdelelrafa.com) |
|||
- [Hyper Host](https://hyper.host) |
|||
- [Appoly](https://www.appoly.co.uk) |
|||
- [OP.GG](https://op.gg) |
|||
|
|||
## Contributing |
|||
|
|||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). |
|||
|
|||
## Code of Conduct |
|||
|
|||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). |
|||
|
|||
## Security Vulnerabilities |
|||
|
|||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. |
|||
|
|||
## License |
|||
|
|||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). |
|||
# Search and Displace Core |
|||
|
|||
## Install |
|||
### Search and Displace Ingest integration |
|||
Add in .env the 'WEBHOOK_CLIENT_SECRET' value. |
|||
|
|||
### Facebook Duckling |
|||
- `$ apt-get install libpcre3-dev` |
|||
- `$ cd ..` |
|||
- `$ git clone https://github.com/facebook/duckling.git && cd duckling` |
|||
- `$ curl -sSL https://get.haskellstack.org/ | sh` |
|||
- `$ stack build && stack exec duckling-example-exe` |
|||
- `$ stack test` |
@ -0,0 +1,108 @@ |
|||
<?php |
|||
|
|||
namespace App\Console\Commands; |
|||
|
|||
use App\SearchDisplace\Ingest\SendDocument; |
|||
use Illuminate\Console\Command; |
|||
use Illuminate\Support\Facades\Storage; |
|||
|
|||
class RunSearchDisplace extends Command |
|||
{ |
|||
/** |
|||
* The name and signature of the console command. |
|||
* |
|||
* @var string |
|||
*/ |
|||
protected $signature = 'sd:run |
|||
{path : The document path} |
|||
{filters* : The filters which will be applied to the search}'; |
|||
|
|||
/** |
|||
* The console command description. |
|||
* |
|||
* @var string |
|||
*/ |
|||
protected $description = 'Run search and displace on document with filters.'; |
|||
|
|||
/** |
|||
* Create a new command instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
parent::__construct(); |
|||
} |
|||
|
|||
/** |
|||
* Execute the console command. |
|||
* |
|||
*/ |
|||
public function handle() |
|||
{ |
|||
$documentPath = $this->argument('path'); |
|||
$searchers = $this->argument('filters'); |
|||
|
|||
$id = md5(uniqid(rand(), true)); |
|||
|
|||
$this->storeSearchers($id, $searchers, $documentPath); |
|||
|
|||
try { |
|||
$sendToIngest = new SendDocument(); |
|||
|
|||
$sendToIngest->execute($documentPath, $id); |
|||
|
|||
$this->info('Processing document..'); |
|||
$this->info('After the processing will be done the result will show up at the same path as the input.'); |
|||
} catch (\Exception $exception) { |
|||
$this->error('Something went wrong. (' . $exception->getMessage() . ')'); |
|||
} |
|||
} |
|||
|
|||
protected function storeSearchers($id, $searchers, $documentPath) |
|||
{ |
|||
$data = [ |
|||
'searchers' => $this->getSearchers($searchers), |
|||
'document_path' => $documentPath, |
|||
]; |
|||
|
|||
$storage = Storage::disk('local'); |
|||
$storage->put("searchers/$id.json", json_encode($data)); |
|||
} |
|||
|
|||
protected function getSearchers($searchers) |
|||
{ |
|||
if (count($searchers) === 1 && str_contains($searchers[0], '.json')) { |
|||
return $this->getSearchersFromFile($searchers[0]); |
|||
} |
|||
|
|||
return $this->getSearchersFromList($searchers); |
|||
} |
|||
|
|||
protected function getSearchersFromList($searchers) |
|||
{ |
|||
$list = []; |
|||
|
|||
foreach ($searchers as $searcher) { |
|||
$result = explode(':', $searcher); |
|||
|
|||
$list[] = [ |
|||
'key' => $result[0], |
|||
'replace_with' => $result[1], |
|||
]; |
|||
} |
|||
|
|||
return $list; |
|||
} |
|||
|
|||
protected function getSearchersFromFile($path) |
|||
{ |
|||
$contents = file_get_contents($path); |
|||
|
|||
if ( ! $contents) { |
|||
throw new \Exception('Something went wrong when tried reading from file.'); |
|||
} |
|||
|
|||
return json_decode($contents); |
|||
} |
|||
} |
@ -0,0 +1,38 @@ |
|||
<?php |
|||
|
|||
namespace App\Events; |
|||
|
|||
use Illuminate\Broadcasting\Channel; |
|||
use Illuminate\Broadcasting\InteractsWithSockets; |
|||
use Illuminate\Broadcasting\PresenceChannel; |
|||
use Illuminate\Broadcasting\PrivateChannel; |
|||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; |
|||
use Illuminate\Foundation\Events\Dispatchable; |
|||
use Illuminate\Queue\SerializesModels; |
|||
|
|||
class IngestDocumentReceived |
|||
{ |
|||
use Dispatchable, InteractsWithSockets, SerializesModels; |
|||
|
|||
public $id; |
|||
|
|||
/** |
|||
* Create a new event instance. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct($id) |
|||
{ |
|||
$this->id = $id; |
|||
} |
|||
|
|||
/** |
|||
* Get the channels the event should broadcast on. |
|||
* |
|||
* @return \Illuminate\Broadcasting\Channel|array |
|||
*/ |
|||
public function broadcastOn() |
|||
{ |
|||
return new PrivateChannel('channel-name'); |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\SearchDisplace\Searchers\Mapper; |
|||
|
|||
class HomeController extends Controller |
|||
{ |
|||
public function index() |
|||
{ |
|||
return view('pages.home', [ |
|||
'searchers' => (new Mapper())->get(), |
|||
]); |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\SearchDisplace\Documents\DocumentFile; |
|||
use App\SearchDisplace\SearchAndDisplace; |
|||
|
|||
class SearchAndDisplaceController extends Controller |
|||
{ |
|||
public function show($id) |
|||
{ |
|||
$handler = new DocumentFile(); |
|||
|
|||
try { |
|||
$documentContent = $handler->getAfterIngest($id); |
|||
|
|||
return response()->json([ |
|||
'content' => $documentContent, |
|||
'ingest_status' => !! $documentContent ? 'success' : 'fail', |
|||
], 200); |
|||
} catch (\Exception $exception) { |
|||
return response()->json([ |
|||
'message' => $exception->getMessage(), |
|||
], 400); |
|||
} |
|||
} |
|||
|
|||
public function store() |
|||
{ |
|||
request()->validate([ |
|||
'content' => 'required', // String or file.
|
|||
'searchers' => 'required|array', // Check if matches all rules, must have 'key' and 'replace_with'.
|
|||
]); |
|||
|
|||
$searchAndDisplace = new SearchAndDisplace(request()->get('content'), [ |
|||
'searchers' => request()->get('searchers'), |
|||
]); |
|||
|
|||
try { |
|||
$resultedDocumentContent = $searchAndDisplace->execute(); |
|||
|
|||
return response()->json([ |
|||
'content' => $resultedDocumentContent, |
|||
], 200); |
|||
} catch (\Exception $exception) { |
|||
return response()->json([ |
|||
'message' => $exception->getMessage(), |
|||
], 400); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
namespace App\Listeners; |
|||
|
|||
use App\SearchDisplace\SearchAndDisplaceFromFiles; |
|||
|
|||
class RunSearchAndDisplaceOnDocument |
|||
{ |
|||
/** |
|||
* Create the event listener. |
|||
* |
|||
* @return void |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Handle the event. |
|||
* |
|||
* @param $event |
|||
* @return void |
|||
*/ |
|||
public function handle($event) |
|||
{ |
|||
(new SearchAndDisplaceFromFiles($event->id))->execute(); |
|||
} |
|||
} |
@ -0,0 +1,32 @@ |
|||
<?php |
|||
|
|||
namespace App\SearchDisplace\Documents; |
|||
|
|||
use Illuminate\Support\Facades\Storage; |
|||
|
|||
class DocumentFile |
|||
{ |
|||
protected $storage; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$this->storage = Storage::disk('local'); |
|||
} |
|||
|
|||
public function getAfterIngest($id) |
|||
{ |
|||
$path = "contracts/$id"; |
|||
|
|||
// Ingest success.
|
|||
if ($this->storage->exists("$path.md")) { |
|||
return $this->storage->get("$path.md"); |
|||
} |
|||
|
|||
// Ingest fail.
|
|||
if ($this->storage->exists($path)) { |
|||
return ''; |
|||
} |
|||
|
|||
throw new \Exception('Document has not been processed yet.'); |
|||
} |
|||
} |
@ -0,0 +1,59 @@ |
|||
<?php |
|||
|
|||
namespace App\SearchDisplace\Ingest; |
|||
|
|||
use GuzzleHttp\Client; |
|||
|
|||
class SendDocument |
|||
{ |
|||
protected $url; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$this->url = env('SD_INGEST_URL') . '/ingest'; |
|||
} |
|||
|
|||
public function execute($documentPath, $id) |
|||
{ |
|||
try { |
|||
$response = $this->sendRequest($documentPath, $id); |
|||
|
|||
if ($response['status'] === 'fail') { |
|||
$message = array_key_exists('message', $response) |
|||
? $response['message'] |
|||
: 'Something went wrong.'; |
|||
|
|||
throw new \Exception($message); |
|||
} |
|||
|
|||
// The file in Ingest si in Processing state.
|
|||
} catch (\Exception $exception) { |
|||
throw new \Exception($exception->getMessage()); |
|||
} |
|||
} |
|||
|
|||
public function sendRequest($documentPath, $id) |
|||
{ |
|||
$client = new Client(); |
|||
|
|||
$response = $client->request('post', $this->url, [ |
|||
'headers' => [ |
|||
'Accept' => 'application/json', |
|||
], |
|||
|
|||
'multipart' => [ |
|||
[ |
|||
'name' => 'id', |
|||
'contents' => $id, |
|||
], |
|||
|
|||
[ |
|||
'name' => 'document', |
|||
'contents' => fopen($documentPath, 'r'), |
|||
] |
|||
], |
|||
]); |
|||
|
|||
return json_decode($response->getBody()->getContents(), true); |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
<?php |
|||
|
|||
namespace App\SearchDisplace; |
|||
|
|||
use App\SearchDisplace\Searchers\Searcher; |
|||
|
|||
class SearchAndDisplace |
|||
{ |
|||
protected $documentContent; |
|||
protected $info; |
|||
|
|||
public function __construct($documentContent, $info) |
|||
{ |
|||
$this->documentContent = $documentContent; |
|||
$this->info = $info; |
|||
} |
|||
|
|||
public function execute() |
|||
{ |
|||
$searchResult = $this->search(); |
|||
|
|||
return $this->displace($searchResult); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @return mixed |
|||
* @throws \Exception |
|||
*/ |
|||
protected function search() |
|||
{ |
|||
$searcher = new Searcher($this->info['searchers'], $this->documentContent); |
|||
|
|||
return $searcher->execute(); |
|||
} |
|||
|
|||
protected function displace($searchResult) |
|||
{ |
|||
$replacements = []; |
|||
|
|||
foreach ($this->info['searchers'] as $searcher) { |
|||
$replacements[$searcher['key']] = $searcher['replace_with']; |
|||
} |
|||
|
|||
$updatedDocumentContent = ''; |
|||
$currentIndex = 0; |
|||
|
|||
foreach ($searchResult as $item) { |
|||
$partialContent = substr($this->documentContent, $currentIndex, $item['start'] - $currentIndex); |
|||
|
|||
$updatedDocumentContent = $updatedDocumentContent . $partialContent; |
|||
|
|||
$updatedDocumentContent = $updatedDocumentContent . $replacements[$item['dim']]; |
|||
|
|||
$currentIndex = $item['end']; |
|||
} |
|||
|
|||
if ($currentIndex < strlen($this->documentContent) - 1) { |
|||
$updatedDocumentContent = $updatedDocumentContent . substr($this->documentContent, $currentIndex); |
|||
} |
|||
|
|||
return $updatedDocumentContent; |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
<?php |
|||
|
|||
namespace App\SearchDisplace; |
|||
|
|||
use Illuminate\Support\Facades\Storage; |
|||
|
|||
class SearchAndDisplaceFromFiles |
|||
{ |
|||
protected $id; |
|||
protected $storage; |
|||
protected $documentFilePath; |
|||
protected $infoFilePath; |
|||
|
|||
public function __construct($id) |
|||
{ |
|||
$this->id = $id; |
|||
|
|||
$this->storage = Storage::disk('local'); |
|||
|
|||
$this->documentFilePath = "contracts/$this->id.md"; |
|||
$this->infoFilePath = "searchers/$this->id.json"; |
|||
} |
|||
|
|||
public function execute() |
|||
{ |
|||
if ( ! $this->storage->exists($this->documentFilePath) || |
|||
! $this->storage->exists($this->infoFilePath) |
|||
) { |
|||
// Handle this case, must report result to user.
|
|||
return; |
|||
} |
|||
|
|||
$documentContent = $this->storage->get($this->documentFilePath); |
|||
$info = json_decode($this->storage->get($this->infoFilePath), true); |
|||
|
|||
$searchAndDisplace = new SearchAndDisplace($documentContent, $info); |
|||
|
|||
$originalDocumentPath = $info['document_path']; |
|||
$pathDetails = pathinfo($originalDocumentPath); |
|||
|
|||
$resultedDocumentPath = $pathDetails['dirname'] . '/' . $pathDetails['filename'] . '-displaced.md'; |
|||
|
|||
try { |
|||
$resultedDocumentContent = $searchAndDisplace->execute(); |
|||
|
|||
file_put_contents($resultedDocumentPath, $resultedDocumentContent); |
|||
} catch (\Exception $exception) { |
|||
\Illuminate\Support\Facades\Log::info('exception: ' . $exception->getMessage()); |
|||
|
|||
return; |
|||
} finally { |
|||
$this->storage->delete($this->documentFilePath); |
|||
$this->storage->delete($this->infoFilePath); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
<?php |
|||
|
|||
namespace App\SearchDisplace\Searchers; |
|||
|
|||
use GuzzleHttp\Client; |
|||
|
|||
class Duckling |
|||
{ |
|||
protected $url; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$this->url = env('SD_DUCKLING_URL'); |
|||
} |
|||
|
|||
public function execute($content, $dimensions) |
|||
{ |
|||
// @TODO Parse result
|
|||
return $this->sendRequest($content, $dimensions); |
|||
} |
|||
|
|||
protected function sendRequest($text, $dimensions) |
|||
{ |
|||
$client = new Client(); |
|||
|
|||
$response = $client->request('post', $this->url, [ |
|||
'headers' => [ |
|||
'Accept' => 'application/json', |
|||
], |
|||
|
|||
'form_params' => [ |
|||
'locale' => 'en_GB', |
|||
'text' => $text, |
|||
'dims' => json_encode($dimensions), |
|||
], |
|||
]); |
|||
|
|||
return json_decode($response->getBody()->getContents(), true); |
|||
} |
|||
} |
@ -1,8 +0,0 @@ |
|||
<?php |
|||
|
|||
namespace App\SearchDisplace\Searchers\Handlers; |
|||
|
|||
class Emails |
|||
{ |
|||
// Use 'duckling' wrapper to find 'emails' in document.
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue