Skip to content

About

Express 4 + Mongoose REST API for Motoran — JWT auth, inventory, order lifecycle, payments and presigned Cloudflare R2 uploads. II3120 project at STEI ITB.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

API Tubes Lasti

Comprehensive documentation for the API endpoints provided by the Tubes Lasti application. Each endpoint includes details on request payloads, responses, and usage.

Table of Contents


Deployment

Untuk menjalankan pada perangkat lokal, jalankan

npm run start

Environment variables

Variable Keterangan
MONGODB_URI Connection string MongoDB
JWT_SECRET Secret penandatangan JWT. Server menolak start kalau kosong.
PORT Port HTTP (default 8080)
R2_ACCOUNT_ID Account ID Cloudflare R2
R2_ACCESS_KEY Access key ID R2
R2_SECRET_KEY Secret access key R2
R2_BUCKET Nama bucket
R2_PUBLIC_URL Base URL publik bucket, dipakai untuk gambar inventory

Bucket R2 harus punya CORS policy yang mengizinkan PUT dari origin frontend, karena browser mengunggah langsung ke R2:

[{ "AllowedOrigins": ["https://motoran.vercel.app", "http://localhost:5173"],
   "AllowedMethods": ["PUT"],
   "AllowedHeaders": ["content-type"],
   "MaxAgeSeconds": 3600 }]

Untuk membuat docker image backend, jalankan

docker build . -t motoran

Jalankan dengan melakukan run atau menggunakan docker compose


API Documentation Table

Method Endpoint Usage
POST /register Membuat account baru
POST /login Login dengan account yang telah didaftarkan dan mendapatkan JWT token
POST /login/verify Melakukan verifikasi JWT token dan memperbaruinya
GET /profile Mendapatkan data diri account yang sedang login
PUT /password Mengganti password account yang sedang login
POST /uploads/presign Meminta presigned URL untuk mengunggah gambar langsung ke R2
GET /inventories Mendapatkan seluruh inventory yang ada
POST /inventories Membuat inventory baru
PUT /inventories/:id Mengupdate inventory berdasarkan id
DELETE /inventories/:id Menghapus inventory berdasarkan id
GET /orders Mendapatkan seluruh order yang pernah dibuat oleh account tertentu
GET /orders/:id Mendapatkan order dengan id tertentu
POST /orders Membuat order baru
PUT /orders/:id/paidstatus Mengubah status pembayaran dari uncomplete menjadi completed
PUT /orders/:id/takenstatus Mengubah status pembayaran dari untaken menjadi taken
PUT /orders/:id/returnedstatus Mengubah status pembayaran dari unreturned menjadi returned
GET /payment/:id/barcode Menghasilkan link yang akan dibuat untuk payment (nantinya di frontend diubah menjadi QR Code)
PUT /payment/:id/pay Mengganti status payment menjadi sudah dibayar

Endpoints

Authentication

POST /register

  • Description: Creates a new user account.
  • Request Body:
    {
      "name": "string",
      "email": "string",
      "password": "string (min. 8 characters)"
    }
  • Responses:
    • 201 (Success):
      {
        "status": "success",
        "message": "Register success",
        "data": {}
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Register error: {error message}",
        "data": {}
      }

POST /login

  • Description: Authenticates a user and returns a JWT token.
  • Request Body:
    {
      "email": "string",
      "password": "string"
    }
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Login success",
        "data": {
          "user": "email",
          "token": "string",
          "isAdmin": "boolean"
        }
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Login error: {error message}",
        "data": {}
      }

GET /login/verify

  • Description: Verify existing JWT token and generate the updated one.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Login success",
        "data": {
          "user": "email",
          "token": "string",
          "isAdmin": "boolean"
        }
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Login error: {error message}",
        "data": {}
      }

GET /profile

  • Description: Returns the signed-in account's own details. The password hash is never included.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "GET Profile success",
        "data": {
          "profile": {
            "name": "string",
            "email": "email",
            "isAdmin": "boolean"
          }
        }
      }
    • 404 (User Not Found):
      {
        "status": "error",
        "message": "Error GET Profile: User not found",
        "data": {}
      }

PUT /password

  • Description: Changes the signed-in account's password. The current password must be supplied and is verified before the new one is written.
  • Authorization: Bearer Token
  • Request Body:
    {
      "currentPassword": "string",
      "newPassword": "string (min 8 characters)"
    }
  • Notes: Existing tokens are not revoked — verifyToken checks the JWT signature only and never re-reads the user, so any token issued before the change stays valid until it expires. Clients should discard their session and re-authenticate.
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Password updated successfully",
        "data": {}
      }
    • 400 (Invalid Request): wrong current password, a new password shorter than 8 characters, a new password identical to the current one, or a non-string field.
      {
        "status": "error",
        "message": "Error PUT Password: current password is incorrect",
        "data": {}
      }
    • 404 (User Not Found):
      {
        "status": "error",
        "message": "Error PUT Password: User not found",
        "data": {}
      }

Uploads

Gambar tidak lagi dikirim ke API. Alurnya tiga langkah:

  1. POST /uploads/presign untuk mendapatkan uploadUrl dan key.
  2. PUT berkasnya langsung ke uploadUrl dengan header Content-Type yang sama persis dengan yang diminta pada langkah 1 — header itu ikut ditandatangani, jadi nilai yang berbeda akan ditolak R2.
  3. Kirim key tersebut ke POST /inventories atau POST /orders.

POST /uploads/presign

  • Description: Generates a short-lived presigned PUT URL for a direct browser upload.
  • Authorization: Bearer Token
  • Request Body:
    {
      "folder": "inventories | idcards",
      "contentType": "image/png | image/jpeg",
      "filename": "string (dipakai hanya untuk mengambil ekstensi)"
    }
  • Notes:
    • folder: "inventories" hanya untuk admin.
    • Hanya image/* dengan ekstensi .png, .jpg, .jpeg.
    • key diacak (UUID), bukan dari nama berkas asli.
    • URL berlaku 300 detik. Objek di atas 5 MB akan ditolak dan dihapus saat key dipakai.
  • Responses:
    • 201 (Success):
      {
        "status": "success",
        "message": "Upload URL generated successfully",
        "data": {
          "uploadUrl": "https://<bucket>.<account>.r2.cloudflarestorage.com/...",
          "key": "idcards/8f2c....png",
          "expiresIn": 300
        }
      }
    • 403 (Forbidden): folder inventories diminta oleh non-admin.
    • 422 (Rejected):
      {
        "status": "error",
        "message": "Error POST Presign: Mimetype application/pdf is not allowed",
        "data": {}
      }

Inventories

GET /inventories

  • Description: Retrieves all available inventory items. Selalu 200; daftar kosong dikembalikan sebagai array kosong.
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "GET inventories success",
        "data": {
          "inventories": [/* Array of Inventory */]
        }
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Error GET Inventory: {error message}",
        "data": {}
      }

POST /inventories

  • Description: Creates a new inventory item.
  • Authorization: Bearer Token
  • Header: Content-Type: application/json
  • Request Body: image adalah key dari POST /uploads/presign.
    {
      "name": "string",
      "type": "Skuter | Sport",
      "image": "inventories/....png",
      "fuel": "integer",
      "transmission": "Matic | Manual",
      "capacity": "integer",
      "price": "integer",
      "total": "integer",
      "available": "integer"
    }
  • Responses:
    • 201 (Success):
      {
        "status": "success",
        "message": "Inventory created successfully.",
        "data": {
          "inventories": { /* Saved Inventory */ }
        }
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Error POST Inventory: {error message}",
        "data": {}
      }

PUT /inventories/:id

  • Description: Updates an inventory item by ID. Seperti POST, bila image dikirim sebagai object key hasil /uploads/presign ia diverifikasi lalu disimpan sebagai URL publik; image yang sudah berupa https:// diteruskan apa adanya. Kolom lain diteruskan ke dokumen tanpa diubah.
  • Authorization: Bearer Token (admin)
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Inventory updated successfully.",
        "data": {
          "inventories": { /* Updated Inventory */ }
        }
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Error PUT Inventory: {error message}",
        "data": {}
      }
    • 422 (Error): unggahan image ditolak (mime, ekstensi, ukuran, atau object tidak ditemukan).

DELETE /inventories/:id

  • Description: Deletes an inventory item by ID. Ditolak selama masih ada orderStatus dengan returnStatus: "unreturned" yang menunjuk motor ini, karena motorId disimpan sebagai string lepas tanpa relasi.
  • Authorization: Bearer Token (admin)
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Inventory deleted successfully.",
        "data": {
          "inventories": { /* Deleted Inventory */ }
        }
      }
    • 403 (Error): bukan admin.
    • 404 (Error): inventory tidak ditemukan.
    • 409 (Error):
      {
        "status": "error",
        "message": "Error DELETE Inventory: {n} unreturned order(s) still reference this inventory",
        "data": {}
      }

Orders

GET /orders

  • Description: Retrieves all orders for a specific user.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "GET Orders success",
        "data": {
          "orders": [ /* Array of Orders */ ]
        }
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Error GET Order: {error message}",
        "data": {}
      }

GET /orders/:id

  • Description: Retrieves a single order status. :id adalah _id milik subdokumen orderStatus, bukan _id dokumen Order.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success): order.idCard berupa presigned URL berumur 300 detik.
      {
        "status": "success",
        "message": "GET order by ID success",
        "data": {
          "order": { /* Order Status */ }
        }
      }
    • 404 (Not Found):
      {
        "status": "error",
        "message": "Error GET Order by ID: Order not found",
        "data": {}
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Error GET Order: {error message}",
        "data": {}
      }

POST /orders

  • Description: Creates a new order.
  • Authorization: Bearer Token
  • Header: Content-Type: application/json
  • Request Body: idCardKey adalah key dari POST /uploads/presign.
    {
      "phone": "string",
      "idCardKey": "idcards/....jpg",
      "startDate": "string (ISO format)",
      "endDate": "string (ISO format)",
      "motorId": "string"
    }
  • Responses:
    • 201 (New Order Created):
      {
        "status": "success",
        "message": "Order created successfully.",
        "data": {
          "order": { /* New Order Data */ }
        }
      }
    • 200 (Order Updated):
      {
        "status": "success",
        "message": "Order status added successfully.",
        "data": {
          "order": { /* Updated Order */ }
        }
      }
    • 400 (Invalid Input):
      {
        "status": "error",
        "message": "Invalid input: {error message}",
        "data": {}
      }

PUT /orders/:id/paidstatus

  • Description: Updates payment status of an order to completed.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Payment status updated to completed",
        "data": {
          "order": { /* Updated Order */ }
        }
      }
    • 404 (Order Not Found):
      {
        "status": "error",
        "message": "Error PUT Order: Order status not found",
        "data": {}
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Error PUT Order: {error message}",
        "data": {}
      }

PUT /orders/:id/takenstatus

  • Description: Updates taken status of an order to taken, and stamps takenAt with the current time.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Taken status updated to completed",
        "data": {
          "order": { /* Updated Order */ }
        }
      }
    • 404 (Order Not Found):
      {
        "status": "error",
        "message": "Error PUT Order: Order status not found",
        "data": {}
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Error PUT Order: {error message}",
        "data": {}
      }

PUT /orders/:id/returnedstatus

  • Description: Updates return status of an order to returned, and stamps returnedAt with the current time. Rejected while takenStatus is still untaken — a vehicle cannot come back before it has been picked up.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Returned status updated to completed",
        "data": {
          "order": { /* Updated Order */ }
        }
      }
    • 404 (Order Not Found):
      {
        "status": "error",
        "message": "Error PUT Order: Order status not found",
        "data": {}
      }
    • 409 (Not Taken Yet):
      {
        "status": "error",
        "message": "Error PUT Order: cannot mark returned while takenStatus is untaken",
        "data": {}
      }
    • 400 (Error):
      {
        "status": "error",
        "message": "Error PUT Order: {error message}",
        "data": {}
      }

Payment

Kedua endpoint hanya bisa diakses oleh customer pemilik order, bukan admin. :id adalah _id milik subdokumen orderStatus.

GET /payment/:id/barcode

  • Description: Returns the order together with the URL that the payment QR code should encode.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success): order.idCard berupa presigned URL berumur 300 detik.
      {
        "status": "success",
        "message": "Barcode link generated successfully",
        "data": {
          "order": { /* Order Status */ },
          "payment_url": "https://api-motoran.faizath.com/payment/12345/pay"
        }
      }
    • 403 (Forbidden):
      {
        "status": "error",
        "message": "Error GET Payment: Only customer can access payment",
        "data": {}
      }
    • 404 (Not Found):
      {
        "status": "error",
        "message": "Error GET Payment: Order not found",
        "data": {}
      }

PUT /payment/:id/pay

  • Description: Marks the order as paid.
  • Authorization: Bearer Token
  • Responses:
    • 200 (Success):
      {
        "status": "success",
        "message": "Payment status updated to completed",
        "data": {
          "order": { /* Updated Order */ }
        }
      }
    • 403 (Forbidden):
      {
        "status": "error",
        "message": "Error PUT Payment: Unauthorized",
        "data": {}
      }
    • 404 (Not Found):
      {
        "status": "error",
        "message": "Error PUT Payment: Order not found",
        "data": {}
      }

About

Express 4 + Mongoose REST API for Motoran — JWT auth, inventory, order lifecycle, payments and presigned Cloudflare R2 uploads. II3120 project at STEI ITB.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Contributors

Languages