Flutter FCM with Laravel - laravel

I am Using Laravel for my App backend and want to send push notification to my flutter app by topic. Now I implemented firebase messaging into my flutter app. as
_registerOnFirebase() {
_firebaseMessaging.subscribeToTopic('all');
_firebaseMessaging.getToken().then((token) => print(token));
}
void getMessage() {
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print('received message');
}, onResume: (Map<String, dynamic> message) async {
print('on resume $message');
}, onLaunch: (Map<String, dynamic> message) async {
print('on launch $message');
});
}
and I am sending the notification to the app by Postman and It's working.
enter image description here
Now please tell me How can I send the notification from my Laravel Forms(From Views Directory).
I have created a controller Named PushNotification and a views directory in the resource directory as (\resources\views\notification\create.blade).

If you have the controller setup then it won't be that tough to send notification from the frontend/views. Here is my complete example.
Create a form in your view form.blade.php file (resources/views/form.blade.php)
<form method="POST" action="{{route('bulksend')}}">
<label>Title</label>
<input type="text" hint="Title" name="title">
<br>
<label>Body</label>
<input type="text" hint="Body" name="body">
<br>
<label>Image URL</label>
<input type="text" hint="Image URL" name="img">
<br>
<label>ID</label>
<input type="text" hint="Image URL" name="id">
<br>
<input type="submit"/>
</form>
Create a web route (routes/web.php)
Route::get('form', function () {
return view('form');
});
Route::post('send','MyController#bulksend')->name('bulksend');
Create a controller named MyController in app/Http/Controller and add this function to it.
public function bulksend(Request $req){
$url = 'https://fcm.googleapis.com/fcm/send';
$dataArr = array('click_action' => 'FLUTTER_NOTIFICATION_CLICK', 'id' => $req->id,'status'=>"done");
$notification = array('title' =>$req->title, 'text' => $req->body, 'image'=> $req->img, 'sound' => 'default', 'badge' => '1',);
$arrayToSend = array('to' => "/topics/all", 'notification' => $notification, 'data' => $dataArr, 'priority'=>'high');
$fields = json_encode ($arrayToSend);
$headers = array (
'Authorization: key=' . "YOUR_FCM_KEY",
'Content-Type: application/json'
);
$ch = curl_init ();
curl_setopt ( $ch, CURLOPT_URL, $url );
curl_setopt ( $ch, CURLOPT_POST, true );
curl_setopt ( $ch, CURLOPT_HTTPHEADER, $headers );
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt ( $ch, CURLOPT_POSTFIELDS, $fields );
$result = curl_exec ( $ch );
//var_dump($result);
curl_close ( $ch );
return $result;
}

We do this in Jobs. I share our server side code. you can arrange according to your need.
class SendFCM implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $FcmLog;
protected $regids;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($token,$payload,$incident_action_id=0)
{
$FcmLog = new FcmLog();
$FcmLog->incident_action_id = $incident_action_id;
$FcmLog->user_fcm_token_ids = $token->pluck('id')->toArray();
$FcmLog->payload = $payload;
$FcmLog->response = '';
$FcmLog->save();
$this->regids = $token->pluck('token')->toArray();
$this->FcmLog = $FcmLog;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
try {
$regids = UserFcmToken::whereIn('id',$this->FcmLog->user_fcm_token_ids)->get();
$targets = [
'android' => [],
'ios' => []
];
foreach($regids as $regid) {
$identifier = $regid->device_info['os'];
if($regid->device_info['os']==='android'&&$regid->device_info['framework']==='flutter') {
$identifier = 'android_flutter';
}
$targets[$identifier][] = $regid->token;
}
$headers = array(
'Authorization'=>'key = ******YOUR FIREBASE KEY*****',
'Content-Type'=>'application/json'
);
$client = new Client([
'base_uri' => 'https://fcm.googleapis.com/',
'timeout' => 30,
'connect_timeout' => 15,
'headers' => $headers
]);
$response = [
'ios'=>null,
'android'=>null,
'android_flutter'=>null
];
if(!empty($targets['ios'])) {
if ($this->FcmLog->payload['notification_type'] == 'incident_action') {
$incident = new Incident();
$incidents = $incident->ofUser([
'request' => (object) [
'resource' => 'pending',
'count' => 10,
'internal_user_id' => $this->FcmLog->payload['notification_body']['user_id']
]
]);
$badgeCount = intval($incidents['total']) ?: 1;
}
$fields = array(
'registration_ids' => $targets['ios'],
'notification' => []
);
if($this->FcmLog->payload['notification_type']=='announcement') {
$fields['notification'] = [
'body'=> $this->FcmLog->payload['notification_body']['announcement']['text'],
'title'=> $this->FcmLog->payload['notification_body']['announcement']['title'],
'sound'=> "default",
'badge'=> $badgeCount,
'id'=> $this->FcmLog->payload['notification_body']['announcement']['id'],
];
} else {
$fields['notification'] = [
'body'=> $this->FcmLog->payload['notification_body']['message'],
'title'=> 'Bildirim!',
'sound'=> "default",
'badge'=> $badgeCount,
'notification_type'=>$this->FcmLog->payload['notification_type'],
'id'=> $this->FcmLog->payload['notification_body']['incident_number'],
'token'=> $this->FcmLog->payload['notification_body']['public_token'],
];
}
$request = $client->post('fcm/send', [
'body' => json_encode($fields),
]);
$response['ios'] = (string) $request->getBody();
}
if(!empty($targets['android'])) {
$fields = array(
'registration_ids' => $targets['android'],
'data' => $this->FcmLog->payload
);
$request = $client->post('fcm/send', [
'body' => json_encode($fields),
]);
$response['android'] = (string) $request->getBody();
}
if(!empty($targets['android_flutter'])) {
if($this->FcmLog->payload['notification_type']=='announcement') {
$notificationBody = $this->FcmLog->payload['notification_body']['announcement']['text'];
$notificationTitle = $this->FcmLog->payload['notification_body']['announcement']['title'];
} else {
$notificationBody = $this->FcmLog->payload['notification_body']['message'];
$notificationTitle = 'Bildirim!';
}
$fields = array(
'registration_ids' => $targets['android_flutter'],
'data' => $this->FcmLog->payload,
'notification' => [
'body'=>$notificationBody,
'title'=>$notificationTitle
]
);
$fields['data']['click_action'] = 'FLUTTER_NOTIFICATION_CLICK';
$request = $client->post('fcm/send', [
'body' => json_encode($fields),
]);
$response['android_flutter'] = (string) $request->getBody();
}
}
catch (\Exception $e) {
$response = [ mb_substr($e->getMessage(),0,200) ];
}
$this->FcmLog->response = $response;
$this->FcmLog->save();
}
}

Related

Access blocked: Authorization Error in Laravel 9 for Google Calendar

I am trying to add google calendar for my laravel project. So I generate the API key and whatever required credentials in google console. Then trying to apply them into my project and the relevant file are attached bellow... When I run the project experiencing error like this...
My client_secret.json file is something like this...
My controller file like this.... Actually I don't have much idea over here but use the code which is extracted from google calendar API reference.
<?php
namespace App\Http\Controllers;
use Carbon\Carbon;
use Google_Client;
use Google_Service_Calendar;
use Google_Service_Calendar_Event;
use Google_Service_Calendar_EventDateTime;
use Illuminate\Http\Request;
class gCalendarController extends Controller
{
protected $client;
public function __construct()
{
$client = new Google_Client();
$client->setAuthConfig('client_secret.json');
$client->addScope(Google_Service_Calendar::CALENDAR);
$guzzleClient = new \GuzzleHttp\Client(array('curl' => array(CURLOPT_SSL_VERIFYPEER => false)));
$client->setHttpClient($guzzleClient);
$this->client = $client;
}
public function index()
{
session_start();
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$calendarId = 'primary';
$results = $service->events->listEvents($calendarId);
return $results->getItems();
} else {
return redirect()->route('oauthCallback');
}
}
public function oauth()
{
session_start();
$rurl = action('gCalendarController#oauth');
$this->client->setRedirectUri($rurl);
if (!isset($_GET['code'])) {
$auth_url = $this->client->createAuthUrl();
$filtered_url = filter_var($auth_url, FILTER_SANITIZE_URL);
return redirect($filtered_url);
} else {
$this->client->authenticate($_GET['code']);
$_SESSION['access_token'] = $this->client->getAccessToken();
return redirect()->route('cal.index');
}
}
public function create()
{
return view('calendar.createEvent');
}
public function store(Request $request)
{
session_start();
$startDateTime = $request->start_date;
$endDateTime = $request->end_date;
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$calendarId = 'primary';
$event = new Google_Service_Calendar_Event([
'summary' => $request->title,
'description' => $request->description,
'start' => ['dateTime' => $startDateTime],
'end' => ['dateTime' => $endDateTime],
'reminders' => ['useDefault' => true],
]);
$results = $service->events->insert($calendarId, $event);
if (!$results) {
return response()->json(['status' => 'error', 'message' => 'Something went wrong']);
}
return response()->json(['status' => 'success', 'message' => 'Event Created']);
} else {
return redirect()->route('oauthCallback');
}
}
public function show($eventId)
{
session_start();
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$event = $service->events->get('primary', $eventId);
if (!$event) {
return response()->json(['status' => 'error', 'message' => 'Something went wrong']);
}
return response()->json(['status' => 'success', 'data' => $event]);
} else {
return redirect()->route('oauthCallback');
}
}
public function update(Request $request, $eventId)
{
session_start();
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$startDateTime = Carbon::parse($request->start_date)->toRfc3339String();
$eventDuration = 30; //minutes
if ($request->has('end_date')) {
$endDateTime = Carbon::parse($request->end_date)->toRfc3339String();
} else {
$endDateTime = Carbon::parse($request->start_date)->addMinutes($eventDuration)->toRfc3339String();
}
// retrieve the event from the API.
$event = $service->events->get('primary', $eventId);
$event->setSummary($request->title);
$event->setDescription($request->description);
//start time
$start = new Google_Service_Calendar_EventDateTime();
$start->setDateTime($startDateTime);
$event->setStart($start);
//end time
$end = new Google_Service_Calendar_EventDateTime();
$end->setDateTime($endDateTime);
$event->setEnd($end);
$updatedEvent = $service->events->update('primary', $event->getId(), $event);
if (!$updatedEvent) {
return response()->json(['status' => 'error', 'message' => 'Something went wrong']);
}
return response()->json(['status' => 'success', 'data' => $updatedEvent]);
} else {
return redirect()->route('oauthCallback');
}
}
public function destroy($eventId)
{
session_start();
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$this->client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($this->client);
$service->events->delete('primary', $eventId);
} else {
return redirect()->route('oauthCallback');
}
}
}
My services.php file is like this...
<?php
return [
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect_uri' => env('GOOGLE_REDIRECT_URI'),
'redirect_callback' => env('GOOGLE_REDIRECT_CALLBACK'),
'scopes' => [
\Google_Service_Calendar::CALENDAR_EVENTS_READONLY,
\Google_Service_Calendar::CALENDAR_READONLY,
\Google_Service_Oauth2::OPENID,
\Google_Service_Oauth2::USERINFO_EMAIL,
\Google_Service_Oauth2::USERINFO_PROFILE,
],
'approval_prompt' => env('GOOGLE_APPROVAL_PROMPT', 'force'),
'access_type' => env('GOOGLE_ACCESS_TYPE', 'offline'),
'include_granted_scopes' => true,
],
];

Laravel - How to perform Advance Excel Import Validation Message using Maatwebsite

I am using Laravel-8 and Maatwebsite-3.1 package to import Excel into the DB using Laravel API as the endpoint.
Trait:
trait ApiResponse {
public
function coreResponse($message, $data = null, $statusCode, $isSuccess = true) {
if (!$message) return response() - > json(['message' => 'Message is required'], 500);
// Send the response
if ($isSuccess) {
return response() - > json([
'message' => $message,
'error' => false,
'code' => $statusCode,
'results' => $data
], $statusCode);
} else {
return response() - > json([
'message' => $message,
'error' => true,
'code' => $statusCode,
], $statusCode);
}
}
public
function success($message, $data, $statusCode = 200) {
return $this - > coreResponse($message, $data, $statusCode);
}
public
function error($message, $statusCode = 500) {
return $this - > coreResponse($message, null, $statusCode, false);
}
}
Import:
class EmployeeImport extends DefaultValueBinder implements OnEachRow, WithStartRow, SkipsOnError, WithValidation, SkipsOnFailure
{
use Importable, SkipsErrors, SkipsFailures;
public function onRow(Row $row)
{
$rowIndex = $row->getIndex();
if($rowIndex >= 1000)
return; // Not more than 1000 rows at a time
$row = $row->toArray();
$employee = Employee::create([
'first_name' => $row[0],
'other_name' => $row[1] ?? '',
'last_name' => $row[2],
'email' => preg_replace('/\s+/', '', strtolower($row[3])),
'created_at' => date("Y-m-d H:i:s"),
'created_by' => Auth::user()->id,
]);
public function startRow(): int
{
return 2;
}
}
Controller:
public function importEmployee(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'document' => 'file|mimes:xls,xlsx|max:5000',
]);
if ($request->hasFile('document'))
{
if($validator->passes()) {
$import = new EmployeeImport;
$file = $request->file('document');
$file->move(public_path('storage/file_imports/employee_imports'), $file->getClientOriginalName());
Excel::import($import, public_path('storage/file_imports/employee_imports/' . $file->getClientOriginalName() ));
foreach ($import->failures() as $failure) {
$importerror = new ImportError();
$importerror->data_row = $failure->row(); // row that went wrong
$importerror->data_attribute = $failure->attribute(); // either heading key (if using heading row concern) or column index
$importerror->data_errors = $failure->errors()[0]; // Actual error messages from Laravel validator
$importerror->data_values = json_encode($failure->values());
$importerror->created_by = Auth::user()->id;
$importerror->created_at = date("Y-m-d H:i:s");
$importerror->save();
}
return $this->success('Employees Successfully Imported.', [
'file' => $file
]);
}else{
return $this->error($validator->errors(), 422);
}
}
} catch(\Throwable $e) {
Log::error($e);
return $this->error($e->getMessage(), $e->getCode());
}
}
I made it to SkipOnError and SkipOnFailure.
If there's error, it saves the error into the DB. This is working.
However, there is issue, if some rows fail it still display success (Employees Successfully Imported) based on this:
return $this->success('Employees Successfully Imported.
When there is partial upload, or all the rows or some of the rows have issues, I want to display this to the user. So that it will be interactive.
How do I achieve this?
Thanks

Configuring Uppy to Use Multipart Uploads with Laravel/Vue

I figured it out
This was the missing piece. Once I clean up my code, I'll post an answer so that hopefully the next poor soul that has to deal with this will not have to go through the same hell I went through ;)
$command = $client->getCommand('UploadPart', array(
'Bucket' => 'the-bucket-name',
'Key' => $key,
'PartNumber' => $partNumber,
'UploadId' => $uploadId,
'Body' => '',
));
$signedUrl = $client->createPresignedRequest($command, '+20 minutes');
$presignedUrl = (string)$signedUrl->getUri();
return response()->json(['url' => $presignedUrl]);
I'm trying to figure out how to configure my server to work with Uppy for uploading multipart uploads to AWS S3 by using the CompanionUrl option. https://uppy.io/docs/aws-s3-multipart/#createMultipartUpload-file.
This is where I got the idea to go this route https://github.com/transloadit/uppy/issues/1189#issuecomment-445521442.
I can't figure this out and I feel like others have been stuck as well with no answer, so I'm posting what I've come up with so far in trying to get Uppy to work with multipart uploads using Laravel/Vue.
For the Vue component I have this:
<template>
<div>
<a id="uppy-trigger" #click="isUppyOpen = !isUppyOpen">Open Uppy</a>
<dashboard-modal
:uppy="uppy"
:open="isUppyOpen"
:props="{trigger: '#uppy-trigger'}"
/>
</div>
</template>
<script>
import Uppy from '#uppy/core'
import AwsS3Multipart from '#uppy/aws-s3-multipart';
import '#uppy/core/dist/style.css';
import '#uppy/dashboard/dist/style.css';
export default {
components: {
'dashboard-modal': DashboardModal,
},
data() {
return {
isUppyOpen: false,
}
},
computed: {
// Uppy Instance
uppy: () => new Uppy({
logger: Uppy.debugLogger
}).use(AwsS3Multipart, {
limit: 4,
companionUrl: 'https://mysite.local/',
}),
},
beforeDestroy () {
this.uppy.close();
},
}
</script>
Then for the routing I've added this to my web.php file.
// AWS S3 Multipart Upload Routes
Route::name('s3.multipart.')->prefix('s3/multipart')
->group(function () {
Route::post('/', ['as' => 'createMultipartUpload', 'uses' => 'AwsS3MultipartController#createMultipartUpload']);
Route::get('{uploadId}', ['as' => 'getUploadedParts', 'uses' => 'AwsS3MultipartController#getUploadedParts']);
Route::get('{uploadId}/{partNumber}', ['as' => 'signPartUpload', 'uses' => 'AwsS3MultipartController#signPartUpload']);
Route::post('{uploadId}/complete', ['as' => 'completeMultipartUpload', 'uses' => 'AwsS3MultipartController#completeMultipartUpload']);
Route::delete('{uploadId}', ['as' => 'abortMultipartUpload', 'uses' => 'AwsS3MultipartController#abortMultipartUpload']);
});
Basically what is happening is that I've set the "companionUrl" to "https://mysite.local/", then Uppy will send five requests when uploading a multipart upload file to these routes, ie "https://mysite.local/s3/multipart/createMultipartUpload".
I then created a controller to handle the requests:
<?php
namespace App\Http\Controllers;
use Aws\S3\S3Client;
use Illuminate\Http\Request;
class AwsS3MultipartController extends Controller
{
public function createMultipartUpload(Request $request)
{
$client = new S3Client([
'version' => 'latest',
'region' => 'us-east-1',
]);
$key = $request->has('filename') ? $request->get('filename') : null;
$type = $request->has('type') ? $request->get('type') : null;
if (!is_string($key)) {
return response()->json(['error' => 's3: filename returned from "getKey" must be a string'], 500);
}
if (!is_string($type)) {
return response()->json(['error' => 's3: content type must be a string'], 400);
}
$response = $client->createMultipartUpload([
'Bucket' => 'the-bucket-name',
'Key' => $key,
'ContentType' => $type,
'Expires' => 60
]);
$mpuKey = !empty($response['Key']) ? $response['Key'] : null;
$mpuUploadId = !empty($response['UploadId']) ? $response['UploadId'] : null;
if (!$mpuKey || !$mpuUploadId) {
return response()->json(['error' => 'Unable to process upload request.'], 400);
}
return response()->json([
'key' => $mpuKey,
'uploadId' => $mpuUploadId
]);
}
public function getUploadedParts($uploadId)
{
// Haven't configured this route yet as I haven't made it this far.
return $uploadId;
}
public function signPartUpload(Request $request, $uploadId, $partNumber)
{
$client = new S3Client([
'version' => 'latest',
'region' => 'us-east-1',
]);
$key = $request->has('key') ? $request->get('key') : null;
if (!is_string($key)) {
return response()->json(['error' => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400);
}
if (!intval($partNumber)) {
return response()->json(['error' => 's3: the part number must be a number between 1 and 10000.'], 400);
}
// Creating a presigned URL. I don't think this is correct.
$cmd = $client->getCommand('PutObject', [
'Bucket' => 'the-bucket-name',
'Key' => $key,
'UploadId' => $uploadId,
'PartNumber' => $partNumber,
]);
$response = $client->createPresignedRequest($cmd, '+20 minutes');
$presignedUrl = (string)$response->getUri();
return response()->json(['url' => $presignedUrl]);
}
public function completeMultipartUpload(Request $request, $uploadId)
{
$client = new S3Client([
'version' => 'latest',
'region' => 'us-east-1',
]);
$key = $request->has('key') ? $request->get('key') : null;
$parts = json_decode($request->getContent(), true)['parts'];
if (!is_string($key)) {
return response()->json(['error' => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400);
}
if (!is_array($parts) || !$this->arePartsValid($parts)) {
return response()->json(['error' => 's3: "parts" must be an array of {ETag, PartNumber} objects.'], 400);
}
// The completeMultipartUpload method fails with the following error.
// "Error executing "CompleteMultipartUpload" on "https://the-bucket-name.s3.amazonaws.com/NewProject.png?uploadId=nlWLdbNgB9zgarpLBXnj17eOIGAmQM_xyBArymtwdM71fhbFvveggDmL6fz4blz.B95TLhMatDvodbMb5p2ZMKqdlLeLFoSW1qcu33aRQTlt6NbiP_dkDO90DFO.pWGH"; AWS HTTP error: Client error: `POST https://the-bucket-name.s3.amazonaws.com/NewProject.png?uploadId=nlWLdbNgB9zgarpLBXnj17eOIGAmQM_xyBArymtwdM71fhbFvveggDmL6fz4blz.B95TLhMatDvodbMb5p2ZMKqdlLeLFoSW1qcu33aRQTlt6NbiP_dkDO90DFO.pWGH` resulted in a `400 Bad Request` response:
// <Error><Code>InvalidPart</Code><Message>One or more of the specified parts could not be found. The part may not have be (truncated...)
// InvalidPart (client): One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag. - <Error><Code>InvalidPart</Code><Message>One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's en"
$result = $client->completeMultipartUpload([
'Bucket' => 'the-bucket-name',
'Key' => $key,
'UploadId' => $uploadId,
'MultipartUpload' => [
'Parts' => $parts,
],
]);
return response()->json(['location' => $result['location']]);
}
public function abortMultipartUpload($uploadId)
{
// Haven't configured this route yet as I haven't made it this far.
return $uploadId;
}
private function arePartsValid($parts)
{
// Validation for the parts will go here, but returning true for now.
return true;
}
}
I can upload a multipart file fine purely PHP/server-side. For huge files though, this isn't going to work though since I would have to wait for the upload to finish on my server, then upload it to AWS in the parts.
$s3_client = new S3Client([
'version' => 'latest',
'region' => 'us-east-1',
]);
$bucket = 'the-bucket-name';
$tmp_name = $request->file('file')->getPathname();
$folder = Carbon::now()->format('Y/m/d/');
$filename = pathinfo($request->file('file')->getClientOriginalName(), PATHINFO_FILENAME);
$extension = $extension = pathinfo($request->file('file')->getClientOriginalName(), PATHINFO_EXTENSION);
$timestamp = Carbon::now()->format('H-i-s');
$name = "{$folder}{$filename}_{$timestamp}.{$extension}";
$response = $s3_client->createMultipartUpload([
'Bucket' => $bucket,
'Key' => $name,
]);
$uploadId = $response['UploadId'];
$file = fopen($tmp_name, 'r');
$parts = [];
$partNumber = 1;
while (! feof($file)) {
$result = $s3_client->uploadPart([
'Bucket' => $bucket,
'Key' => $name,
'UploadId' => $uploadId,
'PartNumber' => $partNumber,
'Body' => fread($file, 5 * 1024 * 1024),
]);
$parts[] = [
'PartNumber' => $partNumber++,
'ETag' => $result['ETag'],
];
}
$result = $s3_client->completeMultipartUpload([
'Bucket' => $bucket,
'Key' => $name,
'UploadId' => $uploadId,
'MultipartUpload' => [
'Parts' => $parts,
],
]);
What I believe is happening is that Uppy is handling the while loop part client-side. In order to do that, I have to return a pre-signed URL Uppy can use, but the pre-signed URL I'm currently returning isn't correct.
One thing I noted is that when I step through the while loop when initiating the multipart upload purely server-side, no file is uploaded to my bucket until the completeMultipartUpload method is fired. If however, I step through the parts being uploaded via Uppy, the parts seem to be being uploaded as the final file and each part is just overwriting the previous part. I'm then left with a fragment of the file, ie the last 3.5MB of a 43.5MB file.
Here's how I was able to get Uppy, Vue, and Laravel to play nicely together.
The Vue Component:
<template>
<div>
<a id="uppy-trigger" #click="isUppyOpen = !isUppyOpen">Open Uppy</a>
<dashboard-modal
:uppy="uppy"
:open="isUppyOpen"
:props="{trigger: '#uppy-trigger'}"
/>
</div>
</template>
<script>
import Uppy from '#uppy/core'
import AwsS3Multipart from '#uppy/aws-s3-multipart';
import '#uppy/core/dist/style.css';
import '#uppy/dashboard/dist/style.css';
export default {
components: {
'dashboard-modal': DashboardModal,
},
data() {
return {
isUppyOpen: false,
}
},
computed: {
// Uppy Instance
uppy: () => new Uppy({
logger: Uppy.debugLogger
}).use(AwsS3Multipart, {
limit: 4,
companionUrl: 'https://mysite.local/',
}),
},
beforeDestroy () {
this.uppy.close();
},
}
</script>
The Routing:
// AWS S3 Multipart Upload Routes
Route::name('s3.multipart.')->prefix('s3/multipart')
->group(function () {
Route::post('/', ['as' => 'createMultipartUpload', 'uses' => 'AwsS3MultipartController#createMultipartUpload']);
Route::get('{uploadId}', ['as' => 'getUploadedParts', 'uses' => 'AwsS3MultipartController#getUploadedParts']);
Route::get('{uploadId}/{partNumber}', ['as' => 'signPartUpload', 'uses' => 'AwsS3MultipartController#signPartUpload']);
Route::post('{uploadId}/complete', ['as' => 'completeMultipartUpload', 'uses' => 'AwsS3MultipartController#completeMultipartUpload']);
Route::delete('{uploadId}', ['as' => 'abortMultipartUpload', 'uses' => 'AwsS3MultipartController#abortMultipartUpload']);
});
The Controller:
<?php
namespace App\Http\Controllers;
use Aws\S3\S3Client;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\Request;
class AwsS3MultipartController extends Controller
{
private $bucket;
private $client;
public function __construct()
{
$this->bucket = 'the-name-of-the-bucket';
$this->client = new S3Client([
'version' => 'latest',
'region' => 'us-east-1',
]);
}
/**
* Create/initiate the multipart upload
* #param Request $request
* #return JsonResponse
*/
public function createMultipartUpload(Request $request)
{
// Get the filename and type from request
$filename = $request->has('filename') ? $request->get('filename') : null;
$type = $request->has('type') ? $request->get('type') : null;
// Check filename
if (!is_string($filename)) {
return response()->json(['error' => 's3: filename returned from "getKey" must be a string'], 500);
}
// Check type
if (!is_string($type)) {
return response()->json(['error' => 's3: content type must be a string'], 400);
}
// Set up key equal to YYYY/MM/DD/filename_H-i-s.ext
$fileBaseName = pathinfo($filename, PATHINFO_FILENAME);
$extension = pathinfo($filename, PATHINFO_EXTENSION);
$folder = Carbon::now()->format('Y/m/d/');
$timestamp = Carbon::now()->format('H-i-s');
$key = "{$folder}{$fileBaseName}_{$timestamp}.{$extension}";
// Create/initiate the multipart upload
try {
$response = $this->client->createMultipartUpload([
'Bucket' => $this->bucket,
'Key' => $key,
'ContentType' => $type,
'Expires' => 60
]);
} catch (Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
// Multipart upload key and id
$mpuKey = !empty($response['Key']) ? $response['Key'] : null;
$mpuUploadId = !empty($response['UploadId']) ? $response['UploadId'] : null;
// Check multipart upload key and id
if (!$mpuKey || !$mpuUploadId) {
return response()->json(['error' => 'Unable to process upload request.'], 400);
}
return response()->json([
'key' => $mpuKey,
'uploadId' => $mpuUploadId
]);
}
/**
* Get parts that have been uploaded
* #param Request $request
* #param string $uploadId
* #return JsonResponse
*/
public function getUploadedParts(Request $request, string $uploadId)
{
$key = $request->has('key') ? $request->get('key') : null;
// Check key
if (!is_string($key)) {
return response()->json(['error' => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400);
}
$parts = [];
$getParts = true;
$startAt = 0;
// Get parts uploaded so far
while ($getParts) {
$partsPage = $this->listPartsPage($key, $uploadId, $startAt, $parts);
if (isset($partsPage['error'])) {
return response()->json(['error' => $partsPage['error']], 400);
}
if ($partsPage['isTruncated']) {
$startAt = $partsPage['nextPartNumberMarker'];
} else {
$getParts = false;
}
}
return response()->json(
$parts,
);
}
/**
* Create a pre-signed URL for parts to be uploaded to
* #param Request $request
* #param string $uploadId
* #param int $partNumber
* #return JsonResponse
*/
public function signPartUpload(Request $request, string $uploadId, int $partNumber)
{
$key = $request->has('key') ? $request->get('key') : null;
// Check key
if (!is_string($key)) {
return response()->json(['error' => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400);
}
// Check part number
if (!intval($partNumber)) {
return response()->json(['error' => 's3: the part number must be a number between 1 and 10000.'], 400);
}
// Create the upload part command and get the pre-signed URL
try {
$command = $this->client->getCommand('UploadPart', [
'Bucket' => $this->bucket,
'Key' => $key,
'PartNumber' => $partNumber,
'UploadId' => $uploadId,
'Body' => '',
]);
$presignedUrl = $this->client->createPresignedRequest($command, '+20 minutes');
} catch (Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
// Convert the pre-signed URL to a string
$presignedUrlString = (string)$presignedUrl->getUri();
return response()->json(['url' => $presignedUrlString]);
}
/**
* Complete the multipart upload
* #param Request $request
* #param string $uploadId
* #return JsonResponse
*/
public function completeMultipartUpload(Request $request, string $uploadId)
{
$key = $request->has('key') ? $request->get('key') : null;
$parts = json_decode($request->getContent(), true)['parts'];
// Check the key
if (!is_string($key)) {
return response()->json(['error' => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400);
}
// Check the parts
if (!is_array($parts) || !$this->arePartsValid($parts)) {
return response()->json(['error' => 's3: "parts" must be an array of {ETag, PartNumber} objects.'], 400);
}
// Complete the multipart upload
try {
$result = $this->client->completeMultipartUpload([
'Bucket' => $this->bucket,
'Key' => $key,
'UploadId' => $uploadId,
'MultipartUpload' => [
'Parts' => $parts,
],
]);
} catch (Exception $e) {
return response()->json(['error' => $e->getMessage()], 400);
}
// Change forwardslash entities to forwardslashes
$location = urldecode($result['Location']);
return response()->json(['location' => $location]);
}
public function abortMultipartUpload(Request $request, $uploadId)
{
$key = $request->has('key') ? $request->get('key') : null;
// Check the key
if (!is_string($key)) {
return response()->json(['error' => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400);
}
// Cancel the multipart upload
try {
$response = $this->client->abortMultipartUpload([
'Bucket' => $this->bucket,
'Key' => $key,
'UploadId' => $uploadId,
]);
} catch (Exception $e) {
//
}
return response()->json();
}
private function listPartsPage(string $key, string $uploadId, int $startAt, array &$parts)
{
// Configure response
$response = [
'isTruncated' => false,
];
// Get list of parts uploaded
try {
$result = $this->client->listParts([
'Bucket' => $this->bucket,
'Key' => $key,
'PartNumberMarker' => $startAt,
'UploadId' => $uploadId,
]);
} catch (Exception $e) {
return ['error' => 's3: unable to continue upload. The upload may have been aborted.'];
}
// Add found parts to parts array
if ($result->hasKey('Parts')) {
array_push($parts, ...$result->get('Parts'));
}
// Check if parts are truncated
if ($result->hasKey('IsTruncated') && $result->get('IsTruncated')) {
$response['isTruncated'] = true;
$response['nextPartNumberMarker'] = $result->get('NextPartNumberMarker');
}
return $response;
}
/**
* Validate the parts for the multipart upload
* #param array $parts An associative array of parts with PartNumber and ETag
* #return bool
*/
private function arePartsValid(array $parts)
{
if (!is_array($parts)) {
return false;
}
foreach ($parts as $part) {
if (!is_int($part['PartNumber']) || !is_string($part['ETag'])) {
return false;
}
}
return true;
}
}
You can use this pre-built laravel package to easily achieve multipart uploading via laravel and uppy:
https://github.com/TappNetwork/laravel-uppy-s3-multipart-upload

Laravel Mqtt's subscription doesn't end

I receive an Mqtt message from Laravel and try to do some action, but if you subscribe, you only get one message and it takes about a minute to delay.
I referred to this at https://github.com/salmanzafar949/MQTT-Laravel.
Implementing Mqtttt motion was made by creating a separate controller.
My code is
<?php
namespace App\Http\Controllers;
use Salman\Mqtt\MqttClass\Mqtt;
use Illuminate\Http\Request;
class MqttController extends Controller{
public $token = "";
public function SendMsgViaMqtt(Request $request)
{
$mqtt = new Mqtt();
//$client_id = Auth::user()->id;/
$topic = $request->topic;
$token = $request->token;
$message = $request->message;
$output = $mqtt->ConnectAndPublish("test", $message, "");
if ($output === true)
{
if($token == "none" || !$token){
return "End";
}else{
$this->SubscribetoTopic($token);
}
}else{
return "Failed";
}
}
public function SubscribetoTopic($token)
{
$topic = 'test';
$this->token = $token;
$message = [];
$mqtt = new Mqtt();
$client_id = "";
$mqtt->ConnectAndSubscribe($topic, function($topic, $msg){
if($msg == "end"){
$message = [
'title' => '魚が釣れました',
'body' => '釣竿を確認してください',
'click_action' => 'Url'
];
}else if($msg == "no"){
$message = [
'title' => '測定できません',
'body' => '波が強すぎると測れません',
'click_action' => 'Url'
];
}else{
return "end";
}
return $this->sendCrul($this->token, $message);
}, "");
}
public function sendCrul($token, $message){
define('SERVER_API_KEY', 'APIKEY');
$tokens = $token;
$header = [
'Authorization: Key=' . SERVER_API_KEY,
'Content-Type: Application/json'
];
$payload = [
'to' => $tokens,
'notification' => $message
];
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://fcm.googleapis.com/fcm/send",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode( $payload ),
CURLOPT_HTTPHEADER => $header
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if($err){
echo "cURL Error #:". $err;
}else{
return $response;
}
// return "ok";
}
}
If you're in trouble like me, let me know how.

Creating laravel service class

My Uptime.php
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://api.uptimerobot.com/v2/getMonitors",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "Your Api Key",
CURLOPT_HTTPHEADER => array(
"cache-control: no-cache",
"content-type: application/x-www-form-urlencoded"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
$data = json_decode($response);
$custom_uptime = ($data->monitors[0]->custom_uptime_ratio);
$uptime = explode("-",$custom_uptime);
}
?>
ApiCommand.php
public function handle()
{
//include(app_path() . '/Includes/Uptime.php')
$this->showMonitors();
}
public function showMonitors(UptimeRobotAPI $uptime_api)
{
$monitors = $uptime_api->getMonitors();
return $monitors;
}
Hello everyone. I just want to ask how can I turn this to a service class? Do I need to use service providers or service containers? Thanks in advance.
Someone convert it to service class and here was my command looks like.
In your terminal, require the guzzle package as you will use it as an HTTP client: composer require guzzlehttp/guzzle
Then you can make a class for your UptimeRobotAPI at app/Services/UptimeRobotAPI.php:
<?php
namespace App\Services;
use GuzzleHttp\Client;
class UptimeRobotAPI
{
protected $url;
protected $http;
protected $headers;
public function __construct(Client $client)
{
$this->url = 'https://api.uptimerobot.com/v2/';
$this->http = $client;
$this->headers = [
'cache-control' => 'no-cache',
'content-type' => 'application/x-www-form-urlencoded',
];
}
private function getResponse(string $uri = null)
{
$full_path = $this->url;
$full_path .= $uri;
$request = $this->http->get($full_path, [
'headers' => $this->headers,
'timeout' => 30,
'connect_timeout' => true,
'http_errors' => true,
]);
$response = $request ? $request->getBody()->getContents() : null;
$status = $request ? $request->getStatusCode() : 500;
if ($response && $status === 200 && $response !== 'null') {
return (object) json_decode($response);
}
return null;
}
private function postResponse(string $uri = null, array $post_params = [])
{
$full_path = $this->url;
$full_path .= $uri;
$request = $this->http->post($full_path, [
'headers' => $this->headers,
'timeout' => 30,
'connect_timeout' => true,
'http_errors' => true,
'form_params' => $post_params,
]);
$response = $request ? $request->getBody()->getContents() : null;
$status = $request ? $request->getStatusCode() : 500;
if ($response && $status === 200 && $response !== 'null') {
return (object) json_decode($response);
}
return null;
}
public function getMonitors()
{
return $this->getResponse('getMonitors');
}
}
You can then add more functions beneath, I created getMonitors() as an example.
To use this in a controller, you can simply dependency inject it into your controller methods:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Services\Promises\UptimeRobotAPI;
class ExampleController extends Controller
{
public function showMonitors(UptimeRobotAPI $uptime_api)
{
$monitors = $uptime_api->getMonitors();
return view('monitors.index')->with(compact('monitors'));
}
}
This is just an example, this does not handle any errors or timeouts that can occur, this is simply for you to understand and extend. I don't know what you want to do with it, but I can't code your whole project, this will definitely answer your question though. :)

Resources