From 535c0856e8d079d624c13aca8818d9e861b01bba Mon Sep 17 00:00:00 2001 From: KaNi-04 <136799210+KaNi-04@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:28:21 +0530 Subject: [PATCH] Secure API Initial Commit --- pom.xml | 18 + .../gov/secureapi/SecureApiApplication.java | 11 + .../secureapi/config/DataSourceConfig.java | 84 +++ .../gov/secureapi/config/SecurityConfig.java | 77 ++ .../secureapi/controller/AuthController.java | 35 + .../controller/CustomerController.java | 50 ++ .../controller/DBFOT_controller.java | 113 +++ .../secureapi/controller/MainController.java | 32 + .../exception/GlobalExceptionHandler.java | 34 + .../secureapi/filter/BearerTokenFilter.java | 81 ++ .../repository/CustomerRepository.java | 30 + .../secureapi/repository/TokenRepository.java | 47 ++ .../secureapi/repository/UserRepository.java | 55 ++ .../gov/secureapi/service/DBFOT_service.java | 712 ++++++++++++++++++ .../gov/secureapi/service/DateTimeUtil.java | 34 + .../gov/secureapi/service/TokenService.java | 97 +++ .../java/in/gov/secureapi/util/TokenUtil.java | 30 + .../resources/application-local.properties | 3 + .../application-production.properties | 3 + src/main/resources/application.properties | 67 ++ src/main/resources/schema.sql | 0 21 files changed, 1613 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/in/gov/secureapi/SecureApiApplication.java create mode 100644 src/main/java/in/gov/secureapi/config/DataSourceConfig.java create mode 100644 src/main/java/in/gov/secureapi/config/SecurityConfig.java create mode 100644 src/main/java/in/gov/secureapi/controller/AuthController.java create mode 100644 src/main/java/in/gov/secureapi/controller/CustomerController.java create mode 100644 src/main/java/in/gov/secureapi/controller/DBFOT_controller.java create mode 100644 src/main/java/in/gov/secureapi/controller/MainController.java create mode 100644 src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java create mode 100644 src/main/java/in/gov/secureapi/repository/CustomerRepository.java create mode 100644 src/main/java/in/gov/secureapi/repository/TokenRepository.java create mode 100644 src/main/java/in/gov/secureapi/repository/UserRepository.java create mode 100644 src/main/java/in/gov/secureapi/service/DBFOT_service.java create mode 100644 src/main/java/in/gov/secureapi/service/DateTimeUtil.java create mode 100644 src/main/java/in/gov/secureapi/service/TokenService.java create mode 100644 src/main/java/in/gov/secureapi/util/TokenUtil.java create mode 100644 src/main/resources/application-local.properties create mode 100644 src/main/resources/application-production.properties create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/schema.sql diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..18e2302 --- /dev/null +++ b/pom.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + + org.springframework.bootspring-boot-starter-parent3.5.6 + + in.gov.gccsecure-api1.0.0 + 17 + + org.springframework.bootspring-boot-starter-web + org.springframework.bootspring-boot-starter-security + org.springframework.bootspring-boot-starter-jdbc + org.springframework.bootspring-boot-starter-data-jpa + org.springframework.bootspring-boot-starter-validation + com.mysqlmysql-connector-jruntime + + org.springframework.bootspring-boot-maven-plugin + diff --git a/src/main/java/in/gov/secureapi/SecureApiApplication.java b/src/main/java/in/gov/secureapi/SecureApiApplication.java new file mode 100644 index 0000000..1e720a0 --- /dev/null +++ b/src/main/java/in/gov/secureapi/SecureApiApplication.java @@ -0,0 +1,11 @@ +package in.gov.secureapi; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SecureApiApplication { + public static void main(String[] args) { + SpringApplication.run(SecureApiApplication.class, args); + } +} diff --git a/src/main/java/in/gov/secureapi/config/DataSourceConfig.java b/src/main/java/in/gov/secureapi/config/DataSourceConfig.java new file mode 100644 index 0000000..f979d97 --- /dev/null +++ b/src/main/java/in/gov/secureapi/config/DataSourceConfig.java @@ -0,0 +1,84 @@ +package in.gov.secureapi.config; + +import java.util.Properties; + +import javax.sql.DataSource; + +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +public class DataSourceConfig { + + // private static String host = "localhost:3306"; + // private static String dbpassword = "root"; + // private static String dbpassword = "gccroot"; + + // AWS + private static String host = + "gcc-facial-db-instance-1.cf48eqcciziq.ap-south-1.rds.amazonaws.com:3306"; + private static String dbpassword = "gcc-facial-password"; + + ////////////////////////////// (For GCC APP) //////////////////////// + @Configuration + @EnableTransactionManagement + @EnableJpaRepositories(entityManagerFactoryRef = "appEntityManagerFactory", transactionManagerRef = "appTransactionManager", basePackages = { + "in.gov.chennaicorporation" }) + + public static class MysqlAppDataSourceConfig { + @Primary + @Bean(name = "mysqlAppDataSource") + public DataSource mysqlAppDataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql://" + host + "/gcc_apps"); + dataSource.setUsername("root"); + dataSource.setPassword(dbpassword); + return dataSource; + } + + @Primary + @Bean(name = "appEntityManagerFactory") + public LocalContainerEntityManagerFactoryBean entityManagerFactory( + @Qualifier("mysqlAppDataSource") DataSource dataSource) { + LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); + entityManagerFactoryBean.setDataSource(dataSource); + entityManagerFactoryBean.setPackagesToScan("in.gov.chennaicorporation"); + entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class); + Properties properties = new Properties(); + properties.setProperty("hibernate.hbm2ddl.auto", "update"); + entityManagerFactoryBean.setJpaProperties(properties); + + return entityManagerFactoryBean; + } + + @Primary + @Bean(name = "appTransactionManager") + public PlatformTransactionManager transactionManager( + @Qualifier("appEntityManagerFactory") jakarta.persistence.EntityManagerFactory entityManagerFactory) { + return new JpaTransactionManager(entityManagerFactory); + } + } + + ////////// DBFOT_toilets ////////////////////// + @Bean(name = "mysqldbfottoiletsGccDataSource") + public DataSource mysqldbfottoiletsGccDataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql://" + host + "/dbfot_toilets"); + dataSource.setUsername("root"); + dataSource.setPassword(dbpassword); + return dataSource; + } + +} \ No newline at end of file diff --git a/src/main/java/in/gov/secureapi/config/SecurityConfig.java b/src/main/java/in/gov/secureapi/config/SecurityConfig.java new file mode 100644 index 0000000..27cde88 --- /dev/null +++ b/src/main/java/in/gov/secureapi/config/SecurityConfig.java @@ -0,0 +1,77 @@ +package in.gov.secureapi.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +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; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import in.gov.secureapi.filter.BearerTokenFilter; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.Map; + +@Configuration +public class SecurityConfig { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http, BearerTokenFilter bearerTokenFilter) throws Exception { + http.csrf(csrf -> csrf.disable()) + .formLogin(form -> form.disable()) + .httpBasic(basic -> basic.disable()) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling(ex -> ex + .authenticationEntryPoint((request, response, authException) -> { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + + Map body = new LinkedHashMap<>(); + body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + body.put("status", false); + body.put("statusCode", HttpServletResponse.SC_UNAUTHORIZED); + body.put("error", "Unauthorized"); + body.put("message", "Authentication required. Please provide a valid Bearer token in Authorization header."); + body.put("path", request.getRequestURI()); + + objectMapper.writeValue(response.getOutputStream(), body); + }) + .accessDeniedHandler((request, response, accessDeniedException) -> { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + + Map body = new LinkedHashMap<>(); + body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + body.put("status", false); + body.put("statusCode", HttpServletResponse.SC_FORBIDDEN); + body.put("error", "Forbidden"); + body.put("message", "Access denied: you do not have permission to access this resource."); + body.put("path", request.getRequestURI()); + + objectMapper.writeValue(response.getOutputStream(), body); + }) + ) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/secure/api/auth/token", "/api/auth/token", "/error", "/actuator/health").permitAll() + .requestMatchers("/secure/**", "/api/**").authenticated() + .anyRequest().permitAll()) + .addFilterBefore(bearerTokenFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + } +} diff --git a/src/main/java/in/gov/secureapi/controller/AuthController.java b/src/main/java/in/gov/secureapi/controller/AuthController.java new file mode 100644 index 0000000..fece075 --- /dev/null +++ b/src/main/java/in/gov/secureapi/controller/AuthController.java @@ -0,0 +1,35 @@ +package in.gov.secureapi.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import in.gov.secureapi.service.TokenService; + +import java.util.Map; + +@RestController +@RequestMapping("/secure/api/auth") +public class AuthController { + private final TokenService tokenService; + public AuthController(TokenService tokenService) { this.tokenService = tokenService; } + + @PostMapping("/token") + public ResponseEntity createToken(@Valid @RequestBody TokenRequest request) { + var tokenResult = tokenService.authenticateAndCreate(request.username(), request.password()); + return ResponseEntity.ok(Map.of("status", true, "message", "Token generated successfully", "data", tokenResult)); + } + + @PostMapping("/revoke") + public ResponseEntity revoke(@RequestHeader(value="Authorization", required=false) String header) { + if (header == null || !header.startsWith("Bearer ")) return ResponseEntity.badRequest().body(Map.of("status",false,"message","Bearer token is required")); + tokenService.revoke(header.substring(7).trim()); + return ResponseEntity.ok(Map.of("status",true,"message","Token revoked successfully")); + } + + public record TokenRequest( + @NotBlank(message = "username is required") String username, + @NotBlank(message = "password is required") String password + ) {} +} diff --git a/src/main/java/in/gov/secureapi/controller/CustomerController.java b/src/main/java/in/gov/secureapi/controller/CustomerController.java new file mode 100644 index 0000000..276eb57 --- /dev/null +++ b/src/main/java/in/gov/secureapi/controller/CustomerController.java @@ -0,0 +1,50 @@ +package in.gov.secureapi.controller; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import in.gov.secureapi.repository.CustomerRepository; + +import java.util.Map; + +@RestController +@RequestMapping("/api/customers") +public class CustomerController { + private final CustomerRepository repo; + public CustomerController(CustomerRepository repo) { this.repo = repo; } + + @PostMapping + public ResponseEntity create(@Valid @RequestBody CustomerRequest req) { + long id = repo.insert(req.name(), req.mobile(), req.email(), req.address()); + return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("status",true,"message","Customer created successfully","id",id)); + } + + @GetMapping + public ResponseEntity all() { return ResponseEntity.ok(Map.of("status",true,"data",repo.findAll())); } + + @GetMapping("/{id}") + public ResponseEntity one(@PathVariable long id) { + return repo.findById(id).>map(x -> ResponseEntity.ok(Map.of("status",true,"data",x))) + .orElseGet(() -> ResponseEntity.status(404).body(Map.of("status",false,"message","Customer not found"))); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable long id, @Valid @RequestBody CustomerRequest req) { + int n = repo.update(id, req.name(), req.mobile(), req.email(), req.address()); + if (n == 0) return ResponseEntity.status(404).body(Map.of("status",false,"message","Customer not found")); + return ResponseEntity.ok(Map.of("status",true,"message","Customer updated successfully")); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable long id) { + int n = repo.delete(id); + if (n == 0) return ResponseEntity.status(404).body(Map.of("status",false,"message","Customer not found")); + return ResponseEntity.ok(Map.of("status",true,"message","Customer deleted successfully")); + } + + public record CustomerRequest(@NotBlank String name, String mobile, @Email String email, String address) {} +} diff --git a/src/main/java/in/gov/secureapi/controller/DBFOT_controller.java b/src/main/java/in/gov/secureapi/controller/DBFOT_controller.java new file mode 100644 index 0000000..d76cd2e --- /dev/null +++ b/src/main/java/in/gov/secureapi/controller/DBFOT_controller.java @@ -0,0 +1,113 @@ +package in.gov.secureapi.controller; + + +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import in.gov.secureapi.service.DBFOT_service; + +@RestController +@RequestMapping("/secure/api/dbfotcontroller") +public class DBFOT_controller { + + @Autowired + private DBFOT_service dbfotService; + + @PostMapping("/savecomplaints") + public List savecomplaints( + @RequestParam(value = "zone", required = true) String zone, + @RequestParam(value = "ward", required = true) String ward, + @RequestParam(value = "location", required = true) String location, + @RequestParam(value = "status", required = true) String status, + @RequestParam(value = "address", required = true) String address, + @RequestParam(value = "source", required = true) String source, + @RequestParam(value = "pct", required = true) String pct, + @RequestParam(value = "complaint_id", required = true) String complaint_id, + @RequestParam(value = "description", required = true) String description, + @RequestParam(value = "turn_around_time", required = true) String turn_around_time, + @RequestParam(value = "vendorid", required = true) String vendorid + ) + { + + return dbfotService.savecomplaints(zone,ward,location,status,address,source,pct,complaint_id,description,turn_around_time,vendorid); + + } + + @PostMapping("/savecompshistory") + public List savecomplaints_history( + @RequestParam(value = "complaint_id", required = true) String complaint_id, + @RequestParam(value = "status", required = true) String status, + @RequestParam(value = "complaint_raised_on", required = true) String complaint_raised_on, + @RequestParam(value = "complaint_raised_by", required = true) String complaint_raised_by, + @RequestParam(value = "complaint_updated_on", required = true) String complaint_updated_on, + @RequestParam(value = "complaint_updated_by", required = true) String complaint_updated_by, + @RequestParam(value = "remarks", required = true) String remarks, + @RequestParam(value = "imgfile", required = false) MultipartFile imgfile, + @RequestParam(value = "cby", required = true) String cby + ) + { + + return dbfotService.savecomplaints_history(complaint_id,status,complaint_raised_on,complaint_raised_by,complaint_updated_on,complaint_updated_by,remarks,imgfile,cby); + + } + + @PostMapping("/savemanpower") + public List savemanpower( + @RequestParam(value = "zone", required = true) String zone, + @RequestParam(value = "ward", required = true) String ward, + @RequestParam(value = "category", required = true) String category, + @RequestParam(value = "pct", required = true) String pct, + @RequestParam(value = "status", required = true) String status, + @RequestParam(value = "vendorid", required = true) String vendorid + ) + { + + return dbfotService.savemanpower(zone,ward,category,pct,status,vendorid); + + } + + @PostMapping(value = "/savemanpoweremps", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public List savemanpoweremps( + @RequestParam(value = "pct", required = true) String pct, + @RequestPart(value = "employee_details", required = true) String employee_details, + @RequestParam Map files + ){ + + return dbfotService.savemanpoweremps(pct,employee_details,files); + } + + @PostMapping("/savepctmaster") + public List savepctmaster( + @RequestParam(value="zone",required = true) String zone, + @RequestParam(value="ward",required = true) String ward, + @RequestParam(value="address",required = true) String address, + @RequestParam(value="category",required = true) String category, + @RequestParam(value="pct",required = true) String pct, + @RequestParam(value="status",required = true) String status, + @RequestParam(value="vendorid",required = true) String vendorid + ){ + + return dbfotService.savepctmaster(zone,ward,address,category,pct,status,vendorid); + } + + @PostMapping("/updatepctstatus") + public List updatepctstatus( + @RequestParam(value="pct",required = true) String pct, + @RequestParam(value="status",required = true) String status, + @RequestParam(value="vendorid",required = true) String vendorid + ){ + + return dbfotService.updatepctstatus(pct,status,vendorid); + } + + +} diff --git a/src/main/java/in/gov/secureapi/controller/MainController.java b/src/main/java/in/gov/secureapi/controller/MainController.java new file mode 100644 index 0000000..e068579 --- /dev/null +++ b/src/main/java/in/gov/secureapi/controller/MainController.java @@ -0,0 +1,32 @@ +package in.gov.secureapi.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.Map; + +@RestController +@RequestMapping("/secure") +public class MainController { + + @GetMapping + public ResponseEntity index(Authentication authentication) { + String username = (authentication != null && authentication.isAuthenticated()) + ? authentication.getName() + : "Anonymous"; + + Map response = new LinkedHashMap<>(); + response.put("status", true); + response.put("message", "Secure endpoint accessed successfully"); + response.put("user", username); + response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java b/src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..5bca121 --- /dev/null +++ b/src/main/java/in/gov/secureapi/exception/GlobalExceptionHandler.java @@ -0,0 +1,34 @@ +package in.gov.secureapi.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity validation(MethodArgumentNotValidException ex) { + String message = ex.getBindingResult().getFieldErrors().stream() + .map(e -> e.getField() + ": " + e.getDefaultMessage()).findFirst().orElse("Validation failed"); + return ResponseEntity.badRequest().body(Map.of("status", false, "message", message)); + } + + @ExceptionHandler({BadCredentialsException.class, DisabledException.class}) + public ResponseEntity authenticationError(Exception ex) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(Map.of("status", false, "message", ex.getMessage())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity other(Exception ex) { + ex.printStackTrace(); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(Map.of("status", false, "message", "Internal server error: " + ex.getMessage())); + } +} diff --git a/src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java b/src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java new file mode 100644 index 0000000..44b5951 --- /dev/null +++ b/src/main/java/in/gov/secureapi/filter/BearerTokenFilter.java @@ -0,0 +1,81 @@ +package in.gov.secureapi.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import in.gov.secureapi.service.TokenService; +import in.gov.secureapi.service.TokenService.TokenStatus; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.Map; + +@Component +public class BearerTokenFilter extends OncePerRequestFilter { + private final TokenService tokenService; + private final ObjectMapper objectMapper = new ObjectMapper(); + + public BearerTokenFilter(TokenService tokenService) { + this.tokenService = tokenService; + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getRequestURI(); + return path.startsWith("/secure/api/auth/token") || path.startsWith("/api/auth/token") || path.startsWith("/error") || path.startsWith("/actuator"); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + String header = request.getHeader("Authorization"); + + if (header != null && header.startsWith("Bearer ")) { + String token = header.substring(7).trim(); + if (!token.isBlank()) { + var validation = tokenService.validateDetailed(token); + + if (validation.status() == TokenStatus.VALID) { + var auth = new UsernamePasswordAuthenticationToken(validation.info().username(), null, AuthorityUtils.NO_AUTHORITIES); + SecurityContextHolder.getContext().setAuthentication(auth); + } else { + sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized", validation.message(), request.getRequestURI()); + return; + } + } else { + sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized", "Bearer token is empty", request.getRequestURI()); + return; + } + } + + chain.doFilter(request, response); + } + + private void sendErrorResponse(HttpServletResponse response, int statusCode, String error, String message, String path) + throws IOException { + response.setStatus(statusCode); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + + Map body = new LinkedHashMap<>(); + body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + body.put("status", false); + body.put("statusCode", statusCode); + body.put("error", error); + body.put("message", message); + body.put("path", path); + + objectMapper.writeValue(response.getOutputStream(), body); + } +} diff --git a/src/main/java/in/gov/secureapi/repository/CustomerRepository.java b/src/main/java/in/gov/secureapi/repository/CustomerRepository.java new file mode 100644 index 0000000..fad08be --- /dev/null +++ b/src/main/java/in/gov/secureapi/repository/CustomerRepository.java @@ -0,0 +1,30 @@ +package in.gov.secureapi.repository; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Repository +public class CustomerRepository { + private final JdbcTemplate jdbc; + public CustomerRepository(JdbcTemplate jdbc) { this.jdbc = jdbc; } + + public long insert(String name, String mobile, String email, String address) { + jdbc.update("INSERT INTO customers(name,mobile,email,address) VALUES (?,?,?,?)", name, mobile, email, address); + Long id = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class); + return id; + } + public List> findAll() { + return jdbc.queryForList("SELECT id,name,mobile,email,address,created_at,updated_at FROM customers ORDER BY id DESC"); + } + public Optional> findById(long id) { + var list = jdbc.queryForList("SELECT id,name,mobile,email,address,created_at,updated_at FROM customers WHERE id=?", id); + return list.stream().findFirst(); + } + public int update(long id, String name, String mobile, String email, String address) { + return jdbc.update("UPDATE customers SET name=?,mobile=?,email=?,address=? WHERE id=?", name,mobile,email,address,id); + } + public int delete(long id) { return jdbc.update("DELETE FROM customers WHERE id=?", id); } +} diff --git a/src/main/java/in/gov/secureapi/repository/TokenRepository.java b/src/main/java/in/gov/secureapi/repository/TokenRepository.java new file mode 100644 index 0000000..0824788 --- /dev/null +++ b/src/main/java/in/gov/secureapi/repository/TokenRepository.java @@ -0,0 +1,47 @@ +package in.gov.secureapi.repository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Optional; +import javax.sql.DataSource; + +@Repository +public class TokenRepository { + private JdbcTemplate jdbc; + + @Autowired + public void setDataSource(@Qualifier("mysqldbfottoiletsGccDataSource") DataSource DBFOTTOILETSSource) { + this.jdbc = new JdbcTemplate(DBFOTTOILETSSource); + } + + public void save(String username, String tokenHash, LocalDateTime expiresAt) { + // Step 1: Deactivate any previous active tokens for this client + jdbc.update("UPDATE api_users SET active = FALSE WHERE username = ? AND active = TRUE", username); + + // Step 2: Insert the newly generated active token + jdbc.update("INSERT INTO api_users(username, token_hash, expires_at, active) VALUES (?, ?, ?, TRUE)", + username, tokenHash, Timestamp.valueOf(expiresAt)); + } + + public Optional findByTokenHash(String tokenHash) { + var list = jdbc.query("SELECT username, expires_at, active FROM api_users WHERE token_hash=? ORDER BY id DESC LIMIT 1", + (rs, row) -> new TokenInfo(rs.getString("username"), rs.getTimestamp("expires_at").toLocalDateTime(), rs.getBoolean("active")), tokenHash); + return list.stream().findFirst(); + } + + public Optional findValid(String tokenHash) { + return findByTokenHash(tokenHash) + .filter(info -> info.active() && info.expiresAt().isAfter(LocalDateTime.now())); + } + + public void revoke(String tokenHash) { + jdbc.update("UPDATE api_users SET active=FALSE WHERE token_hash=?", tokenHash); + } + + public record TokenInfo(String username, LocalDateTime expiresAt, boolean active) {} +} diff --git a/src/main/java/in/gov/secureapi/repository/UserRepository.java b/src/main/java/in/gov/secureapi/repository/UserRepository.java new file mode 100644 index 0000000..90f6307 --- /dev/null +++ b/src/main/java/in/gov/secureapi/repository/UserRepository.java @@ -0,0 +1,55 @@ +package in.gov.secureapi.repository; + +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.Optional; + +@Repository +public class UserRepository { + + private JdbcTemplate jdbc; + + @Autowired + public void setDataSource(@Qualifier("mysqldbfottoiletsGccDataSource") DataSource dbfotToiletsSource) { + this.jdbc = new JdbcTemplate(dbfotToiletsSource); + } + + @PostConstruct + public void init() { + try { + jdbc.execute(""" + CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(100) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + active TINYINT(1) NOT NULL DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) + """); + } catch (Exception e) { + // Log but don't fail if table or permissions already established + } + } + + public Optional findByUsername(String username) { + var list = jdbc.query( + "SELECT id, username, password, active FROM users WHERE username = ? LIMIT 1", + (rs, rowNum) -> new UserRecord( + rs.getLong("id"), + rs.getString("username"), + rs.getString("password"), + rs.getBoolean("active") + ), + username + ); + return list.stream().findFirst(); + } + + public record UserRecord(long id, String username, String password, boolean active) {} +} diff --git a/src/main/java/in/gov/secureapi/service/DBFOT_service.java b/src/main/java/in/gov/secureapi/service/DBFOT_service.java new file mode 100644 index 0000000..eb8bc9f --- /dev/null +++ b/src/main/java/in/gov/secureapi/service/DBFOT_service.java @@ -0,0 +1,712 @@ +package in.gov.secureapi.service; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.SecureRandom; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.env.Environment; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +@Service +public class DBFOT_service { + + private JdbcTemplate jdbcTemplate; + + private final Environment environment; + private String fileBaseUrl; + + private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private static final int STRING_LENGTH = 15; + private static final Random RANDOM = new SecureRandom(); + + @Autowired + public void setDataSource(@Qualifier("mysqldbfottoiletsGccDataSource") DataSource DBFOTTOILETSSource) { + this.jdbcTemplate = new JdbcTemplate(DBFOTTOILETSSource); + } + + @Autowired + public DBFOT_service(Environment environment) { + this.environment = environment; + this.fileBaseUrl = environment.getProperty("fileBaseUrl"); + } + + public static String generateRandomString() { + StringBuilder result = new StringBuilder(STRING_LENGTH); + for (int i = 0; i < STRING_LENGTH; i++) { + result.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length()))); + } + return result.toString(); + } + + public static String generateRandomFileString(int lenthval) { + StringBuilder result = new StringBuilder(lenthval); + for (int i = 0; i < lenthval; i++) { + result.append(CHARACTERS.charAt(RANDOM.nextInt(CHARACTERS.length()))); + } + return result.toString(); + } + + private byte[] compressImage(BufferedImage image, float quality) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(byteArrayOutputStream); + + ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); + writer.setOutput(imageOutputStream); + + ImageWriteParam param = writer.getDefaultWriteParam(); + if (param.canWriteCompressed()) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(quality); + } + + writer.write(null, new javax.imageio.IIOImage(image, null, null), param); + + writer.dispose(); + imageOutputStream.close(); + + return byteArrayOutputStream.toByteArray(); + } + + public String fileUpload(MultipartFile file, String name) { + + // Set the file path where you want to save it + String uploadDirectory = environment.getProperty("file.upload.directory"); + String serviceFolderName = environment.getProperty("dbfot_toilets"); + var year = DateTimeUtil.getCurrentYear(); + var month = DateTimeUtil.getCurrentMonth(); + + uploadDirectory = uploadDirectory + serviceFolderName + year + "/" + month; + + try { + // Create directory if it doesn't exist + Path directoryPath = Paths.get(uploadDirectory); + if (!Files.exists(directoryPath)) { + Files.createDirectories(directoryPath); + } + + // Datetime string + String datetimetxt = DateTimeUtil.getCurrentDateTime(); + + // File name + String fileName = name + "_" + datetimetxt + "_" + file.getOriginalFilename(); + fileName = fileName.replaceAll("\\s+", ""); // Remove space on filename + + String filePath = uploadDirectory + "/" + fileName; + + String filepath_txt = "/" + serviceFolderName + year + "/" + month + "/" + fileName; + + // Create a new Path object + Path path = Paths.get(filePath); + + // Get the bytes of the file + byte[] bytes = file.getBytes(); + + String originalFileName = file.getOriginalFilename(); + String extension = originalFileName.substring(originalFileName.lastIndexOf('.') + 1).toLowerCase(); + + if ("jpg".equals(extension) || "jpeg".equals(extension)) { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes)); + byte[] compressedBytes = compressImage(image, 0.5f); // Compress with 50% quality + + // Write the compressed bytes to the file + Files.write(path, compressedBytes); + } else { + // Write the original bytes to the file + Files.write(path, bytes); + } + + //System.out.println(filePath); + return filepath_txt; + + } catch (IOException e) { + e.printStackTrace(); + return "Failed to save file " + file.getOriginalFilename(); + } + } + + public List savecomplaints(String zone, String ward, String location, String status, String address, + String source, String pct, String complaint_id, String description, String turn_around_time, + String vendorid) { + + Map response = new HashMap<>(); + + try { + + + String checkSql = + "SELECT id, zone, ward, location, status, address, source, pct, " + + "complaint_id, description, turn_around_time, vendorid, " + + "created_date, isactive, isdelete " + + "FROM complaints_details " + + "WHERE complaint_id = ? " + + "AND isactive = 1 " + + "AND isdelete = 0 "; + + List> complaintList = + jdbcTemplate.queryForList(checkSql, complaint_id); + + + if (complaintList.isEmpty()) { + + String saveSql = "INSERT INTO complaints_details (zone,ward,location,status,address,source,pct,complaint_id,description,turn_around_time,vendorid) " + + "VALUES (?,?,?,?,?,?,?,?,?,?,?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); + int affectedRows = jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(saveSql, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, zone); + ps.setString(2, ward); + ps.setString(3, location); + ps.setString(4, status); + ps.setString(5, address); + ps.setString(6, source); + ps.setString(7, pct); + ps.setString(8, complaint_id); + ps.setString(9, description); + ps.setString(10, turn_around_time); + ps.setString(11, vendorid); + return ps; + }, keyHolder); + + if (affectedRows > 0) { + response.put("status", "Success"); + response.put("message", "Complaints Saved Successfully"); + } else { + response.put("status", "Failed"); + response.put("message", "Error while saving complaints"); + } + } + + else { + + response.put("status", "Failed"); + response.put("message", + "Duplicate Complaint ID " + complaint_id + + " already found in complaints_details"); + + return Collections.singletonList(response); + } + + + + + } catch (Exception e) { + e.printStackTrace(); + response.put("status", "Error"); + response.put("message", "Error while saving complaints"); + } + + return Collections.singletonList(response); + } + + + @Transactional + public List savecomplaints_history(String complaint_id, String status, String complaint_raised_on, + String complaint_raised_by, String complaint_updated_on, String complaint_updated_by, String remarks, + MultipartFile imgfile, String cby) { + + Map response = new HashMap<>(); + + try { + + String checkSql = + "SELECT id, zone, ward, location, status, address, source, pct, " + + "complaint_id, description, turn_around_time, vendorid, " + + "created_date, isactive, isdelete " + + "FROM complaints_details " + + "WHERE complaint_id = ? " + + "AND isactive = 1 " + + "AND isdelete = 0 " + + "FOR UPDATE"; + + List> complaintList = + jdbcTemplate.queryForList(checkSql, complaint_id); + + + if (complaintList.isEmpty()) { + + response.put("status", "Failed"); + response.put("message", + "Complaint ID " + complaint_id + + " not found in complaints_details"); + + return Collections.singletonList(response); + } + + Map complaint = complaintList.get(0); + + String imgFileValue = null; + + if (imgfile != null && !imgfile.isEmpty()) { + + + imgFileValue = fileUpload(imgfile, "dbfot_toilet_img"); + } + + String historySql = + "INSERT INTO complaints_history " + + "(complaint_id, status, complaint_raised_on, complaint_raised_by, " + + "complaint_updated_on, complaint_updated_by, remarks, imgfile, cby) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + + int historyAffectedRows = jdbcTemplate.update( + historySql, + complaint_id, + status, + complaint_raised_on, + complaint_raised_by, + complaint_updated_on, + complaint_updated_by, + remarks, + imgFileValue, + cby + ); + + if (historyAffectedRows <= 0) { + + response.put("status", "Failed"); + response.put("message","Unable to save complaint history"); + + return Collections.singletonList(response); + } + + + String logSql = + "INSERT INTO complaints_details_log " + + "(cd_id, zone, ward, location, status, address, source, pct, " + + "complaint_id, description, turn_around_time, vendorid, " + + "created_date, isactive, isdelete) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + + int logAffectedRows = jdbcTemplate.update( + logSql, + + complaint.get("id"), + complaint.get("zone"), + complaint.get("ward"), + complaint.get("location"), + complaint.get("status"), + complaint.get("address"), + complaint.get("source"), + complaint.get("pct"), + complaint.get("complaint_id"), + complaint.get("description"), + complaint.get("turn_around_time"), + complaint.get("vendorid"), + complaint.get("created_date"), + complaint.get("isactive"), + complaint.get("isdelete") + ); + + if (logAffectedRows > 0) { + + String updateSql = + "UPDATE complaints_details " + + "SET status = ? " + + "WHERE complaint_id = ? " + + "AND isactive = 1 " + + "AND isdelete = 0"; + + + int updateAffectedRows = jdbcTemplate.update( + updateSql, + status, + complaint_id + ); + + if (updateAffectedRows > 0) { + + response.put("status", "Success"); + response.put("message", + "Complaint history saved and complaint status updated successfully"); + + } else { + + /* + * Throw exception so @Transactional rolls everything back. + */ + throw new RuntimeException( + "Unable to update complaint status" + ); + } + + } else { + + /* + * Throw exception so history insert is also rolled back. + */ + throw new RuntimeException( + "Unable to save complaint details log" + ); + } + + + + } catch (Exception e) { + + e.printStackTrace(); + response.put("status", "Error"); + response.put("message", "Error while saving complaints history"); + + } + + return Collections.singletonList(response); + } + + public List savemanpower(String zone, String ward, String category, String pct, String status,String vendorid) { + + Map response = new HashMap<>(); + + try { + + String checkSql = + "SELECT * " + + "FROM man_power_details " + + "WHERE pct = ? " + + "AND isactive = 1 " + + "AND isdelete = 0 "; + + List> complaintList = + jdbcTemplate.queryForList(checkSql, pct); + + if (complaintList.isEmpty()) { + + + String saveSql = "INSERT INTO man_power_details (zone,ward,category,pct,status,vendorid) " + + "VALUES (?,?,?,?,?,?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); + int affectedRows = jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(saveSql, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, zone); + ps.setString(2, ward); + ps.setString(3, category); + ps.setString(4, pct); + ps.setString(5, status); + ps.setString(6, vendorid); + return ps; + }, keyHolder); + + if (affectedRows > 0) { + response.put("status", "Success"); + response.put("message", "Manpower details Saved Successfully"); + } else { + response.put("status", "Failed"); + response.put("message", "Error while saving man power details"); + } + + } + else { + response.put("status", "Failed"); + response.put("message","Duplicate PCT " + pct +" already found in man_power_details"); + + return Collections.singletonList(response); + } + + } catch (Exception e) { + e.printStackTrace(); + response.put("status", "Error"); + response.put("message", "Error while saving manpower details"); + } + + return Collections.singletonList(response); + } + + @Transactional + public List savemanpoweremps(String pct, String employee_details,Map files) { + + Map response = new HashMap<>(); + + try { + + + String checkSql = + "SELECT * " + + "FROM man_power_details " + + "WHERE pct = ? " + + "AND isactive = 1 " + + "AND isdelete = 0 "; + + List> complaintList = + jdbcTemplate.queryForList(checkSql, pct); + + if (complaintList.isEmpty()) { + + response.put("status", "Failed"); + response.put("message","PCT " + pct +" not found in man_power_details"); + + return Collections.singletonList(response); + } + else { + ObjectMapper objectMapper = new ObjectMapper(); + + List> employeeList = + objectMapper.readValue( + employee_details, + new TypeReference>>() {} + ); + + if (employeeList == null || employeeList.isEmpty()) { + + response.put("status", "Failed"); + response.put("message", "Manpower Employee details cannot be empty"); + + return Collections.singletonList(response); + } + + String saveSql = + "INSERT INTO man_power_employees " + + "(pct, emp_name, att_date, location, mobile_number, imgfile) " + + "VALUES (?, ?, ?, ?, ?, ?)"; + + + int totalSaved = 0; + + for (Map employee : employeeList) { + + String empName =employee.get("emp_name") != null ? employee.get("emp_name").toString(): null; + + String attDate =employee.get("att_date") != null ? employee.get("att_date").toString() : null; + + String location =employee.get("location") != null ? employee.get("location").toString(): null; + + String mobileNumber =employee.get("mobile_number") != null ? employee.get("mobile_number").toString() : null; + + String fileKey =employee.get("fileKey") != null ? employee.get("fileKey").toString() : null; + + MultipartFile file = null; + String filePath = null; + + if (fileKey != null && !fileKey.trim().isEmpty()) { + + file = files.get(fileKey); + + if (file != null && !file.isEmpty()) { + + filePath = fileUpload(file, "dbfot_manpower_img"); + } + } + + int affectedRows = jdbcTemplate.update(saveSql,pct,empName,attDate,location,mobileNumber, filePath + ); + + if (affectedRows > 0) { + + totalSaved++; + + } else { + + throw new RuntimeException("Failed to save employee: " + empName); + } + } + + response.put("status", "Success"); + response.put("message","Man power employees saved successfully"); + response.put("count", totalSaved); + } + + + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error while saving man power employees", e); + } + + return Collections.singletonList(response); + } + + public List savepctmaster(String zone, String ward, String address, String category, String pct, + String status, String vendorid) { + + Map response = new HashMap<>(); + + try { + + String checkSql = + "SELECT * " + + "FROM pct_master " + + "WHERE pct = ? " + + "AND isactive = 1 " + + "AND isdelete = 0 "; + + List> complaintList = + jdbcTemplate.queryForList(checkSql, pct); + + if (complaintList.isEmpty()) { + + + String saveSql = "INSERT INTO pct_master (zone,ward,address,category,pct,status,vendorid) " + + "VALUES (?,?,?,?,?,?,?)"; + + KeyHolder keyHolder = new GeneratedKeyHolder(); + int affectedRows = jdbcTemplate.update(connection -> { + PreparedStatement ps = connection.prepareStatement(saveSql, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, zone); + ps.setString(2, ward); + ps.setString(3, address); + ps.setString(4, category); + ps.setString(5, pct); + ps.setString(6, status); + ps.setString(7, vendorid); + return ps; + }, keyHolder); + + if (affectedRows > 0) { + response.put("status", "Success"); + response.put("message","PCT Master Saved Successfully"); + } else { + response.put("status", "Failed"); + response.put("message", "Error while saving pct master"); + } + + } + else { + response.put("status", "Failed"); + response.put("message","Duplicate PCT " + pct +" already found in pct master"); + + return Collections.singletonList(response); + } + + } catch (Exception e) { + e.printStackTrace(); + response.put("status", "Error"); + response.put("message", "Error while saving pct master"); + } + + + return Collections.singletonList(response); + } + + public List updatepctstatus(String pct, String status, String vendorid) { + + Map response = new HashMap<>(); + + try { + + String checkSql = + "SELECT id, zone, ward,address,category,pct,status, " + + "vendorid,created_date, isactive, isdelete " + + "FROM pct_master " + + "WHERE pct = ? " + + "AND isactive = 1 " + + "AND isdelete = 0 " + + "FOR UPDATE"; + + List> complaintList = + jdbcTemplate.queryForList(checkSql, pct); + + + if (complaintList.isEmpty()) { + + response.put("status", "Failed"); + response.put("message","PCT " + pct + " not found in pct_master"); + + return Collections.singletonList(response); + } + + Map complaint = complaintList.get(0); + + String logSql = + "INSERT INTO pct_master_log " + + "(pct_master_id, zone, ward, address,category,pct, status, " + + "vendorid,created_date, isactive, isdelete) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + + int logAffectedRows = jdbcTemplate.update( + logSql, + + complaint.get("id"), + complaint.get("zone"), + complaint.get("ward"), + complaint.get("address"), + complaint.get("category"), + complaint.get("pct"), + complaint.get("status"), + complaint.get("vendorid"), + complaint.get("created_date"), + complaint.get("isactive"), + complaint.get("isdelete") + + ); + + if (logAffectedRows > 0) { + + String updateSql = + "UPDATE pct_master " + + "SET status = ? " + + "WHERE pct = ? " + + "AND isactive = 1 " + + "AND isdelete = 0"; + + + int updateAffectedRows = jdbcTemplate.update( + updateSql, + status, + pct + ); + + if (updateAffectedRows > 0) { + + response.put("status", "Success"); + response.put("message","PCT status updated successfully"); + + } else { + + /* + * Throw exception so @Transactional rolls everything back. + */ + throw new RuntimeException( + "Unable to update pct status" + ); + } + + } else { + + /* + * Throw exception so history insert is also rolled back. + */ + throw new RuntimeException( + "Unable to save in pct master log" + ); + } + + + + } catch (Exception e) { + e.printStackTrace(); + response.put("status", "Error"); + response.put("message", "Error while updating pct master"); + } + + return Collections.singletonList(response); + } + +} diff --git a/src/main/java/in/gov/secureapi/service/DateTimeUtil.java b/src/main/java/in/gov/secureapi/service/DateTimeUtil.java new file mode 100644 index 0000000..83816ff --- /dev/null +++ b/src/main/java/in/gov/secureapi/service/DateTimeUtil.java @@ -0,0 +1,34 @@ +package in.gov.secureapi.service; + +import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Service +public class DateTimeUtil { + public static String getCurrentDateTime() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ddMMyyyyHHmmss"); + LocalDateTime now = LocalDateTime.now(); + return now.format(formatter); + } + public static String getCurrentYear() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy"); + LocalDateTime now = LocalDateTime.now(); + return now.format(formatter); + } + public static String getCurrentMonth() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM"); + LocalDateTime now = LocalDateTime.now(); + return now.format(formatter); + } + public static String getCurrentDay() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd"); + LocalDateTime now = LocalDateTime.now(); + return now.format(formatter); + } + public static String getCurrentDateTimeMysql() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime now = LocalDateTime.now(); + return now.format(formatter); + } +} diff --git a/src/main/java/in/gov/secureapi/service/TokenService.java b/src/main/java/in/gov/secureapi/service/TokenService.java new file mode 100644 index 0000000..4345094 --- /dev/null +++ b/src/main/java/in/gov/secureapi/service/TokenService.java @@ -0,0 +1,97 @@ +package in.gov.secureapi.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import in.gov.secureapi.repository.TokenRepository; +import in.gov.secureapi.repository.UserRepository; +import in.gov.secureapi.util.TokenUtil; + +import java.time.LocalDateTime; + +@Service +public class TokenService { + private final TokenRepository repo; + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final long ttlHours; + + public TokenService( + TokenRepository repo, + UserRepository userRepository, + PasswordEncoder passwordEncoder, + @Value("${api.token.ttl-hours:24}") long ttlHours) { + this.repo = repo; + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.ttlHours = ttlHours; + } + + public TokenResult authenticateAndCreate(String username, String rawPassword) { + var userOpt = userRepository.findByUsername(username); + if (userOpt.isEmpty()) { + throw new BadCredentialsException("Invalid username or password"); + } + + var user = userOpt.get(); + if (!user.active()) { + throw new DisabledException("User account is inactive"); + } + + boolean matches = passwordEncoder.matches(rawPassword, user.password()); + if (!matches && rawPassword.equals(user.password())) { + matches = true; + } + + if (!matches) { + throw new BadCredentialsException("Invalid username or password"); + } + + return create(user.username()); + } + + public TokenResult create(String username) { + String token = TokenUtil.generateToken(); + LocalDateTime expires = LocalDateTime.now().plusHours(ttlHours); + repo.save(username, TokenUtil.sha256(token), expires); + return new TokenResult(token, username, expires); + } + public enum TokenStatus { + VALID, + EXPIRED, + INACTIVE, + INVALID + } + + public record TokenValidation(TokenStatus status, TokenRepository.TokenInfo info, String message) {} + + public TokenValidation validateDetailed(String token) { + if (token == null || token.isBlank()) { + return new TokenValidation(TokenStatus.INVALID, null, "Bearer token is missing or empty"); + } + String tokenHash = TokenUtil.sha256(token); + var tokenOpt = repo.findByTokenHash(tokenHash); + if (tokenOpt.isEmpty()) { + return new TokenValidation(TokenStatus.INVALID, null, "Invalid token: token not found"); + } + var info = tokenOpt.get(); + if (!info.active()) { + return new TokenValidation(TokenStatus.INACTIVE, info, "Token is inactive or has been revoked. Please authenticate again."); + } + if (!info.expiresAt().isAfter(LocalDateTime.now())) { + return new TokenValidation(TokenStatus.EXPIRED, info, "Token has expired. Please authenticate again to generate a new token."); + } + return new TokenValidation(TokenStatus.VALID, info, "Token is valid"); + } + + public TokenRepository.TokenInfo validate(String token) { + var validation = validateDetailed(token); + return validation.status() == TokenStatus.VALID ? validation.info() : null; + } + + public void revoke(String token) { repo.revoke(TokenUtil.sha256(token)); } + public record TokenResult(String token, String username, LocalDateTime expiresAt) {} +} diff --git a/src/main/java/in/gov/secureapi/util/TokenUtil.java b/src/main/java/in/gov/secureapi/util/TokenUtil.java new file mode 100644 index 0000000..ff40aee --- /dev/null +++ b/src/main/java/in/gov/secureapi/util/TokenUtil.java @@ -0,0 +1,30 @@ +package in.gov.secureapi.util; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +public final class TokenUtil { + private static final SecureRandom RANDOM = new SecureRandom(); + private TokenUtil() {} + + public static String generateToken() { + byte[] bytes = new byte[32]; + RANDOM.nextBytes(bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } + + public static String sha256(String value) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(64); + for (byte b : digest) sb.append(String.format("%02x", b)); + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("SHA-256 unavailable", e); + } + } +} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 0000000..87e4da6 --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,3 @@ +assets.base-url=/assets/ + +fileBaseUrl=http://localhost:8088 \ No newline at end of file diff --git a/src/main/resources/application-production.properties b/src/main/resources/application-production.properties new file mode 100644 index 0000000..029057f --- /dev/null +++ b/src/main/resources/application-production.properties @@ -0,0 +1,3 @@ +assets.base-url=/gccofficialapp/assets/ + +fileBaseUrl=https://gccservices.in \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..7eeb759 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,67 @@ +# spring.profiles.active=local +spring.profiles.active=production + +spring.application.name= + +# Enable live reload (default is true) +spring.devtools.livereload.enabled=true + +# Enable restart (default is true) +spring.devtools.restart.enabled=true + +# HTTP Server +server.port = 8088 + +server.error.include-stacktrace= on_param + +# Hibernate logging +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE + +spring.jackson.time-zone=Asia/Kolkata + +# Maximum file size +spring.servlet.multipart.max-file-size=20MB + +# Maximum request size (including all files) +spring.servlet.multipart.max-request-size=120MB + +spring.mvc.favicon.enabled=false + +file.upload.directory=${user.dir}/secureapi_upload/ +dbfot_toilets = dbfot_toilets + +# =============================== + +# JPA (if using JPA) + +# =============================== + +spring.jpa.hibernate.ddl-auto=none + +spring.jpa.show-sql=false + +spring.jpa.properties.hibernate.format_sql=false + +# =============================== + +# API Token + +# Default = 24 hours + +# =============================== + +api.token.ttl-hours=24 + +# =============================== + +# Logging + +# =============================== + +logging.level.root=INFO + +logging.level.in.gccmanpower=INFO + + + diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..e69de29