This repository contains the Node.js backend for the PeduliTernak project, encompassing implementation of public endpoints gateway, database connection, and user administration. The backend allows users to create an account and detect disease in cattle (perform image recognition and get survey prediction) using API request methods.
This application is connected to the Firestore Database, Cloud Storage, and Cloud Run microservice.
When a client makes prediction requests, this application synchronously sends requests to the private Cloud Run microservice to perform recognition (because the ML Models are deployed there). The results are then stored in the database and returned to the client as a response.
more: diagram
-
Go to Python-Flask Microservice, start the Flask application, and get the Base URL
-
Create and place Firestore, Cloud Storage, and Cloud Run service account key file in the root directory
Roles:
- Firestore: Cloud Datastore User
- Cloud Storage: Cloud Storage Object User
- Cloud Run: Cloud Run Invoker (role applied to the Python-Flask Microservice as a service identity)
Note: Cloud Run Invoker service account is optional, in case you do not want to make the Python-Flask Microservice private
-
Add this values to Environment Variables or file
.env
PROJECT_ID=your-project-id FIRESTORE_SERVICE_ACCOUNT_KEY_FILE=firestore-sa.json CLOUD_STORAGE_SERVICE_ACCOUNT_KEY_FILE=cloudStorage-sa.json CLOUD_RUN_INVOKER_SERVICE_ACCOUNT_KEY_FILE=cloudRunInvoker-sa.json BUCKET=bucket-name SECRET=something-secret PREDICTION_MICRO_SERVICE_URL=http://flask-base-url.com/
Note:
- the value of
PREDICTION_MICRO_SERVICE_URL
is the Base URL from step 1 CLOUD_RUN_INVOKER_SERVICE_ACCOUNT_KEY_FILE
is optional
- the value of
-
Start the application
npm install npm start
-
You needs to deploy Python-Flask Microservice first, and get the Base URL
-
Do all things on the How to Use section (except "Start the application" section)
-
Build the Docker image and run the Docker container
docker build -t node-api . docker run -p 8080:8080 -d node-api
or Deploy to Cloud Run by configuring the
cloudbuild.yaml
and.gcloudignore
, then submit itgcloud builds submit --config=cloudbuild.yaml
-
Do all things on the How to Use section (except
npm start
) -
Add test values to Environment Variables or file
.env
(or just keep the same values as non-testing variables above)TEST_PROJECT_ID=test-gcp-capstone TEST_FIRESTORE_SERVICE_ACCOUNT_KEY_FILE=test-firestore-sa.json TEST_CLOUD_STORAGE_SERVICE_ACCOUNT_KEY_FILE=test-cloudStorage-sa.json TEST_CLOUD_RUN_INVOKER_SERVICE_ACCOUNT_KEY_FILE=cloudRunInvoker-sa.json TEST_BUCKET=test-bucket-name
-
Place an image on the
tests/
directory namedimage.jpg
(to perform image recognition/prediction testing) -
Run the tests
npm test
Base URL: https://node-api-74e64w7rga-et.a.run.app
Route | HTTP Method | Description | Token Required? |
---|---|---|---|
/api/register | POST | Sign up a new user | - |
/api/login | POST | Login user | - |
/api/user | GET | Get user data | Yes |
/api/user | PUT | Change user data | Yes |
/api/user | DELETE | Delete user and all prediction history | Yes |
/api/prediction | POST | Predict the image and save it to database | Yes |
/api/prediction | GET | Get all the user's prediction history | Yes |
/api/prediction/:id | GET | Get one prediction history by id | Yes |
/api/prediction/:id | DELETE | Delete one prediction history by id | Yes |
tip: just use crtl+f
You will receive the token after succesfully request to /api/register
or /api/login
.
Put the token in the Header with Authorization
key and Bearer <token>
value.
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Otherwise, the request will response 401 Unauthorized
{
"status": false,
"message": "unauthorized access"
}
- Method: POST
- Path:
/api/register
- Body:
{ "username": "john_doe", "name": "John Doe", "noTelepon": "6285123456", "password": "password123" }
-
Status: 200 OK
{ "status": true, "message": "register success", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { "username": "john_doe", "name": "John Doe", "noTelepon": "6285123456" } }
-
Status: 400 Bad Request
{ "status": false, "message": "invalid request argument" }
-
Status: 400 Bad Request
-
Password must at least 8 characters
-
Password must be part of the ASCII character
{ "status": false, "message": "invalid password" }
-
-
Status: 400 Bad Request
-
Phone number must starts with 62
-
Phone number minimum length must be to 9 characters
-
Phone number maximum length must be to 15 characters
{ "status": false, "message": "invalid phone number" }
-
-
Status: 409 Conflict
{ "status": false, "message": "username is already exist" }
-
Status: 409 Conflict
{ "status": false, "message": "noTelepon is already exist" }
- Method: POST
- Path:
/api/login
- Body:
or
{ "username": "john_doe", "password": "password123" }
{ "noTelepon": "6285123456", "password": "password123" }
-
Status: 200 OK
{ "status": true, "message": "login success", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { "username": "john_doe", "name": "John Doe", "noTelepon": "6285123456" } }
-
Status: 400 Bad Request
{ "status": false, "message": "invalid request argument" }
-
Status: 401 Unauthorized
{ "status": false, "message": "invalid username" }
-
Status: 401 Unauthorized
{ "status": false, "message": "invalid noTelepon" }
-
Status: 401 Unauthorized
{ "status": false, "message": "invalid password" }
- Method: GET
- Path:
/api/user
- Status: 200 OK
{ "status": true, "user": { "username": "john_doe", "name": "John Doe", "noTelepon": "6285123456" } }
- Method: PUT
- Path:
/api/user
- Body: (no need to specify all fields)
{ "name": "Updated Name", "noTelepon": "6285123456", "password": "new_password" }
-
Status: 200 OK
{ "status": true, "message": "user data updated successfully", "user": { "username": "john_doe", "name": "Updated Name", "noTelepon": "6285123456" } }
-
Status: 400 Bad Request
{ "status": false, "message": "invalid request argument" }
-
Status: 409 Conflict
{ "status": false, "message": "noTelepon is already exist" }
- Method: DELETE
- Path:
/api/user
- Status: 204 No Content
-
Method: POST
-
Path:
/api/prediction
-
Body:
- Form-Data with a single file field named
file
- The file must be part of
image/*
mime types (an image like .jpg, .png) - Maximum file size is 5 MB
and
-
Form-Data with a string of list (matrix) of gejala penyakit field named
gejala_matrix
"file": image.jpg "gejala_matrix": "[0, 0, 0, 0, 1, 1, 0, ...]"
- Form-Data with a single file field named
-
Status: 200 OK
Prediction result can be 0 or more.
{ "status": true, "prediction": { "id": "john_doe-uniqueId", "imageUrl": "https://storage.googleapis.com/bucketName/image.jpg", "result": { "penyakit": ["Masitis", "Penyakit 2"], "penanganan": [ "Menjaga kandang untuk tetap bersih. Memakai antiseptik ...", "Deskripsi penanganan dari penyakit 2" ] } } }
-
Status: 400 Bad Request
{ "status": false, "message": "invalid request argument or file format" }
- Method: GET
- Path:
/api/prediction
- Status: 200 OK
{ "status": true, "predictions": [ { "id": "john_doe-uniqueId", "imageUrl": "https://storage.googleapis.com/bucketName/image.jpg", "result": { "penyakit": ["Masitis", "Penyakit 2"], "penanganan": [ "Menjaga kandang untuk tetap bersih. Memakai antiseptik ...", "Deskripsi penanganan untuk penyakit 2", ], }, }, { "id": "john_doe-uniqueId2", "imageUrl": "https://storage.googleapis.com/bucketName/image2.jpg", "result": { "penyakit": ["Penyakit 1"], "penanganan": ["Deskripsi penanganan untuk penyakit 1"], }, }, { "id": "john_doe-uniqueId3", "imageUrl": "https://storage.googleapis.com/bucketName/image3.jpg", "result": { "penyakit": [], "penanganan": [] }, }, ... another results, ], }
- Method: GET
- Path:
/api/prediction/:id
-
Status: 200 OK
{ "status": true, "prediction": { "id": "john_doe-uniqueId", "imageUrl": "https://storage.googleapis.com/bucketName/image.jpg", "result": { "penyakit": ["Masitis", "Penyakit 2"], "penanganan": [ "Menjaga kandang untuk tetap bersih. Memakai antiseptik ...", "Deskripsi penanganan dari penyakit 2" ] } } }
-
Status: 404 Not Found
{ "status": false, "message": "prediction id is not found" }
- Method: DELETE
- Path:
/api/prediction/:id
-
Status: 204 No Content
-
Status: 404 Not Found
{ "status": false, "message": "prediction id is not found" }