Backend API yang powerful untuk aplikasi mobile Odyhub - Platform Pengaduan Masyarakat berbasis Flutter
Features β’ Tech Stack β’ API Documentation β’ Installation β’ Architecture
Odyhub adalah platform digital yang memungkinkan masyarakat untuk melaporkan berbagai permasalahan dan keluhan di lingkungan mereka secara cepat dan transparan. Backend ini menyediakan RESTful API yang robust untuk mendukung aplikasi mobile Flutter dengan fitur-fitur modern seperti:
- β Autentikasi Multi-Layer dengan OTP Email & JWT Token
- π Keamanan Tinggi menggunakan Argon2 Password Hashing
- π Geolocation Support untuk tracking lokasi pengaduan
- πΈ Multi-Image Upload dengan optimisasi storage
- π Real-time Status Tracking untuk setiap laporan
- π§ Email Notification System untuk verifikasi dan notifikasi
- π₯ Role-Based Access Control (User & Admin)
- π Analytics Dashboard untuk monitoring pengaduan
- User Registration dengan verifikasi OTP via email
- Secure Login dengan Two-Factor Authentication (2FA)
- JWT Token Management dengan blacklist support
- Admin Authentication tanpa OTP untuk akses cepat
- Session Management dengan auto-expiry
- Password Hashing menggunakan Argon2 (state-of-the-art)
- Create Report dengan lokasi GPS, foto, dan kategori
- Update Report untuk edit pengaduan yang dibuat
- Delete Report dengan validasi kepemilikan
- View My Reports untuk tracking pengaduan pribadi
- Search & Filter berdasarkan judul, kategori, dan status
- Real-time Updates untuk perubahan status laporan
Mendukung 10 kategori pengaduan:
- ποΈ Infrastruktur (jalan rusak, lampu mati, dll)
- π³ Lingkungan (sampah, polusi, dll)
- π Transportasi (macet, parkir liar, dll)
- π‘οΈ Keamanan (pencurian, kriminal, dll)
- π₯ Kesehatan (fasilitas kesehatan, dll)
- π Pendidikan (sekolah, fasilitas pendidikan, dll)
- π₯ Sosial (kemiskinan, kesejahteraan, dll)
- π Izin & Perizinan
- ποΈ Birokrasi & Pelayanan Publik
- π Lainnya
- Dashboard Analytics dengan statistik harian
- Status Management untuk update progress laporan
- Response System untuk memberikan tanggapan
- Filter by Status (Pending, In Progress, Done)
- Daily Report Chart untuk monitoring trend
- Profile Customization dengan foto profil & background
- Image Upload & Storage yang teroptimasi
- Profile Settings untuk update informasi pribadi
- OTP Email dengan template HTML yang menarik
- Registration Confirmation dengan expiry time
- Login Verification untuk keamanan ekstra
- Responsive Email Design yang mobile-friendly
- Spring Boot 3.2.3 - Modern Java framework
- Spring Data JPA - ORM untuk database operations
- Spring Web - RESTful API development
- Spring Mail - Email integration
- JWT (JSON Web Tokens) - Stateless authentication
- Argon2 Password Encoder - Secure password hashing
- Spring Security Crypto - Cryptographic operations
- MySQL 8.0 - Primary database
- Hibernate - JPA implementation
- Connection Pooling - Optimized database connections
- Local File System - Optimized file management
- Multi-part File Upload - Support large files up to 10MB
- Image Processing - Automatic optimization
- JavaMail API - Email functionality
- SMTP Gmail Integration - Reliable email delivery
- HTML Email Templates - Professional email design
- Maven - Dependency management
- Jackson - JSON processing
- SLF4J & Logback - Logging framework
- JUnit - Unit testing
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MOBILE APP (Flutter) β
β Odyhub β
ββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
β HTTP/HTTPS
β REST API
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SPRING BOOT BACKEND β
β β
β βββββββββββββββ ββββββββββββββββ βββββββββββββββ β
β β Controllers β β Services β β Repositoriesβ β
β βββββββββββββββ ββββββββββββββββ βββββββββββββββ β
β β β β β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββ ββββββββββββββββ βββββββββββββββ β
β β JWT Auth β β Email Serviceβ β MySQL DB β β
β β Filter β β (SMTP) β β (JPA) β β
β βββββββββββββββ ββββββββββββββββ βββββββββββββββ β
β β
β βββββββββββββββ ββββββββββββββββ βββββββββββββββ β
β βFile Storage β β Scheduled β β Util β β
β β Service β β Tasks β β Classes β β
β βββββββββββββββ ββββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββ βββββββββββββββββββ ββββββββββββββββββββ
β User β β Pengaduan β β StatusLaporan β
βββββββββββββββ β€ βββββββββββββββββββ€ ββββββββββββββββββββ€
β id (PK) βββββ<β user_id (FK) β>βββ β pengaduan_id (FK)β
β nama β β id (PK) β β id (PK) β
β email β β judul β β status_sebelumnyaβ
β password β β alamat β β status_baru β
β role β β deskripsi β β tanggapan β
β otp_code β β kategori β β gambar β
β otp_expiry β β gambar β β changed_at β
β is_registeringβ β latitude β ββββββββββββββββββββ
βββββββββββββββββ β longitude β
β created_at β ββββββββββββββββββββ
β updated_at β β UserProfile β
βββββββββββββββββββ ββββββββββββββββββββ€
β id (PK) β
β user_id (FK) β
β profile_image β
β background_image β
ββββββββββββββββββββ
- β Java JDK 17+ (Recommended: JDK 20)
- ποΈ MySQL 8.0+
- π¦ Maven 3.6+ (or use IDE built-in)
- π§ Gmail Account (for SMTP email service)
git clone https://github.com/Aldayanday1/odyhub_be.git
cd odyhub_be
-
Start MySQL Service:
# Windows net start MySQL80 # Linux/Mac sudo systemctl start mysql
-
Create Database:
mysql -u root -p CREATE DATABASE sistem_pengaduan; exit;
-
Copy template environment variables:
cp .env.example .env
-
Edit file
.env
dengan credentials Anda:# Database DB_USERNAME=root DB_PASSWORD=your_mysql_password # Email SMTP MAIL_USERNAME=your-email@gmail.com MAIL_PASSWORD=your-gmail-app-password # JWT Secret (generate dengan: openssl rand -base64 32) SECRET_KEY=your-jwt-secret-key # Upload folder UPLOAD_FOLDER=./status-laporan-images
-
Generate Gmail App Password:
- Aktifkan 2FA: https://myaccount.google.com/security
- Generate App Password: https://myaccount.google.com/apppasswords
- Pilih "Mail" dan copy 16-character password
-
File
.env
sudah di-ignore oleh Git - aman untuk menyimpan credentials
Windows PowerShell:
$env:DB_PASSWORD="your_password"
$env:MAIL_USERNAME="your-email@gmail.com"
$env:MAIL_PASSWORD="your-app-password"
$env:SECRET_KEY="your-secret-key"
Linux/Mac:
export DB_PASSWORD="your_password"
export MAIL_USERNAME="your-email@gmail.com"
export MAIL_PASSWORD="your-app-password"
export SECRET_KEY="your-secret-key"
# Create folders for image storage
mkdir uploads
mkdir status-laporan-images
Option 1: Using Maven
mvn spring-boot:run
Option 2: Using IDE
- NetBeans: Right-click project β Run
- IntelliJ IDEA: Click Run button
- Eclipse: Right-click β Run As β Spring Boot App
Option 3: Using JAR
mvn clean package
java -jar target/sistem_pengaduan-0.0.1-SNAPSHOT.jar
# Check if server is running
curl http://localhost:8080/api/users/all
β Backend is ready! Now you can connect your Flutter mobile app.
http://localhost:8080/api/users
POST /register
Content-Type: application/json
{
"nama": "John Doe",
"email": "john@example.com",
"password": "SecurePass123"
}
Response:
{
"message": "Registrasi berhasil. Silakan cek email Anda untuk kode OTP."
}
POST /verify-otp?otp=1234
Response:
{
"message": "Verifikasi OTP berhasil. Silakan login."
}
POST /login?email=john@example.com&password=SecurePass123
Response:
{
"message": "OTP telah dikirimkan ke email Anda."
}
POST /login-with-otp
Content-Type: application/json
{
"otp": "1234"
}
Response:
{
"status": "success",
"message": "Login berhasil",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
POST /admin-login
Content-Type: application/json
{
"nama": "Admin",
"password": "AdminPass123"
}
Response:
{
"status": "success",
"message": "Login berhasil",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
POST /logout
Authorization: Bearer <token>
POST /add
Authorization: Bearer <token>
Content-Type: multipart/form-data
Form Data:
- judul: "Jalan Rusak di Depan Kantor"
- alamat: "Jl. Merdeka No. 123"
- deskripsi: "Jalan berlubang dan berbahaya"
- kategori: "INFRASTRUKTUR"
- latitude: -6.200000
- longitude: 106.816666
- gambar: [file]
Response:
{
"id": 1,
"judul": "Jalan Rusak di Depan Kantor",
"alamat": "Jl. Merdeka No. 123",
"deskripsi": "Jalan berlubang dan berbahaya",
"kategori": "INFRASTRUKTUR",
"gambar": "http://192.168.56.1:8080/api/users/uploads/image.jpg",
"latitude": -6.200000,
"longitude": 106.816666,
"status": "PENDING",
"namaPembuat": "John Doe",
"profileImagePembuat": "http://192.168.56.1:8080/images/profile.png",
"createdAt": "2025-10-01T10:30:00",
"updatedAt": "2025-10-01T10:30:00"
}
GET /all
GET /my-pengaduan
Authorization: Bearer <token>
GET /{id}
PUT /update/{id}
Authorization: Bearer <token>
Content-Type: multipart/form-data
Form Data:
- judul: "Updated Title"
- deskripsi: "Updated Description"
- gambar: [file] (optional)
DELETE /delete/{id}
Authorization: Bearer <token>
GET /search?judul=jalan
GET /kategori/INFRASTRUKTUR
GET /kategori/LINGKUNGAN
GET /kategori/TRANSPORTASI
PUT /update-status/{pengaduanId}
Authorization: Bearer <admin-token>
Content-Type: multipart/form-data
Form Data:
- statusBaru: "PROGRESS" | "DONE"
- tanggapan: "Sedang ditangani oleh tim"
- gambar: [file] (optional)
Response:
{
"id": 1,
"pengaduan": { ... },
"statusSebelumnya": "PENDING",
"statusBaru": "PROGRESS",
"tanggapan": "Sedang ditangani oleh tim",
"gambar": "http://192.168.56.1:8080/api/users/uploads/response.jpg",
"changedAt": "2025-10-01T11:00:00"
}
GET /pengaduan-by-status/PENDING
GET /pengaduan-by-status/PROGRESS
GET /pengaduan-by-status/DONE
Authorization: Bearer <admin-token>
GET /daily-count
Authorization: Bearer <admin-token>
Response:
{
"MONDAY": 15,
"TUESDAY": 23,
"WEDNESDAY": 18,
"THURSDAY": 20,
"FRIDAY": 25,
"SATURDAY": 12,
"SUNDAY": 8
}
GET /profile
Authorization: Bearer <token>
Response:
{
"nama": "John Doe",
"email": "john@example.com",
"profileImage": "http://192.168.56.1:8080/images/profile.png",
"backgroundImage": "http://192.168.56.1:8080/images/background.jpg"
}
PUT /profile/update
Authorization: Bearer <token>
Content-Type: multipart/form-data
Form Data:
- profileImage: [file] (optional)
- backgroundImage: [file] (optional)
GET /uploads/{filename}
Example:
http://localhost:8080/api/users/uploads/image.jpg
- Argon2 Algorithm dengan parameter custom:
- Memory: 16 MB
- Iterations: 32
- Parallelism: 1
- Salt Length: 4096 bytes
- Hash Length: 64 bytes
- Token Expiration: 40 menit (2400 seconds)
- Token Blacklist: Logout akan masukkan token ke blacklist
- Role-Based Claims: Token menyimpan role (USER/ADMIN)
- Secret Key: Disimpan di environment variable
- Expiry Time: 1 menit
- Auto-Cleanup: Scheduled task menghapus OTP expired
- Single Use: OTP dihapus setelah verifikasi berhasil
- Random Generation: 4-digit random number
- Authorization Header: Bearer token untuk protected endpoints
- Role Validation: Middleware memeriksa role untuk admin endpoints
- Token Validation: Setiap request divalidasi JWT-nya
- CORS Configuration: Atur allowed origins sesuai kebutuhan
// lib/services/api_service.dart
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
static const String baseUrl = 'http://YOUR_IP:8080/api/users';
static String? authToken;
// Register User
static Future<Map<String, dynamic>> register({
required String nama,
required String email,
required String password,
}) async {
final response = await http.post(
Uri.parse('$baseUrl/register'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'nama': nama,
'email': email,
'password': password,
}),
);
return jsonDecode(response.body);
}
// Verify OTP
static Future<String> verifyOtp(String otp) async {
final response = await http.post(
Uri.parse('$baseUrl/verify-otp?otp=$otp'),
);
return response.body;
}
// Login
static Future<Map<String, dynamic>> login({
required String email,
required String password,
}) async {
final response = await http.post(
Uri.parse('$baseUrl/login?email=$email&password=$password'),
);
return {'message': response.body};
}
// Login with OTP
static Future<Map<String, dynamic>> loginWithOtp(String otp) async {
final response = await http.post(
Uri.parse('$baseUrl/login-with-otp'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'otp': otp}),
);
final data = jsonDecode(response.body);
if (data['status'] == 'success') {
authToken = data['token'];
}
return data;
}
// Create Pengaduan
static Future<Map<String, dynamic>> createPengaduan({
required String judul,
required String alamat,
required String deskripsi,
required String kategori,
required double latitude,
required double longitude,
required File gambar,
}) async {
var request = http.MultipartRequest(
'POST',
Uri.parse('$baseUrl/add'),
);
request.headers['Authorization'] = 'Bearer $authToken';
request.fields['judul'] = judul;
request.fields['alamat'] = alamat;
request.fields['deskripsi'] = deskripsi;
request.fields['kategori'] = kategori;
request.fields['latitude'] = latitude.toString();
request.fields['longitude'] = longitude.toString();
request.files.add(await http.MultipartFile.fromPath('gambar', gambar.path));
final response = await request.send();
final responseData = await response.stream.bytesToString();
return jsonDecode(responseData);
}
// Get All Pengaduan
static Future<List<dynamic>> getAllPengaduan() async {
final response = await http.get(Uri.parse('$baseUrl/all'));
return jsonDecode(response.body);
}
// Get My Pengaduan
static Future<List<dynamic>> getMyPengaduan() async {
final response = await http.get(
Uri.parse('$baseUrl/my-pengaduan'),
headers: {'Authorization': 'Bearer $authToken'},
);
return jsonDecode(response.body);
}
}
// Example: Login Screen
class LoginScreen extends StatelessWidget {
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
void handleLogin() async {
try {
// Step 1: Request OTP
await ApiService.login(
email: emailController.text,
password: passwordController.text,
);
// Step 2: Navigate to OTP screen
Navigator.push(context, MaterialPageRoute(
builder: (context) => OtpVerificationScreen(),
));
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login failed: $e')),
);
}
}
@override
Widget build(BuildContext context) {
// Your UI implementation
}
}
sistem_pengaduan/
βββ src/
β βββ main/
β β βββ java/sistem_pengaduan/
β β β βββ SistemPengaduanApplication.java
β β β βββ demo/
β β β βββ config/
β β β β βββ SecurityConfig.java
β β β βββ controller/
β β β β βββ UserController.java
β β β β βββ PengaduanController.java
β β β β βββ StatusLaporanController.java
β β β β βββ UserProfileController.java
β β β β βββ FileStorageService.java
β β β βββ model/
β β β β βββ User.java
β β β β βββ UserRole.java
β β β β βββ UserProfile.java
β β β β βββ Pengaduan.java
β β β β βββ Kategori.java
β β β β βββ StatusLaporan.java
β β β β βββ Status.java
β β β βββ repository/
β β β β βββ UserRepo.java
β β β β βββ UserProfileRepo.java
β β β β βββ PengaduanRepo.java
β β β β βββ StatusLaporanRepo.java
β β β βββ service/
β β β β βββ UserService.java
β β β β βββ PengaduanService.java
β β β β βββ StatusLaporanService.java
β β β β βββ EmailServiceRegist.java
β β β β βββ EmailServiceLogin.java
β β β β βββ FileStorageException.java
β β β β βββ ScheduledTasks.java
β β β βββ util/
β β β βββ JwtUtil.java
β β β βββ JwtRequestFilter.java
β β βββ resources/
β β βββ application.properties
β β βββ META-INF/
β β βββ persistence.xml
β βββ test/
β βββ java/sistem_pengaduan/demo/
β βββ SistemPengaduanApplicationTests.java
βββ uploads/ # User uploaded images
βββ status-laporan-images/ # Admin response images
βββ pom.xml
βββ README.md
# Server Configuration
server.port=8080
# Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/sistem_pengaduan?serverTimezone=Asia/Jakarta
spring.datasource.username=root
spring.datasource.password=YOUR_PASSWORD
# JPA Configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# File Upload Configuration
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
# Email Configuration
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=${MAIL_USERNAME:your-email@gmail.com}
spring.mail.password=${MAIL_PASSWORD:your-app-password}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
# Upload Folders
upload.folder=./status-laporan-images
- β Connection Pooling dengan HikariCP
- β Lazy Loading untuk relasi OneToMany
- β Index Optimization pada foreign keys
- β Query Optimization dengan JPQL
- β Compression enabled untuk response
- β Lazy Initialization untuk dependencies
- β Efficient JSON Serialization dengan Jackson
- β Paginated Responses untuk list endpoints
- β Argon2 lebih aman dari BCrypt
- β JWT Stateless mengurangi database hits
- β Token Blacklist in-memory untuk performa
- β Scheduled Cleanup untuk expired OTP
# Windows
netstat -ano | findstr :8080
taskkill /PID <PID> /F
# Linux/Mac
lsof -i :8080
kill -9 <PID>
# Check MySQL service
net start MySQL80 # Windows
sudo systemctl start mysql # Linux
# Verify connection
mysql -u root -p
- Pastikan 2FA aktif di Gmail
- Generate App Password baru
- Update
application.properties
dengan App Password
# Set Java memory options
export MAVEN_OPTS="-Xms256m -Xmx1024m"
mvn spring-boot:run
mvn test
Register:
curl -X POST http://localhost:8080/api/users/register \
-H "Content-Type: application/json" \
-d '{"nama":"Test User","email":"test@example.com","password":"Test123"}'
Login:
curl -X POST "http://localhost:8080/api/users/login?email=test@example.com&password=Test123"
Import collection dengan endpoint yang ada di API Documentation.
-
Build JAR:
mvn clean package -DskipTests
-
Set Environment Variables:
export SECRET_KEY="production-secret-key" export MAIL_USERNAME="production-email@gmail.com" export MAIL_PASSWORD="production-app-password" export SERVER_PORT=8080
-
Run Application:
java -jar target/sistem_pengaduan-0.0.1-SNAPSHOT.jar
-
Setup as Service (Linux):
sudo nano /etc/systemd/system/odyhub-backend.service [Unit] Description=Odyhub Backend Service After=mysql.service [Service] User=youruser ExecStart=/usr/bin/java -jar /path/to/sistem_pengaduan.jar SuccessExitStatus=143 [Install] WantedBy=multi-user.target sudo systemctl enable odyhub-backend sudo systemctl start odyhub-backend
{
"status": "success",
"message": "Operation successful",
"data": { ... }
}
{
"status": "error",
"message": "Error description",
"timestamp": "2025-10-01T10:30:00"
}
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create feature branch (
git checkout -b feature/AmazingFeature
) - Commit changes (
git commit -m 'Add AmazingFeature'
) - Push to branch (
git push origin feature/AmazingFeature
) - Open Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Aldi Raihan
- GitHub: @Aldayanday1
- Repository: odyhub_be
- Spring Boot Team untuk framework yang luar biasa
- MySQL Team untuk database yang reliable
- Gmail SMTP untuk email service
- Flutter Community untuk mobile app integration
Jika ada pertanyaan atau issue:
- π‘ Report Bug or Request Feature
- π§ Email: onlymarfa69@gmail.com
β Star this repository if you find it helpful!
Made with β€οΈ for better community engagement