AmImUmHerbal adalah sebuah aplikasi yang dirancang untuk membantu pengguna menemukan produk herbal dengan mudah dan efisien. Proyek ini dibangun menggunakan FastAPI dan berbagai pustaka lainnya untuk memberikan pengalaman pengguna yang optimal.
Prototype aplikasi AmImUmHerbal disusun menggunakan bantuan figma, berikut untuk gambaran sederhana dari aplikasi ini. "prototype aplikasi AmImUm Herbal"
- Pencarian Produk: Pengguna dapat mencari produk herbal berdasarkan nama, kategori, atau kata kunci.
- Detail Produk: Menampilkan informasi mendetail tentang setiap produk termasuk deskripsi, harga, dan manfaat.
- Tampilan Responsif: Antarmuka yang mudah digunakan di berbagai perangkat.
- API yang Kuat: Backend yang dibangun menggunakan FastAPI untuk kecepatan dan performa yang tinggi.
- Backend: FastAPI
- Database: PostgreSQL
- ORM: SQLAlchemy
- Pengelolaan Lingkungan: Poetry
- Keamanan: JWT untuk otentikasi
- Pengujian: Pytest
- Deployment: Docker, Docker Compose
- Firebase: Untuk otentikasi pengguna
- Supabase: Untuk pengelolaan data dan penyimpanan cloud
- Railway: Untuk deployment ke produksi
Sebelum memulai, pastikan kamu sudah menginstal:
- Python 3.12 atau lebih tinggi
- PostgreSQL
- Docker & Docker Compose (untuk environment development menggunakan container)
- Poetry (untuk manajemen dependensi dan environment)
Ikuti langkah-langkah berikut untuk menginstal dan menjalankan proyek:
git clone https://github.com/username/amimumherbal.git
cd amimumherbalJika kamu menggunakan Poetry:
poetry installBuat file .env di root folder proyek dengan menjadikan .env.example sebagai template utama.
Contoh variabel yang digunakan aplikasi saat ini:
PORT=8000
APP_DEVELOPMENT=True
HOST_URL=http://127.0.0.1:8000
SECRET_KEY=change-me
DB_TYPE=postgresql
DB_HOST=localhost
DB_PORT=5432
DB_NAME=amimum_db
DB_USER=postgres
DB_PASSWORD=postgres
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/amimum_db
FIREBASE_SERVICE_ACCOUNT_KEY=
SUPABASE_URL=
SUPABASE_KEY=
EMAIL_PROVIDER=brevo_api
BREVO_API_KEY=
FROM_EMAIL=
SMTP_TIMEOUT_SECONDS=15
RAJAONGKIR_API_KEY=
RAJAONGKIR_API_HOST=rajaongkir.komerce.id
RAJAONGKIR_API_BASE_PATH=/api/v1
MIDTRANS_SERVER_KEY=
MIDTRANS_CLIENT_KEY=
MIDTRANS_IS_PRODUCTION=false
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
REDIS_PASSWORD=Jika menggunakan Docker, kamu bisa menjalankan PostgreSQL melalui Docker Compose:
docker-compose up -dUntuk memastikan skema database terbaru diterapkan:
poetry run alembic upgrade headUntuk menjalankan aplikasi secara lokal dengan runtime yang lebih terkontrol:
poetry run python run.pyAtau mode development dengan reload:
poetry run uvicorn app.main:app --reloadAplikasi akan berjalan di http://127.0.0.1:8000.
Catatan penting untuk QA lokal:
- Gunakan satu metode run saja dalam satu waktu
- Hindari menjalankan beberapa instance
uvicornbersamaan pada port yang sama - Utamakan
poetry run python run.pysaat ingin verifikasi runtime yang lebih stabil - Gunakan
uvicorn --reloadhanya saat memang sedang development aktif
Untuk menjalankan pengujian unit dan integrasi, gunakan perintah berikut:
poetry run pytestPengujian juga mencakup cakupan kode dengan pytest-cov:
poetry run pytest --cov=appSelama pengembangan atau staging, kamu bisa menggunakan Docker Compose untuk menjalankan stack aplikasi.
docker-compose up --buildPastikan file .env sudah terisi sebelum menjalankan container, karena service app membaca konfigurasi dari environment tersebut.
Catatan penting untuk shipping cost Komerce:
- gunakan
RAJAONGKIR_API_HOST=rajaongkir.komerce.id - gunakan
RAJAONGKIR_API_BASE_PATH=/api/v1 - jangan gunakan lagi endpoint lama
api.rajaongkir.com/starter/*karena sudah nonaktif
Poetry dapat diatur untuk membuat virtual environment di dalam folder proyek (.venv):
poetry config virtualenvs.in-project trueSetelah diatur, jalankan perintah ini untuk menginstall semua dependensi termasuk dependensi pengembangan:
poetry install --with devSelama development, kamu dapat menggunakan poetry run untuk menjalankan aplikasi atau perintah pengujian:
poetry run uvicorn app.main:app --reloadUntuk memastikan skema database terbaru diterapkan:
poetry run alembic upgrade headProyek ini menggunakan beberapa dependensi khusus untuk development:
- ytest: Untuk menjalankan pengujian.
- pytest-cov: Untuk melacak cakupan kode selama pengujian.
- black: Untuk auto-formatting kode.
- isort: Untuk mengatur import.
- flake8: Untuk linting kode.
Kamu dapat menjalankan semua pengujian dengan:
poetry run pytestUntuk memeriksa linting dan formatting:
poetry run black --check .
poetry run flake8 .- Hot Reloading: Uvicorn secara otomatis memuat ulang aplikasi saat ada perubahan jika dijalankan dengan flag
--reload. - Debugging: Gunakan tools seperti VSCode atau PyCharm untuk debugging dengan breakpoint.
Jika backend akan dinaikkan ke Railway sebagai target staging atau awal production, gunakan acuan minimum berikut:
- Railway dapat menjalankan aplikasi dengan command:
python run.pyPORTDATABASE_URLSECRET_KEYMIDTRANS_SERVER_KEYMIDTRANS_CLIENT_KEYMIDTRANS_IS_PRODUCTIONRAJAONGKIR_API_KEYRAJAONGKIR_API_HOST=rajaongkir.komerce.idRAJAONGKIR_API_BASE_PATH=/api/v1SMTP_SERVERSMTP_PORTSMTP_USERSMTP_PASSWORDFROM_EMAIL
GET /docs-> harus200GET /openapi.json-> harus200GET /product/all-> harus200GET /brand/all-> harus200GET /type/all-> harus200dan mengembalikan contract variant list yang memuatproduct_id,product,name,variant,expiration,stock,discount,discounted_price,img,updated_atPOST /type/create-> respons create variant harus minimal memuatproduct_id,name,variant,expiration,stock,discount,created_at,updated_atPUT /type/{type_id}-> respons update variant sebaiknya mengembalikan payload variant yang kaya agar dashboard mudah sinkron setelah update
- pastikan hanya satu runtime app aktif
- jalankan migrasi database pada environment target
- verifikasi ulang flow checkout -> payment -> callback pada environment deploy
- jangan copy
.envlokal mentah, isi ulang env Railway satu per satu dengan nilai yang benar
Status saat ini untuk kesiapan backend menuju staging/produksi:
- Aplikasi dapat dijalankan secara lokal
- Endpoint dokumentasi
/docs->200 - Endpoint OpenAPI
/openapi.json->200 - Endpoint publik katalog berikut merespons
200/product/all/brand/all/type/all
- Flow inti telah melalui beberapa batch hardening pada area:
- service product / pack type / production
- checkout dan payment notification
- auth dan email verification
- firebase init, scheduler, storage, Redis, Midtrans, Supabase config
- Uji endpoint yang membutuhkan autentikasi dengan skenario nyata
- Uji register -> verify email -> login -> checkout -> payment secara end-to-end
- Validasi integrasi eksternal dengan kredensial produksi/staging yang final:
- Firebase
- Supabase
- SMTP
- Midtrans
- RajaOngkir
- Redis
- Validasi migrasi database pada environment staging/production
- Review CORS dan host/domain final
- Review pengelolaan file upload/images pada environment deploy final
- Folder
tests/belum berisi suite test yang memadai, jadi regresi masih banyak bergantung pada smoke test manual - README dan checklist deploy masih perlu terus dijaga sinkron mengikuti perubahan implementasi
- Belum ada healthcheck/deploy verification formal di level container/orchestrator
- Flow
POST /user/registersaat ini sudah lolos validasi schema dan validasi password, tetapi masih gagal end-to-end jika kredensial SMTP aktif tidak diterima provider email (535 Username and Password not accepted)
- Siapkan environment staging final berdasarkan
.env.example - Verifikasi kredensial SMTP aktif dan pastikan provider email menerima login aplikasi
- jika memakai Gmail, gunakan
SMTP_SERVER=smtp.gmail.com,SMTP_PORT=587 - pastikan
SMTP_USERsesuai akun pengirim - gunakan app password yang valid, bukan password login biasa
- pastikan
FROM_EMAILsesuai identitas pengirim yang diizinkan
- jika memakai Gmail, gunakan
- Jalankan migrasi database di staging
- Uji flow end-to-end dengan akun uji dan data uji
- Dokumentasikan hasil uji staging
- Baru lanjut ke launch produksi
- QA runtime register terbaru:
- payload register invalid password ->
400dengan pesan validasi yang lebih jelas - payload register valid -> saat ini mentok di pengiriman email verifikasi karena SMTP login ditolak provider
- koneksi SMTP ke
smtp.gmail.com:587danSTARTTLSberhasil, tetapiserver.login(...)gagal dengan535 Username and Password not accepted
- payload register invalid password ->
- Environment aktif sudah memiliki nilai untuk integrasi utama berikut:
DATABASE_URLFIREBASE_SERVICE_ACCOUNT_KEYSUPABASE_URLSUPABASE_KEYSMTP_SERVERFROM_EMAILRAJAONGKIR_API_KEYMIDTRANS_SERVER_KEYMIDTRANS_CLIENT_KEYREDIS_HOST
- Variabel yang masih perlu dipastikan untuk staging/production final:
PORTAPP_DEVELOPMENTHOST_URLMIDTRANS_IS_PRODUCTION
- Smoke test publik terakhir pada environment lokal aktif:
/docs->200/openapi.json->200/product/all->200/brand/all->200/type/all->200
POST /user/register- siapkan email uji baru yang belum pernah terdaftar
- verifikasi respons sukses
201 - verifikasi data user terbentuk di database
POST /user/verify-email- gunakan kode verifikasi yang benar dari email/sistem
- verifikasi user berubah menjadi terverifikasi/aktif
- uji juga kode salah atau kedaluwarsa untuk memastikan error terkendali
POST /user/login- uji login dengan akun yang sudah terverifikasi
- simpan token login untuk pengujian endpoint terproteksi
- uji kombinasi password salah untuk memastikan respons error sesuai
POST /user/auth/google-login- hanya diuji jika Firebase token uji valid tersedia
POST /payments/create- gunakan token login user valid
- gunakan order yang memang valid dan dimiliki user
- verifikasi transaksi Midtrans berhasil terbentuk
POST /payments/handler-notifications- kirim payload notifikasi yang valid dari skenario sandbox/staging
- verifikasi status payment dan order ikut ter-update
- uji payload tidak valid untuk memastikan validation/error handling tetap aman
GET /docsGET /openapi.jsonGET /product/allGET /brand/allGET /type/all
- Jangan gunakan akun pelanggan riil untuk pengujian staging
- Gunakan data uji yang bisa dibersihkan atau ditelusuri
- Jangan commit
.env, token, atau payload sensitif ke repo - Simpan hasil uji per flow: input, output, status akhir, dan blocker jika ada
Gunakan format berikut setiap kali menjalankan validasi manual:
#### [FLOW NAME]
- Endpoint:
- Method:
- Preconditions:
- Test data:
- Expected result:
- Actual result:
- Status: PASS / FAIL / BLOCKED
- Blocker / notes:GET /docs- Expected: dokumentasi API dapat dibuka
- Actual:
200 - Status: PASS
GET /openapi.json- Expected: schema OpenAPI tersedia
- Actual:
200 - Status: PASS
GET /product/all- Expected: daftar produk publik dapat diakses
- Actual:
200, payload produk berhasil diterima - Status: PASS
GET /brand/all- Expected: daftar brand publik dapat diakses
- Actual:
200 - Status: PASS
GET /type/all- Expected: daftar tipe/variant publik dapat diakses
- Actual:
200 - Status: PASS
POST /rajaongkir/shipping-cost- Expected: kalkulasi ongkir domestic Komerce dapat dipanggil
- Actual:
200, daftar layanan ongkir valid berhasil diterima - Status: PASS
POST /payments/handler-notifications- Expected: callback publik Midtrans dengan signature valid dapat diproses
- Actual:
200, status pembayaran berhasil diperbarui pada simulasi callback - Status: PASS
Role resmi project saat ini:
owner-> level tertinggi, pemilik tokoadmin-> operasional internalcustomer-> pembeli
Prinsip akses internal saat ini:
- endpoint panel internal dapat diakses oleh
admindanowner customertidak boleh mengakses endpoint internal- role
owneradalah penerus penamaan singkat dari konseppemilik_toko
Endpoint admin yang sudah siap dipakai untuk dashboard internal:
POST /admin/loginGET /admin/ordersGET /admin/orders/{order_id}PATCH /admin/orders/{order_id}/statusGET /admin/paymentsGET /admin/payments/order/{order_id}GET /admin/usersGET /admin/users/{user_id}PATCH /admin/users/{user_id}/statusGET /admin/dashboard/summary
Ringkasan metric dashboard admin saat ini mencakup:
- users:
total_userstotal_active_users
- orders:
total_orderstotal_pending_orderstotal_paid_orderstotal_processing_orderstotal_shipped_orderstotal_completed_orderstotal_cancelled_orderstotal_failed_orders
- payments:
total_pending_paymentstotal_settlement_paymentstotal_expire_paymentstotal_cancel_paymentstotal_deny_paymentstotal_refund_paymentstotal_capture_payments
- revenue:
gross_revenue_paid_orders
Catatan:
- Semua endpoint admin di atas membutuhkan token internal yang valid dengan role
adminatauowner, kecualiPOST /admin/login. - Query filter yang sudah tersedia saat ini:
- orders:
status,skip,limit- allowed status:
pending,paid,processing,shipped,completed,cancelled,failed,capture,refund
- allowed status:
- payments:
status,skip,limit- allowed status:
pending,settlement,expire,cancel,deny,refund,capture
- allowed status:
- users:
role,is_active,skip,limit- allowed role saat ini di list admin backend:
admin,customer - catatan policy project terbaru: role resmi global adalah
owner,admin,customer
- allowed role saat ini di list admin backend:
- orders:
- Guard tambahan saat ini:
- endpoint status user admin tidak dipakai untuk mengubah status akun yang role-nya
admin
- endpoint status user admin tidak dipakai untuk mengubah status akun yang role-nya
- Swagger untuk route admin sudah diberi summary/description dasar agar lebih mudah dipakai saat QA dan integrasi frontend admin.
Role resmi yang dipakai frontend dan backend:
owneradmincustomer
- Route login internal:
POST /admin/login
- Role yang boleh masuk panel internal:
owneradmin
- Role
customertidak boleh diarahkan ke dashboard internal.
Landing dashboard internal saat ini dapat memakai:
GET /admin/dashboard/summary
Widget minimum yang siap dipakai:
- total users
- total active users
- total orders
- total pending orders
- total paid orders
- total processing orders
- total shipped orders
- total completed orders
- total cancelled orders
- total failed orders
- total pending payments
- total settlement payments
- total expire payments
- total cancel payments
- total deny payments
- total refund payments
- total capture payments
- gross revenue paid orders
Endpoint yang siap dipakai:
GET /admin/ordersGET /admin/orders/{order_id}PATCH /admin/orders/{order_id}/status
Filter list yang siap:
statusskiplimit
Allowed order status saat ini:
pendingpaidprocessingshippedcompletedcancelledfailedcapturerefund
Endpoint yang siap dipakai:
GET /admin/paymentsGET /admin/payments/order/{order_id}
Filter list yang siap:
statusskiplimit
Allowed payment status saat ini:
pendingsettlementexpirecanceldenyrefundcapture
Endpoint yang siap dipakai:
GET /admin/usersGET /admin/users/{user_id}PATCH /admin/users/{user_id}/status- policy terbaru: owner-only
Filter list yang siap:
roleis_activeskiplimit
Catatan penting user management saat ini:
- endpoint status user tidak dipakai untuk mengubah status akun internal level admin/owner
- list admin backend saat ini masih memvalidasi role filter
admindancustomer - policy project resmi tetap memakai 3 role:
owner,admin,customer
Menu minimum internal panel:
- Dashboard
- Orders
- Payments
- Users
Pemisahan role yang disarankan di frontend:
owner- lihat semua menu internal
- disiapkan untuk menu strategis tambahan di fase berikutnya
admin- lihat dashboard operasional, orders, payments, users
customer- tidak boleh melihat menu internal
- simpan token internal dari
POST /admin/login - gunakan guard route frontend berbasis role
owneratauadmin - jangan tampilkan dashboard internal untuk
customer - siapkan ekstensi berikutnya untuk owner-only actions tanpa merusak panel admin operasional
- Public catalog, brand, type, category, article:
- guest/customer allowed
- Cart, wishlist, shipment address, shipment selection:
customer
- Checkout, payment create, my orders, my profile:
customer
- Customer hanya boleh mengakses resource miliknya sendiri.
- Dashboard summary:
adminowner
- Order management:
adminowner
- Payment monitoring:
adminowner
- User management operasional:
adminowner
- Product, category, production, article, pack type management internal:
adminowner
Area berikut disepakati sebagai owner-only secara policy project, walau sebagian implementasi endpoint spesifiknya masih perlu ditutup bertahap:
- pengelolaan akun admin/internal level tinggi
- perubahan role strategis user internal
- kontrol penuh atas akun
owner - keputusan sensitif yang memengaruhi struktur operasional internal
customertidak boleh masuk panel internaladmintidak boleh diperlakukan setara penuh denganowner- endpoint sensitif level owner tidak boleh dibuka ke
customer - endpoint sensitif level owner tidak boleh dibuka otomatis ke seluruh
admintanpa review policy
Prioritas implementasi berikutnya agar tetap konsisten:
- pertahankan guard internal untuk
admindanowner - tambah guard owner-only saat endpoint strategis mulai dibuat
- pisahkan menu frontend berdasarkan role hasil login
- hindari hardcode akses yang menyamakan semua role internal
- terapkan owner-only guard bertahap pada endpoint sensitif
- saat ini
PATCH /admin/users/{user_id}/statusmulai dibatasi untukowner
- saat ini
Endpoint berikut secara fungsi produk cocok diperlakukan sebagai area publik sebelum login:
- katalog dan detail produk (
product_routerread endpoints) - brand/type/category public listing
- article/news public listing
- RajaOngkir lookup dan shipping cost helper yang dipakai frontend checkout flow
Endpoint berikut cocok diperlakukan sebagai area customer setelah login:
- cart (
cart_router) - wishlist (
wishlist_router) - shipment address (
shipment_address_router) - shipment selection (
shipment_router) - courier selection (
courier_router) - checkout dan my orders (
order_router) - payment create untuk order customer (
payment_routercreate flow) - profile/update profile/password/photo (
user_routerself-service endpoints) - rating product oleh customer (
rating_router)
Endpoint berikut cocok diperlakukan sebagai area internal untuk admin dan owner:
admin_router- create/update/delete article internal
- create/update category internal
- product management internal
- production management internal
- pack type management internal
Dari audit struktur saat ini, area berikut paling layak dipindahkan bertahap ke owner-only policy ketika endpoint spesifiknya mulai dipisah:
- perubahan role user internal
- pengelolaan akun admin/owner
- kontrol sensitif atas user internal level tinggi
- keputusan strategis yang mengubah struktur operasional internal
- struktur global backend sudah mengarah ke 3 lapisan akses yang sehat:
- public / guest
- customer-auth
- internal (
admin+owner)
- guard internal utama sekarang sudah menerima
admindanowner - customer flow tetap cocok dipertahankan seperti marketplace umum: login hanya diperlukan saat mulai memakai resource personal/transaksional
- langkah implementasi berikutnya sebaiknya fokus pada pemisahan lebih tegas antara internal-operational vs owner-only
Screen publik tanpa login:
- Home / landing toko
- Product listing
- Product detail
- Brand / category / type exploration
- Article / news / info page
Tujuan:
- calon customer bisa eksplor tanpa hambatan login
- role belum dipakai untuk area ini
Screen customer setelah login:
- My account / profile
- Edit profile / password / photo
- Wishlist
- Cart
- Shipment address
- Shipment selection / courier selection
- Checkout
- My orders
- Payment follow-up
- Product rating
Prinsip:
- customer hanya melihat dan mengelola resource miliknya sendiri
Screen internal yang boleh dipakai oleh admin dan owner:
- Internal login
- Dashboard summary
- Order management list
- Order detail
- Payment monitoring list
- Payment detail by order
- User list
- User detail
- Product management
- Category management
- Article management
- Production / pack type management
Prinsip:
- fokus pada operasional harian
- layout dan navigation internal sebaiknya sama untuk
admindanowner
Screen/aksi yang sebaiknya disiapkan sebagai owner-only:
- sensitive user control
- internal role control
- admin/owner management
- strategic settings and future high-risk actions
Status implementasi saat ini:
- owner-only action yang sudah mulai aktif di backend:
PATCH /admin/users/{user_id}/status
Sidebar internal minimum:
- Dashboard
- Orders
- Payments
- Users
- Catalog Management
- Content Management
Role-aware navigation:
owner- lihat semua menu internal
- owner-only action/button tampil
admin- lihat menu operasional internal
- owner-only action/button disembunyikan
customer- tidak boleh melihat internal sidebar
- pisahkan route group publik, customer-auth, dan internal
- simpan token internal terpisah secara jelas pada flow dashboard internal
- lakukan role check setelah login sebelum render internal menu
- jangan gunakan pendekatan satu layout untuk semua role tanpa filtering permission
- tandai action sensitif sejak awal agar owner-only mudah dirawat saat frontend membesar
{
"firstname": "Test",
"lastname": "User",
"gender": "male",
"email": "staging-test@example.com",
"phone": "+6281234567890",
"password": "Test12345"
}{
"code": "123456",
"email": "staging-test@example.com"
}{
"email": "staging-test@example.com",
"password": "Test12345"
}{
"order_id": "replace-with-valid-order-id"
}{
"order_id": "replace-with-valid-order-id",
"transaction_status": "settlement",
"fraud_status": "accept",
"payment_type": "bank_transfer",
"gross_amount": "21000.00",
"signature_key": "replace-with-valid-midtrans-signature"
}- Email test harus bisa diakses jika flow verifikasi memakai email nyata
- User test harus dibuat khusus untuk staging, bukan akun pelanggan riil
- Order test harus valid dan memang dimiliki user test
- Kredensial Midtrans/Firebase/SMTP harus sesuai environment yang sedang diuji
- Token login hanya dipakai sementara untuk sesi uji dan tidak disimpan ke repo
POST /payments/createbutuh user login valid dan order validPOST /payments/handler-notificationsbaru bermakna penuh jika ada transaksi/order yang memang sudah terbentukPOST /user/auth/google-loginbergantung pada token Firebase/Google yang valid
GET /product/all- Dilakukan 10 kali probe berturut-turut
- Hasil akhir: stabil
200 - Catatan: belum ada bukti gagal konsisten pada observasi ini
GET /brand/all- Dilakukan 10 kali probe berturut-turut
- Hasil akhir: stabil
200 - Setelah patch konsistensi response, message tervalidasi menjadi seragam:
All list of brands can accessed successfully
GET /type/all- Dilakukan 10 kali probe berturut-turut
- Hasil akhir: stabil
200
- Saat verifikasi restart-aware, terdeteksi dua proses
uvicornaktif dengan interpreter berbeda - Kondisi ini berisiko membuat hasil QA lokal membingungkan karena request bisa tidak selalu merepresentasikan runtime yang benar-benar diharapkan
- Untuk staging/production, jalankan hanya satu instance aplikasi yang terkontrol agar hasil verifikasi lebih dapat dipercaya
- Untuk QA lokal, hindari mencampur interpreter global Python dan interpreter
.venvproject pada runtime yang sama - Endpoint shipping cost lama
api.rajaongkir.com/starter/*sudah nonaktif, gunakan hostrajaongkir.komerce.iddengan base path/api/v1 - Flow courier dan shipment sekarang mengasumsikan satu record aktif per user, create baru akan menonaktifkan pilihan aktif sebelumnya
- Masih ada warning relasi SQLAlchemy pada area shipment/address (
SAWarning: Multiple rows returned with uselist=False) yang perlu dibersihkan sebelum menyatakan production full-clean
Jika kamu ingin berkontribusi pada proyek ini:
- Fork repositori ini.
Buat branch fitur baru (
git checkout -b feature/your-feature). - Lakukan perubahan dan commit (
git commit -m 'Add some feature'). - Push branch kamu (
git push origin feature/your-feature). - Buat Pull Request.
Proyek ini dilisensikan di bawah MIT License. Lihat LICENSE untuk detail lebih lanjut.
Untuk pertanyaan atau saran, silakan hubungi:
Nama: amimumHerbal Email: herbalamimum99@gmail.com