first week launch
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.walkguide;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.walkguide.config;
|
||||
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.walkguide.entity.User;
|
||||
import com.walkguide.repository.UserRepository;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DataSeeder implements CommandLineRunner {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
// Mesin enkripsi password
|
||||
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
// Ngecek kalau tabel users kosong, baru diisi
|
||||
if (userRepository.count() == 0) {
|
||||
|
||||
// 1. Bikin akun Guardian (Pendamping)
|
||||
User guardian = User.builder()
|
||||
.email("guardian@walkguide.com")
|
||||
.password(passwordEncoder.encode("guardian123")) // Password barunya
|
||||
.role("ROLE_GUARDIAN") // Role udah diganti
|
||||
.build();
|
||||
|
||||
// 2. Bikin akun User (Tunanetra)
|
||||
User user = User.builder()
|
||||
.email("user@walkguide.com")
|
||||
.password(passwordEncoder.encode("user123"))
|
||||
.role("ROLE_USER")
|
||||
.build();
|
||||
|
||||
// Simpan ke database
|
||||
userRepository.save(guardian);
|
||||
userRepository.save(user);
|
||||
|
||||
System.out.println("✅ Data Seeder berhasil: Akun Guardian dan User telah masuk ke Database!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.walkguide.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Walk Guide API")
|
||||
.version("1.0")
|
||||
.description("API Documentation for Walk Guide Application (Final Exam)"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.walkguide.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
// MESIN HASHING PASSWORD: Biar password yang disimpan di database gak bisa dibaca langsung, kita enkripsi dulu pake BCrypt
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
// Aturan jalan masuk ke API kita:
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors(cors -> cors.configurationSource(request -> {
|
||||
var corsConfiguration = new org.springframework.web.cors.CorsConfiguration();
|
||||
// Izinkan semua origin localhost dan semua port (biar Chrome gak ngeblok)
|
||||
corsConfiguration.setAllowedOriginPatterns(java.util.List.of(
|
||||
"http://localhost:*",
|
||||
"http://127.0.0.1:*"
|
||||
));
|
||||
corsConfiguration.setAllowedMethods(java.util.List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
corsConfiguration.setAllowedHeaders(java.util.List.of("*"));
|
||||
corsConfiguration.setAllowCredentials(true);
|
||||
return corsConfiguration;
|
||||
}))
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/api/auth/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.walkguide.controller;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.walkguide.dto.AuthRequest;
|
||||
import com.walkguide.dto.AuthResponse;
|
||||
import com.walkguide.service.AuthService;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth") // Ini URL awalnya
|
||||
@RequiredArgsConstructor
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
// Ini URL lengkapnya jadi POST http://localhost:8080/api/auth/login
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<?> login(@RequestBody AuthRequest request) {
|
||||
try {
|
||||
// Lempar kardus AuthRequest ke Service
|
||||
AuthResponse response = authService.login(request);
|
||||
// Kalau sukses, kirim status 200 OK + isinya
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (RuntimeException e) {
|
||||
// Kalau gagal (password salah/email gak ada), kirim status 401 Unauthorized
|
||||
return ResponseEntity.status(401).body(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.walkguide.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class ApiResponse<T> {
|
||||
private boolean success;
|
||||
private T data;
|
||||
private String message;
|
||||
private String errorCode;
|
||||
private String timestamp;
|
||||
|
||||
// Constructor buat Sukses
|
||||
public ApiResponse(boolean success, T data, String message) {
|
||||
this.success = success;
|
||||
this.data = data;
|
||||
this.message = message;
|
||||
this.timestamp = Instant.now().toString();
|
||||
}
|
||||
|
||||
// Constructor buat Error
|
||||
public ApiResponse(boolean success, String errorCode, String message) {
|
||||
this.success = success;
|
||||
this.errorCode = errorCode;
|
||||
this.message = message;
|
||||
this.timestamp = Instant.now().toString();
|
||||
}
|
||||
|
||||
// Getter Setter manual
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
|
||||
public T getData() { return data; }
|
||||
public void setData(T data) { this.data = data; }
|
||||
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
|
||||
public String getErrorCode() { return errorCode; }
|
||||
public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
|
||||
|
||||
public String getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.walkguide.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class AuthRequest {
|
||||
|
||||
@NotBlank(message = "Email tidak boleh kosong")
|
||||
@Email(message = "Format email tidak valid")
|
||||
private String email;
|
||||
|
||||
@NotBlank(message = "Password tidak boleh kosong")
|
||||
private String password;
|
||||
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.walkguide.dto;
|
||||
|
||||
public class AuthResponse {
|
||||
private String token;
|
||||
private String role;
|
||||
|
||||
public AuthResponse(String token, String role) {
|
||||
this.token = token;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
// Manual Getter Setter (Gantiin fungsi @Data)
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.walkguide.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class User {
|
||||
|
||||
// Ini otomatis bikin ID angka urut (1, 2, 3, dst)
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
// Email gak boleh kosong dan gak boleh ada yang kembar
|
||||
@Column(unique = true, nullable = false)
|
||||
private String email;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
|
||||
// Buat nandain dia ini ROLE_USER atau ROLE_ADMIN
|
||||
@Column(nullable = false)
|
||||
private String role;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.walkguide.exception;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
import com.walkguide.dto.ApiResponse;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
// Nangkep error kalau login gagal / user gak ketemu
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleRuntimeException(RuntimeException ex) {
|
||||
ApiResponse<Object> response = new ApiResponse<>(false, "AUTH_ERROR", ex.getMessage());
|
||||
return ResponseEntity.status(401).body(response);
|
||||
}
|
||||
|
||||
// Nangkep error dari Validasi (misal email format salah)
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleValidationException(MethodArgumentNotValidException ex) {
|
||||
String errorMsg = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
|
||||
ApiResponse<Object> response = new ApiResponse<>(false, "VALIDATION_ERROR", errorMsg);
|
||||
return ResponseEntity.status(400).body(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.walkguide.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.walkguide.entity.User;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
// Ini fungsi ajaib Spring Boot. Cuma dari nama fungsinya aja,
|
||||
// dia udah tau maksudnya "Tolong cariin user di database yang emailnya = X"
|
||||
Optional<User> findByEmail(String email);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.walkguide.security;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private Long expiration;
|
||||
|
||||
private Key getSigningKey() {
|
||||
return Keys.hmacShaKeyFor(secret.getBytes());
|
||||
}
|
||||
|
||||
public String generateToken(String email, String role) {
|
||||
return Jwts.builder()
|
||||
.setSubject(email)
|
||||
.claim("role", role)
|
||||
.setIssuedAt(new Date(System.currentTimeMillis()))
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.walkguide.service;
|
||||
|
||||
import com.walkguide.dto.AuthRequest;
|
||||
import com.walkguide.dto.AuthResponse;
|
||||
import com.walkguide.entity.User;
|
||||
import com.walkguide.repository.UserRepository;
|
||||
import com.walkguide.security.JwtUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
public AuthResponse login(AuthRequest request) {
|
||||
// 1. Cari user di database kampus lu berdasarkan email
|
||||
User user = userRepository.findByEmail(request.getEmail())
|
||||
.orElseThrow(() -> new RuntimeException("Email tidak ditemukan!"));
|
||||
|
||||
// 2. Cocokin password yang diketik sama password yang di-hash di database
|
||||
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
||||
throw new RuntimeException("Password salah!");
|
||||
}
|
||||
|
||||
// 3. Kalau email dan password bener, suruh JwtUtil cetak Token
|
||||
String token = jwtUtil.generateToken(user.getEmail(), user.getRole());
|
||||
|
||||
// 4. Bungkus token dan rolenya buat dikirim balik ke Flutter
|
||||
return new AuthResponse(token, user.getRole());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user