Nam Còi
  • Trang chủ
  • Lập Trình Website
  • Khóa Học
  • Động Lực
  • Tuyển Dụng
No Result
View All Result
  • Trang chủ
  • Lập Trình Website
  • Khóa Học
  • Động Lực
  • Tuyển Dụng
No Result
View All Result
Nam Còi
No Result
View All Result
Home Lập Trình Website Laravel

Upload Multiple Files và đính kèm vào Task sử dụng Teamwork API thông qua PHP Laravel API

Nguyễn Hải Nam by Nguyễn Hải Nam
22/02/2023
in Laravel, PHP
0
teamwork_logo

teamwork_logo

0
SHARES
86
VIEWS

Sử dụng back-end PHP Laravel API để Upload Multiple Files và đính kèm files vào task sử dụng Teamwork API có thể gây nhiều rắc rối. Bài viết này sẽ hướng dẫn thiết lập API, tương thích với Teamwork API, thiết lập Feature Test và ví dụ trên Postman

teamwork-logo.png
teamwork-logo.png

Upload Multiple Files sử dụng File uploading via the API (Classic)

Bước đầu tiên, thiết lập API route trong file api.php, với phương thức POST:

                # Uploading Files
                # POST - /teamwork/uploading-files
                Route::post('uploading-files',
                [
                    TeamworkController::class,
                    'uploadingFiles'
                ])->name('uploading-files');

Tiếp theo, trong phần Controller, chỉ là các logic đơn giản như bên dưới, mình cần phải ẩn một số thông số vì lý do thông tin bảo mật cho dự án:

    public function uploadingFiles(
        UploadingFilesRequest $request
    ): Response
    {
        try {
            $files = $request->file('files');

            $teamworkUrl = env('TEAMWORK_URL', 'YOUR_TEAMWORK_URL_HERE');
            $teamworkEndpointFirstStep = "{$teamworkUrl}/pendingFiles.json";
            $teamworkUserServiceRequestProjectId = env('TEAMWORK_USER_SERVICE_REQUEST_PROJECT_ID', YOUR_PROJECT_ID_HERE);
            $teamworkEndpointSecondStep = "{$teamworkUrl}/projects/{$teamworkUserServiceRequestProjectId}/files.json";

            $authorization = 'Basic ' . base64_encode(env('TEAMWORK_API_TOKEN', 'YOUR_TOKEN_HERE'));

            $result = [];
            foreach ($files as $index => $file) {
                $fileName = $file['file']->getClientOriginalName();
                $filePath = $file['file']->getPathname();
                $preSigned = Http::withHeaders(
                    [
                        'Authorization' => $authorization,
                    ]
                )->attach('file',file_get_contents($filePath),$fileName)->post($teamworkEndpointFirstStep);

                $fileRef = json_decode($preSigned->body())->pendingFile->ref;

                $data = [
                        'description' => $fileName,
                        'pendingFileRef' => $fileRef,
                ];

                $response = Http::withHeaders(
                    [
                        'Authorization' => $authorization,
                    ]
                )->post($teamworkEndpointSecondStep,[
                    'file' => $data
                ]);

                $result[$index] = json_decode($response,TRUE)['fileId'];
            }
            return response_success($result);
        } catch (Exception $e) {
            return response_error("[Teamwork Controller] Upload Files Function - {$e}");
        }
    }

Lưu ý: API File uploading via the API (Preferred) https://apidocs.teamwork.com/docs/teamwork/d29b91f3e6558-file-uploading-via-the-api-preferred hoàn toàn không work sau khi mình thử nhiều cách với phương thức mình sử dụng, nhưng sử dụng Javascript thì hoạt động ổn. Vì vậy, chúng ta phải sử dụng: File uploading via the API (Classic): https://apidocs.teamwork.com/docs/teamwork/86ecebd6161af-file-uploading-via-the-api-classic

Theo như sự hỗ trợ từ phía Teamwork Support thì:

Unfortunately we’ve lost our API Support Team and I’ll be stepping in here from our Technical Services team to assist.

For Classic Upload; https://apidocs.teamwork.com/docs/teamwork/86ecebd6161af-file-uploading-via-the-api-classic

Step 1 – Create a Pending File Handle
The first thing to do is to get the actual file uploaded to Teamwork .
To do this you make an API Call to POST /pendingFiles.json. The actual file contents are sent via a form field called “file”.

If this was successful you will receive a Status Code of 201 (Created) and a structure containing a reference code.
{ “pendingFile”: { “ref” : “tf_xxxxxxxxxxxxxxxx” } }

The ref bit is the important part and is the Pending File Handle

Step 2 – Create an actual file object using the Pending File Handle
The next step is to create the actual file object in a project on your Teamwork account.
To do this you make an API Call to POST /projects/{id}/files.json where {id} is the ID of the project you want to create the file in.
{
“file”: {
“description”: “Optional string describing the file”,
“category-id”: “ID of the category you to create the file in. Pass 0 if no category”,
“category-name”: “String if you want to create a new category – Pass category-id=0 also”,
“pendingFileRef”: “tf_xxxxxxxxxxxxxxxx”
}
}

In the JSON packet above you substitute the Pending File Ref you received back from the POST /pendingFiles.json API Call in Step 1.

Teamwork API Team Support

Ngoài ra, chúng ta cũng cần thiết lập Request UploadingFilesRequest như bên dưới:

    public function rules(): array
    {
        return [
            'files' => 'required|array',
            'files.*.file' => 'required',
        ];
    }


    /**
     * Body Parameters of this form
     *
     * @return array
     */
    final public function bodyParameters(): array
    {
        return [
            'files' => [
                'description' => 'The array file of this meeting',
                'example' => [],
            ],
            'files.*.file' => [
                'description' => 'The file need to upload',
            ],
        ];
    }

Dễ dàng nhận thấy rằng, dạng files upload lên sẽ là:

files[]
files[0][file]
files[1][file]
...

Tiếp theo, chúng ta viết Feature Test, ý tưởng là test về permission và test upload 2 files lên Teamwork:

    public function testTeamworkUploadFiles()
    {
        $apiPath = self::API_PATH . "/uploading-files";
        $company = Company::query()
            ->inRandomOrder()
            ->first();
        $user = $company->owner;

        # Unauthenticated
        $response = $this->post($apiPath);
        $response->assertUnauthorized()
            ->assertJson(TestConstant::JSON_UNAUTHENTICATED);

        # Successfully
        # Company
        Storage::fake('files');
        $files = [];
        $files[0]['file'] = UploadedFile::fake()
            ->createWithContent("test-1.pdf", 'abc');

        $files[1]['file'] = UploadedFile::fake()
            ->createWithContent("test-2.pdf", 'abc');
        $data = [
            'files' => $files,
        ];
        # Successfully
        $response = $this->actingAs($user, 'api')
            ->post($apiPath, $data);

        $response->assertSuccessful()
            ->assertJson(TestConstant::JSON_SUCCESS)
            ->assertJsonStructure(TestConstant::JSON_STRUCTURE_VALUES);
    }

Cuối cùng, chúng ta sẽ test trên Postman để gửi ví dụ này cho Front-end Team hoặc các bên đối tác khác.
Điểm lưu ý ở đây là key của form phải là: files[0][file], thay vì chỉ là files hoặc files[0].file:

test-postman-upload-multiple-files-teamwork-api
test-postman-upload-multiple-files-teamwork-api

Tạo task trên Teamwork đính kèm files đã upload với API Create a Task

Đầu tiên, thiết lập API trên Laravel:

                # Create Additional Service Task
                # POST - /teamwork/additional-services
                Route::post('additional-service',
                [
                    TeamworkController::class,
                    'createAdditionalService'
                ])->name('additional-service');

Tiếp theo, viết logic trong Controller (không sử dụng Service để tiết kiệm thời gian, mặc dù không phải là best practice), chúng ta sẽ sử dụng Teamwork API https://apidocs.teamwork.com/docs/teamwork/cd8948166b1b1-create-a-task

    final public function createAdditionalService(
        CreateAdditionalServiceRequest $request
    ): Response {
        try {
            $teamworkUrl = env('TEAMWORK_URL', 'YOUR_URL_HERE');
            $teamworkUserServiceInboxTaskListId = env('TEAMWORK_USER_SERVICE_INBOX_TASK_LIST_ID',YOUR_LIST_ID_HERE);
            $teamworkEndpoint = "{$teamworkUrl}/tasklists/{$teamworkUserServiceInboxTaskListId}/tasks.json";
            $authorization = 'Basic ' . base64_encode(env('TEAMWORK_API_TOKEN', 'YOUR_TOKEN_HERE'));
            $data = [
                "todo-item" => [
                    "content" => env('APP_ENV','unknown') . " - " . $request->input('content'),
                    "description" => $request->input('description'),
                    "notify" => env('TEAMWORK_NOTIFY',FALSE),
                    "responsible-party-id" => env('TEAMWORK_RESPONSIBLE_PARTY_ID_USER_SERVICE_REQUEST',"YOUR_RESPONSIBLE_PARTY_ID_HERE"),
                    "attachments" => $request->input('attachments') ?? ""
                ]
            ];

            $response = Http::withHeaders(
                [
                    'Authorization' => $authorization,
                ]
            )->post($teamworkEndpoint, $data);

            return response_success(json_decode($response, true));
        } catch (Exception $e) {
            return response_error("[Teamwork Controller] Create Additional Service On Teamwork Function - {$e}");
        }
    }

File Request CreateAdditionalServiceRequest

    public function rules(): array
    {
        return [
            'content' => 'required|string',
            'description' => 'required|string',
            'attachments' => 'nullable|string',
        ];
    }

    /**
     * Body Parameters of this form
     *
     * @return array
     */
    final public function bodyParameters(): array
    {
        return [
            'content' => [
                'description' => 'The title of a Teamwork Task',
                'example' => 'Nick has requested for Perform a capital increase',
            ],
            'description' => [
                'description' => 'The description of a Teamwork Task',
                'example' => '#### Requester\n          \n * Nick\n        \n\n#### Order Data\n',
            ],
            'attachments' => [
                'description' => 'The file Id(s) attached to a Teamwork Task, refer to API Teamwork Upload Files',
                'example' => "1343151,1343152"
            ]
        ];
    }

Theo như hướng dẫn từ tài liệu của Teamwork How do I upload a file with the API? https://apidocs.teamwork.com/docs/teamwork/f92e3220710ec-how-do-i-upload-a-file-with-the-api, họ hướng dẫn đính kèm files với field tên là “pendingFileAttachments“, tuy nhiên, field này chỉ sử dụng cho API File uploading via the API (Preferred): https://apidocs.teamwork.com/docs/teamwork/d29b91f3e6558-file-uploading-via-the-api-preferred.

Để sử dụng đúng, chúng ta phải dùng field “attachments” đi cùng với API File uploading via the API (Classic) https://apidocs.teamwork.com/docs/teamwork/86ecebd6161af-file-uploading-via-the-api-classic, data gửi lên sẽ có dạng: “file_id_1,file_id_2”, tương ứng với ví dụ ở trên thì chúng ta phải lấy giá trị “values” và sử dụng built-in function implode của PHP để nối chuỗi nếu sử dụng Feature Test như bên dưới:

    public function testCreateAdditionalService()
    {
        $apiPath = self::API_PATH . "/additional-service";
        $company = Company::query()
            ->inRandomOrder()
            ->first();
        $user = $company->owner;

        # Unauthenticated
        $response = $this->post($apiPath);
        $response->assertUnauthorized()
            ->assertJson(TestConstant::JSON_UNAUTHENTICATED);

        # Successfully
        $request = [
            "content" => "{$company->name} has requested for Perform a capital increase",
            "description" => "test",
        ];

        $response = $this->actingAs($user, 'api')
            ->post($apiPath, $request);

        $response->assertSuccessful()
            ->assertJson(
                array_merge(
                    TestConstant::JSON_SUCCESS,
                    [
                        'data' => [
                            'STATUS' => 'OK',
                        ]
                    ]
                )
            )
            ->assertJsonStructure(
                array_merge(
                    TestConstant::JSON_STRUCTURE,
                    [
                        'data' => [
                            'affectedTaskIds',
                            'id',
                            'STATUS',
                        ]
                    ]
                )
            );

        # Successfully with Files
        Storage::fake('files');
        $files = [];
        $files[0]['file'] = UploadedFile::fake()
            ->createWithContent("test-1.pdf", 'abc');

        $files[1]['file'] = UploadedFile::fake()
            ->createWithContent("test-2.pdf", 'def');
        $data = [
            'files' => $files,
        ];

        $uploadFilesApiPath = self::API_PATH . "/uploading-files";
        $filesUploaded = $this->actingAs($user, 'api')
            ->post($uploadFilesApiPath, $data);
        $fileIds = implode(",", $filesUploaded['data']['values']);

        $request = [
            "content" => "{$company->name} has requested for Perform a capital increase",
            "description" => "test",
            "attachments" => $fileIds,
        ];

        $response = $this->actingAs($user, 'api')
            ->post($apiPath, $request);

        $response->assertSuccessful()
            ->assertJson(
                array_merge(
                    TestConstant::JSON_SUCCESS,
                    [
                        'data' => [
                            'STATUS' => 'OK',
                        ]
                    ]
                )
            )
            ->assertJsonStructure(
                array_merge(
                    TestConstant::JSON_STRUCTURE,
                    [
                        'data' => [
                            'affectedTaskIds',
                            'id',
                            'STATUS',
                        ]
                    ]
                )
            );
    }

Logic của test sẽ là: test permission, test tạo task không có files đi kèm, test tạo task với files đi kèm và phải lưu ý về attachments field, test Postman như bên dưới:

test-postman-create-task-teamwork-api
test-postman-create-task-teamwork-api

Vì tài liệu của Teamwork API không rõ ràng cũng như các thiếu sự hỗ trợ rất nhiều nên team mình đã gặp một số khó khăn với Teamwork API như các lỗi bên dưới khi sử dụng API File uploading via the API (Preferred):

“value”: “Something went wrong: GuzzleHttp\Exception\ClientException: Client error: `PUT https://tw-bucket.s3-accelerate.amazonaws.com

“value”: “Something went wrong: Illuminate\\Http\\Client\\RequestException: HTTP request returned status code 403:\n<?xml version=\”1.0\” encoding=\”UTF-8\”?>\n<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calcul (truncated…)\n

Lỗi này có thể là do phía tương thích với AWS S3 signature mà package HTTP của Laravel hoặc thậm chí là curl đều không thể gửi files lên được.

Related

Tags: laravel apiteamwork api
Previous Post

Dự Án Crabada – Cua Siêu Đắt

Next Post

Webinoly – Trọn Bộ LEMP – Tối Ưu Hóa Máy Chủ Web Chạy NGINX

Nguyễn Hải Nam

Nguyễn Hải Nam

Mình là Nam - nick name ở nhà là Còi, trong tiếng Anh là Nick. Hiện nay, mình đang làm việc tại Axon Active Việt Nam với vị trí là Scrum Master. Ngoài ra, mình còn tham gia thỉnh giảng khóa học Lập Trình Ứng Dụng Website với PHP & MySQL. tại Softech Aptech Đà Nẵng.

Next Post
webinoly-logo

Webinoly - Trọn Bộ LEMP - Tối Ưu Hóa Máy Chủ Web Chạy NGINX

Trả lời Hủy

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Recent News

  • Handling Vericlock Webhooks: Overcoming Challenges and Optimizing Costs
  • Webinoly – Trọn Bộ LEMP – Tối Ưu Hóa Máy Chủ Web Chạy NGINX
  • Upload Multiple Files và đính kèm vào Task sử dụng Teamwork API thông qua PHP Laravel API

Category

  • Aptech PHP
  • Command Line
  • Động Lực
  • Git
  • Khóa Học
  • Lập Trình Website
  • Laravel
  • Nginx
  • Phần Mềm
  • PHP
  • Software
  • SQL
  • Tiền Mã Hoá
  • Tuyển Dụng
  • WordPress

Thông Tin Website

  • Các Điều Khoản Và Điều Kiện
  • Chính Sách Bảo Mật
  • Liên Hệ

Liên Kết

  • Vape PHP Đà Nẵng Website
  • Nga Đức Ninh Thuận Website

Bản Quyền © 2018 - | Nam Còi | [email protected]

No Result
View All Result
  • Trang chủ
  • Lập Trình Website
  • Khóa Học
  • Động Lực
  • Tuyển Dụng

Bản Quyền © 2018 - | Nam Còi | [email protected]