Compare commits

..

93 Commits

Author SHA1 Message Date
817919f8f7 Merge pull request '### Fitur: Penambahan Pagination pada Endpoint Admin Mobile' (#57) from mobile-api/14-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/57
2026-02-14 16:25:39 +08:00
5bdb998d2e ### Fitur: Penambahan Pagination pada Endpoint Admin Mobile
#### Deskripsi Umum
Telah dilakukan penambahan fitur pagination pada beberapa endpoint admin mobile untuk meningkatkan kinerja dan pengalaman pengguna saat mengakses data dalam jumlah besar.

#### File yang Diubah

1. **src/app/api/mobile/admin/job/route.ts**
   - Ditambahkan parameter  dari
   - Diterapkan logika pagination dengan  (default 10) dan
   - Query  telah dimodifikasi untuk mendukung pagination

2. **src/app/api/mobile/admin/event/route.ts**
   - Diperbaiki definisi variabel  untuk memastikan tipe data yang konsisten
   - Ditambahkan default value 1 untuk parameter
   - Perhitungan  disesuaikan agar lebih efisien

3. **src/app/api/mobile/admin/event/[id]/participants/route.ts**
   - Ditambahkan parameter  dari
   - Diterapkan logika pagination dengan  (default 10) dan
   - Query  telah dimodifikasi untuk mendukung pagination

#### Tujuan Perubahan
- Meningkatkan kinerja aplikasi saat mengambil data dalam jumlah besar
- Memungkinkan pengguna untuk mengakses data secara bertahap melalui halaman-halaman
- Mengurangi beban server saat mengambil data dalam jumlah besar
- Memberikan pengalaman pengguna yang lebih baik saat mengakses data admin

#### Cara Penggunaan
Untuk menggunakan fitur pagination, cukup tambahkan parameter  pada query string saat melakukan permintaan ke endpoint yang telah dimodifikasi. Contoh:

Default jumlah data per halaman adalah 10 item.

### No Issue
2026-02-14 15:36:09 +08:00
90031e23ef Merge pull request 'Fix Api Mobile' (#56) from mobile-api/13-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/56
2026-02-13 17:41:31 +08:00
b585aa3024 Fix Api Mobile
API – Admin Master Data
- src/app/api/mobile/admin/master/bank/route.ts
- src/app/api/mobile/admin/master/business-field/route.ts
- src/app/api/mobile/admin/master/business-field/[id]/route.ts

Docs
- PROMPT-AI.md

### No Issue
2026-02-13 17:40:25 +08:00
596ebd2ff4 Merge pull request 'mobile-api/12-feb-26' (#55) from mobile-api/12-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/55
2026-02-12 17:49:00 +08:00
a8f9d2ac0d Fix API Mobile Admin
API – Admin User (Mobile)
- src/app/api/mobile/admin/user/route.ts

Docs
- PROMPT-AI.md

### No Issue
2026-02-12 17:42:06 +08:00
d43f3762a3 Fixed Bug Server
## Summary
This branch contains several bug fixes and performance improvements, primarily focusing on:
- Database connection management
- MQTT client stability
- Logging optimization
- API enhancements

## Detailed Changes

### Fixed Issues
1. **Database Connection Management**
   - Removed  from user-validate API route to prevent connection pool exhaustion
   - Added proper connection handling in global Prisma setup
   - Reduced logging verbosity in production environments

2. **MQTT Client Improvements**
   - Enhanced MQTT client initialization with proper error handling
   - Added reconnection logic with configurable intervals
   - Implemented cleanup functions to prevent memory leaks
   - Added separate initialization logic for server and client-side code

3. **Logging Optimization**
   - Removed excessive logging in middleware that was causing high CPU usage
   - Configured appropriate log levels for development and production

4. **Component Stability**
   - Added safety checks in text editor component to prevent MQTT operations on the server side
   - Improved MQTT publishing logic with client availability checks

### New Files
-  - Utility functions for safe database operations

### Modified Files
1.
   - Removed problematic  call

2.
   - Configured different logging levels for dev/prod
   - Removed process listeners that were causing disconnections
   - Exported prisma instance separately

3.
   - Removed excessive logging statements

4.
   - Enhanced initialization with error handling
   - Added reconnection and timeout configurations

5.
   - Added proper cleanup functions
   - Improved connection handling

6.
   - Added MQTT client availability checks
   - Prevented server-side MQTT operations

### Performance Improvements
- Reduced database connection overhead
- Optimized MQTT connection handling
- Eliminated unnecessary logging in production
- Better memory management with proper cleanup functions

### No Issue
2026-02-12 16:29:03 +08:00
aa700523ca Merge pull request 'feat: Implementasi pagination pada endpoint mobile donation' (#54) from mobile-api/10-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/54
2026-02-10 17:36:02 +08:00
e89886e1db feat: Implementasi pagination pada endpoint mobile donation
4
      5 - Menambahkan pagination pada endpoint GET /api/mobile/donation
      6 - Menambahkan pagination pada endpoint GET /api/mobile/donation/[id]/news
      7 - Menambahkan pagination pada endpoint GET /api/mobile/donation/[id]/donatur
      8 - Memperbaiki validasi payload pada endpoint POST /api/mobile/auth/device-tokens
      9 - Menangani struktur payload yang bersarang dan langsung pada device token endpoint
     10 - Menambahkan informasi pagination ke dalam respons API

### NO Issue
2026-02-10 17:31:39 +08:00
236ab4d4a4 Merge pull request 'Mobile API' (#53) from mobile-api/9-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/53
2026-02-09 17:38:52 +08:00
934d6a3ef1 feat: update mobile donation API and related dependencies
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-09 17:34:49 +08:00
a7694bd7d5 feat: Tambahkan pagination pada API mobile investasi
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Deskripsi:

- Tambahkan pagination pada endpoint investor/route.ts

- Tambahkan pagination pada endpoint news/route.ts untuk kategori all-news

- Gunakan konstanta PAGINATION_DEFAULT_TAKE untuk jumlah data per halaman

- Tambahkan metadata pagination (currentPage, totalData, totalPage, dataPerPage)

Fixes #issue-number
2026-02-09 15:08:33 +08:00
eaa7692359 Merge pull request 'Fix API mobile Investment' (#52) from mobile-api/6-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/52
2026-02-06 17:39:36 +08:00
3b0ea3d847 chore(release): 1.5.40 2026-02-06 17:38:19 +08:00
097758a431 Fix API mobile Investment
API – Investment (Mobile)
- src/app/api/mobile/investment/route.ts
- src/app/api/mobile/investment/[id]/[status]/route.ts
- src/app/api/mobile/investment/[id]/invoice/route.ts
- src/app/api/mobile/investment/[id]/document/route.ts

Docs
- PROMPT-AI.md

### No issue
2026-02-06 17:37:47 +08:00
d51ce346e6 Merge pull request 'Fix API mobile' (#51) from mobile-api/5-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/51
2026-02-05 17:36:40 +08:00
6f5849aa29 Fix API mobile
API – Voting (User)
- src/app/api/mobile/voting/route.ts
- src/app/api/mobile/voting/[id]/[status]/route.ts
- src/app/api/mobile/voting/[id]/contribution/route.ts

Docs
- PROMPT-AI.md

### No Issue
2026-02-05 17:35:49 +08:00
91f4bb6c9e Merge pull request 'mobile-api/4-jan-26' (#50) from mobile-api/4-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/50
2026-02-05 10:11:03 +08:00
6aceb212e4 Fix APi voting mobile laod data
API – Event (Mobile)
- src/app/api/mobile/event/route.ts
- src/app/api/mobile/event/[id]/[status]/route.ts
- src/app/api/mobile/event/[id]/participants/route.ts

API – Voting (Mobile)
- src/app/api/mobile/voting/[id]/[status]/route.ts

Docs
- PROMPT-AI.md

### No Issue
2026-02-04 17:49:32 +08:00
42803f9b92 Fix api load data event dan notifikasi
API – Event (Mobile)
- src/app/api/mobile/event/route.ts
- src/app/api/mobile/event/[id]/[status]/route.ts

API – Notification (Mobile)
- src/app/api/mobile/notification/[id]/route.ts

Docs / Experiment
- PROMPT-AI.md
- zCoba.js

### No issue
2026-02-03 17:50:07 +08:00
1fe0001994 Merge pull request 'Fix API Job untuk loaddata:' (#49) from mobile-api/2-feb-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/49
2026-02-02 17:11:32 +08:00
2a857f54e7 Fix API Job untuk loaddata:
API – Job (Mobile)
- src/app/api/mobile/job/route.ts
- src/app/api/mobile/job/[id]/[status]/route.ts

Docs
- PROMPT-AI.md

Constants
- src/lib/constans-value/

### No Issue
2026-02-02 17:03:51 +08:00
b82a283731 Merge pull request 'mobile-api for load data' (#48) from mobile-api/30-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/48
2026-01-30 17:20:09 +08:00
bb79a68f44 API – Mobile Notification
- src/app/api/mobile/notification/[id]/route.ts

API – Portofolio (Mobile)
- src/app/api/mobile/portofolio/route.ts

Untracked Files
- PROMPT-AI.md
- QWEN.md

### No Issue
2026-01-30 17:16:33 +08:00
f103ae93ad chore(release): 1.5.39 2026-01-30 17:16:05 +08:00
1c9459dcf3 Fix comment forum
Forum API (Mobile)
- src/app/api/mobile/forum/[id]/comment/route.ts

### No Issue
2026-01-29 17:41:21 +08:00
8b54f5ca65 Fix send whatsapp
Auth API
- src/app/api/auth/login/route.ts
- src/app/api/auth/mobile-login/route.ts
- src/app/api/auth/mobile-register/route.ts
- src/app/api/auth/resend/route.ts

User API (Mobile)
- src/app/api/mobile/user/route.ts
- src/app/api/mobile/admin/user/[id]/route.ts

Utility
- src/lib/code-otp-sender.ts

### No issue
2026-01-29 15:04:40 +08:00
6d7d0fd07e Merge pull request 'mobile-notification done' (#46) from mobile-notification/27-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/46
2026-01-27 17:00:26 +08:00
c94da645f3 API – Donation (Admin & User)
- src/app/api/mobile/admin/donation/[id]/disbursement/route.ts
- src/app/api/mobile/donation/[id]/news/route.ts
- src/app/api/mobile/donation/route.ts

Donation Helper / Logic
- src/lib/mobile/donation/

### No Issue
2026-01-27 16:59:31 +08:00
da0477102e chore(release): 1.5.38 2026-01-27 16:57:22 +08:00
bc80bb3441 Merge pull request 'Notification Donasi & EULA on login' (#45) from mobile-notification/23-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/45
2026-01-23 17:06:56 +08:00
70db97f5bb chore(release): 1.5.37 2026-01-23 17:05:04 +08:00
8ccf1f2b6e API – Donation (Admin & User)
- src/app/api/mobile/admin/donation/[id]/route.ts
- src/app/api/mobile/admin/donation/[id]/invoice/route.ts
- src/app/api/mobile/donation/route.ts
- src/app/api/mobile/donation/[id]/invoice/route.ts

Routing Helper
- src/lib/mobile/route-page-mobile.ts

### No Issue
2026-01-23 17:04:43 +08:00
048b7b6094 API Rgister component sender
Legal Documents & Registration
- public/privacy-policy.html
- public/terms-of-service.html
- src/app/api/auth/mobile-register/route.ts

### No Issue;
2026-01-23 14:46:44 +08:00
fc23e01275 Notification investasi mobile done
### No Issue
2026-01-22 17:53:35 +08:00
20d05c1cc7 component sender wa to all device ( apps & web ) 2026-01-22 11:58:37 +08:00
8ab94b9c86 Merge pull request 'mobile-notification invesment' (#44) from mobile-notification/21-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/44
2026-01-21 15:41:18 +08:00
2c269db250 API – Investment (Admin & User)
- src/app/api/mobile/admin/investment/[id]/route.ts
- src/app/api/mobile/admin/investment/[id]/invoice/route.ts
- src/app/api/mobile/investment/[id]/invoice/route.ts
- src/app/api/mobile/investment/[id]/document/route.ts

Routing Helper
- src/lib/mobile/route-page-mobile.ts

### No Issue
2026-01-21 15:34:19 +08:00
fea94df7bb refactor(invesment): penyesuaian endpoint admin, invoice, dan navigasi mobile
- src/app/api/mobile/admin/investment/[id]/route.ts
- src/app/api/mobile/investment/[id]/invoice/route.ts
- src/app/api/mobile/investment/route.ts
- src/lib/mobile/route-page-mobile.ts

### No Issue
2026-01-20 17:48:42 +08:00
6e37b18e42 Merge pull request 'mobile-notification report comment' (#43) from mobile-notification/19-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/43
2026-01-19 17:54:21 +08:00
3c6dde6204 Fix API notification to report comment
Fix:
- src/app/api/mobile/admin/forum/[id]/comment/route.ts
- src/app/api/mobile/admin/forum/[id]/route.ts
- src/app/api/mobile/forum/[id]/preview-report-posting/route.ts
- src/app/api/mobile/forum/[id]/report-commentar/route.ts
- src/app/api/mobile/forum/[id]/report-posting/route.ts
- src/lib/mobile/route-page-mobile.ts
- tsconfig.json

Deleted:
- src/app/api/mobile/forum/[id]/report-comment/route.ts

Add:
- src/app/api/mobile/forum/[id]/preview-report-comment/
- src/app/api/mobile/forum/[id]/report-comment-del-soon/

### No Issue
2026-01-19 17:38:15 +08:00
cb0845e264 Fix notification forum
Fix:
modified:   src/app/api/mobile/admin/forum/[id]/route.ts
modified:   src/app/api/mobile/forum/[id]/comment/route.ts
modified:   src/app/api/mobile/forum/[id]/report-posting/route.ts
modified:   src/app/api/mobile/forum/route.ts
modified:   src/lib/mobile/route-page-mobile.ts

Add:
src/app/api/mobile/forum/[id]/preview-report-posting/

### No Issue
2026-01-17 16:00:46 +08:00
a6db03d0b4 Merge pull request 'mobile-notification event dan voting' (#42) from mobile-notification/15-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/42
2026-01-15 17:41:58 +08:00
c550a4e922 Merge pull request 'mobile-notification try to push to apple and android preview' (#41) from mobile-notification/12-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/41
2026-01-15 17:41:13 +08:00
d09e30c049 Voting notifikasi for mobile
Fix:
- src/app/api/mobile/admin/voting/[id]/route.ts
- src/app/api/mobile/event/route.ts
- src/app/api/mobile/voting/[id]/route.ts
- src/app/api/mobile/voting/route.ts
- src/lib/mobile/route-page-mobile.ts

### No Issue
2026-01-15 17:38:33 +08:00
c8bd928c33 Fix notifikasi join dari event
Fix:
 modified:   src/app/api/mobile/event/[id]/participants/route.ts
        modified:   src/bin/seeder/user_seeder.json

### No Issue
2026-01-15 13:57:00 +08:00
3a558cec8e chore(release): 1.5.36 2026-01-13 17:45:58 +08:00
b9354cb6bf Penerapan notifikasi pada event
Fix:
- src/app/api/mobile/admin/event/[id]/route.ts
- src/app/api/mobile/admin/job/[id]/route.ts
- src/app/api/mobile/event/route.ts
- src/app/api/mobile/job/route.ts
- src/app/api/mobile/notification/[id]/route.ts
- src/lib/mobile/notification/send-notification.ts
- src/lib/mobile/route-page-mobile.ts
- types/type-mobile-notification.ts

### No Issue
2026-01-13 17:45:37 +08:00
7cdde6b5a9 chore(release): 1.5.35 2026-01-12 17:35:50 +08:00
e77e5eb3ac Fix notification reuse component
Fix:
- modified:   src/app/api/auth/mobile-register/route.ts
- modified:   src/lib/mobile/notification/send-notification.ts

### No Issue
2026-01-12 17:35:27 +08:00
2431a3fa3e Merge pull request 'Mobile notification & EULA route' (#40) from mobile-notification/9-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/40
2026-01-09 17:47:40 +08:00
8f3f27122a chore(release): 1.5.34 2026-01-09 17:45:54 +08:00
d84a1d84ff Fix route untuk penambahan fitur EULA
Fix:
- modified:   src/app/api/auth/mobile-login/route.ts

Add:
- src/app/api/auth/mobile-eula/

### No Issue
2026-01-09 17:45:44 +08:00
40ba31edec Fix mobile notification:
- Bug penerima pesan 2 kali

Fix:
modified:   src/lib/mobile/notification/send-notification.ts

### No Issue
2026-01-09 14:42:45 +08:00
a54f8599b4 API Mobile notifikasi job
Fix:
modified:   src/app/api/mobile/admin/job/[id]/route.ts
modified:   src/app/api/mobile/job/[id]/route.ts
modified:   src/app/api/mobile/job/route.ts
modified:   src/lib/mobile/route-page-mobile.ts
modified:   types/type-mobile-notification.ts

### No Issue
2026-01-08 18:35:32 +08:00
1ed0da8c7d Merge pull request 'Fix API mobile notifikasi untuk job' (#39) from mobile-notification/7-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/39
2026-01-08 15:26:19 +08:00
09825756f3 Merge branch 'mobile-notification/7-jan-26' of https://wibugit.wibudev.com/wibu/hipmi into mobile-notification/8-jan-26 2026-01-08 10:35:49 +08:00
2086692897 Fix API notifikasi untuk job
### No Issue
2026-01-08 10:14:35 +08:00
e15a5d796d Merge pull request 'mobile notification' (#38) from mobile-notification/6-jan-26 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/38
2026-01-06 17:53:14 +08:00
87515ae19f Notifikasi mobile job
Add:
 src/lib/mobile/
 types/type-mobile-notification.ts

Fix:
src/app/api/auth/mobile-register/route.ts
src/app/api/mobile/job/route.ts

### No Issue
2026-01-06 17:52:28 +08:00
44d6788f6e chore(release): 1.5.33 2026-01-06 12:24:28 +08:00
ac634100b5 Notifikasi ke admin untuk user baru mendaftar
Fix:

- prisma/schema.prisma
- src/app/api/auth/mobile-register/route.ts
- src/app/api/mobile/admin/user/[id]/route.ts
- src/app/api/mobile/notification/[id]/route.ts
- src/app/api/mobile/notification/route.ts

Add:
Migrasi untuk db table notifikasi
- prisma/migrations/20260105064508_fix_table_notifikasi_optional_data/

### No Issue
2026-01-06 12:20:12 +08:00
836ebfaef0 Merge pull request 'mobile notification API' (#37) from mobile-notification/5-jan-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/37
2026-01-05 14:06:58 +08:00
1b206102b0 Fix API Notifikasi
### No Issue
2026-01-05 14:04:08 +08:00
94a545bd30 chore(release): 1.5.32 2026-01-05 12:37:14 +08:00
3552cf4f39 Merge pull request 'mobile-notification API' (#36) from mobile-notification/24-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/36
2025-12-24 17:48:07 +08:00
d50fda90e0 Fix notifikasi API for mobile
Fix:
modified:   src/app/api/mobile/notification/[id]/route.ts
modified:   src/app/api/mobile/notification/[id]/unread-count/route.ts
modified:   src/app/api/mobile/notification/route.ts

### No Issue
2025-12-24 17:47:00 +08:00
d3d4912a5f chore(release): 1.5.31 2025-12-24 17:46:08 +08:00
b2e8bc3caf Fix database notification untuk mobile
Fix:
- prisma/migrations/20251223084450_add_recipient_and_sender

Add:
- prisma/schema.prisma
- src/app/api/mobile/auth/device-tokens/[id]/route.ts
- src/app/api/mobile/auth/device-tokens/route.ts
- src/app/api/mobile/notification/[id]/unread-count/route.ts
- src/app/api/mobile/notification/route.ts

### No Issue
2025-12-23 17:32:05 +08:00
d207b6feed Merge pull request 'Penerapan notifikasi mobile' (#35) from mobile-notification/19-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/35
2025-12-19 16:40:09 +08:00
f05571caa4 Simpan notifikasi ke database
Add:
- prisma/migrations/20251218071503_add_type_on_db_notifikasi/
- src/app/api/mobile/notification/

Fix:
- modified:   prisma/schema.prisma
- modified:   src/app/api/mobile/auth/device-tokens/route.ts
- deleted:    src/app/api/mobile/notifications/route.ts
- modified:   x.sh

###No Issue
2025-12-19 16:38:33 +08:00
6507bdcd35 chore(release): 1.5.30 2025-12-19 16:35:08 +08:00
e2c8a1edbc chore(release): 1.5.29 2025-12-17 17:41:02 +08:00
02b25ffc84 Penerapaan ke database untuk token device
Add:
src/app/api/mobile/auth/device-tokens/[id]/

Fix:
modified:   src/app/api/mobile/auth/device-tokens/route.ts
modified:   src/app/api/mobile/notifications/route.ts

### No Issue
2025-12-17 17:40:56 +08:00
f1c8432fdc Merge pull request 'Fix DB table donasi' (#33) from login-api/17-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/33
2025-12-17 14:44:53 +08:00
3e0d2743fb Fix DB table donasi:
- Relasi ke master bank dengan nilai default NULL

### No issue
2025-12-17 14:43:09 +08:00
fc3ee6724e chore(release): 1.5.28 2025-12-17 14:42:19 +08:00
1cd4c3713e Merge pull request 'login-api/17-dec-25' (#31) from login-api/17-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/31
2025-12-17 11:43:32 +08:00
a72cf866fa Fix API Login dan filter 0 di input nomor
### No Issue
2025-12-17 11:40:01 +08:00
c50e0ceaf7 chore(release): 1.5.27 2025-12-17 11:07:15 +08:00
4307b383e3 Merge pull request 'Penerapan notifikasi mobil ke database' (#30) from mobile-notification/16-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/30
2025-12-16 17:57:21 +08:00
563d95b928 Penerapan notifikasi mobil ke database
Fix:
- modified:   prisma/schema.prisma

Add:
prisma/migrations/20251216041242_add_token_user_device_indexes/
src/app/api/mobile/auth/device-tokens/

### No Issue
2025-12-16 17:50:03 +08:00
0786d23336 Merge pull request 'API notif dan penambahan package firebase-admin' (#29) from mobile-notification/15-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/29
2025-12-15 17:52:03 +08:00
cb3511f973 Merge pull request 'Fix QC ( Ayu )' (#28) from qc-mobile/10-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/28
2025-12-10 17:38:42 +08:00
b4921c4e82 Merge pull request 'Fix API untuk QC: Ayu' (#27) from qc-mobile/9-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/27
2025-12-09 17:40:20 +08:00
a9325054eb Merge pull request 'Fix Apple Reject' (#26) from qc-mobile/8-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/26
2025-12-08 15:33:58 +08:00
819812149f Merge pull request 'Fix QC Admin ( Inno )' (#25) from qc-mobile/5-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/25
2025-12-05 17:15:53 +08:00
75ba2b29ae Merge pull request 'Fix QC Inno:' (#24) from mobile-reject/4-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/24
2025-12-04 17:45:04 +08:00
54a4d15bdd Merge pull request 'Fix WA Server' (#23) from mobile-reject/3-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/23
2025-12-03 16:28:09 +08:00
1321f33da9 Merge pull request 'Fix Apple Rejected' (#22) from mobile-reject/3-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/22
2025-12-03 15:02:40 +08:00
fad0c33b9a Merge pull request 'Alur autentikasi dirubah' (#21) from mobile-reject/2-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/21
2025-12-02 14:41:44 +08:00
565bab4998 Merge pull request 'QC Mobile: Pak jun dan Inno' (#20) from qc/1-dec-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/20
2025-12-01 17:44:48 +08:00
7530a38c4d Merge pull request 'Fix version' (#19) from apple-reject/28-nov-25 into staging
Reviewed-on: http://wibugit.wibudev.com/wibu/hipmi/pulls/19
2025-11-28 11:47:44 +08:00
94 changed files with 4018 additions and 665 deletions

View File

@@ -2,6 +2,34 @@
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
## [1.5.40](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.39...v1.5.40) (2026-02-06)
## [1.5.39](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.38...v1.5.39) (2026-01-30)
## [1.5.38](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.37...v1.5.38) (2026-01-27)
## [1.5.37](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.36...v1.5.37) (2026-01-23)
## [1.5.36](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.35...v1.5.36) (2026-01-13)
## [1.5.35](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.34...v1.5.35) (2026-01-12)
## [1.5.34](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.33...v1.5.34) (2026-01-09)
## [1.5.33](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.32...v1.5.33) (2026-01-06)
## [1.5.32](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.31...v1.5.32) (2026-01-05)
## [1.5.31](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.30...v1.5.31) (2025-12-24)
## [1.5.30](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.29...v1.5.30) (2025-12-19)
## [1.5.29](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.28...v1.5.29) (2025-12-17)
## [1.5.28](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.27...v1.5.28) (2025-12-17)
## [1.5.27](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.26...v1.5.27) (2025-12-17)
## [1.5.26](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.25...v1.5.26) (2025-12-10)
## [1.5.25](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.24...v1.5.25) (2025-12-09)

63
CHANGELOG_BRANCH.md Normal file
View File

@@ -0,0 +1,63 @@
# Changelog for Branch: fixed-bug/12-feb-26
## Summary
This branch contains several bug fixes and performance improvements, primarily focusing on:
- Database connection management
- MQTT client stability
- Logging optimization
- API enhancements
## Detailed Changes
### Fixed Issues
1. **Database Connection Management**
- Removed `prisma.$disconnect()` from user-validate API route to prevent connection pool exhaustion
- Added proper connection handling in global Prisma setup
- Reduced logging verbosity in production environments
2. **MQTT Client Improvements**
- Enhanced MQTT client initialization with proper error handling
- Added reconnection logic with configurable intervals
- Implemented cleanup functions to prevent memory leaks
- Added separate initialization logic for server and client-side code
3. **Logging Optimization**
- Removed excessive logging in middleware that was causing high CPU usage
- Configured appropriate log levels for development and production
4. **Component Stability**
- Added safety checks in text editor component to prevent MQTT operations on the server side
- Improved MQTT publishing logic with client availability checks
### New Files
- `src/lib/prismaUtils.ts` - Utility functions for safe database operations
### Modified Files
1. `src/app/api/user-validate/route.ts`
- Removed problematic `prisma.$disconnect()` call
2. `src/lib/prisma.ts`
- Configured different logging levels for dev/prod
- Removed process listeners that were causing disconnections
- Exported prisma instance separately
3. `src/middleware.tsx`
- Removed excessive logging statements
4. `src/util/mqtt_client.ts`
- Enhanced initialization with error handling
- Added reconnection and timeout configurations
5. `src/util/mqtt_loader.tsx`
- Added proper cleanup functions
- Improved connection handling
6. `src/app_modules/_global/component/new/comp_V3_text_editor_stiker.tsx`
- Added MQTT client availability checks
- Prevented server-side MQTT operations
### Performance Improvements
- Reduced database connection overhead
- Optimized MQTT connection handling
- Eliminated unnecessary logging in production
- Better memory management with proper cleanup functions

39
CHANGELOG_COMMIT.md Normal file
View File

@@ -0,0 +1,39 @@
## Catatan Perubahan untuk Commit
### Fitur: Penambahan Pagination pada Endpoint Admin Mobile
#### Deskripsi Umum
Telah dilakukan penambahan fitur pagination pada beberapa endpoint admin mobile untuk meningkatkan kinerja dan pengalaman pengguna saat mengakses data dalam jumlah besar.
#### File yang Diubah
1. **src/app/api/mobile/admin/job/route.ts**
- Ditambahkan parameter `page` dari `searchParams`
- Diterapkan logika pagination dengan `takeData` (default 10) dan `skipData`
- Query `prisma.job.findMany` telah dimodifikasi untuk mendukung pagination
2. **src/app/api/mobile/admin/event/route.ts**
- Diperbaiki definisi variabel `page` untuk memastikan tipe data yang konsisten
- Ditambahkan default value 1 untuk parameter `page`
- Perhitungan `skipData` disesuaikan agar lebih efisien
3. **src/app/api/mobile/admin/event/[id]/participants/route.ts**
- Ditambahkan parameter `page` dari `searchParams`
- Diterapkan logika pagination dengan `takeData` (default 10) dan `skipData`
- Query `prisma.event_Peserta.findMany` telah dimodifikasi untuk mendukung pagination
#### Tujuan Perubahan
- Meningkatkan kinerja aplikasi saat mengambil data dalam jumlah besar
- Memungkinkan pengguna untuk mengakses data secara bertahap melalui halaman-halaman
- Mengurangi beban server saat mengambil data dalam jumlah besar
- Memberikan pengalaman pengguna yang lebih baik saat mengakses data admin
#### Cara Penggunaan
Untuk menggunakan fitur pagination, cukup tambahkan parameter `page` pada query string saat melakukan permintaan ke endpoint yang telah dimodifikasi. Contoh:
```
GET /api/mobile/admin/job?page=2
GET /api/mobile/admin/event?page=3
GET /api/mobile/admin/event/{id}/participants?page=1
```
Default jumlah data per halaman adalah 10 item.

45
PROMPT-AI.md Normal file
View File

@@ -0,0 +1,45 @@
File utama: src/app/api/mobile/admin/event/[id]/participants/route.ts
Terapkan pagination pada file "File utama" pada method GET
Analisa juga file "File utama", jika belum memiliki page dari seachParams maka terapkan. Juga pastikan take dan skip sudah sesuai dengan pagination. Buat default nya menjadi 10 untuk take data
Contoh:
const page = Number(searchParams.get("page"));
const takeData = 10;
const skipData = page * takeData - takeData;
dan penerapannya pada query
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
Gunakan bahasa indonesia pada cli agar saya mudah membacanya.
<!-- Additinal prompt -->
File refrensi: src/app/api/mobile/event/[id]/[status]/route.ts
Anda bisa menggunakan refrensi dari "File refrensi" jika butuh pemahaman dengan tipe fitur yang sama
<!-- Auto input promt -->
End-point: src/app/api/mobile/donation/route.ts
Buatkan auto input untuk method POST dengan data yang dibutuhkan sesuai dengan struktur data yang ada di file tersebut, tidak perlu "temporary" cukup data "permanent" dengan ketentuan sebagai berikut:
- authorId: string ( cmha6wb9w0001cfndwl9fcse6 )
- title: string
- target: number
- donasiMaster_DurasiId: number ( 3 )
- donasiMaster_KategoriId: number ( 3 )
- namaBank: string
- rekening: string
- imageId: number ( cm60j9q3m000xc9dc584v8rh8 )
Untuk sisa nya anda bisa bebas mengisi data tersebut.
<!-- COMMIT & PUSH -->
Branch: mobile-api/10-feb-26
Jalankan perintah ini: git checkout -b "Branch"
Setelah itu jalankan perintah ini: git add .
Setelah itu jalankan perintah ini: git commit -m "
<Berikan semua catatan perubahan pada branch ini, tampilan pada saya dan pastikan dalam bahasa indonesia. Saya akan cek baru saya akan berikan perintah push>
"
Setelah itu jalankan perintah ini: git push origin "Branch"

201
QWEN.md Normal file
View File

@@ -0,0 +1,201 @@
# HIPMI Project - QWEN.md
## Project Overview
HIPMI (Himpunan Pengusaha Muya Indonesia) is a comprehensive Next.js-based web application built for the Indonesian Young Entrepreneurs Association. The project is a sophisticated platform that provides multiple business functionalities including investment management, donations, events, job listings, forums, voting systems, and collaborative projects.
### Key Technologies
- **Framework**: Next.js 13+ (with App Router)
- **Language**: TypeScript
- **Database**: PostgreSQL with Prisma ORM
- **Styling**: Tailwind CSS with Mantine UI components
- **Authentication**: JWT-based with custom middleware
- **Runtime**: Bun (instead of Node.js)
- **Deployment**: Standalone output configuration
### Team Structure
- **bagas**: Frontend, DevOps
- **lukman**: Frontend, UI
- **lia**: Backend, Frontend, QC
- **malik**: Leader
## Architecture & Features
### Core Modules
The application is organized into several major functional areas:
1. **Authentication System** (`/auth`) - Login, registration, validation
2. **Investment Platform** (`/investasi`) - Investment creation, trading, portfolio management
3. **Donation System** (`/donasi`) - Fundraising campaigns, donation processing
4. **Event Management** (`/event`) - Event creation, participation, management
5. **Voting System** (`/vote`) - Voting creation and participation
6. **Job Board** (`/job`) - Job posting and application system
7. **Forum** (`/forum`) - Discussion platform with reporting features
8. **Collaboration Platform** (`/colab`) - Project collaboration tools
9. **User Profiles** (`/profile`) - User profile management
10. **Business Maps** (`/maps`) - Business location mapping
### Admin Panel
The application includes a comprehensive admin panel (`/admin`) with modules for managing:
- Investment approvals and transfers
- Donation campaign reviews
- Event management
- Forum moderation
- Job posting reviews
- User access management
- Voting oversight
### Technical Architecture
#### Routing & Authentication
- Custom middleware handles authentication and authorization
- Public routes are defined in the middleware configuration
- JWT tokens are stored in cookies with secure options
- Role-based access control (MasterUserRole model)
#### Database Schema
The Prisma schema defines a comprehensive data model with:
- User management with roles and profiles
- Investment and financial systems
- Donation and crowdfunding features
- Event management
- Forum and discussion systems
- Collaboration platforms
- Notification systems
- Business mapping features
#### Environment Configuration
The application uses multiple environment files for different deployment stages:
- Development: `run.env.dev`, `run.env.local.dev`
- Build: `run.env.build.dev`, `run.env.build.local`
- Runtime: `run.env.start.dev`, `run.env.start.local`
## Building and Running
### Prerequisites
- Bun runtime installed
- PostgreSQL database
- Required environment variables configured
### Setup Commands
```bash
# Install dependencies
bun install
# Setup database (Prisma)
bun prisma generate
bun prisma db push
bun prisma db seed
# Development server
bun run dev
# Build for production
bun run build
# Start production server
bun run start
```
### Development Scripts
- `dev`: Starts development server with HTTPS
- `build`: Builds the application for production
- `start`: Starts the production server
- `lint`: Runs Next.js linting
- `ver`: Creates version tags
## Development Conventions
### Git Workflow
The team follows a structured Git workflow:
1. Check status with `git status`
2. Add specific files with `git add <file>` (avoid `git add -A`)
3. Commit with structured messages following conventional commits:
- `feat`: New features
- `fix`: Bug fixes
- `docs`: Documentation updates
- `chore`: Routine tasks
- `refactor`: Code restructuring
- `test`: Testing additions
- `style`: Styling changes
- `perf`: Performance improvements
### Code Standards
- TypeScript with strict mode enabled
- Component header comments with file description, creator, date
- Function comments with parameter and return value descriptions
- Custom type interface comments
- Error handling comments
- Complex logic comments
### Commit Message Format
```
type: Short description
Body:
- Detailed description of changes
- Motivation for changes
- Breaking changes if any
References: #issue-number
```
## Project Structure
### Main Directories
- `src/app`: Next.js App Router pages and layouts
- `src/app_modules`: Reusable application modules
- `src/lib`: Shared libraries and utilities
- `src/util`: Utility functions
- `prisma`: Database schema and migrations
- `public`: Static assets
- `certificates`, `logs`: Application data directories
### Key Files
- `package.json`: Dependencies and scripts
- `next.config.js`: Next.js configuration
- `middleware.tsx`: Authentication and routing middleware
- `tsconfig.json`: TypeScript configuration
- `tailwind.config.js`: Styling configuration
- `gen_page.tsx`: Generated page routing utility
- `prisma/schema.prisma`: Database schema definition
## Special Features
### Real-time Capabilities
- WebSocket integration for real-time updates
- MQTT support for messaging
- Live notifications system
### Payment Integration
- Midtrans payment gateway for investments
- Multiple payment methods
- Invoice generation and tracking
### File Handling
- PDF generation and viewing
- Image processing and storage
- Document management for investments
### Mobile Support
- Responsive design
- Mobile-specific features
- Device token management
## Security Measures
### Authentication
- JWT-based authentication
- Session management
- Role-based access control
- Secure token storage in cookies
### Input Validation
- Prisma schema validations
- Server-side validation
- Sanitization of user inputs
### Data Protection
- Encrypted tokens
- Secure API routes
- Proper CORS configuration

View File

@@ -39,6 +39,7 @@
"@types/react-dom": "18.2.7",
"@types/uuid": "^9.0.4",
"autoprefixer": "10.4.14",
"axios": "^1.13.5",
"bufferutil": "^4.0.8",
"bun": "^1.1.38",
"caniuse-lite": "^1.0.30001757",
@@ -1324,7 +1325,7 @@
"axe-core": ["axe-core@4.10.2", "", {}, "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w=="],
"axios": ["axios@0.26.1", "", { "dependencies": { "follow-redirects": "^1.14.8" } }, "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA=="],
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
@@ -1882,7 +1883,7 @@
"fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="],
@@ -1892,7 +1893,7 @@
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
@@ -3618,6 +3619,8 @@
"@types/jsonwebtoken/@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="],
"@types/node-fetch/form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"@types/request/@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="],
"@types/request/form-data": ["form-data@2.5.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="],
@@ -3630,6 +3633,8 @@
"ast-types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"autocannon/form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"autoprefixer/caniuse-lite": ["caniuse-lite@1.0.30001701", "", {}, "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw=="],
"babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -3810,6 +3815,8 @@
"metro-file-map/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"midtrans-client/axios": ["axios@0.26.1", "", { "dependencies": { "follow-redirects": "^1.14.8" } }, "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA=="],
"minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
@@ -4190,6 +4197,12 @@
"metro/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"midtrans-client/axios/follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"next-dev/axios/follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
"next-dev/axios/form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
"next-dev/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"next-scroll-loader/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],

View File

@@ -1,6 +1,6 @@
{
"name": "hipmi",
"version": "1.5.26",
"version": "1.5.40",
"private": true,
"prisma": {
"seed": "bun prisma/seed.ts"
@@ -50,6 +50,7 @@
"@types/react-dom": "18.2.7",
"@types/uuid": "^9.0.4",
"autoprefixer": "10.4.14",
"axios": "^1.13.5",
"bufferutil": "^4.0.8",
"bun": "^1.1.38",
"caniuse-lite": "^1.0.30001757",

View File

@@ -0,0 +1,28 @@
-- AlterTable
ALTER TABLE "Notifikasi" ADD COLUMN "deepLink" TEXT,
ADD COLUMN "readAt" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "TokenUserDevice" (
"id" TEXT NOT NULL,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"platform" TEXT,
"deviceId" TEXT,
"model" TEXT,
"appVersion" TEXT,
"token" TEXT NOT NULL,
"userId" TEXT,
CONSTRAINT "TokenUserDevice_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "TokenUserDevice_userId_idx" ON "TokenUserDevice"("userId");
-- CreateIndex
CREATE INDEX "TokenUserDevice_token_idx" ON "TokenUserDevice"("token");
-- AddForeignKey
ALTER TABLE "TokenUserDevice" ADD CONSTRAINT "TokenUserDevice_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Donasi_Invoice" ALTER COLUMN "masterBankId" DROP DEFAULT;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Notifikasi" ADD COLUMN "type" TEXT;

View File

@@ -0,0 +1,16 @@
-- DropForeignKey
ALTER TABLE "Notifikasi" DROP CONSTRAINT "Notifikasi_userRoleId_fkey";
-- AlterTable
ALTER TABLE "Notifikasi" ADD COLUMN "recipientId" TEXT,
ADD COLUMN "senderId" TEXT,
ALTER COLUMN "userRoleId" DROP NOT NULL;
-- AddForeignKey
ALTER TABLE "Notifikasi" ADD CONSTRAINT "Notifikasi_userRoleId_fkey" FOREIGN KEY ("userRoleId") REFERENCES "MasterUserRole"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notifikasi" ADD CONSTRAINT "Notifikasi_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Notifikasi" ADD CONSTRAINT "Notifikasi_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "Notifikasi" ALTER COLUMN "appId" DROP NOT NULL,
ALTER COLUMN "kategoriApp" DROP NOT NULL,
ALTER COLUMN "pesan" DROP NOT NULL;

View File

@@ -58,6 +58,11 @@ model User {
acceptedTermsAt DateTime?
acceptedForumTermsAt DateTime?
tokenUserDevices TokenUserDevice[]
// For Mobile App
NotificationRecipient Notifikasi[] @relation("NotificationRecipient")
NotificationSender Notifikasi[] @relation("NotificationSender")
}
model MasterUserRole {
@@ -586,7 +591,7 @@ model Donasi_Invoice {
imageId String?
MasterBank MasterBank? @relation(fields: [masterBankId], references: [id])
masterBankId String? @default("null")
masterBankId String?
}
model Donasi_Kabar {
@@ -977,20 +982,32 @@ model Notifikasi {
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isRead Boolean @default(false)
appId String
kategoriApp String
pesan String
appId String?
kategoriApp String?
pesan String?
title String?
status String?
isRead Boolean @default(false)
readAt DateTime? // kapan user membaca notifikasi ini
deepLink String? // misal: "announcement/123", "user/profile/cmha6wb9w0001cfndwl9fcse6"
type String?
Role MasterUserRole? @relation(fields: [userRoleId], references: [id])
userRoleId String
userRoleId String?
User User? @relation("UserNotifikasi", fields: [userId], references: [id], map: "NotifikasiUser")
userId String?
Admin User? @relation("AdminNotifikasi", fields: [adminId], references: [id], map: "NotifikasiAdmin")
adminId String?
// Recipient (user who receives the notification)
recipient User? @relation("NotificationRecipient", fields: [recipientId], references: [id])
recipientId String?
// Sender (user who sent the notification)
sender User? @relation("NotificationSender", fields: [senderId], references: [id])
senderId String?
}
// MAPS
@@ -1099,3 +1116,22 @@ model BlockedUser {
@@unique([blockerId, blockedId])
}
model TokenUserDevice {
id String @id @default(uuid())
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
platform String? // "ios" | "android"
deviceId String? // UUID unik dari device (misal: dari expo-device)
model String? // "iPhone15,4", "Pixel 7", dll
appVersion String? // "1.5.15" — sangat berguna saat debug
token String @db.Text
user User? @relation(fields: [userId], references: [id])
userId String?
@@index([userId])
@@index([token]) // untuk pencarian cepat & deduplikasi
}

View File

@@ -295,7 +295,7 @@
<p>You have the right to request access to the personal information we collect from you, details about how we have processed it, correct inaccuracies, or delete your personal information. You may also have the right to withdraw your consent to our processing of your personal information. These rights may be limited in some circumstances by applicable law.</p>
<p>To make a request, please contact us at <a href="mailto:bip.baliinteraktifperkasa@gmail.com">bip.baliinteraktifperkasa@gmail.com</a>.</p>
<hr style="margin: 30px 0; border: 0; border-top: 1px solid #eee;">
<p>© 2025 Bali Interaktif Perkasa. All rights reserved.</p>
<p>© 2026 Bali Interaktif Perkasa. All rights reserved.</p>
</body>
</html>

View File

@@ -104,7 +104,7 @@
</p>
<footer>
<p>© 2025 Bali Interaktif Perkasa. All rights reserved.</p>
<p>© 2026 Bali Interaktif Perkasa. All rights reserved.</p>
</footer>
</body>
</html>

View File

@@ -2,12 +2,13 @@ import { prisma } from "@/lib";
import { randomOTP } from "@/app_modules/auth/fun/rondom_otp";
import backendLogger from "@/util/backendLogger";
import { NextResponse } from "next/server";
import { funSendToWhatsApp } from "@/lib/code-otp-sender";
export async function POST(req: Request) {
if (req.method !== "POST") {
return NextResponse.json(
{ success: false, message: "Method Not Allowed" },
{ status: 405 }
{ status: 405 },
);
}
@@ -26,32 +27,30 @@ export async function POST(req: Request) {
if (!createOtpId)
return NextResponse.json(
{ success: false, message: "Gagal mengirim kode OTP" },
{ status: 400 }
{ status: 400 },
);
const msg = `HIPMI%20-%20Kode%20ini%20bersifat%20RAHASIA%20dan%20JANGAN%20DI%20BAGIKAN%20KEPADA%20SIAPAPUN%2C%20termasuk%20anggota%20ataupun%20pengurus%20HIPMI%20lainnya.%5Cn%5Cn%3E%3E%20Kode%20OTP%20anda%3A%20${codeOtp}.`;
// const encodedMsg = encodeURIComponent(msg);
const resSendCode = await funSendToWhatsApp({
nomor,
codeOtp: codeOtp.toString(),
});
const res = await fetch(
`https://wa.wibudev.com/code?nom=${nomor}&text=${msg}`,
{ cache: "no-cache" }
);
const sendWa = await res.json();
if (sendWa.status !== "success")
if (resSendCode.status !== 200)
return NextResponse.json(
{ success: false, message: "Nomor Whatsapp Tidak Aktif" },
{ status: 400 }
{ status: 400 },
);
const sendWa = await resSendCode.text();
console.log("WA Response:", sendWa);
return NextResponse.json(
{
success: true,
message: "Kode verifikasi terkirim",
kodeId: createOtpId.id,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
backendLogger.log("Error Login", error);
@@ -61,7 +60,7 @@ export async function POST(req: Request) {
message: "Terjadi masalah saat login",
reason: error as Error,
},
{ status: 500 }
{ status: 500 },
);
}
}
}

View File

@@ -0,0 +1,54 @@
import { prisma } from "@/lib";
import { NextResponse } from "next/server";
export async function POST(req: Request) {
try {
const { nomor } = await req.json();
const user = await prisma.user.findUnique({
where: {
nomor: nomor,
},
});
if (!user)
return NextResponse.json({
success: false,
message: "User belum terdaftar",
status: 404,
});
const updateTerms = await prisma.user.update({
where: { nomor: nomor },
data: {
termsOfServiceAccepted: true,
acceptedTermsAt: new Date(),
},
});
if (!updateTerms) {
return NextResponse.json({
success: false,
message: "Gagal setujui syarat dan ketentuan",
status: 400,
});
}
return NextResponse.json(
{
success: true,
message: "Anda telah setujui syarat dan ketentuan",
},
{ status: 200 }
);
} catch (error) {
return NextResponse.json(
{
success: false,
message: "Terjadi masalah saat setujui syarat dan ketentuan",
reason: error as Error,
},
{ status: 500 }
);
}
}

View File

@@ -1,12 +1,12 @@
import { prisma } from "@/lib";
import { randomOTP } from "@/app_modules/auth/fun/rondom_otp";
import { NextResponse } from "next/server";
import { funSendToWhatsApp } from "@/lib/code-otp-sender";
export async function POST(req: Request) {
try {
const codeOtp = randomOTP();
const body = await req.json();
console.log("[Masuk API]", body);
const { nomor } = body;
const user = await prisma.user.findUnique({
@@ -15,9 +15,6 @@ export async function POST(req: Request) {
},
});
console.log(["cek user", user]);
console.log(["cek nomor", nomor]);
if (!user)
return NextResponse.json({
success: false,
@@ -35,30 +32,21 @@ export async function POST(req: Request) {
if (!createOtpId)
return NextResponse.json(
{ success: false, message: "Gagal mengirim kode OTP" },
{ status: 400 }
{ status: 400 },
);
// const msg = `HIPMI - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPAADA SIAPAPUN, termasuk anggota ataupun pengurus HIPMI lainnya.\n\n\n> Kode OTP anda: ${codeOtp}.`;
// const encodedMsg = encodeURIComponent(msg);
const msg = `HIPMI%20-%20Kode%20ini%20bersifat%20RAHASIA%20dan%20JANGAN%20DI%20BAGIKAN%20KEPADA%20SIAPAPUN%2C%20termasuk%20anggota%20ataupun%20pengurus%20HIPMI%20lainnya.%20Kode%20OTP%20anda%3A%20${codeOtp}.`;
const resSendCode = await funSendToWhatsApp({
nomor,
codeOtp: codeOtp.toString(),
});
const res = await fetch(
`https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=${nomor}&text=${msg}`,
{
cache: "no-cache",
headers: {
Authorization: `Bearer ${process.env.WA_SERVER_TOKEN}`,
},
}
);
if (res.status !== 200)
if (resSendCode.status !== 200)
return NextResponse.json(
{ success: false, message: "Nomor Whatsapp Tidak Aktif" },
{ status: 400 }
{ status: 400 },
);
const sendWa = await res.text();
const sendWa = await resSendCode.text();
console.log("WA Response:", sendWa);
return NextResponse.json(
@@ -66,8 +54,9 @@ export async function POST(req: Request) {
success: true,
message: "Kode verifikasi terkirim",
kodeId: createOtpId.id,
isAcceptTerms: user.termsOfServiceAccepted,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
@@ -76,7 +65,7 @@ export async function POST(req: Request) {
message: "Terjadi masalah saat login",
reason: error as Error,
},
{ status: 500 }
{ status: 500 },
);
}
}

View File

@@ -1,7 +1,13 @@
import { sessionCreate } from "@/app/(auth)/_lib/session_create";
import { randomOTP } from "@/app_modules/auth/fun/rondom_otp";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../types/type-mobile-notification";
import { funSendToWhatsApp } from "@/lib/code-otp-sender";
export async function POST(req: Request) {
if (req.method !== "POST") {
@@ -51,12 +57,6 @@ export async function POST(req: Request) {
{ status: 500 }
);
// const token = await sessionCreate({
// sessionKey: process.env.NEXT_PUBLIC_BASE_SESSION_KEY!,
// encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!,
// user: createUser as any,
// });
const createOtpId = await prisma.kodeOtp.create({
data: {
nomor: data.nomor,
@@ -70,28 +70,58 @@ export async function POST(req: Request) {
{ status: 400 }
);
// const msg = `HIPMI - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPAADA SIAPAPUN, termasuk anggota ataupun pengurus HIPMI lainnya.\n\n\n> Kode OTP anda: ${codeOtp}.`;
const msg = `HIPMI%20-%20Kode%20ini%20bersifat%20RAHASIA%20dan%20JANGAN%20DI%20BAGIKAN%20KEPADA%20SIAPAPUN%2C%20termasuk%20anggota%20ataupun%20pengurus%20HIPMI%20lainnya.%20Kode%20OTP%20anda%3A%20${codeOtp}.`;
// // const encodedMsg = encodeURIComponent(msg);
const resSendCode = await funSendToWhatsApp({
nomor: data.nomor,
codeOtp: codeOtp.toString(),
});
const res = await fetch(
`https://wa.wibudev.com/code?nom=${data.nomor}&text=${msg}`,
{ cache: "no-cache" }
);
const sendWa = await res.json();
if (sendWa.status !== "success")
if (resSendCode.status !== 200)
return NextResponse.json(
{ success: false, message: "Nomor Whatsapp Tidak Aktif" },
{ status: 400 }
{ status: 400 },
);
const sendWa = await resSendCode.text();
console.log("WA Response:", sendWa);
// =========== START SEND NOTIFICATION =========== //
const adminUsers = await prisma.user.findMany({
where: { masterUserRoleId: "2", NOT: { id: data.authorId } },
select: { id: true },
});
console.log("Users to notify:", adminUsers);
const dataNotification = {
title: "Pendaftaran Baru",
type: "announcement",
kategoriApp: "OTHER",
createdAt: new Date(),
pesan: "User baru telah melakukan registrasi. Ayo cek dan verifikasi!",
deepLink: `/admin/user-access/${createUser.id}`,
senderId: createUser.id,
};
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Pendaftaran User Baru" as NotificationMobileTitleType,
body: "User baru telah melakukan registrasi. Ayo cek dan verifikasi!" as NotificationMobileBodyType,
type: "announcement",
deepLink: routeAdminMobile.userAccess({ id: createUser.id }),
kategoriApp: "OTHER",
},
});
// =========== END SEND NOTIFICATION =========== //
return NextResponse.json(
{
success: true,
message: "Registrasi Berhasil",
// token: token,
kodeId: createOtpId.id,
},
{ status: 201 }

View File

@@ -2,12 +2,13 @@ import { prisma } from "@/lib";
import { randomOTP } from "@/app_modules/auth/fun/rondom_otp";
import backendLogger from "@/util/backendLogger";
import { NextResponse } from "next/server";
import { funSendToWhatsApp } from "@/lib/code-otp-sender";
export async function POST(req: Request) {
if (req.method !== "POST") {
return NextResponse.json(
{ success: false, message: "Method Not Allowed" },
{ status: 405 }
{ status: 405 },
);
}
@@ -16,24 +17,20 @@ export async function POST(req: Request) {
const body = await req.json();
const { nomor } = body;
const msg = `HIPMI%20-%20Kode%20ini%20bersifat%20RAHASIA%20dan%20JANGAN%20DI%20BAGIKAN%20KEPADA%20SIAPAPUN%2C%20termasuk%20anggota%20ataupun%20pengurus%20HIPMI%20lainnya.%5Cn%5Cn%3E%3E%20Kode%20OTP%20anda%3A%20${codeOtp}.`;
const resSendCode = await funSendToWhatsApp({
nomor,
codeOtp: codeOtp.toString(),
});
const res = await fetch(
`https://wa.wibudev.com/code?nom=${nomor}&text=${msg}`,
{ cache: "no-cache" }
);
const sendWa = await res.json();
if (sendWa.status !== "success")
if (resSendCode.status !== 200)
return NextResponse.json(
{
success: false,
message: "Nomor Whatsapp Tidak Aktif",
},
{ status: 400 }
{ success: false, message: "Nomor Whatsapp Tidak Aktif" },
{ status: 400 },
);
const sendWa = await resSendCode.text();
console.log("WA Response:", sendWa);
const createOtpId = await prisma.kodeOtp.create({
data: {
nomor: nomor,
@@ -47,7 +44,7 @@ export async function POST(req: Request) {
success: false,
message: "Gagal Membuat Kode OTP",
},
{ status: 400 }
{ status: 400 },
);
return NextResponse.json(
@@ -56,7 +53,7 @@ export async function POST(req: Request) {
message: "Kode Verifikasi Dikirim",
kodeId: createOtpId.id,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
backendLogger.error(" Error Resend OTP", error);
@@ -65,7 +62,7 @@ export async function POST(req: Request) {
success: false,
message: "Server Whatsapp Error !!",
},
{ status: 500 }
{ status: 500 },
);
} finally {
await prisma.$disconnect();

View File

@@ -1,11 +1,22 @@
import { funFindDonaturList } from "@/lib/mobile/donation/find-donatur-list";
import {
sendNotificationMobileToManyUser,
sendNotificationMobileToOneUser,
} from "@/lib/mobile/notification/send-notification";
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { POST, GET };
async function POST(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { title, nominalCair, deskripsi, imageId, authorId } = data;
try {
const dataDonasi = await prisma.donasi.findUnique({
@@ -22,19 +33,19 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
return NextResponse.json(
{
success: false,
message: "Pencarian Donasi Gagal",
reason: "Pencarian Donasi Gagal",
message: "DataPencarian Donasi Gagal",
reason: "Data Pencarian Donasi Gagal",
},
{ status: 400 }
{ status: 400 },
);
const createPencairan = await prisma.donasi_PencairanDana.create({
data: {
donasiId: id,
nominalCair: +data.nominalCair,
deskripsi: data.deskripsi,
title: data.title,
imageId: data.imageId,
nominalCair: +nominalCair,
deskripsi: deskripsi,
title: title,
imageId: imageId,
},
});
@@ -45,11 +56,11 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
message: "Pencairan Dana Gagal",
reason: "Pencairan Dana Gagal",
},
{ status: 400 }
{ status: 400 },
);
const hasilTotalPencairan =
Number(dataDonasi.totalPencairan) + Number(data.nominalCair);
Number(dataDonasi.totalPencairan) + Number(nominalCair);
// const hasilAkumulasiPencairan = Number(dataDonasi.akumulasiPencairan) + 1;
const countPencairan = await prisma.donasi_PencairanDana.count({
@@ -66,8 +77,47 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
akumulasiPencairan: countPencairan,
totalPencairan: hasilTotalPencairan,
},
select: {
authorId: true,
title: true,
},
});
// ================= START SEND NOTIFICATION =================
await sendNotificationMobileToOneUser({
recipientId: updateDonasi?.authorId!,
senderId: authorId,
payload: {
title: "Pencairan Dana Berhasil" as NotificationMobileTitleType,
body: `Telah dilaksanakan pencairan dana untuk ${updateDonasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeUserMobile.donationDetailPublish({
id: id,
}),
},
});
const recipientIds = await funFindDonaturList(id);
if (recipientIds.length > 0) {
await sendNotificationMobileToManyUser({
recipientIds,
senderId: authorId,
payload: {
title: "Pencarian Dana" as NotificationMobileTitleType,
body: `Update pencarian dana pada ${updateDonasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeUserMobile.donationDetailPublish({
id: id,
}),
},
});
}
// ================= END SEND NOTIFICATION =================
if (!updateDonasi)
return NextResponse.json(
{
@@ -75,7 +125,7 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
message: "Update Donasi Gagal",
reason: "Update Donasi Gagal",
},
{ status: 400 }
{ status: 400 },
);
return NextResponse.json(
@@ -84,7 +134,7 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
message: "Pencairan Dana Berhasil",
// data: data,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
console.error("[ERROR]", error);
@@ -94,7 +144,7 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
message: "Pencairan Dana Gagal",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}
@@ -110,7 +160,6 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
console.log("[CATEGORY]", category);
let fixData;
try {
if (category === "get-all") {
fixData = await prisma.donasi_PencairanDana.findMany({
take: page ? takeData : undefined,
@@ -140,7 +189,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
message: "Category tidak ditemukan",
reason: "Category tidak ditemukan",
},
{ status: 400 }
{ status: 400 },
);
}
@@ -150,7 +199,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
message: "Success get data disbursement",
data: fixData,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
console.error("[ERROR]", error);
@@ -160,7 +209,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
message: "Gagal mendapatkan data disbursement",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}

View File

@@ -1,6 +1,9 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import _ from "lodash";
import { sendNotificationMobileToOneUser } from "@/lib/mobile/notification/send-notification";
import { NotificationMobileBodyType, NotificationMobileTitleType } from "../../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { GET, PUT };
@@ -50,6 +53,7 @@ async function GET(req: Request, { params }: { params: { id: string } }) {
interface DataType {
donationId: string;
nominal: number;
senderId: string;
}
async function PUT(req: Request, { params }: { params: { id: string } }) {
@@ -111,6 +115,9 @@ async function PUT(req: Request, { params }: { params: { id: string } }) {
data: {
donasiMaster_StatusInvoiceId: checkStatusTransaksi.id,
},
select: {
authorId: true,
},
});
if (!updateInvoice) {
@@ -154,6 +161,38 @@ async function PUT(req: Request, { params }: { params: { id: string } }) {
);
}
// SEND NOTIFICATION: to donatur
await sendNotificationMobileToOneUser({
recipientId: updateInvoice?.authorId as string,
senderId: data?.senderId || "",
payload: {
title: "Transaksi Berhasil" as NotificationMobileTitleType,
body: `Selamat anda menjadi donatur pada ${updateDonasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeUserMobile.donationTransaction,
},
});
// SEND NOTIFICATION: to creator
await sendNotificationMobileToOneUser({
recipientId: updateDonasi?.authorId as any,
senderId: data?.senderId || "",
payload: {
title: "Ada Donatur Baru !" as NotificationMobileTitleType,
body: `Cek daftar donatur pada ${updateDonasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeUserMobile.donationDetailPublish({
id: updateDonasi?.id as string,
}),
},
});
return NextResponse.json(
{
success: true,

View File

@@ -1,6 +1,15 @@
import {
sendNotificationMobileToManyUser,
sendNotificationMobileToOneUser,
} from "@/lib/mobile/notification/send-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
import prisma from "@/lib/prisma";
import _ from "lodash";
import { NextResponse } from "next/server";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
export { GET, PUT };
@@ -48,7 +57,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
DonasiMaster_StatusInvoice: {
name: "Berhasil",
},
},
},
});
return NextResponse.json(
@@ -60,7 +69,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
donatur: successInvoice,
},
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
@@ -69,7 +78,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
message: "Error get detail Investasi",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}
@@ -77,6 +86,10 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { catatan, senderId } = data;
console.log("[PUT CATATAN]", catatan);
console.log("[PUT SENDER ID]", senderId);
const { searchParams } = new URL(request.url);
const status = searchParams.get("status");
const fixStatus = _.startCase(status as string);
@@ -102,7 +115,7 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
message: "Error update data event",
reason: "Status not found",
},
{ status: 500 }
{ status: 500 },
);
if (fixStatus === "Reject") {
@@ -111,11 +124,24 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
id: id,
},
data: {
catatan: data,
catatan: catatan,
donasiMaster_StatusDonasiId: checkStatus.id,
},
});
// SEND NOTIFICATION
await sendNotificationMobileToOneUser({
recipientId: updateData.authorId as any,
senderId: senderId,
payload: {
title: "Pengajuan Review Ditolak",
body: "Mohon perbaiki data sesuai catatan penolakan !",
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeUserMobile.donationByStatus({ status: "reject" }),
},
});
fixData = updateData;
} else if (fixStatus === "Publish") {
const updateData = await prisma.donasi.update({
@@ -128,6 +154,39 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
// SEND NOTIFICAtION
await sendNotificationMobileToOneUser({
recipientId: updateData.authorId as any,
senderId: senderId,
payload: {
title: "Review Selesai",
body: `Donasi kamu telah dipublikasikan ! ${updateData.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeUserMobile.donationByStatus({ status: "publish" }),
},
});
const allUsers = await prisma.user.findMany({
where: {
NOT: { id: updateData.authorId as any },
active: true,
},
select: { id: true },
});
await sendNotificationMobileToManyUser({
recipientIds: allUsers.map((user) => user.id),
senderId: senderId,
payload: {
title: "Ayo Cek Donasi Terbaru" as NotificationMobileTitleType,
body: `${updateData.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeUserMobile.donationDetailPublish({ id: id }),
},
});
fixData = updateData;
}
@@ -137,7 +196,7 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
message: "Data Donasi Berhasil Diambil",
data: data,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
@@ -146,7 +205,7 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
message: "Error get detail Investasi",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}

View File

@@ -1,9 +1,15 @@
import { NextResponse } from "next/server";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
export { GET };
async function GET(request: Request, { params }: { params: { id: string } }) {
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
try {
const { id } = params;
@@ -12,6 +18,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
eventId: id,
},
select: {
id: true,
eventId: true,
userId: true,
isPresent: true,
@@ -35,6 +42,8 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
},
},
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
});
return NextResponse.json(

View File

@@ -1,6 +1,15 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import _ from "lodash";
import {
sendNotificationMobileToManyUser,
sendNotificationMobileToOneUser,
} from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { GET, PUT };
@@ -57,6 +66,8 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { catatan, senderId } = data;
const { searchParams } = new URL(request.url);
const status = searchParams.get("status");
const fixStatus = _.startCase(status as string);
@@ -89,11 +100,23 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
id: id,
},
data: {
catatan: data,
catatan: catatan,
eventMaster_StatusId: checkStatus.id,
},
});
await sendNotificationMobileToOneUser({
recipientId: updateData.authorId as any,
senderId: senderId,
payload: {
title: "Pengajuan Review Ditolak",
body: "Mohon perbaiki data sesuai catatan penolakan !",
type: "announcement",
kategoriApp: "EVENT",
deepLink: routeUserMobile.eventByStatus({status: "reject"}),
},
});
fixData = updateData;
} else if (fixStatus === "Publish") {
const updateData = await prisma.event.update({
@@ -105,6 +128,38 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
await sendNotificationMobileToOneUser({
recipientId: updateData.authorId as any,
senderId: senderId,
payload: {
title: "Review Selesai",
body: "Event kamu telah dipublikasikan !" as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "EVENT",
deepLink: routeUserMobile.eventByStatus({status: "publish"}),
},
});
const adminUsers = await prisma.user.findMany({
where: {
masterUserRoleId: "1",
NOT: { id: updateData.authorId as any },
},
select: { id: true },
});
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: senderId,
payload: {
title: "Event Baru" as NotificationMobileTitleType,
body: `${updateData.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "EVENT",
deepLink: routeUserMobile.eventDetailPublised({ id: id }),
},
});
fixData = updateData;
}

View File

@@ -1,7 +1,8 @@
import _ from "lodash";
import { prisma } from "@/lib";
import { NextResponse } from "next/server";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import _ from "lodash";
import moment from "moment";
import { NextResponse } from "next/server";
export { GET };
@@ -11,13 +12,12 @@ async function GET(request: Request) {
const fixStatus = _.startCase(category || "");
const search = searchParams.get("search");
const page = searchParams.get("page");
const takeData = 10;
const skipData = Number(page) * takeData - takeData;
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
let fixData;
console.log("[CATEGORY]", category);
// console.log("[FIX STATUS]", fixStatus);
try {
if (category === "dashboard") {
@@ -71,7 +71,6 @@ async function GET(request: Request) {
typeOfEvent,
};
} else if (category === "history") {
console.log("[HISTORY HERE]");
const data = await prisma.event.findMany({
take: page ? takeData : undefined,
@@ -151,21 +150,22 @@ async function GET(request: Request) {
},
},
select: {
id: true,
title: true,
tanggal: true,
Author: {
select: {
id: true,
username: true,
Profile: {
select: {
name: true,
},
id: true,
title: true,
tanggal: true,
tanggalSelesai: true,
Author: {
select: {
id: true,
username: true,
Profile: {
select: {
name: true,
},
},
},
},
},
});
fixData = data;
@@ -177,7 +177,7 @@ async function GET(request: Request) {
message: `Success get data event ${category}`,
data: fixData,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
console.log(`[ERROR GET DATA EVENT: ${category}]`, error);
@@ -187,7 +187,7 @@ async function GET(request: Request) {
message: `Error get data event ${category}`,
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}

View File

@@ -1,6 +1,12 @@
import _ from "lodash";
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { sendNotificationMobileToOneUser } from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { GET, PUT };
@@ -82,21 +88,43 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const data = await request.json();
console.log("SENDER Comment", data);
try {
const deleteData = await prisma.forum_Komentar.update({
const deactiveComment = await prisma.forum_Komentar.update({
where: {
id: id,
},
data: {
isActive: false,
},
select: {
authorId: true,
komentar: true,
},
});
// SEND NOTIFICATION
await sendNotificationMobileToOneUser({
recipientId: deactiveComment?.authorId as string,
senderId: data?.senderId,
payload: {
title: "Penghapusan Komentar" as NotificationMobileTitleType,
body: `Komentar anda telah dilaporkan: ${deactiveComment?.komentar}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "FORUM",
deepLink: routeUserMobile.forumPreviewReportComment({ id: id }),
},
});
console.log("[DEACTIVATE COMMENT]");
return NextResponse.json(
{
success: true,
message: "Success deactivate comment",
data: deleteData,
// data: deactiveComment,
},
{ status: 200 }
);

View File

@@ -1,6 +1,12 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import _ from "lodash";
import { sendNotificationMobileToOneUser } from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { GET, PUT };
@@ -78,14 +84,23 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const data = await request.json();
const { senderId } = data;
console.log("SENDER POSTING", data);
try {
const data = await prisma.forum_Posting.update({
const deactivePosting = await prisma.forum_Posting.update({
where: {
id: id,
},
data: {
isActive: false,
},
select: {
authorId: true,
diskusi: true,
},
});
const deactivateComment = await prisma.forum_Komentar.updateMany({
@@ -97,12 +112,25 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
console.log("[DEACTIVATE COMMENT]", deactivateComment);
// SEND NOTIFICATION
await sendNotificationMobileToOneUser({
recipientId: deactivePosting?.authorId as string,
senderId: senderId,
payload: {
title: "Penghapusan Postingan" as NotificationMobileTitleType,
body: `Postingan anda telah dilaporkan: ${deactivePosting?.diskusi}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "FORUM",
deepLink: routeUserMobile.forumPreviewReportPosting({ id: id }),
},
});
console.log("[DEACTIVATE POSTINGAN & COMMENT]", deactivateComment);
return NextResponse.json(
{
success: true,
message: "Success deactivate posting",
data: data,
data: deactivePosting,
},
{ status: 200 }
);

View File

@@ -1,5 +1,11 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import { sendNotificationMobileToOneUser } from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { GET, PUT };
@@ -65,19 +71,39 @@ async function PUT(req: Request, { params }: { params: { id: string } }) {
data: {
statusInvoiceId: "4",
},
// select: {
// StatusInvoice: true,
// authorId: true,
// },
select: {
Investasi: {
select: {
title: true,
},
},
authorId: true,
},
});
// SEND NOTIFICAtION
await sendNotificationMobileToOneUser({
recipientId: updt?.authorId as string,
senderId: data?.senderId || "",
payload: {
title: "Transaksi Tertolak" as NotificationMobileTitleType,
body: `Maaf transaksi kamu telah ditolak ! ${updt?.Investasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeUserMobile.investmentTransaction,
},
});
fixData = updt;
} else if (category === "accept") {
const dataInvestasi: any = await prisma.investasi.findFirst({
const findInvestasi = await prisma.investasi.findFirst({
where: {
id: data.investasiId,
},
select: {
id: true,
title: true,
authorId: true,
totalLembar: true,
sisaLembar: true,
lembarTerbeli: true,
@@ -85,30 +111,33 @@ async function PUT(req: Request, { params }: { params: { id: string } }) {
});
// Hitung TOTAL SISA LEMBAR
const investasi_sisaLembar = Number(dataInvestasi?.sisaLembar);
const investasi_sisaLembar = Number(findInvestasi?.sisaLembar);
const invoice_lembarTerbeli = Number(data.lembarTerbeli);
const resultSisaLembar = investasi_sisaLembar - invoice_lembarTerbeli;
// TAMBAH LEMBAR TERBELI
const investasi_lembarTerbeli = Number(dataInvestasi?.lembarTerbeli);
const investasi_lembarTerbeli = Number(findInvestasi?.lembarTerbeli);
const resultLembarTerbeli =
investasi_lembarTerbeli + invoice_lembarTerbeli;
// Progress
const investasi_totalLembar = Number(dataInvestasi?.totalLembar);
const investasi_totalLembar = Number(findInvestasi?.totalLembar);
const progress = (resultLembarTerbeli / investasi_totalLembar) * 100;
const resultProgres = Number(progress).toFixed(2);
const updt = await prisma.investasi_Invoice.update({
const updateInvoice = await prisma.investasi_Invoice.update({
where: {
id: id,
},
data: {
statusInvoiceId: "1",
},
select: {
authorId: true,
},
});
if (!updt) {
if (!updateInvoice) {
return NextResponse.json(
{
success: false,
@@ -144,7 +173,35 @@ async function PUT(req: Request, { params }: { params: { id: string } }) {
);
}
fixData = updt;
// SEND NOTIFICATION: to investor
await sendNotificationMobileToOneUser({
recipientId: updateInvoice?.authorId as string,
senderId: data?.senderId || "",
payload: {
title: "Transaksi Berhasil" as NotificationMobileTitleType,
body: `Selamat anda menjadi investor pada investasi ${findInvestasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeUserMobile.investmentTransaction,
},
});
// SEND NOTIFICATION: to creator
await sendNotificationMobileToOneUser({
recipientId: findInvestasi?.authorId as any,
senderId: data?.senderId || "",
payload: {
title: "Ada Investor Baru !" as NotificationMobileTitleType,
body: `Cek daftar investor pada ${findInvestasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeUserMobile.investmentDetailPublish({
id: findInvestasi?.id as string,
}),
},
});
fixData = updateInvoice;
} else {
return NextResponse.json(
{

View File

@@ -1,11 +1,20 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import {
sendNotificationMobileToManyUser,
sendNotificationMobileToOneUser,
} from "@/lib/mobile/notification/send-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
export { GET, PUT };
async function GET(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
try {
const data = await prisma.investasi.findUnique({
where: {
@@ -78,14 +87,19 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { catatan, senderId } = data;
console.log("[DATA]", data);
console.log("[CATATAN]", catatan);
console.log("[SENDER ID]", senderId);
const { searchParams } = new URL(request.url);
const status = searchParams.get("status");
console.log("[=======Start Investment console=======]");
console.log("[ID]", id);
console.log("[DATA]", data);
console.log("[STATUS]", status);
console.log("[=======End Investment console=======]");
// console.log("[=======Start Investment console=======]");
// console.log("[ID]", id);
// console.log("[STATUS]", status);
// console.log("[=======End Investment console=======]");
const publishTime = new Date();
@@ -96,9 +110,26 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
id: id,
},
data: {
catatan: data,
catatan: catatan,
masterStatusInvestasiId: "4",
},
select: {
authorId: true,
title: true,
},
});
// SEND NOTIFICATION
await sendNotificationMobileToOneUser({
recipientId: updatedData.authorId as any,
senderId: senderId,
payload: {
title: "Pengajuan Review Ditolak",
body: "Mohon perbaiki data sesuai catatan penolakan !",
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeUserMobile.investmentPortofolioByStatus({ status: "reject" }),
},
});
console.log("[UPDATE REJECT]", updatedData);
@@ -114,6 +145,39 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
// SEND NOTIFICAtION
await sendNotificationMobileToOneUser({
recipientId: updatedData.authorId as any,
senderId: senderId,
payload: {
title: "Review Selesai",
body: `Investasi kamu telah dipublikasikan ! ${updatedData.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeUserMobile.investmentPortofolioByStatus({ status: "publish" }),
},
});
const allUsers = await prisma.user.findMany({
where: {
NOT: { id: updatedData.authorId as any },
active: true,
},
select: { id: true },
});
await sendNotificationMobileToManyUser({
recipientIds: allUsers.map((user) => user.id),
senderId: senderId,
payload: {
title: "Ayo Cek Investasi Terbaru" as NotificationMobileTitleType,
body: `${updatedData.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeUserMobile.investmentDetailPublish({ id: id }),
},
});
console.log("[UPDATE PUBLISH]", updatedData);
}

View File

@@ -1,6 +1,12 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import _ from "lodash";
import {
sendNotificationMobileToManyUser,
sendNotificationMobileToOneUser,
} from "@/lib/mobile/notification/send-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
import { NotificationMobileBodyType, NotificationMobileTitleType } from "../../../../../../../types/type-mobile-notification";
export { GET, PUT };
@@ -54,6 +60,9 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { catatan, senderId } = data;
const { searchParams } = new URL(request.url);
const status = searchParams.get("status");
const fixStatus = _.startCase(status as string);
@@ -83,7 +92,7 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
data: {
masterStatusId: checkStatus.id,
catatan: data,
catatan: catatan,
},
select: {
id: true,
@@ -97,6 +106,18 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
await sendNotificationMobileToOneUser({
recipientId: updt.authorId as any,
senderId: senderId,
payload: {
title: "Pengajuan Review Ditolak",
body: "Mohon perbaiki data sesuai catatan penolakan !",
type: "announcement",
kategoriApp: "JOB",
deepLink: routeUserMobile.jobByStatus({ status: "reject" }),
},
});
fixData = updt;
} else if (fixStatus === "Publish") {
const updt = await prisma.job.update({
@@ -118,6 +139,35 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
await sendNotificationMobileToOneUser({
recipientId: updt.authorId as any,
senderId: senderId,
payload: {
title: "Review Selesai",
body: "Selamat data anda telah terpublikasi",
type: "announcement",
kategoriApp: "JOB",
deepLink: routeUserMobile.jobByStatus({ status: "publish" }),
},
});
const adminUsers = await prisma.user.findMany({
where: { masterUserRoleId: "1", NOT: { id: updt.authorId as any } },
select: { id: true },
});
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Ada Lowongan Kerja Baru" as NotificationMobileTitleType,
body: `${updt.title}` as NotificationMobileBodyType,
type: "announcement",
deepLink: routeUserMobile.jobDetailPublised({ id: id }),
kategoriApp: "JOB",
},
});
fixData = updt;
}

View File

@@ -1,6 +1,7 @@
import _ from "lodash";
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET };
@@ -8,6 +9,9 @@ async function GET(request: Request, { params }: { params: { name: string } }) {
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const search = searchParams.get("search");
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
let fixData;
try {
@@ -66,6 +70,8 @@ async function GET(request: Request, { params }: { params: { name: string } }) {
title: true,
Author: true,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
});
}

View File

@@ -1,14 +1,22 @@
import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import { NextResponse } from "next/server";
export { GET, POST };
async function GET() {
async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
const data = await prisma.masterBank.findMany({
orderBy: {
updatedAt: "desc",
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
});
return NextResponse.json(

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import { NextResponse } from "next/server";
export { GET, PUT };
@@ -11,6 +12,10 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const category = searchParams.get("category");
const subBidangId = searchParams.get("subBidangId");
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
if (category === "all") {
const bidang = await prisma.masterBidangBisnis.findUnique({
where: {
@@ -45,6 +50,16 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
},
});
fixData = subBidang;
} else if (category === "only-sub-bidang") {
const subBidang = await prisma.masterSubBidangBisnis.findMany({
where: {
masterBidangBisnisId: id,
},
take: takeData,
skip: skipData,
});
fixData = subBidang;
}
@@ -71,9 +86,6 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
console.log("category", category);
console.log("data", data);
try {
if (category === "bidang") {
const updateData = await prisma.masterBidangBisnis.update({

View File

@@ -2,15 +2,24 @@ import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import _ from "lodash";
import { Prisma } from "@prisma/client";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, POST };
async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
const data = await prisma.masterBidangBisnis.findMany({
orderBy: {
createdAt: "asc",
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
});
return NextResponse.json({

View File

@@ -1,4 +1,5 @@
import { prisma } from "@/lib";
import { funSendToWhatsApp } from "@/lib/code-otp-sender";
import _ from "lodash";
import { NextResponse } from "next/server";
@@ -34,9 +35,15 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
console.log("Received data:", data);
console.log("User ID:", id);
console.log("Category:", category);
try {
if (data.active) {
if (category === "access") {
const updateData = await prisma.user.update({
where: {
id: id,
@@ -44,10 +51,27 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
data: {
active: data.active,
},
select: {
nomor: true,
},
});
if (data.active) {
const resSendCode = await funSendToWhatsApp({
nomor: updateData.nomor,
newMessage:
"Halo sahabat HIConnect, \nSelamat akun anda telah aktif ! \n\n*Pesan ini di kirim secara otomatis, tidak perlu di balas.",
});
} else {
const resSendCode = await funSendToWhatsApp({
nomor: updateData.nomor,
newMessage:
"Halo sahabat HIConnect, \nMohon maaf akun anda telah dinonaktifkan ! Hubungi admin untuk informasi lebih lanjut. \n\n*Pesan ini di kirim secara otomatis, tidak perlu di balas.",
});
}
console.log("[Update Active Berhasil]", updateData);
} else if (data.role) {
} else if (category === "role") {
const fixName = _.startCase(data.role.replace(/_/g, " "));
const checkRole = await prisma.masterUserRole.findFirst({
@@ -68,6 +92,12 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
});
console.log("[Update Role Berhasil]", updateData);
} else {
return NextResponse.json({
status: 400,
success: false,
message: "Invalid category",
});
}
return NextResponse.json({

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import { NextResponse } from "next/server";
export { GET };
@@ -7,10 +8,16 @@ async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const search = searchParams.get("search");
const category = searchParams.get("category");
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
console.log("SEARCH", search);
console.log("PAGE", page);
let fixData;
try {
if(category === "only-user"){
if (category === "only-user") {
fixData = await prisma.user.findMany({
orderBy: {
updatedAt: "desc",
@@ -22,8 +29,10 @@ async function GET(request: Request) {
mode: "insensitive",
},
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
});
} else if(category === "only-admin"){
} else if (category === "only-admin") {
fixData = await prisma.user.findMany({
orderBy: {
updatedAt: "desc",
@@ -35,8 +44,10 @@ async function GET(request: Request) {
mode: "insensitive",
},
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
});
} else if (category === "all-role"){
} else if (category === "all-role") {
fixData = await prisma.user.findMany({
orderBy: {
updatedAt: "desc",
@@ -48,13 +59,15 @@ async function GET(request: Request) {
},
{
masterUserRoleId: "2",
}
},
],
username: {
contains: search || "",
mode: "insensitive",
},
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
});
}
@@ -65,13 +78,11 @@ async function GET(request: Request) {
data: fixData,
});
} catch (error) {
return NextResponse.json(
{
status: 500,
success: false,
message: "Error get data user access",
reason: (error as Error).message,
},
);
return NextResponse.json({
status: 500,
success: false,
message: "Error get data user access",
reason: (error as Error).message,
});
}
}

View File

@@ -1,8 +1,17 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import _ from "lodash";
import {
sendNotificationMobileToManyUser,
sendNotificationMobileToOneUser,
} from "@/lib/mobile/notification/send-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
export { GET , PUT};
export { GET, PUT };
async function GET(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
@@ -41,12 +50,16 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { catatan, senderId } = data;
console.log("catatan", catatan);
console.log("senderId", senderId);
const { searchParams } = new URL(request.url);
const status = searchParams.get("status");
const fixStatus = _.startCase(status as string);
let fixData;
try {
const checkStatus = await prisma.voting_Status.findFirst({
where: {
@@ -71,9 +84,23 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
data: {
voting_StatusId: checkStatus.id,
catatan: data,
catatan: catatan,
},
});
// SEND NOTIFICATION
await sendNotificationMobileToOneUser({
recipientId: updateStatus.authorId as any,
senderId: senderId,
payload: {
title: "Pengajuan Review Ditolak",
body: "Mohon perbaiki data sesuai catatan penolakan !",
type: "announcement",
kategoriApp: "VOTING",
deepLink: routeUserMobile.votingByStatus({ status: "reject" }),
},
});
fixData = updateStatus;
} else if (fixStatus === "Publish") {
const updateStatus = await prisma.voting.update({
@@ -84,6 +111,39 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
voting_StatusId: checkStatus.id,
},
});
await sendNotificationMobileToOneUser({
recipientId: updateStatus.authorId as any,
senderId: senderId,
payload: {
title: "Review Selesai",
body: "Voting kamu telah dipublikasikan !" as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "VOTING",
deepLink: routeUserMobile.votingByStatus({ status: "publish" }),
},
});
const adminUsers = await prisma.user.findMany({
where: {
masterUserRoleId: "1",
NOT: { id: updateStatus.authorId as any },
},
select: { id: true },
});
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: senderId,
payload: {
title: "Cek Voting Baru Terpublikasi" as NotificationMobileTitleType,
body: `${updateStatus.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "VOTING",
deepLink: routeUserMobile.votingDetailPublised({ id: id }),
},
});
fixData = updateStatus;
}

View File

@@ -0,0 +1,51 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib";
export { DELETE };
async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const { id } = params;
const { searchParams } = new URL(request.url);
const deviceId = searchParams.get("deviceId");
console.log("ID", id);
console.log("DEVICE ID", deviceId);
try {
const findFirst = await prisma.tokenUserDevice.findFirst({
where: {
userId: id,
deviceId: deviceId as any,
},
});
if (!findFirst) {
return NextResponse.json({
success: false,
message: "User tidak ditemukan !",
});
}
const deleted = await prisma.tokenUserDevice.delete({
where: {
id: findFirst.id,
},
});
console.log("DEL", deleted);
return NextResponse.json({
success: true,
message: "Berhasil menghapus device token user",
});
} catch (error) {
console.log("ERROR", error);
return NextResponse.json(
{ error: (error as Error).message, message: "Terjadi error pada API" },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,121 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib";
export { POST, GET };
async function POST(request: NextRequest) {
try {
// Parse the request body - can accept either nested under 'data' or directly
const requestBody = await request.json();
// Check if the data is nested under 'data' property (as described in the issue)
// or if it's directly in the request body (more common pattern)
const payload = requestBody.data ? requestBody.data : requestBody;
const { userId, platform, deviceId, model, appVersion, fcmToken } = payload;
// Validate required fields
if (!fcmToken) {
return NextResponse.json(
{ error: "Missing FCM token", field: "fcmToken" },
{ status: 400 }
);
}
if (!userId) {
return NextResponse.json(
{ error: "Missing user ID", field: "userId" },
{ status: 400 }
);
}
// Verify that the user exists before creating/updating the device token
const userExists = await prisma.user.findUnique({
where: { id: userId },
select: { id: true }
});
if (!userExists) {
return NextResponse.json(
{ error: "User not found", field: "userId" },
{ status: 404 }
);
}
const existing = await prisma.tokenUserDevice.findFirst({
where: {
token: fcmToken,
userId: userId,
},
select: {
id: true,
},
});
console.log("✅ EX", existing);
let deviceToken;
if (existing) {
deviceToken = await prisma.tokenUserDevice.update({
where: {
id: existing.id,
},
data: {
platform,
deviceId,
model,
appVersion,
isActive: true,
updatedAt: new Date(),
},
});
} else {
// Create new device token record
deviceToken = await prisma.tokenUserDevice.create({
data: {
token: fcmToken,
userId: userId,
platform,
deviceId,
model,
appVersion,
isActive: true,
},
});
}
return NextResponse.json({ success: true, data: deviceToken });
} catch (error: any) {
console.error("Error registering device token:", error);
// Return more informative error response
return NextResponse.json(
{
error: "Internal server error",
message: error.message || "An unexpected error occurred",
field: "server"
},
{ status: 500 }
);
}
}
async function GET(request: NextRequest) {
try {
const data = await prisma.tokenUserDevice.findMany({
where: {
isActive: true,
},
});
return NextResponse.json({ success: true, data });
} catch (error) {
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 }
);
}
}

View File

@@ -1,6 +1,7 @@
import _ from "lodash";
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, PUT };
@@ -10,8 +11,14 @@ async function GET(
) {
const { id, status } = params;
const fixStatus = _.startCase(status);
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE
const skipData = page * takeData - takeData;
let fixData;
let meta = null;
try {
const checkStatus = await prisma.donasiMaster_StatusDonasi.findFirst({
where: {
@@ -50,18 +57,38 @@ async function GET(
orderBy: {
updatedAt: "desc",
},
take: takeData,
skip: skipData,
});
const totalData = await prisma.donasi.count({
where: {
authorId: id,
donasiMaster_StatusDonasiId: checkStatus.id,
active: true,
},
});
const totalPages = Math.ceil(totalData / takeData);
fixData = res.map((v: any) => ({
..._.omit(v, ["DonasiMaster_Durasi"]),
nameDonasiDurasi: v.DonasiMaster_Durasi.name,
}));
meta = {
currentPage: page,
totalData: totalData,
totalPage: totalPages,
dataPerPage: takeData,
};
return NextResponse.json({
status: 200,
success: true,
message: "Berhasil mendapatkan data",
data: fixData,
...(meta && { meta }),
});
} catch (error) {
console.log("[ERROR]", error);

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET };
@@ -7,7 +8,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = 5;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
try {

View File

@@ -1,3 +1,4 @@
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
@@ -9,11 +10,12 @@ export async function GET(
let fixData;
const { id } = params;
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = 10;
const page = Number(searchParams.get("page")) || 1; // Default page 1 jika tidak ada atau invalid
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
fixData = await prisma.donasi_Invoice.findMany({
// Query data dengan pagination
const data = await prisma.donasi_Invoice.findMany({
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
orderBy: {
@@ -59,10 +61,31 @@ export async function GET(
},
});
// Hitung total data untuk pagination
const totalCount = await prisma.donasi_Invoice.count({
where: {
donasiId: id,
DonasiMaster_StatusInvoice: {
name: "Berhasil",
},
},
});
// Hitung total halaman
const totalPages = Math.ceil(totalCount / takeData);
fixData = data;
return NextResponse.json({
success: true,
message: "Data berhasil diambil",
data: fixData,
pagination: {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
},
});
} catch (error) {
return NextResponse.json({

View File

@@ -1,6 +1,12 @@
import _ from "lodash";
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
export { POST, GET, PUT };
@@ -33,6 +39,14 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
},
});
if (!create) {
return NextResponse.json({
status: 500,
success: false,
message: "Gagal membuat invoice",
});
}
return NextResponse.json({
status: 201,
success: true,
@@ -48,7 +62,7 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
reason: (error as Error).message,
});
}
}
}
async function GET(request: Request, { params }: { params: { id: string } }) {
try {
@@ -65,7 +79,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
createdAt: true,
donasiMaster_BankId: true,
donasiMaster_StatusInvoiceId: true,
MasterBank: true,
MasterBank: true,
Donasi: {
select: {
id: true,
@@ -139,7 +153,7 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
});
}
const update = await prisma.donasi_Invoice.update({
const updated = await prisma.donasi_Invoice.update({
where: {
id: id,
},
@@ -164,7 +178,40 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
console.log("[UPDATE INVOICE]", update);
if (!updated) {
return NextResponse.json({
status: 500,
success: false,
message: "Gagal memperbarui data",
});
}
const findUsers = await prisma.user.findMany({
where: {
masterUserRoleId: "2",
active: true,
NOT: { id: updated?.Donasi?.authorId as string },
},
select: { id: true },
});
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: findUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Ada Donasi Baru !" as NotificationMobileTitleType,
body: `Cek data investor pada ${updated?.Donasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeAdminMobile.donationDetailPublish({
id: updated?.Donasi?.id as string,
status: "publish",
}),
},
});
console.log("[UPDATE INVOICE]", updated);
return NextResponse.json({
status: 200,

View File

@@ -1,25 +1,40 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib";
import _ from "lodash";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
import { funFindDonaturList } from "@/lib/mobile/donation/find-donatur-list";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET, PUT, DELETE };
async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: { id: string } },
) {
const { id } = params;
const { data } = await request.json();
const { title, deskripsi, imageId } = data;
const senderId = await prisma.donasi.findUnique({
where: { id: id },
select: {
authorId: true,
},
});
try {
if (data && data?.imageId) {
const createWithFile = await prisma.donasi_Kabar.create({
data: {
title: data.title,
deskripsi: data.deskripsi,
title: title,
deskripsi: deskripsi,
donasiId: id,
imageId: data.imageId,
imageId: imageId,
},
});
@@ -28,8 +43,8 @@ async function POST(
} else {
const create = await prisma.donasi_Kabar.create({
data: {
title: data.title,
deskripsi: data.deskripsi,
title: title,
deskripsi: deskripsi,
donasiId: id,
},
});
@@ -38,6 +53,25 @@ async function POST(
return NextResponse.json({ status: 400, message: "Gagal disimpan" });
}
const recipientIds = await funFindDonaturList(id);
// SEND NOTIFICATION
if (recipientIds.length > 0) {
await sendNotificationMobileToManyUser({
recipientIds,
senderId: senderId?.authorId!,
payload: {
title: "Berita terbaru" as NotificationMobileTitleType,
body: `Ada berita terupdate pada ${title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "DONASI",
deepLink: routeUserMobile.donationDetailPublish({
id: id,
}),
},
});
}
return NextResponse.json({
status: 200,
success: true,
@@ -56,16 +90,21 @@ async function POST(
async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: { id: string } },
) {
const { id } = params;
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const page = Number(searchParams.get("page")) || 1; // Default page 1 jika tidak ada
const takeData = PAGINATION_DEFAULT_TAKE; // Default 10 data per halaman
const skipData = page * takeData - takeData;
let fixData;
let totalCount = 0; // Untuk menghitung total data
try {
if (category === "get-all") {
fixData = await prisma.donasi_Kabar.findMany({
const data = await prisma.donasi_Kabar.findMany({
orderBy: {
updatedAt: "desc",
},
@@ -73,6 +112,8 @@ async function GET(
donasiId: id,
active: true,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
title: true,
@@ -80,6 +121,17 @@ async function GET(
createdAt: true,
},
});
// Hitung total data untuk pagination
totalCount = await prisma.donasi_Kabar.count({
where: {
donasiId: id,
active: true,
},
});
fixData = data;
} else if (category === "get-one") {
const data = await prisma.donasi_Kabar.findUnique({
where: {
@@ -102,11 +154,24 @@ async function GET(
};
}
// Hitung total halaman jika kategori adalah get-all
let pagination = undefined;
if (category === "get-all") {
const totalPages = Math.ceil(totalCount / takeData);
pagination = {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
};
}
return NextResponse.json({
status: 200,
success: true,
message: "Berhasil mengambil kabar",
data: fixData,
pagination: pagination,
});
} catch (error) {
console.error("[ERROR GET NEWS]", error);
@@ -178,7 +243,7 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
async function DELETE(
request: Request,
{ params }: { params: { id: string } }
{ params }: { params: { id: string } },
) {
const { id } = params;
try {
@@ -198,7 +263,7 @@ async function DELETE(
headers: {
Authorization: `Bearer ${process.env.WS_APIKEY}`,
},
}
},
);
if (!deleteImage) {

View File

@@ -1,8 +1,12 @@
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import prisma from "@/lib/prisma";
import _ from "lodash";
import { NextResponse } from "next/server";
import { NotificationMobileBodyType } from "../../../../../types/type-mobile-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST };
export { POST, GET };
async function POST(request: Request) {
const { data } = await request.json();
@@ -49,6 +53,26 @@ async function POST(request: Request) {
console.log("[DATA DONASI]", dataDonasi);
const adminUsers = await prisma.user.findMany({
where: { masterUserRoleId: "2", NOT: { id: data.authorId } },
select: { id: true },
});
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Pengajuan Review Baru",
body: data.title as NotificationMobileBodyType,
type: "announcement",
deepLink: routeAdminMobile.donationByStatus({ status: "review" }),
kategoriApp: "DONASI",
},
});
if (!dataDonasi)
return NextResponse.json({
status: 400,
@@ -98,11 +122,16 @@ async function POST(request: Request) {
}
// GET ALL DATA DONASI
export async function GET(request: Request) {
async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const authorId = searchParams.get("authorId");
const page = Number(searchParams.get("page")) || 1; // Default page 1 jika tidak ada
const takeData = PAGINATION_DEFAULT_TAKE; // Default 10 data per halaman
const skipData = page * takeData - takeData;
let fixData;
let totalCount = 0; // Untuk menghitung total data
try {
if (category === "beranda") {
@@ -114,6 +143,8 @@ export async function GET(request: Request) {
donasiMaster_StatusDonasiId: "1",
active: true,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
imageId: true,
@@ -129,6 +160,14 @@ export async function GET(request: Request) {
},
});
// Hitung total data untuk pagination
totalCount = await prisma.donasi.count({
where: {
donasiMaster_StatusDonasiId: "1",
active: true,
},
});
fixData = data.map((v: any) => ({
..._.omit(v, ["DonasiMaster_Durasi"]),
durasiDonasi: v.DonasiMaster_Durasi.name,
@@ -141,6 +180,8 @@ export async function GET(request: Request) {
where: {
authorId: authorId,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
nominal: true,
@@ -167,6 +208,13 @@ export async function GET(request: Request) {
},
});
// Hitung total data untuk pagination
totalCount = await prisma.donasi_Invoice.count({
where: {
authorId: authorId,
},
});
fixData = data.map((v: any) => ({
..._.omit(v, ["DonasiMaster_StatusInvoice", "Donasi"]),
statusInvoice: v.DonasiMaster_StatusInvoice.name,
@@ -179,8 +227,21 @@ export async function GET(request: Request) {
}));
}
// Hitung total halaman
const totalPages = Math.ceil(totalCount / takeData);
return NextResponse.json(
{ success: true, message: "Data berhasil diambil", data: fixData },
{
success: true,
message: "Data berhasil diambil",
data: fixData,
pagination: {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
}
},
{ status: 200 }
);
} catch (error) {

View File

@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import _ from "lodash";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, PUT };
@@ -12,6 +13,11 @@ async function GET(
const { id, status } = params;
const fixStatusName = _.startCase(status);
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE
const skipData = page * takeData - takeData;
const data = await prisma.event.findMany({
orderBy: {
updatedAt: "desc",
@@ -37,13 +43,35 @@ async function GET(
},
authorId: true,
},
take: takeData,
skip: skipData,
});
// Get total count for pagination info
const totalCount = await prisma.event.count({
where: {
active: true,
authorId: id,
isArsip: false,
EventMaster_Status: {
name: fixStatusName,
},
},
});
const totalPages = Math.ceil(totalCount / takeData);
return NextResponse.json(
{
success: true,
message: "Success get event",
data: data,
pagination: {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
},
},
{ status: 200 }
);

View File

@@ -1,5 +1,12 @@
import { sendNotificationMobileToOneUser } from "@/lib/mobile/notification/send-notification";
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, POST };
@@ -13,25 +20,35 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
eventId: id,
userId: userId,
},
// select: {
// Event: {
// select: {
// id: true,
// title: true,
// authorId: true,
// },
// },
// },
});
const findEvent = await prisma.event.findUnique({
where: { id: id },
select: { authorId: true, title: true },
});
// SEND NOTIFICATION
if (userId !== findEvent?.authorId) {
await sendNotificationMobileToOneUser({
recipientId: findEvent?.authorId as string,
senderId: userId,
payload: {
title: "Peserta Baru Join" as NotificationMobileTitleType,
body: `Ada peserta baru dalam event: ${findEvent?.title}` as NotificationMobileBodyType,
type: "announcement",
deepLink: routeUserMobile.eventDetailPublised({ id: id }),
kategoriApp: "EVENT",
},
});
}
return NextResponse.json(
{
success: true,
message: "Success join event",
data: createJoin,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
@@ -40,7 +57,7 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
message: "Error join event",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}
@@ -48,12 +65,17 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
async function GET(request: Request, { params }: { params: { id: string } }) {
try {
const { id } = params;
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE
const skipData = page * takeData - takeData;
const data = await prisma.event_Peserta.findMany({
where: {
eventId: id,
},
select: {
id: true,
eventId: true,
userId: true,
isPresent: true,
@@ -71,6 +93,8 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
},
},
},
take: takeData,
skip: skipData,
});
return NextResponse.json(
@@ -78,8 +102,14 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
success: true,
message: "Success get participants",
data: data,
meta: {
page,
take: takeData,
total: await prisma.event_Peserta.count({ where: { eventId: id } }),
totalPages: Math.ceil(await prisma.event_Peserta.count({ where: { eventId: id } }) / takeData),
},
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
@@ -88,7 +118,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
message: "Error get participants",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}

View File

@@ -1,7 +1,11 @@
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import prisma from "@/lib/prisma";
import _ from "lodash";
import moment from "moment";
import { NextResponse } from "next/server";
import { NotificationMobileBodyType } from "../../../../../types/type-mobile-notification";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, POST };
@@ -30,6 +34,24 @@ async function POST(request: Request) {
},
});
const adminUsers = await prisma.user.findMany({
where: { masterUserRoleId: "2", NOT: { id: data.authorId } },
select: { id: true },
});
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Pengajuan Review Baru",
body: create.title as NotificationMobileBodyType,
type: "announcement",
deepLink: routeAdminMobile.eventByStatus({ status: "review" }),
kategoriApp: "EVENT",
},
});
return NextResponse.json(
{
success: true,
@@ -55,11 +77,15 @@ async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const userId = searchParams.get("userId");
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
console.log("[CAT]", category);
console.log("[USER]", userId);
let fixData;
let totalCount = 0;
if (category === "beranda") {
const allData = await prisma.event.findMany({
@@ -87,84 +113,96 @@ async function GET(request: Request) {
}
}
// const takeData = 10;
// const skipData = page * takeData - takeData;
const data = await prisma.event.findMany({
// take: takeData,
// skip: skipData,
orderBy: [
{
tanggal: "asc",
const [data, count] = await Promise.all([
prisma.event.findMany({
take: takeData,
skip: skipData,
orderBy: [
{
tanggal: "asc",
},
],
where: {
active: true,
eventMaster_StatusId: "1",
isArsip: false,
},
],
where: {
active: true,
eventMaster_StatusId: "1",
isArsip: false,
},
select: {
id: true,
title: true,
deskripsi: true,
tanggal: true,
tanggalSelesai: true,
EventMaster_Status: {
select: {
name: true,
select: {
id: true,
title: true,
deskripsi: true,
tanggal: true,
tanggalSelesai: true,
EventMaster_Status: {
select: {
name: true,
},
},
authorId: true,
Author: {
include: {
Profile: true,
},
},
},
authorId: true,
Author: {
include: {
Profile: true,
},
}),
prisma.event.count({
where: {
active: true,
eventMaster_StatusId: "1",
isArsip: false,
},
},
});
})
]);
fixData = data;
totalCount = count;
} else if (category === "contribution") {
const data = await prisma.event_Peserta.findMany({
where: {
userId: userId,
},
select: {
eventId: true,
userId: true,
Event: {
select: {
id: true,
title: true,
tanggal: true,
Author: {
select: {
id: true,
username: true,
Profile: {
select: {
id: true,
name: true,
imageId: true,
const [data, count] = await Promise.all([
prisma.event_Peserta.findMany({
take: takeData,
skip: skipData,
where: {
userId: userId,
},
select: {
id: true,
eventId: true,
userId: true,
Event: {
select: {
id: true,
title: true,
tanggal: true,
Author: {
select: {
id: true,
username: true,
Profile: {
select: {
id: true,
name: true,
imageId: true,
},
},
},
},
},
Event_Peserta: {
take: 4,
orderBy: {
createdAt: "desc",
},
select: {
id: true,
userId: true,
User: {
select: {
Profile: {
select: {
id: true,
name: true,
imageId: true,
Event_Peserta: {
take: 4,
orderBy: {
createdAt: "desc",
},
select: {
id: true,
userId: true,
User: {
select: {
Profile: {
select: {
id: true,
name: true,
imageId: true,
},
},
},
},
@@ -173,86 +211,109 @@ async function GET(request: Request) {
},
},
},
// User: {
// select: {
// id: true,
// username: true,
// Profile: {
// select: {
// id: true,
// name: true,
// imageId: true,
// },
// },
// },
// },
},
});
}),
prisma.event_Peserta.count({
where: {
userId: userId,
},
})
]);
fixData = data;
totalCount = count;
} else if (category === "all-history") {
const data = await prisma.event.findMany({
orderBy: {
tanggal: "desc",
},
where: {
eventMaster_StatusId: "1",
isArsip: true,
},
select: {
id: true,
title: true,
tanggal: true,
deskripsi: true,
active: true,
authorId: true,
Author: {
select: {
id: true,
username: true,
Profile: true,
const [data, count] = await Promise.all([
prisma.event.findMany({
take: takeData,
skip: skipData,
orderBy: {
tanggal: "desc",
},
where: {
eventMaster_StatusId: "1",
isArsip: true,
},
select: {
id: true,
title: true,
tanggal: true,
deskripsi: true,
active: true,
authorId: true,
Author: {
select: {
id: true,
username: true,
Profile: true,
},
},
},
},
});
}),
prisma.event.count({
where: {
eventMaster_StatusId: "1",
isArsip: true,
},
})
]);
fixData = data;
totalCount = count;
} else if (category === "my-history") {
const data = await prisma.event.findMany({
orderBy: {
tanggal: "desc",
},
where: {
authorId: userId,
eventMaster_StatusId: "1",
isArsip: true,
},
select: {
id: true,
title: true,
tanggal: true,
deskripsi: true,
active: true,
authorId: true,
Author: {
select: {
id: true,
username: true,
Profile: true,
const [data, count] = await Promise.all([
prisma.event.findMany({
take: takeData,
skip: skipData,
orderBy: {
tanggal: "desc",
},
where: {
authorId: userId,
eventMaster_StatusId: "1",
isArsip: true,
},
select: {
id: true,
title: true,
tanggal: true,
deskripsi: true,
active: true,
authorId: true,
Author: {
select: {
id: true,
username: true,
Profile: true,
},
},
},
},
});
}),
prisma.event.count({
where: {
authorId: userId,
eventMaster_StatusId: "1",
isArsip: true,
},
})
]);
fixData = data;
totalCount = count;
}
const totalPages = Math.ceil(totalCount / takeData);
return NextResponse.json(
{
success: true,
message: "Success get event",
data: fixData,
pagination: {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
},
},
{ status: 200 }
);

View File

@@ -1,11 +1,18 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { sendNotificationMobileToOneUser } from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { POST, GET, DELETE };
async function POST(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { comment, authorId } = data;
console.log("[ID COMMENT]", id);
console.log("[DATA COMMENT]", data);
@@ -14,8 +21,8 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
const createComment = await prisma.forum_Komentar.create({
data: {
forum_PostingId: id,
komentar: data.comment,
authorId: data.authorId,
komentar: comment,
authorId: authorId,
},
select: {
id: true,
@@ -38,6 +45,24 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
},
});
const findForum = await prisma.forum_Posting.findUnique({
where: { id: id },
select: { authorId: true, diskusi: true },
});
// SEND NOTIFICATION
await sendNotificationMobileToOneUser({
recipientId: findForum?.authorId as string,
senderId: authorId,
payload: {
title: "Komentar Baru" as NotificationMobileTitleType,
body: `Ayo cek komentar pada postingan: ${findForum?.diskusi}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "FORUM",
deepLink: routeUserMobile.forumDetail({ id: id }),
},
});
if (!createComment) {
return NextResponse.json({
status: 400,
@@ -52,7 +77,6 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
message: "Berhasil update data",
data: createComment,
});
} catch (error) {
console.log("[ERROR COMMENT]", error);
return NextResponse.json({
@@ -66,9 +90,15 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
async function GET(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = 5
const skipData = page * takeData - takeData;
try {
const data = await prisma.forum_Komentar.findMany({
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
orderBy: {
createdAt: "desc",
},
@@ -114,7 +144,10 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
}
}
async function DELETE(request: Request, { params }: { params: { id: string } }) {
async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
const { id } = params;
try {
@@ -146,4 +179,4 @@ async function DELETE(request: Request, { params }: { params: { id: string } })
reason: (error as Error).message || error,
});
}
}
}

View File

@@ -0,0 +1,42 @@
import { NextResponse } from "next/server";
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const { id } = params;
try {
const data = await prisma.forum_Komentar.findUnique({
where: {
id: id,
},
select: {
id: true,
komentar: true,
isActive: true,
createdAt: true,
Forum_ReportKomentar: {
select: {
deskripsi: true,
ForumMaster_KategoriReport: true,
},
},
},
});
return NextResponse.json({
status: 200,
success: true,
data: data,
});
} catch (error) {
console.log("[ERROR]", error);
return NextResponse.json({
status: 500,
success: false,
message: "Gagal mendapatkan data posting",
reason: (error as Error).message,
});
}
}

View File

@@ -0,0 +1,49 @@
import { NextResponse } from "next/server";
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const { id } = params;
try {
const data = await prisma.forum_Posting.findUnique({
where: {
id: id,
},
select: {
id: true,
diskusi: true,
isActive: true,
createdAt: true,
authorId: true,
Author: {
select: {
id: true,
username: true,
},
},
Forum_ReportPosting: {
select: {
deskripsi: true,
ForumMaster_KategoriReport: true,
},
},
},
});
return NextResponse.json({
status: 200,
success: true,
data: data,
});
} catch (error) {
console.log("[ERROR]", error);
return NextResponse.json({
status: 500,
success: false,
message: "Gagal mendapatkan data posting",
reason: (error as Error).message,
});
}
}

View File

@@ -1,5 +1,11 @@
import { prisma } from "@/lib";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import { NextResponse } from "next/server";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
export { POST };
@@ -7,44 +13,70 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
let fixData;
const { id } = params;
const { data } = await request.json();
console.log("[DATA]", data);
console.log("[ID]", id);
const { authorId: reportedUserId, categoryId, description } = data;
try {
const content = await prisma.forum_Komentar.findUnique({
where: {
id: id,
},
// Komentar yang di report
const findComment = await prisma.forum_Komentar.findUnique({
where: { id: id },
select: { authorId: true, komentar: true },
});
const reportList = await prisma.forumMaster_KategoriReport.findUnique({
// List admin untuk dikirim notifikasi
const adminUsers = await prisma.user.findMany({
where: {
id: data.categoryId,
masterUserRoleId: "2",
NOT: { id: findComment?.authorId as any },
},
select: { id: true },
});
const msg = `Report Komentar: "${content?.komentar}" dengan kategori \n\n\n${reportList?.title} : \n\n${reportList?.deskripsi}`;
const res = await fetch(
`https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=6282340374412&text=${msg}`,
{ cache: "no-cache" }
);
if (data.categoryId) {
fixData = await prisma.forum_ReportKomentar.create({
if (categoryId) {
const createdReport = await prisma.forum_ReportKomentar.create({
data: {
forum_KomentarId: id,
userId: data.authorId,
forumMaster_KategoriReportId: data.categoryId as any,
userId: reportedUserId,
forumMaster_KategoriReportId: categoryId,
},
});
//SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: reportedUserId,
payload: {
title: "Laporan Dari User" as NotificationMobileTitleType,
body: `Report terhadap komentar, ${findComment?.komentar}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "FORUM",
deepLink: routeAdminMobile.forumPreviewReportComment,
},
});
fixData = createdReport;
} else {
fixData = await prisma.forum_ReportKomentar.create({
const createdReport = await prisma.forum_ReportKomentar.create({
data: {
forum_KomentarId: id,
userId: data.authorId,
deskripsi: data.description,
userId: reportedUserId,
deskripsi: description,
},
});
//SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: reportedUserId,
payload: {
title: "Laporan Dari User" as NotificationMobileTitleType,
body: `Report terhadap komentar, ${findComment?.komentar}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "FORUM",
deepLink: routeAdminMobile.forumPreviewReportComment,
},
});
fixData = createdReport;
}
if (!fixData) {

View File

@@ -1,5 +1,15 @@
import { NextResponse } from "next/server";
import {
sendNotificationMobileToManyUser
} from "@/lib/mobile/notification/send-notification";
import {
routeAdminMobile
} from "@/lib/mobile/route-page-mobile";
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
export { POST };
@@ -7,38 +17,70 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
let fixData;
const { id } = params;
const { data } = await request.json();
console.log("[DATA]", data);
console.log("[ID]", id);
const { authorId: reportedUserId, categoryId, description } = data;
try {
const content = await prisma.forum_Posting.findUnique({
where: {
id: id,
},
// Postingan yang akan di report
const findPosting = await prisma.forum_Posting.findUnique({
where: { id: id },
select: { authorId: true, diskusi: true },
});
const msg = `Report Postingan: "${content?.diskusi}"`;
const res = await fetch(
`https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=6282340374412&text=${msg}`,
{ cache: "no-cache" }
);
// List admin untuk dikirim notifikasi
const adminUsers = await prisma.user.findMany({
where: {
masterUserRoleId: "2",
NOT: { id: findPosting?.authorId as any },
},
select: { id: true },
});
if (data.categoryId) {
fixData = await prisma.forum_ReportPosting.create({
if (categoryId) {
const createReported = await prisma.forum_ReportPosting.create({
data: {
forum_PostingId: id,
userId: data.authorId,
forumMaster_KategoriReportId: data.categoryId,
userId: reportedUserId,
forumMaster_KategoriReportId: categoryId,
},
});
//SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: reportedUserId,
payload: {
title: "Laporan Dari User" as NotificationMobileTitleType,
body: `Report terhadap postingan, ${findPosting?.diskusi}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "FORUM",
deepLink: routeAdminMobile.forumPreviewReportPosting,
},
});
fixData = createReported;
} else {
fixData = await prisma.forum_ReportPosting.create({
const createReported = await prisma.forum_ReportPosting.create({
data: {
forum_PostingId: id,
userId: data.authorId,
deskripsi: data.description,
userId: reportedUserId,
deskripsi: description,
},
});
//SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: reportedUserId,
payload: {
title: "Laporan Dari User" as NotificationMobileTitleType,
body: `Report terhadap postingan, ${findPosting?.diskusi}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "FORUM",
deepLink: routeAdminMobile.forumPreviewReportPosting,
},
});
fixData = createReported;
}
if (!fixData) {
@@ -63,4 +105,4 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
reason: (error as Error).message,
});
}
}
}

View File

@@ -1,22 +1,54 @@
import _ from "lodash";
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import { NotificationMobileBodyType, NotificationMobileTitleType } from "../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { POST, GET };
async function POST(request: Request) {
const { data } = await request.json();
console.log("[DATA]", data);
const { diskusi, authorId } = data;
try {
const create = await prisma.forum_Posting.create({
data: {
diskusi: data.diskusi,
authorId: data.authorId,
diskusi: diskusi,
authorId: authorId,
forumMaster_StatusPostingId: 1,
},
});
if (!create) {
return NextResponse.json({
success: false,
message: "Gagal memposting",
status: 400,
});
}
const allUsers = await prisma.user.findMany({
where: {
id: { not: authorId },
},
select: { id: true },
});
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: allUsers.map((user) => user.id),
senderId: authorId,
payload: {
title: "Ada Diskusi Baru" as NotificationMobileTitleType,
body: `${diskusi}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "FORUM",
deepLink: routeUserMobile.forumBeranda,
},
});
return NextResponse.json({
success: true,
message: "Berhasil membuat postingan",
@@ -43,7 +75,6 @@ async function GET(request: Request) {
const takeData = 5;
const skipData = (Number(page) - 1) * takeData;
// console.log("authorId", authorId);
// console.log("userLoginId", userLoginId);
// console.log("search", search);
@@ -83,7 +114,7 @@ async function GET(request: Request) {
notIn: blockUserId,
},
},
select: {
id: true,
diskusi: true,
@@ -128,13 +159,12 @@ async function GET(request: Request) {
fixData = newData;
} else if (category === "forumku") {
const count = await prisma.forum_Posting.count({
where: {
isActive: true,
authorId: authorId,
},
})
});
const data = await prisma.forum_Posting.findMany({
take: page ? takeData : undefined,
@@ -191,7 +221,7 @@ async function GET(request: Request) {
const dataFix = {
data: newData,
count,
}
};
fixData = dataFix;
} else {

View File

@@ -1,6 +1,7 @@
import _ from "lodash";
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, PUT };
@@ -9,6 +10,10 @@ async function GET(
{ params }: { params: { id: string; status: string } }
) {
const { id, status } = params;
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page ? page * takeData - takeData : 0;
const fixStatusName = _.startCase(status);
try {
@@ -22,6 +27,8 @@ async function GET(
name: fixStatusName,
},
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
title: true,

View File

@@ -1,5 +1,12 @@
import { NextResponse } from "next/server";
import { prisma } from "@/lib";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../../types/type-mobile-notification";
import { routeAdminMobile, routeUserMobile } from "@/lib/mobile/route-page-mobile";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET, DELETE };
@@ -10,7 +17,7 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
console.log("[POST DOCUMENT DATA]", data);
try {
const create = await prisma.dokumenInvestasi.upsert({
const createdDocs = await prisma.dokumenInvestasi.upsert({
where: {
id: id,
},
@@ -23,9 +30,49 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
title: data.title,
fileId: data.fileId,
},
select: {
investasiId: true,
investasi: {
select: {
title: true,
authorId: true,
},
},
},
});
if (!create)
console.log("[CREATED DOCS]", createdDocs);
const findInvestor = await prisma.investasi_Invoice.findMany({
where: {
investasiId: id,
StatusInvoice: {
name: "Berhasil",
},
},
select: {
authorId: true,
},
});
console.log("[FIND INVESTOR]", findInvestor);
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: findInvestor.map((e) => e.authorId!),
senderId: createdDocs.investasi?.authorId as string,
payload: {
title: "Cek Dokumen" as NotificationMobileTitleType,
body: `Ada dokumen terupdate pada ${createdDocs.investasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeUserMobile.investmentDetailPublish({
id: createdDocs.investasiId as string,
}),
},
});
if (!createdDocs)
return NextResponse.json({
status: 201,
success: true,
@@ -52,6 +99,9 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE
const skipData = page * takeData - takeData
try {
let fixData;
@@ -71,6 +121,8 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
investasiId: id,
active: true,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
});
}
@@ -93,7 +145,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function DELETE(
request: Request,
{ params }: { params: { id: string } }
{ params }: { params: { id: string } },
) {
const { id } = params;
@@ -111,9 +163,9 @@ async function DELETE(
headers: {
Authorization: `Bearer ${process.env.WS_APIKEY}`,
},
}
},
);
return NextResponse.json({
status: 200,
success: true,

View File

@@ -1,10 +1,16 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET };
async function GET(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
try {
const data = await prisma.investasi_Invoice.findMany({
@@ -29,13 +35,30 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
},
},
},
take: takeData,
skip: skipData,
});
const totalData = await prisma.investasi_Invoice.count({
where: {
investasiId: id,
statusInvoiceId: "1",
},
});
const totalPages = Math.ceil(totalData / takeData);
return NextResponse.json({
status: 200,
success: true,
message: "Berhasil Mendapatkan Data",
data: data,
meta: {
currentPage: page,
totalData: totalData,
totalPage: totalPages,
dataPerPage: takeData,
},
});
} catch (error) {
return NextResponse.json({

View File

@@ -1,6 +1,13 @@
import { prisma } from "@/lib";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import _ from "lodash";
import { NextResponse } from "next/server";
import {
NotificationMobileTitleType,
NotificationMobileBodyType,
} from "../../../../../../../types/type-mobile-notification";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET, PUT };
@@ -47,9 +54,11 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const authorId = searchParams.get("authorId");
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page ? page * takeData - takeData : 0;
console.log("[ID INVOICE]", id);
let fixData;
@@ -98,6 +107,8 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
statusInvoiceId: "1",
isActive: true,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
nominal: true,
@@ -124,6 +135,8 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
where: {
authorId: authorId,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
statusInvoiceId: true,
@@ -198,8 +211,49 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
statusInvoiceId: checkStatus.id,
imageId: data.imageId,
},
select: {
investasiId: true,
},
});
if (fixStatus === "Proses") {
// kirim notif ke author investasi
const findInvestasi = await prisma.investasi.findUnique({
where: {
id: update.investasiId as any,
},
select: {
title: true,
authorId: true,
},
});
const findUsers = await prisma.user.findMany({
where: {
masterUserRoleId: "2",
active: true,
NOT: { id: findInvestasi?.authorId as any },
},
select: { id: true },
});
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: findUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Ada Investor Baru !" as NotificationMobileTitleType,
body: `Cek data investor pada ${findInvestasi?.title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeAdminMobile.investmentDetailPublish({
id: update.investasiId as string,
status: "publish",
}),
},
});
}
console.log("[UPDATE]", update);
return NextResponse.json({

View File

@@ -1,6 +1,8 @@
import _ from "lodash";
import { prisma } from "@/lib";
import { NextResponse } from "next/server";
import { sendNotificationInvestmentAddNews } from "@/lib/mobile/notification/notification-add-news-investment";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET, DELETE };
@@ -21,6 +23,21 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
deskripsi: data.deskripsi,
imageId: data.imageId,
},
select: {
investasiId: true,
investasi: {
select: {
title: true,
authorId: true,
},
},
},
});
await sendNotificationInvestmentAddNews({
invesmentId: createWithFile.investasiId,
senderId: createWithFile.investasi.authorId as string,
title: createWithFile.investasi.title,
});
fixData = createWithFile;
@@ -31,6 +48,21 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
title: _.startCase(data.title),
deskripsi: data.deskripsi,
},
select: {
investasiId: true,
investasi: {
select: {
title: true,
authorId: true,
},
},
},
});
await sendNotificationInvestmentAddNews({
invesmentId: createWitOutFile.investasiId,
senderId: createWitOutFile.investasi.authorId as string,
title: createWitOutFile.investasi.title,
});
fixData = createWitOutFile;
@@ -57,8 +89,13 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
console.log("id", id);
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
let fixData;
let meta = null;
try {
if (category === "one-news") {
const data = await prisma.beritaInvestasi.findFirst({
@@ -82,7 +119,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
fixData = newData;
} else if (category === "all-news") {
fixData = await prisma.beritaInvestasi.findMany({
const newsData = await prisma.beritaInvestasi.findMany({
orderBy: {
updatedAt: "desc",
},
@@ -90,7 +127,27 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
investasiId: id,
active: true,
},
take: takeData,
skip: skipData,
});
const totalData = await prisma.beritaInvestasi.count({
where: {
investasiId: id,
active: true,
},
});
const totalPages = Math.ceil(totalData / takeData);
fixData = newsData;
meta = {
currentPage: page,
totalData: totalData,
totalPage: totalPages,
dataPerPage: takeData,
};
}
return NextResponse.json({
@@ -98,6 +155,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
success: true,
message: "Berita berhasil diambil",
data: fixData,
...(meta && { meta }),
});
} catch (error) {
console.log("[ERROR]", error);
@@ -111,7 +169,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
async function DELETE(
request: Request,
{ params }: { params: { id: string } }
{ params }: { params: { id: string } },
) {
const { id } = params;
console.log("id", id);

View File

@@ -2,6 +2,10 @@ import _ from "lodash";
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import moment from "moment";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import { NotificationMobileBodyType } from "../../../../../types/type-mobile-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET };
@@ -9,12 +13,14 @@ async function POST(request: Request) {
const { data } = await request.json();
console.log(["DATA INVESTASI"], data);
const fixTitle = _.startCase(data.title)
try {
const create = await prisma.investasi.create({
data: {
masterStatusInvestasiId: "2",
authorId: data.authorId,
title: _.startCase(data.title),
title: fixTitle,
targetDana: data.targetDana,
hargaLembar: data.hargaLembar,
totalLembar: data.totalLembar,
@@ -30,6 +36,24 @@ async function POST(request: Request) {
console.log("[CREATE INVESTASI]", create);
const adminUsers = await prisma.user.findMany({
where: { masterUserRoleId: "2", NOT: { id: data.authorId } },
select: { id: true },
});
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Pengajuan Review Baru",
body: fixTitle as NotificationMobileBodyType,
type: "announcement",
deepLink: routeAdminMobile.investmentByStatus({ status: "review" }),
kategoriApp: "INVESTASI",
},
});
return NextResponse.json({
status: 201,
success: true,
@@ -50,6 +74,9 @@ async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const authorId = searchParams.get("authorId");
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE
const skipData = page ? page * takeData - takeData : 0;
console.log("[CATEGORY]", category);
console.log("[AUTHOR ID]", authorId);
@@ -109,6 +136,8 @@ async function GET(request: Request) {
where: {
masterStatusInvestasiId: "1",
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
imageId: true,
@@ -133,6 +162,8 @@ async function GET(request: Request) {
authorId: authorId,
statusInvoiceId: "1",
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
investasiId: true,

View File

@@ -1,3 +1,4 @@
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
import prisma from "@/lib/prisma";
import _ from "lodash";
import { NextResponse } from "next/server";
@@ -12,6 +13,11 @@ async function GET(
const { id, status } = params;
const fixStatusName = _.startCase(status);
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page ? page * takeData - takeData : 0;
const data = await prisma.job.findMany({
orderBy: {
updatedAt: "desc",
@@ -28,13 +34,20 @@ async function GET(
id: true,
title: true,
},
take: takeData,
skip: skipData,
});
return NextResponse.json(
{
success: true,
message: "Success get job",
data: data,
pagination: {
currentPage: page,
dataPerPage: takeData,
},
},
{ status: 200 }
);

View File

@@ -13,6 +13,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
include: {
Author: {
select: {
id: true,
username: true,
nomor: true,
Profile: {

View File

@@ -1,5 +1,9 @@
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import prisma from "@/lib/prisma";
import { NextResponse } from "next/server";
import { NotificationMobileBodyType } from "../../../../../types/type-mobile-notification";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET };
@@ -17,13 +21,32 @@ async function POST(request: Request) {
},
});
// kirim notifikasi ke semua admin untuk mengetahui ada job baru yang harus di review
const adminUsers = await prisma.user.findMany({
where: { masterUserRoleId: "2", NOT: { id: data.authorId } },
select: { id: true },
});
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Pengajuan Review Baru",
body: `${create.title}` as NotificationMobileBodyType,
type: "announcement",
deepLink: routeAdminMobile.jobByStatus({ status: "review" }),
kategoriApp: "JOB",
},
});
return NextResponse.json(
{
success: true,
message: "Berhasil disimpan",
data: create,
},
{ status: 201 }
{ status: 201 },
);
} catch (error) {
return NextResponse.json(
@@ -32,7 +55,7 @@ async function POST(request: Request) {
message: "Error create job",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}
@@ -42,94 +65,129 @@ async function GET(request: Request) {
const search = searchParams.get("search");
const category = searchParams.get("category");
const authorId = searchParams.get("authorId");
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
let fixData;
try {
if (category === "archive") {
const data = await prisma.job.findMany({
where: {
authorId: authorId,
isActive: true,
isArsip: true,
MasterStatus: {
name: "Publish",
},
// title: {
// contains: search || "",
// mode: "insensitive",
// },
},
orderBy: {
createdAt: "desc",
},
select: {
id: true,
title: true,
deskripsi: true,
authorId: true,
MasterStatus: {
select: {
name: true,
const [data, count] = await Promise.all([
prisma.job.findMany({
where: {
authorId: authorId,
isActive: true,
isArsip: true,
MasterStatus: {
name: "Publish",
},
// title: {
// contains: search || "",
// mode: "insensitive",
// },
},
Author: {
select: {
id: true,
username: true,
Profile: {
select: {
id: true,
name: true,
imageId: true,
orderBy: {
createdAt: "desc",
},
select: {
id: true,
title: true,
deskripsi: true,
authorId: true,
MasterStatus: {
select: {
name: true,
},
},
Author: {
select: {
id: true,
username: true,
Profile: {
select: {
id: true,
name: true,
imageId: true,
},
},
},
},
},
},
});
take: takeData,
skip: skipData,
}),
prisma.job.count({
where: {
authorId: authorId,
isActive: true,
isArsip: true,
MasterStatus: {
name: "Publish",
},
},
}),
]);
fixData = data;
} else if (category === "beranda") {
const data = await prisma.job.findMany({
where: {
isActive: true,
isArsip: false,
MasterStatus: {
name: "Publish",
},
title: {
contains: search || "",
mode: "insensitive",
},
},
orderBy: {
createdAt: "desc",
},
select: {
id: true,
title: true,
deskripsi: true,
authorId: true,
MasterStatus: {
select: {
name: true,
},
},
Author: {
select: {
id: true,
username: true,
Profile: {
select: {
id: true,
name: true,
imageId: true,
},
},
},
},
},
});
const [data, count] = await Promise.all([
prisma.job.findMany({
where: {
isActive: true,
isArsip: false,
MasterStatus: {
name: "Publish",
},
title: {
contains: search || "",
mode: "insensitive",
},
},
orderBy: {
createdAt: "desc",
},
select: {
id: true,
title: true,
deskripsi: true,
authorId: true,
MasterStatus: {
select: {
name: true,
},
},
Author: {
select: {
id: true,
username: true,
Profile: {
select: {
id: true,
name: true,
imageId: true,
},
},
},
},
},
take: takeData,
skip: skipData,
}),
prisma.job.count({
where: {
isActive: true,
isArsip: false,
MasterStatus: {
name: "Publish",
},
title: {
contains: search || "",
mode: "insensitive",
},
},
}),
]);
fixData = data;
}
@@ -139,8 +197,12 @@ async function GET(request: Request) {
success: true,
message: "Success get data job-vacancy",
data: fixData,
pagination: {
currentPage: page,
dataPerPage: takeData,
},
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
@@ -149,7 +211,7 @@ async function GET(request: Request) {
message: "Error get data job-vacancy",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}

View File

@@ -0,0 +1,248 @@
import { prisma } from "@/lib";
import _ from "lodash";
import { NextRequest, NextResponse } from "next/server";
import { NotificationProp } from "../route";
import { adminMessaging } from "@/lib/firebase-admin";
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } },
) {
const { id } = params;
console.log("ID", id);
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
const fixCategory = _.upperCase(category || "");
const page = Number(searchParams.get("page"));
console.log("page", page);
const takeData = 10;
const skipData = page * takeData - takeData;
let fixData;
try {
const data = await prisma.notifikasi.findMany({
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
orderBy: {
createdAt: "desc",
},
where: {
recipientId: id,
kategoriApp: fixCategory,
},
});
// Jika pagination digunakan, ambil juga total count untuk informasi
let totalCount;
let totalPages;
if (page) {
totalCount = await prisma.notifikasi.count({
where: {
recipientId: id,
kategoriApp: fixCategory,
},
});
totalPages = Math.ceil(totalCount / takeData);
}
fixData = data;
const response = {
success: true,
data: fixData,
};
// Tambahkan metadata pagination jika parameter page disertakan
if (page) {
Object.assign(response, {
meta: {
page,
take: takeData,
skip: skipData,
total: totalCount,
totalPages,
},
});
}
return NextResponse.json(response);
} catch (error) {
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 },
);
}
}
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } },
) {
const { id } = params;
const { searchParams } = new URL(request.url);
const category = searchParams.get("category");
try {
if (category === "one") {
await prisma.notifikasi.update({
where: {
id: id,
},
data: {
isRead: true,
readAt: new Date(),
},
});
} else if (category === "all") {
await prisma.notifikasi.updateMany({
where: {
recipientId: id,
},
data: {
isRead: true,
readAt: new Date(),
},
});
}
return NextResponse.json({
success: true,
message: "Notifications marked as read",
});
} catch (error) {
console.error("Error marking notifications as read:", error);
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 },
);
}
}
// export async function POST(
// request: NextRequest,
// { params }: { params: { id: string } }
// ) {
// const { id } = params;
// const { data } = await request.json();
// const {
// title,
// body: notificationBody,
// userLoginId,
// type,
// kategoriApp,
// appId,
// status,
// deepLink,
// } = data as NotificationProp;
// console.log("Notification Send >>", data);
// try {
// // Cari user yang login
// const findUserLogin = await prisma.user.findUnique({
// where: {
// id: userLoginId,
// },
// });
// if (!findUserLogin) {
// return NextResponse.json({ error: "User not found" }, { status: 404 });
// }
// // Cari token fcm user yang login
// const checkFcmToken = await prisma.tokenUserDevice.findFirst({
// where: {
// userId: findUserLogin.id,
// },
// });
// if (!checkFcmToken) {
// return NextResponse.json(
// { error: "FCM Token not found" },
// { status: 404 }
// );
// }
// const created = await prisma.notifikasi.create({
// data: {
// title,
// type,
// createdAt: new Date(),
// appId,
// kategoriApp,
// pesan: notificationBody || "",
// userRoleId: findUserLogin.masterUserRoleId,
// status,
// deepLink,
// senderId: findUserLogin.id,
// recipientId: id,
// },
// });
// if (created) {
// const deviceToken = await prisma.tokenUserDevice.findMany({
// where: {
// userId: id,
// isActive: true,
// },
// });
// for (let i of deviceToken) {
// const message = {
// token: i.token,
// notification: {
// title,
// body: notificationBody || "",
// },
// data: {
// sentAt: new Date().toISOString(), // ✅ Simpan metadata di data
// id: created.id,
// deepLink: deepLink || "",
// },
// // Konfigurasi Android untuk prioritas tinggi
// android: {
// priority: "high" as const, // Kirim secepatnya, bahkan di doze mode untuk notifikasi penting
// notification: {
// channelId: "default", // Sesuaikan dengan channel yang kamu buat di Android
// },
// ttl: 0 as const, // Kirim secepatnya, jangan tunda
// },
// // Opsional: tambahkan untuk iOS juga
// apns: {
// payload: {
// aps: {
// sound: "default" as const,
// // 'content-available': 1 as const, // jika butuh silent push
// },
// },
// },
// };
// try {
// const response = await adminMessaging.send(message);
// console.log("✅ FCM sent successfully", "Response:", response);
// } catch (error: any) {
// console.error("❌ FCM send failed:", error);
// // Lanjutkan ke token berikutnya meski satu gagal
// }
// }
// }
// return NextResponse.json({
// success: true,
// message: "Notification sent successfully",
// });
// } catch (error) {
// console.error("❌ FCM error:", error);
// return NextResponse.json(
// { error: (error as Error).message },
// { status: 500 }
// );
// }
// }

View File

@@ -0,0 +1,31 @@
import { prisma } from "@/lib";
import { NextRequest, NextResponse } from "next/server";
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const { id } = params;
console.log("User ID:", id);
try {
const data = await prisma.notifikasi.count({
where: {
recipientId: id,
isRead: false,
},
});
console.log("List Notification >>", data);
return NextResponse.json({
success: true,
data: data,
});
} catch (error) {
return NextResponse.json({
success: false,
message: "Failed to get unread count",
});
}
}

View File

@@ -0,0 +1,210 @@
// app/api/test/notifications/route.ts
import { prisma } from "@/lib";
import { adminMessaging } from "@/lib/firebase-admin";
import { NextRequest, NextResponse } from "next/server";
export type NotificationProp = {
title: string;
body: string;
userLoginId: string;
appId?: string;
status?: string;
kategoriApp?: string;
type?: string;
deepLink?: string;
};
export async function POST(request: NextRequest) {
try {
const { data } = await request.json();
const {
title,
body: notificationBody,
userLoginId,
type,
kategoriApp,
appId,
status,
deepLink,
} = data as NotificationProp;
console.log("Notification Send >>", data);
// Cari user yang login
const findUserLogin = await prisma.user.findUnique({
where: {
id: userLoginId,
},
});
if (!findUserLogin) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
// Cari token fcm user yang login
const checkFcmToken = await prisma.tokenUserDevice.findFirst({
where: {
userId: findUserLogin.id,
},
});
if (!checkFcmToken) {
return NextResponse.json(
{ error: "FCM Token not found" },
{ status: 404 }
);
}
// Jika user yang masuk maka notifikasik akan dikirim ke semua admin , begitu sebaliknya !
const filterByCurrentLoginId =
findUserLogin.masterUserRoleId === "1" ? "2" : "1";
// Cari user yang akan menerima notifikasi
const findAllUserBySendTo = await prisma.user.findMany({
where: {
masterUserRoleId: filterByCurrentLoginId,
NOT: {
id: findUserLogin.id,
},
},
});
console.log("Find All User By Send To >>", findAllUserBySendTo);
for (let a of findAllUserBySendTo) {
const responseCreatedNotifications = await createNotification({
title,
type: type as string,
createdAt: new Date(),
pesan: notificationBody || "",
appId: appId as string,
kategoriApp: kategoriApp as string,
userRoleId: findUserLogin.masterUserRoleId,
status: status,
deepLink: deepLink,
senderId: findUserLogin.id,
recipientId: a.id,
});
if (responseCreatedNotifications) {
const deviceToken = await prisma.tokenUserDevice.findMany({
where: {
userId: a.id,
isActive: true,
},
});
for (let i of deviceToken) {
const message = {
token: i.token,
notification: {
title,
body: notificationBody || "",
},
data: {
sentAt: new Date().toISOString(), // ✅ Simpan metadata di data
id: responseCreatedNotifications.id,
deepLink: deepLink || "",
// contoh: senderId, type, etc.
},
// Konfigurasi Android untuk prioritas tinggi
android: {
priority: "high" as const, // Kirim secepatnya, bahkan di doze mode untuk notifikasi penting
notification: {
channelId: "default", // Sesuaikan dengan channel yang kamu buat di Android
// Opsional: sesuaikan icon & warna
// icon: 'ic_notification',
// color: '#FFD700',
},
// FCM akan bangunkan app jika perlu
ttl: 0 as const, // Kirim secepatnya, jangan tunda
},
// Opsional: tambahkan untuk iOS juga
apns: {
payload: {
aps: {
sound: "default" as const,
// 'content-available': 1 as const, // jika butuh silent push
},
},
},
};
try {
const response = await adminMessaging.send(message);
console.log(
"✅ FCM sent to token:",
"Response:",
response
);
} catch (error: any) {
console.error("❌ FCM send failed for token:", i.token, error);
// Lanjutkan ke token berikutnya meski satu gagal
}
}
} else {
return NextResponse.json({
success: false,
message: "Failed to create notification",
});
}
}
return NextResponse.json({
success: true,
message: "Notification sent successfully",
});
} catch (error: any) {
console.error("❌ FCM error:", error);
return NextResponse.json(
{ error: error.message || "Failed to send FCM" },
{ status: 500 }
);
}
}
async function createNotification({
title,
type,
createdAt,
appId,
kategoriApp,
pesan,
userRoleId,
status,
deepLink,
senderId,
recipientId,
}: {
title: string;
type: string;
createdAt: Date;
appId: string;
kategoriApp: string;
userRoleId: string;
status?: string;
deepLink?: string;
pesan: string;
senderId: string;
recipientId: string;
}) {
const createNotification = await prisma.notifikasi.create({
data: {
title,
type,
createdAt,
appId,
kategoriApp,
pesan,
userRoleId,
status,
deepLink,
senderId,
recipientId,
},
});
return createNotification;
}

View File

@@ -1,44 +0,0 @@
// app/api/test/notifications/route.ts
import { adminMessaging } from "@/lib/firebase-admin";
import { NextRequest, NextResponse } from "next/server";
export async function POST(request: NextRequest) {
try {
const { data } = await request.json();
const { fcmToken, title, body: notificationBody } = data;
console.log("Data Notifikasi >>", data);
if (!fcmToken || !title) {
return NextResponse.json(
{ error: "Missing fcmToken or title" },
{ status: 400 }
);
}
const message = {
token: fcmToken,
notification: {
title,
body: notificationBody || "",
},
data: {
sentAt: new Date().toISOString(), // ✅ Simpan metadata di data
// contoh: senderId, type, etc.
},
};
console.log("[MSG]", message);
const response = await adminMessaging.send(message);
console.log("✅ FCM sent:", response);
return NextResponse.json({ success: true, messageId: response });
} catch (error: any) {
console.error("❌ FCM error:", error);
return NextResponse.json(
{ error: error.message || "Failed to send FCM" },
{ status: 500 }
);
}
}

View File

@@ -7,8 +7,13 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");
const page = parseInt(searchParams.get("page") || "1");
const take = 10; // Default 10 data
const skip = page * take - take;
const data = await prisma.portofolio.findMany({
skip,
take,
orderBy: {
createdAt: "desc",
},
@@ -18,22 +23,30 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
},
});
if (!data)
return NextResponse.json(
{
success: false,
message: "Data tidak ditemukan",
},
{ status: 404 }
);
// Hitung total data untuk informasi pagination
const total = await prisma.portofolio.count({
where: {
profileId: id,
active: true,
},
});
const totalPages = Math.ceil(total / take);
return NextResponse.json(
{
success: true,
message: "Berhasil mendapatkan data",
data: data,
meta: {
page,
take,
skip,
total,
totalPages,
},
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
@@ -42,7 +55,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
message: "API Error Get Data Potofolio",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}
@@ -66,7 +79,10 @@ async function POST(request: Request) {
},
});
if (data.subBidang.length > 0 || data.subBidang.map((item: any) => item.id !== "")) {
if (
data.subBidang.length > 0 ||
data.subBidang.map((item: any) => item.id !== "")
) {
for (let i of data.subBidang) {
const createSubBidang =
await prisma.portofolio_BidangDanSubBidangBisnis.create({
@@ -84,7 +100,7 @@ async function POST(request: Request) {
success: false,
message: "Gagal membuat sub bidang bisnis",
},
{ status: 400 }
{ status: 400 },
);
}
}
@@ -95,7 +111,7 @@ async function POST(request: Request) {
success: false,
message: "Gagal membuat portofolio",
},
{ status: 400 }
{ status: 400 },
);
const createMedsos = await prisma.portofolio_MediaSosial.create({
@@ -115,7 +131,7 @@ async function POST(request: Request) {
success: false,
message: "Gagal menambahkan medsos",
},
{ status: 400 }
{ status: 400 },
);
return NextResponse.json(
@@ -124,7 +140,7 @@ async function POST(request: Request) {
message: "Berhasil mendapatkan data",
data: createPortofolio,
},
{ status: 200 }
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
@@ -133,7 +149,7 @@ async function POST(request: Request) {
message: "API Error Post Data",
reason: (error as Error).message,
},
{ status: 500 }
{ status: 500 },
);
}
}

View File

@@ -5,8 +5,16 @@ export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const search = searchParams.get("search");
const page = Number(searchParams.get("page"));
const takeData = 10;
const skipData = page * takeData - takeData;
console.log("SEARCH", search);
console.log("PAGE", page);
const data = await prisma.user.findMany({
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
orderBy: {
username: "asc",
},
@@ -43,16 +51,12 @@ export async function GET(request: Request) {
},
});
return NextResponse.json(
{
success: true,
message: "Berhasil mendapatkan data",
data: data,
},
{
status: 200,
}
);
return NextResponse.json({
status: 200,
success: true,
message: "Berhasil mendapatkan data",
data: data,
});
} catch (error) {
return NextResponse.json(
{
@@ -62,7 +66,7 @@ export async function GET(request: Request) {
},
{
status: 500,
}
},
);
}

View File

@@ -1,6 +1,7 @@
import _ from "lodash";
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET, PUT };
@@ -10,14 +11,33 @@ async function GET(
) {
try {
const { id, status } = params;
console.log("[ID]", id);
const fixStatusName = _.startCase(status);
console.log("[STATUS]", fixStatusName);
let fixData;
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get("page")) || 1;
const takeData = PAGINATION_DEFAULT_TAKE
const skipData = page * takeData - takeData;
let data;
let totalCount;
if (fixStatusName === "Publish") {
fixData = await prisma.voting.findMany({
data = await prisma.voting.findMany({
where: {
authorId: id,
isActive: true,
akhirVote: {
gte: new Date(),
},
Voting_Status: {
name: fixStatusName,
},
},
take: takeData,
skip: skipData,
});
totalCount = await prisma.voting.count({
where: {
authorId: id,
isActive: true,
@@ -30,7 +50,18 @@ async function GET(
},
});
} else {
fixData = await prisma.voting.findMany({
data = await prisma.voting.findMany({
where: {
authorId: id,
Voting_Status: {
name: fixStatusName,
},
},
take: takeData,
skip: skipData,
});
totalCount = await prisma.voting.count({
where: {
authorId: id,
Voting_Status: {
@@ -40,10 +71,18 @@ async function GET(
});
}
const totalPages = Math.ceil(totalCount / takeData);
return NextResponse.json({
success: true,
message: "Success get voting",
data: fixData,
data: data,
pagination: {
currentPage: page,
totalPages: totalPages,
totalData: totalCount,
dataPerPage: takeData,
},
});
} catch (error) {
console.log("[ERROR]", error);
@@ -61,9 +100,7 @@ async function PUT(
) {
try {
const { id, status } = params;
console.log("[ID]", id);
const fixStatusName = _.startCase(status);
console.log("[STATUS]", fixStatusName);
const checkData = await prisma.voting.findFirst({
where: {
@@ -79,8 +116,6 @@ async function PUT(
},
});
console.log("[CHECKDATA]", checkData);
if (!checkData)
return NextResponse.json({
success: false,
@@ -115,8 +150,6 @@ async function PUT(
},
});
console.log("[UPDATE]", updateData);
return NextResponse.json({
success: true,
message: "Success update voting",

View File

@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { GET };
@@ -8,10 +9,9 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const { searchParams } = new URL(request.url);
const authorId = searchParams.get("authorId");
const category = searchParams.get("category");
console.log("[ID]", id);
console.log("[AUTHOR ID]", authorId);
console.log("[CATEGORY]", category);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page ? page * takeData - takeData : 0;
let fixData;
@@ -53,7 +53,10 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
where: {
votingId: id,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
select: {
id: true,
Voting_DaftarNamaVote: {
select: {
value: true,
@@ -75,8 +78,6 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
},
});
console.log("[LIST KONTRIBUTOR]", listKontributor);
fixData = listKontributor;
}

View File

@@ -1,6 +1,12 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import _ from "lodash";
import { sendNotificationMobileToOneUser } from "@/lib/mobile/notification/send-notification";
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../../../types/type-mobile-notification";
import { routeUserMobile } from "@/lib/mobile/route-page-mobile";
export { GET, DELETE, PUT, POST };
@@ -39,7 +45,6 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
const listNamaVote = data?.Voting_DaftarNamaVote || [];
for (let v of listNamaVote) {
const kontributor = await prisma.voting_Kontributor.findMany({
where: {
voting_DaftarNamaVoteId: v.id,
@@ -90,7 +95,6 @@ async function DELETE(
},
});
return NextResponse.json({
success: true,
message: "Berhasil menghapus data",
@@ -171,7 +175,6 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
if (!updateVoting)
return NextResponse.json({ status: 400, message: "Gagal Update" });
}
@@ -193,11 +196,12 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
async function POST(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
const { chooseId, userId } = data;
try {
const findData = await prisma.voting_DaftarNamaVote.findFirst({
const findDatapilihan = await prisma.voting_DaftarNamaVote.findFirst({
where: {
id: data.chooseId,
id: chooseId,
},
select: {
jumlah: true,
@@ -205,28 +209,32 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
},
});
if (!findData)
if (!findDatapilihan)
return NextResponse.json({
success: false,
message: "Data tidak ditemukan",
});
const updateData = await prisma.voting_DaftarNamaVote.update({
const updateDataPilihan = await prisma.voting_DaftarNamaVote.update({
where: {
id: data.chooseId,
},
data: {
jumlah: findData.jumlah + 1,
jumlah: findDatapilihan.jumlah + 1,
},
});
if (!updateData)
if (!updateDataPilihan)
return NextResponse.json({
success: false,
message: "Gagal Update Data",
});
const findVotingData = await prisma.voting.findUnique({
where: { id: id },
select: { authorId: true, title: true },
});
const createKontributor = await prisma.voting_Kontributor.create({
data: {
votingId: id,
@@ -250,6 +258,21 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
message: "Gagal Menjadi Kontributor",
});
// SEND NOTIFICATION
if (userId !== findVotingData?.authorId) {
await sendNotificationMobileToOneUser({
recipientId: findVotingData?.authorId as string,
senderId: userId,
payload: {
title: "User Melakukan Vote" as NotificationMobileTitleType,
body: `Salah satu user telah melakukan voting pada: ${findVotingData?.title}` as NotificationMobileBodyType,
type: "announcement",
deepLink: routeUserMobile.votingDetailPublised({ id: id }),
kategoriApp: "VOTING",
},
});
}
return NextResponse.json({
success: true,
message: "Berhasil Voting",

View File

@@ -1,6 +1,10 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import _ from "lodash";
import { sendNotificationMobileToManyUser } from "@/lib/mobile/notification/send-notification";
import { NotificationMobileBodyType } from "../../../../../types/type-mobile-notification";
import { routeAdminMobile } from "@/lib/mobile/route-page-mobile";
import { PAGINATION_DEFAULT_TAKE } from "@/lib/constans-value/constansValue";
export { POST, GET };
@@ -41,6 +45,24 @@ async function POST(request: Request) {
});
}
const adminUsers = await prisma.user.findMany({
where: { masterUserRoleId: "2", NOT: { id: data.authorId } },
select: { id: true },
});
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: adminUsers.map((user) => user.id),
senderId: data.authorId,
payload: {
title: "Pengajuan Review Baru",
body: create.title as NotificationMobileBodyType,
type: "announcement",
deepLink: routeAdminMobile.votingByStatus({ status: "review" }),
kategoriApp: "VOTING",
},
});
return NextResponse.json(
{
success: true,
@@ -66,8 +88,9 @@ async function GET(request: Request) {
const category = searchParams.get("category");
const authorId = searchParams.get("authorId");
const userLoginId = searchParams.get("userLoginId");
console.log("userLoginId >>", userLoginId);
const page = Number(searchParams.get("page"));
const takeData = PAGINATION_DEFAULT_TAKE;
const skipData = page * takeData - takeData;
let fixData;
@@ -102,6 +125,8 @@ async function GET(request: Request) {
},
},
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
include: {
Voting_DaftarNamaVote: {
orderBy: {
@@ -125,8 +150,6 @@ async function GET(request: Request) {
});
fixData = data;
} else if (category === "contribution") {
const data = await prisma.voting_Kontributor.findMany({
orderBy: {
@@ -135,6 +158,8 @@ async function GET(request: Request) {
where: {
authorId: authorId,
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
include: {
Voting: {
select: {
@@ -192,6 +217,8 @@ async function GET(request: Request) {
mode: "insensitive",
},
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
include: {
Voting_DaftarNamaVote: {
orderBy: {
@@ -230,6 +257,8 @@ async function GET(request: Request) {
mode: "insensitive",
},
},
take: page ? takeData : undefined,
skip: page ? skipData : undefined,
include: {
Voting_DaftarNamaVote: {
orderBy: {

View File

@@ -84,7 +84,7 @@ export async function GET(req: Request) {
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
// Removed prisma.$disconnect() from here to prevent connection pool exhaustion
// Prisma connections are handled globally and shouldn't be disconnected on each request
}

View File

@@ -28,6 +28,7 @@ import { useRouter } from "next/navigation";
import { IconMoodSmileFilled } from "@tabler/icons-react";
import { listStiker } from "../../lib/stiker";
import { UIGlobal_Modal } from "../../ui";
import mqtt_client from "@/util/mqtt_client";
const ReactQuill = dynamic(
async () => {
@@ -248,10 +249,12 @@ function ButtonAction({ value, lengthData }: ButtonActionProps) {
ComponentGlobal_NotifikasiBerhasil(create.message);
router.back();
mqtt_client.publish(
"Forum_create_new",
JSON.stringify({ isNewPost: true, count: 1 })
);
if (typeof window !== 'undefined' && mqtt_client) {
mqtt_client.publish(
"Forum_create_new",
JSON.stringify({ isNewPost: true, count: 1 })
);
}
} else {
ComponentGlobal_NotifikasiGagal(create.message);
}

View File

@@ -21,17 +21,24 @@ import { apiFetchLogin } from "../_lib/api_fetch_auth";
export default function Login({ version }: { version: string }) {
const router = useRouter();
const [phone, setPhone] = useState("");
const [loading, setLoading] = useState(false);
const [isError, setError] = useState(false);
const [phone, setPhone] = useState("");
const [countryCode, setCountryCode] = useState<string>("62"); // default ke Indonesia
async function onLogin() {
const nomor = phone.substring(1);
console.log("phone >>", phone);
const nomor = phone;
if (nomor.length <= 4) return setError(true);
const fixPhone = `${countryCode}${nomor}`;
console.log("fixPhone >>", fixPhone);
try {
setLoading(true);
const respone = await apiFetchLogin({ nomor: nomor });
const respone = await apiFetchLogin({ nomor: fixPhone });
if (respone && respone.success) {
localStorage.setItem("hipmi_auth_code_id", respone.kodeId);
@@ -72,16 +79,38 @@ export default function Login({ version }: { version: string }) {
<Center>
<Text c={MainColor.white}>Nomor telepon</Text>
</Center>
<PhoneInput
countrySelectorStyleProps={{
buttonStyle: {
backgroundColor: MainColor.login,
},
}}
inputStyle={{ width: "100%", backgroundColor: MainColor.login }}
defaultCountry="id"
onChange={(val) => {
setPhone(val);
inputStyle={{ width: "100%", backgroundColor: MainColor.login }}
onChange={(fullPhone, meta) => {
const dialCode = meta.country.dialCode; // string, misal: "62"
let localNumber = fullPhone;
// Hapus kode negara dari awal string
if (fullPhone.startsWith(`+${dialCode}`)) {
localNumber = fullPhone.slice(`+${dialCode}`.length);
}
// Bersihkan semua non-digit
localNumber = localNumber.replace(/\D/g, "");
// ✅ Filter khusus: untuk Indonesia (+62), hapus leading zero
if (dialCode === "62" && localNumber.startsWith("0")) {
localNumber = localNumber.replace(/^0+/, ""); // hapus semua 0 di awal
}
// Simpan hasil akhir
setCountryCode(dialCode);
setPhone(localNumber);
// console.log("Country Code:", dialCode);
// console.log("Clean Local Number:", localNumber);
}}
/>

View File

@@ -1,13 +1,13 @@
[
{
"name": "default_user",
"name": "demo_user",
"nomor": "6282340374412",
"masterUserRoleId": "1",
"active": true,
"termsOfServiceAccepted": false
},
{
"name": "admin_911",
"name": "demo_admin",
"nomor": "6281339158911",
"masterUserRoleId": "3",
"active": true,

View File

@@ -0,0 +1,31 @@
const sendCodeOtp = async ({
nomor,
codeOtp,
newMessage,
}: {
nomor: string;
codeOtp?: string;
newMessage?: string;
}) => {
const msg = newMessage || `HIPMI - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun pengurus HIPMI lainnya.\n\n>> Kode OTP anda: ${codeOtp}.`;
const enCode = encodeURIComponent(msg);
const res = await fetch(
`https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=${nomor}&text=${enCode}`,
{
cache: "no-cache",
headers: {
Authorization: `Bearer ${process.env.WA_SERVER_TOKEN}`,
},
},
);
// const res = await fetch(
// `https://wa.wibudev.com/code?nom=${nomor}&text=HIPMI - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun pengurus HIPMI lainnya.
// \n
// >> Kode OTP anda: ${codeOtp}.
// `,
// );
return res;
};
export { sendCodeOtp as funSendToWhatsApp };

View File

@@ -0,0 +1 @@
export const PAGINATION_DEFAULT_TAKE = 10;

View File

@@ -0,0 +1,25 @@
import prisma from "@/lib/prisma";
export const funFindDonaturList = async (donasiId: string) => {
const finDonatur = await prisma.donasi_Invoice.findMany({
where: {
donasiId: donasiId,
DonasiMaster_StatusInvoice: {
name: "Berhasil",
},
},
select: {
authorId: true,
},
distinct: ["authorId"], // Ambil hanya authorId unik dari DB
});
// Filter null safety (jika diperlukan)
const recipientIds = finDonatur
.map((e) => e.authorId)
.filter((id): id is string => id !== null && id !== undefined);
console.log("[FIND DONATUR UNIK]", recipientIds);
return recipientIds;
};

View File

@@ -0,0 +1,46 @@
import {
NotificationMobileBodyType,
NotificationMobileTitleType,
} from "../../../../types/type-mobile-notification";
import { sendNotificationMobileToManyUser } from "./send-notification";
import { routeUserMobile } from "../route-page-mobile";
import prisma from "@/lib/prisma";
export const sendNotificationInvestmentAddNews = async ({
invesmentId,
senderId,
title,
}: {
invesmentId: string;
senderId: string;
title: string;
}) => {
const findInvestor = await prisma.investasi_Invoice.findMany({
where: {
investasiId: invesmentId,
StatusInvoice: {
name: "Berhasil",
},
},
select: {
authorId: true,
},
});
console.log("[FIND INVESTOR]", findInvestor);
// SEND NOTIFICATION
await sendNotificationMobileToManyUser({
recipientIds: findInvestor.map((e) => e.authorId!),
senderId: senderId,
payload: {
title: "Berita terbaru" as NotificationMobileTitleType,
body: `Ada berita yang terupdate pada ${title}` as NotificationMobileBodyType,
type: "announcement",
kategoriApp: "INVESTASI",
deepLink: routeUserMobile.investmentDetailPublish({
id: invesmentId,
}),
},
});
};

View File

@@ -0,0 +1,119 @@
// lib/notifications/send-notification.ts
import { adminMessaging } from "@/lib/firebase-admin";
import prisma from "@/lib/prisma";
import { NotificationMobilePayload } from "../../../../types/type-mobile-notification";
import _ from "lodash";
/**
* Kirim notifikasi ke satu user (semua device aktifnya)
* @param recipientId - ID penerima
* @param senderId - ID pengirim
* @param payload - Data notifikasi
*/
export async function sendNotificationMobileToOneUser({
recipientId,
senderId,
payload,
}: {
recipientId: string;
senderId: string;
payload: NotificationMobilePayload;
}) {
try {
const kategoriToNormalCase = _.lowerCase(payload.kategoriApp);
const titleFix =
kategoriToNormalCase === "other"
? payload.title
: `${_.startCase(kategoriToNormalCase)}: ${payload.title}`;
console.log("titleFix", titleFix);
// 1. Simpan notifikasi ke DB
const notification = await prisma.notifikasi.create({
data: {
title: titleFix,
pesan: payload.body,
deepLink: payload.deepLink,
kategoriApp: payload.kategoriApp,
recipientId: recipientId,
senderId: senderId,
type: payload.type.trim(),
},
});
// 2. Ambil semua token aktif milik penerima
const tokens = await prisma.tokenUserDevice.findMany({
where: { userId: recipientId },
select: { token: true, id: true },
});
if (tokens.length === 0) {
console.warn(`No active tokens found for user ${recipientId}`);
return;
}
// 3. Kirim FCM ke semua token
await Promise.allSettled(
tokens.map(async ({ token, id }) => {
try {
await adminMessaging.send({
token,
notification: {
title: titleFix,
body: payload.body,
},
data: {
sentAt: new Date().toISOString(), // ✅ Simpan metadata di data
id: notification.id,
deepLink: payload.deepLink,
recipientId: recipientId,
},
android: {
priority: "high" as const,
notification: { channelId: "default" },
ttl: 0 as const,
},
apns: {
payload: { aps: { sound: "default" as const } },
},
});
} catch (fcmError: any) {
// Hapus token jika invalid
if (fcmError.code === "messaging/registration-token-not-registered") {
// Hapus token dari DB
await prisma.tokenUserDevice.delete({ where: { id } });
console.log(`🗑️ Invalid token removed: ${id}`);
}
}
})
);
console.log(`✅ Notification sent to user ${recipientId}`);
} catch (error) {
console.error("Failed to send notification:", error);
throw error; // biarkan caller handle error
}
}
/**
* Kirim notifikasi ke banyak user
*/
export async function sendNotificationMobileToManyUser({
recipientIds,
senderId,
payload,
}: {
recipientIds: string[];
senderId: string;
payload: NotificationMobilePayload;
}) {
await Promise.allSettled(
recipientIds.map((id) =>
sendNotificationMobileToOneUser({
recipientId: id,
senderId: senderId,
payload: payload,
})
)
);
}

View File

@@ -0,0 +1,81 @@
export { routeAdminMobile, routeUserMobile };
type StatusApp = "review" | "draft" | "reject" | "publish";
const routeAdminMobile = {
userAccess: ({ id }: { id: string }) => `/admin/user-access/${id}`,
// JOB
jobByStatus: ({ status }: { status: StatusApp }) =>
`/admin/job/${status}/status`,
// EVENT
eventByStatus: ({ status }: { status: StatusApp }) =>
`/admin/event/${status}/status`,
// VOTING
votingByStatus: ({ status }: { status: StatusApp }) =>
`/admin/voting/${status}/status`,
// FORUM
forumPreviewReportPosting: `/admin/forum/report-posting`,
forumPreviewReportComment: `/admin/forum/report-comment`,
// INVESTMENT
investmentByStatus: ({ status }: { status: StatusApp }) => `/admin/investment/${status}/status`,
investmentDetailPublish: ({
id,
status,
}: {
id: string;
status: StatusApp;
}) => `/admin/investment/${id}/${status}`,
// DONATION
donationByStatus: ({ status }: { status: StatusApp }) => `/admin/donation/${status}/status`,
donationDetailPublish: ({
id,
status,
}: {
id: string;
status: StatusApp;
}) => `/admin/donation/${id}/${status}`,
};
// ================ ROUTER USER =================
const routeUserMobile = {
home: `/(user)/home`,
// JOB
jobByStatus: ({ status }: { status?: StatusApp }) =>
`/job/(tabs)/status?status=${status}`,
jobDetailPublised: ({ id }: { id: string }) => `/job/${id}`,
// EVENT
eventByStatus: ({ status }: { status?: StatusApp }) =>
`/event/(tabs)/status?status=${status}`,
eventDetailPublised: ({ id }: { id: string }) => `/event/${id}/publish`,
// VOTING
votingByStatus: ({ status }: { status?: StatusApp }) =>
`/voting/(tabs)/status?status=${status}`,
votingDetailPublised: ({ id }: { id: string }) => `/voting/${id}`,
// FORUM
forumBeranda: `/forum`,
forumDetail: ({ id }: { id: string }) => `/forum/${id}`,
forumPreviewReportPosting: ({ id }: { id: string }) =>
`/forum/${id}/preview-report-posting`,
forumPreviewReportComment: ({ id }: { id: string }) =>
`/forum/${id}/preview-report-comment`,
// INVESTMENT
investmentPortofolioByStatus: ({ status }: { status?: StatusApp }) =>
`/investment/(tabs)/portofolio?status=${status}`,
investmentDetailPublish: ({ id }: { id: string }) => `/investment/${id}`,
investmentTransaction: `/investment/(tabs)/transaction`,
// DONATION
donationByStatus: ({ status }: { status?: StatusApp }) =>
`/donation/(tabs)/status?status=${status}`,
donationDetailPublish: ({ id }: { id: string }) => `/donation/${id}`,
donationTransaction: `/donation/(tabs)/my-donation`,
};

View File

@@ -9,30 +9,21 @@ declare global {
let prisma: PrismaClient;
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient();
prisma = new PrismaClient({
// Reduce logging in production to improve performance
log: ['error', 'warn'],
});
} else {
if (!global.prisma) {
global.prisma = new PrismaClient();
global.prisma = new PrismaClient({
log: ['error', 'warn', 'info', 'query'], // More verbose logging in development
});
}
prisma = global.prisma;
}
// Tambahkan listener hanya jika belum ditambahkan sebelumnya
if (!global.prismaListenersAdded) {
// Handle uncaught errors
process.on("uncaughtException", async (error) => {
console.error("Uncaught Exception:", error);
await prisma.$disconnect();
process.exit(1);
});
// Handle unhandled promise rejections
process.on("unhandledRejection", async (error) => {
console.error("Unhandled Rejection:", error);
await prisma.$disconnect();
process.exit(1);
});
// Handle graceful shutdown
process.on("SIGINT", async () => {
console.log("Received SIGINT signal. Closing database connections...");
@@ -51,3 +42,4 @@ if (!global.prismaListenersAdded) {
}
export default prisma;
export { prisma };

24
src/lib/prismaUtils.ts Normal file
View File

@@ -0,0 +1,24 @@
import { prisma } from './prisma';
/**
* Utility function to safely execute Prisma operations
* This prevents improper disconnection of the Prisma client
* which was causing high CPU usage and connection pool issues
*/
export async function executeDbOperation<T>(
operation: () => Promise<T>,
errorMessage: string = "Database operation failed"
): Promise<{ success: boolean; data?: T; error?: string }> {
try {
const data = await operation();
return { success: true, data };
} catch (error) {
console.error(errorMessage, error);
return { success: false, error: (error as Error).message };
}
// Note: We intentionally do NOT call prisma.$disconnect() here
// Prisma manages connection pooling automatically and disconnecting
// on each request causes performance issues
}
export { prisma };

View File

@@ -66,9 +66,10 @@ export const middleware = async (req: NextRequest) => {
const { pathname } = req.nextUrl;
const apiBaseUrl = new URL(req.url).origin || process.env.NEXT_PUBLIC_API_URL;
const dbUrl = process.env.DATABASE_URL;
console.log("DATABASE_URL >>", dbUrl);
console.log("URL Access >>", req.url);
// Removed excessive logging that was causing high CPU usage
// const dbUrl = process.env.DATABASE_URL;
// console.log("DATABASE_URL >>", dbUrl);
// console.log("URL Access >>", req.url);
// Handle CORS preflight
const corsResponse = handleCors(req);

View File

@@ -4,7 +4,66 @@ declare global {
var mqtt_client: mqtt.MqttClient;
}
const mqtt_client =
globalThis.mqtt_client || mqtt.connect("wss://io.wibudev.com");
// Initialize MQTT client with proper error handling and reconnection settings
let mqtt_client: mqtt.MqttClient;
if (typeof window === 'undefined') {
// Server-side code
mqtt_client = globalThis.mqtt_client || (() => {
const client = mqtt.connect("wss://io.wibudev.com", {
reconnectPeriod: 5000, // Reconnect every 5 seconds
connectTimeout: 30 * 1000, // 30 second timeout
// Clean session to avoid message queue buildup
clean: true,
// Reduce unnecessary pings
keepalive: 60
});
// Prevent multiple initializations
globalThis.mqtt_client = client;
// Add error handling
client.on('error', (error) => {
console.error('MQTT Connection Error:', error);
});
client.on('reconnect', () => {
console.log('MQTT Reconnecting...');
});
client.on('close', () => {
console.log('MQTT Connection Closed');
});
return client;
})();
} else {
// Client-side code - initialize only once
if (!(globalThis as any).mqtt_client) {
(globalThis as any).mqtt_client = mqtt.connect("wss://io.wibudev.com", {
reconnectPeriod: 5000, // Reconnect every 5 seconds
connectTimeout: 30 * 1000, // 30 second timeout
// Clean session to avoid message queue buildup
clean: true,
// Reduce unnecessary pings
keepalive: 60
});
// Add error handling
(globalThis as any).mqtt_client.on('error', (error: any) => {
console.error('MQTT Connection Error:', error);
});
(globalThis as any).mqtt_client.on('reconnect', () => {
console.log('MQTT Reconnecting...');
});
(globalThis as any).mqtt_client.on('close', () => {
console.log('MQTT Connection Closed');
});
}
mqtt_client = (globalThis as any).mqtt_client;
}
export default mqtt_client;

View File

@@ -3,20 +3,27 @@
import { useEffect } from "react";
import mqtt_client from "./mqtt_client";
export default function MqttLoader() {
export default function MqttLoader() {
useEffect(() => {
mqtt_client.on("connect", () => {
console.log("connected");
});
// Only set up connection handlers once
const handleConnect = () => {
console.log("MQTT connected");
};
const handleError = (error: any) => {
console.error("MQTT Error:", error);
};
// Subscribe to events
mqtt_client.on("connect", handleConnect);
mqtt_client.on("error", handleError);
// Cleanup function to unsubscribe when component unmounts
return () => {
mqtt_client.off("connect", handleConnect);
mqtt_client.off("error", handleError);
};
}, []);
return null;
// <>
// <Stack>
// <Button onClick={onClick}>Tekan</Button>
// <Button onClick={onClick2}>Tekan 2</Button>
// </Stack>
// </>
// );
}

View File

@@ -23,6 +23,6 @@
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app_modules/investasi/proses_transaksi/view.jsx", "src/app/api/investasi/midtrans/[id]/route.ts", "src/app_modules/job/create/TextEdit.tsx", "src/app/api/mobile/forum/[id]/report-comment/route.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/app_modules/investasi/proses_transaksi/view.jsx", "src/app/api/investasi/midtrans/[id]/route.ts", "src/app_modules/job/create/TextEdit.tsx", "src/app/api/mobile/forum/[id]/report-comment-del-soon/route.ts"],
"exclude": ["node_modules"]
}

View File

@@ -0,0 +1,40 @@
// Jika semua custom type diawali "custom_"
export type NotificationMobilePayload = {
title: NotificationMobileTitleType;
body: NotificationMobileBodyType;
userLoginId?: string;
appId?: string;
status?: string;
type: "announcement" | "trigger";
deepLink: string;
kategoriApp: TypeNotificationCategoryApp;
};
export type NotificationMobileTitleType =
| (string & { __type: "NotificationMobileTitleType" })
// Admin
| "Pengajuan Review Baru"
// USER
| "Pengajuan Review Ditolak"
| "Review Selesai"
// to ALL user
export type NotificationMobileBodyType =
// USER
| (string & { __type: "NotificationMobileBodyType" })
| "Ada pengajuan review" // tambah title
// ADMIN
| "Mohon perbaiki data sesuai catatan penolakan !"
| "Selamat data anda telah terpublikasi"
export type TypeNotificationCategoryApp =
| "EVENT"
| "JOB"
| "VOTING"
| "DONASI"
| "INVESTASI"
| "COLLABORATION"
| "FORUM"
| "OTHER";

7
x.sh
View File

@@ -3,10 +3,13 @@ URL="http://localhost:3000"
# curl -X GET -H "Authorization: Bearer $TOKEN" ${URL}/api/middleware
# curl -X GET -H "Cookie: hipmi-key=$TOKEN; user_id=789" ${URL}/dev/home | tee test.html
curl -X POST ${URL}/api/mobile/notifications \
curl -X POST ${URL}/api/mobile/notification \
-H "Content-Type: application/json" \
-d '{
"fcmToken": "cVmHm-3P4E-1vjt6AA9kSF:APA91bHTkHjGTLxrFsb6Le6bZmzboZhwMGYXU4p0FP9yEeXixLDXNKS4F5vLuZV3sRgSnjjQsPpLOgstVLHJB8VJTObctKLdN-CxAp4dnP7Jbc_mH53jWvs",
"title": "Test dari Backend (App Router)!",
"body": "Berhasil di App Router!"
"body": "Berhasil di App Router!",
"userLoginId": "cmha7p6yc0000cfoe5w2e7gdr",
"type": "NOTIFICATION",
"kategoriApp": "PERCOBAAN"
}'

226
zCoba.js
View File

@@ -1,28 +1,204 @@
// const data = [
// {
// authorId: "clx8pl7r90005su4mldioo0v1",
// Donasi: {
// id: "clyr304q0000410ljvzms3mag",
// title: "Donasi Bencana Alam Aceh",
// },
// },
// {
// authorId: "clx8pl7r90005su4mldioo0v1",
// Donasi: {
// id: "clyr304q0000410ljvzms3mag",
// title: "Donasi Bencana Alam Aceh",
// },
// },
// {
// authorId: "clycka5eu0001ina3i1ssgze9",
// Donasi: {
// id: "clyr304q0000410ljvzms3mag",
// title: "Donasi Bencana Alam Aceh",
// },
// },
// ];
const { PrismaClient } = require('@prisma/client')
const axios = require('axios')
const prisma = new PrismaClient()
console.error("errornya disini klik aja",import.meta.url);
console.log(new Set(data.map((d) => d.authorId)));
// Daftar contoh data
const donationDataList = [
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Bantuan Pendidikan Anak-anak Kurang Mampu",
"target": 50000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank Central Asia",
"rekening": "1234567890",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Kami ingin membantu anak-anak kurang mampu mendapatkan pendidikan yang layak.",
"cerita": "Pendidikan adalah hak dasar setiap anak. Namun, banyak anak-anak di pelosok negeri yang tidak bisa menikmati pendidikan karena keterbatasan ekonomi. Melalui kampanye ini, kami ingin mengumpulkan dana untuk membantu biaya pendidikan mereka.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj0"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Pembangunan Masjid di Desa Terpencil",
"target": 100000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank Mandiri",
"rekening": "0987654321",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Membangun masjid untuk masyarakat di daerah terpencil yang belum memiliki tempat ibadah.",
"cerita": "Di sebuah desa terpencil, masyarakat setiap hari harus berjalan jauh untuk bisa melaksanakan sholat berjamaah. Kami ingin membantu membangun masjid di tengah-tengah mereka agar ibadah bisa dilakukan dengan lebih tenang dan khusyuk.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj1"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Bantuan Korban Bencana Alam",
"target": 75000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank Rakyat Indonesia",
"rekening": "5678901234",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Membantu meringankan beban korban bencana alam berupa kebutuhan pokok dan kebutuhan darurat.",
"cerita": "Beberapa wilayah dilanda bencana banjir dan tanah longsor. Masyarakat kehilangan harta benda dan membutuhkan bantuan segera. Dana yang terkumpul akan digunakan untuk menyediakan makanan, obat-obatan, dan kebutuhan pokok lainnya.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj2"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Pengadaan Alat Medis Rumah Sakit",
"target": 150000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank Negara Indonesia",
"rekening": "4321098765",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Meningkatkan kualitas pelayanan kesehatan dengan menyediakan alat medis yang lebih baik.",
"cerita": "Rumah sakit daerah kekurangan alat medis untuk melayani pasien. Melalui donasi ini, kami ingin membantu pengadaan alat-alat medis penting seperti ventilator, USG, dan alat laboratorium untuk meningkatkan kualitas pelayanan kesehatan.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj3"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Program Beasiswa Mahasiswa Berprestasi",
"target": 80000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank Danamon",
"rekening": "1122334455",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Memberikan kesempatan kepada mahasiswa berprestasi untuk melanjutkan pendidikan tanpa beban biaya.",
"cerita": "Banyak mahasiswa berprestasi yang tidak mampu melanjutkan pendidikan karena keterbatasan biaya. Program beasiswa ini akan membantu mereka menyelesaikan kuliah hingga sarjana.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj4"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Pengadaan Air Bersih untuk Desa Kekeringan",
"target": 60000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank Permata",
"rekening": "6677889900",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Menyediakan akses air bersih bagi masyarakat yang tinggal di daerah rawan kekeringan.",
"cerita": "Beberapa desa mengalami kekeringan setiap tahunnya, membuat warga kesulitan mendapatkan air bersih. Kami ingin membangun sumur bor dan sistem distribusi air untuk membantu masyarakat setempat.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj5"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Pengobatan Gratis untuk Warga Tidak Mampu",
"target": 40000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank Panin",
"rekening": "9988776655",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Memberikan layanan kesehatan gratis bagi warga yang tidak mampu membayar biaya pengobatan.",
"cerita": "Banyak warga yang menunda pengobatan karena keterbatasan biaya. Melalui program ini, kami akan menyelenggarakan pengobatan gratis secara berkala di berbagai wilayah.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj6"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Pembangunan Taman Bacaan Masyarakat",
"target": 35000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank Mega",
"rekening": "1357924680",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Membangun taman bacaan untuk meningkatkan minat baca masyarakat di wilayah pedesaan.",
"cerita": "Minat baca masyarakat di pedesaan masih rendah karena keterbatasan akses buku. Taman bacaan ini akan menyediakan ribuan buku gratis dan ruang baca yang nyaman untuk semua usia.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj7"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Pelatihan Keterampilan untuk Pengangguran",
"target": 55000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank CIMB Niaga",
"rekening": "2468135790",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Memberikan pelatihan keterampilan untuk membantu pengangguran mendapatkan pekerjaan atau usaha mandiri.",
"cerita": "Angka pengangguran masih tinggi di beberapa wilayah. Program pelatihan ini akan memberikan keterampilan yang dibutuhkan pasar kerja, seperti menjahit, memasak, teknologi informasi, dan lainnya.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj8"
}
},
{
"data": {
"authorId": "cmha6wb9w0001cfndwl9fcse6",
"title": "Renovasi Gedung Sekolah Rusak",
"target": 90000000,
"donasiMaster_DurasiId": 3,
"donasiMaster_KategoriId": 3,
"namaBank": "Bank OCBC NISP",
"rekening": "1029384756",
"imageId": "cm60j9q3m000xc9dc584v8rh8",
"pembukaan": "Merestrukturasi gedung sekolah yang rusak agar siswa bisa belajar dengan aman dan nyaman.",
"cerita": "Banyak gedung sekolah yang rusak parah dan membahayakan keselamatan siswa. Dana dari kampanye ini akan digunakan untuk renovasi dan perbaikan gedung sekolah yang membutuhkan.",
"imageCeritaId": "cm60j9q3m000xc9dc584v8rj9"
}
}
];
async function sendDonationData() {
const baseUrl = 'http://localhost:3000/api/mobile/donation'; // Sesuaikan dengan URL server Anda
const headers = {
'Content-Type': 'application/json',
};
for (let i = 0; i < donationDataList.length; i++) {
try {
console.log(`Mengirim data ke-${i + 1}...`);
const response = await axios.post(`${baseUrl}?category=permanent`, donationDataList[i], {
headers: headers
});
console.log(`Data ke-${i + 1} berhasil dikirim:`, response.data);
} catch (error) {
console.error(`Error saat mengirim data ke-${i + 1}:`, error.response?.data || error.message);
}
}
}
async function main() {
// Menjalankan fungsi untuk mengirim data donasi
await sendDonationData();
// Fungsi asli untuk update notifikasi
const result = await prisma.notifikasi.updateMany({
where: {
recipientId: 'cmha7p6yc0000cfoe5w2e7gdr',
},
data: {
isRead: false,
readAt: null,
},
})
console.log(`✅ Rows affected: ${result.count}`)
}
main()
.catch((err) => {
console.error('❌ Error:', err)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})