Skip to content

Commit

Permalink
Merge pull request #73 from andreciornavei/develop
Browse files Browse the repository at this point in the history
bugfix to upload document
  • Loading branch information
andreciornavei authored Mar 8, 2024
2 parents ba3afdd + 87d069e commit 3db9bc1
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 41 deletions.
34 changes: 34 additions & 0 deletions backend/app/Domain/Entities/PresignedUrlFormEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App\Domain\Entities;


class PresignedUrlFormEntity
{
private $url;
private $fields;

public function __construct(array | object $data)
{
foreach ($data as $key => $value) {
if (property_exists($this, $key)) {
$this->$key = $value;
}
}
}

public function toJson()
{
return get_object_vars($this);
}

public function getUrl()
{
return $this->url;
}

public function getFields()
{
return $this->fields;
}
}
4 changes: 3 additions & 1 deletion backend/app/Domain/Providers/IStorageProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace App\Domain\Providers;

use App\Domain\Entities\PresignedUrlFormEntity;

interface IStorageProvider
{
public function generateSignedUrl(string $document_key): string;
public function generateSignedUrl(string $document_key): PresignedUrlFormEntity;
public function checkTmpDocument(string $document_key): string | null;
public function moveDocument(string $from_document, string $to_document): string | null;
public function generateReadableUrl(string $document_key): string | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public function handler(TransactionCreateDto $dto): UserEntity | TransactionEnti
"error" => [
"message" => "Failed to access document",
"fields" => [
"document" => "The provided document does not belongs to your user."
"document" => ["The provided document does not belongs to your user."]
]
]
]);
Expand All @@ -97,7 +97,7 @@ public function handler(TransactionCreateDto $dto): UserEntity | TransactionEnti
"error" => [
"message" => "Failed to find document",
"fields" => [
"document" => "The provided document was not founded."
"document" => ["The provided document was not founded."]
]
]
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class TransactionUploadCheckDto
{

private $user_id;
private $filename;

public function __construct(array $data)
{
Expand All @@ -20,4 +21,9 @@ public function getUserId()
{
return $this->user_id ?? null;
}

public function getFilename()
{
return $this->filename ?? null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Exceptions\JsonException;
use Illuminate\Support\Facades\Validator;
use App\Domain\Providers\IStorageProvider;
use App\Domain\Entities\PresignedUrlFormEntity;
use App\Domain\Usecases\TransactionUploadCheck\TransactionUploadCheckDto;

class TransactionUploadCheckUseCase
Expand All @@ -14,13 +15,19 @@ public function __construct(private readonly IStorageProvider $storageProvider)
{
}

public function handler(TransactionUploadCheckDto $dto): string
public function handler(TransactionUploadCheckDto $dto): PresignedUrlFormEntity
{

// create validate schema
$validator = Validator::make(
['user_id' => $dto->getUserId()],
['user_id' => 'required|string']
[
'user_id' => $dto->getUserId(),
"filename" => $dto->getFilename()
],
[
'user_id' => 'required|string',
'filename' => 'required|string',
]
);

// validate provided data
Expand All @@ -33,11 +40,14 @@ public function handler(TransactionUploadCheckDto $dto): string
]);
}

// Extract the extension from the provided string
$extension = pathinfo($dto->getFilename(), PATHINFO_EXTENSION);

// generate a filename with user_id + uuidv4
// user_id is used to retrieve document and validate user on create transaction
// uuidv4 is used to generate a unique document filename
$uuid = Uuid::uuid4();
$filename = $dto->getUserId() . "_" . $uuid->toString() . ".png";
$filename = $dto->getUserId() . "_" . $uuid->toString() . "." . $extension;
return $this->storageProvider->generateSignedUrl($filename);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\_Controller;
use App\Infrastructure\Providers\Storage\AwsS3Provider;
use App\Domain\Usecases\TransactionUploadCheck\TransactionUploadCheckDto;
Expand All @@ -18,12 +19,14 @@ public function __construct()
);
}

public function handler()
public function handler(Request $request)
{
$user = auth()->user();
$filename = $request->query(("filename"));
$signedUrl = $this->transactionUploadCheckUsecase->handler(new TransactionUploadCheckDto([
"user_id" => $user->_id
"user_id" => $user->_id,
"filename" => $filename
]));
return response()->json(["url" => $signedUrl]);
return response()->json($signedUrl->toJson());
}
}
39 changes: 26 additions & 13 deletions backend/app/Infrastructure/Providers/Storage/AwsS3Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
namespace App\Infrastructure\Providers\Storage;

use Aws\Sdk;
use Exception;
use Aws\S3\S3Client;
use Aws\S3\PostObjectV4;
use Aws\Credentials\Credentials;
use App\Domain\Providers\IStorageProvider;
use Exception;
use App\Domain\Entities\PresignedUrlFormEntity;

class AwsS3Provider implements IStorageProvider
{
Expand All @@ -26,19 +28,30 @@ public function __construct()
$this->client = $sdk->createS3();
}

public function generateSignedUrl(string $document_key): string
public function generateSignedUrl(string $document_key): PresignedUrlFormEntity
{
// mount command
$expiry = "+5 minutes";
$cmd = $this->client->getCommand('PutObject', [
'Bucket' => $this->bucket,
'Key' => "tmp" . "/" . $document_key,
'ACL' => 'private',
], []);
// execute command
$request = $this->client->createPresignedRequest($cmd, $expiry);
// return url result
return $request->getUri();
// Generate the form
$postObject = new PostObjectV4(
$this->client,
$this->bucket,
[
'acl' => 'private',
"bucket" => $this->bucket,
'key' => "tmp" . "/" . $document_key,
],
[
['acl' => 'private'],
["bucket" => $this->bucket],
['key' => "tmp" . "/" . $document_key],
],
'+5 minutes'
);

// return build form entity
return new PresignedUrlFormEntity([
"url" => $postObject->getFormAttributes()['action'],
"fields" => $postObject->getFormInputs()
]);
}

public function checkTmpDocument(string $document_key): string | null
Expand Down
6 changes: 3 additions & 3 deletions backend/routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
use App\Http\Controllers\AuthLogoutController;
use App\Http\Controllers\AuthRegisterController;
use App\Http\Controllers\AuthUserDataController;
use App\Http\Controllers\TransactionCheckUrlController;
use App\Http\Controllers\TransactionCreateController;
use App\Http\Controllers\UserBalanceSummaryController;
use App\Http\Controllers\TransactionCheckUrlController;
use App\Http\Controllers\TransactionFindForUserController;
use App\Http\Controllers\TransactionFindPendingController;
use App\Http\Controllers\TransactionUploadCheckController;
use App\Http\Controllers\TransactionUpdateStatusController;
use App\Http\Controllers\UserBalanceSummaryController;

/*
|--------------------------------------------------------------------------
Expand Down Expand Up @@ -42,6 +42,6 @@
Route::get("balance/summary", [UserBalanceSummaryController::class, 'handler']);
Route::post('transactions', [TransactionCreateController::class, 'handler']);
Route::get('transactions', [TransactionFindForUserController::class, 'handler']);
Route::post('transactions/upload', [TransactionUploadCheckController::class, 'handler']);
Route::get('transactions/upload', [TransactionUploadCheckController::class, 'handler']);
});
});
27 changes: 15 additions & 12 deletions frontend/src/pages/deposits_new/controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useCallback, useMemo, useState } from 'react'
import { HttpMessageType } from '@type/http_error_type'
import { DepositsNewPageControllerProps } from './types'
import { FormDepositType } from '@type/form_deposit_type'
import { PresignedUrlType } from '@type/presigned_url_type'
import { TransactionsCustomerApi } from '@services/api/transactions_customer_api'

export const DepositsNewPageController = ({
Expand All @@ -16,7 +17,9 @@ export const DepositsNewPageController = ({
const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar()
const [loading, setLoading] = useState<boolean>(false)
const [uploadUrl, setUploadUrl] = useState<string | undefined>(undefined)
const [presigned, setPresigned] = useState<PresignedUrlType | undefined>(
undefined
)
const [error, setError] = useState<HttpMessageType | undefined>(undefined)

// 3ªChain - Create transaction with uploaded document
Expand All @@ -40,7 +43,7 @@ export const DepositsNewPageController = ({

// 2ªChain - Upload document to signed URL
const handleUploadDocument = useCallback(
(url: string, form: FormDepositType) => {
(presign: PresignedUrlType, form: FormDepositType) => {
// return if file not selected
if (!form.file) {
return [
Expand All @@ -55,14 +58,17 @@ export const DepositsNewPageController = ({
}

// extract document name and mount FormData
const document = new URL(url).pathname.replace(/\/$/, '').split('/').pop()
const document = presign.fields?.key?.split('/')?.pop()
const formData = new FormData()
for (const fieldKey in presign?.fields || []) {
formData.append(fieldKey, presign?.fields?.[fieldKey] || '')
}
formData.append('file', form.file)

// call presigned url with file to upload
const headers = { 'Content-Type': 'multipart/form-data' }
axios
.put(url, formData, { headers })
.post(presign.url, formData, { headers })
.then((r) => [handleCreateDeposit({ ...form, document })])
.catch((e) => [
setLoading(false),
Expand All @@ -80,14 +86,11 @@ export const DepositsNewPageController = ({
// 1ªChain - Generage presigned URL
const handleLoadPresignedUrl = useCallback(
(form: FormDepositType) => {
if (uploadUrl) return handleUploadDocument(uploadUrl, form)
if (presigned) return handleUploadDocument(presigned, form)
api
.instanceOf<TransactionsCustomerApi>(TransactionsCustomerApi)
.presignUpload()
.then((res) => [
setUploadUrl(res.url),
handleUploadDocument(res.url, form),
])
.presignUpload({ filename: form.file?.name })
.then((res) => [setPresigned(res), handleUploadDocument(res, form)])
.catch((error) => [
setLoading(false),
setError(error.response.data),
Expand All @@ -96,9 +99,9 @@ export const DepositsNewPageController = ({
},
[
api,
uploadUrl,
presigned,
setError,
setUploadUrl,
setPresigned,
enqueueSnackbar,
handleUploadDocument,
]
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/services/api/transactions_customer_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FormDepositType } from '@type/form_deposit_type'
import { TransactionEntity } from '@entities/TransactionEntity'
import { PresignedUrlType } from '@type/presigned_url_type'
import { FormFindTransactionsType } from '@type/form_find_transactions_type'
import { FormPresignUploadType } from '@type/form_presign_upload_type'

export class TransactionsCustomerApi extends ApiAction {
async purchase(payload: FormPurchaseType): Promise<UserEntity> {
Expand All @@ -21,9 +22,9 @@ export class TransactionsCustomerApi extends ApiAction {
)
return response.data
}
async presignUpload(): Promise<PresignedUrlType> {
const response = await this.http.post<PresignedUrlType>(
`/api/transactions/upload`
async presignUpload(props: FormPresignUploadType): Promise<PresignedUrlType> {
const response = await this.http.get<PresignedUrlType>(
`/api/transactions/upload?filename=${props.filename}`
)
return response.data
}
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/types/form_presign_upload_type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type FormPresignUploadType = {
filename?: string
}
1 change: 1 addition & 0 deletions frontend/src/types/presigned_url_type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type PresignedUrlType = {
url: string
fields?: Record<string, string>
}

0 comments on commit 3db9bc1

Please sign in to comment.