im so tired man

This commit is contained in:
Wowieee4
2026-05-28 11:27:06 +07:00
parent 66da2473e1
commit 6272ece15d
86 changed files with 1759 additions and 4090 deletions
@@ -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
@@ -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;
@@ -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,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);
}
}
}
@@ -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 =====
@@ -0,0 +1,2 @@
ALTER TABLE location_history
ADD COLUMN IF NOT EXISTS battery_level INTEGER;