im so tired man
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
Set-Location : A positional parameter cannot be found that accepts argument 'PROGRAM'.
|
||||
At line:1 char:1
|
||||
+ Set-Location C:\COBA PROGRAM SEMESTER 4\UAS FLUTTER FT. SPRINGBOOT da ...
|
||||
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
+ CategoryInfo : InvalidArgument: (:) [Set-Location], ParameterBindingException
|
||||
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.SetLocationCommand
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
# Copy this file to walkguide-backend/demo/secrets.properties.
|
||||
# secrets.properties is gitignored and is imported by application.properties.
|
||||
|
||||
DB_URL=jdbc:postgresql://<host>:<port>/<database>
|
||||
DB_USERNAME=<database_username>
|
||||
DB_PASSWORD=<database_password>
|
||||
JWT_SECRET=<base64_hs256_secret_at_least_32_bytes>
|
||||
AGORA_APP_ID=<agora_app_id>
|
||||
AGORA_APP_CERTIFICATE=<agora_app_certificate>
|
||||
FIREBASE_CREDENTIALS_PATH=classpath:firebase/google-services-admin.json
|
||||
+1
@@ -8,4 +8,5 @@ public class LocationUpdateRequest {
|
||||
private Double accuracy;
|
||||
private Double speed;
|
||||
private Double heading;
|
||||
private Integer batteryLevel;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@@ -22,6 +23,8 @@ public class DashboardResponse {
|
||||
// Status
|
||||
private long unreadSosCount;
|
||||
private long unreadNotifCount;
|
||||
private long obstaclesToday;
|
||||
private Map<String, Object> userStatus;
|
||||
|
||||
// Recent activity (5 terbaru)
|
||||
private List<ActivityLogResponse> recentActivity;
|
||||
|
||||
@@ -16,5 +16,6 @@ public class LocationResponse {
|
||||
private Double accuracy;
|
||||
private Double speed;
|
||||
private Double heading;
|
||||
private Integer batteryLevel;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ public class LocationHistory {
|
||||
private Double speed; // m/s
|
||||
private Double heading; // derajat 0-360
|
||||
|
||||
@Column(name = "battery_level")
|
||||
private Integer batteryLevel;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
|
||||
+6
-1
@@ -39,8 +39,13 @@ public class GlobalExceptionHandler {
|
||||
}
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleRuntime(RuntimeException ex) {
|
||||
String message = ex.getMessage();
|
||||
if ("Email tidak terdaftar".equals(message) || "Password salah".equals(message)) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(ApiResponse.error("AUTH_INVALID", message));
|
||||
}
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(ApiResponse.error("INTERNAL_ERROR", ex.getMessage()));
|
||||
.body(ApiResponse.error("INTERNAL_ERROR", message));
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
|
||||
+4
@@ -4,7 +4,11 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
public interface ObstacleLogRepository extends JpaRepository<ObstacleLog, Long> {
|
||||
Page<ObstacleLog> findByUserIdOrderByCreatedAtDesc(Long userId, Pageable pageable);
|
||||
long countByUserIdAndCreatedAtAfter(Long userId, LocalDateTime createdAt);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -82,7 +85,34 @@ public class JwtUtil {
|
||||
}
|
||||
|
||||
private Key getSignInKey() {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
|
||||
byte[] keyBytes = decodeSecret(secretKey);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
private byte[] decodeSecret(String configuredSecret) {
|
||||
String trimmed = configuredSecret == null ? "" : configuredSecret.trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
throw new IllegalStateException("JWT secret must not be empty");
|
||||
}
|
||||
|
||||
byte[] keyBytes;
|
||||
try {
|
||||
keyBytes = Decoders.BASE64.decode(trimmed);
|
||||
} catch (RuntimeException base64Error) {
|
||||
try {
|
||||
keyBytes = Decoders.BASE64URL.decode(trimmed);
|
||||
} catch (RuntimeException base64UrlError) {
|
||||
keyBytes = trimmed.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
if (keyBytes.length >= 32) {
|
||||
return keyBytes;
|
||||
}
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256").digest(keyBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("SHA-256 is not available", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -8,6 +8,11 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GuardianDashboardService {
|
||||
@@ -17,6 +22,7 @@ public class GuardianDashboardService {
|
||||
private final ActivityLogService activityLogService;
|
||||
private final SosEventRepository sosEventRepository;
|
||||
private final GuardianNotificationRepository notifRepository;
|
||||
private final ObstacleLogRepository obstacleLogRepository;
|
||||
|
||||
public DashboardResponse getDashboard(Long guardianId) {
|
||||
var pairing = pairingRelationRepository
|
||||
@@ -40,6 +46,21 @@ public class GuardianDashboardService {
|
||||
long unreadSos = sosEventRepository.findByUserIdOrderByCreatedAtDesc(userId, PageRequest.of(0, 100))
|
||||
.stream().filter(s -> s.getStatus().name().equals("TRIGGERED")).count();
|
||||
long unreadNotif = notifRepository.countByUserIdAndIsReadFalse(userId);
|
||||
long obstaclesToday = obstacleLogRepository.countByUserIdAndCreatedAtAfter(
|
||||
userId,
|
||||
LocalDateTime.of(LocalDateTime.now().toLocalDate(), LocalTime.MIN)
|
||||
);
|
||||
|
||||
Map<String, Object> userStatus = new HashMap<>();
|
||||
userStatus.put("displayName", user.getDisplayName());
|
||||
userStatus.put("email", user.getEmail());
|
||||
userStatus.put("online", lastLocation != null
|
||||
&& lastLocation.getCreatedAt() != null
|
||||
&& lastLocation.getCreatedAt().isAfter(LocalDateTime.now().minusMinutes(2)));
|
||||
userStatus.put("lastSeenAt", lastLocation != null ? lastLocation.getCreatedAt() : null);
|
||||
userStatus.put("battery", lastLocation != null ? lastLocation.getBatteryLevel() : null);
|
||||
userStatus.put("lastSpeed", lastLocation != null ? lastLocation.getSpeed() : null);
|
||||
userStatus.put("obstaclesToday", obstaclesToday);
|
||||
|
||||
return DashboardResponse.builder()
|
||||
.pairedUserId(userId)
|
||||
@@ -49,6 +70,8 @@ public class GuardianDashboardService {
|
||||
.lastLocation(lastLocation)
|
||||
.unreadSosCount(unreadSos)
|
||||
.unreadNotifCount(unreadNotif)
|
||||
.obstaclesToday(obstaclesToday)
|
||||
.userStatus(userStatus)
|
||||
.recentActivity(recentActivity)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ public class LocationService {
|
||||
.accuracy(req.getAccuracy())
|
||||
.speed(req.getSpeed())
|
||||
.heading(req.getHeading())
|
||||
.batteryLevel(req.getBatteryLevel())
|
||||
.build();
|
||||
loc = locationHistoryRepository.save(loc);
|
||||
|
||||
@@ -136,6 +137,7 @@ public class LocationService {
|
||||
return LocationResponse.builder()
|
||||
.id(l.getId()).lat(l.getLat()).lng(l.getLng())
|
||||
.accuracy(l.getAccuracy()).speed(l.getSpeed()).heading(l.getHeading())
|
||||
.batteryLevel(l.getBatteryLevel())
|
||||
.createdAt(l.getCreatedAt()).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,9 @@
|
||||
# ===================================================
|
||||
# Profile: prod (production)
|
||||
# Aktifkan dengan: --spring.profiles.active=prod
|
||||
# Semua nilai WAJIB diisi via environment variable
|
||||
# Tidak ada default value — akan gagal start jika kosong
|
||||
# ===================================================
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: ${DB_URL}
|
||||
username: ${DB_USERNAME}
|
||||
password: ${DB_PASSWORD}
|
||||
|
||||
jpa:
|
||||
show-sql: false
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: false
|
||||
|
||||
server:
|
||||
port: ${PORT:8080}
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET}
|
||||
expiration: ${JWT_EXPIRATION:86400000}
|
||||
|
||||
agora:
|
||||
app-id: ${AGORA_APP_ID}
|
||||
app-certificate: ${AGORA_APP_CERTIFICATE}
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.walkguide: INFO
|
||||
org.springframework.messaging: WARN
|
||||
org.springframework.web.socket: WARN
|
||||
DB_URL=jdbc:postgresql://<host>:<port>/<database>
|
||||
DB_USERNAME=<database_username>
|
||||
DB_PASSWORD=<database_password>
|
||||
JWT_SECRET=<base64_hs256_secret_at_least_32_bytes>
|
||||
JWT_EXPIRATION=86400000
|
||||
AGORA_APP_ID=<agora_app_id>
|
||||
AGORA_APP_CERTIFICATE=<agora_app_certificate>
|
||||
FIREBASE_CREDENTIALS_PATH=classpath:firebase/google-services-admin.json
|
||||
FIREBASE_NOTIFICATIONS_COLLECTION=notifications
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: ${DB_URL:jdbc:postgresql://202.46.28.160:2002/uas_5803024001}
|
||||
username: ${DB_USERNAME:5803024001}
|
||||
password: ${DB_PASSWORD:pw5803024001}
|
||||
url: ${DB_URL}
|
||||
username: ${DB_USERNAME}
|
||||
password: ${DB_PASSWORD}
|
||||
hikari:
|
||||
maximum-pool-size: ${DB_POOL_MAX:1}
|
||||
minimum-idle: ${DB_POOL_MIN_IDLE:0}
|
||||
@@ -16,8 +16,8 @@ spring:
|
||||
idle-timeout: ${DB_IDLE_TIMEOUT:30000}
|
||||
max-lifetime: ${DB_MAX_LIFETIME:120000}
|
||||
|
||||
flyway:
|
||||
enabled: ${FLYWAY_ENABLED:false}
|
||||
flyway:
|
||||
enabled: ${FLYWAY_ENABLED:true}
|
||||
|
||||
jpa:
|
||||
show-sql: true
|
||||
@@ -26,12 +26,12 @@ spring:
|
||||
format_sql: true
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:d2Fsa2d1aWRlLWRldi1qd3Qtc2VjcmV0LWtleS0zMi1ieXRlcy1taW5pbXVt}
|
||||
secret: ${JWT_SECRET}
|
||||
expiration: ${JWT_EXPIRATION:86400000}
|
||||
|
||||
agora:
|
||||
app-id: ${AGORA_APP_ID:e36c2b6592e34cfda1f6ea6432a5e68d}
|
||||
app-certificate: ${AGORA_APP_CERTIFICATE:70a4288475734a8c92ff8686c66cbc77}
|
||||
agora:
|
||||
app-id: ${AGORA_APP_ID:}
|
||||
app-certificate: ${AGORA_APP_CERTIFICATE:}
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# ===== SERVER =====
|
||||
spring.config.import=optional:file:./secrets.properties
|
||||
spring.config.import=optional:file:./secrets.properties,optional:file:walkguide-backend/demo/secrets.properties
|
||||
server.port=${SERVER_PORT:8080}
|
||||
server.address=${SERVER_ADDRESS:0.0.0.0}
|
||||
|
||||
# ===== POSTGRESQL CONNECTION =====
|
||||
spring.datasource.url=${DB_URL:jdbc:postgresql://202.46.28.160:2002/uas_5803024001}
|
||||
spring.datasource.username=${DB_USERNAME:5803024001}
|
||||
spring.datasource.password=${DB_PASSWORD:pw5803024001}
|
||||
spring.datasource.url=${DB_URL}
|
||||
spring.datasource.username=${DB_USERNAME}
|
||||
spring.datasource.password=${DB_PASSWORD}
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
# ===== HIKARI POOL (keep DB classroom slots low) =====
|
||||
spring.datasource.hikari.maximum-pool-size=${DB_POOL_MAX:1}
|
||||
@@ -27,7 +27,7 @@ spring.flyway.locations=classpath:db/migration
|
||||
spring.flyway.baseline-on-migrate=true
|
||||
|
||||
# ===== JWT =====
|
||||
jwt.secret=${JWT_SECRET:d2Fsa2d1aWRlLWRldi1qd3Qtc2VjcmV0LWtleS0zMi1ieXRlcy1taW5pbXVt}
|
||||
jwt.secret=${JWT_SECRET}
|
||||
jwt.expiration=${JWT_EXPIRATION:86400000}
|
||||
|
||||
# ===== SWAGGER =====
|
||||
@@ -35,7 +35,7 @@ springdoc.swagger-ui.path=/swagger-ui.html
|
||||
springdoc.api-docs.path=/v3/api-docs
|
||||
|
||||
# ===== AGORA RTC =====
|
||||
agora.app-id=${AGORA_APP_ID:e36c2b6592e34cfda1f6ea6432a5e68d}
|
||||
agora.app-id=${AGORA_APP_ID:}
|
||||
agora.app-certificate=${AGORA_APP_CERTIFICATE:}
|
||||
|
||||
# ===== FIREBASE =====
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE location_history
|
||||
ADD COLUMN IF NOT EXISTS battery_level INTEGER;
|
||||
Reference in New Issue
Block a user